From 52fa565eb55cb346d505de9d7479afe8656dc87d Mon Sep 17 00:00:00 2001 From: ecker Date: Sun, 21 Sep 2025 19:30:11 -0500 Subject: [PATCH] some more menial tasks (implemented triangleAabb as composing the AABB as a triangulated box since it seems better that way, added inertia calculations for meshes even though I think the approach for inertia tensors and rotations seems really wrong) --- bin/data/entities/player.json | 6 +- bin/data/entities/prop.json | 7 +- bin/data/scenes/sourceengine/test_grid.json | 18 +-- bin/exe/default/renderer | 2 +- engine/inc/uf/utils/math/matrix.h | 5 + engine/inc/uf/utils/math/matrix/pod.inl | 32 ++++ engine/src/engine/graph/graph.cpp | 2 +- engine/src/engine/object/behavior.cpp | 2 +- engine/src/utils/math/physics/aabb.inl | 4 +- engine/src/utils/math/physics/impl.cpp | 47 +++++- engine/src/utils/math/physics/triangle.inl | 171 ++++++++++++++------ 11 files changed, 218 insertions(+), 78 deletions(-) diff --git a/bin/data/entities/player.json b/bin/data/entities/player.json index 32d23af1..0ca53fbd 100644 --- a/bin/data/entities/player.json +++ b/bin/data/entities/player.json @@ -75,7 +75,7 @@ }, "physics": { "gravity": [ 0, -9.81, 0 ], - "inertia": [ 0, 0, 0 ], + "inertia": false, "offset": [ 0, 0, 0 ], @@ -85,6 +85,10 @@ "type": "capsule", "radius": 1, "height": 2, + + // "type": "bounding box", + // "min": [ -1, -1, -1 ], + // "max": [ 1, 1, 1 ], "mass": 100, "friction": 0.95, diff --git a/bin/data/entities/prop.json b/bin/data/entities/prop.json index 5f6faf7b..b3ed5dee 100644 --- a/bin/data/entities/prop.json +++ b/bin/data/entities/prop.json @@ -6,9 +6,10 @@ "metadata": { "holdable": true, "physics": { - "mass": 0, - // "type": "bounding box" - "type": "mesh" + "mass": 1000, + "inertia": false, + "type": "bounding box" + // "type": "mesh" } } } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/test_grid.json b/bin/data/scenes/sourceengine/test_grid.json index 7964f1dd..0754dbdd 100644 --- a/bin/data/scenes/sourceengine/test_grid.json +++ b/bin/data/scenes/sourceengine/test_grid.json @@ -3,21 +3,5 @@ "assets": [ // { "filename": "./models/test_grid.glb" } { "filename": "./models/test_grid/graph.json" } - ], - "metadata": { - "graph": { - "tags": { - " worldspawn": { - "physics": { "type": "bounding box", "static": true }, - // "physics": { "type": "plane", "static": true, "direction": [ 0, 1, 0 ], "offset": 0 }, - "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true }, - "optimize meshlets": { "simplify": 0.125, "print": false }, - "unwrap mesh": true - }, - - "/^prop_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } }, - "/^func_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } } - } - } - } + ] } \ No newline at end of file diff --git a/bin/exe/default/renderer b/bin/exe/default/renderer index 91caa7c1..53395cff 100644 --- a/bin/exe/default/renderer +++ b/bin/exe/default/renderer @@ -1 +1 @@ -opengl \ No newline at end of file +vulkan \ No newline at end of file diff --git a/engine/inc/uf/utils/math/matrix.h b/engine/inc/uf/utils/math/matrix.h index 902a4281..14d68870 100644 --- a/engine/inc/uf/utils/math/matrix.h +++ b/engine/inc/uf/utils/math/matrix.h @@ -32,7 +32,10 @@ namespace pod { FORCE_INLINE Matrix operator*( const Matrix& matrix ) const; FORCE_INLINE Matrix operator*( T scalar ) const; FORCE_INLINE Matrix operator+( const Matrix& matrix ) const; + FORCE_INLINE Matrix operator-( const Matrix& matrix ) const; FORCE_INLINE Matrix& operator *=( const Matrix& matrix ); + FORCE_INLINE Matrix& operator +=( const Matrix& matrix ); + FORCE_INLINE Matrix& operator -=( const Matrix& matrix ); FORCE_INLINE bool operator==( const Matrix& matrix ) const; FORCE_INLINE bool operator!=( const Matrix& matrix ) const; }; @@ -74,9 +77,11 @@ namespace uf { template /*FORCE_INLINE*/ pod::Vector3t multiply(const pod::Matrix3t& mat, const pod::Vector3t& v ); template /*FORCE_INLINE*/ pod::Vector3t multiply( const pod::Matrix4t& mat, const pod::Vector3t& vector, T w = 1, bool = false ); template /*FORCE_INLINE*/ pod::Vector4t multiply( const pod::Matrix4t& mat, const pod::Vector4t& vector, bool = false ); + template /*FORCE_INLINE*/ pod::Matrix /*UF_API*/ outerProduct( const pod::Vector& a, const pod::Vector& b ); template /*FORCE_INLINE*/ T /*UF_API*/ multiplyAll( const T& matrix, typename T::type_t scalar ); template /*FORCE_INLINE*/ T /*UF_API*/ add( const T& lhs, const T& rhs ); + template /*FORCE_INLINE*/ T /*UF_API*/ subtract( const T& lhs, const T& rhs ); template /*FORCE_INLINE*/ T& /*UF_API*/ inverse_( T& matrix ); template /*FORCE_INLINE*/ pod::Matrix multiply_( T& left, const U& right ); diff --git a/engine/inc/uf/utils/math/matrix/pod.inl b/engine/inc/uf/utils/math/matrix/pod.inl index 36998bce..75f8b7c2 100644 --- a/engine/inc/uf/utils/math/matrix/pod.inl +++ b/engine/inc/uf/utils/math/matrix/pod.inl @@ -69,10 +69,22 @@ FORCE_INLINE pod::Matrix pod::Matrix::operator+( const Matrix +FORCE_INLINE pod::Matrix pod::Matrix::operator-( const Matrix& matrix ) const { + return uf::matrix::subtract(*this, matrix); +} +template FORCE_INLINE pod::Matrix& pod::Matrix::operator*=( const Matrix& matrix ) { return uf::matrix::multiply_(*this, matrix); } template +FORCE_INLINE pod::Matrix& pod::Matrix::operator+=( const Matrix& matrix ) { + return *this = uf::matrix::add(*this, matrix); // to-do: non-const +} +template +FORCE_INLINE pod::Matrix& pod::Matrix::operator-=( const Matrix& matrix ) { + return *this = uf::matrix::subtract(*this, matrix); // to-do: non-const +} +template FORCE_INLINE bool pod::Matrix::operator==( const Matrix& matrix ) const { return uf::matrix::equals( *this, matrix ); } @@ -146,6 +158,17 @@ template pod::Matrix pod::Matrix /*UF_API*/ uf::matrix::outerProduct( const pod::Vector& a, const pod::Vector& b ) { + pod::Matrix m{}; + for ( auto i = 0; i < M; ++i ) { + for ( auto j = 0; j < N; ++j ) { + m(i, j) = a[i] * b[j]; + } + } + return m; +} + template T /*UF_API*/ uf::matrix::multiplyAll( const T& m, typename T::type_t scalar ) { T matrix; @@ -164,6 +187,15 @@ template T /*UF_API*/ uf::matrix::add( const T& lhs, const T& rhs ) return matrix; } +template T /*UF_API*/ uf::matrix::subtract( const T& lhs, const T& rhs ) { + T matrix; + + FOR_EACH(T::rows * T::columns, { + matrix[i] = lhs[i] - rhs[i]; + }); + + return matrix; +} template T uf::matrix::transpose( const T& matrix ) { #if UF_USE_SIMD if constexpr (std::is_same_v && T::rows == 4 && T::columns == 4 ) { diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index f64b41d3..2c4578a1 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -1690,7 +1690,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) { graph.settings.stream.lastUpdate = uf::physics::time::current; if ( drawCommandHash == graph.settings.stream.hash ) { - // return; + return; } graph.settings.stream.hash = drawCommandHash; diff --git a/engine/src/engine/object/behavior.cpp b/engine/src/engine/object/behavior.cpp index c962a204..da177cae 100644 --- a/engine/src/engine/object/behavior.cpp +++ b/engine/src/engine/object/behavior.cpp @@ -184,7 +184,7 @@ void uf::ObjectBehavior::initialize( uf::Object& self ) { physicsBody.velocity = uf::vector::decode( metadataJsonPhysics["velocity"], physicsBody.velocity ); physicsBody.angularVelocity = uf::vector::decode( metadataJsonPhysics["angularVelocity"], physicsBody.angularVelocity ); - if ( this->getName() == "Player" ) { + if ( metadataJsonPhysics["inertia"].is() && !metadataJsonPhysics["inertia"].as() ) { physicsBody.inertiaTensor = { FLT_MAX, FLT_MAX, FLT_MAX }; physicsBody.inverseInertiaTensor = { 0.0f, 0.0f, 0.0f }; } diff --git a/engine/src/utils/math/physics/aabb.inl b/engine/src/utils/math/physics/aabb.inl index 70866edd..7dbd6ada 100644 --- a/engine/src/utils/math/physics/aabb.inl +++ b/engine/src/utils/math/physics/aabb.inl @@ -84,13 +84,11 @@ namespace { const auto transform = ::getTransform( body ); switch ( body.collider.type ) { case pod::ShapeType::AABB: { - /* + // return ::transformAabbToWorld( body.collider.aabb, *body.transform ); return { transform.position + body.collider.aabb.min, transform.position + body.collider.aabb.max, }; - */ - return ::transformAabbToWorld( body.collider.aabb, *body.transform ); } break; case pod::ShapeType::SPHERE: { return { diff --git a/engine/src/utils/math/physics/impl.cpp b/engine/src/utils/math/physics/impl.cpp index 0ad9ad6d..ea95cc84 100644 --- a/engine/src/utils/math/physics/impl.cpp +++ b/engine/src/utils/math/physics/impl.cpp @@ -29,7 +29,7 @@ namespace { // to-do: find possibly better values for this uint32_t solverIterations = 10; - float baumgarteCorrectionPercent = 0.2f; + float baumgarteCorrectionPercent = 0.4f; float baumgarteCorrectionSlop = 0.01f; uf::stl::unordered_map manifoldsCache; @@ -42,7 +42,7 @@ namespace { .displacementThreshold = 0.25f, .overlapThreshold = 2.0f, .dirtyRatioThreshold = 0.3f, - .maxFramesBeforeRebuild = 60 * 10, // 10 seconds + .maxFramesBeforeRebuild = 60, // * 10, // 10 seconds }; } @@ -257,7 +257,8 @@ pod::Vector3f uf::physics::impl::getGravity( pod::PhysicsBody& body ) { void uf::physics::impl::updateInertia( pod::PhysicsBody& body ) { if ( body.isStatic || body.mass <= 0 ) { - body.inverseInertiaTensor = {}; + body.inertiaTensor = { FLT_MAX, FLT_MAX, FLT_MAX }; + body.inverseInertiaTensor = { 0.0f, 0.0f, 0.0f }; return; } @@ -287,7 +288,47 @@ void uf::physics::impl::updateInertia( pod::PhysicsBody& body ) { body.inertiaTensor = { Ixx, Iyy, Izz }; body.inverseInertiaTensor = { 1.0f/Ixx, 1.0f/Iyy, 1.0f/Izz }; } break; + case pod::ShapeType::MESH: { + const auto& bvh = *body.collider.mesh.bvh; + pod::Matrix3f inertia = {}; + float totalVolume = 0.0f; + + // compute total volume + for ( auto& box : bvh.bounds ) { + auto extents = box.max - box.min; + totalVolume += extents.x * extents.y * extents.z; + } + + // accumulate inertia + for ( auto& box : bvh.bounds ) { + auto extents = box.max - box.min; + float mass = body.mass * extents.x * extents.y * extents.z / totalVolume; + + // inertia tensor of a box about its center + float x2 = extents.x * extents.x; + float y2 = extents.y * extents.y; + float z2 = extents.z * extents.z; + + pod::Matrix3f Ibox; + Ibox(0,0) = (1.0f/12.0f) * mass * (y2 + z2); + Ibox(1,1) = (1.0f/12.0f) * mass * (x2 + z2); + Ibox(2,2) = (1.0f/12.0f) * mass * (x2 + y2); + + // parallel axis theorem + pod::Vector3f center = (box.min + box.max) * 0.5f; + pod::Vector3f d = center; // relative to mesh COM (assume COM at origin for now) + float d2 = uf::vector::dot(d, d); + + pod::Matrix3f pat = uf::matrix::identityi() * (mass * d2); + pat -= uf::matrix::outerProduct(d, d) * mass; + + inertia += Ibox + pat; + } + + body.inertiaTensor = { inertia(0,0), inertia(1,1), inertia(2,2) }; + body.inverseInertiaTensor = 1.0f / body.inertiaTensor; + } break; // to-do: add others default: { } break; diff --git a/engine/src/utils/math/physics/triangle.inl b/engine/src/utils/math/physics/triangle.inl index be5dcbfe..d6434651 100644 --- a/engine/src/utils/math/physics/triangle.inl +++ b/engine/src/utils/math/physics/triangle.inl @@ -148,15 +148,15 @@ namespace { } bool computeTriangleTriangleSegment( const pod::TriangleWithNormal& A, const pod::TriangleWithNormal& B, pod::Vector3f& p0, pod::Vector3f& p1, float eps = EPS(1e-6f) ) { - uf::stl::vector intersections; - intersections.reserve(3); + int intersections = 0; + pod::Vector3f intersectionBuffers[6]; auto checkAndPush = [&]( const pod::Vector3f& pt ) { // avoid duplicates - for ( auto& v : intersections ) { + for ( auto& v : intersectionBuffers ) { if ( uf::vector::distanceSquared( v, pt ) < eps*eps ) return; } - intersections.emplace_back(pt); + intersectionBuffers[intersections++] = pt; }; // segment-plane intersection @@ -198,23 +198,23 @@ namespace { } }); - if ( intersections.empty() ) return false; + if ( intersections == 0 ) return false; // degenerate intersection - if ( intersections.size() == 1 ) { - p0 = p1 = intersections[0]; + if ( intersections == 1 ) { + p0 = p1 = intersectionBuffers[0]; return true; } // find two furthest apart points for intersection segment float maxDist2 = -1.0f; - for ( auto i = 0 ; i < intersections.size(); i++ ) { - for ( auto j = i + 1 ; j maxDist2 ) { maxDist2 = d2; - p0 = intersections[i]; - p1 = intersections[j]; + p0 = intersectionBuffers[i]; + p1 = intersectionBuffers[j]; } } } @@ -277,7 +277,7 @@ namespace { if ( triMin > r || triMax < -r ) return false; // separating axis // compute overlap depth - float overlap = std::min( triMin - r, triMax + r ); + float overlap = std::min(triMax, r) - std::max(triMin, -r); if ( overlap < minOverlap ) { minOverlap = overlap; bestAxis = n; @@ -308,56 +308,131 @@ namespace { } bool triangleTriangle( const pod::TriangleWithNormal& a, const pod::TriangleWithNormal& b, pod::Manifold& manifold, float eps ) { - // if ( !::triangleTriangleIntersect( a, b ) ) return false; + size_t axes = 0; + pod::Vector3f axesBuffer[12]; + axesBuffer[axes++] = ::triangleNormal(a); + axesBuffer[axes++] = ::triangleNormal(b); - uf::stl::vector axes = { ::triangleNormal( a ), ::triangleNormal( b ) }; - axes.reserve(2+3); - - pod::Vector3f p0 = {}, p1 = {}; - if ( !::computeTriangleTriangleSegment(a, b, p0, p1) ) { - auto contact = ( p0 + p1 ) * 0.5f; - auto normal = uf::vector::normalize( axes[0] + axes[1] ); - manifold.points.emplace_back(pod::Contact{ contact, normal, eps }); - return true; - } - - auto contact = ( p0 + p1 ) * 0.5f; - float penetration = std::numeric_limits::max(); - pod::Vector3f normal; - - // check edge cross-products - for ( auto i = 0; i < 3; i++ ) { - auto ea = a.points[( i + 1 ) % 3] - a.points[i]; - for ( auto j = 0; j < 3; j++ ) { - auto eb = b.points[( j + 1 ) % 3] - b.points[j]; + for (int i = 0; i < 3; i++) { + auto ea = a.points[(i+1)%3] - a.points[i]; + for (int j = 0; j < 3; j++) { + auto eb = b.points[(j+1)%3] - b.points[j]; auto axis = uf::vector::cross(ea, eb); - if ( uf::vector::magnitude( axis ) > eps*eps ) axes.emplace_back( axis ); + if ( uf::vector::magnitude(axis) > eps ) axesBuffer[axes++] = uf::vector::normalize(axis); } } - // project onto each axis - for ( auto axis : axes ) { - axis = uf::vector::normalize( axis ); - pod::Vector2f aP = ::projectTriangleOntoAxis( a, axis ); - pod::Vector2f bP = ::projectTriangleOntoAxis( b, axis ); + // SAT test + float minOverlap = FLT_MAX; + pod::Vector3f bestAxis; - float overlap = std::min( aP.x, bP.x ) - std::max( aP.y, bP.y ); - if ( overlap < 0) return false; // separating axis - if ( overlap < penetration ) { - penetration = overlap; - normal = axis; + for ( auto& axis : axesBuffer ) { + auto projA = ::projectTriangleOntoAxis(a, axis); + auto projB = ::projectTriangleOntoAxis(b, axis); + + float overlap = std::min(projA.y, projB.y) - std::max(projA.x, projB.x); + if (overlap < 0) return false; // separating axis + + if (overlap < minOverlap) { + minOverlap = overlap; + bestAxis = axis; } } - manifold.points.emplace_back(pod::Contact{ contact, normal, penetration }); - return true; + + // clip polygons + int polyCount = 0; + pod::Vector3f poly[8]; + poly[polyCount++] = b.points[0]; + poly[polyCount++] = b.points[1]; + poly[polyCount++] = b.points[2]; + + auto clipAgainstPlane = [&](const pod::Vector3f& n, const pod::Vector3f& p) { + int outCount = 0; + pod::Vector3f out[8]; + + for ( auto i = 0; i < polyCount; i++ ) { + auto curr = poly[i]; + auto prev = poly[(i+polyCount-1)%polyCount]; + float dCurr = uf::vector::dot(n, curr - p); + float dPrev = uf::vector::dot(n, prev - p); + + if ( dCurr >= 0 ) { + if ( dPrev < 0 ) { + float t = dPrev / (dPrev - dCurr); + out[outCount++] = prev + (curr - prev) * t; + } + out[outCount++] = curr; + } else if ( dPrev >= 0 ) { + float t = dPrev / (dPrev - dCurr); + out[outCount++] = prev + (curr - prev) * t; + } + } + // copy back + polyCount = outCount; + for ( auto i = 0; i < outCount; i++ ) poly[i] = out[i]; + }; + + if ( uf::vector::dot(bestAxis, ::triangleCenter(b) - ::triangleCenter(a)) < 0.0f ) bestAxis = -bestAxis; + /* + pod::Vector3f centroid{0,0,0}; + for ( auto i = 0; i < polyCount; i++ ) centroid += poly[i]; + centroid /= (float) polyCount; + if ( uf::vector::dot(bestAxis, centroid - ::triangleCenter(a)) < 0.0f ) bestAxis = -bestAxis; + */ + + for ( auto i = 0; i < 3; i++ ) { + auto p0 = a.points[i]; + auto p1 = a.points[(i+1)%3]; + auto edge = p1 - p0; + auto edgeNormal = uf::vector::normalize(uf::vector::cross(bestAxis, edge)); + clipAgainstPlane(edgeNormal, p0); + if ( polyCount == 0 ) return false; + } + + // build manifold + float penetration = std::max( minOverlap, 0.05f ); // slop + for (int i = 0; i < polyCount; i++) { + manifold.points.emplace_back(pod::Contact{ poly[i], bestAxis, penetration }); + } + + return ( polyCount > 0 ); } + bool triangleAabbTri( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps ) { + // 8 corners + pod::Vector3f v[8] = { + {body.bounds.min.x, body.bounds.min.y, body.bounds.min.z}, + {body.bounds.max.x, body.bounds.min.y, body.bounds.min.z}, + {body.bounds.max.x, body.bounds.max.y, body.bounds.min.z}, + {body.bounds.min.x, body.bounds.max.y, body.bounds.min.z}, + {body.bounds.min.x, body.bounds.min.y, body.bounds.max.z}, + {body.bounds.max.x, body.bounds.min.y, body.bounds.max.z}, + {body.bounds.max.x, body.bounds.max.y, body.bounds.max.z}, + {body.bounds.min.x, body.bounds.max.y, body.bounds.max.z} + }; + + pod::TriangleWithNormal tris[12] = { + { {v[0], v[4], v[7]}, {-1,0,0} }, { {v[0], v[7], v[3]}, {-1,0,0} }, // left (x-) + { {v[1], v[5], v[6]}, {1,0,0} }, { {v[1], v[6], v[2]}, {1,0,0} }, // right (x+) + { {v[0], v[1], v[5]}, {0,-1,0} }, { {v[0], v[5], v[4]}, {0,-1,0} }, // bottom (y-) + { {v[3], v[2], v[6]}, {0,1,0} }, { {v[3], v[6], v[7]}, {0,1,0} }, // top (y+) + { {v[0], v[1], v[2]}, {0,0,-1} }, { {v[0], v[2], v[3]}, {0,0,-1} }, // back (z-) + { {v[4], v[5], v[6]}, {0,0,1} }, { {v[4], v[6], v[7]}, {0,0,1} }, // front (z+) + }; + + bool hit = false; + for ( auto& t : tris ) { + if ( ::triangleTriangle( tri, t, manifold, eps ) ) hit = true; + } + return hit; + } bool triangleAabb( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps ) { const auto& aabb = body; #if 1 - return ::triangleAabbSAT( tri, body, manifold, eps ); + return ::triangleAabbTri( tri, body, manifold, eps ); + //return ::triangleAabbSAT( tri, body, manifold, eps ); #else auto closest = ::closestPointOnTriangle( ::getPosition( aabb ), tri ); auto closestAabb = ::closestPointOnAABB( closest, aabb.bounds );