Skip to content

Commit 39baa58

Browse files
committed
feat(collider): add mesh collider support and related debug drawing functionality
1 parent e88eaf6 commit 39baa58

5 files changed

Lines changed: 145 additions & 1 deletion

File tree

src/game/components/collider.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#include "collider.hpp"
22

3+
#include "asset-loader.hpp"
4+
35
static gameplay::ColliderShape parseColliderShape(const std::string& value, gameplay::ColliderShape fallback) {
46
if (value == "Sphere") return gameplay::ColliderShape::Sphere;
57
if (value == "Capsule") return gameplay::ColliderShape::Capsule;
8+
if (value == "Mesh" || value == "Model") return gameplay::ColliderShape::Mesh;
69
return fallback;
710
}
811

@@ -24,6 +27,33 @@ namespace gameplay {
2427
isTrigger = data.value("isTrigger", isTrigger);
2528
std::string stringLayer = data.value("layer", "");
2629
layer = layerStringToGroup(stringLayer);
30+
std::string modelName = data.value("model", "");
31+
std::string meshName =
32+
data.value("mesh", ""); // "mesh" is an alternative key for the model name, in case "model" is not provided
33+
if (!modelName.empty()) {
34+
our::Model* model = our::AssetLoader<our::Model>::get(modelName);
35+
if (model) {
36+
std::cout << "Loaded model for collider: " << modelName << std::endl;
37+
mesh = model->getCombinedMesh();
38+
}
39+
if (!mesh) {
40+
std::cerr << "\033[31mFailed to load model for collider: " << modelName << "\033[0m" << std::endl;
41+
}
42+
} else if (!meshName.empty()) {
43+
our::Mesh* assetMesh = our::AssetLoader<our::Mesh>::get(meshName);
44+
if (assetMesh) {
45+
mesh = assetMesh;
46+
}
47+
if (!mesh) {
48+
std::cerr << "\033[31mFailed to load mesh for collider: " << meshName << "\033[0m" << std::endl;
49+
}
50+
}
51+
}
52+
53+
ColliderComponent::~ColliderComponent() {
54+
if (shape == ColliderShape::Mesh && bulletMesh) {
55+
delete bulletMesh;
56+
}
2757
}
2858

2959
} // namespace gameplay

src/game/components/collider.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#pragma once
22

33
#include <ecs/component.hpp>
4+
#include <glm/glm.hpp>
5+
#include <model/model.hpp>
6+
7+
#include "bullet/BulletCollision/CollisionShapes/btTriangleMesh.h"
48

59
namespace gameplay {
610

7-
enum class ColliderShape { Sphere, Capsule };
11+
enum class ColliderShape { Sphere, Capsule, Mesh };
812

913
enum CollisionLayer : short {
1014
LAYER_PLAYER = 1 << 0, // bit 0: 0000 0001
@@ -21,12 +25,16 @@ namespace gameplay {
2125
float radius = 0.5f;
2226
float height = 1.0f; // must be the total height
2327
bool isTrigger = false;
28+
our::Mesh* mesh = nullptr; // optional mesh for mesh colliders
29+
btTriangleMesh* bulletMesh =
30+
nullptr; // owned by ColliderComponent, freed in destructor (only used if shape == Mesh)
2431

2532
static std::string getID() {
2633
return "Collider";
2734
}
2835

2936
void deserialize(const nlohmann::json& data) override;
37+
~ColliderComponent();
3038
};
3139

3240
} // namespace gameplay

src/game/systems/collision-debug-drawer.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ out vec4 fragColor;
3737
void main() {
3838
fragColor = vec4(vColor, 1.0);
3939
}
40+
)";
41+
42+
static const char* kMeshVertexShader = R"(
43+
#version 330 core
44+
layout(location = 0) in vec3 aPos;
45+
uniform mat4 uMVP;
46+
void main() {
47+
gl_Position = uMVP * vec4(aPos, 1.0);
48+
}
49+
)";
50+
51+
static const char* kMeshFragmentShader = R"(
52+
#version 330 core
53+
uniform vec3 uColor;
54+
out vec4 fragColor;
55+
void main() {
56+
fragColor = vec4(uColor, 1.0);
57+
}
4058
)";
4159

4260
// ---- Helper: compile a single shader stage ----
@@ -77,6 +95,29 @@ void main() {
7795
glDeleteShader(frag);
7896

7997
vpUniformLoc = glGetUniformLocation(shaderProgram, "uVP");
98+
99+
// Mesh shader program
100+
GLuint mVert = compileShaderStage(GL_VERTEX_SHADER, kMeshVertexShader);
101+
GLuint mFrag = compileShaderStage(GL_FRAGMENT_SHADER, kMeshFragmentShader);
102+
103+
meshShaderProgram = glCreateProgram();
104+
glAttachShader(meshShaderProgram, mVert);
105+
glAttachShader(meshShaderProgram, mFrag);
106+
glLinkProgram(meshShaderProgram);
107+
108+
success = 0;
109+
glGetProgramiv(meshShaderProgram, GL_LINK_STATUS, &success);
110+
if (!success) {
111+
char log[512];
112+
glGetProgramInfoLog(meshShaderProgram, sizeof(log), nullptr, log);
113+
fprintf(stderr, "[CollisionDebugDrawer] Mesh Shader link error:\n%s\n", log);
114+
}
115+
116+
glDeleteShader(mVert);
117+
glDeleteShader(mFrag);
118+
119+
meshMVPUniformLoc = glGetUniformLocation(meshShaderProgram, "uMVP");
120+
meshColorUniformLoc = glGetUniformLocation(meshShaderProgram, "uColor");
80121
}
81122

82123
void CollisionDebugDrawer::initialize() {
@@ -115,7 +156,12 @@ void main() {
115156
glDeleteProgram(shaderProgram);
116157
shaderProgram = 0;
117158
}
159+
if (meshShaderProgram) {
160+
glDeleteProgram(meshShaderProgram);
161+
meshShaderProgram = 0;
162+
}
118163
lineVertices.clear();
164+
meshDrawCommands.clear();
119165
}
120166

121167
// ---- Wireframe generation helpers ----
@@ -213,6 +259,10 @@ void main() {
213259
}
214260
}
215261

262+
void CollisionDebugDrawer::drawMeshWireframe(our::Mesh* mesh, const glm::mat4& transform, const glm::vec3& color) {
263+
meshDrawCommands.push_back({mesh, transform, color});
264+
}
265+
216266
// ---- btIDebugDraw overrides (still needed since Bullet requires them) ----
217267
void CollisionDebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& color) {
218268
lineVertices.push_back({from.getX(), from.getY(), from.getZ(), color.getX(), color.getY(), color.getZ()});
@@ -243,6 +293,23 @@ void main() {
243293

244294
glUseProgram(0);
245295

296+
// Render meshes if any
297+
if (!meshDrawCommands.empty()) {
298+
glUseProgram(meshShaderProgram);
299+
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
300+
301+
for (const auto& cmd : meshDrawCommands) {
302+
glm::mat4 MVP = VP * cmd.transform;
303+
glUniformMatrix4fv(meshMVPUniformLoc, 1, GL_FALSE, glm::value_ptr(MVP));
304+
glUniform3fv(meshColorUniformLoc, 1, glm::value_ptr(cmd.color));
305+
cmd.mesh->draw();
306+
}
307+
308+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
309+
glUseProgram(0);
310+
meshDrawCommands.clear();
311+
}
312+
246313
// Restore previous state
247314
if (prevDepthTest) glEnable(GL_DEPTH_TEST);
248315

src/game/systems/collision-debug-drawer.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <glad/gl.h>
77

88
#include <glm/glm.hpp>
9+
#include <mesh/mesh.hpp>
910
#include <vector>
1011

1112
namespace gameplay {
@@ -21,6 +22,7 @@ namespace gameplay {
2122
// ---- Manual wireframe generators (used instead of Bullet's debugDrawObject) ----
2223
void drawSphereWireframe(const glm::mat4& transform, float radius, const glm::vec3& color);
2324
void drawCapsuleWireframe(const glm::mat4& transform, float radius, float totalHeight, const glm::vec3& color);
25+
void drawMeshWireframe(our::Mesh* mesh, const glm::mat4& transform, const glm::vec3& color);
2426

2527
// ---- btIDebugDraw overrides (required by interface) ----
2628
void drawLine(const btVector3& from, const btVector3& to, const btVector3& color) override;
@@ -42,11 +44,22 @@ namespace gameplay {
4244

4345
std::vector<LineVertex> lineVertices;
4446

47+
struct MeshDrawCommand {
48+
our::Mesh* mesh;
49+
glm::mat4 transform;
50+
glm::vec3 color;
51+
};
52+
std::vector<MeshDrawCommand> meshDrawCommands;
53+
4554
GLuint vao = 0;
4655
GLuint vbo = 0;
4756
GLuint shaderProgram = 0;
4857
GLint vpUniformLoc = -1;
4958

59+
GLuint meshShaderProgram = 0;
60+
GLint meshMVPUniformLoc = -1;
61+
GLint meshColorUniformLoc = -1;
62+
5063
int debugMode = DBG_DrawWireframe;
5164

5265
void createShader();

src/game/systems/collision-system.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,26 @@ namespace gameplay {
295295
shape = new btCapsuleShape(collider->radius, spine);
296296
break;
297297
}
298+
case ColliderShape::Mesh:
299+
if (collider->mesh) {
300+
collider->bulletMesh = new btTriangleMesh();
301+
const std::vector<our::Vertex>& vertices = collider->mesh->getVertices();
302+
const std::vector<unsigned int>& indices = collider->mesh->getIndices();
303+
collider->bulletMesh->preallocateVertices(vertices.size());
304+
collider->bulletMesh->preallocateIndices(indices.size());
305+
for (size_t i = 0; i < indices.size(); i += 3) {
306+
const glm::vec3& v0 = vertices[indices[i]].position;
307+
const glm::vec3& v1 = vertices[indices[i + 1]].position;
308+
const glm::vec3& v2 = vertices[indices[i + 2]].position;
309+
collider->bulletMesh->addTriangle(glmToBtVec3(v0), glmToBtVec3(v1), glmToBtVec3(v2));
310+
}
311+
shape = new btBvhTriangleMeshShape(collider->bulletMesh, true);
312+
} else {
313+
std::cerr << "\033[31mCollider mesh is null for entity " << entity->name
314+
<< ". Defaulting to sphere shape.\033[0m" << std::endl;
315+
shape = new btSphereShape(collider->radius);
316+
}
317+
break;
298318
}
299319
shape->setLocalScaling(glmToBtVec3(entity->localTransform.scale));
300320
shapesCache[shapeKey] = shape;
@@ -411,6 +431,7 @@ namespace gameplay {
411431
btVector3 scale = obj->getCollisionShape()->getLocalScaling();
412432
float scaledRadius = collider->radius * scale.getX();
413433
float scaledHeight = collider->height * scale.getY();
434+
transform = transform * glm::scale(glm::mat4(1.0f), glm::vec3(scale.getX(), scale.getY(), scale.getZ()));
414435

415436
switch (collider->shape) {
416437
case ColliderShape::Sphere:
@@ -419,6 +440,11 @@ namespace gameplay {
419440
case ColliderShape::Capsule:
420441
debugDrawer->drawCapsuleWireframe(transform, scaledRadius, scaledHeight, color);
421442
break;
443+
case ColliderShape::Mesh:
444+
if (collider->mesh) {
445+
debugDrawer->drawMeshWireframe(collider->mesh, transform, color);
446+
}
447+
break;
422448
}
423449
}
424450

0 commit comments

Comments
 (0)