From bc406d5b629d19dec8a5bf272a344877cb3dc91a Mon Sep 17 00:00:00 2001 From: ecker Date: Wed, 13 Aug 2025 19:46:05 -0500 Subject: [PATCH] agony (added streaming in mesh data if it's out of range (but for the worldspawn only), re-enabled mesh slicing because I commented it out months ago and didn't remember why, some other things probably in the quest of getting this added) --- Makefile | 2 +- bin/data/config.json | 2 +- bin/data/entities/model.json | 7 + .../sourceengine/base_sourceengine.json | 4 +- bin/data/scenes/sourceengine/scene.json | 2 +- .../scenes/sourceengine/sourceengine.json | 1 + bin/data/scenes/sourceengine/test_grid.json | 15 ++ bin/data/shaders/graph/cull/comp.glsl | 2 + bin/exe/default/renderer | 2 +- engine/inc/uf/engine/graph/graph.h | 11 + engine/inc/uf/ext/opengl/buffer.h | 5 +- engine/inc/uf/ext/reactphysics/reactphysics.h | 6 +- engine/inc/uf/ext/zlib/zlib.h | 5 + engine/inc/uf/utils/io/file.h | 11 + engine/inc/uf/utils/mesh/grid.h | 55 ++++ engine/inc/uf/utils/mesh/mesh.h | 1 + engine/src/engine/graph/decode.cpp | 88 +++--- engine/src/engine/graph/graph.cpp | 254 +++++++++++++++--- engine/src/engine/object/behavior.cpp | 6 +- engine/src/engine/object/behaviors/graph.cpp | 13 +- engine/src/ext/gltf/processPrimitives.inl | 1 + engine/src/ext/opengl/graphic.cpp | 82 +++--- engine/src/ext/reactphysics/reactphysics.cpp | 247 +++++++++-------- engine/src/ext/vulkan/graphic.cpp | 4 +- engine/src/ext/zlib/zlib.cpp | 103 ++++++- engine/src/utils/io/file.cpp | 82 +++++- engine/src/utils/mesh/grid.cpp | 251 +++++------------ 27 files changed, 822 insertions(+), 440 deletions(-) create mode 100644 bin/data/scenes/sourceengine/test_grid.json diff --git a/Makefile b/Makefile index 9a89185e..2ad1fcfa 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ FLAGS += -DUF_DEV_ENV ifneq (,$(findstring win64,$(ARCH))) ifneq (,$(findstring -DUF_DEV_ENV,$(FLAGS))) REQ_DEPS += meshoptimizer toml xatlas curl ffx:fsr cpptrace vall_e # ncurses openvr draco discord bullet ultralight-ux - FLAGS += -march=native # -flto # -g + FLAGS += -march=native -g # -flto # -g endif 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 diff --git a/bin/data/config.json b/bin/data/config.json index 9cf8b1e5..c4846148 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -311,7 +311,7 @@ "debug draw": { "enabled": false, "line width": 8, - "layer": "", + "layer": "Gui", "rate": 0.0125 } }, diff --git a/bin/data/entities/model.json b/bin/data/entities/model.json index 6618cac5..19b4f82a 100644 --- a/bin/data/entities/model.json +++ b/bin/data/entities/model.json @@ -74,6 +74,13 @@ "lightmap": "auto", // "disable if lightmapped": false, "shadows": true + }, + "stream": { + "tag": "worldspawn", + "player": "info_player_spawn", + "enabled": "auto", + "radius": 32, + "every": 4 } } } diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index 330764df..f4af035a 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -11,12 +11,12 @@ // exact matches "worldspawn": { "physics": { "type": "mesh", "static": true }, - "grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true }, + "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true }, "optimize meshlets": { "simplify": 0.125, "print": false }, "unwrap mesh": true }, "worldspawn_skybox": { - "grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true }, + "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true }, "optimize meshlets": { "simplify": 0.125, "print": false }, "unwrap mesh": true }, diff --git a/bin/data/scenes/sourceengine/scene.json b/bin/data/scenes/sourceengine/scene.json index a27dd52d..1e49a4ec 100644 --- a/bin/data/scenes/sourceengine/scene.json +++ b/bin/data/scenes/sourceengine/scene.json @@ -6,7 +6,7 @@ ], "metadata": { "light": { - "fog": { + "fog-0": { // "color": [ 0.1, 0.1, 0.1 ], // "color": [ 0.2, 0.2, 0.2 ], "color": [ 0.3, 0.3, 0.3 ], diff --git a/bin/data/scenes/sourceengine/sourceengine.json b/bin/data/scenes/sourceengine/sourceengine.json index c1897bce..feac6dec 100644 --- a/bin/data/scenes/sourceengine/sourceengine.json +++ b/bin/data/scenes/sourceengine/sourceengine.json @@ -1,6 +1,7 @@ { // "import": "./rp_downtown_v2.json" "import": "./ss2_medsci1.json" +// "import": "./test_grid.json" // "import": "./sh2_mcdonalds.json" // "import": "./animal_crossing.json" // "import": "./mds_mcdonalds.json" diff --git a/bin/data/scenes/sourceengine/test_grid.json b/bin/data/scenes/sourceengine/test_grid.json new file mode 100644 index 00000000..a7845900 --- /dev/null +++ b/bin/data/scenes/sourceengine/test_grid.json @@ -0,0 +1,15 @@ +{ + "import": "./base_sourceengine.json", + "assets": [ + // { "filename": "./models/test_grid.glb" } + { "filename": "./models/test_grid/graph.json" } + ], + "metadata": { + "graph": { + "tags": { + "/^prop_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } }, + "/^func_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } } + } + } + } +} \ No newline at end of file diff --git a/bin/data/shaders/graph/cull/comp.glsl b/bin/data/shaders/graph/cull/comp.glsl index 0b174a2b..e312b560 100644 --- a/bin/data/shaders/graph/cull/comp.glsl +++ b/bin/data/shaders/graph/cull/comp.glsl @@ -83,6 +83,8 @@ bool frustumCull( uint id ) { const DrawCommand drawCommand = drawCommands[id]; const Instance instance = instances[drawCommand.instanceID]; + if ( drawCommand.indices == 0 || drawCommand.vertices == 0 ) return false; + bool visible = false; for ( uint pass = 0; pass < PushConstant.passes; ++pass ) { #if 0 diff --git a/bin/exe/default/renderer b/bin/exe/default/renderer index 53395cff..91caa7c1 100644 --- a/bin/exe/default/renderer +++ b/bin/exe/default/renderer @@ -1 +1 @@ -vulkan \ No newline at end of file +opengl \ 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 358de2cc..cb0327a6 100644 --- a/engine/inc/uf/engine/graph/graph.h +++ b/engine/inc/uf/engine/graph/graph.h @@ -59,6 +59,17 @@ namespace pod { uf::stl::string target = ""; } animations; + struct { + bool enabled = false; + float radius = 64.0f; + float every = 4.0f; + + uf::stl::string tag = "worldspawn"; + uf::stl::string player = "info_player_spawn"; + + size_t hash = 0; + float lastUpdate = 0; + } stream; } settings; // Local storage, used for save/load diff --git a/engine/inc/uf/ext/opengl/buffer.h b/engine/inc/uf/ext/opengl/buffer.h index 0c9ffdf3..d1312fbb 100644 --- a/engine/inc/uf/ext/opengl/buffer.h +++ b/engine/inc/uf/ext/opengl/buffer.h @@ -85,14 +85,17 @@ namespace ext { template inline size_t initializeBuffer( const T& data, GLenum usage, bool alias = false ) { return initializeBuffer( (const void*) &data, static_cast(sizeof(T)), usage, alias ); } inline bool updateBuffer( const void* data, GLsizeiptr length, size_t index = 0, bool alias = false ) const { return updateBuffer( data, length, buffers.at(index), alias ); } + /* + inline bool updateBuffer( const void* data, GLsizeiptr length, size_t index = 0, bool alias = false ) const { return updateBuffer( data, length, buffers.at(index), alias ); } inline bool updateBuffer( void* data, GLsizeiptr length, size_t index = 0, bool alias = false ) const { return updateBuffer( (const void*) data, length, index, alias ); } inline bool updateBuffer( void* data, GLsizeiptr length, const Buffer& buffer, bool alias = false ) const { return updateBuffer( (const void*) data, length, buffer, alias ); } - + template inline bool updateBuffer( const T& data, size_t index = 0, bool alias = false ) const { return updateBuffer( (const void*) &data, static_cast(sizeof(T)), index, alias ); } template inline bool updateBuffer( const T& data, GLsizeiptr length, size_t index = 0, bool alias = false ) const { return updateBuffer( (const void*) &data, length, index, alias ); } template inline bool updateBuffer( const T& data, const Buffer& buffer, bool alias = false ) const { return updateBuffer( (const void*) &data, static_cast(sizeof(T)), buffer, alias ); } template inline bool updateBuffer( const T& data, GLsizeiptr length, const Buffer& buffer, bool alias = false ) const { return updateBuffer( (const void*) &data, length, buffer, alias ); } + */ }; } } \ No newline at end of file diff --git a/engine/inc/uf/ext/reactphysics/reactphysics.h b/engine/inc/uf/ext/reactphysics/reactphysics.h index c10be9c5..4da21d17 100644 --- a/engine/inc/uf/ext/reactphysics/reactphysics.h +++ b/engine/inc/uf/ext/reactphysics/reactphysics.h @@ -20,6 +20,7 @@ namespace pod { bool shared = false; // share control of the transform both in-engine and bullet, set to true if you're directly modifying the transform rp3d::RigidBody* body = NULL; rp3d::CollisionShape* shape = NULL; + rp3d::Collider* colliders = NULL; rp3d::PhysicsWorld* world = NULL; @@ -100,12 +101,15 @@ namespace ext { void UF_API detach( pod::PhysicsState& ); // collider for mesh (static or dynamic) - pod::PhysicsState& create( uf::Object&, const uf::Mesh&, bool ); + pod::PhysicsState& UF_API create( uf::Object&, const uf::Mesh&, bool ); // collider for boundingbox pod::PhysicsState& UF_API create( uf::Object&, const pod::Vector3f& ); // collider for capsule pod::PhysicsState& UF_API create( uf::Object&, float, float ); + // update mesh + void UF_API update( pod::PhysicsState&, const uf::Mesh&, bool ); + // synchronize engine transforms to bullet transforms void UF_API syncTo( ext::reactphysics::WorldState& ); // synchronize bullet transforms to engine transforms diff --git a/engine/inc/uf/ext/zlib/zlib.h b/engine/inc/uf/ext/zlib/zlib.h index 347fb07d..c6065b35 100644 --- a/engine/inc/uf/ext/zlib/zlib.h +++ b/engine/inc/uf/ext/zlib/zlib.h @@ -5,6 +5,7 @@ #include #include +#include namespace ext { namespace zlib { @@ -26,7 +27,11 @@ namespace ext { memcpy( vector.data(), compressed.data(), compressed.size() ); } */ + uf::stl::vector UF_API decompressFromFile( const uf::stl::string& ); + uf::stl::vector UF_API decompressFromFile( const uf::stl::string& filename, size_t start, size_t len ); + uf::stl::vector UF_API decompressFromFile( const uf::stl::string& filename, const uf::stl::vector& ranges ); + size_t UF_API compressToFile( const uf::stl::string&, const void*, size_t ); } } diff --git a/engine/inc/uf/utils/io/file.h b/engine/inc/uf/utils/io/file.h index 7fbee3a9..65d5737a 100644 --- a/engine/inc/uf/utils/io/file.h +++ b/engine/inc/uf/utils/io/file.h @@ -5,6 +5,13 @@ #include #include +namespace pod { + struct Range { + size_t start; + size_t len; + }; +} + namespace uf { namespace io { extern UF_API const uf::stl::string root; @@ -20,6 +27,8 @@ namespace uf { uf::stl::string UF_API readAsString( const uf::stl::string&, const uf::stl::string& = "" ); uf::stl::vector UF_API readAsBuffer( const uf::stl::string&, const uf::stl::string& = "" ); + uf::stl::vector UF_API readAsBuffer( const uf::stl::string&, size_t start, size_t len, const uf::stl::string& = "" ); + uf::stl::vector UF_API readAsBuffer( const uf::stl::string&, const uf::stl::vector& ranges, const uf::stl::string& = "" ); size_t UF_API write( const uf::stl::string& filename, const void*, size_t = SIZE_MAX ); template inline size_t write( const uf::stl::string& filename, const uf::stl::vector& buffer, size_t size = SIZE_MAX ) { @@ -30,6 +39,8 @@ namespace uf { } uf::stl::vector UF_API decompress( const uf::stl::string& ); + uf::stl::vector UF_API decompress( const uf::stl::string&, size_t, size_t ); + uf::stl::vector UF_API decompress( const uf::stl::string&, const uf::stl::vector& ); size_t UF_API compress( const uf::stl::string&, const void*, size_t = SIZE_MAX ); template inline size_t compress( const uf::stl::string& filename, const uf::stl::vector& buffer, size_t size = SIZE_MAX ) { diff --git a/engine/inc/uf/utils/mesh/grid.h b/engine/inc/uf/utils/mesh/grid.h index 3332f584..ddc34d2c 100644 --- a/engine/inc/uf/utils/mesh/grid.h +++ b/engine/inc/uf/utils/mesh/grid.h @@ -83,6 +83,53 @@ namespace uf { uf::meshgrid::calculate( grid, eps ); + // it's better to naively clip the mesh multiple times rather than calculate the triangles needed to clip + if ( clip ) { + for ( auto& pair : grid.nodes ) { + ++atlasID; + for ( auto& meshlet : meshlets ) { + auto& node = pair.second; + + uf::stl::vector vertices = meshlet.vertices; + uf::stl::vector indices = meshlet.indices; + + uf::shapes::clip( vertices, indices, node.extents.min, node.extents.max ); + + if ( vertices.empty() || indices.empty() ) continue; + + size_t primitiveID = partitioned.size(); + auto& slice = partitioned.emplace_back(); + + slice.vertices = std::move( vertices ); + slice.indices = std::move( indices ); + + for ( auto& vertex : slice.vertices ) { + vertex.id.x = primitiveID; + vertex.id.y = meshlet.primitive.instance.meshID; + } + + slice.primitive.instance = meshlet.primitive.instance; + slice.primitive.instance.materialID = meshlet.primitive.instance.materialID; + slice.primitive.instance.primitiveID = primitiveID; + slice.primitive.instance.meshID = meshlet.primitive.instance.meshID; + slice.primitive.instance.objectID = 0; + slice.primitive.instance.auxID = atlasID; + slice.primitive.instance.bounds.min = node.extents.min; + slice.primitive.instance.bounds.max = node.extents.max; + + slice.primitive.drawCommand.indices = slice.indices.size(); + slice.primitive.drawCommand.instances = 1; + slice.primitive.drawCommand.indexID = 0; + slice.primitive.drawCommand.vertexID = 0; + slice.primitive.drawCommand.instanceID = 0; + slice.primitive.drawCommand.auxID = atlasID; // meshlet.primitive.instance.meshID; + slice.primitive.drawCommand.vertices = slice.vertices.size(); + } + } + + return partitioned; + } + for ( auto& meshlet : meshlets ) uf::meshgrid::partition( grid, meshlet.vertices, meshlet.indices, meshlet.primitive ); if ( cleanup ) uf::meshgrid::cleanup( grid ); @@ -106,6 +153,14 @@ namespace uf { vertex.id.y = meshlet.primitive.instance.meshID; } + /* + if ( clip ) { + node.effectiveExtents.min = node.extents.min; + node.effectiveExtents.max = node.extents.max; + uf::shapes::clip( slice.vertices, slice.indices, node.extents.min, node.extents.max ); + } + */ + slice.primitive.instance = meshlet.primitive.instance; slice.primitive.instance.materialID = meshlet.primitive.instance.materialID; slice.primitive.instance.primitiveID = primitiveID; diff --git a/engine/inc/uf/utils/mesh/mesh.h b/engine/inc/uf/utils/mesh/mesh.h index b47fb01c..1acde79a 100644 --- a/engine/inc/uf/utils/mesh/mesh.h +++ b/engine/inc/uf/utils/mesh/mesh.h @@ -158,6 +158,7 @@ namespace uf { } bounds; */ uf::stl::vector buffers; + uf::stl::vector buffer_paths; // crunge protected: void _destroy( uf::Mesh::Input& input ); void _bind( bool interleaved = uf::Mesh::defaultInterleaved ); diff --git a/engine/src/engine/graph/decode.cpp b/engine/src/engine/graph/decode.cpp index d9192a27..6ea894f4 100644 --- a/engine/src/engine/graph/decode.cpp +++ b/engine/src/engine/graph/decode.cpp @@ -237,9 +237,28 @@ namespace { ext::json::forEach( json["buffers"], [&]( ext::json::Value& value ){ const uf::stl::string filename = value.as(); const uf::stl::string directory = uf::io::directory( graph.name ); - auto& buffer = mesh.buffers.emplace_back(uf::io::readAsBuffer( directory + "/" + filename )); + // uf::io::readAsBuffer( directory + "/" + filename ) + if ( graph.metadata["stream"]["enabled"].as() ) { + mesh.buffers.emplace_back(); + mesh.buffer_paths.emplace_back(directory + "/" + filename ); + } else { + // to-do: make it work for interleaved meshes + mesh.buffers.emplace_back(uf::io::readAsBuffer( directory + "/" + filename )); + } }); + // load non vertex/index buffers + for ( size_t i = 0; i < mesh.instance.attributes.size(); ++i ) { + auto& attribute = mesh.instance.attributes[i]; + if ( !mesh.buffers[attribute.buffer].empty() ) continue; + mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] ); + } + for ( size_t i = 0; i < mesh.indirect.attributes.size(); ++i ) { + auto& attribute = mesh.indirect.attributes[i]; + if ( !mesh.buffers[attribute.buffer].empty() ) continue; + mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] ); + } + #if UF_ENV_DREAMCAST // remove extraneous buffers if ( graph.metadata["renderer"]["separate"].as() ) { @@ -265,7 +284,7 @@ namespace { #endif mesh.updateDescriptor(); - + #if 0 if ( graph.metadata["renderer"]["separate"].as() ) { #if UF_ENV_DREAMCAST && GL_QUANTIZED_SHORT mesh.convert(); @@ -287,43 +306,6 @@ namespace { #endif mesh.updateDescriptor(); } - - #if 0 - // swap winding order - if ( graph.metadata["decode"]["invert winding order"].as() ) { - if ( mesh.index.count ) { - uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front(); - size_t tri[3]; - for ( size_t i = 0; i < mesh.index.count / 3; ++i ) { - switch ( mesh.index.size ) { - case sizeof(uint8_t): { - uint8_t* pointer = (uint8_t*) (indexAttribute.pointer); - tri[0] = pointer[i * 3 + 0]; - tri[2] = pointer[i * 3 + 2]; - - pointer[i * 3 + 0] = tri[2]; - pointer[i * 3 + 2] = tri[0]; - } break; - case sizeof(uint16_t): { - uint16_t* pointer = (uint16_t*) (indexAttribute.pointer); - tri[0] = pointer[i * 3 + 0]; - tri[2] = pointer[i * 3 + 2]; - - pointer[i * 3 + 0] = tri[2]; - pointer[i * 3 + 2] = tri[0]; - } break; - case sizeof(uint32_t): { - uint32_t* pointer = (uint32_t*) (indexAttribute.pointer); - tri[0] = pointer[i * 3 + 0]; - tri[2] = pointer[i * 3 + 2]; - - pointer[i * 3 + 0] = tri[2]; - pointer[i * 3 + 2] = tri[0]; - } break; - } - } - } - } #endif return mesh; @@ -390,6 +372,34 @@ pod::Graph uf::graph::load( const uf::stl::string& filename, const uf::Serialize #endif } + // failsafes + if ( graph.metadata["stream"]["enabled"].is() && graph.metadata["stream"]["enabled"].as() == "auto" ) { + #if UF_ENV_DREAMCAST + graph.metadata["stream"]["enabled"] = true; + #else + graph.metadata["stream"]["enabled"] = false; + #endif + } + if ( graph.metadata["stream"]["enabled"].as() && graph.metadata["stream"]["radius"].as(0) <= 0.0f ) { + graph.metadata["stream"]["enabled"] = false; + } + if ( !graph.metadata["stream"]["enabled"].as() ) { + graph.metadata["stream"]["radius"] = 0.0f; + } + + // copy important settings + { + graph.settings.stream.enabled = graph.metadata["stream"]["enabled"].as(graph.settings.stream.enabled); + graph.settings.stream.radius = graph.metadata["stream"]["radius"].as(graph.settings.stream.radius); + graph.settings.stream.every = graph.metadata["stream"]["every"].as(graph.settings.stream.every); + + graph.settings.stream.tag = graph.metadata["stream"]["tag"].as(graph.settings.stream.tag); + graph.settings.stream.player = graph.metadata["stream"]["player"].as(graph.settings.stream.player); + + 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); + } + uf::stl::string key = graph.metadata["key"].as(""); if ( key != "" ) { key += ":"; diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 20cd65a4..df352614 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -23,8 +23,20 @@ #define UF_DEBUG_TIMER_MULTITRACE_END(...) #endif +#define UF_GRAPH_SPARSE_READ_MESH 1 + namespace { bool newGraphAdded = true; + + // todo: shove it into the "std"lib + inline uint64_t fnv1aHash(const uf::stl::vector& bits) { + uint64_t hash = 1469598103934665603ULL; + for (bool b : bits) { + hash ^= static_cast(b); + hash *= 1099511628211ULL; + } + return hash; + } } #if UF_ENV_DREAMCAST @@ -1424,6 +1436,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) bounds.min = uf::vector::min( bounds.min, instance.bounds.min ); bounds.max = uf::vector::max( bounds.max, instance.bounds.max ); + primitive.drawCommand.instanceID = instanceID; if ( mesh.indirect.count && mesh.indirect.count <= primitives.size() ) { auto& attribute = mesh.indirect.attributes.front(); auto& buffer = mesh.buffers[mesh.isInterleaved(mesh.indirect.interleaved) ? mesh.indirect.interleaved : attribute.buffer]; @@ -1541,6 +1554,12 @@ void uf::graph::update( pod::Graph& graph, float delta ) { auto& scene = uf::scene::getCurrentScene(); auto& storage = uf::graph::globalStorage ? uf::graph::storage : scene.getComponent(); + // get last update time + if ( graph.settings.stream.enabled && graph.settings.stream.hash != 0 && uf::physics::time::current - graph.settings.stream.lastUpdate > graph.settings.stream.every ) { + graph.settings.stream.lastUpdate = uf::physics::time::current; + uf::graph::reload( graph ); + } + // update our instances #if !UF_ENV_DREAMCAST // UF_TIMER_MULTITRACE_START("Tick Start"); @@ -1870,45 +1889,212 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) { ext::json::Value tag = ext::json::find( node.name, graph.metadata["tags"] ); - if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) { - auto model = uf::transform::model( transform ); - auto& mesh = storage.meshes.map[graph.meshes[node.mesh]]; + pod::Vector3f controllerPosition; + auto& controller = scene.getController(); + if ( controller.getName() != "Scene" ) { + auto& controllerTransform = controller.getComponent>(); + controllerPosition = controllerTransform.position; + } else { + // find info_player_spawn + // to-do: deduce the node via tag that attaches the player + for ( auto& node : graph.nodes ) { + if ( node.name != graph.settings.stream.player ) continue; + auto& controllerTransform = node.entity->getComponent>(); + controllerPosition = controllerTransform.position; + break; + } + } - #if 0 - if ( (graph.metadata["renderer"]["separate"].as()) && graph.metadata["renderer"]["render"].as() ) { - #endif - if ( graph.metadata["renderer"]["render"].as() ) { - bool exists = entity.hasComponent(); - if ( exists ) { - auto& graphic = entity.getComponent(); - graphic.updateMesh( mesh ); - } else { - uf::graph::initializeGraphics( graph, entity, mesh ); + auto model = uf::transform::model( transform ); + auto& mesh = storage.meshes.map[graph.meshes[node.mesh]]; + auto& primitives = storage.primitives.map[graph.primitives[node.mesh]]; + + float radius = graph.settings.stream.radius; + float radiusSquared = radius * radius; + + // disable if not tagged for streaming + // to-do: check tag + if ( node.name != graph.settings.stream.tag ) { + radius = 0; + } + + if ( radius > 0 && mesh.indirect.count && mesh.indirect.count <= primitives.size() ) { + // deduce draw command (indirect) buffer to write to + auto& attribute = mesh.indirect.attributes.front(); + auto& buffer = mesh.buffers[mesh.isInterleaved(mesh.indirect.interleaved) ? mesh.indirect.interleaved : attribute.buffer]; + pod::DrawCommand* drawCommands = (pod::DrawCommand*) buffer.data(); + // queues + uf::stl::unordered_map> ranges; + uf::stl::vector queuedDrawIDs( primitives.size(), false ); // this is to maintain draw command order because apparently my code requires draw commands to stay in order + // fallbacks for when no draw calls are requested (mainly for the collision mesh) + float closestDistance = std::numeric_limits::max(); + size_t closestDrawID = 0; + bool found = false; + + // iterate through meshlets and cull if out of radius + for ( size_t drawID = 0; drawID < primitives.size(); ++drawID ) { + auto& primitive = primitives[drawID]; + auto& instance = primitive.instance; + auto& drawCommand = primitive.drawCommand; + + pod::Vector3f center = uf::matrix::multiply( model, (instance.bounds.max + instance.bounds.min) * 0.5f, 1.0f ); // transform the center of the draw call + float distanceSquared = uf::vector::distanceSquared( center, controllerPosition ); // saves a sqrt() + + // store closest draw call + if ( distanceSquared < closestDistance ) { + closestDistance = distanceSquared; + closestDrawID = drawID; + } + // queue if we're within the radius + if ( (queuedDrawIDs[drawID] = distanceSquared <= radiusSquared) ) { + found = true; } } - { - auto phyziks = tag["physics"]; - if ( !ext::json::isObject( phyziks ) ) phyziks = metadataJson["physics"]; - else metadataJson["physics"] = phyziks; - - if ( ext::json::isObject( phyziks ) ) { - uf::stl::string type = phyziks["type"].as(); - if ( type == "mesh" ) { - bool exists = entity.hasComponent(); - if ( exists ) { - uf::physics::terminate( entity ); - // entity.deleteComponent(); - } - auto& collider = entity.getComponent(); - collider.stats.mass = phyziks["mass"].as(collider.stats.mass); - collider.stats.friction = phyziks["friction"].as(collider.stats.friction); - collider.stats.restitution = phyziks["restitution"].as(collider.stats.restitution); - collider.stats.inertia = uf::vector::decode( phyziks["inertia"], collider.stats.inertia ); - collider.stats.gravity = uf::vector::decode( phyziks["gravity"], collider.stats.gravity ); - - uf::physics::impl::create( entity, mesh, !phyziks["static"].as(true) ); + // insert closest primitive if all are out of range (because of cringe logic) + if ( !found ) { + queuedDrawIDs[closestDrawID] = true; + } + + // bail if no update is detected + uint64_t drawCommandHash = ::fnv1aHash(queuedDrawIDs); + if ( drawCommandHash == graph.settings.stream.hash ) { + return; + } + graph.settings.stream.hash = drawCommandHash; + graph.settings.stream.lastUpdate = uf::physics::time::current; + // read from disk + #if UF_GRAPH_SPARSE_READ_MESH + // reset counts + mesh.vertex.count = 0; + mesh.index.count = 0; + + for (size_t drawID = 0; drawID < queuedDrawIDs.size(); ++drawID) { + bool queued = queuedDrawIDs[drawID]; + auto& primitive = primitives[drawID]; + auto& drawCommand = drawCommands[drawID]; + + // disable draw call + if ( !queued ) { + drawCommand.instances = 0; + drawCommand.vertices = 0; + drawCommand.indices = 0; + drawCommand.vertexID = 0; + drawCommand.indexID = 0; + continue; + } + + // queue up ranges to read from disk + for (auto& attribute : mesh.index.attributes) { + auto size = attribute.descriptor.size; + ranges[attribute.buffer].emplace_back(pod::Range{ + primitive.drawCommand.indexID * size, + primitive.drawCommand.indices * size, + }); + } + for (auto& attribute : mesh.vertex.attributes) { + auto size = attribute.descriptor.size; + ranges[attribute.buffer].emplace_back(pod::Range{ + primitive.drawCommand.vertexID * size, + primitive.drawCommand.vertices * size, + }); + } + + // reset draw call and remap + drawCommand = primitive.drawCommand; + drawCommand.vertexID = mesh.vertex.count; + drawCommand.indexID = mesh.index.count; + // increment remap indices + mesh.vertex.count += drawCommand.vertices; + mesh.index.count += drawCommand.indices; + } + + // load mesh data + for ( auto& attribute : mesh.index.attributes ) { + if ( ranges.count(attribute.buffer) <= 0 ) { + mesh.buffers[attribute.buffer].clear(); + } else { + mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer], ranges[attribute.buffer] ); + } + } + for ( auto& attribute : mesh.vertex.attributes ) { + if ( ranges.count(attribute.buffer) <= 0 ) { + mesh.buffers[attribute.buffer].clear(); + } else { + mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer], ranges[attribute.buffer] ); + } + } + // keep the vertex data intact + #else + // disable remaining draw commands + for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) { + bool queued = queuedDrawIDs[drawID]; + if ( !queued ) { + drawCommands[drawID].instances = 0; + drawCommands[drawID].vertices = 0; + drawCommands[drawID].indices = 0; + drawCommands[drawID].indexID = 0; + drawCommands[drawID].vertexID = 0; + } else { + drawCommands[drawID] = primitives[drawID].drawCommand; + } + } + + // load mesh data + for ( auto& attribute : mesh.index.attributes ) { + mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] ); + } + for ( auto& attribute : mesh.vertex.attributes ) { + mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] ); + } + #endif + } else { + // load mesh data + for ( auto& attribute : mesh.index.attributes ) { + if ( mesh.buffers[attribute.buffer].empty() ) mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] ); + } + for ( auto& attribute : mesh.vertex.attributes ) { + if ( mesh.buffers[attribute.buffer].empty() ) mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] ); + } + } + + mesh.updateDescriptor(); + + // update graphic +#if 0 + if ( (graph.metadata["renderer"]["separate"].as()) && graph.metadata["renderer"]["render"].as() ) { +#endif + if ( graph.metadata["renderer"]["render"].as() ) { + bool exists = entity.hasComponent(); + if ( exists ) { + auto& graphic = entity.getComponent(); + graphic.updateMesh( mesh ); + } else { + uf::graph::initializeGraphics( graph, entity, mesh ); + } + } + // bind mesh to physics state + { + auto phyziks = tag["physics"]; + if ( !ext::json::isObject( phyziks ) ) phyziks = metadataJson["physics"]; + else metadataJson["physics"] = phyziks; + + if ( ext::json::isObject( phyziks ) ) { + uf::stl::string type = phyziks["type"].as(); + if ( type == "mesh" ) { + bool exists = entity.hasComponent(); + if ( exists ) { + uf::physics::impl::destroy( entity ); } + + auto& collider = entity.getComponent(); + collider.stats.mass = phyziks["mass"].as(collider.stats.mass); + collider.stats.friction = phyziks["friction"].as(collider.stats.friction); + collider.stats.restitution = phyziks["restitution"].as(collider.stats.restitution); + collider.stats.inertia = uf::vector::decode( phyziks["inertia"], collider.stats.inertia ); + collider.stats.gravity = uf::vector::decode( phyziks["gravity"], collider.stats.gravity ); + + uf::physics::impl::create( entity, mesh, !phyziks["static"].as(true) ); } } } diff --git a/engine/src/engine/object/behavior.cpp b/engine/src/engine/object/behavior.cpp index 4b709adb..e73d1169 100644 --- a/engine/src/engine/object/behavior.cpp +++ b/engine/src/engine/object/behavior.cpp @@ -196,9 +196,9 @@ void uf::ObjectBehavior::destroy( uf::Object& self ) { // this->deleteComponent(); } if ( this->hasComponent() ) { - // auto& collider = this->getComponent(); - // uf::physics::detach( collider ); - uf::physics::terminate( *this ); + auto& collider = this->getComponent(); + uf::physics::impl::detach( collider ); + // this->deleteComponent(); } if ( this->hasComponent() ) { auto& renderMode = this->getComponent(); diff --git a/engine/src/engine/object/behaviors/graph.cpp b/engine/src/engine/object/behaviors/graph.cpp index a45fff82..ee4d32a3 100644 --- a/engine/src/engine/object/behaviors/graph.cpp +++ b/engine/src/engine/object/behaviors/graph.cpp @@ -12,9 +12,10 @@ #include #include #include +#include UF_BEHAVIOR_REGISTER_CPP(uf::GraphBehavior) -UF_BEHAVIOR_TRAITS_CPP(uf::GraphBehavior, ticks = false, renders = false, multithread = false) +UF_BEHAVIOR_TRAITS_CPP(uf::GraphBehavior, ticks = true, renders = false, multithread = false) #define this (&self) void uf::GraphBehavior::initialize( uf::Object& self ) { auto& metadata = this->getComponent(); @@ -66,7 +67,15 @@ void uf::GraphBehavior::initialize( uf::Object& self ) { }); } void uf::GraphBehavior::destroy( uf::Object& self ) {} -void uf::GraphBehavior::tick( uf::Object& self ) {} +void uf::GraphBehavior::tick( uf::Object& self ) { + /* Test */ { + TIMER(1, uf::inputs::kbm::states::T ) { + UF_MSG_DEBUG("Regenerating graph graphics..."); + auto& graph = this->getComponent(); + uf::graph::reload( graph ); + } + } +} void uf::GraphBehavior::render( uf::Object& self ) {} void uf::GraphBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ) {} void uf::GraphBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ) {} diff --git a/engine/src/ext/gltf/processPrimitives.inl b/engine/src/ext/gltf/processPrimitives.inl index 1cded6d5..246a896c 100644 --- a/engine/src/ext/gltf/processPrimitives.inl +++ b/engine/src/ext/gltf/processPrimitives.inl @@ -378,6 +378,7 @@ if ( meshopt.should ) { mesh.bindIndirect(); mesh.bind(false); // default to de-interleaved regardless of requirement (makes things easier) + UF_MSG_DEBUG("{}: {}", keyName, meshlets.size() ); for ( auto& meshlet : meshlets ) { auto& drawCommand = drawCommands.emplace_back(pod::DrawCommand{ .indices = meshlet.indices.size(), diff --git a/engine/src/ext/opengl/graphic.cpp b/engine/src/ext/opengl/graphic.cpp index b7e6f93d..ed45c3b6 100644 --- a/engine/src/ext/opengl/graphic.cpp +++ b/engine/src/ext/opengl/graphic.cpp @@ -241,28 +241,29 @@ void ext::opengl::Graphic::initializeMesh( uf::Mesh& mesh, bool buffer ) { GLenum usage; }; uf::stl::vector queue; - descriptor.inputs.bufferOffset = buffers.empty() ? 0 : buffers.size() - 1; + descriptor.inputs.bufferOffset = buffers.size(); // buffers.empty() ? 0 : buffers.size() - 1; - #define PARSE_ATTRIBUTE(i, usage) {\ - auto& buffer = mesh.buffers[i];\ - if ( queue.size() <= i ) queue.resize( i );\ - if ( !buffer.empty() ) queue.emplace_back(Queue{ (void*) buffer.data(), buffer.size(), usage });\ + #define PARSE_INPUT_INITIALIZE(NAME, USAGE){\ + if ( mesh.isInterleaved( mesh.NAME.interleaved ) ) {\ + auto& buffer = mesh.buffers[mesh.NAME.interleaved];\ + if ( !buffer.empty() ) {\ + descriptor.inputs.NAME.interleaved = initializeBuffer( (const void*) buffer.data(), buffer.size(), USAGE, alias );\ + this->metadata.buffers[#NAME] = descriptor.inputs.NAME.interleaved;\ + } else mesh.NAME.interleaved = -1;\ + } else for ( size_t i = 0; i < descriptor.inputs.NAME.attributes.size(); ++i ) {\ + auto& attribute = descriptor.inputs.NAME.attributes[i];\ + auto& buffer = mesh.buffers[attribute.buffer];\ + if ( !buffer.empty() ) {\ + attribute.buffer = initializeBuffer( (const void*) buffer.data(), buffer.size(), USAGE, alias );\ + this->metadata.buffers[#NAME"["+attribute.descriptor.name+"]"] = attribute.buffer;\ + } else attribute.buffer = -1;\ + }\ } - #define PARSE_INPUT(name, usage){\ - if ( mesh.isInterleaved( mesh.name.interleaved ) ) PARSE_ATTRIBUTE(descriptor.inputs.name.interleaved, usage)\ - else for ( auto& attribute : descriptor.inputs.name.attributes ) PARSE_ATTRIBUTE(attribute.buffer, usage)\ - } - - PARSE_INPUT(vertex, uf::renderer::enums::Buffer::VERTEX) - PARSE_INPUT(index, uf::renderer::enums::Buffer::INDEX) - PARSE_INPUT(instance, uf::renderer::enums::Buffer::VERTEX) - PARSE_INPUT(indirect, uf::renderer::enums::Buffer::INDIRECT) - // allocate buffers - for ( auto i = 0; i < queue.size(); ++i ) { - auto& q = queue[i]; - initializeBuffer( q.data, q.size, q.usage, alias ); - } + PARSE_INPUT_INITIALIZE(vertex, uf::renderer::enums::Buffer::VERTEX ) + PARSE_INPUT_INITIALIZE(index, uf::renderer::enums::Buffer::INDEX ) + PARSE_INPUT_INITIALIZE(instance, uf::renderer::enums::Buffer::VERTEX ) + PARSE_INPUT_INITIALIZE(indirect, uf::renderer::enums::Buffer::INDIRECT ) } if ( mesh.instance.count == 0 && mesh.instance.attributes.empty() ) { @@ -292,27 +293,32 @@ bool ext::opengl::Graphic::updateMesh( uf::Mesh& mesh ) { }; uf::stl::vector queue; - #define PARSE_ATTRIBUTE(i, usage) {\ - auto& buffer = mesh.buffers[i];\ - if ( queue.size() <= i ) queue.resize( i );\ - if ( !buffer.empty() ) queue.emplace_back(Queue{ (void*) buffer.data(), buffer.size(), usage });\ - } - #define PARSE_INPUT(name, usage){\ - if ( mesh.isInterleaved( mesh.name.interleaved ) ) PARSE_ATTRIBUTE(descriptor.inputs.name.interleaved, usage)\ - else for ( auto& attribute : descriptor.inputs.name.attributes ) PARSE_ATTRIBUTE(attribute.buffer, usage)\ + #define PARSE_INPUT_UPDATE(NAME, USAGE){\ + if ( mesh.isInterleaved( mesh.NAME.interleaved ) ) {\ + auto& buffer = mesh.buffers[mesh.NAME.interleaved];\ + if ( !buffer.empty() ) {\ + rebuild |= updateBuffer( (const void*) buffer.data(), buffer.size(), this->metadata.buffers[#NAME] );\ + } else mesh.NAME.interleaved = -1;\ + } else for ( size_t i = 0; i < descriptor.inputs.NAME.attributes.size(); ++i ) {\ + auto& attribute = descriptor.inputs.NAME.attributes[i];\ + auto& buffer = mesh.buffers[attribute.buffer];\ + if ( !buffer.empty() ) {\ + rebuild |= updateBuffer( (const void*) buffer.data(), buffer.size(), this->metadata.buffers[#NAME"["+attribute.descriptor.name+"]"] );\ + } else attribute.buffer = -1;\ + }\ } - PARSE_INPUT(vertex, uf::renderer::enums::Buffer::VERTEX) - PARSE_INPUT(index, uf::renderer::enums::Buffer::INDEX) - PARSE_INPUT(instance, uf::renderer::enums::Buffer::VERTEX) - PARSE_INPUT(indirect, uf::renderer::enums::Buffer::INDIRECT) - + bool rebuild = true; + ext::opengl::states::rebuild = true; + // we actually don't buffer anything directly at the moment so this will always fail + // as vertex data is directly read from the mesh object + /* bool rebuild = false; - // allocate buffers - for ( auto i = 0; i < queue.size(); ++i ) { - auto& q = queue[i]; - rebuild = rebuild || updateBuffer( q.data, q.size, descriptor.inputs.bufferOffset + i, true ); - } + PARSE_INPUT_UPDATE(vertex, uf::renderer::enums::Buffer::VERTEX) + PARSE_INPUT_UPDATE(index, uf::renderer::enums::Buffer::INDEX) + PARSE_INPUT_UPDATE(instance, uf::renderer::enums::Buffer::VERTEX) + PARSE_INPUT_UPDATE(indirect, uf::renderer::enums::Buffer::INDIRECT) + */ if ( mesh.instance.count == 0 && mesh.instance.attributes.empty() ) { descriptor.inputs.instance.count = 1; @@ -385,8 +391,6 @@ void ext::opengl::Graphic::record( CommandBuffer& commandBuffer, const GraphicDe drawCommandInfoBase.color.value = uniforms->color; drawCommandInfoBase.color.enabled = drawCommandInfoBase.color.value != pod::Vector4f{1.0f, 1.0f, 1.0f, 1.0f}; - - UF_MSG_DEBUG("{}: {}", uf::vector::toString(drawCommandInfoBase.color.value), drawCommandInfoBase.color.enabled); } struct { diff --git a/engine/src/ext/reactphysics/reactphysics.cpp b/engine/src/ext/reactphysics/reactphysics.cpp index 278a1903..0a478a3d 100644 --- a/engine/src/ext/reactphysics/reactphysics.cpp +++ b/engine/src/ext/reactphysics/reactphysics.cpp @@ -15,6 +15,132 @@ namespace { rp3d::PhysicsCommon common; rp3d::PhysicsWorld* world; + reactphysics3d::TriangleMesh* createTriangleMesh( const uf::Mesh& mesh ) { + auto* rMesh = ::common.createTriangleMesh(); + + uf::Mesh::Input vertexInput = mesh.vertex; + uf::Mesh::Input indexInput = mesh.index; + + uf::Mesh::Attribute vertexAttribute = mesh.vertex.attributes.front(); + uf::Mesh::Attribute normalAttribute = mesh.vertex.attributes.front(); + uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front(); + + for ( auto& attribute : mesh.vertex.attributes ) { + if ( attribute.descriptor.name == "position" ) vertexAttribute = attribute; + if ( attribute.descriptor.name == "normal" ) normalAttribute = attribute; + } + UF_ASSERT( vertexAttribute.descriptor.name == "position" ); + + rp3d::TriangleVertexArray::IndexDataType indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE; + rp3d::TriangleVertexArray::VertexDataType vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE; + rp3d::TriangleVertexArray::NormalDataType normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE; + switch ( mesh.index.size ) { + case sizeof(uint16_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_SHORT_TYPE; break; + case sizeof(uint32_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE; break; + default: UF_EXCEPTION("unsupported index type: {}", mesh.index.size); break; + } + switch ( vertexAttribute.descriptor.type ) { + case uf::renderer::enums::Type::USHORT: + case uf::renderer::enums::Type::SHORT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_SHORT_TYPE; break; + case uf::renderer::enums::Type::FLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE; break; + #if UF_USE_FLOAT16 + case uf::renderer::enums::Type::HALF: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT16_TYPE; break; + #endif + #if UF_USE_BFLOAT16 + case uf::renderer::enums::Type::BFLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_BFLOAT16_TYPE; break; + #endif + default: UF_EXCEPTION("unsupported vertex type: {}", vertexAttribute.descriptor.type); break; + } + switch ( normalAttribute.descriptor.type ) { + case uf::renderer::enums::Type::USHORT: + case uf::renderer::enums::Type::SHORT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_SHORT_TYPE; break; + case uf::renderer::enums::Type::FLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE; break; + #if UF_USE_FLOAT16 + case uf::renderer::enums::Type::HALF: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT16_TYPE; break; + #endif + #if UF_USE_BFLOAT16 + case uf::renderer::enums::Type::BFLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_BFLOAT16_TYPE; break; + #endif + default: UF_EXCEPTION("unsupported normal type: {}", normalAttribute.descriptor.type); break; + } + + if ( mesh.indirect.count ) { + for ( auto i = 0; i < mesh.indirect.count; ++i ) { + vertexInput = mesh.remapVertexInput( i ); + indexInput = mesh.remapIndexInput( i ); + + // skip if culled + if ( vertexInput.count == 0 || indexInput.count == 0 ) continue; + + if ( normalAttribute.descriptor.name == "normal" ) { + rMesh->addSubpart(new rp3d::TriangleVertexArray( + vertexInput.count, + (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, + vertexAttribute.stride, + + (const uint8_t*) (normalAttribute.pointer) + normalAttribute.stride * vertexInput.first, + normalAttribute.stride, + + indexInput.count / 3, + (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, + indexAttribute.stride * 3, + + vertexType, + normalType, + indexType + )); + } else { + rMesh->addSubpart(new rp3d::TriangleVertexArray( + vertexInput.count, + (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, + vertexAttribute.stride, + + indexInput.count / 3, + (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, + indexAttribute.stride * 3, + + vertexType, + indexType + )); + } + } + } else if ( vertexInput.count > 0 && indexInput.count > 0 ) { + if ( normalAttribute.descriptor.name == "normal" ) { + rMesh->addSubpart(new rp3d::TriangleVertexArray( + vertexInput.count, + (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, + vertexAttribute.stride, + + (const uint8_t*) (normalAttribute.pointer) + normalAttribute.stride * vertexInput.first, + normalAttribute.stride, + + indexInput.count / 3, + (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, + indexAttribute.stride * 3, + + vertexType, + normalType, + indexType + )); + } else { + rMesh->addSubpart(new rp3d::TriangleVertexArray( + vertexInput.count, + (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, + vertexAttribute.stride, + + indexInput.count / 3, + (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, + indexAttribute.stride * 3, + + vertexType, + indexType + )); + } + } + + return rMesh; + } + class EventListener : public rp3d::EventListener { public: virtual void onContact( const rp3d::CollisionCallback::CallbackData& callbackData ) override { @@ -344,130 +470,15 @@ void ext::reactphysics::detach( pod::PhysicsState& state ) { // auto& world = ext::reactphysics::globalStorage ? ::world : scene.getComponent(); state.world->destroyRigidBody(state.body); state.body = NULL; + + state = {}; // necessary if it gets reused } // collider for mesh (static or dynamic) pod::PhysicsState& ext::reactphysics::create( uf::Object& object, const uf::Mesh& mesh, bool dynamic ) { UF_ASSERT( mesh.index.count ); - auto* rMesh = ::common.createTriangleMesh(); - - uf::Mesh::Input vertexInput = mesh.vertex; - uf::Mesh::Input indexInput = mesh.index; - - uf::Mesh::Attribute vertexAttribute = mesh.vertex.attributes.front(); - uf::Mesh::Attribute normalAttribute = mesh.vertex.attributes.front(); - uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front(); - - for ( auto& attribute : mesh.vertex.attributes ) { - if ( attribute.descriptor.name == "position" ) vertexAttribute = attribute; - if ( attribute.descriptor.name == "normal" ) normalAttribute = attribute; - } - UF_ASSERT( vertexAttribute.descriptor.name == "position" ); - - rp3d::TriangleVertexArray::IndexDataType indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE; - rp3d::TriangleVertexArray::VertexDataType vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE; - rp3d::TriangleVertexArray::NormalDataType normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE; - switch ( mesh.index.size ) { - case sizeof(uint16_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_SHORT_TYPE; break; - case sizeof(uint32_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE; break; - default: UF_EXCEPTION("unsupported index type: {}", mesh.index.size); break; - } - switch ( vertexAttribute.descriptor.type ) { - case uf::renderer::enums::Type::USHORT: - case uf::renderer::enums::Type::SHORT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_SHORT_TYPE; break; - case uf::renderer::enums::Type::FLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE; break; - #if UF_USE_FLOAT16 - case uf::renderer::enums::Type::HALF: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT16_TYPE; break; - #endif - #if UF_USE_BFLOAT16 - case uf::renderer::enums::Type::BFLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_BFLOAT16_TYPE; break; - #endif - default: UF_EXCEPTION("unsupported vertex type: {}", vertexAttribute.descriptor.type); break; - } - switch ( normalAttribute.descriptor.type ) { - case uf::renderer::enums::Type::USHORT: - case uf::renderer::enums::Type::SHORT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_SHORT_TYPE; break; - case uf::renderer::enums::Type::FLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE; break; - #if UF_USE_FLOAT16 - case uf::renderer::enums::Type::HALF: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT16_TYPE; break; - #endif - #if UF_USE_BFLOAT16 - case uf::renderer::enums::Type::BFLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_BFLOAT16_TYPE; break; - #endif - default: UF_EXCEPTION("unsupported normal type: {}", normalAttribute.descriptor.type); break; - } - - if ( mesh.indirect.count ) { - for ( auto i = 0; i < mesh.indirect.count; ++i ) { - vertexInput = mesh.remapVertexInput( i ); - indexInput = mesh.remapIndexInput( i ); - - if ( normalAttribute.descriptor.name == "normal" ) { - rMesh->addSubpart(new rp3d::TriangleVertexArray( - vertexInput.count, - (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, - vertexAttribute.stride, - - (const uint8_t*) (normalAttribute.pointer) + normalAttribute.stride * vertexInput.first, - normalAttribute.stride, - - indexInput.count / 3, - (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, - indexAttribute.stride * 3, - - vertexType, - normalType, - indexType - )); - } else { - rMesh->addSubpart(new rp3d::TriangleVertexArray( - vertexInput.count, - (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, - vertexAttribute.stride, - - indexInput.count / 3, - (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, - indexAttribute.stride * 3, - - vertexType, - indexType - )); - } - } - } else { - if ( normalAttribute.descriptor.name == "normal" ) { - rMesh->addSubpart(new rp3d::TriangleVertexArray( - vertexInput.count, - (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, - vertexAttribute.stride, - - (const uint8_t*) (normalAttribute.pointer) + normalAttribute.stride * vertexInput.first, - normalAttribute.stride, - - indexInput.count / 3, - (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, - indexAttribute.stride * 3, - - vertexType, - normalType, - indexType - )); - } else { - rMesh->addSubpart(new rp3d::TriangleVertexArray( - vertexInput.count, - (const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first, - vertexAttribute.stride, - - indexInput.count / 3, - (const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first, - indexAttribute.stride * 3, - - vertexType, - indexType - )); - } - } + auto* rMesh = ::createTriangleMesh( mesh ); auto& state = ext::reactphysics::create( object ); state.shape = ::common.createConcaveMeshShape( rMesh ); diff --git a/engine/src/ext/vulkan/graphic.cpp b/engine/src/ext/vulkan/graphic.cpp index 347a4df0..6a3c9d28 100644 --- a/engine/src/ext/vulkan/graphic.cpp +++ b/engine/src/ext/vulkan/graphic.cpp @@ -1169,13 +1169,13 @@ bool ext::vulkan::Graphic::updateMesh( uf::Mesh& mesh ) { if ( mesh.isInterleaved( mesh.NAME.interleaved ) ) {\ auto& buffer = mesh.buffers[mesh.NAME.interleaved];\ if ( !buffer.empty() ) {\ - rebuild = rebuild || updateBuffer( (const void*) buffer.data(), buffer.size(), this->metadata.buffers[#NAME] );\ + rebuild = updateBuffer( (const void*) buffer.data(), buffer.size(), this->metadata.buffers[#NAME] ) || rebuild;\ } else mesh.NAME.interleaved = -1;\ } else for ( size_t i = 0; i < descriptor.inputs.NAME.attributes.size(); ++i ) {\ auto& attribute = descriptor.inputs.NAME.attributes[i];\ auto& buffer = mesh.buffers[attribute.buffer];\ if ( !buffer.empty() ) {\ - rebuild = rebuild || updateBuffer( (const void*) buffer.data(), buffer.size(), this->metadata.buffers[#NAME"["+attribute.descriptor.name+"]"] );\ + rebuild = updateBuffer( (const void*) buffer.data(), buffer.size(), this->metadata.buffers[#NAME"["+attribute.descriptor.name+"]"] ) || rebuild;\ } else attribute.buffer = -1;\ }\ } diff --git a/engine/src/ext/zlib/zlib.cpp b/engine/src/ext/zlib/zlib.cpp index 6f6b7aa4..77c321ba 100644 --- a/engine/src/ext/zlib/zlib.cpp +++ b/engine/src/ext/zlib/zlib.cpp @@ -14,20 +14,20 @@ uf::stl::vector ext::zlib::compress( const void* data, size_t size ) { uf::stl::vector buffer; // zlib struct - z_stream defstream; - defstream.zalloc = Z_NULL; - defstream.zfree = Z_NULL; - defstream.opaque = Z_NULL; - // setup "a" as the input and "b" as the compressed output - defstream.avail_in = (uInt) strlen(a)+1; // size of input, string + terminator - defstream.next_in = (Bytef*) a; // input char array - defstream.avail_out = (uInt) sizeof(b); // size of output - defstream.next_out = (Bytef*) b; // output char array - - // the actual compression work. - deflateInit(&defstream, Z_BEST_COMPRESSION); - deflate(&defstream, Z_FINISH); - deflateEnd(&defstream); + z_stream defstream; + defstream.zalloc = Z_NULL; + defstream.zfree = Z_NULL; + defstream.opaque = Z_NULL; + // setup "a" as the input and "b" as the compressed output + defstream.avail_in = (uInt) strlen(a)+1; // size of input, string + terminator + defstream.next_in = (Bytef*) a; // input char array + defstream.avail_out = (uInt) sizeof(b); // size of output + defstream.next_out = (Bytef*) b; // output char array + + // the actual compression work. + deflateInit(&defstream, Z_BEST_COMPRESSION); + deflate(&defstream, Z_FINISH); + deflateEnd(&defstream); return buffer; } @@ -77,4 +77,79 @@ size_t ext::zlib::compressToFile( const uf::stl::string& filename, const void* d gzclose( out ); return uf::io::size( filename ); } + +uf::stl::vector ext::zlib::decompressFromFile( const uf::stl::string& filename, size_t start, size_t len ) { + uf::stl::vector buffer; + + gzFile in = gzopen(filename.c_str(), "rb"); + if ( !in ) { + UF_MSG_ERROR("Zlib: failed to open file for read: {}", filename); + return buffer; + } + + // Seek to the requested uncompressed offset + if (gzseek(in, static_cast(start), SEEK_SET) == -1) { + UF_MSG_ERROR("Zlib: failed to seek to position {} in file {}", start, filename); + gzclose(in); + return buffer; + } + + buffer.resize(len); + int bytesRead = gzread(in, buffer.data(), static_cast(len)); + if (bytesRead < 0) { + int errnum; + const char* errMsg = gzerror(in, &errnum); + UF_MSG_ERROR("Zlib read error: {}", errMsg ? errMsg : "unknown error"); + buffer.clear(); + } else { + buffer.resize(static_cast(bytesRead)); // Adjust size in case EOF occurs early + } + + gzclose(in); + return buffer; +} + +uf::stl::vector ext::zlib::decompressFromFile( const uf::stl::string& filename, const uf::stl::vector& ranges ) { + uf::stl::vector result; + + if ( ranges.empty() ) { + return result; + } + + // ensure they're ordered + uf::stl::vector sortedRanges = ranges; + std::sort( sortedRanges.begin(), sortedRanges.end(), [](const pod::Range& a, const pod::Range& b) { return a.start < b.start; } ); + + gzFile in = gzopen(filename.c_str(), "rb"); + if ( !in ) { + UF_MSG_ERROR("Zlib: failed to open file for read: {}", filename); + return result; + } + + for ( const auto& r : sortedRanges ) { + if ( gzseek(in, static_cast(r.start), SEEK_SET) == -1 ) { + UF_MSG_ERROR("Zlib: failed to seek to position {} in file {}", r.start, filename); + gzclose(in); + return uf::stl::vector(); // Return empty on failure + } + + size_t oldSize = result.size(); + result.resize(oldSize + r.len); + + int bytesRead = gzread(in, result.data() + oldSize, static_cast(r.len)); + if ( bytesRead < 0 ) { + int errnum; + const char* errMsg = gzerror(in, &errnum); + UF_MSG_ERROR("Zlib read error: {}", errMsg ? errMsg : "unknown error"); + gzclose(in); + return uf::stl::vector(); // Return empty on error + } + + // In case EOF ended early + result.resize(oldSize + static_cast(bytesRead)); + } + + gzclose(in); + return result; +} #endif \ No newline at end of file diff --git a/engine/src/utils/io/file.cpp b/engine/src/utils/io/file.cpp index 5247b5ee..7c9783fc 100644 --- a/engine/src/utils/io/file.cpp +++ b/engine/src/utils/io/file.cpp @@ -133,7 +133,73 @@ uf::stl::vector uf::io::readAsBuffer( const uf::stl::string& _filename, buffer.assign((std::istreambuf_iterator(is)), std::istreambuf_iterator()); } uf::stl::string expected = ""; - if ( hash != "" && (expected = uf::string::sha256( buffer )) != hash ) { + if ( !hash.empty() && (expected = uf::string::sha256( buffer )) != hash ) { + UF_MSG_ERROR("Error: Hash mismatch for file {}; expecting {}, got {}", filename, hash, expected); + return uf::stl::vector(); + } + return buffer; +} + +uf::stl::vector uf::io::readAsBuffer( const uf::stl::string& _filename, size_t start, size_t len, const uf::stl::string& hash ) { + uf::stl::vector buffer; + uf::stl::string filename = sanitize(_filename); + uf::stl::string extension = uf::io::extension(filename); + + if ( extension == "gz" || extension == "lz4" ) { + buffer = uf::io::decompress( filename, start, len ); + } else { + std::ifstream is(filename, std::ios::binary); + if (!is.is_open()) { + UF_MSG_ERROR("Error: Could not open file: {}", filename); + return buffer; + } + is.seekg(start, std::ios::beg); + buffer.resize(len); + is.read(reinterpret_cast(buffer.data()), len); + buffer.resize(static_cast(is.gcount())); // adjust if EOF + } + + uf::stl::string expected; + if ( !hash.empty() && (expected = uf::string::sha256( buffer )) != hash ) { + UF_MSG_ERROR("Error: Hash mismatch for file {}; expecting {}, got {}", filename, hash, expected); + return uf::stl::vector(); + } + return buffer; +} + +uf::stl::vector uf::io::readAsBuffer( const uf::stl::string& _filename, const uf::stl::vector& ranges, const uf::stl::string& hash ) { + uf::stl::vector buffer; + uf::stl::string filename = sanitize(_filename); + uf::stl::string extension = uf::io::extension(filename); + + if ( extension == "gz" || extension == "lz4" ) { + buffer = uf::io::decompress( filename, ranges ); + } else { + std::ifstream is(filename, std::ios::binary); + if (!is.is_open()) { + UF_MSG_ERROR("Error: Could not open file: {}", filename); + return buffer; + } + + // Precompute total size to reserve memory + size_t totalBytes = 0; + for (const auto& r : ranges) { + totalBytes += r.len; + } + buffer.reserve(totalBytes); + + // Read each range + for (const auto& r : ranges) { + is.seekg(r.start, std::ios::beg); + size_t oldSize = buffer.size(); + buffer.resize(oldSize + r.len); + is.read(reinterpret_cast(buffer.data() + oldSize), r.len); + buffer.resize(oldSize + static_cast(is.gcount())); + } + } + + uf::stl::string expected; + if ( !hash.empty() && (expected = uf::string::sha256( buffer )) != hash ) { UF_MSG_ERROR("Error: Hash mismatch for file {}; expecting {}, got {}", filename, hash, expected); return uf::stl::vector(); } @@ -159,6 +225,20 @@ uf::stl::vector uf::io::decompress( const uf::stl::string& filename ) { UF_MSG_ERROR("unsupported compression format requested: {}", extension); return {}; } +uf::stl::vector uf::io::decompress( const uf::stl::string& filename, size_t start, size_t len ) { + uf::stl::string extension = uf::io::extension( filename ); + if ( extension == "gz" ) return ext::zlib::decompressFromFile( filename, start, len ); +// if ( extension == "lz4" ) return ext::lz4::decompressFromFile( filename, start, len ); + UF_MSG_ERROR("unsupported compression format requested: {}", extension); + return {}; +} +uf::stl::vector uf::io::decompress( const uf::stl::string& filename, const uf::stl::vector& ranges ) { + uf::stl::string extension = uf::io::extension( filename ); + if ( extension == "gz" ) return ext::zlib::decompressFromFile( filename, ranges ); +// if ( extension == "lz4" ) return ext::lz4::decompressFromFile( filename, ranges ); + UF_MSG_ERROR("unsupported compression format requested: {}", extension); + return {}; +} size_t uf::io::compress( const uf::stl::string& filename, const void* buffer, size_t size ) { uf::stl::string extension = uf::io::extension( filename ); if ( extension == "gz" ) return ext::zlib::compressToFile( filename, buffer, size ); diff --git a/engine/src/utils/mesh/grid.cpp b/engine/src/utils/mesh/grid.cpp index 0da0614f..f99d6138 100644 --- a/engine/src/utils/mesh/grid.cpp +++ b/engine/src/utils/mesh/grid.cpp @@ -5,6 +5,15 @@ namespace { // return ( min.x <= p.x && p.x <= max.x && min.y <= p.y && p.y <= max.y && min.z <= p.z && p.z <= max.z ); return (p.x <= max.x && p.x >= min.x) && (p.y <= max.y && p.y >= min.y) && (p.z <= max.z && p.z >= min.z); } + + inline pod::Vector3ui cellCoords(const pod::Vector3f& p, const pod::Vector3f& min, const pod::Vector3f& piece, const pod::Vector3ui& divs) { + pod::Vector3f rel = (p - min) / piece; + return { + std::min(std::max(0, (int)rel.x), divs.x - 1), + std::min(std::max(0, (int)rel.y), divs.y - 1), + std::min(std::max(0, (int)rel.z), divs.z - 1) + }; + } } void uf::meshgrid::print( const uf::meshgrid::Grid& grid ) { @@ -15,30 +24,7 @@ void uf::meshgrid::print( const uf::meshgrid::Grid& grid ) { for ( auto& pair2 : node.meshlets ) indices += pair2.second.indices.size(); float percentage = 100.0f * indices / grid.indices; total += percentage; - /* - UF_MSG_DEBUG( - "[" << node.id.x << "," << node.id.y << "," << node.id.z << "] " - "[" << percentage << "%|" << total << "%] " - "Min: " << uf::vector::toString( node.extents.min ) << " | " - "Max: " << uf::vector::toString( node.extents.max ) << " | " - "Meshlets: " << node.meshlets.size() << " | " - "Indices: " << indices - ); - */ } -/* - UF_MSG_DEBUG( "== == == =="); - UF_MSG_DEBUG( "Min: {}", uf::vector::toString( grid.extents.min ) ); - UF_MSG_DEBUG( "Max: {}", uf::vector::toString( grid.extents.max ) ); - UF_MSG_DEBUG( "Center: {}", uf::vector::toString( grid.extents.center ) ); - UF_MSG_DEBUG( "Corner: {}", uf::vector::toString( grid.extents.corner ) ); - UF_MSG_DEBUG( "Size: {}", uf::vector::toString( grid.extents.size ) ); - UF_MSG_DEBUG( "Piece: {}", uf::vector::toString( grid.extents.piece ) ); - UF_MSG_DEBUG( "Divisions: {}", uf::vector::toString( grid.divisions ) ); - UF_MSG_DEBUG( "Nodes: {}", grid.nodes.size() ); - UF_MSG_DEBUG( "Indices: {}", grid.indices ); - UF_MSG_DEBUG( "== == == =="); -*/ } void uf::meshgrid::cleanup( uf::meshgrid::Grid& grid ) { uf::stl::vector eraseNodes; @@ -57,198 +43,103 @@ void uf::meshgrid::cleanup( uf::meshgrid::Grid& grid ) { } for ( auto& e : eraseNodes ) grid.nodes.erase(e); - -/* - for ( auto it = grid.nodes.begin(); it != grid.nodes.end(); ++it ) { - auto& node = it->second; - - uf::stl::vector eraseMeshlets; - for ( auto it2 = node.meshlets.begin(); it2 != node.meshlets.end(); ++it2 ) { - auto& meshlet = it2->second; - - if ( !meshlet.indices.empty() ) continue; - UF_MSG_DEBUG("Erase: " << it2->first); - it2 = node.meshlets.erase(it2); - } - - if ( !node.meshlets.empty() ) continue; - UF_MSG_DEBUG("Erase: " << uf::vector::toString(it->first)); - it = grid.nodes.erase(it); - } -*/ -/* - for ( auto& pair : grid.nodes ) { auto& node = pair.second; - for ( auto& pair2 : node.meshlets ) { auto& meshlet = pair2.second; - if ( !meshlet.indices.empty() ) continue; - // node.meshlets.erase(pair2.first); - // UF_MSG_DEBUG("Erase: " << pair2.first); - } - if ( !node.meshlets.empty() ) continue; - UF_MSG_DEBUG("Erase: " << uf::vector::toString(pair.first)); - grid.nodes.erase(pair.first); - } -*/ } -void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, int divisions, float eps ){ +void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, int divisions, float padding ){ grid.divisions = {divisions, divisions, divisions}; - return calculate( grid, eps ); + return calculate( grid, padding ); } -void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, const pod::Vector3ui& divisions, float eps ){ +void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, const pod::Vector3ui& divisions, float padding ){ grid.divisions = divisions; - return calculate( grid, eps ); + return calculate( grid, padding ); } -void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, float eps ){ - // calculate extents - constexpr float epsilon = std::numeric_limits::epsilon(); - grid.extents.min -= pod::Vector3f{ epsilon, epsilon, epsilon }; // dilate - grid.extents.max += pod::Vector3f{ epsilon, epsilon, epsilon }; // dilate - grid.extents.size = uf::vector::abs(grid.extents.max - grid.extents.min); +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 }; + + grid.extents.size = grid.extents.max - grid.extents.min; grid.extents.piece = grid.extents.size / grid.divisions; - grid.extents.center = (grid.extents.max + grid.extents.min) * 0.5f; - grid.extents.corner = grid.extents.size * 0.5f; + grid.nodes.clear(); + grid.nodes.reserve(grid.divisions.x * grid.divisions.y * grid.divisions.z); - -// initialize - grid.nodes.reserve( grid.divisions.x * grid.divisions.y * grid.divisions.z ); - for ( int z = 0; z < grid.divisions.z; ++z ) { - for ( int y = 0; y < grid.divisions.y; ++y ) { - for ( int x = 0; x < grid.divisions.x; ++x ) { - auto id = pod::Vector3ui{ x, y, z }; - grid.nodes[id] = { - .extents = { - .min = grid.extents.min + grid.extents.piece * pod::Vector3f{ x, y, z }, - .max = grid.extents.min + grid.extents.piece * pod::Vector3f{ x+1, y+1, z+1 }, - }, - .id = id - }; + for (uint32_t z = 0; z < grid.divisions.z; ++z) { + for (uint32_t y = 0; y < grid.divisions.y; ++y) { + for (uint32_t x = 0; x < grid.divisions.x; ++x) { + Node node; + node.id = { x, y, z }; + node.extents.min = grid.extents.min + grid.extents.piece * pod::Vector3f{ x, y, z }; + node.extents.max = node.extents.min + grid.extents.piece; + grid.nodes[{x,y,z}] = std::move(node); } } } } -void uf::meshgrid::partition( uf::meshgrid::Grid& grid, + +void uf::meshgrid::partition(uf::meshgrid::Grid& grid, const void* pPointer, size_t pStride, const void* iPointer, size_t iStride, - const pod::Primitive& primitive -) { + const pod::Primitive& primitive) +{ size_t pFirst = primitive.drawCommand.vertexID; size_t pCount = primitive.drawCommand.vertices; size_t iFirst = primitive.drawCommand.indexID; size_t iCount = primitive.drawCommand.indices; - const float oneOverThree = 1.0f / 3.0f; - struct Triangle { pod::Vector3ui indices; - pod::Vector3f center; - pod::Vector3f vertices[3]; + pod::Vector3f verts[3]; }; - uf::stl::vector rejects; - for ( auto& pair : grid.nodes ) { auto& node = pair.second; - auto& meshlet = node.meshlets[primitive.instance.primitiveID]; - meshlet.primitive = primitive; - meshlet.primitive.instance.bounds.min = node.extents.min; - meshlet.primitive.instance.bounds.max = node.extents.max; - } + const uint8_t* idxBase = reinterpret_cast(iPointer); + const uint8_t* vtxBase = reinterpret_cast(pPointer); - // iterate - for ( size_t i = 0; i < iCount; i+=3 ) { + for (size_t i = 0; i < iCount; i += 3) { Triangle tri{}; - const uint8_t* indexPointers[3] = { - (static_cast(iPointer) + iStride * (iFirst + i + 0)), - (static_cast(iPointer) + iStride * (iFirst + i + 1)), - (static_cast(iPointer) + iStride * (iFirst + i + 2)), + // Read indices + auto readIndex = [&](size_t offs) -> uint32_t { + switch (iStride) { + case 1: return *(const uint8_t*)(idxBase + iStride * (iFirst + offs)); + case 2: return *(const uint16_t*)(idxBase + iStride * (iFirst + offs)); + default: return *(const uint32_t*)(idxBase + iStride * (iFirst + offs)); + } }; + tri.indices[0] = readIndex(i); + tri.indices[1] = readIndex(i+1); + tri.indices[2] = readIndex(i+2); - switch ( iStride ) { - case sizeof(uint8_t): { - tri.indices[0] = *( const uint8_t*) indexPointers[0]; - tri.indices[1] = *( const uint8_t*) indexPointers[1]; - tri.indices[2] = *( const uint8_t*) indexPointers[2]; - } break; - case sizeof(uint16_t): { - tri.indices[0] = *(const uint16_t*) indexPointers[0]; - tri.indices[1] = *(const uint16_t*) indexPointers[1]; - tri.indices[2] = *(const uint16_t*) indexPointers[2]; - } break; - case sizeof(uint32_t): { - tri.indices[0] = *(const uint32_t*) indexPointers[0]; - tri.indices[1] = *(const uint32_t*) indexPointers[1]; - tri.indices[2] = *(const uint32_t*) indexPointers[2]; - } break; + // Read vertices + for (int v = 0; v < 3; v++) { + tri.verts[v] = *(const pod::Vector3f*)(vtxBase + pStride * (pFirst + tri.indices[v])); } - tri.vertices[0] = *(const pod::Vector3f*) (static_cast(pPointer) + pStride * (pFirst + tri.indices[0])); - tri.vertices[1] = *(const pod::Vector3f*) (static_cast(pPointer) + pStride * (pFirst + tri.indices[1])); - tri.vertices[2] = *(const pod::Vector3f*) (static_cast(pPointer) + pStride * (pFirst + tri.indices[2])); - - tri.center = (tri.vertices[0] + tri.vertices[1] + tri.vertices[2]) * oneOverThree; + // 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]); - bool found = false; - for ( auto& pair : grid.nodes ) { auto& node = pair.second; - auto& meshlet = node.meshlets[primitive.instance.primitiveID]; + 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); - if ( isInside( tri.vertices[0], node.extents.min, node.extents.max ) || isInside( tri.vertices[1], node.extents.min, node.extents.max ) || isInside( tri.vertices[2], node.extents.min, node.extents.max ) ) { - // if ( isInside( tri.center, node.extents.min, node.extents.max ) ) { - meshlet.indices.emplace_back( tri.indices[0] ); - meshlet.indices.emplace_back( tri.indices[1] ); - meshlet.indices.emplace_back( tri.indices[2] ); + // 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]; + auto& meshlet = node.meshlets[primitive.instance.primitiveID]; + meshlet.primitive = primitive; - #pragma unroll // GCC unroll N - for ( uint_fast8_t _ = 0; _ < 3; ++_ ) { - node.effectiveExtents.min = uf::vector::min( node.effectiveExtents.min, tri.vertices[_] ); - node.effectiveExtents.max = uf::vector::max( node.effectiveExtents.max, tri.vertices[_] ); + meshlet.indices.emplace_back(tri.indices[0]); + meshlet.indices.emplace_back(tri.indices[1]); + meshlet.indices.emplace_back(tri.indices[2]); + + for (int v = 0; v < 3; v++) { + node.effectiveExtents.min = uf::vector::min(node.effectiveExtents.min, tri.verts[v]); + node.effectiveExtents.max = uf::vector::max(node.effectiveExtents.max, tri.verts[v]); + } } - found = true; - break; } - // if ( isInside( tri.vertices[0], node.extents.min, node.extents.max ) || isInside( tri.vertices[1], node.extents.min, node.extents.max ) || isInside( tri.vertices[2], node.extents.min, node.extents.max ) ) { - if ( isInside( tri.center, node.extents.min, node.extents.max ) ) { - meshlet.indices.emplace_back( tri.indices[0] ); - meshlet.indices.emplace_back( tri.indices[1] ); - meshlet.indices.emplace_back( tri.indices[2] ); - - #pragma unroll // GCC unroll N - for ( uint_fast8_t _ = 0; _ < 3; ++_ ) { - node.effectiveExtents.min = uf::vector::min( node.effectiveExtents.min, tri.vertices[_] ); - node.effectiveExtents.max = uf::vector::max( node.effectiveExtents.max, tri.vertices[_] ); - } - found = true; - break; - } - } - if ( found ) { - continue; - } - rejects.emplace_back(tri); - } - - for ( auto& tri : rejects ) { - uf::meshgrid::Node* closest = NULL; - float closestDistance = std::numeric_limits::max(); - - for ( auto& pair : grid.nodes ) { auto& node = pair.second; - auto nodeCenter = (node.extents.max + node.extents.min) * 0.5f; - float curDistance = uf::vector::distanceSquared( nodeCenter, tri.center ); - if ( curDistance < closestDistance ) { - closestDistance = curDistance; - closest = &node; - } - } - if ( closest ) { - auto& meshlet = closest->meshlets[primitive.instance.primitiveID]; - meshlet.indices.emplace_back( tri.indices[0] ); - meshlet.indices.emplace_back( tri.indices[1] ); - meshlet.indices.emplace_back( tri.indices[2] ); - - #pragma unroll // GCC unroll N - for ( uint_fast8_t _ = 0; _ < 3; ++_ ) { - closest->effectiveExtents.min = uf::vector::min( closest->effectiveExtents.min, tri.vertices[_] ); - closest->effectiveExtents.max = uf::vector::max( closest->effectiveExtents.max, tri.vertices[_] ); - } - } else { } }