From d07878408805a5b112033b393cd23e04f0ecc115 Mon Sep 17 00:00:00 2001 From: ecker Date: Sun, 9 Aug 2026 11:35:04 -0500 Subject: [PATCH] fixed footsteps returning wrong sound (due to it assuming the view ID was a triangle ID), overhauled flattened BVHs by shrinking node size and quantizing AABBS to reduce memory footprint, fixed BVHs to clear the unflattened node/bounds when flattened (for meshes, broadphase BVHs need to be updated to use the flattened versions) --- bin/data/config.json | 12 + bin/data/entities/model.json | 4 +- bin/dreamcast/data/config.json | 6 +- engine/inc/uf/utils/math/physics/common.h | 8 + .../uf/utils/math/physics/narrowphase/ray.h | 1 + engine/inc/uf/utils/math/shapes.h | 5 + engine/inc/uf/utils/memory/reader.h | 11 + engine/inc/uf/utils/mesh/mesh.h | 29 +- engine/src/engine/graph/graph.cpp | 4 +- engine/src/ext/lua/usertypes/mesh.cpp | 8 +- .../src/utils/math/physics/broadphase/bvh.cpp | 286 ++++++++---------- engine/src/utils/math/physics/common.cpp | 48 ++- .../utils/math/physics/narrowphase/mesh.cpp | 8 +- .../utils/math/physics/narrowphase/ray.cpp | 12 +- 14 files changed, 249 insertions(+), 193 deletions(-) diff --git a/bin/data/config.json b/bin/data/config.json index e40edf39..a438a8d5 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -355,6 +355,18 @@ "slop": 0.01, // 0.005 "max": 0.1 // 0.2 }, + "bvh": { + "bodies": { + "capacity": 1, + "flatten": true, + "sah": true + }, + "meshes": { + "capacity": 4, + "flatten": true, + "sah": true + } + }, "debug draw": { "static": false, "dynamic": false, diff --git a/bin/data/entities/model.json b/bin/data/entities/model.json index 92b82b86..2373e157 100644 --- a/bin/data/entities/model.json +++ b/bin/data/entities/model.json @@ -11,7 +11,7 @@ "metadata": { "graph": { "debug": { - "simple": true, + // "simple": true, "no cleanup": false, "print": { "tree": false, @@ -79,7 +79,7 @@ "stream": { "tag": "worldspawn", "player": "info_player_start", - "enabled": true, // "auto", + "enabled": "auto", "radius": 50, "every": 1 } diff --git a/bin/dreamcast/data/config.json b/bin/dreamcast/data/config.json index cf30fb2d..6d02eb65 100644 --- a/bin/dreamcast/data/config.json +++ b/bin/dreamcast/data/config.json @@ -81,13 +81,13 @@ }, "bvh": { "bodies": { - "capacity": 4, - "flatten": false, + "capacity": 1, + "flatten": true, "sah": false }, "meshes": { "capacity": 4, - "flatten": false, + "flatten": true, "sah": false } }, diff --git a/engine/inc/uf/utils/math/physics/common.h b/engine/inc/uf/utils/math/physics/common.h index c61ed502..d0207dd2 100644 --- a/engine/inc/uf/utils/math/physics/common.h +++ b/engine/inc/uf/utils/math/physics/common.h @@ -79,4 +79,12 @@ namespace impl { std::pair getCapsuleSegment( const pod::PhysicsBody& body ); pod::AABB computeAABB( const pod::PhysicsBody& body ); pod::AABB transformAabbToLocal( const pod::AABB& box, const pod::Transform<>& transform ); + + /*FORCE_INLINE*/ bool aabbOverlap( const pod::qAABB& a, const pod::qAABB& b ); + /*FORCE_INLINE*/ pod::qAABB quantizeAABB( const pod::AABB& box, const pod::AABB& root, const pod::Vector3f& invScale ); + /*FORCE_INLINE*/ pod::AABB dequantizeAABB( const pod::qAABB& qbox, const pod::AABB& root ); + /*FORCE_INLINE*/ pod::AABB dequantizeAABB( const pod::qAABB& qbox, const pod::AABB& root, const pod::Vector3f& scale ); + /*FORCE_INLINE*/ pod::Vector3f computeDequantizeScale( const pod::AABB& root ); + /*FORCE_INLINE*/ pod::Vector3f computeQuantizeScale( const pod::AABB& root ); + } \ No newline at end of file diff --git a/engine/inc/uf/utils/math/physics/narrowphase/ray.h b/engine/inc/uf/utils/math/physics/narrowphase/ray.h index 2e490020..41a61e5d 100644 --- a/engine/inc/uf/utils/math/physics/narrowphase/ray.h +++ b/engine/inc/uf/utils/math/physics/narrowphase/ray.h @@ -5,6 +5,7 @@ namespace impl { bool rayTriangleIntersect( const pod::Ray& ray, const pod::Triangle& tri, float& t, float& u, float& v ); bool rayAabbIntersect( const pod::Ray& ray, const pod::AABB& box, float& tMin, float& tMax ); + bool rayAabbIntersect( const pod::Ray& ray, const pod::qAABB& qbox, const pod::AABB& root, float& tMin, float& tMax ); bool rayAabb( const pod::Ray& ray, const pod::PhysicsBody& body, pod::RayQuery& rayHit ); bool rayObb( const pod::Ray& ray, const pod::PhysicsBody& body, pod::RayQuery& rayHit ); diff --git a/engine/inc/uf/utils/math/shapes.h b/engine/inc/uf/utils/math/shapes.h index b5aeb693..52dde0f2 100644 --- a/engine/inc/uf/utils/math/shapes.h +++ b/engine/inc/uf/utils/math/shapes.h @@ -16,6 +16,11 @@ namespace pod { pod::Vector3f max; }; + struct qAABB { + pod::Vector3us min; + pod::Vector3us max; + }; + struct OBB { #if OBB_EXTENT_CENTER pod::Vector3f extent; diff --git a/engine/inc/uf/utils/memory/reader.h b/engine/inc/uf/utils/memory/reader.h index 596c2b0e..c4fd9c71 100644 --- a/engine/inc/uf/utils/memory/reader.h +++ b/engine/inc/uf/utils/memory/reader.h @@ -32,6 +32,17 @@ namespace uf { inline uint32_t remaining() const { return m_endOffset - m_offset; } inline void skip( size_t bytes ) { m_offset += bytes; } + + template + void read( T* copy, size_t elements = 1 ) { + size_t readSize = sizeof(T) * elements; + align( alignof(T) ); + + if ( m_offset + readSize > m_endOffset ) return; + uf::stl::memcpy(copy, m_buffer.data() + m_offset, readSize); + m_offset += readSize; + } + template const T* read( size_t readSize = sizeof(T) ) { align( alignof(T) ); diff --git a/engine/inc/uf/utils/mesh/mesh.h b/engine/inc/uf/utils/mesh/mesh.h index 59569906..3b7114fb 100644 --- a/engine/inc/uf/utils/mesh/mesh.h +++ b/engine/inc/uf/utils/mesh/mesh.h @@ -156,17 +156,14 @@ namespace pod { typedef std::pair pair_t; typedef uf::stl::vector pairs_t; - static constexpr uint32_t VIEW_SHIFT = 16; - static constexpr uint32_t TRI_MASK = 0xFFFF; + static constexpr pod::BVH::index_t VIEW_SHIFT = 16; + static constexpr pod::BVH::index_t TRI_MASK = 0xFFFF; - static inline uint32_t packID(uint32_t viewID, uint32_t triID) { + static inline pod::BVH::index_t packID( pod::BVH::index_t viewID, pod::BVH::index_t triID ) { return (viewID << VIEW_SHIFT) | (triID & TRI_MASK); } - static inline uint32_t unpackView(uint32_t packedID) { - return packedID >> VIEW_SHIFT; - } - static inline uint32_t unpackTri(uint32_t packedID) { - return packedID & TRI_MASK; + static inline std::pair unpackID( pod::BVH::index_t packedID ) { + return std::make_pair( packedID >> VIEW_SHIFT, packedID & TRI_MASK ); } struct Node { BVH::index_t left = 0; @@ -184,8 +181,10 @@ namespace pod { }; struct FlatNode { - BVH::index_t start = 0; - BVH::index_t skipIndex = 0; + union { + BVH::index_t start; + BVH::index_t skipIndex; + }; BVH::index_t flags = 0; BVH::index_t getCount() const { return flags & 0x3FFFFFFF; } @@ -195,6 +194,8 @@ namespace pod { void setCount(BVH::index_t c) { flags = (flags & 0xC0000000u) | (c & 0x3FFFFFFF); } void setAsleep(bool a) { flags = (flags & ~0x80000000u) | (a ? 0x80000000u : 0); } void setUnloaded(bool u) { flags = (flags & ~0x40000000u) | (u ? 0x40000000u : 0); } + + BVH::index_t getSkipIndex( BVH::index_t idx ) const { return (getCount() > 0) ? (idx + 1) : skipIndex; } }; struct UpdatePolicy { enum class Decision { @@ -209,13 +210,15 @@ namespace pod { }; bool dirty = false; - bool flat = false; + + pod::AABB rootBounds; uf::stl::vector indices; + uf::stl::vector nodes; - uf::stl::vector flattened; + uf::stl::vector flatNodes; uf::stl::vector bounds; - uf::stl::vector flatBounds; + uf::stl::vector qBounds; }; } diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 03dcf384..71856723 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -2312,7 +2312,7 @@ void uf::graph::reload( pod::Graph& graph ) { auto& bvh = storage.bvhs.map[meshName]; auto& bvhStream = graph.streams.bvhs[meshName]; - if ( bvh.flattened.empty() && bvh.nodes.empty() && bvhStream.buffer.length > 0 ) { + if ( bvh.flatNodes.empty() && bvh.nodes.empty() && bvhStream.buffer.length > 0 ) { work.needsBvhLoad = true; } @@ -2614,7 +2614,7 @@ void uf::graph::reload( pod::Graph& graph ) { if ( bvhStream.buffer.length == 0 ) { rebuildBvh = true; } - bool bvhValid = !bvh.flattened.empty() || !bvh.nodes.empty(); + bool bvhValid = !bvh.flatNodes.empty() || !bvh.nodes.empty(); auto& indirectAttr = mesh.indirect.attributes.front(); pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.buffers[indirectAttr.buffer].data(); diff --git a/engine/src/ext/lua/usertypes/mesh.cpp b/engine/src/ext/lua/usertypes/mesh.cpp index a49e1173..9d6d9062 100644 --- a/engine/src/ext/lua/usertypes/mesh.cpp +++ b/engine/src/ext/lua/usertypes/mesh.cpp @@ -8,17 +8,15 @@ namespace binds { } std::tuple unpackID( uf::Mesh& self, size_t packedID ) { - uint32_t viewID = pod::BVH::unpackView(packedID); - uint32_t triID = pod::BVH::unpackTri(packedID); - return std::make_tuple( viewID, triID ); + return pod::BVH::unpackID( packedID ); } std::tuple fetchView( uf::Mesh& self, size_t triID ) { const auto* view = uf::mesh::fetchView( self, triID ); return std::make_tuple( view, triID ); } // to-do: fix - const pod::DrawCommand& fetchDrawCommand( uf::Mesh& mesh, size_t triID ) { - return uf::mesh::fetchDrawCommand( mesh, triID ); + const pod::DrawCommand& fetchDrawCommand( uf::Mesh& mesh, size_t viewID ) { + return uf::mesh::fetchDrawCommand( mesh, mesh.buffer_views[viewID] ); } size_t fetchIndex( const uf::Mesh::View& view, const uf::stl::string& name, size_t index ) { diff --git a/engine/src/utils/math/physics/broadphase/bvh.cpp b/engine/src/utils/math/physics/broadphase/bvh.cpp index 5786e296..e3594512 100644 --- a/engine/src/utils/math/physics/broadphase/bvh.cpp +++ b/engine/src/utils/math/physics/broadphase/bvh.cpp @@ -203,14 +203,12 @@ void impl::buildBroadphaseBVH( pod::BVH& bvh, const uf::stl::vector bounds; bounds.reserve( triangles ); - uf::stl::vector packedMap; - packedMap.reserve( triangles ); + uf::stl::vector packed; + packed.reserve( triangles ); const auto& views = mesh.buffer_views; UF_ASSERT( !views.empty() ); - uint32_t flatTriID = 0; + uint32_t triID = 0; uint32_t viewID = 0; for ( auto& view : views ) { auto& indices = view["index"]; @@ -247,25 +245,26 @@ void impl::buildMeshBVH( pod::BVH& bvh, const uf::Mesh& mesh, pod::BVH::index_t auto tri = uf::mesh::fetchTriangle( view, indices, positions, triIndexID ); auto aabb = impl::computeTriangleAABB( tri ); bounds.emplace_back( aabb ); - packedMap.emplace_back( pod::BVH::packID(viewID, triIndexID) ); - bvh.indices.emplace_back( flatTriID++ ); + packed.emplace_back( pod::BVH::packID(viewID, triIndexID) ); + bvh.indices.emplace_back( triID++ ); } viewID++; } UF_ASSERT( !bounds.empty() ); + // recursively build BVH from indices if ( uf::physics::settings.useBvhSahMeshes ) impl::buildBVHNode_SAH( bvh, bounds, 0, bvh.indices.size(), capacity ); else impl::buildBVHNode( bvh, bounds, 0, bvh.indices.size(), capacity ); - - if ( uf::physics::settings.flattenBvhMeshes ) { + // set root bounds + bvh.rootBounds = bvh.bounds[0]; + // flatten if requested + if ( uf::physics::settings.flattenBvhBodies ) { impl::flattenBVH( bvh, 0 ); + // to-do: cleanup unused buffers } - - for ( size_t i = 0; i < bvh.indices.size(); ++i ) { - bvh.indices[i] = packedMap[bvh.indices[i]]; - } - + // update packed IDs + for ( size_t i = 0; i < bvh.indices.size(); ++i ) bvh.indices[i] = packed[bvh.indices[i]]; // mark as clean bvh.dirty = false; } @@ -300,14 +299,11 @@ void impl::buildConvexHullBVH( pod::BVH& bvh, const uf::Mesh& mesh, pod::BVH::in // recursively build BVH from indices if ( uf::physics::settings.useBvhSahMeshes ) impl::buildBVHNode_SAH( bvh, bounds, 0, bvh.indices.size(), capacity ); else impl::buildBVHNode( bvh, bounds, 0, bvh.indices.size(), capacity ); - + bvh.rootBounds = bvh.bounds[0]; // flatten if requested - if ( uf::physics::settings.flattenBvhMeshes ) { + if ( uf::physics::settings.flattenBvhBodies ) { impl::flattenBVH( bvh, 0 ); - //bvh.nodes.clear(); - //bvh.bounds.clear(); - //bvh.nodes.shrink_to_fit(); - //bvh.bounds.shrink_to_fit(); + // to-do: cleanup unused buffers } // mark as clean @@ -322,7 +318,7 @@ pod::BVH::UpdatePolicy::Decision impl::decideBVHUpdate( pod::BVH& bvh, uf::stl:: if ( bodies.empty() ) return pod::BVH::UpdatePolicy::Decision::NONE; uint32_t dirtyCount = 0; - float oldRootArea = impl::aabbSurfaceArea( bvh.bounds[0] ); + float oldRootArea = impl::aabbSurfaceArea( bvh.rootBounds ); // update/check each body for ( auto i = 0; i < bvh.nodes.size(); ++i ) { @@ -334,8 +330,8 @@ pod::BVH::UpdatePolicy::Decision impl::decideBVHUpdate( pod::BVH& bvh, uf::stl:: auto& newBounds = body.bounds; // compute displacement relative to size - pod::Vector3f oldCenter = ( oldBounds.min + oldBounds.max ) * 0.5f; - pod::Vector3f newCenter = ( newBounds.min + newBounds.max ) * 0.5f; + pod::Vector3f oldCenter = impl::aabbCenter( oldBounds ); + pod::Vector3f newCenter = impl::aabbCenter( newBounds ); float displacement = uf::vector::distance( newCenter, oldCenter ); pod::Vector3f extent = oldBounds.max - oldBounds.min; @@ -343,34 +339,6 @@ pod::BVH::UpdatePolicy::Decision impl::decideBVHUpdate( pod::BVH& bvh, uf::stl:: if ( displacement > policy.displacementThreshold * size ) ++dirtyCount; } -/* - for ( auto idx : bvh.indices ) { - auto& body = *bodies[idx]; - - // to-do: instead check against bounds in BVH - pod::AABB oldBounds = body.bounds; - body.bounds = impl::computeAABB( body ); - pod::AABB newBounds = body.bounds; - - // compute displacement relative to size - pod::Vector3f oldCenter = ( oldBounds.min + oldBounds.max ) * 0.5f; - pod::Vector3f newCenter = ( newBounds.min + newBounds.max ) * 0.5f; - float displacement = uf::vector::distance( newCenter, oldCenter ); - - pod::Vector3f extent = oldBounds.max - oldBounds.min; - float size = std::max({extent.x, extent.y, extent.z, 1e-6f}); - - if ( displacement > policy.displacementThreshold * size ) ++dirtyCount; - } - // update nodes - for ( auto i = 0; i < bvh.nodes.size(); ++i ) { - auto& node = bvh.nodes[i]; - if ( node.getCount() == 0 ) continue; - auto& bound = bvh.bounds[i]; - bound = bodies[bvh.indices[node.start]]->bounds; - for ( auto i = 1; i < node.getCount(); ++i ) bound = impl::mergeAabb( bound, bodies[bvh.indices[node.start + i]]->bounds ); - } -*/ float dirtyRatio = (float) dirtyCount / (float) bodies.size(); @@ -419,7 +387,7 @@ void impl::refitBVH( pod::BVH& bvh, const uf::stl::vector& bounds ) { } } - if ( !bvh.flattened.empty() ) impl::flattenBVH( bvh, 0 ); + if ( !bvh.flatNodes.empty() ) impl::flattenBVH( bvh, 0 ); } // avoids creating a vector for bounds @@ -454,7 +422,7 @@ void impl::refitBVH( pod::BVH& bvh, const uf::stl::vector& bo node.setAsleep( bvh.nodes[node.left].isAsleep() && bvh.nodes[node.right].isAsleep()); } - if ( !bvh.flattened.empty() ) impl::flattenBVH( bvh, 0 ); + if ( !bvh.flatNodes.empty() ) impl::flattenBVH( bvh, 0 ); } void impl::refitBVH( pod::BVH& bvh, const uf::Mesh& mesh ) { @@ -485,43 +453,37 @@ void impl::refitBVH( pod::BVH& bvh, const uf::Mesh& mesh ) { pod::BVH::index_t impl::flattenBVH( pod::BVH& bvh, pod::BVH::index_t nodeID ) { if ( nodeID == 0 ) { - bvh.flattened.clear(); - bvh.flatBounds.clear(); + bvh.flatNodes.clear(); + bvh.qBounds.clear(); - bvh.flattened.reserve(bvh.nodes.size()); - bvh.flatBounds.reserve(bvh.bounds.size()); + bvh.flatNodes.reserve(bvh.nodes.size()); + bvh.qBounds.reserve(bvh.bounds.size()); } const auto& node = bvh.nodes[nodeID]; - - pod::BVH::index_t flatID = (pod::BVH::index_t) bvh.flattened.size(); - bvh.flattened.emplace_back(); // placeholder - bvh.flatBounds.emplace_back( bvh.bounds[nodeID] ); + pod::BVH::index_t flatID = (pod::BVH::index_t) bvh.flatNodes.size(); + bvh.flatNodes.emplace_back(); // placeholder + + pod::Vector3f invScale = impl::computeQuantizeScale( bvh.rootBounds ); + bvh.qBounds.emplace_back( impl::quantizeAABB( bvh.bounds[nodeID], bvh.rootBounds, invScale ) ); pod::BVH::FlatNode flat{}; - flat.start = 0; - flat.setCount(0); - flat.skipIndex = 0; + flat.setCount(node.getCount()); flat.setAsleep(node.isAsleep()); // leaf if ( node.getCount() > 0 ) { flat.start = node.start; - flat.setCount(node.getCount()); - flat.skipIndex = flatID + 1; - bvh.flattened[flatID] = flat; + bvh.flatNodes[flatID] = flat; return flatID + 1; } // internal else { - flat.start = 0; - flat.setCount(0); - pod::BVH::index_t leftID = impl::flattenBVH( bvh, node.left ); pod::BVH::index_t rightID = impl::flattenBVH( bvh, node.right ); - flat.skipIndex = rightID; // skip entire subtree - bvh.flattened[flatID] = flat; + flat.skipIndex = rightID; + bvh.flatNodes[flatID] = flat; return rightID; } } @@ -659,7 +621,7 @@ void impl::traverseBVH( const pod::BVH& bvh, pod::BVH::index_t nodeID, pod::BVH: } void impl::queryOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs ) { - if ( !bvh.flattened.empty() ) return impl::queryFlatOverlaps( bvh, outPairs ); + if ( !bvh.flatNodes.empty() ) return impl::queryFlatOverlaps( bvh, outPairs ); if ( bvh.nodes.empty() ) return; outPairs.reserve(uf::physics::settings.reserveCount); @@ -669,7 +631,7 @@ void impl::queryOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs ) { } void impl::queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ) { - if ( !bvhA.flattened.empty() && !bvhB.flattened.empty() ) return impl::queryFlatOverlaps( bvhA, bvhB, outPairs ); + if ( !bvhA.flatNodes.empty() && !bvhB.flatNodes.empty() ) return impl::queryFlatOverlaps( bvhA, bvhB, outPairs ); if ( bvhA.nodes.empty() || bvhB.nodes.empty() ) return; outPairs.reserve(uf::physics::settings.reserveCount); @@ -679,7 +641,7 @@ void impl::queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH:: } void impl::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 impl::queryFlatOverlaps( bvhA, bvhB, relTransform, outPairs ); + if ( !bvhA.flatNodes.empty() && !bvhB.flatNodes.empty() ) return impl::queryFlatOverlaps( bvhA, bvhB, relTransform, outPairs ); if ( bvhA.nodes.empty() || bvhB.nodes.empty() ) return; outPairs.reserve(uf::physics::settings.reserveCount); @@ -690,10 +652,9 @@ void impl::queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod: // query a BVH with an AABB via a stack void impl::queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector& outIndices ) { - if ( bvh.nodes.empty() ) return; - - if ( !bvh.flattened.empty() ) return impl::queryFlatBVH( bvh, bounds, outIndices ); + if ( !bvh.flatNodes.empty() ) return impl::queryFlatBVH( bvh, bounds, outIndices ); + if ( bvh.nodes.empty() ) return; outIndices.reserve(uf::physics::settings.reserveCount); static thread_local uf::stl::stack stack; @@ -720,7 +681,7 @@ void impl::queryBVH( const pod::BVH& bvh, const pod::PhysicsBody& body, uf::stl: // query a BVH with an AABB via recursion void impl::queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector& outIndices, pod::BVH::index_t nodeID ) { - if ( !bvh.flattened.empty() ) return impl::queryFlatBVH( bvh, bounds, outIndices ); + if ( !bvh.flatNodes.empty() ) return impl::queryFlatBVH( bvh, bounds, outIndices ); if ( nodeID == 0 ) outIndices.reserve(uf::physics::settings.reserveCount); @@ -740,7 +701,7 @@ void impl::queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vect // query a BVH with a ray via a stack void impl::queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector& outIndices, float maxDist ) { - if ( !bvh.flattened.empty() ) return impl::queryFlatBVH( bvh, ray, outIndices, maxDist ); + if ( !bvh.flatNodes.empty() ) return impl::queryFlatBVH( bvh, ray, outIndices, maxDist ); if ( bvh.nodes.empty() ) return; outIndices.reserve(uf::physics::settings.reserveCount); @@ -768,7 +729,7 @@ void impl::queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector

& outIndices, pod::BVH::index_t nodeID, float maxDist ) { - if ( !bvh.flattened.empty() ) return impl::queryFlatBVH( bvh, ray, outIndices, maxDist ); + if ( !bvh.flatNodes.empty() ) return impl::queryFlatBVH( bvh, ray, outIndices, maxDist ); if ( nodeID == 0 ) outIndices.reserve(uf::physics::settings.reserveCount); @@ -789,8 +750,8 @@ void impl::queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector

indexB ) std::swap(indexA, indexB); - outPairs.emplace_back( indexA, indexB ); } } @@ -830,12 +790,12 @@ void impl::queryFlatOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs ) impl::postprocessPairs( outPairs ); } void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ) { - auto& nodesA = bvhA.flattened; - auto& boundsA = bvhA.flatBounds; + auto& nodesA = bvhA.flatNodes; + auto& boundsA = bvhA.qBounds; auto& indicesA = bvhA.indices; - auto& nodesB = bvhB.flattened; - auto& boundsB = bvhB.flatBounds; + auto& nodesB = bvhB.flatNodes; + auto& boundsB = bvhB.qBounds; auto& indicesB = bvhB.indices; if ( nodesA.empty() || nodesB.empty() ) return; @@ -844,6 +804,9 @@ void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::B STATIC_THREAD_LOCAL(pod::BVH::pairs_t, stack); stack.emplace_back(0, 0); + pod::Vector3f scaleA = impl::computeDequantizeScale( bvhA.rootBounds ); + pod::Vector3f scaleB = impl::computeDequantizeScale( bvhB.rootBounds ); + while ( !stack.empty() ) { auto [a, b] = stack.back(); stack.pop_back(); @@ -851,12 +814,12 @@ void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::B const auto& nodeA = nodesA[a]; const auto& nodeB = nodesB[b]; - if ( nodeA.isAsleep() && nodeB.isAsleep() ) continue; - if ( nodeA.isUnloaded() || nodeB.isUnloaded() ) continue; + if ( (nodeA.isAsleep() && nodeB.isAsleep()) || nodeA.isUnloaded() || nodeB.isUnloaded() ) continue; - if ( !impl::aabbOverlap( boundsA[a], boundsB[b] ) ) { - continue; - } + pod::AABB floatA = impl::dequantizeAABB( boundsA[a], bvhA.rootBounds, scaleA ); + pod::AABB floatB = impl::dequantizeAABB( boundsB[b], bvhB.rootBounds, scaleB ); + + if ( !impl::aabbOverlap( floatA, floatB ) ) continue; bool isLeafA = (nodeA.getCount() > 0); bool isLeafB = (nodeB.getCount() > 0); @@ -871,18 +834,18 @@ void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::B } } else if ( isLeafA ) { - pod::BVH::index_t rightB = nodesB[b + 1].skipIndex; + pod::BVH::index_t rightB = bvhB.flatNodes[b + 1].getSkipIndex( b + 1 ); stack.emplace_back(a, b + 1); stack.emplace_back(a, rightB); } else if ( isLeafB ) { - pod::BVH::index_t rightA = nodesA[a + 1].skipIndex; + pod::BVH::index_t rightA = bvhA.flatNodes[a + 1].getSkipIndex( a + 1 ); stack.emplace_back(a + 1, b); stack.emplace_back(rightA, b); } else { - pod::BVH::index_t rightA = nodesA[a + 1].skipIndex; - pod::BVH::index_t rightB = nodesB[b + 1].skipIndex; + pod::BVH::index_t rightA = bvhA.flatNodes[a + 1].getSkipIndex( a + 1 ); + pod::BVH::index_t rightB = bvhB.flatNodes[b + 1].getSkipIndex( b + 1 ); stack.emplace_back(a + 1, b + 1); stack.emplace_back(a + 1, rightB); @@ -890,17 +853,16 @@ void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::B stack.emplace_back(rightA, rightB); } } - impl::postprocessPairs( outPairs ); } void impl::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& nodesA = bvhA.flatNodes; + auto& boundsA = bvhA.qBounds; auto& indicesA = bvhA.indices; - auto& nodesB = bvhB.flattened; - auto& boundsB = bvhB.flatBounds; + auto& nodesB = bvhB.flatNodes; + auto& boundsB = bvhB.qBounds; auto& indicesB = bvhB.indices; if ( nodesA.empty() || nodesB.empty() ) return; @@ -909,18 +871,24 @@ void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const STATIC_THREAD_LOCAL(pod::BVH::pairs_t, stack); stack.emplace_back(0, 0); + pod::Vector3f scaleA = (bvhA.rootBounds.max - bvhA.rootBounds.min) / 65535.0f; + pod::Vector3f scaleB = (bvhB.rootBounds.max - bvhB.rootBounds.min) / 65535.0f; + while ( !stack.empty() ) { auto [a, b] = stack.back(); stack.pop_back(); - const auto& nodeA = bvhA.flattened[a]; - const auto& nodeB = bvhB.flattened[b]; + const auto& nodeA = nodesA[a]; + const auto& nodeB = nodesB[b]; - if ( nodeA.isAsleep() && nodeB.isAsleep() ) continue; - if ( nodeA.isUnloaded() || nodeB.isUnloaded() ) continue; + if ( (nodeA.isAsleep() && nodeB.isAsleep()) || nodeA.isUnloaded() || nodeB.isUnloaded() ) continue; - pod::AABB boundsB_in_A = impl::transformAabbToWorld(boundsB[b], relTransform); - if ( !impl::aabbOverlap( boundsA[a], boundsB_in_A ) ) continue; + pod::AABB floatA = impl::dequantizeAABB( boundsA[a], bvhA.rootBounds, scaleA ); + pod::AABB floatB = impl::dequantizeAABB( boundsB[b], bvhB.rootBounds, scaleB ); + + pod::AABB boundsB_in_A = impl::transformAabbToWorld(floatB, relTransform); + + if ( !impl::aabbOverlap( floatA, boundsB_in_A ) ) continue; bool isLeafA = (nodeA.getCount() > 0); bool isLeafB = (nodeB.getCount() > 0); @@ -935,18 +903,18 @@ void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const } } else if ( isLeafA ) { - pod::BVH::index_t rightB = nodesB[b + 1].skipIndex; + pod::BVH::index_t rightB = nodesB[b + 1].getSkipIndex( b + 1 ); stack.emplace_back(a, b + 1); stack.emplace_back(a, rightB); } else if ( isLeafB ) { - pod::BVH::index_t rightA = nodesA[a + 1].skipIndex; + pod::BVH::index_t rightA = nodesA[a + 1].getSkipIndex( a + 1 ); stack.emplace_back(a + 1, b); stack.emplace_back(rightA, b); } else { - pod::BVH::index_t rightA = nodesA[a + 1].skipIndex; - pod::BVH::index_t rightB = nodesB[b + 1].skipIndex; + pod::BVH::index_t rightA = nodesA[a + 1].getSkipIndex( a + 1 ); + pod::BVH::index_t rightB = nodesB[b + 1].getSkipIndex( b + 1 ); stack.emplace_back(a + 1, b + 1); stack.emplace_back(a + 1, rightB); @@ -954,22 +922,25 @@ void impl::queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const stack.emplace_back(rightA, rightB); } } - impl::postprocessPairs( outPairs ); } void impl::queryFlatBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector& outIndices ) { - auto& nodes = bvh.flattened; + auto& nodes = bvh.flatNodes; auto& indices = bvh.indices; + if ( nodes.empty() ) return; outIndices.reserve(uf::physics::settings.reserveCount); + if ( !impl::aabbOverlap( bounds, bvh.rootBounds ) ) return; + + pod::Vector3f invScale = impl::computeQuantizeScale( bvh.rootBounds ); + pod::qAABB query = impl::quantizeAABB( bounds, bvh.rootBounds, invScale ); pod::BVH::index_t idx = 0; while ( idx < nodes.size() ) { const auto& node = nodes[idx]; - if ( !node.isAsleep() && !node.isUnloaded() && impl::aabbOverlap( bounds, bvh.flatBounds[idx] ) ) { - // leaf + if ( !node.isAsleep() && !node.isUnloaded() && impl::aabbOverlap( query, bvh.qBounds[idx] ) ) { if ( node.getCount() > 0 ) { for ( auto i = 0; i < node.getCount(); ++i ) { outIndices.emplace_back( indices[node.start + i] ); @@ -977,34 +948,37 @@ void impl::queryFlatBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl:: } ++idx; } else { - // skip this subtree - idx = node.skipIndex; + idx = node.getSkipIndex( idx ); } } } void impl::queryFlatBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector& outIndices, float maxDist ) { - auto& nodes = bvh.flattened; + auto& nodes = bvh.flatNodes; auto& indices = bvh.indices; outIndices.reserve(uf::physics::settings.reserveCount); pod::BVH::index_t idx = 0; + pod::Vector3f scale = (bvh.rootBounds.max - bvh.rootBounds.min) / 65535.0f; while ( idx < nodes.size() ) { const auto& node = nodes[idx]; float tMin, tMax; - if ( !node.isAsleep() && !node.isUnloaded() && impl::rayAabbIntersect( ray, bvh.flatBounds[idx], tMin, tMax ) && tMin <= maxDist ) { - // leaf - if ( node.getCount() > 0 ) { - for ( auto i = 0; i < node.getCount(); ++i ) { - outIndices.emplace_back( indices[node.start + i] ); + + if ( !node.isAsleep() && !node.isUnloaded() ) { + pod::AABB bounds = impl::dequantizeAABB( bvh.qBounds[idx], bvh.rootBounds, scale ); + + if ( impl::rayAabbIntersect( ray, bounds, tMin, tMax ) && tMin <= maxDist ) { + if ( node.getCount() > 0 ) { + for ( auto i = 0; i < node.getCount(); ++i ) { + outIndices.emplace_back( indices[node.start + i] ); + } } + ++idx; + continue; } - ++idx; - } else { - // skip this subtree - idx = node.skipIndex; } + idx = node.getSkipIndex( idx ); } } @@ -1013,23 +987,21 @@ void impl::postprocessPairs( pod::BVH::pairs_t& pairs ) { pairs.erase(std::unique(pairs.begin(), pairs.end()), pairs.end()); } -void uf::bvh::flagAsActive( pod::BVH& bvh, uint32_t viewID, bool active ) { - if ( !bvh.flattened.empty() ) { - for ( auto& node : bvh.flattened ) { +void uf::bvh::flagAsActive( pod::BVH& bvh, uint32_t index, bool active ) { + if ( !bvh.flatNodes.empty() ) { + for ( auto& node : bvh.flatNodes ) { if ( node.getCount() > 0 ) { - uint32_t packedID = bvh.indices[node.start]; - if ( pod::BVH::unpackView(packedID) == viewID ) { - node.setUnloaded(!active); - } + auto [ viewID, triID ] = pod::BVH::unpackID( bvh.indices[node.start] ); + if ( viewID != index ) continue; + node.setUnloaded(!active); } } } else if ( !bvh.nodes.empty() ) { for ( auto& node : bvh.nodes ) { if ( node.getCount() > 0 ) { - uint32_t packedID = bvh.indices[node.start]; - if ( pod::BVH::unpackView(packedID) == viewID ) { - node.setUnloaded(!active); - } + auto [ viewID, triID ] = pod::BVH::unpackID( bvh.indices[node.start] ); + if ( viewID != index ) continue; + node.setUnloaded(!active); } } } @@ -1040,11 +1012,11 @@ size_t uf::bvh::serialize( const pod::BVH& bvh, uf::stl::vector& outBuf writer.write( (uint32_t)( bvh.indices.size() ) ); writer.write( (uint32_t)( bvh.nodes.size() ) ); - writer.write( (uint32_t)( bvh.flattened.size() ) ); + writer.write( (uint32_t)( bvh.flatNodes.size() ) ); if ( !bvh.indices.empty() ) writer.write( bvh.indices ); if ( !bvh.nodes.empty() ) { writer.write( bvh.nodes ); writer.write( bvh.bounds); } - if ( !bvh.flattened.empty() ) { writer.write( bvh.flattened ); writer.write( bvh.flatBounds ); } + if ( !bvh.flatNodes.empty() ) { writer.write( bvh.flatNodes ); writer.write( bvh.qBounds ); } return writer.offset() - offset; } @@ -1062,12 +1034,11 @@ bool uf::bvh::deserialize( pod::BVH& bvh, const uf::stl::vector& buffer uint32_t numNodes = *pNumNodes; uint32_t numFlat = *pNumFlat; -// UF_MSG_DEBUG("Indices={}, Map={}, Nodes={}, Flat={}", numIndices, numMap, numNodes, numFlat); bvh.indices.clear(); bvh.nodes.clear(); bvh.bounds.clear(); - bvh.flattened.clear(); - bvh.flatBounds.clear(); + bvh.flatNodes.clear(); + bvh.qBounds.clear(); if ( numIndices > 0 ) { if ( !reader.read( numIndices, bvh.indices ) ) return false; @@ -1075,13 +1046,22 @@ bool uf::bvh::deserialize( pod::BVH& bvh, const uf::stl::vector& buffer if ( numNodes > 0 ) { - if ( !reader.read( numNodes, bvh.nodes ) ) return false; - if ( !reader.read( numNodes, bvh.bounds ) ) return false; + // it "works", but sometimes unstable + if ( numFlat > 0 ) { + reader.skip( numNodes * sizeof(pod::BVH::Node) ); + reader.read( &bvh.rootBounds ); // read the first bounds as our root bounds + reader.skip( (numNodes - 1) * sizeof(pod::AABB) ); + } else { + if ( !reader.read( numNodes, bvh.nodes ) ) return false; + if ( !reader.read( numNodes, bvh.bounds ) ) return false; + + bvh.rootBounds = bvh.bounds[0]; // to-do: serialize this instead? + } } if ( numFlat > 0 ) { - if ( !reader.read( numFlat, bvh.flattened ) ) return false; - if ( !reader.read( numFlat, bvh.flatBounds ) ) return false; + if ( !reader.read( numFlat, bvh.flatNodes ) ) return false; + if ( !reader.read( numFlat, bvh.qBounds ) ) return false; } bvh.dirty = false; diff --git a/engine/src/utils/math/physics/common.cpp b/engine/src/utils/math/physics/common.cpp index 727ebf4e..7f2caeb7 100644 --- a/engine/src/utils/math/physics/common.cpp +++ b/engine/src/utils/math/physics/common.cpp @@ -567,9 +567,7 @@ pod::Vector3f impl::triangleNormal( const pod::TriangleWithNormal& tri ) { } // if body is a mesh, apply its transform to the triangles, else reorient the normal with respect to the body pod::TriangleWithNormal impl::fetchTriangle( const uf::Mesh& mesh, size_t packedID, const pod::PhysicsBody& body ) { - // to-do: adjust these better - uint32_t viewID = pod::BVH::unpackView(packedID); - uint32_t triID = pod::BVH::unpackTri(packedID); + auto [ viewID, triID ] = pod::BVH::unpackID(packedID); auto tri = uf::mesh::fetchTriangle( mesh, viewID, triID ); auto transform = impl::getTransform( body ); @@ -750,8 +748,7 @@ pod::AABB impl::computeAABB( const pod::PhysicsBody& body ) { case pod::ShapeType::CONVEX_HULL: { if ( body.collider.mesh.bvh ) { const auto& bvh = *body.collider.mesh.bvh; - if ( !bvh.flatBounds.empty() ) return impl::transformAabbToWorld( bvh.flatBounds[0], transform ); - if ( !bvh.bounds.empty() ) return impl::transformAabbToWorld( bvh.bounds[0], transform ); + return impl::transformAabbToWorld( bvh.rootBounds, transform ); } const auto& meshData = *body.collider.mesh.mesh; pod::AABB bounds = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } }; @@ -824,14 +821,49 @@ float impl::getMaterialTransmittance( const uf::stl::string& materialName ) { return 0.2f; } -uf::stl::string impl::getMaterialName( const pod::PhysicsBody& body, uint32_t triID ) { - if ( triID == (uint32_t)(-1) ) return ""; +uf::stl::string impl::getMaterialName( const pod::PhysicsBody& body, uint32_t packedID ) { + if ( packedID == (uint32_t)(-1) ) return ""; if ( body.collider.type != pod::ShapeType::MESH ) return ""; + auto [ viewID, triID ] = pod::BVH::unpackID( packedID ); auto& scene = uf::scene::getCurrentScene(); auto& graph = scene.getComponent(); auto& mesh = *body.collider.mesh.mesh; - auto drawCommand = uf::mesh::fetchDrawCommand( mesh, triID ); + auto& view = mesh.buffer_views[viewID]; + auto drawCommand = uf::mesh::fetchDrawCommand( mesh, view ); auto instance = uf::graph::getInstance( graph, drawCommand.instanceID ); return uf::graph::getMaterialName( graph, instance.materialID ); +} +// +// quantized AABBs +bool impl::aabbOverlap( const pod::qAABB& a, const pod::qAABB& b ) { + return (a.min <= b.max) && (a.max >= b.min); +} +pod::qAABB impl::quantizeAABB( const pod::AABB& box, const pod::AABB& root, const pod::Vector3f& invScale ) { + pod::Vector3f min = (box.min - root.min) * invScale; + pod::Vector3f max = (box.max - root.min) * invScale; + + return { + uf::vector::clamp( min, 0.0f, 65535.0f ), + uf::vector::clamp( max, 0.0f, 65535.0f ) + }; +} +pod::AABB impl::dequantizeAABB( const pod::qAABB& qbox, const pod::AABB& root ) { + pod::Vector3f scale = (root.max - root.min) / 65535.0f; + return impl::dequantizeAABB( qbox, root, scale ); +} +pod::AABB impl::dequantizeAABB( const pod::qAABB& qbox, const pod::AABB& root, const pod::Vector3f& scale ) { + return { + root.min + static_cast(qbox.min) * scale, + root.min + static_cast(qbox.max) * scale + }; +} +pod::Vector3f impl::computeDequantizeScale( const pod::AABB& root ) { + return (root.max - root.min) * (1.0f / 65535.0f); +} + +pod::Vector3f impl::computeQuantizeScale( const pod::AABB& root ) { + pod::Vector3f extent = root.max - root.min; + extent = uf::vector::max( extent, pod::Vector3f{EPS, EPS, EPS} ); + return uf::vector::divide( 65535.0f, extent ); } \ No newline at end of file diff --git a/engine/src/utils/math/physics/narrowphase/mesh.cpp b/engine/src/utils/math/physics/narrowphase/mesh.cpp index 7df600b4..ef81e121 100644 --- a/engine/src/utils/math/physics/narrowphase/mesh.cpp +++ b/engine/src/utils/math/physics/narrowphase/mesh.cpp @@ -216,8 +216,10 @@ void impl::drawMesh( const pod::PhysicsBody& body ) { // draw BVH instead if ( body.inverseMass == 0.0f ) { const auto& bvh = *body.collider.mesh.bvh; - if ( !bvh.flatBounds.empty() ) { - for ( const auto& bound : bvh.flatBounds ) uf::debug::drawShape( bound, transform ); + if ( !bvh.qBounds.empty() ) { + for ( const auto& qBounds : bvh.qBounds ) { + uf::debug::drawShape( impl::dequantizeAABB( qBounds, bvh.rootBounds ), transform ); + } return; } if ( !bvh.bounds.empty() ) { @@ -263,7 +265,7 @@ pod::PhysicsBody& uf::physics::initialize( pod::PhysicsBody& body, const uf::Mes body.collider.mesh.ownsBvh = false; // to-do: move this to the above initialize to allow for deferred BVH building? - if ( bvh.nodes.empty() && bvh.flattened.empty() ) { + if ( bvh.nodes.empty() && bvh.flatNodes.empty() ) { impl::buildMeshBVH( bvh, mesh, uf::physics::settings.meshBvhCapacity ); } diff --git a/engine/src/utils/math/physics/narrowphase/ray.cpp b/engine/src/utils/math/physics/narrowphase/ray.cpp index 2a7b850f..a0f0de95 100644 --- a/engine/src/utils/math/physics/narrowphase/ray.cpp +++ b/engine/src/utils/math/physics/narrowphase/ray.cpp @@ -52,6 +52,11 @@ bool impl::rayAabbIntersect( const pod::Ray& ray, const pod::AABB& box, float& t return true; } +bool impl::rayAabbIntersect( const pod::Ray& ray, const pod::qAABB& qbox, const pod::AABB& root, float& tMin, float& tMax ) { + pod::AABB box = impl::dequantizeAABB( qbox, root ); + return impl::rayAabbIntersect( ray, box, tMin, tMax ); +} + bool impl::rayAabb( const pod::Ray& ray, const pod::PhysicsBody& body, pod::RayQuery& rayHit ) { float tMin = 0.0f; float tMax = FLT_MAX; @@ -243,9 +248,8 @@ bool impl::rayMesh( const pod::Ray& r, const pod::PhysicsBody& body, pod::RayQue impl::queryBVH( bvh, ray, candidates ); for ( auto packedID : candidates ) { - uint32_t viewID = pod::BVH::unpackView(packedID); - uint32_t triID = pod::BVH::unpackTri(packedID); - auto tri = uf::mesh::fetchTriangle( meshData, triID ); + auto [ viewID, triID ] = pod::BVH::unpackID( packedID ); + auto tri = uf::mesh::fetchTriangle( meshData, viewID, triID ); float t, u, v; if ( !impl::rayTriangleIntersect( ray, tri, t, u, v ) ) continue; @@ -263,7 +267,7 @@ bool impl::rayMesh( const pod::Ray& r, const pod::PhysicsBody& body, pod::RayQue rayHit.contact.point = p; rayHit.contact.normal = n; rayHit.contact.penetration = t; - rayHit.contact.featureA = triID; + rayHit.contact.featureA = packedID; // could instead pack featureA and featureB with unpacked IDs, but nothing uses this right now } return rayHit.hit;