Skip to content
Closed
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
12 changes: 5 additions & 7 deletions config/app.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,8 @@
{
"type": "Collider",
"layer": "enemy",
"shape": "Capsule",
"radius": 0.5,
"height": 1.5
"shape": "Box",
"size": [1.0, 1.0, 1.0]
}
]
},
Expand Down Expand Up @@ -272,7 +271,8 @@
{
"type": "Collider",
"layer": "enemy",
"radius": 0.8
"shape": "Box",
"size": [1.0, 1.0, 1.0]
}
]
}
Expand Down Expand Up @@ -327,9 +327,7 @@
"layer": "player",
"radius": 0.45,
"height": 1.8
}
],
"children": [
},
{
"position": [1, -1, -2],
"rotation": [0, 0, 0],
Expand Down
6 changes: 6 additions & 0 deletions src/game/components/collider.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#include "collider.hpp"

#include "deserialize-utils.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 == "Box") return gameplay::ColliderShape::Box;
return fallback;
}

Expand All @@ -22,8 +25,11 @@ namespace gameplay {
radius = data.value("radius", radius);
height = data.value("height", height);
isTrigger = data.value("isTrigger", isTrigger);

std::string stringLayer = data.value("layer", "");
layer = layerStringToGroup(stringLayer);

halfExtents = data.value("size", glm::vec3(0.5f));
}

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

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

namespace gameplay {

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

enum CollisionLayer : short {
LAYER_PLAYER = 1 << 0, // bit 0: 0000 0001
Expand All @@ -21,6 +22,7 @@ namespace gameplay {
float radius = 0.5f;
float height = 1.0f; // must be the total height
bool isTrigger = false;
glm::vec3 halfExtents = glm::vec3(0.5f);

static std::string getID() {
return "Collider";
Expand Down
27 changes: 27 additions & 0 deletions src/game/systems/collision-debug-drawer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,33 @@ void main() {
addCircle(center, forward, up, radius, color, 32); // ZY circle
}

void CollisionDebugDrawer::drawBoxWireframe(const glm::mat4& transform, const glm::vec3& halfExtents,
const glm::vec3& color) {
glm::vec3 center = glm::vec3(transform[3]);
glm::vec3 right = glm::normalize(glm::vec3(transform[0]));
glm::vec3 up = glm::normalize(glm::vec3(transform[1]));
glm::vec3 forward = glm::normalize(glm::vec3(transform[2]));

// 8 corners of the box
glm::vec3 corners[8];
for (int i = 0; i < 8; i++) {
float x = (i & 1) ? halfExtents.x : -halfExtents.x;
float y = (i & 2) ? halfExtents.y : -halfExtents.y;
float z = (i & 4) ? halfExtents.z : -halfExtents.z;
corners[i] = center + right * x + up * y + forward * z;
}

// 12 edges of the box (pairs of corner indices)
int edgeIndices[12][2] = {
{0, 1}, {1, 3}, {3, 2}, {2, 0}, // bottom face
{4, 5}, {5, 7}, {7, 6}, {6, 4}, // top face
{0, 4}, {1, 5}, {2, 6}, {3, 7} // vertical edges
};
for (int i = 0; i < 12; i++) {
addLine(corners[edgeIndices[i][0]], corners[edgeIndices[i][1]], color);
}
}

void CollisionDebugDrawer::drawCapsuleWireframe(const glm::mat4& transform, float radius, float totalHeight,
const glm::vec3& color) {
glm::vec3 center = glm::vec3(transform[3]);
Expand Down
1 change: 1 addition & 0 deletions src/game/systems/collision-debug-drawer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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 drawBoxWireframe(const glm::mat4& transform, const glm::vec3& halfExtents, const glm::vec3& color);
void drawCapsuleWireframe(const glm::mat4& transform, float radius, float totalHeight, const glm::vec3& color);

// ---- btIDebugDraw overrides (required by interface) ----
Expand Down
62 changes: 56 additions & 6 deletions src/game/systems/collision-system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ static inline glm::vec3 worldToLocal(const our::Entity* entity, glm::vec3 worldV
return worldVector; // if no parent then local and world are the same
}

static inline std::string getShapeKey(gameplay::ColliderComponent* collider, our::Entity* entity) {
std::string shapeKey = std::string("");

// Shape
shapeKey += std::to_string(static_cast<int>(collider->shape));

// r_h_extents
shapeKey += "_" + std::to_string(collider->radius) + "_" + std::to_string(collider->height) + "_HalfExtents_" +
std::to_string(collider->halfExtents[0]) + "_" + std::to_string(collider->halfExtents[1]) + "_" +
std::to_string(collider->halfExtents[2]);

// Scale_x_y_z
shapeKey += "_Scale_" + std::to_string(entity->localTransform.scale.x) + "_" +
std::to_string(entity->localTransform.scale.y) + "_" + std::to_string(entity->localTransform.scale.z);

return shapeKey;
}

static btTransform entityToBtTransform(our::Entity* entity) {
glm::mat4 m = entity->getLocalToWorldMatrix();

Expand Down Expand Up @@ -58,7 +76,7 @@ namespace gameplay {
case LAYER_PLAYER:
return LAYER_ENEMY | LAYER_ENVIRONMENT | LAYER_TRIGGER;
case LAYER_ENEMY:
return LAYER_PLAYER | LAYER_PROJECTILE | LAYER_ENVIRONMENT;
return LAYER_PLAYER | LAYER_PROJECTILE | LAYER_ENVIRONMENT | LAYER_ENEMY;
Comment thread
LoayAhmed304 marked this conversation as resolved.
case LAYER_ENVIRONMENT:
return LAYER_PLAYER | LAYER_ENEMY | LAYER_PROJECTILE;
case LAYER_PROJECTILE:
Expand Down Expand Up @@ -275,11 +293,8 @@ namespace gameplay {
// Create or reuse collision shape based on collider data
btCollisionShape* shape = nullptr;

// Shape_r_h_Scale_x_y_z
std::string shapeKey =
std::to_string(static_cast<int>(collider->shape)) + "_" + std::to_string(collider->radius) + "_" +
std::to_string(collider->height) + "_Scale_" + std::to_string(entity->localTransform.scale.x) + "_" +
std::to_string(entity->localTransform.scale.y) + "_" + std::to_string(entity->localTransform.scale.z);
// Shape_r_h_HalfExtents_x_y_z_Scale_x_y_z
std::string shapeKey = getShapeKey(collider, entity);
if (shapesCache.find(shapeKey) != shapesCache.end()) {
ownedShapes[shapesCache[shapeKey]]++;
shape = shapesCache[shapeKey];
Expand All @@ -289,6 +304,9 @@ namespace gameplay {
case ColliderShape::Sphere:
shape = new btSphereShape(collider->radius);
break;
case ColliderShape::Box:
shape = new btBoxShape(glmToBtVec3(collider->halfExtents));
break;
case ColliderShape::Capsule: {
float spine = collider->height - 2.0f * collider->radius;
if (spine < 0.0f) spine = 0.0f;
Expand Down Expand Up @@ -367,6 +385,34 @@ namespace gameplay {
return frameCollisions;
}

// Get the height of the entity from its origin point.
float CollisionSystem::getOriginEntityHeight(const our::Entity* entity) {
btCollisionObject* obj = entityToBullet.at(const_cast<our::Entity*>(entity));
if (!obj) return 0.0f;

btCollisionShape* shape = obj->getCollisionShape();

switch (shape->getShapeType()) {
case SPHERE_SHAPE_PROXYTYPE: {
const btSphereShape* sphere = static_cast<const btSphereShape*>(shape);
return sphere->getRadius(); // radius as height (from origin)
}
case BOX_SHAPE_PROXYTYPE: {
const btBoxShape* box = static_cast<const btBoxShape*>(shape);
btVector3 halfExtents = box->getHalfExtentsWithMargin();
return halfExtents.getY(); // half Y extent as height (from origin)
}
case CAPSULE_SHAPE_PROXYTYPE: {
const btCapsuleShape* capsule = static_cast<const btCapsuleShape*>(shape);
float radius = capsule->getRadius();
float halfHeight = capsule->getHalfHeight();
return halfHeight + radius; // cylinder half height + 1 hemisphere (from origin)
}
}

return 0.0f;
}

#ifdef COLLISION_DEBUG_DRAW
// Returns a debug color based on the collision layer of the entity
static glm::vec3 getLayerColor(our::Entity* entity) {
Expand Down Expand Up @@ -419,6 +465,10 @@ namespace gameplay {
case ColliderShape::Capsule:
debugDrawer->drawCapsuleWireframe(transform, scaledRadius, scaledHeight, color);
break;
case ColliderShape::Box:
debugDrawer->drawBoxWireframe(
transform, collider->halfExtents * glm::vec3(scale.getX(), scale.getY(), scale.getZ()), color);
break;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/game/systems/collision-system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ namespace gameplay {
#endif

// Entity-Bullet mappings
std::unordered_map<our::Entity*, btCollisionObject*> entityToBullet;
inline static std::unordered_map<our::Entity*, btCollisionObject*> entityToBullet;
std::unordered_map<btCollisionShape*, std::uint16_t> ownedShapes;
std::unordered_map<std::string, btCollisionShape*> shapesCache;

Expand All @@ -92,6 +92,8 @@ namespace gameplay {

const std::vector<CollisionEvent>& getCollisions() const;

static float getOriginEntityHeight(const our::Entity* entity);

#ifdef COLLISION_DEBUG_DRAW
/// Call after the main renderer finishes to overlay collision wireframes.
/// @param VP the View-Projection matrix from the active camera.
Expand Down
13 changes: 1 addition & 12 deletions src/game/systems/enemy-health-bar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ namespace gameplay {
}
}

static float estimateBarHeight(const our::Entity* entity, const ColliderComponent* collider) {
float scaleHeight = std::max(std::abs(entity->localTransform.scale.y) * 1.2f, 1.0f);
if (!collider) return scaleHeight;

float colliderHeight = collider->radius * 2.0f;
if (collider->shape == ColliderShape::Capsule) colliderHeight = std::max(colliderHeight, collider->height);

return std::max(colliderHeight, scaleHeight);
}

public:
void deserialize(const nlohmann::json& sceneConfig) {
if (!(sceneConfig.contains("game") && sceneConfig["game"].contains("enemyHealthBars"))) return;
Expand Down Expand Up @@ -103,8 +93,7 @@ namespace gameplay {

if (!(visibleWhenDamaged || visibleWhenClose)) continue;

ColliderComponent* collider = entity->getComponent<ColliderComponent>();
float barHeightOffset = estimateBarHeight(entity, collider) + 0.35f;
float barHeightOffset = CollisionSystem::getOriginEntityHeight(entity) + 0.35f;

glm::vec3 barWorldPos = glm::vec3(localToWorld * glm::vec4(0.0f, barHeightOffset, 0.0f, 1.0f));
our::ScreenPoint screen = our::UIRenderer::worldToScreen(barWorldPos, viewProj, framebufferSize);
Expand Down
Loading