Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions config/app.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -315,21 +315,20 @@
},
{
"type": "Post Process Effects Component"
}
],
"children": [
{
"type": "Health",
"maxHealth": 100.0
},
{
"type": "Collider",
"layer": "player",
"radius": 0.45,
"height": 1.8
"height": 1.8,
"shape": "Capsule"
}
],
"children": [
{
"type": "Health",
"maxHealth": 100.0
},
{
"position": [1, -1, -2],
"rotation": [0, 0, 0],
Expand Down Expand Up @@ -364,6 +363,12 @@
{
"type": "Model Renderer",
"model": "map"
},
{
"type": "Collider",
"layer": "environment",
"shape": "Model",
"model": "map"
}
]
}
Expand Down
32 changes: 32 additions & 0 deletions src/game/components/collider.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#include "collider.hpp"

#include <btBulletCollisionCommon.h>

#include "asset-loader.hpp"

static gameplay::ColliderShape parseColliderShape(const std::string& value, gameplay::ColliderShape fallback) {
if (value == "Sphere") return gameplay::ColliderShape::Sphere;
if (value == "Capsule") return gameplay::ColliderShape::Capsule;
if (value == "Mesh" || value == "Model") return gameplay::ColliderShape::Mesh;
return fallback;
}

Expand All @@ -24,6 +29,33 @@ namespace gameplay {
isTrigger = data.value("isTrigger", isTrigger);
std::string stringLayer = data.value("layer", "");
layer = layerStringToGroup(stringLayer);
std::string modelName = data.value("model", "");
std::string meshName =
data.value("mesh", ""); // "mesh" is an alternative key for the model name, in case "model" is not provided
if (!modelName.empty()) {
our::Model* model = our::AssetLoader<our::Model>::get(modelName);
if (model) {
std::cout << "Loaded model for collider: " << modelName << std::endl;
mesh = model->getCombinedMesh();
}
if (!mesh) {
std::cerr << "\033[31mFailed to load model for collider: " << modelName << "\033[0m" << std::endl;
}
} else if (!meshName.empty()) {
our::Mesh* assetMesh = our::AssetLoader<our::Mesh>::get(meshName);
if (assetMesh) {
mesh = assetMesh;
}
if (!mesh) {
std::cerr << "\033[31mFailed to load mesh for collider: " << meshName << "\033[0m" << std::endl;
}
}
}

ColliderComponent::~ColliderComponent() {
if (shape == ColliderShape::Mesh && bulletMesh) {
delete bulletMesh;
}
}

} // namespace gameplay
9 changes: 8 additions & 1 deletion src/game/components/collider.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once

#include <ecs/component.hpp>
#include <glm/glm.hpp>
#include <model/model.hpp>

struct btTriangleMesh;
namespace gameplay {

enum class ColliderShape { Sphere, Capsule };
enum class ColliderShape { Sphere, Capsule, Mesh };

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

static std::string getID() {
return "Collider";
}

void deserialize(const nlohmann::json& data) override;
~ColliderComponent();
};

} // namespace gameplay
67 changes: 67 additions & 0 deletions src/game/systems/collision-debug-drawer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ out vec4 fragColor;
void main() {
fragColor = vec4(vColor, 1.0);
}
)";

static const char* kMeshVertexShader = R"(
#version 330 core
layout(location = 0) in vec3 aPos;
uniform mat4 uMVP;
void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
}
)";

static const char* kMeshFragmentShader = R"(
#version 330 core
uniform vec3 uColor;
out vec4 fragColor;
void main() {
fragColor = vec4(uColor, 1.0);
}
)";

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

vpUniformLoc = glGetUniformLocation(shaderProgram, "uVP");

// Mesh shader program
GLuint mVert = compileShaderStage(GL_VERTEX_SHADER, kMeshVertexShader);
GLuint mFrag = compileShaderStage(GL_FRAGMENT_SHADER, kMeshFragmentShader);

meshShaderProgram = glCreateProgram();
glAttachShader(meshShaderProgram, mVert);
glAttachShader(meshShaderProgram, mFrag);
glLinkProgram(meshShaderProgram);

success = 0;
glGetProgramiv(meshShaderProgram, GL_LINK_STATUS, &success);
if (!success) {
char log[512];
glGetProgramInfoLog(meshShaderProgram, sizeof(log), nullptr, log);
fprintf(stderr, "[CollisionDebugDrawer] Mesh Shader link error:\n%s\n", log);
}

glDeleteShader(mVert);
glDeleteShader(mFrag);

meshMVPUniformLoc = glGetUniformLocation(meshShaderProgram, "uMVP");
meshColorUniformLoc = glGetUniformLocation(meshShaderProgram, "uColor");
}

void CollisionDebugDrawer::initialize() {
Expand Down Expand Up @@ -115,7 +156,12 @@ void main() {
glDeleteProgram(shaderProgram);
shaderProgram = 0;
}
if (meshShaderProgram) {
glDeleteProgram(meshShaderProgram);
meshShaderProgram = 0;
}
lineVertices.clear();
meshDrawCommands.clear();
}

// ---- Wireframe generation helpers ----
Expand Down Expand Up @@ -213,6 +259,10 @@ void main() {
}
}

void CollisionDebugDrawer::drawMeshWireframe(our::Mesh* mesh, const glm::mat4& transform, const glm::vec3& color) {
meshDrawCommands.push_back({mesh, transform, color});
}

// ---- btIDebugDraw overrides (still needed since Bullet requires them) ----
void CollisionDebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& color) {
lineVertices.push_back({from.getX(), from.getY(), from.getZ(), color.getX(), color.getY(), color.getZ()});
Expand Down Expand Up @@ -243,6 +293,23 @@ void main() {

glUseProgram(0);

// Render meshes if any
if (!meshDrawCommands.empty()) {
glUseProgram(meshShaderProgram);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

for (const auto& cmd : meshDrawCommands) {
glm::mat4 MVP = VP * cmd.transform;
glUniformMatrix4fv(meshMVPUniformLoc, 1, GL_FALSE, glm::value_ptr(MVP));
glUniform3fv(meshColorUniformLoc, 1, glm::value_ptr(cmd.color));
cmd.mesh->draw();
}

glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glUseProgram(0);
meshDrawCommands.clear();
}

// Restore previous state
if (prevDepthTest) glEnable(GL_DEPTH_TEST);

Expand Down
13 changes: 13 additions & 0 deletions src/game/systems/collision-debug-drawer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <glad/gl.h>

#include <glm/glm.hpp>
#include <mesh/mesh.hpp>
#include <vector>

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

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

std::vector<LineVertex> lineVertices;

struct MeshDrawCommand {
our::Mesh* mesh;
glm::mat4 transform;
glm::vec3 color;
};
std::vector<MeshDrawCommand> meshDrawCommands;

GLuint vao = 0;
GLuint vbo = 0;
GLuint shaderProgram = 0;
GLint vpUniformLoc = -1;

GLuint meshShaderProgram = 0;
GLint meshMVPUniformLoc = -1;
GLint meshColorUniformLoc = -1;

int debugMode = DBG_DrawWireframe;

void createShader();
Expand Down
26 changes: 26 additions & 0 deletions src/game/systems/collision-system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,26 @@ namespace gameplay {
shape = new btCapsuleShape(collider->radius, spine);
break;
}
case ColliderShape::Mesh:
if (collider->mesh) {
collider->bulletMesh = new btTriangleMesh();
const std::vector<our::Vertex>& vertices = collider->mesh->getVertices();
const std::vector<unsigned int>& indices = collider->mesh->getIndices();
collider->bulletMesh->preallocateVertices(vertices.size());
collider->bulletMesh->preallocateIndices(indices.size());
for (size_t i = 0; i < indices.size(); i += 3) {
const glm::vec3& v0 = vertices[indices[i]].position;
const glm::vec3& v1 = vertices[indices[i + 1]].position;
const glm::vec3& v2 = vertices[indices[i + 2]].position;
collider->bulletMesh->addTriangle(glmToBtVec3(v0), glmToBtVec3(v1), glmToBtVec3(v2));
}
shape = new btBvhTriangleMeshShape(collider->bulletMesh, true);
} else {
std::cerr << "\033[31mCollider mesh is null for entity " << entity->name
<< ". Defaulting to sphere shape.\033[0m" << std::endl;
shape = new btSphereShape(collider->radius);
}
break;
}
shape->setLocalScaling(glmToBtVec3(entity->localTransform.scale));
shapesCache[shapeKey] = shape;
Expand Down Expand Up @@ -411,6 +431,7 @@ namespace gameplay {
btVector3 scale = obj->getCollisionShape()->getLocalScaling();
float scaledRadius = collider->radius * scale.getX();
float scaledHeight = collider->height * scale.getY();
transform = transform * glm::scale(glm::mat4(1.0f), glm::vec3(scale.getX(), scale.getY(), scale.getZ()));

switch (collider->shape) {
case ColliderShape::Sphere:
Expand All @@ -419,6 +440,11 @@ namespace gameplay {
case ColliderShape::Capsule:
debugDrawer->drawCapsuleWireframe(transform, scaledRadius, scaledHeight, color);
break;
case ColliderShape::Mesh:
if (collider->mesh) {
debugDrawer->drawMeshWireframe(collider->mesh, transform, color);
}
break;
}
}

Expand Down
Loading