this is saner (although selectively querying for specific mesh attributes on view construction, just to have an interface to access them by name seems extremely silly), also triangleAabbSAT for stability (it's marginally more stable)

This commit is contained in:
ecker 2025-09-17 12:34:11 -05:00
parent 58dee945f8
commit 3ac3022f88
9 changed files with 105 additions and 29 deletions

View File

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

View File

@ -104,7 +104,6 @@ namespace pod {
struct MeshBVH {
pod::BVH* bvh;
const uf::Mesh* mesh;
const uf::Mesh::views_t* views;
};
typedef uint32_t CollisionMask;

View File

@ -197,10 +197,13 @@ namespace uf {
// crunge, but it's better this way for streaming in mesh data
uf::stl::vector<uf::stl::string> buffer_paths;
// mega cringe, but i'd like to have a way to cache it
uf::stl::vector<uf::Mesh::View> buffer_views;
protected:
void _destroy( uf::Mesh::Input& input );
void _bind( bool interleaved = uf::Mesh::defaultInterleaved );
void _updateDescriptor( uf::Mesh::Input& input );
void _updateViews();
uf::Mesh::Attribute _remapAttribute( const uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute, size_t i = 0 ) const;
bool _hasV( const uf::Mesh::Input& input, const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) const;

View File

@ -212,7 +212,7 @@ namespace {
bvh.dirty = false;
}
void buildMeshBVH( pod::BVH& bvh, const uf::Mesh& mesh, const uf::Mesh::views_t& views, pod::BVH::index_t capacity = 4 ) {
void buildMeshBVH( pod::BVH& bvh, const uf::Mesh& mesh, pod::BVH::index_t capacity = 4 ) {
uint32_t triangles = mesh.index.count / 3;
bvh.indices.clear();
@ -223,6 +223,7 @@ namespace {
uf::stl::vector<pod::AABB> bounds;
bounds.reserve( triangles );
const auto& views = mesh.buffer_views;
UF_ASSERT( !views.empty() );
// populate initial indices and bounds
@ -375,12 +376,13 @@ namespace {
}
}
void refitBVH( pod::BVH& bvh, const uf::Mesh& mesh, const uf::Mesh::views_t& views ) {
void refitBVH( pod::BVH& bvh, const uf::Mesh& mesh ) {
uint32_t triangles = mesh.index.count / 3;
uf::stl::vector<pod::AABB> bounds;
bounds.reserve( triangles );
const auto& views = mesh.buffer_views;
UF_ASSERT( !views.empty() );
// populate initial indices and bounds

View File

@ -14,15 +14,15 @@ namespace {
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
uint32_t broadphaseBvhCapacity = 1; // number of bodies per leaf node
uint32_t meshBvhCapacity = 1; // number of triangles per leaf node
uint32_t broadphaseBvhCapacity = 4; // number of bodies per leaf node
uint32_t meshBvhCapacity = 4; // number of triangles per leaf node
// additionally flattens a BVH for linear iteration, rather than a recursive / stack-based traversal
bool flattenBvhBodies = true;
bool flattenBvhMeshes = true;
// use surface area heuristics for building the BVH, rather than naive splits
bool useBvhSahBodies = false; // it actually seems slower to use these......
bool useBvhSahBodies = true; // it actually seems slower to use these......
bool useBvhSahMeshes = true;
bool useSplitBvhs = true; // creates separate BVHs for static / dynamic objects
@ -396,11 +396,10 @@ pod::PhysicsBody& uf::physics::impl::create( pod::World& world, uf::Object& obje
auto& body = uf::physics::impl::create( world, object, mass, offset );
body.collider.type = pod::ShapeType::MESH;
body.collider.mesh.mesh = &mesh;
auto& views = *(body.collider.mesh.views = new uf::Mesh::views_t(mesh.makeViews({"position", "normal"})));
body.collider.mesh.bvh = new pod::BVH;
auto& bvh = *body.collider.mesh.bvh;
::buildMeshBVH( bvh, mesh, views, ::meshBvhCapacity );
::buildMeshBVH( bvh, mesh, ::meshBvhCapacity );
body.bounds = ::computeAABB( body );
uf::physics::impl::updateInertia( body );
@ -459,7 +458,6 @@ void uf::physics::impl::destroy( pod::PhysicsBody& body ) {
// remove any pointered collider data
if ( body.collider.type == pod::ShapeType::MESH ) {
if ( body.collider.mesh.bvh ) delete body.collider.mesh.bvh;
if ( body.collider.mesh.views ) delete body.collider.mesh.views;
}
}

View File

@ -21,7 +21,6 @@ namespace {
const auto& bvh = *mesh.collider.mesh.bvh;
const auto& meshData = *mesh.collider.mesh.mesh;
const auto& meshViews = *mesh.collider.mesh.views;
// transform to local space for BVH query
auto bounds = ::transformAabbToLocal( aabb.bounds, ::getTransform( mesh ) );
@ -32,7 +31,7 @@ namespace {
bool hit = false;
// do collision per triangle
for ( auto triID : candidates ) {
auto tri = ::fetchTriangle( meshData, meshViews, triID, mesh ); // transform triangle to world space
auto tri = ::fetchTriangle( meshData, triID, mesh ); // transform triangle to world space
if ( !::triangleAabb( tri, aabb, manifold, eps ) ) continue;
hit = true;
}
@ -46,7 +45,6 @@ namespace {
const auto& bvh = *mesh.collider.mesh.bvh;
const auto& meshData = *mesh.collider.mesh.mesh;
const auto& meshViews = *mesh.collider.mesh.views;
// transform to local space for BVH query
auto bounds = ::transformAabbToLocal( sphere.bounds, ::getTransform( mesh ) );
@ -57,7 +55,7 @@ namespace {
bool hit = false;
// do collision per triangle
for ( auto triID : candidates ) {
auto tri = ::fetchTriangle( meshData, meshViews, triID, mesh ); // transform triangle to world space
auto tri = ::fetchTriangle( meshData, triID, mesh ); // transform triangle to world space
if ( !::triangleSphere( tri, sphere, manifold, eps ) ) continue;
hit = true;
}
@ -73,7 +71,6 @@ namespace {
const auto& bvh = *mesh.collider.mesh.bvh;
const auto& meshData = *mesh.collider.mesh.mesh;
const auto& meshViews = *mesh.collider.mesh.views;
// transform to local space for BVH query
auto bounds = ::transformAabbToLocal( plane.bounds, ::getTransform( mesh ) );
@ -84,7 +81,7 @@ namespace {
bool hit = false;
// do collision per triangle
for ( auto triID : candidates ) {
auto tri = ::fetchTriangle( meshData, meshViews, triID, mesh ); // transform triangle to world space
auto tri = ::fetchTriangle( meshData, triID, mesh ); // transform triangle to world space
if ( !::trianglePlane( tri, plane, manifold, eps ) ) continue;
hit = true;
}
@ -99,7 +96,6 @@ namespace {
const auto& bvh = *mesh.collider.mesh.bvh;
const auto& meshData = *mesh.collider.mesh.mesh;
const auto& meshViews = *mesh.collider.mesh.views;
// transform to local space for BVH query
auto bounds = ::transformAabbToLocal( capsule.bounds, ::getTransform( mesh ) );
@ -110,7 +106,7 @@ namespace {
bool hit = false;
// do collision per triangle
for ( auto triID : candidates ) {
auto tri = ::fetchTriangle( meshData, meshViews, triID, mesh ); // transform triangle to world space
auto tri = ::fetchTriangle( meshData, triID, mesh ); // transform triangle to world space
if ( !::triangleCapsule( tri, capsule, manifold, eps ) ) continue;
hit = true;
}
@ -123,11 +119,9 @@ namespace {
const auto& bvhA = *a.collider.mesh.bvh;
const auto& meshA = *a.collider.mesh.mesh;
const auto& viewsA = *a.collider.mesh.views;
const auto& bvhB = *b.collider.mesh.bvh;
const auto& meshB = *b.collider.mesh.mesh;
const auto& viewsB = *b.collider.mesh.views;
// compute overlaps between one BVH and another BVH
thread_local pod::BVH::pairs_t pairs;
@ -137,8 +131,8 @@ namespace {
bool hit = false;
// do collision per triangle
for (auto [ idA, idB] : pairs ) {
auto tA = ::fetchTriangle( meshA, viewsA, idA, a ); // transform triangles to world space
auto tB = ::fetchTriangle( meshB, viewsB, idB, b );
auto tA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space
auto tB = ::fetchTriangle( meshB, idB, b );
if ( !::triangleTriangle( tA, tB, manifold, eps ) ) continue;
hit = true;

View File

@ -195,7 +195,6 @@ namespace {
bool rayMesh( const pod::Ray& r, const pod::PhysicsBody& body, pod::RayQuery& rayHit ) {
const auto& bvh = *body.collider.mesh.bvh;
const auto& meshData = *body.collider.mesh.mesh;
const auto& meshViews = *body.collider.mesh.views;
const auto transform = ::getTransform( body );
@ -208,7 +207,7 @@ namespace {
::queryBVH( bvh, ray, candidates );
for ( auto triID : candidates ) {
auto tri = ::fetchTriangle( meshData, meshViews, triID );
auto tri = ::fetchTriangle( meshData, triID );
float t, u, v;
if ( !::rayTriangleIntersect( ray, tri, t, u, v ) ) continue;

View File

@ -91,7 +91,8 @@ namespace {
return tri;
}
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, const uf::Mesh::views_t& views, size_t triID ) {
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID ) {
const auto& views = mesh.buffer_views;
UF_ASSERT(!views.empty());
// find which view contains this triangle index.
@ -118,8 +119,8 @@ 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, const uf::Mesh::views_t& views, size_t triID, const pod::PhysicsBody& body, bool fast = false ) {
auto tri = ::fetchTriangle( mesh, views, triID );
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body, bool fast = false ) {
auto tri = ::fetchTriangle( mesh, triID );
auto transform = ::getTransform( body );
@ -230,10 +231,82 @@ namespace {
return { std::min({ p0, p1, p2 }), std::max({ p0, p1, p2 }) };
}
}
// triangle colliders
namespace {
// i feel like it'd just be better to treat an AABB as a 12-triangle mesh and do a triangleTriangle collision instead of 3 + 1 + 3 * 3 axis tests
// but what do i know
bool triangleAabbSAT( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps = 1e-6f ) {
const auto& aabb = body.bounds;
// box center and half extents
pod::Vector3f boxCenter = ::aabbCenter( aabb );
pod::Vector3f boxHalf = ::aabbExtent( aabb );
// move triangle into box-local space
pod::Vector3f v0 = tri.points[0] - boxCenter;
pod::Vector3f v1 = tri.points[1] - boxCenter;
pod::Vector3f v2 = tri.points[2] - boxCenter;
// triangle edges
pod::Vector3f e0 = v1 - v0;
pod::Vector3f e1 = v2 - v1;
pod::Vector3f e2 = v0 - v2;
float minOverlap = FLT_MAX;
pod::Vector3f bestAxis;
auto testAxis = [&](const pod::Vector3f& axis) -> bool {
if ( uf::vector::magnitude(axis) < eps ) return true; // skip degenerate
pod::Vector3f n = uf::vector::normalize(axis);
// project triangle
float t0 = uf::vector::dot(v0, n);
float t1 = uf::vector::dot(v1, n);
float t2 = uf::vector::dot(v2, n);
float triMin = std::min({t0, t1, t2});
float triMax = std::max({t0, t1, t2});
// project box (radius along axis)
float r = boxHalf.x * fabs(n.x) + boxHalf.y * fabs(n.y) + boxHalf.z * fabs(n.z); // to-do: use boxHalf + uf::vector::abs( n ) or something
// overlap test
if ( triMin > r || triMax < -r ) return false; // separating axis
// compute overlap depth
float overlap = std::min( triMin - r, triMax + r );
if ( overlap < minOverlap ) {
minOverlap = overlap;
bestAxis = n;
}
return true;
};
if ( !testAxis({1,0,0}) ) return false;
if ( !testAxis({0,1,0}) ) return false;
if ( !testAxis({0,0,1}) ) return false;
if ( !testAxis(uf::vector::cross(e0, e1)) ) return false;
pod::Vector3f boxAxes[3] = { {1,0,0}, {0,1,0}, {0,0,1} };
pod::Vector3f edges[3] = { e0, e1, e2 };
for ( auto& edge : edges ) {
for ( auto& axis : boxAxes ) if ( !testAxis(uf::vector::cross(edge, axis)) ) return false;
}
pod::Vector3f triNormal = uf::vector::normalize(uf::vector::cross(e0, e1));
float d = uf::vector::dot(triNormal, v0);
float dist = uf::vector::dot(triNormal, -boxCenter) - d;
pod::Vector3f contact = boxCenter - triNormal * dist;
if ( uf::vector::dot(bestAxis, triNormal) < 0.0f ) bestAxis = -bestAxis;
manifold.points.emplace_back( pod::Contact{ contact, bestAxis, minOverlap } );
return true;
}
bool triangleTriangle( const pod::TriangleWithNormal& a, const pod::TriangleWithNormal& b, pod::Manifold& manifold, float eps ) {
// if ( !::triangleTriangleIntersect( a, b ) ) return false;
@ -283,6 +356,9 @@ namespace {
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 );
#else
auto closest = ::closestPointOnTriangle( ::getPosition( aabb ), tri );
auto closestAabb = ::closestPointOnAABB( closest, aabb.bounds );
@ -306,6 +382,7 @@ namespace {
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
return true;
#endif
}
bool triangleSphere( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps ) {
const auto& sphere = body;

View File

@ -137,6 +137,7 @@ void uf::Mesh::updateDescriptor() {
_updateDescriptor(index);
_updateDescriptor(instance);
_updateDescriptor(indirect);
_updateViews();
}
void uf::Mesh::bind( const uf::Mesh& mesh ) { return bind( mesh, isInterleaved() ); }
void uf::Mesh::bind( const uf::Mesh& mesh, bool interleaved ) {
@ -490,6 +491,9 @@ void uf::Mesh::_updateDescriptor( uf::Mesh::Input& input ) {
attribute.stride = isInterleaved(input.interleaved) ? input.size : attribute.descriptor.size;
}
}
void uf::Mesh::_updateViews() {
buffer_views = makeViews();
}
uf::Mesh::Attribute uf::Mesh::_remapAttribute( const uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute, size_t i ) const {
UF_ASSERT( i < indirect.count );
UF_ASSERT( &input == &vertex || &input == &index );