From 0ce6b57867b6c82871222384af1b8963bcca238c Mon Sep 17 00:00:00 2001 From: ecker Date: Mon, 1 Sep 2025 20:21:32 -0500 Subject: [PATCH] fixes because i neglected to test the uf::graph changes on other scenes (just to realize i really need to optimize the internal physics system...) --- Makefile | 6 +- bin/data/entities/prop.json | 7 +- .../sourceengine/base_sourceengine.json | 2 +- .../scenes/sourceengine/mds_mcdonalds.json | 4 +- .../scenes/sourceengine/sourceengine.json | 4 +- bin/data/scenes/sourceengine/ss2_medsci1.json | 4 +- bin/exe/default/renderer | 2 +- engine/inc/uf/engine/graph/graph.h | 4 + engine/inc/uf/utils/math/physics/impl.h | 41 ++++++- engine/src/engine/ext/player/behavior.cpp | 19 +++- engine/src/engine/graph/decode.cpp | 12 +++ engine/src/engine/graph/graph.cpp | 35 +++--- engine/src/engine/object/behavior.cpp | 12 ++- engine/src/ext/gltf/gltf.cpp | 3 +- engine/src/ext/gltf/processPrimitives.inl | 21 ++-- engine/src/ext/xatlas/xatlas.cpp | 102 +++++++++++------- engine/src/utils/math/physics/bvh.inl | 75 +++++++------ engine/src/utils/math/physics/helpers.inl | 7 ++ engine/src/utils/math/physics/impl.cpp | 82 +++++++++++--- engine/src/utils/math/physics/integration.inl | 2 +- engine/src/utils/math/physics/mesh.inl | 4 - engine/src/utils/math/physics/ray.inl | 4 - 22 files changed, 304 insertions(+), 148 deletions(-) diff --git a/Makefile b/Makefile index b7379926..19a4ffdb 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ ifneq (,$(findstring win64,$(ARCH))) REQ_DEPS += meshoptimizer toml xatlas curl ffx:fsr dc:texconv # vall_e cpptrace # openvr # ncurses draco discord bullet ultralight-ux FLAGS += -march=native -g # -flto # -g endif - REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit simd ctti gltf imgui fmt freetype openal ogg wav # reactphysics + REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit reactphysics simd ctti gltf imgui fmt freetype openal ogg wav FLAGS += -DUF_ENV_WINDOWS -DUF_ENV_WIN64 -DWIN32_LEAN_AND_MEAN DEPS += -lgdi32 -ldwmapi LINKS += #-Wl,-subsystem,windows @@ -76,13 +76,13 @@ else ifneq (,$(findstring linux,$(ARCH))) REQ_DEPS += toml xatlas curl dc:texconv # meshoptimizer ffx:fsr cpptrace vall_e # ncurses openvr draco discord bullet ultralight-ux FLAGS += -march=native -g # -flto # -g endif - REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit simd ctti gltf imgui fmt freetype openal ogg wav # reactphysics + REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit reactphysics simd ctti gltf imgui fmt freetype openal ogg wav FLAGS += -DUF_ENV_LINUX -fPIC DEPS += -pthread -ldl -lX11 -lXrandr INCS := -I./dep/master/include $(INCS) else ifneq (,$(findstring dreamcast,$(ARCH))) FLAGS += -DUF_ENV_DREAMCAST # -DUF_LEAN_AND_MEAN # this apparently crashes - REQ_DEPS += opengl gldc json:nlohmann zlib lua simd ctti fmt freetype openal aldc ogg wav png # imgui # reactphysics + REQ_DEPS += opengl gldc json:nlohmann zlib lua reactphysics simd ctti fmt freetype openal aldc ogg wav png # imgui INCS := -I./dep/dreamcast/include $(INCS) endif diff --git a/bin/data/entities/prop.json b/bin/data/entities/prop.json index a44b9326..b1987d11 100644 --- a/bin/data/entities/prop.json +++ b/bin/data/entities/prop.json @@ -6,12 +6,7 @@ "metadata": { "holdable": true, "physics": { - // "gravity": [ 0, -9.81, 0 ], - // "inertia": [10, 10, 10], - // "mass": 10, - - "type": "bounding box", - "recenter": false + "type": "bounding box" } } } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index 9c359469..184162b5 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -36,7 +36,7 @@ // "/^light_[^e]/": { "ignore": true }, // regexp matches - "/^worldspawn_/": { "physics": { "type": "mesh", "static": true } }, + // "/^worldspawn_/": { "physics": { "type": "mesh", "static": true } }, "/^func_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } }, "/^prop_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } }, "/^prop_static/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ }, diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index 2084cb50..d75355e9 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -2,8 +2,8 @@ "import": "./base_sourceengine.json", "assets": [ // { "filename": "./models/mds_mcdonalds.glb" }, - { "filename": "./models/mds_mcdonalds/graph.json" }, - { "filename": "/burger.json", "delay": 1 } + { "filename": "./models/mds_mcdonalds/graph.json" } + // { "filename": "/burger.json", "delay": 1 } ], "metadata": { "graph": { diff --git a/bin/data/scenes/sourceengine/sourceengine.json b/bin/data/scenes/sourceengine/sourceengine.json index feac6dec..95a20fe6 100644 --- a/bin/data/scenes/sourceengine/sourceengine.json +++ b/bin/data/scenes/sourceengine/sourceengine.json @@ -1,9 +1,9 @@ { // "import": "./rp_downtown_v2.json" - "import": "./ss2_medsci1.json" +// "import": "./ss2_medsci1.json" // "import": "./test_grid.json" // "import": "./sh2_mcdonalds.json" // "import": "./animal_crossing.json" -// "import": "./mds_mcdonalds.json" + "import": "./mds_mcdonalds.json" // "import": "./gm_construct.json" } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/ss2_medsci1.json b/bin/data/scenes/sourceengine/ss2_medsci1.json index 5c86088e..cf2e9a70 100644 --- a/bin/data/scenes/sourceengine/ss2_medsci1.json +++ b/bin/data/scenes/sourceengine/ss2_medsci1.json @@ -2,9 +2,9 @@ "import": "./base_sourceengine.json", "assets": [ // { "filename": "./models/ss2_medsci1.glb" } - // { "filename": "./models/ss2_medsci1/graph.json" } + { "filename": "./models/ss2_medsci1/graph.json" } // { "filename": "./models/ss2_medsci1_small.glb" } - { "filename": "./models/ss2_medsci1_small/graph.json" } + // { "filename": "./models/ss2_medsci1_small/graph.json" } // { "filename": "./models/ss2_medsci1_smallish.glb" } // { "filename": "./models/ss2_medsci1_smallish/graph.json" } ], 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/engine/graph/graph.h b/engine/inc/uf/engine/graph/graph.h index e99d3bcf..b3f86aa5 100644 --- a/engine/inc/uf/engine/graph/graph.h +++ b/engine/inc/uf/engine/graph/graph.h @@ -29,6 +29,8 @@ namespace pod { // Render information uf::stl::vector primitives; // + uf::stl::vector instances; // ugh + uf::stl::vector drawCommands; // ugh uf::stl::vector meshes; // uf::stl::vector images; // @@ -79,8 +81,10 @@ namespace pod { // Local storage, used for save/load struct Storage { + uf::stl::KeyMap instances; // should be unused but is a counter for global instance IDs because I cannot for the life of me figure out a working way to remap otherwise uf::stl::KeyMap instanceAddresses; uf::stl::KeyMap> primitives; + uf::stl::KeyMap> drawCommands; // unused uf::stl::KeyMap meshes; uf::stl::KeyMap images; diff --git a/engine/inc/uf/utils/math/physics/impl.h b/engine/inc/uf/utils/math/physics/impl.h index e454315c..1b9bc044 100644 --- a/engine/inc/uf/utils/math/physics/impl.h +++ b/engine/inc/uf/utils/math/physics/impl.h @@ -62,9 +62,36 @@ namespace pod { typedef uint32_t CollisionMask; + struct Collider { + enum CategoryMask : uint32_t { + CATEGORY_NONE = 0, + CATEGORY_STATIC = 1 << 0, + CATEGORY_DYNAMIC = 1 << 1, + CATEGORY_PLAYER = 1 << 2, + CATEGORY_NPC = 1 << 3, + CATEGORY_TRIGGER = 1 << 4, + CATEGORY_PROJECTILE = 1 << 5, + CATEGORY_CHARACTER = CATEGORY_PLAYER | CATEGORY_NPC, + CATEGORY_ALL = 0xFFFFFFFF + }; + + enum CollisionMask : uint32_t { + MASK_NONE = 0, + MASK_STATIC = CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC | CATEGORY_PROJECTILE, + MASK_DYNAMIC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC, + MASK_PLAYER = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_NPC | CATEGORY_PROJECTILE, + MASK_NPC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_PROJECTILE, + MASK_TRIGGER = CATEGORY_PLAYER | CATEGORY_NPC, + MASK_PROJECTILE = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC, + MASK_CHARACTER = MASK_PLAYER | MASK_NPC, + MASK_ALL = 0xFFFFFFFF + }; + pod::ShapeType type; - pod::CollisionMask mask = 0xFFFFFFFF; + pod::CollisionMask category = Collider::CATEGORY_ALL; + pod::CollisionMask mask = Collider::MASK_ALL; + union { pod::Sphere sphere; pod::AABB aabb; @@ -105,6 +132,8 @@ namespace pod { pod::Vector3f inertiaTensor = { 1, 1, 1 }; pod::Vector3f inverseInertiaTensor = { 1, 1, 1 }; + pod::Vector3f gravity = { NAN, NAN, NAN }; // an invalid gravity will fallback to world gravity + pod::AABB bounds; pod::Collider collider; pod::PhysicsMaterial material; @@ -169,7 +198,15 @@ namespace uf { void UF_API substep( pod::World&, float dt, int substeps ); void UF_API setMass( pod::RigidBody& body, float mass = 0.0f ); - void UF_API setInertia( pod::RigidBody& body ); + void UF_API setColliderCategory( pod::RigidBody&, uint32_t category = pod::Collider::CATEGORY_ALL ); + void UF_API setColliderCategory( pod::RigidBody&, const uf::stl::string& ); + void UF_API setColliderMask( pod::RigidBody&, uint32_t mask = pod::Collider::MASK_ALL ); + void UF_API setColliderMask( pod::RigidBody&, const uf::stl::string& ); + void UF_API setGravity( pod::RigidBody&, const pod::Vector3f& = { NAN, NAN, NAN } ); + pod::Vector3f UF_API getGravity( pod::RigidBody& ); + + void UF_API updateInertia( pod::RigidBody& body ); + void UF_API applyForce( pod::RigidBody& body, const pod::Vector3f& force ); void UF_API applyForceAtPoint( pod::RigidBody body, const pod::Vector3f& force, const pod::Vector3f& point ); void UF_API applyImpulse( pod::RigidBody& body, const pod::Vector3f& impulse ); diff --git a/engine/src/engine/ext/player/behavior.cpp b/engine/src/engine/ext/player/behavior.cpp index f211a76f..6b9ddd40 100644 --- a/engine/src/engine/ext/player/behavior.cpp +++ b/engine/src/engine/ext/player/behavior.cpp @@ -297,7 +297,7 @@ void ext::PlayerBehavior::tick( uf::Object& self ) { if ( query.hit ) { if ( metadata.movement.floored.print ) UF_MSG_DEBUG("{}: {} | {}", query.contact.penetration, uf::string::toString(*query.body->object), uf::vector::toString(physics.velocity)); stats.floored = true; - if ( physics.velocity.y < 0.0f ) physics.velocity.y = 0.0f; + //if ( physics.velocity.y < 0.0f ) physics.velocity.y = 0.0f; } #endif } @@ -348,6 +348,8 @@ void ext::PlayerBehavior::tick( uf::Object& self ) { #if UF_USE_REACTPHYSICS if ( collider.stats.gravity == pod::Vector3f{0,0,0} ) stats.noclipped = true; +#else + if ( physics.gravity == pod::Vector3f{0,0,0} ) stats.noclipped = true; #endif { @@ -429,6 +431,11 @@ void ext::PlayerBehavior::tick( uf::Object& self ) { translator.forward.y += cameraTransform.forward.y; translator.forward = uf::vector::normalize( translator.forward ); } + #else + else if ( stats.noclipped || physics.gravity == pod::Vector3f{0,0,0} ){ + translator.forward.y += cameraTransform.forward.y; + translator.forward = uf::vector::normalize( translator.forward ); + } #endif if ( metadata.system.control ) { @@ -441,6 +448,16 @@ void ext::PlayerBehavior::tick( uf::Object& self ) { collider.body->enableGravity(!state); uf::physics::impl::activateCollision(collider, !state); } + #else + if ( !state ) { + uf::physics::impl::setGravity( physics ); + uf::physics::impl::setColliderCategory( physics, "ALL"); + uf::physics::impl::setColliderMask( physics, "ALL"); + } else { + uf::physics::impl::setGravity( physics, pod::Vector3f{0,0,0}); + uf::physics::impl::setColliderCategory( physics, "NONE"); + uf::physics::impl::setColliderMask( physics, "NONE"); + } #endif stats.noclipped = state; diff --git a/engine/src/engine/graph/decode.cpp b/engine/src/engine/graph/decode.cpp index 064afd40..a7d2a71d 100644 --- a/engine/src/engine/graph/decode.cpp +++ b/engine/src/engine/graph/decode.cpp @@ -8,6 +8,7 @@ #include #include #include +#include // it's too unstable right now to do multithreaded loading, perhaps there's a better way #if UF_USE_OPENGL @@ -414,6 +415,17 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const graph.settings.stream.hash = graph.metadata["stream"]["hash"].as(graph.settings.stream.hash); graph.settings.stream.lastUpdate = graph.metadata["stream"]["lastUpdate"].as(graph.settings.stream.lastUpdate); } + // store offsets + { + size_t instances = 0; + for ( auto& key : storage.primitives.keys ) { + instances += storage.primitives.map[key].size(); + } + + graph.metadata["offsets"]["instances"] = instances; + graph.metadata["offsets"]["materials"] = storage.materials.keys.size(); + graph.metadata["offsets"]["joints"] = storage.joints.keys.size(); + } uf::stl::string key = graph.metadata["key"].as(""); if ( key != "" ) { diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 7c5adff4..8039bc59 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -1043,14 +1043,6 @@ void uf::graph::process( pod::Graph& graph ) { auto& drawCommand = primitive.drawCommand; auto& instance = primitive.instance; - // remap instance ID - if ( 0 <= drawCommand.instanceID && drawCommand.instanceID < graph.primitives.size() ) { - for ( auto key : storage.primitives.keys ) { - if ( key == name ) break; - drawCommand.instanceID += storage.primitives[key].size(); - } - } - if ( 0 <= instance.materialID && instance.materialID < graph.materials.size() ) { auto& keys = storage.materials.keys; auto& indices = storage.materials.indices; @@ -1291,18 +1283,19 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) } // what a mess + node.object = storage.entities.keys.size(); + auto objectKeyName = ::objectKey( node ); + storage.entities[objectKeyName] = &entity; + auto& objectData = storage.objects[objectKeyName]; + auto model = uf::transform::model( transform ); + objectData.model = model; + objectData.previous = model; + // if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) { auto& mesh = storage.meshes.map[graph.meshes[node.mesh]]; auto& primitives = storage.primitives.map[graph.primitives[node.mesh]]; - node.object = storage.entities.keys.size(); - auto objectKeyName = ::objectKey( node ); - storage.entities[objectKeyName] = &entity; - auto& objectData = storage.objects[objectKeyName]; - auto model = uf::transform::model( transform ); - objectData.model = model; - objectData.previous = model; pod::Instance::Bounds bounds; pod::DrawCommand* drawCommands = NULL; @@ -1316,11 +1309,19 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) auto& primitive = primitives[primitiveID]; auto& instance = primitive.instance; auto& drawCommand = primitive.drawCommand; - auto instanceID = primitiveID; - + instance.objectID = node.object; instance.jointID = graphMetadataJson["renderer"]["skinned"].as() ? 0 : -1; + // to-do: fix needing this to fix graphs + #if 0 + auto instanceID = primitiveID; + #else + size_t instanceID = storage.instances.keys.size(); + auto instanceKeyName = graph.instances.emplace_back(std::to_string(instanceID)); + storage.instances[instanceKeyName] = instance; + #endif + bounds.min = uf::vector::min( bounds.min, instance.bounds.min ); bounds.max = uf::vector::max( bounds.max, instance.bounds.max ); diff --git a/engine/src/engine/object/behavior.cpp b/engine/src/engine/object/behavior.cpp index 26006201..dd69d928 100644 --- a/engine/src/engine/object/behavior.cpp +++ b/engine/src/engine/object/behavior.cpp @@ -156,8 +156,12 @@ void uf::ObjectBehavior::initialize( uf::Object& self ) { #else auto type = metadataJsonPhysics["type"].as(); float mass = metadataJsonPhysics["mass"].as(); + + auto category = metadataJsonPhysics["category"].as("ALL"); + auto mask = metadataJsonPhysics["mask"].as("ALL"); pod::Vector3f offset = uf::vector::decode( metadataJsonPhysics["offset"], pod::Vector3f{} ); + pod::Vector3f gravity = uf::vector::decode( metadataJsonPhysics["gravity"], pod::Vector3f{ NAN, NAN, NAN } ); if ( type == "bounding box" || type == "aabb" ) { pod::Vector3f min = uf::vector::decode( metadataJsonPhysics["min"], pod::Vector3f{-0.5f, -0.5f, -0.5f} ); @@ -175,13 +179,17 @@ void uf::ObjectBehavior::initialize( uf::Object& self ) { uf::physics::impl::create( self, pod::Sphere{ radius }, mass, offset ); } else if ( type == "capsule" ) { float radius = metadataJsonPhysics["radius"].as(); - float height = metadataJsonPhysics["height"].as(); + float halfHeight = metadataJsonPhysics["height"].as() * 0.5f; - uf::physics::impl::create( self, pod::Capsule{ radius, height * 0.5f }, mass, offset ); + uf::physics::impl::create( self, pod::Capsule{ radius, halfHeight }, mass, offset ); } if ( this->hasComponent() ) { auto& body = this->getComponent(); + + uf::physics::impl::setColliderCategory( body, category ); + uf::physics::impl::setColliderMask( body, mask ); + uf::physics::impl::setGravity( body, gravity ); if ( this->getName() == "Player" ) { body.inertiaTensor = { FLT_MAX, FLT_MAX, FLT_MAX }; diff --git a/engine/src/ext/gltf/gltf.cpp b/engine/src/ext/gltf/gltf.cpp index 7e551a4b..091ce053 100644 --- a/engine/src/ext/gltf/gltf.cpp +++ b/engine/src/ext/gltf/gltf.cpp @@ -253,7 +253,8 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const } // load meshes { - size_t masterAuxID = 0; + size_t masterInstanceID = 0; + graph.meshes.reserve(model.meshes.size()); storage.meshes.reserve(model.meshes.size()); diff --git a/engine/src/ext/gltf/processPrimitives.inl b/engine/src/ext/gltf/processPrimitives.inl index fcc59c18..3ed92421 100644 --- a/engine/src/ext/gltf/processPrimitives.inl +++ b/engine/src/ext/gltf/processPrimitives.inl @@ -383,18 +383,15 @@ if ( meshopt.should ) { primitives.reserve( meshlets.size() ); for ( auto& meshlet : meshlets ) { - // to-do: check against the meshlet's actual primitive information - auto& drawCommand = drawCommands.emplace_back(pod::DrawCommand{ - .indices = meshlet.indices.size(), - .instances = 1, - .indexID = indexID, - .vertexID = vertexID, - .instanceID = meshlet.primitive.drawCommand.instanceID, - .auxID = meshlet.primitive.drawCommand.auxID, - .materialID = meshlet.primitive.drawCommand.materialID, - .vertices = meshlet.vertices.size(), - }); - + meshlet.primitive.drawCommand.instances = 1; + meshlet.primitive.drawCommand.instanceID = ++masterInstanceID; // this doesn't matter...... + meshlet.primitive.drawCommand.indexID = indexID; + meshlet.primitive.drawCommand.indices = meshlet.indices.size(); + meshlet.primitive.drawCommand.vertexID = vertexID; + meshlet.primitive.drawCommand.vertices = meshlet.vertices.size(); + + drawCommands.emplace_back(meshlet.primitive.drawCommand); + primitives.emplace_back( meshlet.primitive ); indexID += meshlet.indices.size(); diff --git a/engine/src/ext/xatlas/xatlas.cpp b/engine/src/ext/xatlas/xatlas.cpp index d7e25e8d..7cf1f536 100644 --- a/engine/src/ext/xatlas/xatlas.cpp +++ b/engine/src/ext/xatlas/xatlas.cpp @@ -62,53 +62,79 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) { source = mesh; source.updateDescriptor(); - for (auto& view : source.makeViews({"position", "uv", "st", "index"})) { - auto pos = view["position"]; - auto uv = view["uv"]; - auto st = view["st"]; - auto idx = view["index"]; + uf::Mesh::Input vertexInput = mesh.vertex; - // Must have positions + indices - if (!pos.valid() || !idx.valid()) continue; - if (view.vertex.count == 0 || view.index.count == 0) continue; + uf::Mesh::Attribute positionAttribute; + uf::Mesh::Attribute uvAttribute; + uf::Mesh::Attribute stAttribute; + + for ( auto& attribute : mesh.vertex.attributes ) { + if ( attribute.descriptor.name == "position" ) positionAttribute = attribute; + else if ( attribute.descriptor.name == "uv" ) uvAttribute = attribute; + else if ( attribute.descriptor.name == "st" ) stAttribute = attribute; + } + UF_ASSERT( positionAttribute.descriptor.name == "position" && uvAttribute.descriptor.name == "uv" && stAttribute.descriptor.name == "st" ); + + if ( mesh.index.count ) { + uf::Mesh::Input indexInput = mesh.index; + uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front(); - // Choose xatlas index format ::xatlas::IndexFormat indexType = ::xatlas::IndexFormat::UInt32; - switch (idx.attribute.descriptor.size) { + switch ( mesh.index.size ) { case sizeof(uint16_t): indexType = ::xatlas::IndexFormat::UInt16; break; case sizeof(uint32_t): indexType = ::xatlas::IndexFormat::UInt32; break; default: UF_EXCEPTION("unsupported index type"); break; } - // Get atlas target from indirect auxID or default 0 - size_t atlasID = 0; - if ( 0 <= view.indirectIndex ) { - auto* drawCommand = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data(); - atlasID = drawCommand[view.indirectIndex].auxID; + if ( mesh.indirect.count ) { + auto& primitives = /*graph.storage*/storage.primitives[name]; + pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data(); + + for ( auto i = 0; i < mesh.indirect.count; ++i ) { + size_t atlasID = drawCommands[i].auxID; + + vertexInput = mesh.remapVertexInput( i ); + indexInput = mesh.remapIndexInput( i ); + + auto& atlas = atlases[atlasID]; + auto& entry = atlas.entries.emplace_back(); + entry.index = index; + entry.commandID = i; + + auto& decl = entry.decl; + + decl.vertexPositionData = static_cast(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first; + decl.vertexPositionStride = positionAttribute.stride; + + decl.vertexUvData = static_cast(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first; + decl.vertexUvStride = uvAttribute.stride; + + decl.vertexCount = vertexInput.count; + + decl.indexCount = indexInput.count; + decl.indexData = static_cast(indexAttribute.pointer) + indexAttribute.stride * indexInput.first; + decl.indexFormat = indexType; + } + } else { + size_t atlasID = 0; + auto& atlas = atlases[atlasID]; + auto& entry = atlas.entries.emplace_back(); + entry.index = index; + + auto& decl = entry.decl; + decl.vertexPositionData = static_cast(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first; + decl.vertexPositionStride = positionAttribute.stride; + + decl.vertexUvData = static_cast(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first; + decl.vertexUvStride = uvAttribute.stride; + + decl.vertexCount = vertexInput.count; + + decl.indexCount = indexInput.count; + decl.indexData = static_cast(indexAttribute.pointer) + indexAttribute.stride * indexInput.first; + decl.indexFormat = indexType; } - - auto& atlas = atlases[atlasID]; - auto& entry = atlas.entries.emplace_back(); - entry.index = index; - - auto& decl = entry.decl; - decl.vertexPositionData = pos.data(view.vertex.first); - decl.vertexPositionStride = pos.stride(); - - if ( uv.valid() ) { - decl.vertexUvData = uv.data(view.vertex.first); - decl.vertexUvStride = uv.stride(); - } else if ( st.valid() ) { - decl.vertexUvData = st.data(view.vertex.first); - decl.vertexUvStride = st.stride(); - } - - decl.vertexCount = view.vertex.count; - - decl.indexCount = view.index.count; - decl.indexData = idx.data(view.index.first); - decl.indexFormat = indexType; - } + } else UF_EXCEPTION("to-do: not require indices for meshes"); } ::xatlas::ChartOptions chartOptions{}; diff --git a/engine/src/utils/math/physics/bvh.inl b/engine/src/utils/math/physics/bvh.inl index bbdd7525..97b758fc 100644 --- a/engine/src/utils/math/physics/bvh.inl +++ b/engine/src/utils/math/physics/bvh.inl @@ -1,50 +1,54 @@ // BVH namespace { // collects a list of nodes that are overlapping with each other - void traverseNodePair( const pod::BVH& bvh, int leftID, int rightID, pod::BVH::pair_t& pairs ) { - const auto& left = bvh.nodes[leftID]; - const auto& right = bvh.nodes[rightID]; + void traverseNodePair( const pod::BVH& bvh, int indexA, int indexB, pod::BVH::pair_t& pairs ) { + const auto& nodeA = bvh.nodes[indexA]; + const auto& nodeB = bvh.nodes[indexB]; - if ( !::aabbOverlap( left.bounds, right.bounds ) ) return; + if ( !::aabbOverlap( nodeA.bounds, nodeB.bounds ) ) return; - if ( left.count > 0 && right.count > 0 ) { - for ( auto i = 0; i < left.count; ++i ) - for ( auto j = 0; j < right.count; ++j ) { - int a = bvh.indices[left.start + i]; - int b = bvh.indices[right.start + j]; - pairs.emplace_back(std::pair{a, b}); + if ( nodeA.count > 0 && nodeB.count > 0 ) { + for ( auto i = 0; i < nodeA.count; ++i ) + for ( auto j = 0; j < nodeB.count; ++j ) { + int indexA = bvh.indices[nodeA.start + i]; + int indexB = bvh.indices[nodeB.start + j]; + pairs.emplace_back(std::pair{indexA, indexB}); } return; } - if ( left.count == 0 ) { - ::traverseNodePair( bvh, left.left, rightID, pairs ); - ::traverseNodePair( bvh, left.right, rightID, pairs ); - } else if ( right.count == 0 ) { - ::traverseNodePair( bvh, leftID, right.left, pairs ); - ::traverseNodePair( bvh, leftID, right.right, pairs ); + if ( nodeA.count == 0 ) { + ::traverseNodePair( bvh, nodeA.left, indexB, pairs ); + ::traverseNodePair( bvh, nodeA.right, indexB, pairs ); + } else if ( nodeB.count == 0 ) { + ::traverseNodePair( bvh, indexA, nodeB.left, pairs ); + ::traverseNodePair( bvh, indexA, nodeB.right, pairs ); } } // collects a list of nodes from each BVH that are overlapping with each other (for mesh v mesh) - void traverseNodePair( const pod::BVH& a, int nodeA, const pod::BVH& b, int nodeB, pod::BVH::pair_t& pairs ) { - const auto& nA = a.nodes[nodeA]; - const auto& nB = b.nodes[nodeB]; + void traverseNodePair( const pod::BVH& bvhA, int indexA, const pod::BVH& bvhB, int indexB, pod::BVH::pair_t& pairs ) { + const auto& nodeA = bvhA.nodes[indexA]; + const auto& nodeB = bvhB.nodes[indexB]; - if ( !::aabbOverlap( nA.bounds, nB.bounds ) ) return; + if ( !::aabbOverlap( nodeA.bounds, nodeB.bounds ) ) return; - if ( nA.count > 0 && nB.count > 0 ) { - for ( auto i = 0; i < nA.count; i++ ) - for ( auto j = 0; j < nB.count; j++ ) - pairs.emplace_back(a.indices[nA.start+i], b.indices[nB.start+j]); + if ( nodeA.count > 0 && nodeB.count > 0 ) { + for ( auto i = 0; i < nodeA.count; i++ ) { + for ( auto j = 0; j < nodeB.count; j++ ) { + auto indexA = bvhA.indices[nodeA.start+i]; + auto indexB = bvhB.indices[nodeB.start+j]; + pairs.emplace_back(std::pair{indexA, indexB}); + } + } return; } - if ( nA.count == 0 ) { - ::traverseNodePair( a, nA.left, b , nodeB, pairs ); - ::traverseNodePair( a, nA.right, b , nodeB, pairs ); - } else { - ::traverseNodePair( a, nodeA, b, nB.left, pairs ); - ::traverseNodePair( a, nodeA, b, nB.right, pairs ); + if ( nodeA.count == 0 ) { + ::traverseNodePair( bvhA, nodeA.left, bvhB , indexB, pairs ); + ::traverseNodePair( bvhA, nodeA.right, bvhB , indexB, pairs ); + } else if ( nodeB.count == 0 ) { + ::traverseNodePair( bvhA, indexA, bvhB, nodeB.left, pairs ); + ::traverseNodePair( bvhA, indexA, bvhB, nodeB.right, pairs ); } } @@ -54,9 +58,14 @@ namespace { if ( node.count > 0 ) { for ( auto i = 0; i < node.count; ++i ) { for ( auto j = i + 1; j < node.count; ++j ) { - int a = bvh.indices[node.start + i]; - int b = bvh.indices[node.start + j]; - pairs.emplace_back(std::pair{a, b}); + int indexA = bvh.indices[node.start + i]; + int indexB = bvh.indices[node.start + j]; + + if ( !::aabbOverlap( bvh.nodes[indexA].bounds, bvh.nodes[indexB].bounds ) ) { + continue; + } + + pairs.emplace_back(std::pair{indexA, indexB}); } } return; diff --git a/engine/src/utils/math/physics/helpers.inl b/engine/src/utils/math/physics/helpers.inl index 8a7c4e97..96dd1367 100644 --- a/engine/src/utils/math/physics/helpers.inl +++ b/engine/src/utils/math/physics/helpers.inl @@ -74,6 +74,13 @@ namespace { return ::getTransform( body ).position; } + bool shouldCollide( const pod::Collider& a, const pod::Collider& b ) { + return ( a.category & b.mask ) && ( b.category & a.mask ); + } + bool shouldCollide( const pod::RigidBody& a, const pod::RigidBody& b ) { + return ::shouldCollide( a.collider, b.collider ); + } + // normalizes the delta between two bodies / contacts by the distance (as it was already computed) if non-zero // a lot of collider v colliders use this semantic pod::Vector3f normalizeDelta( const pod::Vector3f& delta, float dist, float eps = EPS(1.0e-6), const pod::Vector3f& fallback = pod::Vector3f{0,1,0} ) { diff --git a/engine/src/utils/math/physics/impl.cpp b/engine/src/utils/math/physics/impl.cpp index 750c9bae..0587daa3 100644 --- a/engine/src/utils/math/physics/impl.cpp +++ b/engine/src/utils/math/physics/impl.cpp @@ -6,14 +6,18 @@ namespace { bool warmupSolver = true; - bool blockContactSolver = false; - bool psgContactSolver = true; - bool useGjk = false; + bool blockContactSolver = false; // blockNxN solver is flawed + bool psgContactSolver = true; // iterative solver is flawed + bool useGjk = false; // currently don't have a way to broadphase mesh => narrowphase tri via GJK bool fixedStep = true; - int substeps = 0; + int substeps = 4; - int solverIterations = 10; - float baumgarteCorrectionPercent = 0.005f; + // increasing these make things lag + int broadphaseBvhCapacity = 1; + int meshBvhCapacity = 1; + + int solverIterations = 5; + float baumgarteCorrectionPercent = 0.005f; // needs to be very small or the correction is too large float baumgarteCorrectionSlop = 0.001f; uf::stl::unordered_map manifoldsCache; } @@ -106,7 +110,7 @@ void uf::physics::impl::step( pod::World& world, float dt ) { uf::stl::vector> pairs; // create BVH - ::buildBroadphaseBVH(bvh, bodies); + ::buildBroadphaseBVH( bvh, bodies, ::broadphaseBvhCapacity ); // query for overlaps ::queryOverlaps( bvh, pairs ); // iterate overlaps @@ -114,6 +118,9 @@ void uf::physics::impl::step( pod::World& world, float dt ) { auto& a = *bodies[ia]; auto& b = *bodies[ib]; + // could be also pruned in the broadphase, but traversal needs to be agnostic between a BVH for bodies or a BVH for triangles + if ( !::shouldCollide( a, b ) ) continue; + pod::Manifold manifold; if ( ::generateContacts( a, b, manifold, dt ) ) { // bodies with meshes already reorient the normal to the triangle's center @@ -169,9 +176,47 @@ void uf::physics::impl::step( pod::World& world, float dt ) { void uf::physics::impl::setMass( pod::RigidBody& body, float mass ) { body.mass = mass; body.inverseMass = 1.0f / mass; - uf::physics::impl::setInertia( body ); + uf::physics::impl::updateInertia( body ); } -void uf::physics::impl::setInertia( pod::RigidBody& body ) { + +void uf::physics::impl::setColliderCategory( pod::RigidBody& body, uint32_t category ) { + body.collider.category = category; +} +void uf::physics::impl::setColliderCategory( pod::RigidBody& body, const uf::stl::string& category ) { + auto c = uf::string::uppercase( category ); + if ( c == "NONE" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_NONE ); + if ( c == "STATIC" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_STATIC ); + if ( c == "DYNAMIC" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_DYNAMIC ); + if ( c == "PLAYER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_PLAYER ); + if ( c == "NPC" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_NPC ); + if ( c == "TRIGGER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_TRIGGER ); + if ( c == "PROJECTILE" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_PROJECTILE ); + if ( c == "CHARACTER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_CHARACTER ); + if ( c == "ALL" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::CATEGORY_ALL ); +} +void uf::physics::impl::setColliderMask( pod::RigidBody& body, uint32_t mask ) { + body.collider.mask = mask; +} +void uf::physics::impl::setColliderMask( pod::RigidBody& body, const uf::stl::string& mask ) { + auto m = uf::string::uppercase( mask ); + if ( m == "NONE" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_NONE ); + if ( m == "STATIC" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_STATIC ); + if ( m == "DYNAMIC" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_DYNAMIC ); + if ( m == "PLAYER" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_PLAYER ); + if ( m == "NPC" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_NPC ); + if ( m == "TRIGGER" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_TRIGGER ); + if ( m == "PROJECTILE" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_PROJECTILE ); + if ( m == "CHARACTER" ) return uf::physics::impl::setColliderCategory( body, pod::Collider::MASK_CHARACTER ); + if ( m == "ALL" ) return uf::physics::impl::setColliderMask( body, pod::Collider::MASK_ALL ); +} +void uf::physics::impl::setGravity( pod::RigidBody& body, const pod::Vector3f& gravity ) { + body.gravity = gravity; +} +pod::Vector3f uf::physics::impl::getGravity( pod::RigidBody& body ) { + return uf::vector::isValid( body.gravity ) ? body.gravity : body.world->gravity; +} + +void uf::physics::impl::updateInertia( pod::RigidBody& body ) { if ( body.isStatic || body.mass <= 0 ) { body.inverseInertiaTensor = {}; return; @@ -254,6 +299,11 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object body.inverseMass = mass == 0.0f ? 0.0f : 1.0f / mass; body.isStatic = mass == 0.0f; + if ( body.isStatic ) { + uf::physics::impl::setColliderCategory(body, "STATIC"); + uf::physics::impl::setColliderMask(body, "STATIC"); + } + // insert into world world.bodies.emplace_back(&body); @@ -264,7 +314,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object body.collider.type = pod::ShapeType::AABB; body.collider.u.aabb = aabb; body.bounds = ::computeAABB( body ); - uf::physics::impl::setInertia( body ); + uf::physics::impl::updateInertia( body ); UF_MSG_DEBUG("Creating body of type: {}, mass={}, min={}, max={}", "AABB", mass, uf::vector::toString(aabb.min), uf::vector::toString(aabb.max) ); return body; } @@ -273,7 +323,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object body.collider.type = pod::ShapeType::SPHERE; body.collider.u.sphere = sphere; body.bounds = ::computeAABB( body ); - uf::physics::impl::setInertia( body ); + uf::physics::impl::updateInertia( body ); UF_MSG_DEBUG("Creating body of type={}, mass={}, radius={}", "SPHERE", mass, sphere.radius ); return body; } @@ -282,7 +332,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object body.collider.type = pod::ShapeType::PLANE; body.collider.u.plane = plane; body.bounds = ::computeAABB( body ); - uf::physics::impl::setInertia( body ); + uf::physics::impl::updateInertia( body ); UF_MSG_DEBUG("Creating body of type={}, mass={}, normal={}, offset={}", "PLANE", mass, uf::vector::toString( plane.normal ), plane.offset ); return body; } @@ -291,7 +341,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object body.collider.type = pod::ShapeType::CAPSULE; body.collider.u.capsule = capsule; body.bounds = ::computeAABB( body ); - uf::physics::impl::setInertia( body ); + uf::physics::impl::updateInertia( body ); UF_MSG_DEBUG("Creating body of type={}, mass={}, radius={}, height={}", "CAPSULE", mass, capsule.radius, capsule.halfHeight * 2.0f ); return body; } @@ -300,7 +350,7 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object body.collider.type = pod::ShapeType::TRIANGLE; body.collider.u.triangle = tri; body.bounds = ::computeAABB( body ); - uf::physics::impl::setInertia( body ); + uf::physics::impl::updateInertia( body ); UF_MSG_DEBUG("Creating body of type={}, mass={}, point[0]={}, point[1]={}, point[2]={}", "TRIANGLE", mass, uf::vector::toString( tri.points[0] ), uf::vector::toString( tri.points[1] ), uf::vector::toString( tri.points[2] ) ); return body; } @@ -310,10 +360,10 @@ pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object body.collider.u.mesh.mesh = &mesh; body.collider.u.mesh.bvh = new pod::BVH; - ::buildMeshBVH( *body.collider.u.mesh.bvh, mesh ); + ::buildMeshBVH( *body.collider.u.mesh.bvh, mesh, ::meshBvhCapacity ); body.bounds = ::computeAABB( body ); - uf::physics::impl::setInertia( body ); + uf::physics::impl::updateInertia( body ); UF_MSG_DEBUG("Creating body of type={}, mass={}, nodes={} indices={} ", "MESH", mass, body.collider.u.mesh.bvh->nodes.size(), body.collider.u.mesh.bvh->indices.size()); return body; } diff --git a/engine/src/utils/math/physics/integration.inl b/engine/src/utils/math/physics/integration.inl index 535f3f44..4fa79aae 100644 --- a/engine/src/utils/math/physics/integration.inl +++ b/engine/src/utils/math/physics/integration.inl @@ -214,7 +214,7 @@ namespace { // linear integration pod::Vector3f acceleration = body.forceAccumulator * body.inverseMass; - acceleration += world.gravity; // apply gravity + acceleration += uf::physics::impl::getGravity( body ); // apply gravity body.velocity += acceleration * dt; body.transform/*.reference*/->position += body.velocity * dt; diff --git a/engine/src/utils/math/physics/mesh.inl b/engine/src/utils/math/physics/mesh.inl index 6724e97f..d0877bca 100644 --- a/engine/src/utils/math/physics/mesh.inl +++ b/engine/src/utils/math/physics/mesh.inl @@ -123,16 +123,12 @@ namespace { pod::BVH::pair_t pairs; ::traverseNodePair(bvhA, 0, bvhB, 0, pairs); - UF_MSG_DEBUG("candidates={}", pairs.size()); - bool hit = false; // do collision per triangle for (auto [ idA, idB] : pairs ) { auto tA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space auto tB = ::fetchTriangle( meshB, idB, b ); - UF_MSG_DEBUG("triA={}, triB={}", idA, idB ); - if ( !::triangleTriangle( tA, tB, manifold, eps ) ) continue; hit = true; } diff --git a/engine/src/utils/math/physics/ray.inl b/engine/src/utils/math/physics/ray.inl index cde1659b..9d0a663c 100644 --- a/engine/src/utils/math/physics/ray.inl +++ b/engine/src/utils/math/physics/ray.inl @@ -87,8 +87,6 @@ namespace { float disc = b*b - 4*a*c; - UF_MSG_DEBUG( "center={}, r={}, oc={}, a, b, c, disc", uf::vector::toString( center ), r, uf::vector::toString( oc ), a, b, c, disc ); - if ( disc < 0 ) return false; float sqrtDisc = std::sqrt(disc); @@ -97,8 +95,6 @@ namespace { float t = ( t0 >= 0 ) ? t0 : t1; - UF_MSG_DEBUG( "sqrtDisc={}, t0={}, t1={}, t={}, rayHit.contact.penetration={}", sqrtDisc, t0, t1, t, rayHit.contact.penetration ); - if ( t < 0 ) return false; // both behind ray // compare against current best hit