From 84f2b63a8f24586011aa7058b17b8c26507e2fd3 Mon Sep 17 00:00:00 2001 From: ecker Date: Mon, 27 Apr 2026 17:09:26 -0500 Subject: [PATCH] revamped meshopt-based mesh optimizations, fixed mesh slicer/gridder, added LOD system (i think it works), physics tweaks (to-do: make meshMesh viable) --- bin/data/config.json | 8 +- bin/data/entities/burger.json | 9 +- bin/data/entities/prop.json | 2 +- .../sourceengine/base_sourceengine.json | 10 +- .../scenes/sourceengine/mds_mcdonalds.json | 8 +- bin/data/shaders/common/structs.h | 9 + .../shaders/display/depth-pyramid/comp.glsl | 2 +- bin/data/shaders/graph/cull/comp.glsl | 43 +- engine/inc/uf/engine/graph/graph.h | 1 + engine/inc/uf/ext/meshopt/meshopt.h | 5 +- engine/inc/uf/ext/xatlas/xatlas.h | 3 - engine/inc/uf/utils/math/physics/impl.h | 11 +- engine/inc/uf/utils/mesh/mesh.h | 21 +- engine/src/engine/graph/decode.cpp | 10 + engine/src/engine/graph/encode.cpp | 11 + engine/src/engine/graph/graph.cpp | 14 +- engine/src/ext/gltf/gltf.cpp | 16 + engine/src/ext/gltf/processPrimitives.inl | 11 +- engine/src/ext/meshopt/meshopt.cpp | 341 +++++++----- engine/src/ext/xatlas/xatlas.cpp | 526 +++--------------- engine/src/utils/math/physics/bvh.inl | 100 +++- engine/src/utils/math/physics/integration.inl | 6 +- engine/src/utils/math/physics/mesh.inl | 11 +- engine/src/utils/mesh/grid.cpp | 60 +- engine/src/utils/mesh/mesh.cpp | 42 +- 25 files changed, 599 insertions(+), 681 deletions(-) diff --git a/bin/data/config.json b/bin/data/config.json index 6910bc75..30553382 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -66,9 +66,9 @@ "vulkan": { "version": 1.3, "validation": { - "enabled": true, - "messages": true, - "checkpoints": true, + "enabled": false, + "messages": false, + "checkpoints": false, "filters": [ // "0xe5d1743c" // VUID-vkCmdDispatch-None-02699 (problem when using VXGI) (seems to be fixed?) // ,"0x6714bd0c" // VUID-vkCmdDispatch-format-07753 (for some dumb shit) (seems to be fixed?) @@ -118,7 +118,7 @@ "vsync": true, // vsync on vulkan side rather than engine-side "hdr": true, "vxgi": false, - "culling": false, + "culling": true, "bloom": false, "dof": false, "rt": false, diff --git a/bin/data/entities/burger.json b/bin/data/entities/burger.json index 8f1c1ae8..231b16a8 100644 --- a/bin/data/entities/burger.json +++ b/bin/data/entities/burger.json @@ -5,8 +5,8 @@ "import": "/model.json", "assets": [ // "/burger/burger.glb" - // "/burger/burger_simpler.glb" // "/burger/burger/graph.json" + // "/burger/burger_simpler.glb" "/burger/burger_simpler/graph.json" ], "behaviors": [], @@ -38,7 +38,7 @@ "exporter": { "enabled": true, "unwrap": false, - "optimize": false + "optimize": "tagged" }, "baking": { "enabled": false @@ -48,6 +48,11 @@ }, "lighting": { "lightmap": false + }, + "tags": { + "/^.*/": { + "optimize meshlets": { "simplify": 0.125, "print": true } + } } } } diff --git a/bin/data/entities/prop.json b/bin/data/entities/prop.json index 130962ac..d8412781 100644 --- a/bin/data/entities/prop.json +++ b/bin/data/entities/prop.json @@ -6,7 +6,7 @@ "metadata": { "holdable": true, "physics": { - "mass": 0, + "mass": 100, "inertia": false, "type": "bounding box" // "type": "mesh" diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index 8d0f6eb9..8fb73c7e 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -12,13 +12,13 @@ "worldspawn": { "physics": { "type": "mesh", "static": true, "mass": 0 }, "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true }, - "optimize meshlets": { "simplify": 0.125, "print": false }, + "optimize mesh": { "simplify": 0, "lods": true, "print": true }, "unwrap mesh": true }, "worldspawn_skybox": { - "physics": { "type": "mesh", "static": true, "mass": 0 }, + "physics": { "type": "aabb", "static": true, "mass": 0 }, "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true }, - "optimize meshlets": { "simplify": 0.125, "print": false }, + "optimize mesh": { "simplify": 0, "lods": true, "print": true }, "unwrap mesh": true }, "info_player_spawn": { "action": "attach", "filename": "./player.json", "transform": { "orientation": [ 0, 1, 0, 0 ] } }, @@ -42,8 +42,8 @@ "/^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 ] } } }*/ }, "/^prop_dynamic/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ }, - "/^func_physbox/": { "action": "load", "payload": { "import": "/prop.json" } }, - "/^prop_physics/": { "action": "load", "payload": { "import": "/prop.json" } }, + "/^func_physbox/": { "action": "load", "payload": { "import": "/prop.json" }, "optimize mesh": { "simplify": 0, "lods": true, "print": true } }, + "/^prop_physics/": { "action": "load", "payload": { "import": "/prop.json" }, "optimize mesh": { "simplify": 0, "lods": true, "print": true } }, "/^tools\\/toolsnodraw/": { "material": { "base": [ 1.0, 1.0, 1.0, 0.0 ] } } } diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index af7f4cfd..cee95459 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": { @@ -15,10 +15,10 @@ "func_door_rotating_5568": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, "func_door_rotating_5584": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, - "prop_physics_override_5813": { "action": "load", "payload": { "import": "/physics_prop.json" } }, + // "prop_physics_override_5813": { "action": "load", "payload": { "import": "/physics_prop.json" } }, // regex matches - // "/^prop_physics_[^o]/": { "action": "load", "payload": { "import": "/prop.json" } }, + "/^prop_physics_[^o]/": { "action": "load", "payload": { "import": "/prop.json" } }, "/^tools\\/toolsnodraw/": { "material": { "base": [ 1.0, 1.0, 1.0, 1.0 ], diff --git a/bin/data/shaders/common/structs.h b/bin/data/shaders/common/structs.h index f575c105..a38437d0 100644 --- a/bin/data/shaders/common/structs.h +++ b/bin/data/shaders/common/structs.h @@ -99,6 +99,15 @@ struct Bounds { float padding2; }; +struct LOD { + uint indices; + uint indexID; +}; + +struct LODMetadata { + LOD levels[4]; +}; + struct Instance { uint materialID; uint primitiveID; diff --git a/bin/data/shaders/display/depth-pyramid/comp.glsl b/bin/data/shaders/display/depth-pyramid/comp.glsl index 2285e7df..12b6b014 100644 --- a/bin/data/shaders/display/depth-pyramid/comp.glsl +++ b/bin/data/shaders/display/depth-pyramid/comp.glsl @@ -57,7 +57,7 @@ AF4 SpdLoadSourceImage(ASU2 p, AU1 slice) { } AF4 SpdLoad(ASU2 p, AU1 slice) { - uint loadMip = min(6u, MIPS - 1); + uint loadMip = min(6u - 1, MIPS - 1); float d = imageLoad(outImage[loadMip], p).r; return AF4(d, d, d, d); } diff --git a/bin/data/shaders/graph/cull/comp.glsl b/bin/data/shaders/graph/cull/comp.glsl index ac7be7aa..464d89f5 100644 --- a/bin/data/shaders/graph/cull/comp.glsl +++ b/bin/data/shaders/graph/cull/comp.glsl @@ -11,16 +11,18 @@ layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; #define QUERY_MIPMAPS 1 #define DEPTH_BIAS 0.00005 #define FRUSTUM_CULLING 1 -#define OCCLUSION_CULLING 0 // currently whack +#define OCCLUSION_CULLING 1 // currently whack +#define LODS 1 +#define MAX_LODS 4 #include "../../common/macros.h" #include "../../common/structs.h" float mipLevels( vec2 size ) { - return floor(log2(max(size.x, size.y))); + return ceil(log2(max(size.x, size.y))); } float mipLevels( ivec2 size ) { - return floor(log2(max(size.x, size.y))); + return ceil(log2(max(size.x, size.y))); } vec4 aabbToSphere( Bounds bounds ) { @@ -61,19 +63,23 @@ layout (binding = 0) uniform Camera { Viewport viewport[PASSES]; } camera; -layout (std140, binding = 1) buffer DrawCommands { +layout (std430, binding = 1) buffer DrawCommands { DrawCommand drawCommands[]; }; -layout (std140, binding = 2) buffer Instances { +layout (std430, binding = 2) buffer Instances { Instance instances[]; }; -layout (std140, binding = 3) buffer Objects { +layout (std430, binding = 3) buffer LODs { + LODMetadata lodMetadata[]; +}; + +layout (std430, binding = 4) buffer Objects { Object objects[]; }; -layout (binding = 4) uniform sampler2D samplerDepth; +layout (binding = 5) uniform sampler2D samplerDepth; shared vec4 sharedPlanes[PASSES][6]; @@ -152,7 +158,7 @@ void main() { float width = (aabb.z - aabb.x) * pyramidSize.x; float height = (aabb.w - aabb.y) * pyramidSize.y; - float level = floor(log2(max(width, height))); + float level = mipLevels(vec2(width, height)); level = max(0.0, level); float d1 = textureLod(samplerDepth, vec2(aabb.x, aabb.y), level).x; @@ -174,6 +180,27 @@ void main() { } } #endif +#if LODS + if ( isVisible ) { + vec3 viewCenter = (camera.viewport[0].view * vec4(worldCenter, 1.0)).xyz; + float P11 = camera.viewport[0].projection[1][1]; + + float screenRadius = (worldRadius * P11) / max(abs(viewCenter.z), 0.001); + + uint lodLevel = 0; + if ( screenRadius < 0.5 ) lodLevel = 1; + if ( screenRadius < 0.2 ) lodLevel = 2; + if ( screenRadius < 0.05 ) lodLevel = 3; + lodLevel = min(lodLevel, MAX_LODS - 1); + + LOD lod = lodMetadata[drawCommand.instanceID].levels[lodLevel]; + + if ( lod.indices > 0 ) { + drawCommands[gID].indices = lod.indices; + drawCommands[gID].indexID = lod.indexID; + } + } +#endif drawCommands[gID].instances = isVisible ? 1 : 0; } \ 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 48e14849..e9f24db4 100644 --- a/engine/inc/uf/engine/graph/graph.h +++ b/engine/inc/uf/engine/graph/graph.h @@ -106,6 +106,7 @@ namespace pod { uf::renderer::Buffer drawCommands; uf::renderer::Buffer instance; uf::renderer::Buffer instanceAddresses; + uf::renderer::Buffer lodMetadata; uf::renderer::Buffer joint; uf::renderer::Buffer object; uf::renderer::Buffer material; diff --git a/engine/inc/uf/ext/meshopt/meshopt.h b/engine/inc/uf/ext/meshopt/meshopt.h index ac7dd9cc..3b093385 100644 --- a/engine/inc/uf/ext/meshopt/meshopt.h +++ b/engine/inc/uf/ext/meshopt/meshopt.h @@ -11,6 +11,9 @@ namespace ext { namespace meshopt { bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX, bool verbose = false ); + uf::stl::vector computeLODs( size_t count, size_t maxLODs = 4, size_t minIndices = 32 ); + uf::stl::vector UF_API generateLODs( uf::Mesh&, const uf::stl::vector&, bool verbose = false ); + template bool optimize( uf::Meshlet_T& meshlet, float simplify = 1.0f, size_t o = SIZE_MAX, bool verbose = false ) { #if UF_USE_MESHOPT @@ -39,7 +42,7 @@ namespace ext { float targetError = 1e-2f / simplify; float realError = 0.0f; - size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetError, realError); + size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), indicesCount * simplify, targetError); // size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetIndices); if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError); diff --git a/engine/inc/uf/ext/xatlas/xatlas.h b/engine/inc/uf/ext/xatlas/xatlas.h index d022eb3a..2628e96a 100644 --- a/engine/inc/uf/ext/xatlas/xatlas.h +++ b/engine/inc/uf/ext/xatlas/xatlas.h @@ -6,9 +6,6 @@ namespace ext { namespace xatlas { size_t UF_API unwrap( pod::Graph& ); - - size_t UF_API unwrapLazy( pod::Graph& ); - size_t UF_API unwrapExperimental( pod::Graph& ); } } #endif \ No newline at end of file diff --git a/engine/inc/uf/utils/math/physics/impl.h b/engine/inc/uf/utils/math/physics/impl.h index dd932bd7..935c5ecd 100644 --- a/engine/inc/uf/utils/math/physics/impl.h +++ b/engine/inc/uf/utils/math/physics/impl.h @@ -57,7 +57,8 @@ namespace pod { } }; - typedef uf::stl::unordered_set pairs_t; + // typedef uf::stl::unordered_set pairs_t; + typedef uf::stl::vector pairs_t; struct Node { BVH::index_t left = 0; @@ -232,13 +233,13 @@ namespace pod { bool blockContactSolver = true; // use BlockNxN solvers (where N = number of contacts for a manifold) bool psgContactSolver = true; // use PSG contact solver bool useGjk = false; // currently don't have a way to broadphase mesh => narrowphase tri via GJK - bool fixedStep = false; // run physics simulation with a fixed delta time (with accumulation), rather than rely on actual engine deltatime - uint32_t substeps = 4; // number of substeps per frame tick + bool fixedStep = true; // run physics simulation with a fixed delta time (with accumulation), rather than rely on actual engine deltatime + uint32_t substeps = 1; // number of substeps per frame tick 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 = 2; // number of bodies per leaf node - uint32_t meshBvhCapacity = 2; // number of triangles per leaf node + uint32_t broadphaseBvhCapacity = 1; // number of bodies per leaf node + uint32_t meshBvhCapacity = 1; // number of triangles per leaf node // additionally flattens a BVH for linear iteration, rather than a recursive / stack-based traversal bool flattenBvhBodies = true; diff --git a/engine/inc/uf/utils/mesh/mesh.h b/engine/inc/uf/utils/mesh/mesh.h index 98c2b46b..f85887ac 100644 --- a/engine/inc/uf/utils/mesh/mesh.h +++ b/engine/inc/uf/utils/mesh/mesh.h @@ -73,6 +73,14 @@ namespace pod { alignas(4) uint32_t vertices = 0; // stores vertex count, should be unused }; + // stores index offsets for LODs + struct UF_API LODMetadata { + struct Level { + alignas(4) uint32_t indices = 0; + alignas(4) uint32_t indexID = 0; + } levels[4]; + }; + // stores information about how to transform a draw call // to-do: clean up this mess struct UF_API Instance { @@ -133,6 +141,7 @@ namespace pod { struct Primitive { pod::DrawCommand drawCommand; pod::Instance instance; + pod::LODMetadata lod; }; } @@ -265,9 +274,9 @@ namespace uf { void eraseAttribute( uf::Mesh::Input&, const uf::Mesh::Attribute& ); void eraseAttribute( uf::Mesh::Input&, size_t ); - uf::Mesh::Input remapInput( const uf::Mesh::Input&, size_t i = 0 ) const; - uf::Mesh::Input remapVertexInput( size_t i = 0 ) const; - uf::Mesh::Input remapIndexInput( size_t i = 0 ) const; + uf::Mesh::Input remapInput( const uf::Mesh::Input&, size_t = 0, size_t = 0 ) const; + uf::Mesh::Input remapVertexInput( size_t i = 0, size_t = 0 ) const; + uf::Mesh::Input remapIndexInput( size_t i = 0, size_t = 0 ) const; void print( bool = true ) const; @@ -276,9 +285,9 @@ namespace uf { std::string printInstances( bool = true ) const; std::string printIndirects( bool = true ) const; - uf::Mesh::View makeView( const uf::stl::vector& wanted = {} ) const; - uf::Mesh::View makeView( size_t commandIndex, const uf::stl::vector& wanted = {} ) const; - uf::stl::vector makeViews( const uf::stl::vector& wanted = {} ) const; + uf::Mesh::View makeView( const uf::stl::vector& wanted = {}, size_t index = 0 ) const; + uf::Mesh::View makeView( size_t commandIndex, const uf::stl::vector& wanted = {}, size_t index = 0 ) const; + uf::stl::vector makeViews( const uf::stl::vector& wanted = {}, size_t index = 0 ) const; inline bool hasVertex( const uf::stl::vector& descriptors ) const { return _hasV( vertex, descriptors ); } inline bool hasVertex( const uf::Mesh& mesh ) const { return _hasV( vertex, mesh.vertex ); } diff --git a/engine/src/engine/graph/decode.cpp b/engine/src/engine/graph/decode.cpp index 924e259b..9bb25d2f 100644 --- a/engine/src/engine/graph/decode.cpp +++ b/engine/src/engine/graph/decode.cpp @@ -185,10 +185,20 @@ namespace { return drawCommand; } + pod::LODMetadata decodeLODMetadata( ext::json::Value& json, pod::Graph& graph ) { + pod::LODMetadata lodMetadata; + ext::json::forEach( json, [&]( size_t i, ext::json::Value& value ){ + lodMetadata.levels[i].indices = value["indices"].as( lodMetadata.levels[i].indices ); + lodMetadata.levels[i].indexID = value["indexID"].as( lodMetadata.levels[i].indexID ); + }); + return lodMetadata; + } + pod::Primitive decodePrimitive( ext::json::Value& json, pod::Graph& graph ) { pod::Primitive prim; prim.instance = decodeInstance( json["instance"], graph ); prim.drawCommand = decodeDrawCommand( json["drawCommand"], graph ); + prim.lod = decodeLODMetadata( json["lod"], graph ); return prim; } diff --git a/engine/src/engine/graph/encode.cpp b/engine/src/engine/graph/encode.cpp index 1122d563..36513fa5 100644 --- a/engine/src/engine/graph/encode.cpp +++ b/engine/src/engine/graph/encode.cpp @@ -135,10 +135,21 @@ namespace { json["vertices"] = drawCommand.vertices; return json; } + uf::Serializer encode( const pod::LODMetadata& lodMetadata, const EncodingSettings& settings, const pod::Graph& graph ) { + uf::Serializer json; + ext::json::reserve( json, 4 ); + for ( size_t i = 0; i < 4; ++i ) { + auto& value = json.emplace_back(); + value["indices"] = lodMetadata.levels[i].indices; + value["indexID"] = lodMetadata.levels[i].indexID; + } + return json; + } uf::Serializer encode( const pod::Primitive& primitive, const EncodingSettings& settings, const pod::Graph& graph ) { uf::Serializer json; json["drawCommand"] = encode( primitive.drawCommand, settings, graph ); json["instance"] = encode( primitive.instance, settings, graph ); + json["lod"] = encode( primitive.lod, settings, graph ); return json; } diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 45ea5880..5b60373b 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -477,6 +477,7 @@ namespace { shader.aliasBuffer( "camera", storage.buffers.camera ); shader.aliasBuffer( "indirect", *indirect ); shader.aliasBuffer( "instance", storage.buffers.instance ); + shader.aliasBuffer( "lodMetadata", storage.buffers.lodMetadata ); shader.aliasBuffer( "object", storage.buffers.object ); shader.textures.clear(); @@ -1361,6 +1362,7 @@ void uf::graph::initialize( pod::Graph::Storage& storage, size_t initialElements storage.buffers.drawCommands.initialize( (const void*) nullptr, sizeof(pod::DrawCommand) * initialElements, uf::renderer::enums::Buffer::STORAGE ); storage.buffers.instance.initialize( (const void*) nullptr, sizeof(pod::Instance) * initialElements, uf::renderer::enums::Buffer::STORAGE ); storage.buffers.instanceAddresses.initialize( (const void*) nullptr, sizeof(pod::Instance::Addresses) * initialElements, uf::renderer::enums::Buffer::STORAGE ); + storage.buffers.lodMetadata.initialize( (const void*) nullptr, sizeof(pod::LODMetadata) * initialElements, uf::renderer::enums::Buffer::STORAGE ); storage.buffers.joint.initialize( (const void*) nullptr, sizeof(pod::Matrix4f) * initialElements, uf::renderer::enums::Buffer::STORAGE ); storage.buffers.object.initialize( (const void*) nullptr, sizeof(pod::Instance::Object) * initialElements, uf::renderer::enums::Buffer::STORAGE ); storage.buffers.material.initialize( (const void*) nullptr, sizeof(pod::Material) * initialElements, uf::renderer::enums::Buffer::STORAGE ); @@ -1397,6 +1399,7 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) { static thread_local uf::stl::vector instances; static thread_local uf::stl::vector instanceAddresses; + static thread_local uf::stl::vector lodMetadata; static thread_local uf::stl::vector joints; static thread_local uf::stl::vector objects; static thread_local uf::stl::vector materials; @@ -1430,10 +1433,15 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) { rebuild = storage.buffers.object.update( (const void*) objects.data(), objects.size() * sizeof(pod::Instance::Object) ) || rebuild; if ( ::newGraphAdded ) { + drawCommands.clear(); instances.clear(); + lodMetadata.clear(); + for ( auto& key : storage.primitives.keys ) { for ( auto& primitive : storage.primitives[key] ) { + drawCommands.emplace_back( primitive.drawCommand ); instances.emplace_back( primitive.instance ); + lodMetadata.emplace_back( primitive.lod ); } } @@ -1448,14 +1456,10 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) { materials.clear(); for ( auto& key : storage.materials.keys ) materials.emplace_back( storage.materials.map[key] ); - drawCommands.clear(); - for ( auto& key : storage.primitives.keys ) { - for ( auto& primitive : storage.primitives[key] ) drawCommands.emplace_back( primitive.drawCommand ); - } - rebuild = storage.buffers.instance.update( (const void*) instances.data(), instances.size() * sizeof(pod::Instance) ) || rebuild; rebuild = storage.buffers.instanceAddresses.update( (const void*) instanceAddresses.data(), instanceAddresses.size() * sizeof(pod::Instance::Addresses) ) || rebuild; rebuild = storage.buffers.drawCommands.update( (const void*) drawCommands.data(), drawCommands.size() * sizeof(pod::DrawCommand) ) || rebuild; + rebuild = storage.buffers.lodMetadata.update( (const void*) lodMetadata.data(), lodMetadata.size() * sizeof(pod::LODMetadata) ) || rebuild; rebuild = storage.buffers.material.update( (const void*) materials.data(), materials.size() * sizeof(pod::Material) ) || rebuild; rebuild = storage.buffers.texture.update( (const void*) textures.data(), textures.size() * sizeof(pod::Texture) ) || rebuild; diff --git a/engine/src/ext/gltf/gltf.cpp b/engine/src/ext/gltf/gltf.cpp index 091ce053..8f8e4962 100644 --- a/engine/src/ext/gltf/gltf.cpp +++ b/engine/src/ext/gltf/gltf.cpp @@ -282,6 +282,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const struct { bool should = false; bool print = false; + bool lods = false; size_t level = SIZE_MAX; float simplify = 1.0f; } meshopt; @@ -309,6 +310,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const meshopt.level = value["optimize meshlets"]["level"].as(meshopt.level); meshopt.simplify = value["optimize meshlets"]["simplify"].as(meshopt.simplify); meshopt.print = value["optimize meshlets"]["print"].as(meshopt.print); + meshopt.lods = value["optimize meshlets"]["lods"].as(meshopt.lods); } }); } @@ -522,6 +524,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const size_t level = SIZE_MAX; float simplify = 1.0f; bool print = false; + bool lods = false; if ( graph.metadata["exporter"]["optimize"].as("") == "tagged" ) { bool should = false; @@ -536,6 +539,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const level = value["optimize mesh"]["level"].as(level); simplify = value["optimize mesh"]["simplify"].as(simplify); print = value["optimize mesh"]["print"].as(print); + lods = value["optimize mesh"]["lods"].as(lods); } }); @@ -547,6 +551,18 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const if ( !ext::meshopt::optimize( mesh, simplify, level, print ) ) { UF_MSG_ERROR("Mesh optimization failed: {}", keyName ); } + if ( lods ) { + auto factors = ext::meshopt::computeLODs( mesh.index.count ); + auto lodMetadata = ext::meshopt::generateLODs( mesh, factors, print ); + if ( lodMetadata.empty() ) { + UF_MSG_ERROR("LOD generation failed: {}", keyName ); + } else { + UF_MSG_DEBUG("Generated {} LODs: {}", factors.size() - 1, keyName); + auto& primitives = storage.primitives[keyName]; + UF_ASSERT( primitives.size() == lodMetadata.size() ); + for ( auto i = 0; i < primitives.size(); ++i ) primitives[i].lod = lodMetadata[i]; + } + } } UF_MSG_DEBUG( "Optimized mesh" ); diff --git a/engine/src/ext/gltf/processPrimitives.inl b/engine/src/ext/gltf/processPrimitives.inl index 3ed92421..a0831838 100644 --- a/engine/src/ext/gltf/processPrimitives.inl +++ b/engine/src/ext/gltf/processPrimitives.inl @@ -356,7 +356,7 @@ if ( meshgrid.grid.divisions.x > 1 || meshgrid.grid.divisions.y > 1 || meshgrid. auto partitioned = uf::meshgrid::partition( meshgrid.grid, meshlets, meshgrid.eps, meshgrid.clip, meshgrid.cleanup ); if ( meshgrid.print ) UF_MSG_DEBUG( "Draw commands: {}: {} -> {} | Partitions: {} -> {}", m.name, meshlets.size(), partitioned.size(), (meshgrid.grid.divisions.x * meshgrid.grid.divisions.y * meshgrid.grid.divisions.z), meshgrid.grid.nodes.size() - ); + ); meshlets = std::move( partitioned ); } @@ -367,6 +367,15 @@ if ( meshopt.should ) { if ( !ext::meshopt::optimize( meshlet, meshopt.simplify, meshopt.level, meshopt.print ) ) { UF_MSG_ERROR("Mesh optimization failed: {}", keyName ); } + /* + if ( meshopt.lods ) { + auto factors = ext::meshopt::computeLODs( meshlet.indices.size() ); + auto lodMetadata = ext::meshopt::generateLODs( meshlet, factors, meshopt.print ); + if ( lodMetadata.empty() ) { + UF_MSG_ERROR("LOD generation failed: {}", keyName ); + } + } + */ } } #endif diff --git a/engine/src/ext/meshopt/meshopt.cpp b/engine/src/ext/meshopt/meshopt.cpp index d8192e01..ea537ad7 100644 --- a/engine/src/ext/meshopt/meshopt.cpp +++ b/engine/src/ext/meshopt/meshopt.cpp @@ -4,175 +4,224 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verbose ) { if ( mesh.isInterleaved() ) { - UF_MSG_ERROR("optimization of interleaved meshes is currently not supported. Consider optimizing on meshlets."); + UF_MSG_ERROR("Optimization of interleaved meshes is currently not supported. Consider optimizing on meshlets."); return false; } mesh.updateDescriptor(); - struct Remap { - uf::Mesh::Attribute attribute; - uf::stl::vector buffer; - }; - - uf::stl::vector attributes; - uf::stl::vector streams; - for ( auto& attribute : mesh.vertex.attributes ) { - auto& p = attributes.emplace_back(); - p.attribute = attribute; - - auto& stream = streams.emplace_back(); - stream.data = p.attribute.pointer; - stream.size = p.attribute.descriptor.size; - stream.stride = p.attribute.stride; + const auto& views = mesh.buffer_views; + if ( views.empty() ) { + UF_MSG_ERROR("No buffer views found. Cannot optimize per-submesh."); + return false; } - bool hasIndices = mesh.index.count > 0; - size_t indicesCount = hasIndices ? mesh.index.count : mesh.vertex.count; - uf::stl::vector remap(indicesCount); + uf::stl::vector optIndices; + pod::DrawCommand* drawCommands = mesh.indirect.count > 0 ? (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data() : nullptr; - uint32_t* sourceIndicesPointer = NULL; - uf::stl::vector sourceIndices(indicesCount); - if ( hasIndices ) { - uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data(); - for ( auto index = 0; index < indicesCount; ++index ) { - switch ( mesh.index.size ) { - case 1: sourceIndices[index] = (( uint8_t*) pointer)[index]; break; - case 2: sourceIndices[index] = ((uint16_t*) pointer)[index]; break; - case 4: sourceIndices[index] = ((uint32_t*) pointer)[index]; break; - } - } - } + for ( size_t viewIdx = 0; viewIdx < views.size(); ++viewIdx ) { + const auto& view = views[viewIdx]; + auto& indicesView = view["index"]; + auto& positionsView = view["position"]; - size_t verticesCount = meshopt_generateVertexRemapMulti( &remap[0], sourceIndicesPointer, indicesCount, indicesCount, &streams[0], streams.size() ); + if ( !indicesView.valid() || !positionsView.valid() ) continue; - // generate new indices, as they're going to be specific to a region of vertices due to drawcommand shittery - uf::stl::vector indices(indicesCount); - meshopt_remapIndexBuffer(&indices[0], sourceIndicesPointer, indicesCount, &remap[0]); + size_t indicesCount = view.index.count; + size_t vertexCount = view.vertex.count; - // - for ( auto& p : attributes ) { - auto& buffer = p.buffer; - buffer.resize(verticesCount * p.attribute.descriptor.size); - - meshopt_remapVertexBuffer(&buffer[0], p.attribute.pointer, indicesCount, p.attribute.descriptor.size, &remap[0]); - } - // - meshopt_optimizeVertexCache(&indices[0], &indices[0], indicesCount, verticesCount); - // - meshopt_optimizeVertexFetchRemap(&remap[0], &indices[0], indicesCount, verticesCount); - // - for ( auto& p : attributes ) { - auto& buffer = p.buffer; - p.attribute.pointer = &buffer[0]; - - meshopt_remapVertexBuffer(p.attribute.pointer, p.attribute.pointer, verticesCount, p.attribute.descriptor.size, &remap[0]); - } - // almost always causes ID discontinuities - if ( 0.0f < simplify && simplify < 1.0f ) { - uf::stl::vector indicesSimplified(indicesCount); - - uf::Mesh::Attribute positionAttribute; - for ( auto& p : attributes ) if ( p.attribute.descriptor.name == "position" ) positionAttribute = p.attribute; - - size_t targetIndices = indicesCount * simplify; - float targetError = 1e-2f / simplify; - - float realError = 0.0f; - size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetError, realError); - // size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetIndices); - - if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError); - - indicesCount = realIndices; - indices.swap( indicesSimplified ); - } - // done - if ( mesh.indirect.count ) { - bool discontinuityDetected = false; - size_t lastID = 0; - struct Remap { - pod::DrawCommand* drawCommand; - struct { - size_t start = SIZE_MAX; - size_t end = 0; - } index, vertex; - }; - - pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data(); - uf::stl::vector remappedDrawCommands( mesh.indirect.count ); - - uf::Mesh::Attribute idAttribute; - for ( auto& p : attributes ) if ( p.attribute.descriptor.name == "id" ) idAttribute = p.attribute; - for ( size_t index = 0; index < indicesCount; ++index ) { - size_t vertex = indices[index]; - pod::Vector& id = *(pod::Vector*) ( static_cast(idAttribute.pointer) + idAttribute.stride * vertex ); - - auto& d = remappedDrawCommands[id.x]; - d.drawCommand = &drawCommands[id.x]; - d.vertex.start = std::min( d.vertex.start, vertex ); - d.vertex.end = std::max( d.vertex.end, vertex ); - - d.index.start = std::min( d.index.start, index ); - d.index.end = std::max( d.index.end, index ); - - if ( lastID == id.x ) { - - } else if ( lastID + 1 == id.x ) { - lastID = id.x; - } else { - if ( verbose ) UF_MSG_DEBUG("Discontinuity detected: {} | {} | {} | {}", index, vertex, lastID, id.x); - discontinuityDetected = true; - lastID = id.x; + uf::stl::vector submeshIndices(indicesCount); + for ( size_t i = 0; i < indicesCount; ++i ) { + size_t global_i = view.index.first + i; + switch ( indicesView.attribute.descriptor.size ) { + case 1: submeshIndices[i] = indicesView.get(global_i)[0]; break; + case 2: submeshIndices[i] = indicesView.get(global_i)[0]; break; + case 4: submeshIndices[i] = indicesView.get(global_i)[0]; break; } } - if ( discontinuityDetected ) { - UF_MSG_ERROR("Discontinuity detected, bailing..."); - return false; - } + meshopt_optimizeVertexCache(&submeshIndices[0], &submeshIndices[0], indicesCount, mesh.vertex.count); - for ( auto& d : remappedDrawCommands ) { - d.drawCommand->indices = d.index.end - d.index.start + 1; - d.drawCommand->indexID = d.index.start; - d.drawCommand->vertexID = d.vertex.start; - d.drawCommand->vertices = d.vertex.end - d.vertex.start + 1; - } + meshopt_optimizeOverdraw( + &submeshIndices[0], + &submeshIndices[0], + indicesCount, + (const float*) positionsView.data(), + mesh.vertex.count, + positionsView.stride(), + 1.05f + ); - for ( size_t index = 0; index < indicesCount; ++index ) { - auto& vertex = indices[index]; - pod::Vector& id = *(pod::Vector*) ( static_cast(idAttribute.pointer) + idAttribute.stride * vertex ); + if ( 0.0f < simplify && simplify < 1.0f ) { + uf::stl::vector indicesSimplified(indicesCount); - auto& d = remappedDrawCommands[id.x]; - vertex -= d.vertex.start; - } - } + float targetError = 0.1; // 1e-2f / simplify; + float realError = 0.0f; - mesh.index.count = indicesCount; - mesh.vertex.count = verticesCount; + size_t realIndices = meshopt_simplify( + &indicesSimplified[0], + &submeshIndices[0], + indicesCount, + (const float*) positionsView.data(), + mesh.vertex.count, + positionsView.stride(), + indicesCount * simplify, + targetError + //,0, &realError + ); - // vertices - for ( size_t i = 0; i < attributes.size(); ++i ) { - auto& attribute = mesh.vertex.attributes[i]; - auto& remapped = attributes[i]; - - mesh.buffers[attribute.buffer].swap( remapped.buffer ); - attribute.pointer = mesh.buffers[attribute.buffer].data(); - } - - // indices - { - mesh.resizeIndices( mesh.index.count ); - uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data(); - for ( auto index = 0; index < indicesCount; ++index ) { - switch ( mesh.index.size ) { - case 1: (( uint8_t*) pointer)[index] = indices[index]; break; - case 2: ((uint16_t*) pointer)[index] = indices[index]; break; - case 4: ((uint32_t*) pointer)[index] = indices[index]; break; + if ( verbose ) { + UF_MSG_DEBUG("[View {} Simplified] indices: {} -> {} | error: {} -> {}", viewIdx, indicesCount, realIndices, targetError, realError); } + + indicesCount = realIndices; + submeshIndices.swap(indicesSimplified); + submeshIndices.resize(indicesCount); + } + + size_t newIndexStart = optIndices.size(); + optIndices.insert(optIndices.end(), submeshIndices.begin(), submeshIndices.end()); + + if ( drawCommands ) { + drawCommands[view.indirectIndex].indexID = newIndexStart; + drawCommands[view.indirectIndex].indices = indicesCount; + } + } + + mesh.index.count = optIndices.size(); + mesh.resizeIndices( mesh.index.count ); + + uint8_t* dstPointer = (uint8_t*) mesh.getBuffer(mesh.index).data(); + for ( size_t i = 0; i < optIndices.size(); ++i ) { + switch ( mesh.index.size ) { + case 1: (( uint8_t*) dstPointer)[i] = (uint8_t) optIndices[i]; break; + case 2: ((uint16_t*) dstPointer)[i] = (uint16_t) optIndices[i]; break; + case 4: ((uint32_t*) dstPointer)[i] = (uint32_t) optIndices[i]; break; } } mesh.updateDescriptor(); return true; } + +uf::stl::vector ext::meshopt::computeLODs( size_t count, size_t maxLODs, size_t minIndices ) { + uf::stl::vector factors; + factors.reserve(maxLODs); + factors.emplace_back(1.0f); + + float factor = 1.0f; + for (size_t i = 1; i < maxLODs; ++i) { + factor *= 0.5f; + if ( count * factor < minIndices ) break; + factors.emplace_back(factor); + } + + return factors; +} + +uf::stl::vector ext::meshopt::generateLODs( uf::Mesh& mesh, const uf::stl::vector& lodFactors, bool verbose ) { + uf::stl::vector lodMetadata; + + if ( mesh.isInterleaved() ) { + UF_MSG_ERROR("Cannot generate LODs on interleaved meshes."); + return lodMetadata; + } + mesh.updateDescriptor(); + + const auto& views = mesh.buffer_views; + if ( views.empty() ) return lodMetadata; + + size_t numLODs = std::min(lodFactors.size(), (size_t)4); + lodMetadata.resize(mesh.indirect.count); + + uf::stl::vector> lodBlocks(numLODs); + pod::DrawCommand* drawCommands = mesh.indirect.count > 0 ? (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data() : nullptr; + + for ( size_t viewIdx = 0; viewIdx < views.size(); ++viewIdx ) { + const auto& view = views[viewIdx]; + uint32_t cmdIdx = view.indirectIndex; + + auto& indicesView = view["index"]; + auto& positionsView = view["position"]; + + size_t baseIndicesCount = view.index.count; + uf::stl::vector baseIndices(baseIndicesCount); + + for ( size_t i = 0; i < baseIndicesCount; ++i ) { + size_t global_i = view.index.first + i; + switch ( indicesView.attribute.descriptor.size ) { + case 1: baseIndices[i] = indicesView.get(global_i)[0]; break; + case 2: baseIndices[i] = indicesView.get(global_i)[0]; break; + case 4: baseIndices[i] = indicesView.get(global_i)[0]; break; + } + } + + meshopt_optimizeVertexCache(&baseIndices[0], &baseIndices[0], baseIndicesCount, mesh.vertex.count); + + for ( size_t lodIdx = 0; lodIdx < numLODs; ++lodIdx ) { + float simplify = lodFactors[lodIdx]; + uf::stl::vector lodIndices = baseIndices; + size_t currentIndicesCount = baseIndicesCount; + + if ( simplify < 1.0f ) { + float targetError = 0.1; // 1e-2f / simplify; + float realError = 0.0f; + currentIndicesCount = meshopt_simplify( + &lodIndices[0], &baseIndices[0], baseIndicesCount, + (const float*)positionsView.data(0), mesh.vertex.count, positionsView.stride(), + baseIndicesCount * simplify, targetError + //, 0, &realError + ); + + if ( baseIndicesCount == currentIndicesCount ) { + continue; + } + + if ( verbose ) { + UF_MSG_DEBUG("[View {} Simplified LOD {}] indices: {} -> {} | error: {} -> {}", viewIdx, lodIdx, baseIndicesCount, currentIndicesCount, targetError, realError); + } + + + lodIndices.resize(currentIndicesCount); + } + + lodMetadata[cmdIdx].levels[lodIdx].indexID = lodBlocks[lodIdx].size(); + lodMetadata[cmdIdx].levels[lodIdx].indices = currentIndicesCount; + + lodBlocks[lodIdx].insert(lodBlocks[lodIdx].end(), lodIndices.begin(), lodIndices.end()); + } + } + + uf::stl::vector unifiedIndices; + size_t currentGlobalOffset = 0; + + for ( size_t lodIdx = 0; lodIdx < numLODs; ++lodIdx ) { + for ( size_t viewIdx = 0; viewIdx < views.size(); ++viewIdx ) { + uint32_t cmdIdx = views[viewIdx].indirectIndex; + lodMetadata[cmdIdx].levels[lodIdx].indexID += currentGlobalOffset; + + if ( lodIdx == 0 && drawCommands ) { + drawCommands[cmdIdx].indexID = lodMetadata[cmdIdx].levels[0].indexID; + drawCommands[cmdIdx].indices = lodMetadata[cmdIdx].levels[0].indices; + } + } + + unifiedIndices.insert(unifiedIndices.end(), lodBlocks[lodIdx].begin(), lodBlocks[lodIdx].end()); + currentGlobalOffset = unifiedIndices.size(); + } + + mesh.index.count = unifiedIndices.size(); + mesh.resizeIndices( mesh.index.count ); + uint8_t* dstPointer = (uint8_t*) mesh.getBuffer(mesh.index).data(); + for ( size_t i = 0; i < unifiedIndices.size(); ++i ) { + switch ( mesh.index.size ) { + case 1: (( uint8_t*) dstPointer)[i] = (uint8_t) unifiedIndices[i]; break; + case 2: ((uint16_t*) dstPointer)[i] = (uint16_t) unifiedIndices[i]; break; + case 4: ((uint32_t*) dstPointer)[i] = (uint32_t) unifiedIndices[i]; break; + } + } + + mesh.updateDescriptor(); + + return lodMetadata; +} + #endif \ No newline at end of file diff --git a/engine/src/ext/xatlas/xatlas.cpp b/engine/src/ext/xatlas/xatlas.cpp index 7cf1f536..4c790fbe 100644 --- a/engine/src/ext/xatlas/xatlas.cpp +++ b/engine/src/ext/xatlas/xatlas.cpp @@ -2,13 +2,9 @@ #if UF_USE_XATLAS #include -#define UF_XATLAS_UNWRAP_MULTITHREAD 1 -#define UF_XATLAS_UNWRAP_SERIAL 1 // really big scenes will gorge on memory +#define UF_XATLAS_UNWRAP_MULTITHREAD 0 // prone to crashing size_t ext::xatlas::unwrap( pod::Graph& graph ) { - return graph.metadata["exporter"]["unwrap lazy"].as(false) ? unwrapLazy( graph ) : unwrapExperimental( graph ); -} -size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) { struct Entry { size_t index = 0; size_t commandID = 0; @@ -60,81 +56,50 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) { if ( !should ) continue; source = mesh; - source.updateDescriptor(); + source.updateDescriptor(); - uf::Mesh::Input vertexInput = mesh.vertex; + for ( size_t viewIdx = 0; viewIdx < source.buffer_views.size(); ++viewIdx ) { + const auto& view = source.buffer_views[viewIdx]; - uf::Mesh::Attribute positionAttribute; - uf::Mesh::Attribute uvAttribute; - uf::Mesh::Attribute stAttribute; + size_t atlasID = 0; + if ( view.indirectIndex != -1 ) { + pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data(); + atlasID = drawCommands[view.indirectIndex].auxID; + } - 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" ); + auto& atlas = atlases[atlasID]; + auto& entry = atlas.entries.emplace_back(); + entry.index = index; + entry.commandID = viewIdx; - if ( mesh.index.count ) { - uf::Mesh::Input indexInput = mesh.index; - uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front(); + auto& decl = entry.decl; + auto posView = view["position"]; + auto uvView = view["uv"]; + auto idxView = view["index"]; - ::xatlas::IndexFormat indexType = ::xatlas::IndexFormat::UInt32; - 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; - } + UF_ASSERT( posView.valid() && uvView.valid() ); - if ( mesh.indirect.count ) { - auto& primitives = /*graph.storage*/storage.primitives[name]; - pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data(); + decl.vertexCount = view.vertex.count; + decl.vertexPositionData = posView.data(view.vertex.first); + decl.vertexPositionStride = posView.stride(); + decl.vertexUvData = uvView.data(view.vertex.first); + decl.vertexUvStride = uvView.stride(); - for ( auto i = 0; i < mesh.indirect.count; ++i ) { - size_t atlasID = drawCommands[i].auxID; + if ( idxView.valid() ) { + decl.indexCount = view.index.count; + // Pass view.index.first to offset the index pointer! + decl.indexData = idxView.data(view.index.first); - 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; - } - } else UF_EXCEPTION("to-do: not require indices for meshes"); + switch ( idxView.attribute.descriptor.size ) { + case 1: UF_EXCEPTION("xatlas does not support 8-bit indices"); break; + case 2: decl.indexFormat = ::xatlas::IndexFormat::UInt16; break; + case 4: decl.indexFormat = ::xatlas::IndexFormat::UInt32; break; + default: UF_EXCEPTION("unsupported index type"); break; + } + } else { + decl.indexCount = 0; + } + } } ::xatlas::ChartOptions chartOptions{}; @@ -168,7 +133,6 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) { } } - // pack #if UF_XATLAS_UNWRAP_MULTITHREAD auto tasks = uf::thread::schedule(true); @@ -272,108 +236,70 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) { // update vertices for ( auto& pair : atlases ) { - auto& atlas = pair.second; + auto& atlas = pair.second; - size_t vertexIDOffset = 0; - for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) { - auto& xmesh = atlas.pointer->meshes[i]; - auto& entry = atlas.entries[i]; - auto& name = graph.meshes[entry.index]; - auto& mesh = /*graph.storage*/storage.meshes[name]; - auto& source = sources[entry.index]; + for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) { + auto& xmesh = atlas.pointer->meshes[i]; + auto& entry = atlas.entries[i]; - if ( source.vertex.count == 0 ) continue; + auto& name = graph.meshes[entry.index]; + auto& mesh = storage.meshes[name]; + auto& source = sources[entry.index]; - // draw commands - if ( mesh.indirect.count ) { - auto srcInput = source.remapVertexInput( entry.commandID ); - auto dstInput = mesh.remapVertexInput( entry.commandID ); + if ( source.vertex.count == 0 ) continue; - auto& primitives = /*graph.storage*/storage.primitives[name]; - pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data(); + // Grab the read-only view from our unmodified source mesh + const auto& srcView = source.buffer_views[entry.commandID]; - auto& drawCommand = drawCommands[entry.commandID]; - auto& primitive = primitives[entry.commandID]; + // We dynamically calculate the destination offsets using the updated draw commands + // Or if it's direct, it's just 0. + size_t dstVertexFirst = 0; + size_t dstIndexFirst = 0; + if ( mesh.indirect.count > 0 ) { + pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data(); + dstVertexFirst = drawCommands[entry.commandID].vertexID; + dstIndexFirst = drawCommands[entry.commandID].indexID; + } - for ( auto j = 0; j < xmesh.vertexCount; ++j ) { - auto& vertex = xmesh.vertexArray[j]; - auto ref = vertex.xref; - - for ( auto _ = 0; _ < srcInput.attributes.size(); ++_ ) { - auto srcAttribute = srcInput.attributes[_]; - auto dstAttribute = dstInput.attributes[_]; + // 1. Copy over the vertices based on the xref mapping + for ( auto j = 0; j < xmesh.vertexCount; ++j ) { + auto& vertex = xmesh.vertexArray[j]; + uint32_t ref = vertex.xref; // original vertex index relative to the sub-mesh - memcpy( - static_cast(dstAttribute.pointer) + dstAttribute.stride * (j + dstInput.first), - static_cast(srcAttribute.pointer) + srcAttribute.stride * (ref + srcInput.first), - srcAttribute.stride - ); - } - } + for ( auto attrIdx = 0; attrIdx < mesh.vertex.attributes.size(); ++attrIdx ) { + auto srcAttribute = srcView.vertex.attributes[attrIdx]; + auto dstAttribute = mesh.vertex.attributes[attrIdx]; - for ( auto j = 0; j < xmesh.vertexCount; ++j ) { - auto& vertex = xmesh.vertexArray[j]; - auto ref = vertex.xref; - - for ( auto _ = 0; _ < srcInput.attributes.size(); ++_ ) { - auto dstAttribute = dstInput.attributes[_]; - if ( dstAttribute.descriptor.name != "st" ) continue; - pod::Vector2f& st = *(pod::Vector2f*) ( static_cast(dstAttribute.pointer) + dstAttribute.stride * (j + dstInput.first) ); - st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };; - } - } + uint8_t* dstPtr = static_cast(dstAttribute.pointer) + dstAttribute.stride * (dstVertexFirst + j); - // indices - if ( mesh.index.count ) { - uf::Mesh::Input indexInput = mesh.remapIndexInput( entry.commandID ); - uf::Mesh::Attribute indexAttribute = indexInput.attributes.front(); - uint8_t* pointer = (uint8_t*) static_cast(indexAttribute.pointer) + indexAttribute.stride * indexInput.first; - for ( auto index = 0; index < xmesh.indexCount; ++index ) { - switch ( mesh.index.size ) { - case 1: (( uint8_t*) pointer)[index] = xmesh.indexArray[index]; break; - case 2: ((uint16_t*) pointer)[index] = xmesh.indexArray[index]; break; - case 4: ((uint32_t*) pointer)[index] = xmesh.indexArray[index]; break; - } - } - } - } else { - uf::Mesh::Attribute stAttribute; - for ( auto& attribute : mesh.vertex.attributes ) if ( attribute.descriptor.name == "st" ) stAttribute = attribute; - UF_ASSERT( stAttribute.descriptor.name == "st" ); + if ( dstAttribute.descriptor.name == "st" ) { + // Write new lightmap STs! + pod::Vector2f& st = *(pod::Vector2f*)dstPtr; + st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height }; + } else { + // Copy original vertex data + const uint8_t* srcPtr = static_cast(srcAttribute.pointer) + srcAttribute.stride * (srcView.vertex.first + ref); + memcpy(dstPtr, srcPtr, srcAttribute.descriptor.size); + } + } + } - // vertices - for ( auto j = 0; j < xmesh.vertexCount; ++j ) { - auto& vertex = xmesh.vertexArray[j]; - auto ref = vertex.xref; + // 2. Write new indices + if ( mesh.index.count ) { + uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front(); + uint8_t* dstIndexPtr = static_cast(indexAttribute.pointer) + indexAttribute.stride * dstIndexFirst; - for ( auto k = 0; k < mesh.vertex.attributes.size(); ++k ) { - auto srcAttribute = source.vertex.attributes[k]; - auto dstAttribute = mesh.vertex.attributes[k]; - - if ( dstAttribute.descriptor.name == "st" ) { - pod::Vector2f& st = *(pod::Vector2f*) ( ((uint8_t*) dstAttribute.pointer) + dstAttribute.stride * (mesh.vertex.first + j)); - st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height }; - } else { - memcpy( static_cast(dstAttribute.pointer) + dstAttribute.stride * j, static_cast(srcAttribute.pointer) + srcAttribute.stride * ref, srcAttribute.stride ); - } - } - } - // indices - if ( mesh.index.count ) { - // uint8_t* pointer = (uint8_t*) mesh.buffers[mesh.isInterleaved(mesh.index.interleaved) ? mesh.index.interleaved : mesh.index.attributes.front().buffer].data(); - uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data(); - for ( auto index = 0; index < xmesh.indexCount; ++index ) { - switch ( mesh.index.size ) { - case 1: (( uint8_t*) pointer)[index] = xmesh.indexArray[index]; break; - case 2: ((uint16_t*) pointer)[index] = xmesh.indexArray[index]; break; - case 4: ((uint32_t*) pointer)[index] = xmesh.indexArray[index]; break; - } - } - } - } - mesh.updateDescriptor(); - } - } + for ( auto idx = 0; idx < xmesh.indexCount; ++idx ) { + switch ( mesh.index.size ) { + case 1: (( uint8_t*) dstIndexPtr)[idx] = (uint8_t) xmesh.indexArray[idx]; break; + case 2: ((uint16_t*) dstIndexPtr)[idx] = (uint16_t) xmesh.indexArray[idx]; break; + case 4: ((uint32_t*) dstIndexPtr)[idx] = (uint32_t) xmesh.indexArray[idx]; break; + } + } + } + mesh.updateDescriptor(); + } + } // cleanup size_t atlasCount = 0; @@ -384,276 +310,4 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) { } return atlasCount; } - -size_t ext::xatlas::unwrapLazy( pod::Graph& graph ) { - struct Entry { - size_t index = 0; - size_t commandID = 0; - ::xatlas::MeshDecl decl; - }; - struct Atlas { - ::xatlas::Atlas* pointer = NULL; - uf::stl::vector entries; - size_t vertexOffset = 0; - }; - - uf::stl::unordered_map atlases; - atlases.reserve(graph.meshes.size()); - - auto& scene = uf::scene::getCurrentScene(); - auto& storage = uf::graph::globalStorage ? uf::graph::storage : scene.getComponent(); - - // copy source meshes - // create mesh decls for passing to xatlas - for ( auto index = 0; index < graph.meshes.size(); ++index ) { - auto& name = graph.meshes[index]; - auto& mesh = /*graph.storage*/storage.meshes[name]; - - if ( mesh.isInterleaved() ) { - UF_EXCEPTION("unwrapping interleaved mesh is not supported"); - } - - bool should = false; - if ( graph.metadata["exporter"]["unwrap"].is() && graph.metadata["exporter"]["unwrap"].as() ) { - should = true; - } else { - ext::json::forEach( graph.metadata["tags"], [&]( const uf::stl::string& key, ext::json::Value& value ) { - if ( uf::string::isRegex( key ) ) { - if ( !uf::string::matched( name, key ) ) return; - } else if ( name != key ) return; - - if ( ext::json::isNull( value["unwrap mesh"] ) ) return; - if ( !value["unwrap mesh"].as(false) ) return; - should = true; - }); - } - if ( !should ) continue; - - - uf::Mesh::Input vertexInput = mesh.vertex; - - 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(); - - ::xatlas::IndexFormat indexType = ::xatlas::IndexFormat::UInt32; - 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; - } - - 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; - } - } else UF_EXCEPTION("to-do: not require indices for meshes"); - } - - ::xatlas::ChartOptions chartOptions{}; - chartOptions.useInputMeshUvs = graph.metadata["baking"]["settings"]["useInputMeshUvs"].as(chartOptions.useInputMeshUvs); - chartOptions.maxIterations = graph.metadata["baking"]["settings"]["maxIterations"].as(chartOptions.maxIterations); - - ::xatlas::PackOptions packOptions{}; - packOptions.maxChartSize = graph.metadata["baking"]["settings"]["maxChartSize"].as(packOptions.maxChartSize); - packOptions.padding = graph.metadata["baking"]["settings"]["padding"].as(packOptions.padding); - packOptions.texelsPerUnit = graph.metadata["baking"]["settings"]["texelsPerUnit"].as(packOptions.texelsPerUnit); - packOptions.bilinear = graph.metadata["baking"]["settings"]["bilinear"].as(packOptions.bilinear); - packOptions.blockAlign = graph.metadata["baking"]["settings"]["blockAlign"].as(packOptions.blockAlign); - packOptions.bruteForce = graph.metadata["baking"]["settings"]["bruteForce"].as(packOptions.bruteForce); - packOptions.createImage = graph.metadata["baking"]["settings"]["createImage"].as(packOptions.createImage); - packOptions.rotateChartsToAxis = graph.metadata["baking"]["settings"]["rotateChartsToAxis"].as(packOptions.rotateChartsToAxis); - packOptions.rotateCharts = graph.metadata["baking"]["settings"]["rotateCharts"].as(packOptions.rotateCharts); - packOptions.resolution = graph.metadata["baking"]["resolution"].as(packOptions.resolution); - - // pack -#if UF_XATLAS_UNWRAP_SERIAL - size_t atlasCount = 0; - for ( auto& pair : atlases ) { - auto& atlas = pair.second; - if ( !atlas.pointer ) atlas.pointer = ::xatlas::Create(); - - for ( auto& entry : atlas.entries ) { - ::xatlas::AddMeshError error = ::xatlas::AddMesh(atlas.pointer, entry.decl, atlas.entries.size()); - if (error != ::xatlas::AddMeshError::Success) { - ::xatlas::Destroy(atlas.pointer); - UF_EXCEPTION("{}", ::xatlas::StringForEnum(error)); - } - } - - ::xatlas::Generate(atlas.pointer, chartOptions, packOptions); - - for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) { - auto& xmesh = atlas.pointer->meshes[i]; - auto& entry = atlas.entries[i]; - auto& name = graph.meshes[entry.index]; - auto& mesh = /*graph.storage*/storage.meshes[name]; - - // draw commands - if ( mesh.indirect.count ) { - auto vertexInput = mesh.remapVertexInput( entry.commandID ); - for ( auto j = 0; j < xmesh.vertexCount; ++j ) { - auto& vertex = xmesh.vertexArray[j]; - auto ref = vertex.xref; - - for ( auto k = 0; k < vertexInput.attributes.size(); ++k ) { - auto dstAttribute = vertexInput.attributes[k]; - if ( dstAttribute.descriptor.name != "st" ) continue; - pod::Vector2f& st = *(pod::Vector2f*) ( static_cast(dstAttribute.pointer) + dstAttribute.stride * (ref + vertexInput.first) ); - st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };; - } - } - } else { - for ( auto j = 0; j < xmesh.vertexCount; ++j ) { - auto& vertex = xmesh.vertexArray[j]; - auto ref = vertex.xref; - - for ( auto k = 0; k < mesh.vertex.attributes.size(); ++k ) { - auto dstAttribute = mesh.vertex.attributes[k]; - if ( dstAttribute.descriptor.name != "st" ) continue; - pod::Vector2f& st = *(pod::Vector2f*) ( static_cast(dstAttribute.pointer) + dstAttribute.stride * (ref + mesh.vertex.first) ); - st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };; - } - } - } - mesh.updateDescriptor(); - } - - ::xatlas::Destroy(atlas.pointer); - ++atlasCount; - } -#else - // add mesh decls to mesh atlases - // done after the fact since we'll know the total amount of meshes added - for ( auto& pair : atlases ) { - auto& atlas = pair.second; - if ( !atlas.pointer ) atlas.pointer = ::xatlas::Create(); - - for ( auto& entry : atlas.entries ) { - ::xatlas::AddMeshError error = ::xatlas::AddMesh(atlas.pointer, entry.decl, atlas.entries.size()); - if (error != ::xatlas::AddMeshError::Success) { - ::xatlas::Destroy(atlas.pointer); - UF_EXCEPTION("{}", ::xatlas::StringForEnum(error)); - } - } - } - -#if UF_XATLAS_UNWRAP_MULTITHREAD - auto tasks = uf::thread::schedule(true); -#else - auto tasks = uf::thread::schedule(false); -#endif - for ( auto& pair : atlases ) { - tasks.queue([&]{ - auto& atlas = pair.second; - ::xatlas::Generate(atlas.pointer, chartOptions, packOptions); - }); - } - uf::thread::execute( tasks ); - - // update vertices - for ( auto& pair : atlases ) { - auto& atlas = pair.second; - - for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) { - auto& xmesh = atlas.pointer->meshes[i]; - auto& entry = atlas.entries[i]; - auto& name = graph.meshes[entry.index]; - auto& mesh = /*graph.storage*/storage.meshes[name]; - - // draw commands - if ( mesh.indirect.count ) { - auto vertexInput = mesh.remapVertexInput( entry.commandID ); - for ( auto j = 0; j < xmesh.vertexCount; ++j ) { - auto& vertex = xmesh.vertexArray[j]; - auto ref = vertex.xref; - - for ( auto k = 0; k < vertexInput.attributes.size(); ++k ) { - auto dstAttribute = vertexInput.attributes[k]; - if ( dstAttribute.descriptor.name != "st" ) continue; - pod::Vector2f& st = *(pod::Vector2f*) ( static_cast(dstAttribute.pointer) + dstAttribute.stride * (ref + vertexInput.first) ); - st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };; - } - } - } else { - for ( auto j = 0; j < xmesh.vertexCount; ++j ) { - auto& vertex = xmesh.vertexArray[j]; - auto ref = vertex.xref; - - for ( auto k = 0; k < mesh.vertex.attributes.size(); ++k ) { - auto dstAttribute = mesh.vertex.attributes[k]; - if ( dstAttribute.descriptor.name != "st" ) continue; - pod::Vector2f& st = *(pod::Vector2f*) ( static_cast(dstAttribute.pointer) + dstAttribute.stride * (ref + mesh.vertex.first) ); - st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };; - } - } - } - mesh.updateDescriptor(); - } - } - // cleanup - size_t atlasCount = 0; - for ( auto& pair : atlases ) { - auto& atlas = pair.second; - ::xatlas::Destroy(atlas.pointer); - ++atlasCount; - } -#endif - return atlasCount; -} #endif \ No newline at end of file diff --git a/engine/src/utils/math/physics/bvh.inl b/engine/src/utils/math/physics/bvh.inl index 6f58bbbe..7f66ea73 100644 --- a/engine/src/utils/math/physics/bvh.inl +++ b/engine/src/utils/math/physics/bvh.inl @@ -6,6 +6,11 @@ namespace { void queryFlatOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs ); void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ); + + void postprocessPairs( pod::BVH::pairs_t& pairs ) { + std::sort(pairs.begin(), pairs.end()); + pairs.erase(std::unique(pairs.begin(), pairs.end()), pairs.end()); + } } // BVH @@ -466,7 +471,7 @@ namespace { if ( bodyA == bodyB ) continue; if ( bodyA > bodyB ) std::swap( bodyA, bodyB ); - pairs.emplace(bodyA, bodyB); + pairs.emplace_back(bodyA, bodyB); } } return; @@ -496,17 +501,26 @@ namespace { if ( bodyA == bodyB ) continue; if ( bodyA > bodyB ) std::swap( bodyA, bodyB ); - pairs.emplace(bodyA, bodyB); + pairs.emplace_back(bodyA, bodyB); } } return; } - if ( nodeA.getCount() == 0 ) { + if ( nodeA.getCount() == 0 && nodeB.getCount() == 0 ) { + if ( ::aabbSurfaceArea(bvhA.bounds[nodeAID]) > ::aabbSurfaceArea(bvhB.bounds[nodeBID]) ) { + ::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, pairs ); + ::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, pairs ); + } else { + ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, pairs ); + ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, pairs ); + } + } + else if ( nodeA.getCount() == 0 ) { ::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, pairs ); ::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, pairs ); } - if ( nodeB.getCount() == 0 ) { + else if ( nodeB.getCount() == 0 ) { ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, pairs ); ::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, pairs ); } @@ -524,7 +538,7 @@ namespace { if ( bodyA == bodyB ) continue; if ( bodyA > bodyB ) std::swap( bodyA, bodyB ); - pairs.emplace(bodyA, bodyB); + pairs.emplace_back(bodyA, bodyB); } } return; @@ -539,6 +553,8 @@ namespace { if ( bvh.nodes.empty() ) return; outPairs.reserve(uf::physics::impl::settings.reserveCount); ::traverseBVH( bvh, 0, outPairs ); + + ::postprocessPairs( outPairs ); } void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ) { @@ -547,6 +563,8 @@ namespace { if ( bvhA.nodes.empty() || bvhB.nodes.empty() ) return; outPairs.reserve(uf::physics::impl::settings.reserveCount); ::traverseNodePair(bvhA, 0, bvhB, 0, outPairs); + + ::postprocessPairs( outPairs ); } } @@ -681,13 +699,15 @@ namespace { if ( indexA == indexB ) continue; if ( indexA > indexB ) std::swap(indexA, indexB); - outPairs.emplace( indexA, indexB ); + outPairs.emplace_back( indexA, indexB ); } } } ++b; } } + + ::postprocessPairs( outPairs ); } void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ) { auto& nodesA = bvhA.flattened; @@ -701,34 +721,56 @@ namespace { if ( nodesA.empty() || nodesB.empty() ) return; outPairs.reserve(uf::physics::impl::settings.reserveCount); - for ( pod::BVH::index_t a = 0; a < nodesA.size(); ++a ) { - const auto& nodeA = nodesA[a]; - if ( nodeA.getCount() <= 0 || nodeA.isAsleep() ) continue; + static thread_local uf::stl::vector> stack; + stack.clear(); + stack.emplace_back(0, 0); - const auto& bA = boundsA[a]; + while ( !stack.empty() ) { + auto [a, b] = stack.back(); + stack.pop_back(); - pod::BVH::index_t b = 0; - while ( b < nodesB.size() ) { - const auto& nodeB = nodesB[b]; + const auto& nodeA = bvhA.flattened[a]; + const auto& nodeB = bvhB.flattened[b]; - if ( nodeB.isAsleep() || !::aabbOverlap(bA, boundsB[b]) ) { - b = nodeB.skipIndex; - continue; - } + if ( nodeA.isAsleep() && nodeB.isAsleep() ) continue; + if ( !::aabbOverlap( bvhA.flatBounds[a], bvhB.flatBounds[b] ) ) continue; - if ( nodeB.getCount() > 0 ) { - for ( pod::BVH::index_t ia = 0; ia < nodeA.getCount(); ++ia ) { - for ( pod::BVH::index_t ib = 0; ib < nodeB.getCount(); ++ib ) { - auto indexA = indicesA[nodeA.start + ia]; - auto indexB = indicesB[nodeB.start + ib]; + bool isLeafA = (nodeA.getCount() > 0); + bool isLeafB = (nodeB.getCount() > 0); - outPairs.emplace(indexA, indexB); - } + if ( isLeafA && isLeafB ) { + for ( pod::BVH::index_t ia = 0; ia < nodeA.getCount(); ++ia ) { + for ( pod::BVH::index_t ib = 0; ib < nodeB.getCount(); ++ib ) { + auto indexA = bvhA.indices[nodeA.start + ia]; + auto indexB = bvhB.indices[nodeB.start + ib]; + + // if ( indexA > indexB ) std::swap( indexA, indexB ); + outPairs.emplace_back(indexA, indexB); } } - ++b; + } + else if ( isLeafA ) { + pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex; + stack.emplace_back(a, b + 1); + stack.emplace_back(a, rightB); + } + else if ( isLeafB ) { + pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex; + stack.emplace_back(a + 1, b); + stack.emplace_back(rightA, b); + } + else { + pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex; + pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex; + + stack.emplace_back(a + 1, b + 1); + stack.emplace_back(a + 1, rightB); + stack.emplace_back(rightA, b + 1); + stack.emplace_back(rightA, rightB); } } + + ::postprocessPairs( outPairs ); } void queryFlatBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector& outIndices ) { @@ -836,10 +878,15 @@ namespace { pod::BVH::index_t root = unionizer.find(i); + auto [ it, inserted ] = rootToIsland.try_emplace( root, (pod::BVH::index_t) islands.size()); + if ( inserted ) islands.emplace_back(); + + /* if ( rootToIsland.find(root) == rootToIsland.end() ) { rootToIsland[root] = (pod::BVH::index_t) islands.size(); islands.emplace_back(); } + */ pod::BVH::index_t islandID = rootToIsland[root]; islands[islandID].indices.emplace_back( i ); @@ -857,7 +904,8 @@ namespace { pod::BVH::index_t root = unionizer.find(a); if ( rootToIsland.find(root) != rootToIsland.end() ) { pod::BVH::index_t islandID = rootToIsland[root]; - islands[islandID].pairs.emplace(a, b); + + islands[islandID].pairs.emplace_back(a, b); if ( bodies[a]->activity.awake || bodies[b]->activity.awake ) { ::wakeBody( *bodies[dynamicIndex] ); diff --git a/engine/src/utils/math/physics/integration.inl b/engine/src/utils/math/physics/integration.inl index 88d2017d..cbd115f1 100644 --- a/engine/src/utils/math/physics/integration.inl +++ b/engine/src/utils/math/physics/integration.inl @@ -147,8 +147,10 @@ namespace { } void mergeContacts( pod::Manifold& manifold ) { - uf::stl::vector result; + static thread_local uf::stl::vector result; + result.clear(); result.reserve(4); + for ( auto& c : manifold.points ) { bool merged = false; for ( auto& r : result ) { @@ -163,8 +165,6 @@ namespace { if ( !merged ) result.emplace_back( c ); } - // UF_MSG_DEBUG("Merged {} => {} contacts", manifold.points.size(), result.size()); - manifold.points = result; } diff --git a/engine/src/utils/math/physics/mesh.inl b/engine/src/utils/math/physics/mesh.inl index 58b9ee00..c8047f44 100644 --- a/engine/src/utils/math/physics/mesh.inl +++ b/engine/src/utils/math/physics/mesh.inl @@ -126,17 +126,26 @@ namespace { // compute overlaps between one BVH and another BVH static thread_local pod::BVH::pairs_t pairs; pairs.clear(); + + + //UF_TIMER_MULTITRACE_START("Colliding {} ({} indices) <=> {} ({} indices)", a.object->getName(), bvhA.indices.size(), b.object->getName(), bvhB.indices.size()); + //UF_TIMER_MULTITRACE("Querying overlaps..."); ::queryOverlaps( bvhA, bvhB, pairs ); + //UF_TIMER_MULTITRACE("Queried overlaps."); bool hit = false; // do collision per triangle + //UF_TIMER_MULTITRACE("Colliding triangles (pairs={})...", pairs.size()); for (auto [idA, idB] : pairs ) { auto tA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space auto tB = ::fetchTriangle( meshB, idB, b ); - if ( !::triangleTriangle( tA, tB, manifold, eps ) ) continue; + bool collides = ::triangleTriangle( tA, tB, manifold, eps ); + if ( !collides ) continue; hit = true; } + //UF_TIMER_MULTITRACE("Collided triangles."); + //UF_TIMER_MULTITRACE_END("Collided mesh."); return hit; } } \ No newline at end of file diff --git a/engine/src/utils/mesh/grid.cpp b/engine/src/utils/mesh/grid.cpp index f99d6138..710fe82b 100644 --- a/engine/src/utils/mesh/grid.cpp +++ b/engine/src/utils/mesh/grid.cpp @@ -14,6 +14,56 @@ namespace { std::min(std::max(0, (int)rel.z), divs.z - 1) }; } + + namespace { + // to-do: just use the physics copy of AABB + inline bool intersectTriangleAABB(const pod::Vector3f& v0, const pod::Vector3f& v1, const pod::Vector3f& v2, const pod::Vector3f& aabbMin, const pod::Vector3f& aabbMax) { + pod::Vector3f boxCenter = (aabbMin + aabbMax) * 0.5f; + pod::Vector3f boxExtents = (aabbMax - aabbMin) * 0.5f; + + pod::Vector3f tv0 = v0 - boxCenter; + pod::Vector3f tv1 = v1 - boxCenter; + pod::Vector3f tv2 = v2 - boxCenter; + + pod::Vector3f e0 = tv1 - tv0; + pod::Vector3f e1 = tv2 - tv1; + pod::Vector3f e2 = tv0 - tv2; + + pod::Vector3f triMin = uf::vector::min(uf::vector::min(tv0, tv1), tv2); + pod::Vector3f triMax = uf::vector::max(uf::vector::max(tv0, tv1), tv2); + if (triMin.x > boxExtents.x || triMax.x < -boxExtents.x) return false; + if (triMin.y > boxExtents.y || triMax.y < -boxExtents.y) return false; + if (triMin.z > boxExtents.z || triMax.z < -boxExtents.z) return false; + + pod::Vector3f normal = uf::vector::cross(e0, e1); + float planeD = uf::vector::dot(normal, tv0); + float r = boxExtents.x * std::abs(normal.x) + boxExtents.y * std::abs(normal.y) + boxExtents.z * std::abs(normal.z); + if (std::abs(planeD) > r) return false; + + float p0, p1, p2, rad; + auto testAxis = [&](const pod::Vector3f& axis) { + p0 = uf::vector::dot(tv0, axis); + p1 = uf::vector::dot(tv1, axis); + p2 = uf::vector::dot(tv2, axis); + rad = boxExtents.x * std::abs(axis.x) + boxExtents.y * std::abs(axis.y) + boxExtents.z * std::abs(axis.z); + return std::min({p0, p1, p2}) > rad || std::max({p0, p1, p2}) < -rad; + }; + + if (testAxis({0, -e0.z, e0.y})) return false; + if (testAxis({0, -e1.z, e1.y})) return false; + if (testAxis({0, -e2.z, e2.y})) return false; + + if (testAxis({e0.z, 0, -e0.x})) return false; + if (testAxis({e1.z, 0, -e1.x})) return false; + if (testAxis({e2.z, 0, -e2.x})) return false; + + if (testAxis({-e0.y, e0.x, 0})) return false; + if (testAxis({-e1.y, e1.x, 0})) return false; + if (testAxis({-e2.y, e2.x, 0})) return false; + + return true; + } + } } void uf::meshgrid::print( const uf::meshgrid::Grid& grid ) { @@ -54,7 +104,6 @@ void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, const pod::Vector3ui& di return calculate( grid, padding ); } void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, float padding ){ - // Pad bounding box grid.extents.min -= pod::Vector3f{ padding, padding, padding }; grid.extents.max += pod::Vector3f{ padding, padding, padding }; @@ -97,7 +146,6 @@ void uf::meshgrid::partition(uf::meshgrid::Grid& grid, for (size_t i = 0; i < iCount; i += 3) { Triangle tri{}; - // Read indices auto readIndex = [&](size_t offs) -> uint32_t { switch (iStride) { case 1: return *(const uint8_t*)(idxBase + iStride * (iFirst + offs)); @@ -109,24 +157,26 @@ void uf::meshgrid::partition(uf::meshgrid::Grid& grid, tri.indices[1] = readIndex(i+1); tri.indices[2] = readIndex(i+2); - // Read vertices for (int v = 0; v < 3; v++) { tri.verts[v] = *(const pod::Vector3f*)(vtxBase + pStride * (pFirst + tri.indices[v])); } - // Compute bounding cell range pod::Vector3f triMin = uf::vector::min(uf::vector::min(tri.verts[0], tri.verts[1]), tri.verts[2]); pod::Vector3f triMax = uf::vector::max(uf::vector::max(tri.verts[0], tri.verts[1]), tri.verts[2]); pod::Vector3ui minCell = ::cellCoords(triMin, grid.extents.min, grid.extents.piece, grid.divisions); pod::Vector3ui maxCell = ::cellCoords(triMax, grid.extents.min, grid.extents.piece, grid.divisions); - // Assign triangle to all overlapping cells for (uint32_t z = minCell.z; z <= maxCell.z; z++) { for (uint32_t y = minCell.y; y <= maxCell.y; y++) { for (uint32_t x = minCell.x; x <= maxCell.x; x++) { pod::Vector3ui cid{x,y,z}; auto& node = grid.nodes[cid]; + + if ( !intersectTriangleAABB(tri.verts[0], tri.verts[1], tri.verts[2], node.extents.min, node.extents.max) ) { + continue; + } + auto& meshlet = node.meshlets[primitive.instance.primitiveID]; meshlet.primitive = primitive; diff --git a/engine/src/utils/mesh/mesh.cpp b/engine/src/utils/mesh/mesh.cpp index 28d64c0d..253f9aba 100644 --- a/engine/src/utils/mesh/mesh.cpp +++ b/engine/src/utils/mesh/mesh.cpp @@ -363,7 +363,7 @@ std::string uf::Mesh::printIndirects( bool full ) const { return str.str(); } -uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector& wanted ) const { +uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector& wanted, size_t lod ) const { uf::Mesh::View view; view.vertex = vertex; view.index = index; @@ -378,15 +378,15 @@ uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector& wante } if ( !index.attributes.empty() ) { - view.attributes["index"] = { index.attributes.front() }; + view.attributes["index"] = { index.attributes[lod] }; } return view; } -uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector& wanted ) const { +uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector& wanted, size_t lod ) const { uf::Mesh::View view; - view.vertex = remapVertexInput(i); - view.index = remapIndexInput(i); + view.vertex = remapVertexInput(i, lod); + view.index = remapIndexInput(i, lod); view.indirectIndex = i; if ( wanted.size() ) { @@ -399,47 +399,47 @@ uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector uf::Mesh::makeViews( const uf::stl::vector& wanted ) const { +uf::stl::vector uf::Mesh::makeViews( const uf::stl::vector& wanted, size_t lod ) const { uf::stl::vector views; if ( indirect.count > 0 ) { - for ( auto i = 0; i < indirect.count; i++ ) views.emplace_back(makeView(i, wanted)); + for ( auto i = 0; i < indirect.count; i++ ) views.emplace_back(makeView(i, wanted, lod)); } else { - views.emplace_back( makeView(wanted) ); + views.emplace_back( makeView(wanted, lod) ); } return views; } -uf::Mesh::Input uf::Mesh::remapInput( const uf::Mesh::Input& input, size_t i ) const { +uf::Mesh::Input uf::Mesh::remapInput( const uf::Mesh::Input& input, size_t i, size_t lod ) const { uf::Mesh::Input res = input; UF_ASSERT( &input == &vertex || &input == &index ); UF_ASSERT( i < indirect.count ); - const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect).data())[i]; + const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i]; res.first = &input == &vertex ? drawCommand.vertexID : drawCommand.indexID; res.count = &input == &vertex ? drawCommand.vertices : drawCommand.indices; return res; } -uf::Mesh::Input uf::Mesh::remapVertexInput( size_t i ) const { +uf::Mesh::Input uf::Mesh::remapVertexInput( size_t i, size_t lod ) const { uf::Mesh::Input res = vertex; UF_ASSERT( i < indirect.count ); - const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect).data())[i]; + const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i]; res.first = drawCommand.vertexID; res.count = drawCommand.vertices; return res; } -uf::Mesh::Input uf::Mesh::remapIndexInput( size_t i ) const { +uf::Mesh::Input uf::Mesh::remapIndexInput( size_t i, size_t lod ) const { uf::Mesh::Input res = index; UF_ASSERT( i < indirect.count ); - const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect).data())[i]; + const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i]; res.first = drawCommand.indexID; res.count = drawCommand.indices; @@ -482,7 +482,13 @@ void uf::Mesh::_updateDescriptor( uf::Mesh::Input& input ) { auto& buffer = buffers[interleaved ? input.interleaved : attribute.buffer]; attribute.length = buffer.size(); attribute.pointer = buffer.data() + attribute.offset; - input.size += attribute.descriptor.size; + + if ( &input == &index || &input == &indirect ) { + input.size = attribute.descriptor.size; + } else { + input.size += attribute.descriptor.size; + } + if ( interleaved ) { attribute.pointer = static_cast(attribute.pointer) + attribute.descriptor.offset; } @@ -588,8 +594,8 @@ void uf::Mesh::_insertIs( uf::Mesh::Input& dstInput, const uf::Mesh& mesh, const // both meshes are de-interleaved, just copy directly } else if ( !isInterleaved(dstInput.interleaved) && !isInterleaved(srcInput.interleaved) ) { for ( auto i = 0; i < dstInput.attributes.size(); ++i ) { - auto& src = mesh.getBuffer( srcInput ); - auto& dst = getBuffer( dstInput ); + auto& src = mesh.getBuffer( srcInput, i ); + auto& dst = getBuffer( dstInput, i ); dst.insert( dst.end(), src.begin(), src.end() ); }