Preliminary work on collisions

This commit is contained in:
Safariminer 2025-08-05 23:33:34 -04:00
parent f35ff8ae46
commit 65031d57f3
10 changed files with 284 additions and 37 deletions

View File

@ -8,4 +8,6 @@
- raylib
- boost::asio
- imgui
- rlimgui
- rlimgui
Note: different backend support is planned for Vulkan and whatnot. raylib's raymath component will still be necessary for physics maths.

View File

@ -0,0 +1,3 @@
<collparam>
</collparam>

View File

@ -1,4 +1,5 @@
math_3d_floor_collision_work false
math_3d_collision_work false
vid_2d_background false
vid_3d_renderer true
vid_3d_grid false

150
mpfw/MPFW_Physics.cpp Normal file
View File

@ -0,0 +1,150 @@
#include "MPFW_Physics.h"
#include <algorithm>
#include <print>
#include <thread>
#include <mutex>
// simple aabb
bool __collides(MPFW::Physics::Box a, MPFW::Physics::Box b) {
return
a.min.x <= b.max.x &&
a.max.x >= b.min.x &&
a.min.y <= b.max.y &&
a.max.y >= b.min.y &&
a.min.z <= b.max.z &&
a.max.z >= b.min.z;
}
MPFW::Physics::Box MPFW::Physics::GenerateBoundingBoxPolygon(Polygon poly)
{
Polygon polyCopy = poly;
std::vector<float> x;
std::vector<float> y;
std::vector<float> z;
for (int i = 0; i < polyCopy.size(); i++) {
x.push_back(polyCopy[i].x);
y.push_back(polyCopy[i].y);
z.push_back(polyCopy[i].z);
}
std::sort(x.begin(), x.end());
std::sort(y.begin(), y.end());
std::sort(z.begin(), z.end());
Box retval;
retval.min.x = x[0];
retval.min.y = y[0];
retval.min.z = z[0];
retval.max.x = x[x.size()-1];
retval.max.y = y[y.size()-1];
retval.max.z = z[z.size()-1];
return retval;
}
std::mutex collSetMutex;
void PolygonCalcThread(MPFW::Physics::CollisionSet* collSet, std::vector<MPFW::Physics::CollisionFace> pStructs, int v) {
for (int p = 0; p < pStructs.size(); p++) {
if (collSet->collisionFaces.size() - 1 != p) {
collSet->collisionFaces = std::vector<MPFW::Physics::CollisionFace>(0);
p = 0;
}
if (__collides(MPFW::Physics::GenerateBoundingBoxPolygon(pStructs[p].polygon), collSet->size)) {
std::lock_guard<std::mutex> guard(collSetMutex);
collSet->collisionFaces.push_back(pStructs[p]);
}
std::print("v:{},p:{}\n", v, p);
}
}
MPFW::Physics::CollisionMap MPFW::Physics::GenerateCollisionMap(std::vector<Polygon> polygons, std::vector<Vector3> totalVertices, CollisionMapGenParams cmgp)
{
std::vector<Polygon> polygonsCopy = polygons;
std::vector<std::thread> polygonCalculationsThreads;
std::vector<CollisionFace> pStructs(polygons.size());
for (int cFCount = 0; cFCount < polygons.size(); cFCount++) {
CollisionFace cface;
cface.polygon = polygonsCopy[cFCount]; // assuming polygonsCopy == polygons, because it should be.
// calculating normals (if polygon has 3 vertices or more)
if(cface.polygon.size() >= 3){
// according to: https://titan.csit.rmit.edu.au/~e20068/teaching/i3dg&a/2012/normals/normals.xhtml
Vector3 a, b;
a = Vector3Subtract(cface.polygon[1], cface.polygon[0]);
b = Vector3Subtract(cface.polygon[2], cface.polygon[0]);
cface.normal = Vector3Normalize(Vector3CrossProduct(a,b)); // does that actually work? I can't believe it would
cface.normalCalculated = true;
}
pStructs[cFCount] = cface;
}
Box mapBoundingBox = GenerateBoundingBoxPolygon(totalVertices);
Vector3 sizeOfMap = mapBoundingBox.max - mapBoundingBox.min;
Vector3 sizeOfVoxel = sizeOfMap / cmgp.voxelLateralCount;
CollisionMap retval;
int vCount = 0;
for (int z = 0; z < cmgp.voxelLateralCount; z++) {
for (int y = 0; y < cmgp.voxelLateralCount; y++) {
for (int x = 0; x < cmgp.voxelLateralCount; x++) {
CollisionSet cs;
cs.size.min =
{
mapBoundingBox.min.x + sizeOfVoxel.x * x,
mapBoundingBox.min.y + sizeOfVoxel.y * y,
mapBoundingBox.min.z + sizeOfVoxel.z * z
};
cs.size.max =
{
mapBoundingBox.min.x + sizeOfVoxel.x * (x + 1),
mapBoundingBox.min.y + sizeOfVoxel.y * (y + 1),
mapBoundingBox.min.z + sizeOfVoxel.z * (z + 1)
};
cs.collisionFaces = std::vector<CollisionFace>(0);
retval.collisionSets.push_back(cs);
// polygonCalculationsThreads.push_back(std::thread(PolygonCalcThread, &retval.collisionSets[vCount], pStructs, vCount));
for(int p = 0; p < pStructs.size(); p++){
if (__collides(MPFW::Physics::GenerateBoundingBoxPolygon(pStructs[p].polygon), retval.collisionSets[vCount].size)) {
// std::lock_guard<std::mutex> guard(collSetMutex);
retval.collisionSets[vCount].collisionFaces.push_back(pStructs[p]);
}
std::print("v:{},p:{}\n", vCount, p);
}
vCount++;
}
}
}
/*for (int i = 0; i < polygonCalculationsThreads.size(); i++) {
if(polygonCalculationsThreads[i].joinable())polygonCalculationsThreads[i].join();
}*/
return retval;
}

34
mpfw/MPFW_Physics.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <raymath.h>
#include <iostream>
#include <vector>
namespace MPFW {
namespace Physics {
struct Box {
Vector3 min, max;
};
using Polygon = std::vector<Vector3>;
struct CollisionFace {
Polygon polygon;
Vector3 normal;
bool normalCalculated = false;
};
struct CollisionSet {
Box size;
std::vector<CollisionFace> collisionFaces;
};
struct CollisionMapGenParams {
int voxelLateralCount = 3;
};
// collision maps subdivide the maps for collisions
struct CollisionMap {
std::vector<CollisionSet> collisionSets;
};
Box GenerateBoundingBoxPolygon(Polygon poly);
CollisionMap GenerateCollisionMap(std::vector<Polygon> polygons, std::vector<Vector3> totalVertices, CollisionMapGenParams cmgp);
}
}

View File

@ -22,9 +22,9 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
sstr << fileHandle.rdbuf(); // as fast as possible
buffer = sstr.str(); // 3 LINES [WR SPEEDRUN]
// operating on files as a string or a char array is simpler
// than writing some fancy << fuckery (even though it is nice
// to see some << fuckery work without any moving parts)
// operating on files as a string or a char array is simpler
// than writing some fancy << fuckery (even though it is nice
// to see some << fuckery work without any moving parts)
}
else {
@ -35,15 +35,15 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
}
fileHandle.close(); // close it as soon as we can get file loaded
// as to not disturb other programs
// as to not disturb other programs
// we can now start the parsing
// hehehe >:3
// we can now start the parsing
// hehehe >:3
// if file is smaller than header
// if file is smaller than header
if (buffer.size() < 124) {
std::print("Bad file content size.\n");
throw std::runtime_error("File is smaller than header. Can't parse.");
@ -58,7 +58,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
data.header.version += buffer[3] << 24;
std::vector<DirectoryEntry> vecdir;
@ -67,7 +67,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
for (int i = 4; i < 123; i += 8) {
DirectoryEntry nd{0,0}; // new directory;
DirectoryEntry nd{ 0,0 }; // new directory;
nd.offset = Utils::Strings::StringIndexToInteger_4b_le(buffer, i);
nd.size = Utils::Strings::StringIndexToInteger_4b_le(buffer, i + 4);
@ -105,20 +105,20 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
// Loading Models
// we're loading models first because that's
// what the spec does <3
// Loading Models
// we're loading models first because that's
// what the spec does <3
ds = Debug::MODELS;
#ifdef MPFW_QUAKE_SLOW_LOADING
data.models.resize(data.header.models.size / 64);
modelsCDBG = 0;
for (int i = 0; i < data.header.models.size / 64; i++) {
int base = data.header.models.offset + 64 * i;
qModel qm;
@ -150,7 +150,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
// std::print("Model {}/{}\n", i, data.header.models.size / 64);
}
#else
std::string modelsStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.models.offset, data.header.models.size);
@ -199,7 +199,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
int base = data.header.edges.offset + (4 * i);
Edge e;
e.vertex0 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base);
e.vertex1 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base+2);
e.vertex1 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 2);
data.edges[i] = e;
edgesCDBG++;
}
@ -258,12 +258,12 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
#endif
// parsing faces
ds = Debug::FACES;
#ifdef MPFW_QUAKE_SLOW_LOADING
@ -276,9 +276,9 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
f.side = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 2);
f.ledgeId = (signed long)(Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 4));
f.ledgeNum = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 8);
f.texinfoId = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 10);
@ -324,19 +324,19 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
std::vector<long> texOffsets;
// std::memcpy(texOffsets.data(), Utils::Strings::IterativeStringExcerpt(buffer, data.header.miptex.offset + 4, data.header.miptex.size - 4).data(), data.header.miptex.size - 4);
for (int i = 0; i < cmh.numtex; i++) {
texOffsets.push_back(Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset + 4 + 4*i));
texOffsets.push_back(Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset + 4 + 4 * i));
}
for (int i = 0; i < cmh.numtex; i++) {
unsigned int base = static_cast<unsigned int>(texOffsets[i]) + (unsigned)data.header.miptex.offset;
// this shit is ¤ undocumented! ¤
// fuck the unofficial quake specs
// this shit is ¤ undocumented! ¤
// fuck the unofficial quake specs
if (texOffsets[i] == -1) {
// is a texoffset of 0xFFFFFFFF a reference to the fact that
// it starts right after the mipheader? probably not, but it
// makes the texture loading code happy so... :shrug:
// is a texoffset of 0xFFFFFFFF a reference to the fact that
// it starts right after the mipheader? probably not, but it
// makes the texture loading code happy so... :shrug:
base = (unsigned)data.header.miptex.offset + 4 + texOffsets.size() * 4;
}
@ -368,7 +368,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
UnloadImage(img);
data.textures.push_back(mt);
}
data.renderFaces.clear();
@ -389,6 +389,8 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
data.header.planes.size
);
std::vector<Physics::Polygon> collPolygons;
std::vector<Vector3> totalRlVec;
for (int i = 0; i < data.faces.size(); i++) {
CalculatedFace cface;
@ -419,6 +421,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
]
));
}
totalRlVec.push_back(cface.vertices[cface.vertices.size() - 1]); // push back the last vertex for collision
}
@ -427,9 +430,9 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
cface.glTextureWidth = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width;
cface.glTextureHeight = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height;
collPolygons.push_back(cface.vertices);
for (int j = 0; j < cface.vertices.size(); j++) {
cface.texCoords.push_back(Vector2(
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
@ -464,7 +467,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
planeType == 0 || planeType == 3 ? 0 :
planeType == 1 || planeType == 4 ? 1 :
2;
int lmw, lmh;
switch (valueForgotten) {
@ -494,7 +497,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
cface.hasLightMap = true;
for (int y = 0; y < lmh; y++) {
for (int x = 0; x < lmw; x++) {
unsigned char lightValue = data.lightmap[y*lmw+x + lmbase];
unsigned char lightValue = data.lightmap[y * lmw + x + lmbase];
unsigned char invertedLightValue = 255 - lightValue;
// std::cout << "light value : " << (int)invertedLightValue << "\n";
ImageDrawPixel(&lightmap, x, y, {
@ -502,7 +505,7 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
0,
0,
invertedLightValue
});
});
}
}
@ -526,7 +529,13 @@ void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
}
Physics::CollisionMapGenParams cmgp;
*collMap = Physics::GenerateCollisionMap(collPolygons, totalRlVec, cmgp);

View File

@ -6,6 +6,7 @@
#include <iostream>
#include <vector>
#include <print>
#include "MPFW_Physics.h"
namespace MPFW {
namespace Quake {
@ -195,7 +196,7 @@ namespace MPFW {
Debug::DebugState ds = Debug::NONE;
MapData data;
Palette* pal;
Physics::CollisionMap* collMap;
int modelsCDBG = 0;
int verticesCDBG = 0;
int edgesCDBG = 0;

View File

@ -13,6 +13,7 @@
#include "MPFW_UI.h"
#include <algorithm>
#include <mutex>
#include "MPFW_Physics.h"
// turn quake miptex into rl texture
Texture2D RLMT_QUAKE(MPFW::Quake::Maps::rlMipTex rlmt) {
@ -185,6 +186,13 @@ int main() {
map.pal = new MPFW::Quake::Maps::Palette("data/palette.lmp");
MPFW::Physics::CollisionMap collisionMap; // map of the collisions
map.collMap = &collisionMap;
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
InitWindow(1280, 720, TextFormat("mpfw"));
@ -193,6 +201,8 @@ int main() {
DisableCursor();
MPFW::UI::UIRenderer uiRenderer;
MPFW::Console::CommandHandlerResources chr;
chr.mapQuake = &map;
@ -292,12 +302,35 @@ int main() {
velocity.y -= 10 * GetFrameTime();
}
if(chr.cvars["math_3d_floor_collision_work"] == "true"){
for (int b = 0; b < collisionMap.collisionSets.size(); b++) {
BoundingBox bb;
bb.min = collisionMap.collisionSets[b].size.min;
bb.max = collisionMap.collisionSets[b].size.max;
if (CheckCollisionBoxSphere(bb, camera.position, 10)) {
MPFW::Physics::CollisionSet cs = collisionMap.collisionSets[b];
for (int i = 0; i < cs.collisionFaces.size(); i++) {
for (int t = 2; t < cs.collisionFaces[i].polygon.size(); t++) {
Vector3 a = cs.collisionFaces[i].polygon[0];
Vector3 b = cs.collisionFaces[i].polygon[t];
Vector3 c = cs.collisionFaces[i].polygon[t - 1];
Ray r;
r.position = camera.position;
r.direction = { 0,-1,0 };
RayCollision coll = GetRayCollisionTriangle(r, a, b, c);
}
}
}
}
}
velocity *= {GetFrameTime(), 1, GetFrameTime()};
camera.position += velocity;
camera.target = camera.position + rotation;
}
@ -389,6 +422,12 @@ int main() {
}
}
for (int i = 0; i < collisionMap.collisionSets.size(); i++) {
BoundingBox b; b.min = collisionMap.collisionSets[i].size.min;
b.max = collisionMap.collisionSets[i].size.max;
DrawBoundingBox(b, RED);
}
EndMode3D();
}

View File

@ -149,6 +149,7 @@
<ClCompile Include="MPFW_Console.cpp" />
<ClCompile Include="MPFW_HL.cpp" />
<ClCompile Include="MPFW_Net.cpp" />
<ClCompile Include="MPFW_Physics.cpp" />
<ClCompile Include="MPFW_Quake.cpp" />
<ClCompile Include="MPFW_UI.cpp" />
<ClCompile Include="MPFW_Utils.cpp" />
@ -160,6 +161,7 @@
<ClInclude Include="MPFW_HL.h" />
<ClInclude Include="MPFW_MPFWMF.h" />
<ClInclude Include="MPFW_Net.h" />
<ClInclude Include="MPFW_Physics.h" />
<ClInclude Include="MPFW_Quake.h" />
<ClInclude Include="MPFW_UI.h" />
<ClInclude Include="MPFW_Utils.h" />

View File

@ -66,6 +66,9 @@
<ClCompile Include="MPFW_Backend_raylib.cpp">
<Filter>Backends</Filter>
</ClCompile>
<ClCompile Include="MPFW_Physics.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="MPFW_HL.h">
@ -95,5 +98,8 @@
<ClInclude Include="MPFW_Backend_raylib.h">
<Filter>Backends</Filter>
</ClInclude>
<ClInclude Include="MPFW_Physics.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>