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)

This commit is contained in:
ecker 2025-09-21 19:30:11 -05:00
parent 3ac3022f88
commit 52fa565eb5
11 changed files with 218 additions and 78 deletions

View File

@ -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,

View File

@ -6,9 +6,10 @@
"metadata": {
"holdable": true,
"physics": {
"mass": 0,
// "type": "bounding box"
"type": "mesh"
"mass": 1000,
"inertia": false,
"type": "bounding box"
// "type": "mesh"
}
}
}

View File

@ -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 ] } } } }
}
}
}
]
}

View File

@ -1 +1 @@
opengl
vulkan

View File

@ -32,7 +32,10 @@ namespace pod {
FORCE_INLINE Matrix<T,R,C> operator*( const Matrix<T,R,C>& matrix ) const;
FORCE_INLINE Matrix<T,R,C> operator*( T scalar ) const;
FORCE_INLINE Matrix<T,R,C> operator+( const Matrix<T,R,C>& matrix ) const;
FORCE_INLINE Matrix<T,R,C> operator-( const Matrix<T,R,C>& matrix ) const;
FORCE_INLINE Matrix<T,R,C>& operator *=( const Matrix<T,R,C>& matrix );
FORCE_INLINE Matrix<T,R,C>& operator +=( const Matrix<T,R,C>& matrix );
FORCE_INLINE Matrix<T,R,C>& operator -=( const Matrix<T,R,C>& matrix );
FORCE_INLINE bool operator==( const Matrix<T,R,C>& matrix ) const;
FORCE_INLINE bool operator!=( const Matrix<T,R,C>& matrix ) const;
};
@ -74,9 +77,11 @@ namespace uf {
template<typename T=NUM> /*FORCE_INLINE*/ pod::Vector3t<T> multiply(const pod::Matrix3t<T>& mat, const pod::Vector3t<T>& v );
template<typename T=NUM> /*FORCE_INLINE*/ pod::Vector3t<T> multiply( const pod::Matrix4t<T>& mat, const pod::Vector3t<T>& vector, T w = 1, bool = false );
template<typename T=NUM> /*FORCE_INLINE*/ pod::Vector4t<T> multiply( const pod::Matrix4t<T>& mat, const pod::Vector4t<T>& vector, bool = false );
template<typename T=NUM, size_t M, size_t N> /*FORCE_INLINE*/ pod::Matrix<T,M,N> /*UF_API*/ outerProduct( const pod::Vector<T,M>& a, const pod::Vector<T,N>& b );
template<typename T=pod::Matrix4> /*FORCE_INLINE*/ T /*UF_API*/ multiplyAll( const T& matrix, typename T::type_t scalar );
template<typename T=pod::Matrix4> /*FORCE_INLINE*/ T /*UF_API*/ add( const T& lhs, const T& rhs );
template<typename T=pod::Matrix4> /*FORCE_INLINE*/ T /*UF_API*/ subtract( const T& lhs, const T& rhs );
template<typename T=pod::Matrix4> /*FORCE_INLINE*/ T& /*UF_API*/ inverse_( T& matrix );
template<typename T, typename U> /*FORCE_INLINE*/ pod::Matrix<typename T::type_t, T::columns, T::columns> multiply_( T& left, const U& right );

View File

@ -69,10 +69,22 @@ FORCE_INLINE pod::Matrix<T,R,C> pod::Matrix<T,R,C>::operator+( const Matrix<T,R,
return uf::matrix::add(*this, matrix);
}
template<typename T, size_t R, size_t C>
FORCE_INLINE pod::Matrix<T,R,C> pod::Matrix<T,R,C>::operator-( const Matrix<T,R,C>& matrix ) const {
return uf::matrix::subtract(*this, matrix);
}
template<typename T, size_t R, size_t C>
FORCE_INLINE pod::Matrix<T,R,C>& pod::Matrix<T,R,C>::operator*=( const Matrix<T,R,C>& matrix ) {
return uf::matrix::multiply_(*this, matrix);
}
template<typename T, size_t R, size_t C>
FORCE_INLINE pod::Matrix<T,R,C>& pod::Matrix<T,R,C>::operator+=( const Matrix<T,R,C>& matrix ) {
return *this = uf::matrix::add(*this, matrix); // to-do: non-const
}
template<typename T, size_t R, size_t C>
FORCE_INLINE pod::Matrix<T,R,C>& pod::Matrix<T,R,C>::operator-=( const Matrix<T,R,C>& matrix ) {
return *this = uf::matrix::subtract(*this, matrix); // to-do: non-const
}
template<typename T, size_t R, size_t C>
FORCE_INLINE bool pod::Matrix<T,R,C>::operator==( const Matrix<T,R,C>& matrix ) const {
return uf::matrix::equals( *this, matrix );
}
@ -146,6 +158,17 @@ template<typename T, typename U> pod::Matrix<typename T::type_t, T::columns, T::
return res;
}
template<typename T, size_t M, size_t N> pod::Matrix<T,M,N> /*UF_API*/ uf::matrix::outerProduct( const pod::Vector<T,M>& a, const pod::Vector<T,N>& b ) {
pod::Matrix<T, M, N> 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<typename T> T /*UF_API*/ uf::matrix::multiplyAll( const T& m, typename T::type_t scalar ) {
T matrix;
@ -164,6 +187,15 @@ template<typename T> T /*UF_API*/ uf::matrix::add( const T& lhs, const T& rhs )
return matrix;
}
template<typename T> 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<typename T> T uf::matrix::transpose( const T& matrix ) {
#if UF_USE_SIMD
if constexpr (std::is_same_v<T,float> && T::rows == 4 && T::columns == 4 ) {

View File

@ -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;

View File

@ -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<bool>() && !metadataJsonPhysics["inertia"].as<bool>() ) {
physicsBody.inertiaTensor = { FLT_MAX, FLT_MAX, FLT_MAX };
physicsBody.inverseInertiaTensor = { 0.0f, 0.0f, 0.0f };
}

View File

@ -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 {

View File

@ -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<size_t, pod::Manifold> 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<pod::Matrix3f>() * (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;

View File

@ -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<pod::Vector3f> 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<intersections.size(); j++ ) {
float d2 = uf::vector::distanceSquared( intersections[i], intersections[j] );
for ( auto i = 0 ; i < intersections; i++ ) {
for ( auto j = i + 1; j < intersections; j++ ) {
float d2 = uf::vector::distanceSquared( intersectionBuffers[i], intersectionBuffers[j] );
if ( d2 > 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<pod::Vector3f> 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<float>::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 );