#include "CollisionSystem.h" using namespace Seele; using namespace Seele::Component; CollisionSystem::CollisionSystem(entt::registry& registry) : registry(registry) { registry.on_construct().connect<&BVH::colliderCallback>(bvh); } CollisionSystem::~CollisionSystem() {} void CollisionSystem::detectCollisions(Array& collisions) { collisions.clear(); auto view = registry.view(); for (auto&& [entity, collider, transform] : view.each()) { if (collider.type == ColliderType::DYNAMIC) { bvh.updateDynamicCollider(entity, collider.boundingbox.getTransformedBox(transform.toMatrix())); } collider.physicsMesh.transform(transform).visualize(); } bvh.visualize(); Array> overlaps; bvh.findOverlaps(overlaps); for (auto pair : overlaps) { if (checkCollision(pair)) { collisions.add(Collision{ .a = pair.key, .b = pair.value, }); } } } bool CollisionSystem::checkCollision(Pair pair) { const auto& [collider1, transform1] = registry.get(pair.key); const auto& [collider2, transform2] = registry.get(pair.value); ShapeBase shape1 = collider1.physicsMesh.transform(transform1); ShapeBase shape2 = collider2.physicsMesh.transform(transform2); Witness witness; if (cachedWitness.exists(pair)) { witness = cachedWitness[pair]; } if (witnessValid(witness, shape1, shape2)) { return false; } return createWitness(witness, shape1, shape2); } void CollisionSystem::updateWitness(Witness& result, const glm::vec3& point, const glm::vec3& v1, const glm::vec3& v2) { const glm::vec3 faceNormal = glm::normalize(glm::cross(v1, v2)); result.n = faceNormal; result.p = point; } bool CollisionSystem::createWitness(Witness& result, const ShapeBase& source, const ShapeBase& other) { for (size_t i = 0; i < source.indices.size(); i += 3) { const glm::vec3 point1 = source.vertices[source.indices[i + 0]]; const glm::vec3 point2 = source.vertices[source.indices[i + 1]]; const glm::vec3 point3 = source.vertices[source.indices[i + 2]]; const glm::vec3 v1 = point2 - point1; const glm::vec3 v2 = point3 - point1; updateWitness(result, point1, v1, v2); result.point1Index = source.indices[i + 0]; result.point2Index = source.indices[i + 1]; result.point3Index = source.indices[i + 2]; result.point4Index = UINT32_MAX; bool valid = witnessValid(result, source, other); // if not, it is a valid separating plane, so no collision if (valid) { return false; } } for (size_t i = 0; i < source.indices.size(); i += 3) { auto findEdgePlane = [this, &source, &other, &result](uint32_t point1Index, uint32_t point2Index) { const glm::vec3 point1 = source.vertices[point1Index]; const glm::vec3 point2 = source.vertices[point2Index]; result.point1Index = point1Index; result.point2Index = point2Index; const glm::vec3 d1 = point2 - point1; for (size_t j = 0; j < other.indices.size(); j += 3) { for (const auto& [point3Index, point4Index] : {std::pair(j + 1, j), std::pair(j + 2, j + 1), std::pair(j, j + 2)}) { result.point3Index = other.indices[point3Index]; result.point4Index = other.indices[point4Index]; const glm::vec3 point3 = other.vertices[result.point3Index]; const glm::vec3 point4 = other.vertices[result.point4Index]; const glm::vec3 d2 = point3 - point4; updateWitness(result, point1, d2, d1); if (witnessValid(result, source, other)) { return true; } } } return false; }; if (findEdgePlane(source.indices[i], source.indices[i + 1])) { return false; } if (findEdgePlane(source.indices[i + 1], source.indices[i + 2])) { return false; } if (findEdgePlane(source.indices[i + 2], source.indices[i])) { return false; } } // no separating plane was found, collision return true; } bool CollisionSystem::witnessValid(const Witness& witness, const Component::ShapeBase& shape1, const Component::ShapeBase& shape2) { const float e = 0.0001f; for (size_t i = 0; i < shape1.vertices.size(); i++) { if (glm::dot(witness.n, shape1.vertices[i] - witness.p) > e) { // something intersecting the separating plane // it is not valid anymore return false; } } for (size_t i = 0; i < shape2.vertices.size(); i++) { if (glm::dot(witness.n, shape2.vertices[i] - witness.p) < -e) { // something intersecting the separating plane // it is not valid anymore return false; } } return true; }