feat: add more collider types + camera snapping fix

This commit is contained in:
2026-10-24 02:34:09 +02:00
parent 355dfa7851
commit 118016bcc7
26 changed files with 1525 additions and 354 deletions
@@ -0,0 +1,41 @@
#pragma once
#include <algorithm>
#include <destrum/Components/Physics/MeshShapeCollider.h>
class ConvexHullCollider final : public MeshShapeCollider {
public:
static constexpr int MinVertices = 4;
static constexpr int MaxVertices = 256;
explicit ConvexHullCollider(GameObject& owner, int maxVertices = 64)
: MeshShapeCollider(owner)
, m_MaxVertices(std::clamp(maxVertices, MinVertices, MaxVertices)) {}
std::string GetTypeName() const override {
return "ConvexHullCollider";
}
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void DrawDebugLines(const glm::vec4& color) const override;
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
return BuildConvexHullShape(m_MaxVertices);
}
[[nodiscard]] int GetMaxVertices() const {
return m_MaxVertices;
}
void SetMaxVertices(int maxVertices) {
m_MaxVertices = std::clamp(maxVertices, MinVertices, MaxVertices);
NotifyPhysicsChanged();
}
private:
int m_MaxVertices{64};
};
@@ -0,0 +1,66 @@
#pragma once
#include <algorithm>
#include <destrum/Components/Physics/Collider.h>
class CylinderCollider final : public Collider {
public:
explicit CylinderCollider(GameObject& owner, float radius = 0.5f, float height = 1.0f)
: Collider(owner)
, m_Radius(std::max(radius, 0.0001f))
, m_Height(std::max(height, 0.0001f)) {}
void Update(float) override {}
std::string GetTypeName() const override {
return "CylinderCollider";
}
void ImGuiInspector() override;
nlohmann::json Serialize() const override {
auto data = SerializeCollider();
data["radius"] = m_Radius;
data["height"] = m_Height;
return data;
}
void Deserialize(const nlohmann::json& data) override {
DeserializeCollider(data);
if (data.contains("radius")) {
SetRadius(data.at("radius").get<float>());
}
if (data.contains("height")) {
SetHeight(data.at("height").get<float>());
}
}
void DrawDebugLines(const glm::vec4& color) const override;
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
return PhysicsShapeDesc::Cylinder(m_Radius, m_Height, m_CenterOffset, m_IsTrigger);
}
[[nodiscard]] float GetRadius() const {
return m_Radius;
}
void SetRadius(float radius) {
m_Radius = std::max(radius, 0.0001f);
NotifyPhysicsChanged();
}
[[nodiscard]] float GetHeight() const {
return m_Height;
}
void SetHeight(float height) {
m_Height = std::max(height, 0.0001f);
NotifyPhysicsChanged();
}
private:
float m_Radius{0.5f};
float m_Height{1.0f};
};
@@ -0,0 +1,32 @@
#pragma once
#include <destrum/Components/Physics/MeshShapeCollider.h>
// Exact triangle mesh for static and kinematic bodies. Dynamic bodies can't use one, they get a convex hull instead.
class MeshCollider final : public MeshShapeCollider {
public:
static constexpr int DynamicHullVertices = 64;
explicit MeshCollider(GameObject& owner)
: MeshShapeCollider(owner) {}
std::string GetTypeName() const override {
return "MeshCollider";
}
nlohmann::json Serialize() const override {
return SerializeCollider();
}
void Deserialize(const nlohmann::json& data) override {
DeserializeCollider(data);
}
void ImGuiInspector() override;
void DrawDebugLines(const glm::vec4& color) const override;
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override;
[[nodiscard]] bool UsesConvexFallback() const;
};
@@ -0,0 +1,36 @@
#pragma once
#include <memory>
#include <destrum/Components/Physics/Collider.h>
// Base for colliders shaped by the MeshRendererComponent on the same object. Unlike the primitive colliders,
// the mesh is in local units, so the transform's world scale is applied.
class MeshShapeCollider : public Collider {
public:
explicit MeshShapeCollider(GameObject& owner)
: Collider(owner) {}
void Update(float) override {}
void ImGuiInspector() override;
// Used instead of the MeshRenderer mesh, not serialized
void SetCustomMesh(std::shared_ptr<const PhysicsMeshData> mesh) {
m_CustomMesh = std::move(mesh);
NotifyPhysicsChanged();
}
[[nodiscard]] std::shared_ptr<const PhysicsMeshData> GetSourceMesh() const;
protected:
[[nodiscard]] glm::vec3 GetShapeScale() const;
[[nodiscard]] glm::mat4 GetShapeMatrix() const;
// Falls back to a box around the mesh when it has no volume
[[nodiscard]] PhysicsShapeDesc BuildConvexHullShape(int maxVertices) const;
void DrawConvexHull(int maxVertices, const glm::vec4& color) const;
private:
std::shared_ptr<const PhysicsMeshData> m_CustomMesh;
};
@@ -4,6 +4,9 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <cstdint>
#include <vector>
namespace DebugDraw {
void Arc(const glm::vec3& center, const glm::vec3& axisA, const glm::vec3& axisB, float radius,
float fromAngle, float toAngle, const glm::vec4& color, int segments = 16);
@@ -14,6 +17,12 @@ namespace DebugDraw {
void Sphere(const glm::vec3& center, float radius, const glm::vec4& color);
void Capsule(const glm::vec3& center, const glm::quat& rotation, float radius, float height, const glm::vec4& color);
void Cylinder(const glm::vec3& center, const glm::quat& rotation, float radius, float height, const glm::vec4& color);
// Wireframe of an indexed triangle list, stops after maxTriangles
void Triangles(const glm::mat4& transform, const std::vector<glm::vec3>& points,
const std::vector<std::uint32_t>& indices, const glm::vec4& color,
std::size_t maxTriangles = 20000);
}
#endif // DEBUGDRAW_H
@@ -0,0 +1,18 @@
#pragma once
#include <memory>
#include <string>
#include <destrum/Physics/PhysicsTypes.h>
struct CPUMesh;
// Shares collision data between colliders using the same mesh, so physics backends can share their shapes too.
// Main thread only.
namespace PhysicsMeshCache {
[[nodiscard]] std::shared_ptr<const PhysicsMeshData> FromCPUMesh(const std::string& key, const CPUMesh& mesh);
// Null when the points don't span a volume (e.g. a plane)
[[nodiscard]] std::shared_ptr<const PhysicsMeshData> ConvexHull(const std::shared_ptr<const PhysicsMeshData>& source,
int maxVertices);
}
+57 -2
View File
@@ -2,6 +2,8 @@
#include <cstdint>
#include <limits>
#include <memory>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
@@ -17,7 +19,10 @@ enum class PhysicsShapeType {
None,
Box,
Sphere,
Capsule
Capsule,
Cylinder,
ConvexHull,
Mesh
};
struct PhysicsBodyHandle {
@@ -50,6 +55,12 @@ struct PhysicsMaterialDesc {
float restitution{0.0f};
};
// Triangles share vertices through indices. A convex hull only uses the points, the indices are for drawing it.
struct PhysicsMeshData {
std::vector<glm::vec3> points;
std::vector<std::uint32_t> indices;
};
struct PhysicsShapeDesc {
PhysicsShapeType type{PhysicsShapeType::None};
@@ -59,9 +70,14 @@ struct PhysicsShapeDesc {
// Sphere / capsule
float radius{0.5f};
// Capsule full height, including the two hemispheres.
// Capsule full height, including the two hemispheres. Cylinder full height.
float height{2.0f};
// Convex hull / mesh, in the collider's local space before scale. Backends may cache
// shapes per mesh data, so it must not be modified once shared.
std::shared_ptr<const PhysicsMeshData> mesh;
glm::vec3 scale{1.0f};
// Local offset from the rigidbody transform.
glm::vec3 centerOffset{0.0f};
@@ -101,6 +117,45 @@ struct PhysicsShapeDesc {
desc.isTrigger = isTrigger;
return desc;
}
[[nodiscard]] static PhysicsShapeDesc Cylinder(float radius,
float height,
const glm::vec3& centerOffset = glm::vec3{0.0f},
bool isTrigger = false) {
PhysicsShapeDesc desc{};
desc.type = PhysicsShapeType::Cylinder;
desc.radius = radius;
desc.height = height;
desc.centerOffset = centerOffset;
desc.isTrigger = isTrigger;
return desc;
}
[[nodiscard]] static PhysicsShapeDesc ConvexHull(std::shared_ptr<const PhysicsMeshData> hull,
const glm::vec3& scale = glm::vec3{1.0f},
const glm::vec3& centerOffset = glm::vec3{0.0f},
bool isTrigger = false) {
PhysicsShapeDesc desc{};
desc.type = PhysicsShapeType::ConvexHull;
desc.mesh = std::move(hull);
desc.scale = scale;
desc.centerOffset = centerOffset;
desc.isTrigger = isTrigger;
return desc;
}
[[nodiscard]] static PhysicsShapeDesc Mesh(std::shared_ptr<const PhysicsMeshData> mesh,
const glm::vec3& scale = glm::vec3{1.0f},
const glm::vec3& centerOffset = glm::vec3{0.0f},
bool isTrigger = false) {
PhysicsShapeDesc desc{};
desc.type = PhysicsShapeType::Mesh;
desc.mesh = std::move(mesh);
desc.scale = scale;
desc.centerOffset = centerOffset;
desc.isTrigger = isTrigger;
return desc;
}
};
struct PhysicsBodyDesc {
@@ -12,6 +12,9 @@
#include <destrum/Components/Spinner.h>
#include <destrum/Components/Physics/BoxCollider.h>
#include <destrum/Components/Physics/CapsuleCollider.h>
#include <destrum/Components/Physics/ConvexHullCollider.h>
#include <destrum/Components/Physics/CylinderCollider.h>
#include <destrum/Components/Physics/MeshCollider.h>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/SphereCollider.h>
#include <destrum/Components/UI/Canvas.h>
@@ -32,7 +35,7 @@
X(Rigidbody) \
X(BoxCollider) \
X(SphereCollider) \
X(CapsuleCollider) \
X(CapsuleCollider) X(CylinderCollider) X(ConvexHullCollider) X(MeshCollider) \
X(Canvas) \
X(RectTransform) \
X(UIImage) \