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)

This commit is contained in:
ecker 2025-08-13 19:46:05 -05:00
parent a9b5a03f94
commit bc406d5b62
27 changed files with 822 additions and 440 deletions

View File

@ -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

View File

@ -311,7 +311,7 @@
"debug draw": {
"enabled": false,
"line width": 8,
"layer": "",
"layer": "Gui",
"rate": 0.0125
}
},

View File

@ -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
}
}
}

View File

@ -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
},

View File

@ -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 ],

View File

@ -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"

View File

@ -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 ] } } } }
}
}
}
}

View File

@ -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

View File

@ -1 +1 @@
vulkan
opengl

View File

@ -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

View File

@ -85,14 +85,17 @@ namespace ext {
template<typename T> inline size_t initializeBuffer( const T& data, GLenum usage, bool alias = false ) { return initializeBuffer( (const void*) &data, static_cast<GLsizeiptr>(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<typename T> inline bool updateBuffer( const T& data, size_t index = 0, bool alias = false ) const { return updateBuffer( (const void*) &data, static_cast<GLsizeiptr>(sizeof(T)), index, alias ); }
template<typename T> 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<typename T> inline bool updateBuffer( const T& data, const Buffer& buffer, bool alias = false ) const { return updateBuffer( (const void*) &data, static_cast<GLsizeiptr>(sizeof(T)), buffer, alias ); }
template<typename T> inline bool updateBuffer( const T& data, GLsizeiptr length, const Buffer& buffer, bool alias = false ) const { return updateBuffer( (const void*) &data, length, buffer, alias ); }
*/
};
}
}

View File

@ -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

View File

@ -5,6 +5,7 @@
#include <uf/utils/memory/vector.h>
#include <uf/utils/memory/string.h>
#include <uf/utils/io/file.h>
namespace ext {
namespace zlib {
@ -26,7 +27,11 @@ namespace ext {
memcpy( vector.data(), compressed.data(), compressed.size() );
}
*/
uf::stl::vector<uint8_t> UF_API decompressFromFile( const uf::stl::string& );
uf::stl::vector<uint8_t> UF_API decompressFromFile( const uf::stl::string& filename, size_t start, size_t len );
uf::stl::vector<uint8_t> UF_API decompressFromFile( const uf::stl::string& filename, const uf::stl::vector<pod::Range>& ranges );
size_t UF_API compressToFile( const uf::stl::string&, const void*, size_t );
}
}

View File

@ -5,6 +5,13 @@
#include <uf/utils/memory/vector.h>
#include <fstream>
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<uint8_t> UF_API readAsBuffer( const uf::stl::string&, const uf::stl::string& = "" );
uf::stl::vector<uint8_t> UF_API readAsBuffer( const uf::stl::string&, size_t start, size_t len, const uf::stl::string& = "" );
uf::stl::vector<uint8_t> UF_API readAsBuffer( const uf::stl::string&, const uf::stl::vector<pod::Range>& ranges, const uf::stl::string& = "" );
size_t UF_API write( const uf::stl::string& filename, const void*, size_t = SIZE_MAX );
template<typename T> inline size_t write( const uf::stl::string& filename, const uf::stl::vector<T>& buffer, size_t size = SIZE_MAX ) {
@ -30,6 +39,8 @@ namespace uf {
}
uf::stl::vector<uint8_t> UF_API decompress( const uf::stl::string& );
uf::stl::vector<uint8_t> UF_API decompress( const uf::stl::string&, size_t, size_t );
uf::stl::vector<uint8_t> UF_API decompress( const uf::stl::string&, const uf::stl::vector<pod::Range>& );
size_t UF_API compress( const uf::stl::string&, const void*, size_t = SIZE_MAX );
template<typename T> inline size_t compress( const uf::stl::string& filename, const uf::stl::vector<T>& buffer, size_t size = SIZE_MAX ) {

View File

@ -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<T> vertices = meshlet.vertices;
uf::stl::vector<U> indices = meshlet.indices;
uf::shapes::clip<T,U>( 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<T,U>( 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<T,U>( 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;

View File

@ -158,6 +158,7 @@ namespace uf {
} bounds;
*/
uf::stl::vector<buffer_t> buffers;
uf::stl::vector<uf::stl::string> buffer_paths; // crunge
protected:
void _destroy( uf::Mesh::Input& input );
void _bind( bool interleaved = uf::Mesh::defaultInterleaved );

View File

@ -237,9 +237,28 @@ namespace {
ext::json::forEach( json["buffers"], [&]( ext::json::Value& value ){
const uf::stl::string filename = value.as<uf::stl::string>();
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<bool>() ) {
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<bool>() ) {
@ -265,7 +284,7 @@ namespace {
#endif
mesh.updateDescriptor();
#if 0
if ( graph.metadata["renderer"]["separate"].as<bool>() ) {
#if UF_ENV_DREAMCAST && GL_QUANTIZED_SHORT
mesh.convert<float, uint16_t>();
@ -287,43 +306,6 @@ namespace {
#endif
mesh.updateDescriptor();
}
#if 0
// swap winding order
if ( graph.metadata["decode"]["invert winding order"].as<bool>() ) {
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<uf::stl::string>() && graph.metadata["stream"]["enabled"].as<uf::stl::string>() == "auto" ) {
#if UF_ENV_DREAMCAST
graph.metadata["stream"]["enabled"] = true;
#else
graph.metadata["stream"]["enabled"] = false;
#endif
}
if ( graph.metadata["stream"]["enabled"].as<bool>() && graph.metadata["stream"]["radius"].as<float>(0) <= 0.0f ) {
graph.metadata["stream"]["enabled"] = false;
}
if ( !graph.metadata["stream"]["enabled"].as<bool>() ) {
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<uf::stl::string>("");
if ( key != "" ) {
key += ":";

View File

@ -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<bool>& bits) {
uint64_t hash = 1469598103934665603ULL;
for (bool b : bits) {
hash ^= static_cast<uint64_t>(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<pod::Graph::Storage>();
// 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<pod::Transform<>>();
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<pod::Transform<>>();
controllerPosition = controllerTransform.position;
break;
}
}
#if 0
if ( (graph.metadata["renderer"]["separate"].as<bool>()) && graph.metadata["renderer"]["render"].as<bool>() ) {
#endif
if ( graph.metadata["renderer"]["render"].as<bool>() ) {
bool exists = entity.hasComponent<uf::renderer::Graphic>();
if ( exists ) {
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
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<size_t, uf::stl::vector<pod::Range>> ranges;
uf::stl::vector<bool> 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<float>::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<uf::stl::string>();
if ( type == "mesh" ) {
bool exists = entity.hasComponent<pod::PhysicsState>();
if ( exists ) {
uf::physics::terminate( entity );
// entity.deleteComponent<pod::PhysicsState>();
}
auto& collider = entity.getComponent<pod::PhysicsState>();
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<bool>(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<bool>()) && graph.metadata["renderer"]["render"].as<bool>() ) {
#endif
if ( graph.metadata["renderer"]["render"].as<bool>() ) {
bool exists = entity.hasComponent<uf::renderer::Graphic>();
if ( exists ) {
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
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<uf::stl::string>();
if ( type == "mesh" ) {
bool exists = entity.hasComponent<pod::PhysicsState>();
if ( exists ) {
uf::physics::impl::destroy( entity );
}
auto& collider = entity.getComponent<pod::PhysicsState>();
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<bool>(true) );
}
}
}

View File

@ -196,9 +196,9 @@ void uf::ObjectBehavior::destroy( uf::Object& self ) {
// this->deleteComponent<uf::Atlas>();
}
if ( this->hasComponent<pod::PhysicsState>() ) {
// auto& collider = this->getComponent<pod::PhysicsState>();
// uf::physics::detach( collider );
uf::physics::terminate( *this );
auto& collider = this->getComponent<pod::PhysicsState>();
uf::physics::impl::detach( collider );
// this->deleteComponent<pod::PhysicsState>();
}
if ( this->hasComponent<uf::renderer::RenderTargetRenderMode>() ) {
auto& renderMode = this->getComponent<uf::renderer::RenderTargetRenderMode>();

View File

@ -12,9 +12,10 @@
#include <uf/utils/thread/thread.h>
#include <uf/utils/mesh/mesh.h>
#include <uf/utils/string/hash.h>
#include <uf/utils/io/inputs.h>
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<uf::Serializer>();
@ -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<pod::Graph>();
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 ) {}

View File

@ -378,6 +378,7 @@ if ( meshopt.should ) {
mesh.bindIndirect<pod::DrawCommand>();
mesh.bind<UF_GRAPH_MESH_FORMAT>(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(),

View File

@ -241,28 +241,29 @@ void ext::opengl::Graphic::initializeMesh( uf::Mesh& mesh, bool buffer ) {
GLenum usage;
};
uf::stl::vector<Queue> 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> 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 {

View File

@ -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<ext::reactphysics::WorldState>();
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 );

View File

@ -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;\
}\
}

View File

@ -14,20 +14,20 @@ uf::stl::vector<uint8_t> ext::zlib::compress( const void* data, size_t size ) {
uf::stl::vector<uint8_t> 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<uint8_t> ext::zlib::decompressFromFile( const uf::stl::string& filename, size_t start, size_t len ) {
uf::stl::vector<uint8_t> 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<z_off_t>(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<unsigned int>(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<size_t>(bytesRead)); // Adjust size in case EOF occurs early
}
gzclose(in);
return buffer;
}
uf::stl::vector<uint8_t> ext::zlib::decompressFromFile( const uf::stl::string& filename, const uf::stl::vector<pod::Range>& ranges ) {
uf::stl::vector<uint8_t> result;
if ( ranges.empty() ) {
return result;
}
// ensure they're ordered
uf::stl::vector<pod::Range> 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<z_off_t>(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<uint8_t>(); // Return empty on failure
}
size_t oldSize = result.size();
result.resize(oldSize + r.len);
int bytesRead = gzread(in, result.data() + oldSize, static_cast<unsigned int>(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<uint8_t>(); // Return empty on error
}
// In case EOF ended early
result.resize(oldSize + static_cast<size_t>(bytesRead));
}
gzclose(in);
return result;
}
#endif

View File

@ -133,7 +133,73 @@ uf::stl::vector<uint8_t> uf::io::readAsBuffer( const uf::stl::string& _filename,
buffer.assign((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
}
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<uint8_t>();
}
return buffer;
}
uf::stl::vector<uint8_t> uf::io::readAsBuffer( const uf::stl::string& _filename, size_t start, size_t len, const uf::stl::string& hash ) {
uf::stl::vector<uint8_t> 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<char*>(buffer.data()), len);
buffer.resize(static_cast<size_t>(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<uint8_t>();
}
return buffer;
}
uf::stl::vector<uint8_t> uf::io::readAsBuffer( const uf::stl::string& _filename, const uf::stl::vector<pod::Range>& ranges, const uf::stl::string& hash ) {
uf::stl::vector<uint8_t> 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<char*>(buffer.data() + oldSize), r.len);
buffer.resize(oldSize + static_cast<size_t>(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<uint8_t>();
}
@ -159,6 +225,20 @@ uf::stl::vector<uint8_t> uf::io::decompress( const uf::stl::string& filename ) {
UF_MSG_ERROR("unsupported compression format requested: {}", extension);
return {};
}
uf::stl::vector<uint8_t> 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<uint8_t> uf::io::decompress( const uf::stl::string& filename, const uf::stl::vector<pod::Range>& 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 );

View File

@ -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<uint32_t>(std::max<int>(0, (int)rel.x), divs.x - 1),
std::min<uint32_t>(std::max<int>(0, (int)rel.y), divs.y - 1),
std::min<uint32_t>(std::max<int>(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<pod::Vector3ui> 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<size_t> 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<float>::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<Triangle> 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<const uint8_t*>(iPointer);
const uint8_t* vtxBase = reinterpret_cast<const uint8_t*>(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<const uint8_t*>(iPointer) + iStride * (iFirst + i + 0)),
(static_cast<const uint8_t*>(iPointer) + iStride * (iFirst + i + 1)),
(static_cast<const uint8_t*>(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<const uint8_t*>(pPointer) + pStride * (pFirst + tri.indices[0]));
tri.vertices[1] = *(const pod::Vector3f*) (static_cast<const uint8_t*>(pPointer) + pStride * (pFirst + tri.indices[1]));
tri.vertices[2] = *(const pod::Vector3f*) (static_cast<const uint8_t*>(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<float>::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 {
}
}