diff --git a/bin/data/entities/physics_prop.json b/bin/data/entities/physics_prop.json index 061b363c..a675afcb 100644 --- a/bin/data/entities/physics_prop.json +++ b/bin/data/entities/physics_prop.json @@ -6,12 +6,12 @@ "metadata": { "holdable": true, "physics": { - "gravity": [ 0, 0, 0 ], + // "gravity": [ 0, -1, 0 ], + "mass": 100, "inertia": false, - - "mass": 1, - "type": "aabb", - "recenter": false + // "type": "bounding box" + "type": "mesh" + // "type": "hull" } } } \ No newline at end of file diff --git a/bin/data/entities/prop.json b/bin/data/entities/prop.json index e3db7789..9771eaec 100644 --- a/bin/data/entities/prop.json +++ b/bin/data/entities/prop.json @@ -10,6 +10,7 @@ "inertia": false, "type": "bounding box" // "type": "mesh" + // "type": "mesh" } } } \ No newline at end of file diff --git a/engine/inc/uf/utils/math/physics/impl.h b/engine/inc/uf/utils/math/physics/impl.h index 935c5ecd..4aaf9010 100644 --- a/engine/inc/uf/utils/math/physics/impl.h +++ b/engine/inc/uf/utils/math/physics/impl.h @@ -22,6 +22,7 @@ namespace pod { CAPSULE, TRIANGLE, MESH, + CONVEX_HULL, }; struct SupportPoint { @@ -107,6 +108,12 @@ namespace pod { const uf::Mesh* mesh; }; + // MIGHT contain additional data, so far mirrors the above + struct ConvexHullBVH { + pod::BVH* bvh; + const uf::Mesh* mesh; + }; + typedef uint32_t CollisionMask; @@ -147,6 +154,7 @@ namespace pod { pod::Capsule capsule; pod::TriangleWithNormal triangle; pod::MeshBVH mesh; + pod::ConvexHullBVH convexHull; }; }; @@ -176,7 +184,8 @@ namespace pod { bool isStatic = false; float mass = 1.0f; - float inverseMass = 1.0f; + float inverseMass = 1.0f; // for fast division + int32_t viewIndex = -1; // -1 means it's not an aliased view /*alignas(16)*/ pod::Vector3f offset = {}; @@ -232,9 +241,9 @@ namespace pod { bool warmupSolver = true; // cache manifold data to warm up the solver bool blockContactSolver = true; // use BlockNxN solvers (where N = number of contacts for a manifold) bool psgContactSolver = true; // use PSG contact solver - bool useGjk = false; // currently don't have a way to broadphase mesh => narrowphase tri via GJK + bool useGjk = true; // currently don't have a way to broadphase mesh => narrowphase tri via GJK bool fixedStep = true; // run physics simulation with a fixed delta time (with accumulation), rather than rely on actual engine deltatime - uint32_t substeps = 1; // number of substeps per frame tick + uint32_t substeps = 4; // number of substeps per frame tick uint32_t reserveCount = 32; // amount of elements to reserve for vectors used in this system, to-do: have it tie to a memory pool allocator // increasing these make things lag for reasons I can imagine why @@ -333,7 +342,7 @@ namespace uf { pod::PhysicsBody& UF_API create( uf::Object& object, const pod::Sphere& sphere, float mass = 0.0f, const pod::Vector3f& = {} ); pod::PhysicsBody& UF_API create( uf::Object& object, const pod::Plane& plane, float mass = 0.0f, const pod::Vector3f& = {} ); pod::PhysicsBody& UF_API create( uf::Object& object, const pod::Capsule& capsule, float mass = 0.0f, const pod::Vector3f& = {} ); - pod::PhysicsBody& UF_API create( uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {} ); + pod::PhysicsBody& UF_API create( uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {}, bool convex = false ); pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, float mass = 0.0f, const pod::Vector3f& = {} ); pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::AABB& aabb, float mass = 0.0f, const pod::Vector3f& = {} ); @@ -341,7 +350,7 @@ namespace uf { pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::Plane& plane, float mass = 0.0f, const pod::Vector3f& = {} ); pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::Capsule& capsule, float mass = 0.0f, const pod::Vector3f& = {} ); pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::TriangleWithNormal& tri, float mass = 0.0f, const pod::Vector3f& = {} ); - pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {} ); + pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {}, bool convex = false ); void UF_API destroy( uf::Object& ); void UF_API destroy( pod::PhysicsBody& ); diff --git a/engine/inc/uf/utils/math/transform.h b/engine/inc/uf/utils/math/transform.h index 4d0ca165..f887f214 100644 --- a/engine/inc/uf/utils/math/transform.h +++ b/engine/inc/uf/utils/math/transform.h @@ -56,7 +56,7 @@ namespace uf { template pod::Transform /*UF_API*/ inverse(const pod::Transform& t); template pod::Vector3t /*UF_API*/ apply( const pod::Transform& transform, const pod::Vector3t& point ); template pod::Vector3t /*UF_API*/ applyInverse(const pod::Transform& t, const pod::Vector3t& worldPoint); - + template pod::Transform /*UF_API*/ relative(const pod::Transform& a, const pod::Transform& b); template uf::stl::string /*UF_API*/ toString( const pod::Transform&, bool flatten = true ); template ext::json::Value /*UF_API*/ encode( const pod::Transform&, bool flatten = true, const ext::json::EncodingSettings& = {} ); diff --git a/engine/inc/uf/utils/math/transform/transform.inl b/engine/inc/uf/utils/math/transform/transform.inl index e49806a2..8864406b 100644 --- a/engine/inc/uf/utils/math/transform/transform.inl +++ b/engine/inc/uf/utils/math/transform/transform.inl @@ -1,4 +1,5 @@ -template pod::Transform& /*UF_API*/ uf::transform::initialize( pod::Transform& transform ) { +template +pod::Transform& /*UF_API*/ uf::transform::initialize( pod::Transform& transform ) { transform.position = {0, 0, 0}; transform.scale = {1, 1, 1}; @@ -7,17 +8,20 @@ template pod::Transform& /*UF_API*/ uf::transform::initialize( po transform.forward = {0, 0, 1}; transform.orientation = {0, 0, 0, 1}; - transform.model = uf::matrix::identity(); - - transform.reference = NULL; + transform.reference = nullptr; + return transform; } -template pod::Transform /*UF_API*/ uf::transform::initialize() { + +template +pod::Transform /*UF_API*/ uf::transform::initialize() { pod::Transform transform; - return transform = uf::transform::initialize(transform); + return uf::transform::initialize(transform); } -template pod::Transform& /*UF_API*/ uf::transform::lookAt( pod::Transform& transform, const pod::Vector3t& at ) { + +template +pod::Transform& /*UF_API*/ uf::transform::lookAt( pod::Transform& transform, const pod::Vector3t& at ) { pod::Vector3t forward = uf::vector::normalize( at - transform.position ); pod::Vector3t right = uf::vector::normalize(uf::vector::cross( forward, transform.up )); pod::Vector3t up = uf::vector::normalize(uf::vector::cross(at, right)); @@ -25,132 +29,139 @@ template pod::Transform& /*UF_API*/ uf::transform::lookAt( pod::T transform.up = up; transform.right = right; transform.forward = forward; - transform.orientation = uf::quaternion::lookAt( at, up ); + return transform; } -template pod::Transform& /*UF_API*/ uf::transform::move( pod::Transform& transform, const pod::Vector3t& axis, pod::Math::num_t delta ) { + +template +pod::Transform& /*UF_API*/ uf::transform::move( pod::Transform& transform, const pod::Vector3t& axis, pod::Math::num_t delta ) { transform.position += (axis * delta); return transform; } -template pod::Transform& /*UF_API*/ uf::transform::move( pod::Transform& transform, const pod::Vector3t& delta ) { + +template +pod::Transform& /*UF_API*/ uf::transform::move( pod::Transform& transform, const pod::Vector3t& delta ) { transform.position += delta; return transform; } -template pod::Transform& /*UF_API*/ uf::transform::reorient( pod::Transform& transform ) { -/* - transform.up = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 1.0f, 0.0f})); - transform.right = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{1.0f, 0.0f, 0.0f})); - transform.forward = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 0.0f, 1.0f})); -*/ + +template +pod::Transform& /*UF_API*/ uf::transform::reorient( pod::Transform& transform ) { pod::Quaternion q = transform.orientation; transform.forward = { 2 * (q.x * q.z + q.w * q.y), 2 * (q.y * q.x - q.w * q.x), 1 - 2 * (q.x * q.x + q.y * q.y) }; transform.up = { 2 * (q.x * q.y - q.w * q.z), 1 - 2 * (q.x * q.x + q.z * q.z), 2 * (q.y * q.z + q.w * q.x)}; transform.right = { 1 - 2 * (q.y * q.y + q.z * q.z), 2 * (q.x * q.y + q.w * q.z), 2 * (q.x * q.z - q.w * q.y)}; return transform; } -template pod::Transform /*UF_API*/ uf::transform::reorient( const pod::Transform& _transform ) { -/* - transform.up = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 1.0f, 0.0f})); - transform.right = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{1.0f, 0.0f, 0.0f})); - transform.forward = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 0.0f, 1.0f})); -*/ + +template +pod::Transform /*UF_API*/ uf::transform::reorient( const pod::Transform& _transform ) { pod::Transform transform = _transform; - pod::Quaternion q = transform.orientation; - transform.forward = { 2 * (q.x * q.z + q.w * q.y), 2 * (q.y * q.x - q.w * q.x), 1 - 2 * (q.x * q.x + q.y * q.y) }; - transform.up = { 2 * (q.x * q.y - q.w * q.z), 1 - 2 * (q.x * q.x + q.z * q.z), 2 * (q.y * q.z + q.w * q.x)}; - transform.right = { 1 - 2 * (q.y * q.y + q.z * q.z), 2 * (q.x * q.y + q.w * q.z), 2 * (q.x * q.z - q.w * q.y)}; - return transform; + return uf::transform::reorient(transform); } -template pod::Transform& /*UF_API*/ uf::transform::rotate( pod::Transform& transform, const pod::Vector3t& axis, pod::Math::num_t delta ) { + +template +pod::Transform& /*UF_API*/ uf::transform::rotate( pod::Transform& transform, const pod::Vector3t& axis, pod::Math::num_t delta ) { pod::Quaternion<> quat = uf::quaternion::axisAngle( axis, delta ); - transform.orientation = uf::vector::normalize(uf::quaternion::multiply(quat, transform.orientation)); - transform = uf::transform::reorient(transform); - - return transform; + return uf::transform::reorient(transform); } -template pod::Transform& /*UF_API*/ uf::transform::rotate( pod::Transform& transform, const pod::Quaternion& quat ) { + +template +pod::Transform& /*UF_API*/ uf::transform::rotate( pod::Transform& transform, const pod::Quaternion& quat ) { transform.orientation = uf::vector::normalize(uf::quaternion::multiply(quat, transform.orientation)); - transform = uf::transform::reorient(transform); - - return transform; + return uf::transform::reorient(transform); } -template pod::Transform& /*UF_API*/ uf::transform::scale( pod::Transform& transform, const pod::Vector3t& factor ) { + +template +pod::Transform& /*UF_API*/ uf::transform::scale( pod::Transform& transform, const pod::Vector3t& factor ) { transform.scale = factor; return transform; } -template inline pod::Transform /*UF_API*/ uf::transform::condense( const pod::Transform& transform ) { + +template +inline pod::Transform /*UF_API*/ uf::transform::condense( const pod::Transform& transform ) { return uf::transform::fromMatrix( uf::transform::model( transform, false ) ); } -template pod::Transform /*UF_API*/ uf::transform::flatten( const pod::Transform& transform, size_t depth ) { + +template +pod::Transform /*UF_API*/ uf::transform::flatten( const pod::Transform& transform, size_t depth ) { if ( !transform.reference ) return transform; -// if ( depth == SIZE_MAX ) return uf::transform::condense( transform ); + pod::Transform combined = transform; - combined.reference = NULL; + combined.reference = nullptr; const pod::Transform* pointer = transform.reference; + while ( pointer && depth-- > 0 ) { - // for some reason I can't just += for the camera combined.position = { combined.position.x + pointer->position.x, combined.position.y + pointer->position.y, combined.position.z + pointer->position.z, }; - pod::Quaternion orientation = pointer->orientation; - combined.orientation = uf::quaternion::multiply( orientation, combined.orientation ); + combined.orientation = uf::quaternion::multiply( pointer->orientation, combined.orientation ); combined.scale = { combined.scale.x * pointer->scale.x, combined.scale.y * pointer->scale.y, combined.scale.z * pointer->scale.z, }; combined.model = pointer->model * combined.model; + if ( pointer == pointer->reference ) break; pointer = pointer->reference; } - return combined = uf::transform::reorient(combined); + return uf::transform::reorient(combined); } -template pod::Matrix4t /*UF_API*/ uf::transform::model( const pod::Transform& transform, bool flatten, size_t depth ) { + +template +pod::Matrix4t /*UF_API*/ uf::transform::model( const pod::Transform& transform, bool flatten, size_t depth ) { if ( flatten ) { - pod::Transform flatten = uf::transform::flatten(transform, depth); - return uf::matrix::translate( uf::matrix::identity(), flatten.position ) * - uf::quaternion::matrix( flatten.orientation ) * - uf::matrix::scale( uf::matrix::identity(), flatten.scale ) * - flatten.model; + pod::Transform flat = uf::transform::flatten(transform, depth); + return uf::matrix::translate( uf::matrix::identity(), flat.position ) * + uf::quaternion::matrix( flat.orientation ) * + uf::matrix::scale( uf::matrix::identity(), flat.scale ) * + flat.model; } pod::Matrix4t matrix = uf::matrix::identity(); const pod::Transform* pointer = &transform; + do { - pod::Matrix4t model = uf::matrix::translate( uf::matrix::identity(), pointer->position ) * + pod::Matrix4t modelMat = uf::matrix::translate( uf::matrix::identity(), pointer->position ) * uf::quaternion::matrix( pointer->orientation ) * uf::matrix::scale( uf::matrix::identity(), pointer->scale ) * pointer->model; - matrix = model * matrix; + matrix = modelMat * matrix; + if ( pointer == pointer->reference ) break; pointer = pointer->reference; - } while ( pointer && --depth >= 0 ); + } while ( pointer && --depth > 0 ); + return matrix; } -template pod::Transform uf::transform::fromMatrix( const pod::Matrix4t& matrix ) { + +template +pod::Transform uf::transform::fromMatrix( const pod::Matrix4t& matrix ) { pod::Transform transform; transform.position = uf::matrix::multiply( matrix, pod::Vector4f{ 0, 0, 0, 1 } ); transform.orientation = uf::quaternion::fromMatrix( matrix ); transform.model = matrix; - return transform = reorient( transform ); + return reorient( transform ); } -template pod::Transform& /*UF_API*/ uf::transform::reference( pod::Transform& transform, const pod::Transform& parent, bool reorient ) { +template +pod::Transform& /*UF_API*/ uf::transform::reference( pod::Transform& transform, const pod::Transform& parent, bool reorient ) { + transform.reference = const_cast*>(&parent); if ( !reorient ) { - transform.reference = const_cast*>(&parent); return transform; } transform.position = parent.position - transform.position; - // reorient orientation here too - transform.reference = const_cast*>(&parent); return transform; } -template pod::Transform /*UF_API*/ uf::transform::interpolate( const pod::Transform& from, const pod::Transform& to, float factor, bool reorient ) { + +template +pod::Transform /*UF_API*/ uf::transform::interpolate( const pod::Transform& from, const pod::Transform& to, float factor, bool reorient ) { pod::Transform transform = to; transform.position = uf::vector::lerp( from.position, to.position, factor ); transform.orientation = uf::quaternion::slerp( from.orientation, to.orientation, factor ); @@ -159,48 +170,50 @@ template pod::Transform /*UF_API*/ uf::transform::interpolate( co template pod::Transform uf::transform::inverse(const pod::Transform& t) { - pod::Transform inv; + pod::Transform inv; - // Inverse orientation = conjugate of normalized quaternion - pod::Quaternion qInv = uf::quaternion::conjugate(uf::vector::normalize(t.orientation)); + pod::Quaternion qInv = uf::quaternion::conjugate(uf::vector::normalize(t.orientation)); - // Inverse scale is reciprocal (with protection against zero) - pod::Vector3t sInv { - (fabs(t.scale.x) > 1e-8) ? (1.0f / t.scale.x) : 0.0f, - (fabs(t.scale.y) > 1e-8) ? (1.0f / t.scale.y) : 0.0f, - (fabs(t.scale.z) > 1e-8) ? (1.0f / t.scale.z) : 0.0f - }; + pod::Vector3t sInv { + (fabs(t.scale.x) > 1e-8) ? (1.0f / t.scale.x) : 0.0f, + (fabs(t.scale.y) > 1e-8) ? (1.0f / t.scale.y) : 0.0f, + (fabs(t.scale.z) > 1e-8) ? (1.0f / t.scale.z) : 0.0f + }; - inv.scale = sInv; - inv.orientation = qInv; + inv.scale = sInv; + inv.orientation = qInv; - // To invert translation: apply inverse orientation/scale - pod::Vector3t negPos = -t.position; - inv.position = uf::quaternion::rotate(qInv, pod::Vector3f{ negPos.x * sInv.x, - negPos.y * sInv.y, - negPos.z * sInv.z }); + pod::Vector3t negPos = -t.position; + inv.position = uf::quaternion::rotate(qInv, pod::Vector3f{ + negPos.x * sInv.x, + negPos.y * sInv.y, + negPos.z * sInv.z + }); - // Update basis vectors - inv = uf::transform::reorient(inv); - - return inv; + return uf::transform::reorient(inv); } - -template // Normalizes a vector -pod::Vector3t /*UF_API*/ uf::transform::apply( const pod::Transform& transform, const pod::Vector3t& point ) { - // return uf::transform::model( transform ) * point; - return uf::matrix::multiply( uf::transform::model( transform ), point ); -} - -// Apply inverse transform to a point template -pod::Vector3t uf::transform::applyInverse(const pod::Transform& t, const pod::Vector3t& worldPoint) { - pod::Transform inv = inverse(t); - return uf::transform::apply(inv, worldPoint); +pod::Vector3t /*UF_API*/ uf::transform::apply( const pod::Transform& t, const pod::Vector3t& p ) { + return uf::quaternion::rotate( t.orientation, p * t.scale ) + t.position; } -template // Normalizes a vector +template +pod::Vector3t uf::transform::applyInverse( const pod::Transform& t, const pod::Vector3t& worldPoint ) { + pod::Vector3t translated = worldPoint - t.position; + return uf::quaternion::rotate(uf::quaternion::inverse(t.orientation), translated) / t.scale; +} + +template +pod::Transform uf::transform::relative( const pod::Transform& a, const pod::Transform& b ) { + pod::Transform rel; + auto invOriA = uf::quaternion::inverse(a.orientation); + rel.position = uf::quaternion::rotate(invOriA, b.position - a.position); + rel.orientation = invOriA * b.orientation; + return rel; +} + +template uf::stl::string /*UF_API*/ uf::transform::toString( const pod::Transform& t, bool flatten ) { pod::Transform transform = flatten ? uf::transform::flatten(t) : t; uf::stl::stringstream ss; @@ -218,46 +231,26 @@ ext::json::Value /*UF_API*/ uf::transform::encode( const pod::Transform& t, b json["model"] = uf::matrix::encode(transform.model, settings); return json; } + template pod::Transform& /*UF_API*/ uf::transform::decode( const ext::json::Value& _json, pod::Transform& transform ) { - ext::json::Value json = _json; -// transform.position = uf::vector::decode(json["position"], transform.position); -// transform.scale = uf::vector::decode(json["scale"], transform.scale); -// transform.orientation = uf::vector::decode(json["orientation"], transform.orientation); -// if ( ext::json::isArray(json["position"]) || ext::json::isObject(json["position"]) ) - transform.position = uf::vector::decode(json["position"], transform.position); -// if ( ext::json::isArray(json["scale"]) || ext::json::isObject(json["scale"]) ) - transform.scale = uf::vector::decode(json["scale"], transform.scale); -// if ( ext::json::isArray(json["orientation"]) || ext::json::isObject(json["orientation"]) ) - transform.orientation = uf::vector::decode(json["orientation"], transform.orientation); + ext::json::Value json = _json; // cringe null behavior + transform.position = uf::vector::decode(json["position"], transform.position); + transform.scale = uf::vector::decode(json["scale"], transform.scale); + transform.orientation = uf::vector::decode(json["orientation"], transform.orientation); + if ( ext::json::isObject(json["rotation"]) && !ext::json::isNull(json["rotation"]["axis"]) && !ext::json::isNull(json["rotation"]["angle"]) ) { pod::Vector3t axis = uf::vector::decode(json["rotation"]["axis"]); T angle = json["rotation"]["angle"].as(); transform.orientation = uf::quaternion::axisAngle( axis, angle ); } -// if ( ext::json::isArray(json["model"]) ) - transform.model = uf::matrix::decode(json["model"], transform.model); + + transform.model = uf::matrix::decode(json["model"], transform.model); return transform; } + template -pod::Transform /*UF_API*/ uf::transform::decode( const ext::json::Value& _json, const pod::Transform& t ) { - ext::json::Value json = _json; +pod::Transform /*UF_API*/ uf::transform::decode( const ext::json::Value& json, const pod::Transform& t ) { pod::Transform transform = t; -// transform.position = uf::vector::decode(json["position"], transform.position); -// transform.scale = uf::vector::decode(json["scale"], transform.scale); -// transform.orientation = uf::vector::decode(json["orientation"], transform.orientation); -// if ( ext::json::isArray(json["position"]) || ext::json::isObject(json["position"]) ) - transform.position = uf::vector::decode(json["position"], transform.position); -// if ( ext::json::isArray(json["scale"]) || ext::json::isObject(json["scale"]) ) - transform.scale = uf::vector::decode(json["scale"], transform.scale); -// if ( ext::json::isArray(json["orientation"]) || ext::json::isObject(json["orientation"]) ) - transform.orientation = uf::vector::decode(json["orientation"], transform.orientation); - if ( ext::json::isObject(json["rotation"]) && !ext::json::isNull(json["rotation"]["axis"]) && !ext::json::isNull(json["rotation"]["angle"]) ) { - pod::Vector3t axis = uf::vector::decode(json["rotation"]["axis"]); - T angle = json["rotation"]["angle"].as(); - transform.orientation = uf::quaternion::axisAngle( axis, angle ); - } -// if ( ext::json::isArray(json["model"]) ) - transform.model = uf::matrix::decode(json["model"], transform.model); - return transform; + return uf::transform::decode(json, transform); } \ No newline at end of file diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 5da94bfa..c3a3079c 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -1317,7 +1317,8 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) if ( ext::json::isObject( phyziks ) ) { uf::stl::string type = phyziks["type"].as(); - if ( type != "mesh" ) { + bool isMesh = type == "mesh" || type == "hull"; + if ( !isMesh ) { auto min = bounds.min; // uf::matrix::multiply( model, bounds.min, 1.0f ); auto max = bounds.max; // uf::matrix::multiply( model, bounds.max, 1.0f ); @@ -1330,7 +1331,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) if ( ext::json::isNull( metadataJson["physics"]["max"] ) ) metadataJson["physics"]["max"] = uf::vector::encode( max ); } #if !UF_GRAPH_EXTENDED - if ( type == "mesh" ) { + if ( isMesh ) { auto& physicsBody = entity.getComponent(); float mass = phyziks["mass"].as(physicsBody.mass); @@ -1338,8 +1339,9 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) physicsBody.material.restitution = phyziks["restitution"].as(physicsBody.material.restitution); physicsBody.inertiaTensor = uf::vector::decode( phyziks["inertia"], physicsBody.inertiaTensor ); physicsBody.gravity = uf::vector::decode( phyziks["gravity"], physicsBody.gravity ); - - uf::physics::impl::create( entity.as(), mesh, mass ); + auto center = uf::vector::decode( phyziks["center"], pod::Vector3f{} ); + + uf::physics::impl::create( entity.as(), mesh, mass, center, type != "mesh" ); } #endif } @@ -1938,15 +1940,16 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) { } } // bind mesh to physics state - // to-do: figure out why the mesh just suddenly breaks when re-streamed in dreamcast (could just be the version of reactphysics) + // to-do: test if this works in the internal physics system (the entire reason why I wrote it was because of this mess) { auto phyziks = tag["physics"]; if ( !ext::json::isObject( phyziks ) ) phyziks = metadataJson["physics"]; else metadataJson["physics"] = phyziks; if ( ext::json::isObject( phyziks ) ) { - uf::stl::string type = phyziks["type"].as(); - if ( type == "mesh" ) { + uf::stl::string type = phyziks["type"].as(); + bool isMesh = type == "mesh" || type == "hull"; + if ( isMesh ) { bool exists = entity.hasComponent(); if ( exists ) { uf::physics::impl::destroy( entity ); @@ -1959,8 +1962,9 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) { physicsBody.material.restitution = phyziks["restitution"].as(physicsBody.material.restitution); physicsBody.inertiaTensor = uf::vector::decode( phyziks["inertia"], physicsBody.inertiaTensor ); physicsBody.gravity = uf::vector::decode( phyziks["gravity"], physicsBody.gravity ); + auto center = uf::vector::decode( phyziks["center"], pod::Vector3f{} ); - uf::physics::impl::create( entity.as(), mesh, mass ); + uf::physics::impl::create( entity.as(), mesh, mass, center, type != "mesh" ); } } } diff --git a/engine/src/utils/math/physics/aabb.inl b/engine/src/utils/math/physics/aabb.inl index 691b2811..4141f0f2 100644 --- a/engine/src/utils/math/physics/aabb.inl +++ b/engine/src/utils/math/physics/aabb.inl @@ -30,6 +30,16 @@ namespace { }; } + pod::AABB computeConvexHullAABB( const uf::Mesh::View& view, const uf::Mesh::AttributeView& positions, pod::AABB bounds = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } } ) { + for ( size_t i = 0; i < view.vertex.count; ++i ) { + pod::Vector3f v = ::getVertex( view, positions, i ); + bounds.min = uf::vector::min( bounds.min, v ); + bounds.max = uf::vector::max( bounds.max, v ); + } + + return bounds; + } + FORCE_INLINE pod::AABB mergeAabb( const pod::AABB& a, const pod::AABB& b ) { return { uf::vector::min( a.min, b.min ), @@ -106,6 +116,21 @@ namespace { transform.position + body.collider.mesh.bvh->bounds[0].min, transform.position + body.collider.mesh.bvh->bounds[0].max, }; + const auto& meshData = *body.collider.mesh.mesh; + pod::AABB bounds = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } }; + for ( const auto& view : meshData.buffer_views ) ::computeConvexHullAABB( view, view["position"], bounds ); + return ::transformAabbToWorld( bounds, transform ); + } break; + case pod::ShapeType::CONVEX_HULL: { + if ( body.collider.convexHull.bvh && !body.collider.convexHull.bvh->bounds.empty() ) + return { + transform.position + body.collider.convexHull.bvh->bounds[0].min, + transform.position + body.collider.convexHull.bvh->bounds[0].max, + }; + const auto& meshData = *body.collider.convexHull.mesh; + pod::AABB bounds = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } }; + for ( const auto& view : meshData.buffer_views ) ::computeConvexHullAABB( view, view["position"], bounds ); + return ::transformAabbToWorld( bounds, transform ); } break; default: { } break; @@ -280,4 +305,8 @@ namespace { ASSERT_COLLIDER_TYPES( AABB, MESH ); REVERSE_COLLIDER( a, b, meshAabb ); } + bool aabbHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( AABB, CONVEX_HULL ); + REVERSE_COLLIDER( a, b, hullAabb ); + } } \ No newline at end of file diff --git a/engine/src/utils/math/physics/bvh.inl b/engine/src/utils/math/physics/bvh.inl index 90c7230d..6fb2d6a5 100644 --- a/engine/src/utils/math/physics/bvh.inl +++ b/engine/src/utils/math/physics/bvh.inl @@ -6,6 +6,7 @@ namespace { void queryFlatOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs ); void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ); + void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs ); void postprocessPairs( pod::BVH::pairs_t& pairs ) { std::sort(pairs.begin(), pairs.end()); @@ -263,6 +264,42 @@ namespace { // mark as clean bvh.dirty = false; } + + void buildConvexHullBVH( pod::BVH& bvh, const uf::Mesh& mesh, pod::BVH::index_t capacity = 1 ) { + const auto& views = mesh.buffer_views; + UF_ASSERT( !views.empty() ); + + uint32_t hullCount = views.size(); + + bvh.indices.clear(); + bvh.nodes.clear(); + bvh.indices.reserve( hullCount ); + + // stores bounds + uf::stl::vector bounds; + bounds.reserve( hullCount ); + + // populate initial indices and bounds + for ( size_t viewID = 0; viewID < hullCount; ++viewID ) { + const auto& view = views[viewID]; + auto aabb = ::computeConvexHullAABB( view, view["position"] ); + + bounds.emplace_back( aabb ); + bvh.indices.emplace_back( viewID ); + } + + UF_ASSERT( !bounds.empty() ); + + // recursively build BVH from indices + if ( uf::physics::impl::settings.useBvhSahMeshes ) ::buildBVHNode_SAH( bvh, bounds, 0, bvh.indices.size(), capacity ); + else ::buildBVHNode( bvh, bounds, 0, bvh.indices.size(), capacity ); + + // flatten if requested + if ( uf::physics::impl::settings.flattenBvhMeshes ) ::flattenBVH( bvh, 0 ); + + // mark as clean + bvh.dirty = false; + } } namespace { @@ -528,6 +565,43 @@ namespace { } } + void traverseNodePair( const pod::BVH& bvhA, pod::BVH::index_t nodeAID, const pod::BVH& bvhB, pod::BVH::index_t nodeBID, const pod::Transform<>& relTransform, pod::BVH::pairs_t& pairs ) { + const auto& nodeA = bvhA.nodes[nodeAID]; + const auto& nodeB = bvhB.nodes[nodeBID]; + + if ( nodeA.isAsleep() && nodeB.isAsleep() ) return; + + pod::AABB boundsB_in_A = ::transformAabbToWorld(bvhB.bounds[nodeBID], relTransform); + if ( !::aabbOverlap( bvhA.bounds[nodeAID], boundsB_in_A ) ) return; + + if ( nodeA.getCount() > 0 && nodeB.getCount() > 0 ) { + for ( auto i = 0; i < nodeA.getCount(); ++i ) { + for ( auto j = 0; j < nodeB.getCount(); ++j ) { + pairs.emplace_back(bvhA.indices[nodeA.start + i], bvhB.indices[nodeB.start + j]); + } + } + return; + } + + if ( nodeA.getCount() == 0 && nodeB.getCount() == 0 ) { + if ( ::aabbSurfaceArea(bvhA.bounds[nodeAID]) > ::aabbSurfaceArea(boundsB_in_A) ) { + ::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, relTransform, pairs ); + ::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, relTransform, pairs ); + } else { + ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, relTransform, pairs ); + ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, relTransform, pairs ); + } + } + else if ( nodeA.getCount() == 0 ) { + ::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, relTransform, pairs ); + ::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, relTransform, pairs ); + } + else if ( nodeB.getCount() == 0 ) { + ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, relTransform, pairs ); + ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, relTransform, pairs ); + } + } + void traverseBVH( const pod::BVH& bvh, pod::BVH::index_t nodeID, pod::BVH::pairs_t& pairs ) { const auto& node = bvh.nodes[nodeID]; @@ -568,6 +642,17 @@ namespace { ::postprocessPairs( outPairs ); } + + void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs ) { + if ( !bvhA.flattened.empty() && !bvhB.flattened.empty() ) return ::queryFlatOverlaps( bvhA, bvhB, relTransform, outPairs ); + + if ( bvhA.nodes.empty() || bvhB.nodes.empty() ) return; + outPairs.reserve(uf::physics::impl::settings.reserveCount); + ::traverseNodePair(bvhA, 0, bvhB, 0, relTransform, outPairs); + UF_EXCEPTION("unimplemented"); + + ::postprocessPairs( outPairs ); + } } namespace { @@ -775,6 +860,77 @@ namespace { ::postprocessPairs( outPairs ); } + void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs ) { + auto& nodesA = bvhA.flattened; + auto& boundsA = bvhA.flatBounds; + auto& indicesA = bvhA.indices; + + auto& nodesB = bvhB.flattened; + auto& boundsB = bvhB.flatBounds; + auto& indicesB = bvhB.indices; + + if ( nodesA.empty() || nodesB.empty() ) return; + outPairs.reserve(uf::physics::impl::settings.reserveCount); + + static thread_local uf::stl::vector> stack; + stack.clear(); + stack.emplace_back(0, 0); + + while ( !stack.empty() ) { + auto [a, b] = stack.back(); + stack.pop_back(); + + const auto& nodeA = bvhA.flattened[a]; + const auto& nodeB = bvhB.flattened[b]; + + if ( nodeA.isAsleep() && nodeB.isAsleep() ) continue; + + /* + pod::AABB worldBoundsA = ::transformAabbToWorld( boundsA[a], tA ); + pod::AABB worldBoundsB = ::transformAabbToWorld( boundsB[b], tB ); + if ( !::aabbOverlap( worldBoundsA, worldBoundsB ) ) continue; + */ + pod::AABB boundsB_in_A = ::transformAabbToWorld(boundsB[b], relTransform); + if ( !::aabbOverlap( boundsA[a], boundsB_in_A ) ) continue; + + bool isLeafA = (nodeA.getCount() > 0); + bool isLeafB = (nodeB.getCount() > 0); + + if ( isLeafA && isLeafB ) { + for ( pod::BVH::index_t ia = 0; ia < nodeA.getCount(); ++ia ) { + for ( pod::BVH::index_t ib = 0; ib < nodeB.getCount(); ++ib ) { + auto indexA = bvhA.indices[nodeA.start + ia]; + auto indexB = bvhB.indices[nodeB.start + ib]; + + // if ( indexA > indexB ) std::swap( indexA, indexB ); + outPairs.emplace_back(indexA, indexB); + } + } + } + else if ( isLeafA ) { + pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex; + stack.emplace_back(a, b + 1); + stack.emplace_back(a, rightB); + } + else if ( isLeafB ) { + pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex; + stack.emplace_back(a + 1, b); + stack.emplace_back(rightA, b); + } + else { + pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex; + pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex; + + stack.emplace_back(a + 1, b + 1); + stack.emplace_back(a + 1, rightB); + stack.emplace_back(rightA, b + 1); + stack.emplace_back(rightA, rightB); + } + } + + ::postprocessPairs( outPairs ); + } + void queryFlatBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector& outIndices ) { auto& nodes = bvh.flattened; auto& indices = bvh.indices; diff --git a/engine/src/utils/math/physics/capsule.inl b/engine/src/utils/math/physics/capsule.inl index e31f34e4..1a41675b 100644 --- a/engine/src/utils/math/physics/capsule.inl +++ b/engine/src/utils/math/physics/capsule.inl @@ -94,4 +94,8 @@ namespace { ASSERT_COLLIDER_TYPES( CAPSULE, MESH ); REVERSE_COLLIDER( a, b, meshCapsule ); } + bool capsuleHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( CAPSULE, CONVEX_HULL ); + REVERSE_COLLIDER( a, b, hullCapsule ); + } } \ No newline at end of file diff --git a/engine/src/utils/math/physics/convexHull.inl b/engine/src/utils/math/physics/convexHull.inl new file mode 100644 index 00000000..35d062a4 --- /dev/null +++ b/engine/src/utils/math/physics/convexHull.inl @@ -0,0 +1,86 @@ +namespace { + bool hullGeneric( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + const auto& hull = a; + const auto& body = b; + + const auto& bvh = *hull.collider.convexHull.bvh; + const auto& meshData = *hull.collider.convexHull.mesh; + + // transform to local space for BVH query + auto bounds = ::transformAabbToLocal( body.bounds, ::getTransform( hull ) ); + static thread_local uf::stl::vector candidates; + candidates.clear(); + ::queryBVH( bvh, bounds, candidates ); + + bool hit = false; + // do collision per hull + for ( auto hullID : candidates ) { + auto hullView = ::physicsBodyHullView( hull, hullID ); + + pod::Simplex simplex; + if ( !::gjk( hullView, body, simplex ) ) continue; + + auto result = ::epa( hullView, body, simplex ); + + if ( !uf::vector::isValid(result.point) ) continue; + + manifold.points.emplace_back(result); + hit = true; + } + return hit; + } +} + +namespace { + bool hullAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( CONVEX_HULL, AABB ); + return hullGeneric(a, b, manifold, eps); + } + bool hullSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( CONVEX_HULL, SPHERE ); + return hullGeneric(a, b, manifold, eps); + } + bool hullPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( CONVEX_HULL, PLANE ); + return hullGeneric(a, b, manifold, eps); + } + bool hullCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( CONVEX_HULL, CAPSULE ); + return hullGeneric(a, b, manifold, eps); + } + bool hullMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( CONVEX_HULL, MESH ); + REVERSE_COLLIDER( a, b, meshHull ); + } + bool hullHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( CONVEX_HULL, CONVEX_HULL ); + const auto& bvhA = *a.collider.convexHull.bvh; + const auto& bvhB = *b.collider.convexHull.bvh; + + auto tA = ::getTransform( a ); + auto tB = ::getTransform( b ); + auto relTransform = uf::transform::relative( tA, tB ); + + static thread_local pod::BVH::pairs_t pairs; + pairs.clear(); + ::queryOverlaps( bvhA, bvhB, relTransform, pairs ); + + bool hit = false; + + for (auto [ viewIdA, viewIdB ] : pairs ) { + auto viewA = ::physicsBodyHullView( a, viewIdA ); + auto viewB = ::physicsBodyHullView( b, viewIdB ); + + pod::Simplex simplex; + if ( !::gjk( viewA, viewB, simplex ) ) continue; + + auto result = ::epa( viewA, viewB, simplex ); + + if ( !uf::vector::isValid(result.point) ) continue; + + manifold.points.emplace_back(result); + hit = true; + } + return hit; + } +} \ No newline at end of file diff --git a/engine/src/utils/math/physics/epa.inl b/engine/src/utils/math/physics/epa.inl index f9784544..970fbd4d 100644 --- a/engine/src/utils/math/physics/epa.inl +++ b/engine/src/utils/math/physics/epa.inl @@ -80,7 +80,7 @@ namespace { } } - pod::Contact epa( const pod::PhysicsBody& a, const pod::PhysicsBody& b, const pod::Simplex& simplex, uint32_t maxIterations = 64, float eps = EPS ) { + pod::Contact epa( const pod::PhysicsBody& a, const pod::PhysicsBody& b, const pod::Simplex& simplex, uint32_t maxIterations, float eps ) { UF_ASSERT( ::isValidSimplex(simplex) ); auto faces = ::initialPolytope(simplex); diff --git a/engine/src/utils/math/physics/gjk.inl b/engine/src/utils/math/physics/gjk.inl index 136f93c2..abd4dc62 100644 --- a/engine/src/utils/math/physics/gjk.inl +++ b/engine/src/utils/math/physics/gjk.inl @@ -12,6 +12,18 @@ namespace { ( dir.z >= 0.0f ) ? body.bounds.max.z : body.bounds.min.z }; } break; + case pod::ShapeType::PLANE: { + const auto& plane = body.collider.plane; + pod::Vector3f n = plane.normal; + float d = plane.offset; + + pod::Vector3f basePoint = n * d; + float dot = uf::vector::dot( dir, n ); + if ( std::fabs(dot) > 0.9999f ) return basePoint; + + pod::Vector3f tangent = uf::vector::normalize( dir - (n * dot) ); + return basePoint + tangent * 100000.0f; + } break; case pod::ShapeType::CAPSULE: { auto up = uf::quaternion::rotate( transform.orientation, pod::Vector3f{0,1,0} ); auto p1 = transform.position + up * body.collider.capsule.halfHeight; @@ -29,8 +41,34 @@ namespace { if ( d1 > d2 ) return tri.points[1]; return tri.points[2]; } break; + case pod::ShapeType::CONVEX_HULL: { + const auto transform = ::getTransform( body ); + const auto& mesh = *body.collider.convexHull.mesh; + auto selectedViewIdx = body.viewIndex; + + pod::Vector3f localDir = uf::quaternion::rotate( uf::quaternion::inverse(transform.orientation), dir ); + float maxDist = -FLT_MAX; + pod::Vector3f furthestVertex; + + for ( auto viewIdx = 0; viewIdx < mesh.buffer_views.size(); ++viewIdx ) { + if ( 0 <= selectedViewIdx && selectedViewIdx != viewIdx ) continue; // cringe, but saves code duplication (could just alter the bounds above) + const auto& view = mesh.buffer_views[viewIdx]; + auto& positions = view["position"]; + for ( size_t i = 0; i < view.vertex.count; ++i ) { + pod::Vector3f v = ::getVertex( view, positions, i ); + float dist = uf::vector::dot( v, localDir ); + if ( dist > maxDist ) { + maxDist = dist; + furthestVertex = v; + } + } + } + + return uf::transform::apply( transform, furthestVertex ); + } break; default: { + UF_EXCEPTION("unsupported shape: {}", (int)body.collider.type); } break; } return {}; @@ -150,7 +188,7 @@ namespace { return false; } - bool gjk( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Simplex& simplex, int maxIterations = 20, float eps = EPS ) { + bool gjk( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Simplex& simplex, int maxIterations, float eps ) { const float eps2 = eps * eps; auto dir = ::getPosition( b ) - ::getPosition( a ); if ( uf::vector::magnitude( dir ) < eps2 ) dir = {1,0,0}; // fallback direction @@ -205,4 +243,70 @@ namespace { return !simplex.pts.empty(); #endif } +} + +// GJK ray-cast +namespace { + pod::Vector3f closestPointOnSimplex( const pod::Vector3f& x, pod::Vector3f* simplex, int& sCount ) { + if ( sCount == 1 ) { + return simplex[0]; + } else if ( sCount == 2 ) { + auto p = ::closestPointOnSegment( x, simplex[0], simplex[1] ); + return p; + } else if ( sCount == 3 ) { + auto p = ::closestPointOnTriangle( x, simplex[0], simplex[1], simplex[2] ); + return p; + } + + return x; + } + + bool gjk( const pod::Ray& ray, const pod::PhysicsBody& body, float maxDist, float& outT, pod::Vector3f& outNormal, float eps ) { + float t = 0.0f; + pod::Vector3f x = ray.origin; + pod::Vector3f n = {0, 0, 0}; + + pod::Vector3f supportPoint = ::support( body, -ray.direction ); + pod::Vector3f v = x - supportPoint; + + pod::Vector3f simplex[4]; + int sCount = 0; + + for ( int iter = 0; iter < 32; ++iter ) { + float vSq = uf::vector::dot( v, v ); + // origin was inside the shape to begin with, or we perfectly converged + if ( vSq < eps * eps ) break; + + pod::Vector3f dir = -v; + pod::Vector3f p = ::support( body, dir ); + pod::Vector3f w = x - p; + + if ( uf::vector::dot( v, w ) > 0.0f ) { + float vDotD = uf::vector::dot( v, ray.direction ); + if ( vDotD >= 0.0f ) return false; // miss + + float dt = uf::vector::dot( v, w ) / vDotD; + t = t - dt; + + if ( t > maxDist ) return false; + + x = ray.origin + ray.direction * t; + n = uf::vector::normalize( v ); + + // reset + sCount = 0; + } + + simplex[sCount++] = p; + + pod::Vector3f closest = ::closestPointOnSimplex( x, simplex, sCount ); + v = x - closest; + + if ( sCount == 4 ) break; // collided + } + + outT = t; + outNormal = n; + return true; + } } \ No newline at end of file diff --git a/engine/src/utils/math/physics/helpers.inl b/engine/src/utils/math/physics/helpers.inl index 50db6cb8..e194bd88 100644 --- a/engine/src/utils/math/physics/helpers.inl +++ b/engine/src/utils/math/physics/helpers.inl @@ -11,30 +11,35 @@ namespace { bool aabbPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool aabbCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool aabbMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool aabbHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool sphereSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool sphereAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool spherePlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool sphereCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool sphereMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool sphereHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool planeAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool planeSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool planePlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool planeCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool planeMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool planeHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool capsuleCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool capsuleAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool capsulePlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool capsuleSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool capsuleMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool capsuleHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool meshAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool meshSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool meshPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool meshCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool meshMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool meshHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); bool triangleTriangle( const pod::TriangleWithNormal& a, const pod::TriangleWithNormal& b, pod::Manifold& manifold, float eps = EPS ); bool triangleAabb( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps = EPS ); @@ -42,10 +47,23 @@ namespace { bool trianglePlane( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps = EPS ); bool triangleCapsule( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps = EPS ); + bool hullAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool hullSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool hullPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool hullCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool hullMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + bool hullHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS ); + // ugh FORCE_INLINE bool aabbOverlap( const pod::AABB& a, const pod::AABB& b, float eps = EPS ); - pod::Vector3f aabbCenter( const pod::AABB& aabb ); + pod::Vector3f getVertex( const uf::Mesh::View& view, const uf::Mesh::AttributeView& positions, size_t index ); + pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body, bool fast = false ); + + // to-do: define maxIterations as a setting + bool gjk( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Simplex& simplex, int maxIterations = 20, float eps = EPS ); + pod::Contact epa( const pod::PhysicsBody& a, const pod::PhysicsBody& b, const pod::Simplex& simplex, uint32_t maxIterations = 64, float eps = EPS ); + bool gjk( const pod::Ray& ray, const pod::PhysicsBody& body, float maxDist, float& outT, pod::Vector3f& outNormal, float eps = EPS ); void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector& indices ); void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector& indices, pod::BVH::index_t nodeID ); @@ -53,6 +71,7 @@ namespace { void queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector& indices, pod::BVH::index_t nodeID, float maxDist = FLT_MAX ); void queryOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs ); void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ); + void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs ); } namespace { @@ -107,7 +126,7 @@ namespace { } // returns an absolute transform while also allowing offsetting the collision body - // to-do: find a succint way to explain this madness + // to-do: find a succinct way to explain this madness pod::Transform<> getTransform( const pod::PhysicsBody& body ) { pod::Transform<> t; t.position = body.offset; @@ -120,6 +139,26 @@ namespace { return ::getTransform( body ).position; } + pod::PhysicsBody physicsBodyHullView( const pod::PhysicsBody& body, int32_t index = -1 ) { + pod::PhysicsBody view = body; + view.viewIndex = index; + return view; + } + + pod::PhysicsBody physicsBodyTriView( const pod::PhysicsBody& body, const pod::TriangleWithNormal triangle ) { + pod::PhysicsBody view = body; + view.collider.type = pod::ShapeType::TRIANGLE; + view.collider.triangle = triangle; + // assume triangle is already transformed + view.offset = {}; + view.transform = NULL; + return view; + } + pod::PhysicsBody physicsBodyTriView( const pod::PhysicsBody& body, size_t triID ) { + auto tri = ::fetchTriangle( *body.collider.mesh.mesh, triID, body ); + return ::physicsBodyTriView( body, tri ); + } + bool shouldCollide( const pod::Collider& a, const pod::Collider& b ) { return ( a.category & b.mask ) && ( b.category & a.mask ); } diff --git a/engine/src/utils/math/physics/impl.cpp b/engine/src/utils/math/physics/impl.cpp index d72a8e29..a1462cb0 100644 --- a/engine/src/utils/math/physics/impl.cpp +++ b/engine/src/utils/math/physics/impl.cpp @@ -16,6 +16,7 @@ #include "capsule.inl" #include "triangle.inl" #include "mesh.inl" +#include "convexHull.inl" #include "ray.inl" #include "bvh.inl" #include "gjk.inl" @@ -149,7 +150,7 @@ void uf::physics::impl::step( pod::World& world, float dt ) { // do not do it for planes bool shouldReorient = true; if ( a.collider.type == pod::ShapeType::MESH || b.collider.type == pod::ShapeType::MESH ) shouldReorient = false; - if ( a.collider.type == pod::ShapeType::PLANE || b.collider.type == pod::ShapeType::PLANE ) shouldReorient = false; + //if ( a.collider.type == pod::ShapeType::PLANE || b.collider.type == pod::ShapeType::PLANE ) shouldReorient = false; if ( shouldReorient ) { for ( auto& c : manifold.points ) c.normal = ::orientNormalToAB( a, b, c.normal ); } @@ -440,14 +441,23 @@ pod::PhysicsBody& uf::physics::impl::create( pod::World& world, uf::Object& obje uf::physics::impl::updateInertia( body ); return body; } -pod::PhysicsBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset ) { +pod::PhysicsBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset, bool convex ) { auto& body = uf::physics::impl::create( world, object, mass, offset ); - body.collider.type = pod::ShapeType::MESH; - body.collider.mesh.mesh = &mesh; - - body.collider.mesh.bvh = new pod::BVH; - auto& bvh = *body.collider.mesh.bvh; - ::buildMeshBVH( bvh, mesh, uf::physics::impl::settings.meshBvhCapacity ); + if ( !convex ) { + body.collider.type = pod::ShapeType::MESH; + body.collider.mesh.mesh = &mesh; + body.collider.mesh.bvh = new pod::BVH; + + auto& bvh = *body.collider.mesh.bvh; + ::buildMeshBVH( bvh, mesh, uf::physics::impl::settings.meshBvhCapacity ); + } else { + body.collider.type = pod::ShapeType::CONVEX_HULL; + body.collider.convexHull.mesh = &mesh; + body.collider.convexHull.bvh = new pod::BVH; + + auto& bvh = *body.collider.convexHull.bvh; + ::buildConvexHullBVH( bvh, mesh/*, uf::physics::impl::settings.meshBvhCapacity*/ ); + } body.bounds = ::computeAABB( body ); uf::physics::impl::updateInertia( body ); @@ -483,10 +493,10 @@ pod::PhysicsBody& uf::physics::impl::create( uf::Object& object, const pod::Caps auto& world = scene.getComponent(); return create( world, object, capsule, mass, offset ); } -pod::PhysicsBody& uf::physics::impl::create( uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset ) { +pod::PhysicsBody& uf::physics::impl::create( uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset, bool convex ) { auto& scene = uf::scene::getCurrentScene(); auto& world = scene.getComponent(); - return create( world, object, mesh, mass, offset ); + return create( world, object, mesh, mass, offset, convex ); } void uf::physics::impl::destroy( uf::Object& object ) { @@ -539,6 +549,7 @@ pod::RayQuery uf::physics::impl::rayCast( const pod::Ray& ray, const pod::World& case pod::ShapeType::PLANE: rayPlane( ray, *b, rayHit ); break; case pod::ShapeType::CAPSULE: rayCapsule( ray, *b, rayHit ); break; case pod::ShapeType::MESH: rayMesh( ray, *b, rayHit ); break; + case pod::ShapeType::CONVEX_HULL: rayHull( ray, *b, rayHit ); break; } } diff --git a/engine/src/utils/math/physics/integration.inl b/engine/src/utils/math/physics/integration.inl index 2f35bc73..2b6e233d 100644 --- a/engine/src/utils/math/physics/integration.inl +++ b/engine/src/utils/math/physics/integration.inl @@ -69,7 +69,10 @@ namespace { } bool generateContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt ) { - if ( uf::physics::impl::settings.useGjk ) return generateContactsGjk( a, b, manifold, dt ); + bool useGjk = uf::physics::impl::settings.useGjk; + if ( a.collider.type == pod::ShapeType::MESH || b.collider.type == pod::ShapeType::MESH ) useGjk = false; + //if ( a.collider.type == pod::ShapeType::PLANE || b.collider.type == pod::ShapeType::PLANE ) useGjk = false; + if ( useGjk ) return generateContactsGjk( a, b, manifold, dt ); ::bindManifold( a, b, manifold, dt ); #define CHECK_CONTACT( A, B, fun )\ @@ -80,30 +83,44 @@ namespace { CHECK_CONTACT( AABB, PLANE, aabbPlane ); CHECK_CONTACT( AABB, CAPSULE, aabbCapsule ); CHECK_CONTACT( AABB, MESH, aabbMesh ); + CHECK_CONTACT( AABB, CONVEX_HULL, aabbHull ); CHECK_CONTACT( SPHERE, AABB, sphereAabb ); CHECK_CONTACT( SPHERE, SPHERE, sphereSphere ); CHECK_CONTACT( SPHERE, PLANE, spherePlane ); CHECK_CONTACT( SPHERE, CAPSULE, sphereCapsule ); CHECK_CONTACT( SPHERE, MESH, sphereMesh ); + CHECK_CONTACT( SPHERE, CONVEX_HULL, sphereHull ); CHECK_CONTACT( PLANE, AABB, planeAabb ); CHECK_CONTACT( PLANE, SPHERE, planeSphere ); CHECK_CONTACT( PLANE, PLANE, planePlane ); CHECK_CONTACT( PLANE, CAPSULE, planeCapsule ); CHECK_CONTACT( PLANE, MESH, planeMesh ); + CHECK_CONTACT( PLANE, CONVEX_HULL, planeHull ); CHECK_CONTACT( CAPSULE, AABB, capsuleAabb ); CHECK_CONTACT( CAPSULE, SPHERE, capsuleSphere ); CHECK_CONTACT( CAPSULE, PLANE, capsulePlane ); CHECK_CONTACT( CAPSULE, CAPSULE, capsuleCapsule ); CHECK_CONTACT( CAPSULE, MESH, capsuleMesh ); + CHECK_CONTACT( CAPSULE, CONVEX_HULL, capsuleHull ); CHECK_CONTACT( MESH, AABB, meshAabb ); CHECK_CONTACT( MESH, SPHERE, meshSphere ); CHECK_CONTACT( MESH, PLANE, meshPlane ); CHECK_CONTACT( MESH, CAPSULE, meshCapsule ); CHECK_CONTACT( MESH, MESH, meshMesh ); + CHECK_CONTACT( MESH, CONVEX_HULL, meshHull ); + + CHECK_CONTACT( CONVEX_HULL, AABB, hullAabb ); + CHECK_CONTACT( CONVEX_HULL, SPHERE, hullSphere ); + CHECK_CONTACT( CONVEX_HULL, PLANE, hullPlane ); + CHECK_CONTACT( CONVEX_HULL, CAPSULE, hullCapsule ); + CHECK_CONTACT( CONVEX_HULL, MESH, hullMesh ); + CHECK_CONTACT( CONVEX_HULL, CONVEX_HULL, hullHull ); + + UF_EXCEPTION("unregistered contact: {} vs {}", (int) a.collider.type, (int) b.collider.type ); return false; } diff --git a/engine/src/utils/math/physics/mesh.inl b/engine/src/utils/math/physics/mesh.inl index c8047f44..ee9d6995 100644 --- a/engine/src/utils/math/physics/mesh.inl +++ b/engine/src/utils/math/physics/mesh.inl @@ -1,17 +1,3 @@ -/* -// transform capsule line segments to local space - p1 = uf::transform::applyInverse( mesh.transform, p1 ); - p2 = uf::transform::applyInverse( mesh.transform, p2 ); - -*/ -/* - -contact = uf::transform::apply( mesh.transform, contact ); -normal = uf::quaternion::rotate( mesh.transform.orientation, normal ); - -*/ - -// namespace { bool meshAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { ASSERT_COLLIDER_TYPES( MESH, AABB ); @@ -113,7 +99,7 @@ namespace { return hit; } - // to-do + bool meshMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { ASSERT_COLLIDER_TYPES( MESH, MESH ); @@ -123,29 +109,61 @@ namespace { const auto& bvhB = *b.collider.mesh.bvh; const auto& meshB = *b.collider.mesh.mesh; + auto tA = ::getTransform( a ); + auto tB = ::getTransform( b ); + auto relTransform = uf::transform::relative( tA, tB ); + // compute overlaps between one BVH and another BVH static thread_local pod::BVH::pairs_t pairs; pairs.clear(); - - //UF_TIMER_MULTITRACE_START("Colliding {} ({} indices) <=> {} ({} indices)", a.object->getName(), bvhA.indices.size(), b.object->getName(), bvhB.indices.size()); - //UF_TIMER_MULTITRACE("Querying overlaps..."); - ::queryOverlaps( bvhA, bvhB, pairs ); - //UF_TIMER_MULTITRACE("Queried overlaps."); + ::queryOverlaps( bvhA, bvhB, relTransform, pairs ); bool hit = false; // do collision per triangle - //UF_TIMER_MULTITRACE("Colliding triangles (pairs={})...", pairs.size()); for (auto [idA, idB] : pairs ) { - auto tA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space - auto tB = ::fetchTriangle( meshB, idB, b ); + auto triA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space + auto triB = ::fetchTriangle( meshB, idB, b ); + + bool collides = ::triangleTriangle( triA, triB, manifold, eps ); + if ( !collides ) continue; + hit = true; + } + return hit; + } + + bool meshHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( MESH, CONVEX_HULL ); + + const auto& mesh = a; + const auto& hull = b; + + const auto& bvhA = *a.collider.mesh.bvh; + const auto& meshA = *a.collider.mesh.mesh; + + const auto& bvhB = *b.collider.convexHull.bvh; + const auto& meshB = *b.collider.convexHull.mesh; + + auto tA = ::getTransform( a ); + auto tB = ::getTransform( b ); + auto relTransform = uf::transform::relative( tA, tB ); + + // compute overlaps between one BVH and another BVH + static thread_local pod::BVH::pairs_t pairs; + pairs.clear(); + ::queryOverlaps( bvhA, bvhB, relTransform, pairs ); + + bool hit = false; + // do collision per hull and triangle + for (auto [ triID, hullID ] : pairs ) { + auto triView = ::physicsBodyTriView( mesh, triID ); + auto hullView = ::physicsBodyHullView( hull, hullID ); + + bool collides = ::triangleHull( triView, hullView, manifold, eps ); - bool collides = ::triangleTriangle( tA, tB, manifold, eps ); if ( !collides ) continue; hit = true; } - //UF_TIMER_MULTITRACE("Collided triangles."); - //UF_TIMER_MULTITRACE_END("Collided mesh."); return hit; } } \ No newline at end of file diff --git a/engine/src/utils/math/physics/plane.inl b/engine/src/utils/math/physics/plane.inl index 7f624663..e0842dd2 100644 --- a/engine/src/utils/math/physics/plane.inl +++ b/engine/src/utils/math/physics/plane.inl @@ -54,4 +54,8 @@ namespace { ASSERT_COLLIDER_TYPES( PLANE, MESH ); REVERSE_COLLIDER( a, b, meshPlane ); } + bool planeHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( PLANE, CONVEX_HULL ); + REVERSE_COLLIDER( a, b, hullPlane ); + } } \ No newline at end of file diff --git a/engine/src/utils/math/physics/ray.inl b/engine/src/utils/math/physics/ray.inl index 2e9b4ccc..f0e16395 100644 --- a/engine/src/utils/math/physics/ray.inl +++ b/engine/src/utils/math/physics/ray.inl @@ -229,4 +229,38 @@ namespace { return rayHit.hit; } + + bool rayHull( const pod::Ray& r, const pod::PhysicsBody& body, pod::RayQuery& rayHit ) { + const auto& bvh = *body.collider.convexHull.bvh; + const auto& meshData = *body.collider.convexHull.mesh; + + const auto transform = ::getTransform( body ); + + pod::Ray ray; + ray.origin = uf::transform::applyInverse( transform, r.origin ); + ray.direction = uf::quaternion::rotate( uf::quaternion::inverse( transform.orientation ), r.direction ); + + thread_local uf::stl::vector candidates; + candidates.clear(); + ::queryBVH( bvh, ray, candidates ); + + for ( auto hullID : candidates ) { + auto hullView = ::physicsBodyHullView( body, hullID ); + + float t; + pod::Vector3f normal; + + if ( !::gjk( ray, hullView, rayHit.contact.penetration, t, normal ) ) continue; + if ( t >= rayHit.contact.penetration ) continue; + + rayHit.hit = true; + rayHit.body = &body; + + rayHit.contact.point = uf::transform::apply( transform, ray.origin + ray.direction * t ); + rayHit.contact.normal = uf::quaternion::rotate( transform.orientation, normal ); + rayHit.contact.penetration = t; + } + + return rayHit.hit; + } } \ No newline at end of file diff --git a/engine/src/utils/math/physics/sphere.inl b/engine/src/utils/math/physics/sphere.inl index 463cf1e2..bc7ef513 100644 --- a/engine/src/utils/math/physics/sphere.inl +++ b/engine/src/utils/math/physics/sphere.inl @@ -56,4 +56,8 @@ namespace { ASSERT_COLLIDER_TYPES( SPHERE, MESH ); REVERSE_COLLIDER( a, b, meshSphere ); } + bool sphereHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( SPHERE, CONVEX_HULL ); + REVERSE_COLLIDER( a, b, hullSphere ); + } } \ No newline at end of file diff --git a/engine/src/utils/math/physics/triangle.inl b/engine/src/utils/math/physics/triangle.inl index 92211e1c..07537fcf 100644 --- a/engine/src/utils/math/physics/triangle.inl +++ b/engine/src/utils/math/physics/triangle.inl @@ -120,7 +120,7 @@ namespace { } // if body is a mesh, apply its transform to the triangles, else reorient the normal with respect to the body - pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body, bool fast = false ) { + pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body, bool fast ) { auto tri = ::fetchTriangle( mesh, triID ); auto transform = ::getTransform( body ); @@ -566,4 +566,27 @@ namespace { ASSERT_COLLIDER_TYPES( TRIANGLE, CAPSULE ); return ::triangleCapsule( a.collider.triangle, b, manifold, eps ); } + bool triangleHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) { + ASSERT_COLLIDER_TYPES( TRIANGLE, CONVEX_HULL ); + const auto& tri = a; + const auto& hull = b; + + pod::Simplex simplex; + if ( !::gjk( tri, hull, simplex ) ) return false; + + auto result = ::epa( tri, hull, simplex ); + + if ( !uf::vector::isValid(result.point) ) return false; + + manifold.points.emplace_back( result ); + return true; + } + + bool triangleHull( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps ) { + pod::PhysicsBody triView = {}; + triView.collider.type = pod::ShapeType::TRIANGLE; + triView.collider.triangle = tri; + + return ::triangleHull( triView, body, manifold, eps ); + } } \ No newline at end of file