revamped meshopt-based mesh optimizations, fixed mesh slicer/gridder, added LOD system (i think it works), physics tweaks (to-do: make meshMesh viable)

This commit is contained in:
ecker 2026-04-27 17:09:26 -05:00
parent fc36c77167
commit 84f2b63a8f
25 changed files with 599 additions and 681 deletions

View File

@ -66,9 +66,9 @@
"vulkan": {
"version": 1.3,
"validation": {
"enabled": true,
"messages": true,
"checkpoints": true,
"enabled": false,
"messages": false,
"checkpoints": false,
"filters": [
// "0xe5d1743c" // VUID-vkCmdDispatch-None-02699 (problem when using VXGI) (seems to be fixed?)
// ,"0x6714bd0c" // VUID-vkCmdDispatch-format-07753 (for some dumb shit) (seems to be fixed?)
@ -118,7 +118,7 @@
"vsync": true, // vsync on vulkan side rather than engine-side
"hdr": true,
"vxgi": false,
"culling": false,
"culling": true,
"bloom": false,
"dof": false,
"rt": false,

View File

@ -5,8 +5,8 @@
"import": "/model.json",
"assets": [
// "/burger/burger.glb"
// "/burger/burger_simpler.glb"
// "/burger/burger/graph.json"
// "/burger/burger_simpler.glb"
"/burger/burger_simpler/graph.json"
],
"behaviors": [],
@ -38,7 +38,7 @@
"exporter": {
"enabled": true,
"unwrap": false,
"optimize": false
"optimize": "tagged"
},
"baking": {
"enabled": false
@ -48,6 +48,11 @@
},
"lighting": {
"lightmap": false
},
"tags": {
"/^.*/": {
"optimize meshlets": { "simplify": 0.125, "print": true }
}
}
}
}

View File

@ -6,7 +6,7 @@
"metadata": {
"holdable": true,
"physics": {
"mass": 0,
"mass": 100,
"inertia": false,
"type": "bounding box"
// "type": "mesh"

View File

@ -12,13 +12,13 @@
"worldspawn": {
"physics": { "type": "mesh", "static": true, "mass": 0 },
"grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true },
"optimize meshlets": { "simplify": 0.125, "print": false },
"optimize mesh": { "simplify": 0, "lods": true, "print": true },
"unwrap mesh": true
},
"worldspawn_skybox": {
"physics": { "type": "mesh", "static": true, "mass": 0 },
"physics": { "type": "aabb", "static": true, "mass": 0 },
"grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true },
"optimize meshlets": { "simplify": 0.125, "print": false },
"optimize mesh": { "simplify": 0, "lods": true, "print": true },
"unwrap mesh": true
},
"info_player_spawn": { "action": "attach", "filename": "./player.json", "transform": { "orientation": [ 0, 1, 0, 0 ] } },
@ -42,8 +42,8 @@
"/^prop_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } },
"/^prop_static/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ },
"/^prop_dynamic/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ },
"/^func_physbox/": { "action": "load", "payload": { "import": "/prop.json" } },
"/^prop_physics/": { "action": "load", "payload": { "import": "/prop.json" } },
"/^func_physbox/": { "action": "load", "payload": { "import": "/prop.json" }, "optimize mesh": { "simplify": 0, "lods": true, "print": true } },
"/^prop_physics/": { "action": "load", "payload": { "import": "/prop.json" }, "optimize mesh": { "simplify": 0, "lods": true, "print": true } },
"/^tools\\/toolsnodraw/": { "material": { "base": [ 1.0, 1.0, 1.0, 0.0 ] } }
}

View File

@ -2,8 +2,8 @@
"import": "./base_sourceengine.json",
"assets": [
// { "filename": "./models/mds_mcdonalds.glb" }
{ "filename": "./models/mds_mcdonalds/graph.json" },
{ "filename": "/burger.json", "delay": 1 }
{ "filename": "./models/mds_mcdonalds/graph.json" }
// ,{ "filename": "/burger.json", "delay": 1 }
],
"metadata": {
"graph": {
@ -15,10 +15,10 @@
"func_door_rotating_5568": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } },
"func_door_rotating_5584": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } },
"prop_physics_override_5813": { "action": "load", "payload": { "import": "/physics_prop.json" } },
// "prop_physics_override_5813": { "action": "load", "payload": { "import": "/physics_prop.json" } },
// regex matches
// "/^prop_physics_[^o]/": { "action": "load", "payload": { "import": "/prop.json" } },
"/^prop_physics_[^o]/": { "action": "load", "payload": { "import": "/prop.json" } },
"/^tools\\/toolsnodraw/": { "material": {
"base": [ 1.0, 1.0, 1.0, 1.0 ],

View File

@ -99,6 +99,15 @@ struct Bounds {
float padding2;
};
struct LOD {
uint indices;
uint indexID;
};
struct LODMetadata {
LOD levels[4];
};
struct Instance {
uint materialID;
uint primitiveID;

View File

@ -57,7 +57,7 @@ AF4 SpdLoadSourceImage(ASU2 p, AU1 slice) {
}
AF4 SpdLoad(ASU2 p, AU1 slice) {
uint loadMip = min(6u, MIPS - 1);
uint loadMip = min(6u - 1, MIPS - 1);
float d = imageLoad(outImage[loadMip], p).r;
return AF4(d, d, d, d);
}

View File

@ -11,16 +11,18 @@ layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
#define QUERY_MIPMAPS 1
#define DEPTH_BIAS 0.00005
#define FRUSTUM_CULLING 1
#define OCCLUSION_CULLING 0 // currently whack
#define OCCLUSION_CULLING 1 // currently whack
#define LODS 1
#define MAX_LODS 4
#include "../../common/macros.h"
#include "../../common/structs.h"
float mipLevels( vec2 size ) {
return floor(log2(max(size.x, size.y)));
return ceil(log2(max(size.x, size.y)));
}
float mipLevels( ivec2 size ) {
return floor(log2(max(size.x, size.y)));
return ceil(log2(max(size.x, size.y)));
}
vec4 aabbToSphere( Bounds bounds ) {
@ -61,19 +63,23 @@ layout (binding = 0) uniform Camera {
Viewport viewport[PASSES];
} camera;
layout (std140, binding = 1) buffer DrawCommands {
layout (std430, binding = 1) buffer DrawCommands {
DrawCommand drawCommands[];
};
layout (std140, binding = 2) buffer Instances {
layout (std430, binding = 2) buffer Instances {
Instance instances[];
};
layout (std140, binding = 3) buffer Objects {
layout (std430, binding = 3) buffer LODs {
LODMetadata lodMetadata[];
};
layout (std430, binding = 4) buffer Objects {
Object objects[];
};
layout (binding = 4) uniform sampler2D samplerDepth;
layout (binding = 5) uniform sampler2D samplerDepth;
shared vec4 sharedPlanes[PASSES][6];
@ -152,7 +158,7 @@ void main() {
float width = (aabb.z - aabb.x) * pyramidSize.x;
float height = (aabb.w - aabb.y) * pyramidSize.y;
float level = floor(log2(max(width, height)));
float level = mipLevels(vec2(width, height));
level = max(0.0, level);
float d1 = textureLod(samplerDepth, vec2(aabb.x, aabb.y), level).x;
@ -174,6 +180,27 @@ void main() {
}
}
#endif
#if LODS
if ( isVisible ) {
vec3 viewCenter = (camera.viewport[0].view * vec4(worldCenter, 1.0)).xyz;
float P11 = camera.viewport[0].projection[1][1];
float screenRadius = (worldRadius * P11) / max(abs(viewCenter.z), 0.001);
uint lodLevel = 0;
if ( screenRadius < 0.5 ) lodLevel = 1;
if ( screenRadius < 0.2 ) lodLevel = 2;
if ( screenRadius < 0.05 ) lodLevel = 3;
lodLevel = min(lodLevel, MAX_LODS - 1);
LOD lod = lodMetadata[drawCommand.instanceID].levels[lodLevel];
if ( lod.indices > 0 ) {
drawCommands[gID].indices = lod.indices;
drawCommands[gID].indexID = lod.indexID;
}
}
#endif
drawCommands[gID].instances = isVisible ? 1 : 0;
}

View File

@ -106,6 +106,7 @@ namespace pod {
uf::renderer::Buffer drawCommands;
uf::renderer::Buffer instance;
uf::renderer::Buffer instanceAddresses;
uf::renderer::Buffer lodMetadata;
uf::renderer::Buffer joint;
uf::renderer::Buffer object;
uf::renderer::Buffer material;

View File

@ -11,6 +11,9 @@ namespace ext {
namespace meshopt {
bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX, bool verbose = false );
uf::stl::vector<float> computeLODs( size_t count, size_t maxLODs = 4, size_t minIndices = 32 );
uf::stl::vector<pod::LODMetadata> UF_API generateLODs( uf::Mesh&, const uf::stl::vector<float>&, bool verbose = false );
template<typename T, typename U = uint32_t>
bool optimize( uf::Meshlet_T<T,U>& meshlet, float simplify = 1.0f, size_t o = SIZE_MAX, bool verbose = false ) {
#if UF_USE_MESHOPT
@ -39,7 +42,7 @@ namespace ext {
float targetError = 1e-2f / simplify;
float realError = 0.0f;
size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetError, realError);
size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), indicesCount * simplify, targetError);
// size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetIndices);
if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError);

View File

@ -6,9 +6,6 @@
namespace ext {
namespace xatlas {
size_t UF_API unwrap( pod::Graph& );
size_t UF_API unwrapLazy( pod::Graph& );
size_t UF_API unwrapExperimental( pod::Graph& );
}
}
#endif

View File

@ -57,7 +57,8 @@ namespace pod {
}
};
typedef uf::stl::unordered_set<pair_t, PairHash, PairEq> pairs_t;
// typedef uf::stl::unordered_set<pair_t, PairHash, PairEq> pairs_t;
typedef uf::stl::vector<pair_t> pairs_t;
struct Node {
BVH::index_t left = 0;
@ -232,13 +233,13 @@ namespace pod {
bool blockContactSolver = true; // use BlockNxN solvers (where N = number of contacts for a manifold)
bool psgContactSolver = true; // use PSG contact solver
bool useGjk = false; // currently don't have a way to broadphase mesh => narrowphase tri via GJK
bool fixedStep = false; // run physics simulation with a fixed delta time (with accumulation), rather than rely on actual engine deltatime
uint32_t substeps = 4; // number of substeps per frame tick
bool fixedStep = true; // run physics simulation with a fixed delta time (with accumulation), rather than rely on actual engine deltatime
uint32_t substeps = 1; // number of substeps per frame tick
uint32_t reserveCount = 32; // amount of elements to reserve for vectors used in this system, to-do: have it tie to a memory pool allocator
// increasing these make things lag for reasons I can imagine why
uint32_t broadphaseBvhCapacity = 2; // number of bodies per leaf node
uint32_t meshBvhCapacity = 2; // number of triangles per leaf node
uint32_t broadphaseBvhCapacity = 1; // number of bodies per leaf node
uint32_t meshBvhCapacity = 1; // number of triangles per leaf node
// additionally flattens a BVH for linear iteration, rather than a recursive / stack-based traversal
bool flattenBvhBodies = true;

View File

@ -73,6 +73,14 @@ namespace pod {
alignas(4) uint32_t vertices = 0; // stores vertex count, should be unused
};
// stores index offsets for LODs
struct UF_API LODMetadata {
struct Level {
alignas(4) uint32_t indices = 0;
alignas(4) uint32_t indexID = 0;
} levels[4];
};
// stores information about how to transform a draw call
// to-do: clean up this mess
struct UF_API Instance {
@ -133,6 +141,7 @@ namespace pod {
struct Primitive {
pod::DrawCommand drawCommand;
pod::Instance instance;
pod::LODMetadata lod;
};
}
@ -265,9 +274,9 @@ namespace uf {
void eraseAttribute( uf::Mesh::Input&, const uf::Mesh::Attribute& );
void eraseAttribute( uf::Mesh::Input&, size_t );
uf::Mesh::Input remapInput( const uf::Mesh::Input&, size_t i = 0 ) const;
uf::Mesh::Input remapVertexInput( size_t i = 0 ) const;
uf::Mesh::Input remapIndexInput( size_t i = 0 ) const;
uf::Mesh::Input remapInput( const uf::Mesh::Input&, size_t = 0, size_t = 0 ) const;
uf::Mesh::Input remapVertexInput( size_t i = 0, size_t = 0 ) const;
uf::Mesh::Input remapIndexInput( size_t i = 0, size_t = 0 ) const;
void print( bool = true ) const;
@ -276,9 +285,9 @@ namespace uf {
std::string printInstances( bool = true ) const;
std::string printIndirects( bool = true ) const;
uf::Mesh::View makeView( const uf::stl::vector<uf::stl::string>& wanted = {} ) const;
uf::Mesh::View makeView( size_t commandIndex, const uf::stl::vector<uf::stl::string>& wanted = {} ) const;
uf::stl::vector<uf::Mesh::View> makeViews( const uf::stl::vector<uf::stl::string>& wanted = {} ) const;
uf::Mesh::View makeView( const uf::stl::vector<uf::stl::string>& wanted = {}, size_t index = 0 ) const;
uf::Mesh::View makeView( size_t commandIndex, const uf::stl::vector<uf::stl::string>& wanted = {}, size_t index = 0 ) const;
uf::stl::vector<uf::Mesh::View> makeViews( const uf::stl::vector<uf::stl::string>& wanted = {}, size_t index = 0 ) const;
inline bool hasVertex( const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) const { return _hasV( vertex, descriptors ); }
inline bool hasVertex( const uf::Mesh& mesh ) const { return _hasV( vertex, mesh.vertex ); }

View File

@ -185,10 +185,20 @@ namespace {
return drawCommand;
}
pod::LODMetadata decodeLODMetadata( ext::json::Value& json, pod::Graph& graph ) {
pod::LODMetadata lodMetadata;
ext::json::forEach( json, [&]( size_t i, ext::json::Value& value ){
lodMetadata.levels[i].indices = value["indices"].as( lodMetadata.levels[i].indices );
lodMetadata.levels[i].indexID = value["indexID"].as( lodMetadata.levels[i].indexID );
});
return lodMetadata;
}
pod::Primitive decodePrimitive( ext::json::Value& json, pod::Graph& graph ) {
pod::Primitive prim;
prim.instance = decodeInstance( json["instance"], graph );
prim.drawCommand = decodeDrawCommand( json["drawCommand"], graph );
prim.lod = decodeLODMetadata( json["lod"], graph );
return prim;
}

View File

@ -135,10 +135,21 @@ namespace {
json["vertices"] = drawCommand.vertices;
return json;
}
uf::Serializer encode( const pod::LODMetadata& lodMetadata, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
ext::json::reserve( json, 4 );
for ( size_t i = 0; i < 4; ++i ) {
auto& value = json.emplace_back();
value["indices"] = lodMetadata.levels[i].indices;
value["indexID"] = lodMetadata.levels[i].indexID;
}
return json;
}
uf::Serializer encode( const pod::Primitive& primitive, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["drawCommand"] = encode( primitive.drawCommand, settings, graph );
json["instance"] = encode( primitive.instance, settings, graph );
json["lod"] = encode( primitive.lod, settings, graph );
return json;
}

View File

@ -477,6 +477,7 @@ namespace {
shader.aliasBuffer( "camera", storage.buffers.camera );
shader.aliasBuffer( "indirect", *indirect );
shader.aliasBuffer( "instance", storage.buffers.instance );
shader.aliasBuffer( "lodMetadata", storage.buffers.lodMetadata );
shader.aliasBuffer( "object", storage.buffers.object );
shader.textures.clear();
@ -1361,6 +1362,7 @@ void uf::graph::initialize( pod::Graph::Storage& storage, size_t initialElements
storage.buffers.drawCommands.initialize( (const void*) nullptr, sizeof(pod::DrawCommand) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.instance.initialize( (const void*) nullptr, sizeof(pod::Instance) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.instanceAddresses.initialize( (const void*) nullptr, sizeof(pod::Instance::Addresses) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.lodMetadata.initialize( (const void*) nullptr, sizeof(pod::LODMetadata) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.joint.initialize( (const void*) nullptr, sizeof(pod::Matrix4f) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.object.initialize( (const void*) nullptr, sizeof(pod::Instance::Object) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.material.initialize( (const void*) nullptr, sizeof(pod::Material) * initialElements, uf::renderer::enums::Buffer::STORAGE );
@ -1397,6 +1399,7 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
static thread_local uf::stl::vector<pod::Instance> instances;
static thread_local uf::stl::vector<pod::Instance::Addresses> instanceAddresses;
static thread_local uf::stl::vector<pod::LODMetadata> lodMetadata;
static thread_local uf::stl::vector<pod::Matrix4f> joints;
static thread_local uf::stl::vector<pod::Instance::Object> objects;
static thread_local uf::stl::vector<pod::Material> materials;
@ -1430,10 +1433,15 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
rebuild = storage.buffers.object.update( (const void*) objects.data(), objects.size() * sizeof(pod::Instance::Object) ) || rebuild;
if ( ::newGraphAdded ) {
drawCommands.clear();
instances.clear();
lodMetadata.clear();
for ( auto& key : storage.primitives.keys ) {
for ( auto& primitive : storage.primitives[key] ) {
drawCommands.emplace_back( primitive.drawCommand );
instances.emplace_back( primitive.instance );
lodMetadata.emplace_back( primitive.lod );
}
}
@ -1448,14 +1456,10 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
materials.clear();
for ( auto& key : storage.materials.keys ) materials.emplace_back( storage.materials.map[key] );
drawCommands.clear();
for ( auto& key : storage.primitives.keys ) {
for ( auto& primitive : storage.primitives[key] ) drawCommands.emplace_back( primitive.drawCommand );
}
rebuild = storage.buffers.instance.update( (const void*) instances.data(), instances.size() * sizeof(pod::Instance) ) || rebuild;
rebuild = storage.buffers.instanceAddresses.update( (const void*) instanceAddresses.data(), instanceAddresses.size() * sizeof(pod::Instance::Addresses) ) || rebuild;
rebuild = storage.buffers.drawCommands.update( (const void*) drawCommands.data(), drawCommands.size() * sizeof(pod::DrawCommand) ) || rebuild;
rebuild = storage.buffers.lodMetadata.update( (const void*) lodMetadata.data(), lodMetadata.size() * sizeof(pod::LODMetadata) ) || rebuild;
rebuild = storage.buffers.material.update( (const void*) materials.data(), materials.size() * sizeof(pod::Material) ) || rebuild;
rebuild = storage.buffers.texture.update( (const void*) textures.data(), textures.size() * sizeof(pod::Texture) ) || rebuild;

View File

@ -282,6 +282,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
struct {
bool should = false;
bool print = false;
bool lods = false;
size_t level = SIZE_MAX;
float simplify = 1.0f;
} meshopt;
@ -309,6 +310,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
meshopt.level = value["optimize meshlets"]["level"].as(meshopt.level);
meshopt.simplify = value["optimize meshlets"]["simplify"].as(meshopt.simplify);
meshopt.print = value["optimize meshlets"]["print"].as(meshopt.print);
meshopt.lods = value["optimize meshlets"]["lods"].as(meshopt.lods);
}
});
}
@ -522,6 +524,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
size_t level = SIZE_MAX;
float simplify = 1.0f;
bool print = false;
bool lods = false;
if ( graph.metadata["exporter"]["optimize"].as<uf::stl::string>("") == "tagged" ) {
bool should = false;
@ -536,6 +539,7 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
level = value["optimize mesh"]["level"].as(level);
simplify = value["optimize mesh"]["simplify"].as(simplify);
print = value["optimize mesh"]["print"].as(print);
lods = value["optimize mesh"]["lods"].as(lods);
}
});
@ -547,6 +551,18 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
if ( !ext::meshopt::optimize( mesh, simplify, level, print ) ) {
UF_MSG_ERROR("Mesh optimization failed: {}", keyName );
}
if ( lods ) {
auto factors = ext::meshopt::computeLODs( mesh.index.count );
auto lodMetadata = ext::meshopt::generateLODs( mesh, factors, print );
if ( lodMetadata.empty() ) {
UF_MSG_ERROR("LOD generation failed: {}", keyName );
} else {
UF_MSG_DEBUG("Generated {} LODs: {}", factors.size() - 1, keyName);
auto& primitives = storage.primitives[keyName];
UF_ASSERT( primitives.size() == lodMetadata.size() );
for ( auto i = 0; i < primitives.size(); ++i ) primitives[i].lod = lodMetadata[i];
}
}
}
UF_MSG_DEBUG( "Optimized mesh" );

View File

@ -356,7 +356,7 @@ if ( meshgrid.grid.divisions.x > 1 || meshgrid.grid.divisions.y > 1 || meshgrid.
auto partitioned = uf::meshgrid::partition( meshgrid.grid, meshlets, meshgrid.eps, meshgrid.clip, meshgrid.cleanup );
if ( meshgrid.print ) UF_MSG_DEBUG( "Draw commands: {}: {} -> {} | Partitions: {} -> {}", m.name, meshlets.size(), partitioned.size(),
(meshgrid.grid.divisions.x * meshgrid.grid.divisions.y * meshgrid.grid.divisions.z), meshgrid.grid.nodes.size()
);
);
meshlets = std::move( partitioned );
}
@ -367,6 +367,15 @@ if ( meshopt.should ) {
if ( !ext::meshopt::optimize( meshlet, meshopt.simplify, meshopt.level, meshopt.print ) ) {
UF_MSG_ERROR("Mesh optimization failed: {}", keyName );
}
/*
if ( meshopt.lods ) {
auto factors = ext::meshopt::computeLODs( meshlet.indices.size() );
auto lodMetadata = ext::meshopt::generateLODs( meshlet, factors, meshopt.print );
if ( lodMetadata.empty() ) {
UF_MSG_ERROR("LOD generation failed: {}", keyName );
}
}
*/
}
}
#endif

View File

@ -4,175 +4,224 @@
bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verbose ) {
if ( mesh.isInterleaved() ) {
UF_MSG_ERROR("optimization of interleaved meshes is currently not supported. Consider optimizing on meshlets.");
UF_MSG_ERROR("Optimization of interleaved meshes is currently not supported. Consider optimizing on meshlets.");
return false;
}
mesh.updateDescriptor();
struct Remap {
uf::Mesh::Attribute attribute;
uf::stl::vector<uint8_t> buffer;
};
uf::stl::vector<Remap> attributes;
uf::stl::vector<meshopt_Stream> streams;
for ( auto& attribute : mesh.vertex.attributes ) {
auto& p = attributes.emplace_back();
p.attribute = attribute;
auto& stream = streams.emplace_back();
stream.data = p.attribute.pointer;
stream.size = p.attribute.descriptor.size;
stream.stride = p.attribute.stride;
const auto& views = mesh.buffer_views;
if ( views.empty() ) {
UF_MSG_ERROR("No buffer views found. Cannot optimize per-submesh.");
return false;
}
bool hasIndices = mesh.index.count > 0;
size_t indicesCount = hasIndices ? mesh.index.count : mesh.vertex.count;
uf::stl::vector<uint32_t> remap(indicesCount);
uf::stl::vector<uint32_t> optIndices;
pod::DrawCommand* drawCommands = mesh.indirect.count > 0 ? (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data() : nullptr;
uint32_t* sourceIndicesPointer = NULL;
uf::stl::vector<uint32_t> sourceIndices(indicesCount);
if ( hasIndices ) {
uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data();
for ( auto index = 0; index < indicesCount; ++index ) {
switch ( mesh.index.size ) {
case 1: sourceIndices[index] = (( uint8_t*) pointer)[index]; break;
case 2: sourceIndices[index] = ((uint16_t*) pointer)[index]; break;
case 4: sourceIndices[index] = ((uint32_t*) pointer)[index]; break;
}
}
}
for ( size_t viewIdx = 0; viewIdx < views.size(); ++viewIdx ) {
const auto& view = views[viewIdx];
auto& indicesView = view["index"];
auto& positionsView = view["position"];
size_t verticesCount = meshopt_generateVertexRemapMulti( &remap[0], sourceIndicesPointer, indicesCount, indicesCount, &streams[0], streams.size() );
if ( !indicesView.valid() || !positionsView.valid() ) continue;
// generate new indices, as they're going to be specific to a region of vertices due to drawcommand shittery
uf::stl::vector<uint32_t> indices(indicesCount);
meshopt_remapIndexBuffer(&indices[0], sourceIndicesPointer, indicesCount, &remap[0]);
size_t indicesCount = view.index.count;
size_t vertexCount = view.vertex.count;
//
for ( auto& p : attributes ) {
auto& buffer = p.buffer;
buffer.resize(verticesCount * p.attribute.descriptor.size);
meshopt_remapVertexBuffer(&buffer[0], p.attribute.pointer, indicesCount, p.attribute.descriptor.size, &remap[0]);
}
//
meshopt_optimizeVertexCache(&indices[0], &indices[0], indicesCount, verticesCount);
//
meshopt_optimizeVertexFetchRemap(&remap[0], &indices[0], indicesCount, verticesCount);
//
for ( auto& p : attributes ) {
auto& buffer = p.buffer;
p.attribute.pointer = &buffer[0];
meshopt_remapVertexBuffer(p.attribute.pointer, p.attribute.pointer, verticesCount, p.attribute.descriptor.size, &remap[0]);
}
// almost always causes ID discontinuities
if ( 0.0f < simplify && simplify < 1.0f ) {
uf::stl::vector<uint32_t> indicesSimplified(indicesCount);
uf::Mesh::Attribute positionAttribute;
for ( auto& p : attributes ) if ( p.attribute.descriptor.name == "position" ) positionAttribute = p.attribute;
size_t targetIndices = indicesCount * simplify;
float targetError = 1e-2f / simplify;
float realError = 0.0f;
size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetError, realError);
// size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetIndices);
if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError);
indicesCount = realIndices;
indices.swap( indicesSimplified );
}
// done
if ( mesh.indirect.count ) {
bool discontinuityDetected = false;
size_t lastID = 0;
struct Remap {
pod::DrawCommand* drawCommand;
struct {
size_t start = SIZE_MAX;
size_t end = 0;
} index, vertex;
};
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
uf::stl::vector<Remap> remappedDrawCommands( mesh.indirect.count );
uf::Mesh::Attribute idAttribute;
for ( auto& p : attributes ) if ( p.attribute.descriptor.name == "id" ) idAttribute = p.attribute;
for ( size_t index = 0; index < indicesCount; ++index ) {
size_t vertex = indices[index];
pod::Vector<uint16_t,2>& id = *(pod::Vector<uint16_t,2>*) ( static_cast<uint8_t*>(idAttribute.pointer) + idAttribute.stride * vertex );
auto& d = remappedDrawCommands[id.x];
d.drawCommand = &drawCommands[id.x];
d.vertex.start = std::min( d.vertex.start, vertex );
d.vertex.end = std::max( d.vertex.end, vertex );
d.index.start = std::min( d.index.start, index );
d.index.end = std::max( d.index.end, index );
if ( lastID == id.x ) {
} else if ( lastID + 1 == id.x ) {
lastID = id.x;
} else {
if ( verbose ) UF_MSG_DEBUG("Discontinuity detected: {} | {} | {} | {}", index, vertex, lastID, id.x);
discontinuityDetected = true;
lastID = id.x;
uf::stl::vector<uint32_t> submeshIndices(indicesCount);
for ( size_t i = 0; i < indicesCount; ++i ) {
size_t global_i = view.index.first + i;
switch ( indicesView.attribute.descriptor.size ) {
case 1: submeshIndices[i] = indicesView.get<uint8_t>(global_i)[0]; break;
case 2: submeshIndices[i] = indicesView.get<uint16_t>(global_i)[0]; break;
case 4: submeshIndices[i] = indicesView.get<uint32_t>(global_i)[0]; break;
}
}
if ( discontinuityDetected ) {
UF_MSG_ERROR("Discontinuity detected, bailing...");
return false;
}
meshopt_optimizeVertexCache(&submeshIndices[0], &submeshIndices[0], indicesCount, mesh.vertex.count);
for ( auto& d : remappedDrawCommands ) {
d.drawCommand->indices = d.index.end - d.index.start + 1;
d.drawCommand->indexID = d.index.start;
d.drawCommand->vertexID = d.vertex.start;
d.drawCommand->vertices = d.vertex.end - d.vertex.start + 1;
}
meshopt_optimizeOverdraw(
&submeshIndices[0],
&submeshIndices[0],
indicesCount,
(const float*) positionsView.data(),
mesh.vertex.count,
positionsView.stride(),
1.05f
);
for ( size_t index = 0; index < indicesCount; ++index ) {
auto& vertex = indices[index];
pod::Vector<uint16_t,2>& id = *(pod::Vector<uint16_t,2>*) ( static_cast<uint8_t*>(idAttribute.pointer) + idAttribute.stride * vertex );
if ( 0.0f < simplify && simplify < 1.0f ) {
uf::stl::vector<uint32_t> indicesSimplified(indicesCount);
auto& d = remappedDrawCommands[id.x];
vertex -= d.vertex.start;
}
}
float targetError = 0.1; // 1e-2f / simplify;
float realError = 0.0f;
mesh.index.count = indicesCount;
mesh.vertex.count = verticesCount;
size_t realIndices = meshopt_simplify(
&indicesSimplified[0],
&submeshIndices[0],
indicesCount,
(const float*) positionsView.data(),
mesh.vertex.count,
positionsView.stride(),
indicesCount * simplify,
targetError
//,0, &realError
);
// vertices
for ( size_t i = 0; i < attributes.size(); ++i ) {
auto& attribute = mesh.vertex.attributes[i];
auto& remapped = attributes[i];
mesh.buffers[attribute.buffer].swap( remapped.buffer );
attribute.pointer = mesh.buffers[attribute.buffer].data();
}
// indices
{
mesh.resizeIndices( mesh.index.count );
uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data();
for ( auto index = 0; index < indicesCount; ++index ) {
switch ( mesh.index.size ) {
case 1: (( uint8_t*) pointer)[index] = indices[index]; break;
case 2: ((uint16_t*) pointer)[index] = indices[index]; break;
case 4: ((uint32_t*) pointer)[index] = indices[index]; break;
if ( verbose ) {
UF_MSG_DEBUG("[View {} Simplified] indices: {} -> {} | error: {} -> {}", viewIdx, indicesCount, realIndices, targetError, realError);
}
indicesCount = realIndices;
submeshIndices.swap(indicesSimplified);
submeshIndices.resize(indicesCount);
}
size_t newIndexStart = optIndices.size();
optIndices.insert(optIndices.end(), submeshIndices.begin(), submeshIndices.end());
if ( drawCommands ) {
drawCommands[view.indirectIndex].indexID = newIndexStart;
drawCommands[view.indirectIndex].indices = indicesCount;
}
}
mesh.index.count = optIndices.size();
mesh.resizeIndices( mesh.index.count );
uint8_t* dstPointer = (uint8_t*) mesh.getBuffer(mesh.index).data();
for ( size_t i = 0; i < optIndices.size(); ++i ) {
switch ( mesh.index.size ) {
case 1: (( uint8_t*) dstPointer)[i] = (uint8_t) optIndices[i]; break;
case 2: ((uint16_t*) dstPointer)[i] = (uint16_t) optIndices[i]; break;
case 4: ((uint32_t*) dstPointer)[i] = (uint32_t) optIndices[i]; break;
}
}
mesh.updateDescriptor();
return true;
}
uf::stl::vector<float> ext::meshopt::computeLODs( size_t count, size_t maxLODs, size_t minIndices ) {
uf::stl::vector<float> factors;
factors.reserve(maxLODs);
factors.emplace_back(1.0f);
float factor = 1.0f;
for (size_t i = 1; i < maxLODs; ++i) {
factor *= 0.5f;
if ( count * factor < minIndices ) break;
factors.emplace_back(factor);
}
return factors;
}
uf::stl::vector<pod::LODMetadata> ext::meshopt::generateLODs( uf::Mesh& mesh, const uf::stl::vector<float>& lodFactors, bool verbose ) {
uf::stl::vector<pod::LODMetadata> lodMetadata;
if ( mesh.isInterleaved() ) {
UF_MSG_ERROR("Cannot generate LODs on interleaved meshes.");
return lodMetadata;
}
mesh.updateDescriptor();
const auto& views = mesh.buffer_views;
if ( views.empty() ) return lodMetadata;
size_t numLODs = std::min(lodFactors.size(), (size_t)4);
lodMetadata.resize(mesh.indirect.count);
uf::stl::vector<uf::stl::vector<uint32_t>> lodBlocks(numLODs);
pod::DrawCommand* drawCommands = mesh.indirect.count > 0 ? (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data() : nullptr;
for ( size_t viewIdx = 0; viewIdx < views.size(); ++viewIdx ) {
const auto& view = views[viewIdx];
uint32_t cmdIdx = view.indirectIndex;
auto& indicesView = view["index"];
auto& positionsView = view["position"];
size_t baseIndicesCount = view.index.count;
uf::stl::vector<uint32_t> baseIndices(baseIndicesCount);
for ( size_t i = 0; i < baseIndicesCount; ++i ) {
size_t global_i = view.index.first + i;
switch ( indicesView.attribute.descriptor.size ) {
case 1: baseIndices[i] = indicesView.get<uint8_t>(global_i)[0]; break;
case 2: baseIndices[i] = indicesView.get<uint16_t>(global_i)[0]; break;
case 4: baseIndices[i] = indicesView.get<uint32_t>(global_i)[0]; break;
}
}
meshopt_optimizeVertexCache(&baseIndices[0], &baseIndices[0], baseIndicesCount, mesh.vertex.count);
for ( size_t lodIdx = 0; lodIdx < numLODs; ++lodIdx ) {
float simplify = lodFactors[lodIdx];
uf::stl::vector<uint32_t> lodIndices = baseIndices;
size_t currentIndicesCount = baseIndicesCount;
if ( simplify < 1.0f ) {
float targetError = 0.1; // 1e-2f / simplify;
float realError = 0.0f;
currentIndicesCount = meshopt_simplify(
&lodIndices[0], &baseIndices[0], baseIndicesCount,
(const float*)positionsView.data(0), mesh.vertex.count, positionsView.stride(),
baseIndicesCount * simplify, targetError
//, 0, &realError
);
if ( baseIndicesCount == currentIndicesCount ) {
continue;
}
if ( verbose ) {
UF_MSG_DEBUG("[View {} Simplified LOD {}] indices: {} -> {} | error: {} -> {}", viewIdx, lodIdx, baseIndicesCount, currentIndicesCount, targetError, realError);
}
lodIndices.resize(currentIndicesCount);
}
lodMetadata[cmdIdx].levels[lodIdx].indexID = lodBlocks[lodIdx].size();
lodMetadata[cmdIdx].levels[lodIdx].indices = currentIndicesCount;
lodBlocks[lodIdx].insert(lodBlocks[lodIdx].end(), lodIndices.begin(), lodIndices.end());
}
}
uf::stl::vector<uint32_t> unifiedIndices;
size_t currentGlobalOffset = 0;
for ( size_t lodIdx = 0; lodIdx < numLODs; ++lodIdx ) {
for ( size_t viewIdx = 0; viewIdx < views.size(); ++viewIdx ) {
uint32_t cmdIdx = views[viewIdx].indirectIndex;
lodMetadata[cmdIdx].levels[lodIdx].indexID += currentGlobalOffset;
if ( lodIdx == 0 && drawCommands ) {
drawCommands[cmdIdx].indexID = lodMetadata[cmdIdx].levels[0].indexID;
drawCommands[cmdIdx].indices = lodMetadata[cmdIdx].levels[0].indices;
}
}
unifiedIndices.insert(unifiedIndices.end(), lodBlocks[lodIdx].begin(), lodBlocks[lodIdx].end());
currentGlobalOffset = unifiedIndices.size();
}
mesh.index.count = unifiedIndices.size();
mesh.resizeIndices( mesh.index.count );
uint8_t* dstPointer = (uint8_t*) mesh.getBuffer(mesh.index).data();
for ( size_t i = 0; i < unifiedIndices.size(); ++i ) {
switch ( mesh.index.size ) {
case 1: (( uint8_t*) dstPointer)[i] = (uint8_t) unifiedIndices[i]; break;
case 2: ((uint16_t*) dstPointer)[i] = (uint16_t) unifiedIndices[i]; break;
case 4: ((uint32_t*) dstPointer)[i] = (uint32_t) unifiedIndices[i]; break;
}
}
mesh.updateDescriptor();
return lodMetadata;
}
#endif

View File

@ -2,13 +2,9 @@
#if UF_USE_XATLAS
#include <xatlas/xatlas.h>
#define UF_XATLAS_UNWRAP_MULTITHREAD 1
#define UF_XATLAS_UNWRAP_SERIAL 1 // really big scenes will gorge on memory
#define UF_XATLAS_UNWRAP_MULTITHREAD 0 // prone to crashing
size_t ext::xatlas::unwrap( pod::Graph& graph ) {
return graph.metadata["exporter"]["unwrap lazy"].as<bool>(false) ? unwrapLazy( graph ) : unwrapExperimental( graph );
}
size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) {
struct Entry {
size_t index = 0;
size_t commandID = 0;
@ -60,81 +56,50 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) {
if ( !should ) continue;
source = mesh;
source.updateDescriptor();
source.updateDescriptor();
uf::Mesh::Input vertexInput = mesh.vertex;
for ( size_t viewIdx = 0; viewIdx < source.buffer_views.size(); ++viewIdx ) {
const auto& view = source.buffer_views[viewIdx];
uf::Mesh::Attribute positionAttribute;
uf::Mesh::Attribute uvAttribute;
uf::Mesh::Attribute stAttribute;
size_t atlasID = 0;
if ( view.indirectIndex != -1 ) {
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
atlasID = drawCommands[view.indirectIndex].auxID;
}
for ( auto& attribute : mesh.vertex.attributes ) {
if ( attribute.descriptor.name == "position" ) positionAttribute = attribute;
else if ( attribute.descriptor.name == "uv" ) uvAttribute = attribute;
else if ( attribute.descriptor.name == "st" ) stAttribute = attribute;
}
UF_ASSERT( positionAttribute.descriptor.name == "position" && uvAttribute.descriptor.name == "uv" && stAttribute.descriptor.name == "st" );
auto& atlas = atlases[atlasID];
auto& entry = atlas.entries.emplace_back();
entry.index = index;
entry.commandID = viewIdx;
if ( mesh.index.count ) {
uf::Mesh::Input indexInput = mesh.index;
uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front();
auto& decl = entry.decl;
auto posView = view["position"];
auto uvView = view["uv"];
auto idxView = view["index"];
::xatlas::IndexFormat indexType = ::xatlas::IndexFormat::UInt32;
switch ( mesh.index.size ) {
case sizeof(uint16_t): indexType = ::xatlas::IndexFormat::UInt16; break;
case sizeof(uint32_t): indexType = ::xatlas::IndexFormat::UInt32; break;
default: UF_EXCEPTION("unsupported index type"); break;
}
UF_ASSERT( posView.valid() && uvView.valid() );
if ( mesh.indirect.count ) {
auto& primitives = /*graph.storage*/storage.primitives[name];
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
decl.vertexCount = view.vertex.count;
decl.vertexPositionData = posView.data(view.vertex.first);
decl.vertexPositionStride = posView.stride();
decl.vertexUvData = uvView.data(view.vertex.first);
decl.vertexUvStride = uvView.stride();
for ( auto i = 0; i < mesh.indirect.count; ++i ) {
size_t atlasID = drawCommands[i].auxID;
if ( idxView.valid() ) {
decl.indexCount = view.index.count;
// Pass view.index.first to offset the index pointer!
decl.indexData = idxView.data(view.index.first);
vertexInput = mesh.remapVertexInput( i );
indexInput = mesh.remapIndexInput( i );
auto& atlas = atlases[atlasID];
auto& entry = atlas.entries.emplace_back();
entry.index = index;
entry.commandID = i;
auto& decl = entry.decl;
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
decl.vertexPositionStride = positionAttribute.stride;
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
decl.vertexUvStride = uvAttribute.stride;
decl.vertexCount = vertexInput.count;
decl.indexCount = indexInput.count;
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
decl.indexFormat = indexType;
}
} else {
size_t atlasID = 0;
auto& atlas = atlases[atlasID];
auto& entry = atlas.entries.emplace_back();
entry.index = index;
auto& decl = entry.decl;
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
decl.vertexPositionStride = positionAttribute.stride;
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
decl.vertexUvStride = uvAttribute.stride;
decl.vertexCount = vertexInput.count;
decl.indexCount = indexInput.count;
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
decl.indexFormat = indexType;
}
} else UF_EXCEPTION("to-do: not require indices for meshes");
switch ( idxView.attribute.descriptor.size ) {
case 1: UF_EXCEPTION("xatlas does not support 8-bit indices"); break;
case 2: decl.indexFormat = ::xatlas::IndexFormat::UInt16; break;
case 4: decl.indexFormat = ::xatlas::IndexFormat::UInt32; break;
default: UF_EXCEPTION("unsupported index type"); break;
}
} else {
decl.indexCount = 0;
}
}
}
::xatlas::ChartOptions chartOptions{};
@ -168,7 +133,6 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) {
}
}
// pack
#if UF_XATLAS_UNWRAP_MULTITHREAD
auto tasks = uf::thread::schedule(true);
@ -272,108 +236,70 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) {
// update vertices
for ( auto& pair : atlases ) {
auto& atlas = pair.second;
auto& atlas = pair.second;
size_t vertexIDOffset = 0;
for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) {
auto& xmesh = atlas.pointer->meshes[i];
auto& entry = atlas.entries[i];
auto& name = graph.meshes[entry.index];
auto& mesh = /*graph.storage*/storage.meshes[name];
auto& source = sources[entry.index];
for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) {
auto& xmesh = atlas.pointer->meshes[i];
auto& entry = atlas.entries[i];
if ( source.vertex.count == 0 ) continue;
auto& name = graph.meshes[entry.index];
auto& mesh = storage.meshes[name];
auto& source = sources[entry.index];
// draw commands
if ( mesh.indirect.count ) {
auto srcInput = source.remapVertexInput( entry.commandID );
auto dstInput = mesh.remapVertexInput( entry.commandID );
if ( source.vertex.count == 0 ) continue;
auto& primitives = /*graph.storage*/storage.primitives[name];
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
// Grab the read-only view from our unmodified source mesh
const auto& srcView = source.buffer_views[entry.commandID];
auto& drawCommand = drawCommands[entry.commandID];
auto& primitive = primitives[entry.commandID];
// We dynamically calculate the destination offsets using the updated draw commands
// Or if it's direct, it's just 0.
size_t dstVertexFirst = 0;
size_t dstIndexFirst = 0;
if ( mesh.indirect.count > 0 ) {
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
dstVertexFirst = drawCommands[entry.commandID].vertexID;
dstIndexFirst = drawCommands[entry.commandID].indexID;
}
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
auto ref = vertex.xref;
for ( auto _ = 0; _ < srcInput.attributes.size(); ++_ ) {
auto srcAttribute = srcInput.attributes[_];
auto dstAttribute = dstInput.attributes[_];
// 1. Copy over the vertices based on the xref mapping
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
uint32_t ref = vertex.xref; // original vertex index relative to the sub-mesh
memcpy(
static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * (j + dstInput.first),
static_cast<uint8_t*>(srcAttribute.pointer) + srcAttribute.stride * (ref + srcInput.first),
srcAttribute.stride
);
}
}
for ( auto attrIdx = 0; attrIdx < mesh.vertex.attributes.size(); ++attrIdx ) {
auto srcAttribute = srcView.vertex.attributes[attrIdx];
auto dstAttribute = mesh.vertex.attributes[attrIdx];
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
auto ref = vertex.xref;
for ( auto _ = 0; _ < srcInput.attributes.size(); ++_ ) {
auto dstAttribute = dstInput.attributes[_];
if ( dstAttribute.descriptor.name != "st" ) continue;
pod::Vector2f& st = *(pod::Vector2f*) ( static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * (j + dstInput.first) );
st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };;
}
}
uint8_t* dstPtr = static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * (dstVertexFirst + j);
// indices
if ( mesh.index.count ) {
uf::Mesh::Input indexInput = mesh.remapIndexInput( entry.commandID );
uf::Mesh::Attribute indexAttribute = indexInput.attributes.front();
uint8_t* pointer = (uint8_t*) static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
for ( auto index = 0; index < xmesh.indexCount; ++index ) {
switch ( mesh.index.size ) {
case 1: (( uint8_t*) pointer)[index] = xmesh.indexArray[index]; break;
case 2: ((uint16_t*) pointer)[index] = xmesh.indexArray[index]; break;
case 4: ((uint32_t*) pointer)[index] = xmesh.indexArray[index]; break;
}
}
}
} else {
uf::Mesh::Attribute stAttribute;
for ( auto& attribute : mesh.vertex.attributes ) if ( attribute.descriptor.name == "st" ) stAttribute = attribute;
UF_ASSERT( stAttribute.descriptor.name == "st" );
if ( dstAttribute.descriptor.name == "st" ) {
// Write new lightmap STs!
pod::Vector2f& st = *(pod::Vector2f*)dstPtr;
st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };
} else {
// Copy original vertex data
const uint8_t* srcPtr = static_cast<const uint8_t*>(srcAttribute.pointer) + srcAttribute.stride * (srcView.vertex.first + ref);
memcpy(dstPtr, srcPtr, srcAttribute.descriptor.size);
}
}
}
// vertices
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
auto ref = vertex.xref;
// 2. Write new indices
if ( mesh.index.count ) {
uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front();
uint8_t* dstIndexPtr = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * dstIndexFirst;
for ( auto k = 0; k < mesh.vertex.attributes.size(); ++k ) {
auto srcAttribute = source.vertex.attributes[k];
auto dstAttribute = mesh.vertex.attributes[k];
if ( dstAttribute.descriptor.name == "st" ) {
pod::Vector2f& st = *(pod::Vector2f*) ( ((uint8_t*) dstAttribute.pointer) + dstAttribute.stride * (mesh.vertex.first + j));
st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };
} else {
memcpy( static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * j, static_cast<uint8_t*>(srcAttribute.pointer) + srcAttribute.stride * ref, srcAttribute.stride );
}
}
}
// indices
if ( mesh.index.count ) {
// uint8_t* pointer = (uint8_t*) mesh.buffers[mesh.isInterleaved(mesh.index.interleaved) ? mesh.index.interleaved : mesh.index.attributes.front().buffer].data();
uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data();
for ( auto index = 0; index < xmesh.indexCount; ++index ) {
switch ( mesh.index.size ) {
case 1: (( uint8_t*) pointer)[index] = xmesh.indexArray[index]; break;
case 2: ((uint16_t*) pointer)[index] = xmesh.indexArray[index]; break;
case 4: ((uint32_t*) pointer)[index] = xmesh.indexArray[index]; break;
}
}
}
}
mesh.updateDescriptor();
}
}
for ( auto idx = 0; idx < xmesh.indexCount; ++idx ) {
switch ( mesh.index.size ) {
case 1: (( uint8_t*) dstIndexPtr)[idx] = (uint8_t) xmesh.indexArray[idx]; break;
case 2: ((uint16_t*) dstIndexPtr)[idx] = (uint16_t) xmesh.indexArray[idx]; break;
case 4: ((uint32_t*) dstIndexPtr)[idx] = (uint32_t) xmesh.indexArray[idx]; break;
}
}
}
mesh.updateDescriptor();
}
}
// cleanup
size_t atlasCount = 0;
@ -384,276 +310,4 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) {
}
return atlasCount;
}
size_t ext::xatlas::unwrapLazy( pod::Graph& graph ) {
struct Entry {
size_t index = 0;
size_t commandID = 0;
::xatlas::MeshDecl decl;
};
struct Atlas {
::xatlas::Atlas* pointer = NULL;
uf::stl::vector<Entry> entries;
size_t vertexOffset = 0;
};
uf::stl::unordered_map<size_t, Atlas> atlases;
atlases.reserve(graph.meshes.size());
auto& scene = uf::scene::getCurrentScene();
auto& storage = uf::graph::globalStorage ? uf::graph::storage : scene.getComponent<pod::Graph::Storage>();
// copy source meshes
// create mesh decls for passing to xatlas
for ( auto index = 0; index < graph.meshes.size(); ++index ) {
auto& name = graph.meshes[index];
auto& mesh = /*graph.storage*/storage.meshes[name];
if ( mesh.isInterleaved() ) {
UF_EXCEPTION("unwrapping interleaved mesh is not supported");
}
bool should = false;
if ( graph.metadata["exporter"]["unwrap"].is<bool>() && graph.metadata["exporter"]["unwrap"].as<bool>() ) {
should = true;
} else {
ext::json::forEach( graph.metadata["tags"], [&]( const uf::stl::string& key, ext::json::Value& value ) {
if ( uf::string::isRegex( key ) ) {
if ( !uf::string::matched( name, key ) ) return;
} else if ( name != key ) return;
if ( ext::json::isNull( value["unwrap mesh"] ) ) return;
if ( !value["unwrap mesh"].as<bool>(false) ) return;
should = true;
});
}
if ( !should ) continue;
uf::Mesh::Input vertexInput = mesh.vertex;
uf::Mesh::Attribute positionAttribute;
uf::Mesh::Attribute uvAttribute;
uf::Mesh::Attribute stAttribute;
for ( auto& attribute : mesh.vertex.attributes ) {
if ( attribute.descriptor.name == "position" ) positionAttribute = attribute;
else if ( attribute.descriptor.name == "uv" ) uvAttribute = attribute;
else if ( attribute.descriptor.name == "st" ) stAttribute = attribute;
}
UF_ASSERT( positionAttribute.descriptor.name == "position" && uvAttribute.descriptor.name == "uv" && stAttribute.descriptor.name == "st" );
if ( mesh.index.count ) {
uf::Mesh::Input indexInput = mesh.index;
uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front();
::xatlas::IndexFormat indexType = ::xatlas::IndexFormat::UInt32;
switch ( mesh.index.size ) {
case sizeof(uint16_t): indexType = ::xatlas::IndexFormat::UInt16; break;
case sizeof(uint32_t): indexType = ::xatlas::IndexFormat::UInt32; break;
default: UF_EXCEPTION("unsupported index type"); break;
}
if ( mesh.indirect.count ) {
auto& primitives = /*graph.storage*/storage.primitives[name];
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
for ( auto i = 0; i < mesh.indirect.count; ++i ) {
size_t atlasID = drawCommands[i].auxID;
vertexInput = mesh.remapVertexInput( i );
indexInput = mesh.remapIndexInput( i );
auto& atlas = atlases[atlasID];
auto& entry = atlas.entries.emplace_back();
entry.index = index;
entry.commandID = i;
auto& decl = entry.decl;
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
decl.vertexPositionStride = positionAttribute.stride;
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
decl.vertexUvStride = uvAttribute.stride;
decl.vertexCount = vertexInput.count;
decl.indexCount = indexInput.count;
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
decl.indexFormat = indexType;
}
} else {
size_t atlasID = 0;
auto& atlas = atlases[atlasID];
auto& entry = atlas.entries.emplace_back();
entry.index = index;
auto& decl = entry.decl;
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
decl.vertexPositionStride = positionAttribute.stride;
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
decl.vertexUvStride = uvAttribute.stride;
decl.vertexCount = vertexInput.count;
decl.indexCount = indexInput.count;
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
decl.indexFormat = indexType;
}
} else UF_EXCEPTION("to-do: not require indices for meshes");
}
::xatlas::ChartOptions chartOptions{};
chartOptions.useInputMeshUvs = graph.metadata["baking"]["settings"]["useInputMeshUvs"].as(chartOptions.useInputMeshUvs);
chartOptions.maxIterations = graph.metadata["baking"]["settings"]["maxIterations"].as(chartOptions.maxIterations);
::xatlas::PackOptions packOptions{};
packOptions.maxChartSize = graph.metadata["baking"]["settings"]["maxChartSize"].as(packOptions.maxChartSize);
packOptions.padding = graph.metadata["baking"]["settings"]["padding"].as(packOptions.padding);
packOptions.texelsPerUnit = graph.metadata["baking"]["settings"]["texelsPerUnit"].as(packOptions.texelsPerUnit);
packOptions.bilinear = graph.metadata["baking"]["settings"]["bilinear"].as(packOptions.bilinear);
packOptions.blockAlign = graph.metadata["baking"]["settings"]["blockAlign"].as(packOptions.blockAlign);
packOptions.bruteForce = graph.metadata["baking"]["settings"]["bruteForce"].as(packOptions.bruteForce);
packOptions.createImage = graph.metadata["baking"]["settings"]["createImage"].as(packOptions.createImage);
packOptions.rotateChartsToAxis = graph.metadata["baking"]["settings"]["rotateChartsToAxis"].as(packOptions.rotateChartsToAxis);
packOptions.rotateCharts = graph.metadata["baking"]["settings"]["rotateCharts"].as(packOptions.rotateCharts);
packOptions.resolution = graph.metadata["baking"]["resolution"].as(packOptions.resolution);
// pack
#if UF_XATLAS_UNWRAP_SERIAL
size_t atlasCount = 0;
for ( auto& pair : atlases ) {
auto& atlas = pair.second;
if ( !atlas.pointer ) atlas.pointer = ::xatlas::Create();
for ( auto& entry : atlas.entries ) {
::xatlas::AddMeshError error = ::xatlas::AddMesh(atlas.pointer, entry.decl, atlas.entries.size());
if (error != ::xatlas::AddMeshError::Success) {
::xatlas::Destroy(atlas.pointer);
UF_EXCEPTION("{}", ::xatlas::StringForEnum(error));
}
}
::xatlas::Generate(atlas.pointer, chartOptions, packOptions);
for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) {
auto& xmesh = atlas.pointer->meshes[i];
auto& entry = atlas.entries[i];
auto& name = graph.meshes[entry.index];
auto& mesh = /*graph.storage*/storage.meshes[name];
// draw commands
if ( mesh.indirect.count ) {
auto vertexInput = mesh.remapVertexInput( entry.commandID );
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
auto ref = vertex.xref;
for ( auto k = 0; k < vertexInput.attributes.size(); ++k ) {
auto dstAttribute = vertexInput.attributes[k];
if ( dstAttribute.descriptor.name != "st" ) continue;
pod::Vector2f& st = *(pod::Vector2f*) ( static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * (ref + vertexInput.first) );
st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };;
}
}
} else {
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
auto ref = vertex.xref;
for ( auto k = 0; k < mesh.vertex.attributes.size(); ++k ) {
auto dstAttribute = mesh.vertex.attributes[k];
if ( dstAttribute.descriptor.name != "st" ) continue;
pod::Vector2f& st = *(pod::Vector2f*) ( static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * (ref + mesh.vertex.first) );
st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };;
}
}
}
mesh.updateDescriptor();
}
::xatlas::Destroy(atlas.pointer);
++atlasCount;
}
#else
// add mesh decls to mesh atlases
// done after the fact since we'll know the total amount of meshes added
for ( auto& pair : atlases ) {
auto& atlas = pair.second;
if ( !atlas.pointer ) atlas.pointer = ::xatlas::Create();
for ( auto& entry : atlas.entries ) {
::xatlas::AddMeshError error = ::xatlas::AddMesh(atlas.pointer, entry.decl, atlas.entries.size());
if (error != ::xatlas::AddMeshError::Success) {
::xatlas::Destroy(atlas.pointer);
UF_EXCEPTION("{}", ::xatlas::StringForEnum(error));
}
}
}
#if UF_XATLAS_UNWRAP_MULTITHREAD
auto tasks = uf::thread::schedule(true);
#else
auto tasks = uf::thread::schedule(false);
#endif
for ( auto& pair : atlases ) {
tasks.queue([&]{
auto& atlas = pair.second;
::xatlas::Generate(atlas.pointer, chartOptions, packOptions);
});
}
uf::thread::execute( tasks );
// update vertices
for ( auto& pair : atlases ) {
auto& atlas = pair.second;
for ( auto i = 0; i < atlas.pointer->meshCount; i++ ) {
auto& xmesh = atlas.pointer->meshes[i];
auto& entry = atlas.entries[i];
auto& name = graph.meshes[entry.index];
auto& mesh = /*graph.storage*/storage.meshes[name];
// draw commands
if ( mesh.indirect.count ) {
auto vertexInput = mesh.remapVertexInput( entry.commandID );
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
auto ref = vertex.xref;
for ( auto k = 0; k < vertexInput.attributes.size(); ++k ) {
auto dstAttribute = vertexInput.attributes[k];
if ( dstAttribute.descriptor.name != "st" ) continue;
pod::Vector2f& st = *(pod::Vector2f*) ( static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * (ref + vertexInput.first) );
st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };;
}
}
} else {
for ( auto j = 0; j < xmesh.vertexCount; ++j ) {
auto& vertex = xmesh.vertexArray[j];
auto ref = vertex.xref;
for ( auto k = 0; k < mesh.vertex.attributes.size(); ++k ) {
auto dstAttribute = mesh.vertex.attributes[k];
if ( dstAttribute.descriptor.name != "st" ) continue;
pod::Vector2f& st = *(pod::Vector2f*) ( static_cast<uint8_t*>(dstAttribute.pointer) + dstAttribute.stride * (ref + mesh.vertex.first) );
st = pod::Vector2f{ vertex.uv[0] / atlas.pointer->width, vertex.uv[1] / atlas.pointer->height };;
}
}
}
mesh.updateDescriptor();
}
}
// cleanup
size_t atlasCount = 0;
for ( auto& pair : atlases ) {
auto& atlas = pair.second;
::xatlas::Destroy(atlas.pointer);
++atlasCount;
}
#endif
return atlasCount;
}
#endif

View File

@ -6,6 +6,11 @@ namespace {
void queryFlatOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs );
void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs );
void postprocessPairs( pod::BVH::pairs_t& pairs ) {
std::sort(pairs.begin(), pairs.end());
pairs.erase(std::unique(pairs.begin(), pairs.end()), pairs.end());
}
}
// BVH
@ -466,7 +471,7 @@ namespace {
if ( bodyA == bodyB ) continue;
if ( bodyA > bodyB ) std::swap( bodyA, bodyB );
pairs.emplace(bodyA, bodyB);
pairs.emplace_back(bodyA, bodyB);
}
}
return;
@ -496,17 +501,26 @@ namespace {
if ( bodyA == bodyB ) continue;
if ( bodyA > bodyB ) std::swap( bodyA, bodyB );
pairs.emplace(bodyA, bodyB);
pairs.emplace_back(bodyA, bodyB);
}
}
return;
}
if ( nodeA.getCount() == 0 ) {
if ( nodeA.getCount() == 0 && nodeB.getCount() == 0 ) {
if ( ::aabbSurfaceArea(bvhA.bounds[nodeAID]) > ::aabbSurfaceArea(bvhB.bounds[nodeBID]) ) {
::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, pairs );
::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, pairs );
} else {
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, pairs );
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, pairs );
}
}
else if ( nodeA.getCount() == 0 ) {
::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, pairs );
::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, pairs );
}
if ( nodeB.getCount() == 0 ) {
else if ( nodeB.getCount() == 0 ) {
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, pairs );
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, pairs );
}
@ -524,7 +538,7 @@ namespace {
if ( bodyA == bodyB ) continue;
if ( bodyA > bodyB ) std::swap( bodyA, bodyB );
pairs.emplace(bodyA, bodyB);
pairs.emplace_back(bodyA, bodyB);
}
}
return;
@ -539,6 +553,8 @@ namespace {
if ( bvh.nodes.empty() ) return;
outPairs.reserve(uf::physics::impl::settings.reserveCount);
::traverseBVH( bvh, 0, outPairs );
::postprocessPairs( outPairs );
}
void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ) {
@ -547,6 +563,8 @@ namespace {
if ( bvhA.nodes.empty() || bvhB.nodes.empty() ) return;
outPairs.reserve(uf::physics::impl::settings.reserveCount);
::traverseNodePair(bvhA, 0, bvhB, 0, outPairs);
::postprocessPairs( outPairs );
}
}
@ -681,13 +699,15 @@ namespace {
if ( indexA == indexB ) continue;
if ( indexA > indexB ) std::swap(indexA, indexB);
outPairs.emplace( indexA, indexB );
outPairs.emplace_back( indexA, indexB );
}
}
}
++b;
}
}
::postprocessPairs( outPairs );
}
void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs ) {
auto& nodesA = bvhA.flattened;
@ -701,34 +721,56 @@ namespace {
if ( nodesA.empty() || nodesB.empty() ) return;
outPairs.reserve(uf::physics::impl::settings.reserveCount);
for ( pod::BVH::index_t a = 0; a < nodesA.size(); ++a ) {
const auto& nodeA = nodesA[a];
if ( nodeA.getCount() <= 0 || nodeA.isAsleep() ) continue;
static thread_local uf::stl::vector<std::pair<pod::BVH::index_t, pod::BVH::index_t>> stack;
stack.clear();
stack.emplace_back(0, 0);
const auto& bA = boundsA[a];
while ( !stack.empty() ) {
auto [a, b] = stack.back();
stack.pop_back();
pod::BVH::index_t b = 0;
while ( b < nodesB.size() ) {
const auto& nodeB = nodesB[b];
const auto& nodeA = bvhA.flattened[a];
const auto& nodeB = bvhB.flattened[b];
if ( nodeB.isAsleep() || !::aabbOverlap(bA, boundsB[b]) ) {
b = nodeB.skipIndex;
continue;
}
if ( nodeA.isAsleep() && nodeB.isAsleep() ) continue;
if ( !::aabbOverlap( bvhA.flatBounds[a], bvhB.flatBounds[b] ) ) continue;
if ( nodeB.getCount() > 0 ) {
for ( pod::BVH::index_t ia = 0; ia < nodeA.getCount(); ++ia ) {
for ( pod::BVH::index_t ib = 0; ib < nodeB.getCount(); ++ib ) {
auto indexA = indicesA[nodeA.start + ia];
auto indexB = indicesB[nodeB.start + ib];
bool isLeafA = (nodeA.getCount() > 0);
bool isLeafB = (nodeB.getCount() > 0);
outPairs.emplace(indexA, indexB);
}
if ( isLeafA && isLeafB ) {
for ( pod::BVH::index_t ia = 0; ia < nodeA.getCount(); ++ia ) {
for ( pod::BVH::index_t ib = 0; ib < nodeB.getCount(); ++ib ) {
auto indexA = bvhA.indices[nodeA.start + ia];
auto indexB = bvhB.indices[nodeB.start + ib];
// if ( indexA > indexB ) std::swap( indexA, indexB );
outPairs.emplace_back(indexA, indexB);
}
}
++b;
}
else if ( isLeafA ) {
pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex;
stack.emplace_back(a, b + 1);
stack.emplace_back(a, rightB);
}
else if ( isLeafB ) {
pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex;
stack.emplace_back(a + 1, b);
stack.emplace_back(rightA, b);
}
else {
pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex;
pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex;
stack.emplace_back(a + 1, b + 1);
stack.emplace_back(a + 1, rightB);
stack.emplace_back(rightA, b + 1);
stack.emplace_back(rightA, rightB);
}
}
::postprocessPairs( outPairs );
}
void queryFlatBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<pod::BVH::index_t>& outIndices ) {
@ -836,10 +878,15 @@ namespace {
pod::BVH::index_t root = unionizer.find(i);
auto [ it, inserted ] = rootToIsland.try_emplace( root, (pod::BVH::index_t) islands.size());
if ( inserted ) islands.emplace_back();
/*
if ( rootToIsland.find(root) == rootToIsland.end() ) {
rootToIsland[root] = (pod::BVH::index_t) islands.size();
islands.emplace_back();
}
*/
pod::BVH::index_t islandID = rootToIsland[root];
islands[islandID].indices.emplace_back( i );
@ -857,7 +904,8 @@ namespace {
pod::BVH::index_t root = unionizer.find(a);
if ( rootToIsland.find(root) != rootToIsland.end() ) {
pod::BVH::index_t islandID = rootToIsland[root];
islands[islandID].pairs.emplace(a, b);
islands[islandID].pairs.emplace_back(a, b);
if ( bodies[a]->activity.awake || bodies[b]->activity.awake ) {
::wakeBody( *bodies[dynamicIndex] );

View File

@ -147,8 +147,10 @@ namespace {
}
void mergeContacts( pod::Manifold& manifold ) {
uf::stl::vector<pod::Contact> result;
static thread_local uf::stl::vector<pod::Contact> result;
result.clear();
result.reserve(4);
for ( auto& c : manifold.points ) {
bool merged = false;
for ( auto& r : result ) {
@ -163,8 +165,6 @@ namespace {
if ( !merged ) result.emplace_back( c );
}
// UF_MSG_DEBUG("Merged {} => {} contacts", manifold.points.size(), result.size());
manifold.points = result;
}

View File

@ -126,17 +126,26 @@ namespace {
// compute overlaps between one BVH and another BVH
static thread_local pod::BVH::pairs_t pairs;
pairs.clear();
//UF_TIMER_MULTITRACE_START("Colliding {} ({} indices) <=> {} ({} indices)", a.object->getName(), bvhA.indices.size(), b.object->getName(), bvhB.indices.size());
//UF_TIMER_MULTITRACE("Querying overlaps...");
::queryOverlaps( bvhA, bvhB, pairs );
//UF_TIMER_MULTITRACE("Queried overlaps.");
bool hit = false;
// do collision per triangle
//UF_TIMER_MULTITRACE("Colliding triangles (pairs={})...", pairs.size());
for (auto [idA, idB] : pairs ) {
auto tA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space
auto tB = ::fetchTriangle( meshB, idB, b );
if ( !::triangleTriangle( tA, tB, manifold, eps ) ) continue;
bool collides = ::triangleTriangle( tA, tB, manifold, eps );
if ( !collides ) continue;
hit = true;
}
//UF_TIMER_MULTITRACE("Collided triangles.");
//UF_TIMER_MULTITRACE_END("Collided mesh.");
return hit;
}
}

View File

@ -14,6 +14,56 @@ namespace {
std::min<uint32_t>(std::max<int>(0, (int)rel.z), divs.z - 1)
};
}
namespace {
// to-do: just use the physics copy of AABB
inline bool intersectTriangleAABB(const pod::Vector3f& v0, const pod::Vector3f& v1, const pod::Vector3f& v2, const pod::Vector3f& aabbMin, const pod::Vector3f& aabbMax) {
pod::Vector3f boxCenter = (aabbMin + aabbMax) * 0.5f;
pod::Vector3f boxExtents = (aabbMax - aabbMin) * 0.5f;
pod::Vector3f tv0 = v0 - boxCenter;
pod::Vector3f tv1 = v1 - boxCenter;
pod::Vector3f tv2 = v2 - boxCenter;
pod::Vector3f e0 = tv1 - tv0;
pod::Vector3f e1 = tv2 - tv1;
pod::Vector3f e2 = tv0 - tv2;
pod::Vector3f triMin = uf::vector::min(uf::vector::min(tv0, tv1), tv2);
pod::Vector3f triMax = uf::vector::max(uf::vector::max(tv0, tv1), tv2);
if (triMin.x > boxExtents.x || triMax.x < -boxExtents.x) return false;
if (triMin.y > boxExtents.y || triMax.y < -boxExtents.y) return false;
if (triMin.z > boxExtents.z || triMax.z < -boxExtents.z) return false;
pod::Vector3f normal = uf::vector::cross(e0, e1);
float planeD = uf::vector::dot(normal, tv0);
float r = boxExtents.x * std::abs(normal.x) + boxExtents.y * std::abs(normal.y) + boxExtents.z * std::abs(normal.z);
if (std::abs(planeD) > r) return false;
float p0, p1, p2, rad;
auto testAxis = [&](const pod::Vector3f& axis) {
p0 = uf::vector::dot(tv0, axis);
p1 = uf::vector::dot(tv1, axis);
p2 = uf::vector::dot(tv2, axis);
rad = boxExtents.x * std::abs(axis.x) + boxExtents.y * std::abs(axis.y) + boxExtents.z * std::abs(axis.z);
return std::min({p0, p1, p2}) > rad || std::max({p0, p1, p2}) < -rad;
};
if (testAxis({0, -e0.z, e0.y})) return false;
if (testAxis({0, -e1.z, e1.y})) return false;
if (testAxis({0, -e2.z, e2.y})) return false;
if (testAxis({e0.z, 0, -e0.x})) return false;
if (testAxis({e1.z, 0, -e1.x})) return false;
if (testAxis({e2.z, 0, -e2.x})) return false;
if (testAxis({-e0.y, e0.x, 0})) return false;
if (testAxis({-e1.y, e1.x, 0})) return false;
if (testAxis({-e2.y, e2.x, 0})) return false;
return true;
}
}
}
void uf::meshgrid::print( const uf::meshgrid::Grid& grid ) {
@ -54,7 +104,6 @@ void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, const pod::Vector3ui& di
return calculate( grid, padding );
}
void uf::meshgrid::calculate( uf::meshgrid::Grid& grid, float padding ){
// Pad bounding box
grid.extents.min -= pod::Vector3f{ padding, padding, padding };
grid.extents.max += pod::Vector3f{ padding, padding, padding };
@ -97,7 +146,6 @@ void uf::meshgrid::partition(uf::meshgrid::Grid& grid,
for (size_t i = 0; i < iCount; i += 3) {
Triangle tri{};
// Read indices
auto readIndex = [&](size_t offs) -> uint32_t {
switch (iStride) {
case 1: return *(const uint8_t*)(idxBase + iStride * (iFirst + offs));
@ -109,24 +157,26 @@ void uf::meshgrid::partition(uf::meshgrid::Grid& grid,
tri.indices[1] = readIndex(i+1);
tri.indices[2] = readIndex(i+2);
// Read vertices
for (int v = 0; v < 3; v++) {
tri.verts[v] = *(const pod::Vector3f*)(vtxBase + pStride * (pFirst + tri.indices[v]));
}
// Compute bounding cell range
pod::Vector3f triMin = uf::vector::min(uf::vector::min(tri.verts[0], tri.verts[1]), tri.verts[2]);
pod::Vector3f triMax = uf::vector::max(uf::vector::max(tri.verts[0], tri.verts[1]), tri.verts[2]);
pod::Vector3ui minCell = ::cellCoords(triMin, grid.extents.min, grid.extents.piece, grid.divisions);
pod::Vector3ui maxCell = ::cellCoords(triMax, grid.extents.min, grid.extents.piece, grid.divisions);
// Assign triangle to all overlapping cells
for (uint32_t z = minCell.z; z <= maxCell.z; z++) {
for (uint32_t y = minCell.y; y <= maxCell.y; y++) {
for (uint32_t x = minCell.x; x <= maxCell.x; x++) {
pod::Vector3ui cid{x,y,z};
auto& node = grid.nodes[cid];
if ( !intersectTriangleAABB(tri.verts[0], tri.verts[1], tri.verts[2], node.extents.min, node.extents.max) ) {
continue;
}
auto& meshlet = node.meshlets[primitive.instance.primitiveID];
meshlet.primitive = primitive;

View File

@ -363,7 +363,7 @@ std::string uf::Mesh::printIndirects( bool full ) const {
return str.str();
}
uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector<uf::stl::string>& wanted ) const {
uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector<uf::stl::string>& wanted, size_t lod ) const {
uf::Mesh::View view;
view.vertex = vertex;
view.index = index;
@ -378,15 +378,15 @@ uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector<uf::stl::string>& wante
}
if ( !index.attributes.empty() ) {
view.attributes["index"] = { index.attributes.front() };
view.attributes["index"] = { index.attributes[lod] };
}
return view;
}
uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector<uf::stl::string>& wanted ) const {
uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector<uf::stl::string>& wanted, size_t lod ) const {
uf::Mesh::View view;
view.vertex = remapVertexInput(i);
view.index = remapIndexInput(i);
view.vertex = remapVertexInput(i, lod);
view.index = remapIndexInput(i, lod);
view.indirectIndex = i;
if ( wanted.size() ) {
@ -399,47 +399,47 @@ uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector<uf::stl::stri
}
if ( !index.attributes.empty() ) {
view.attributes["index"] = { index.attributes.front() };
view.attributes["index"] = { index.attributes[lod] };
}
return view;
}
uf::stl::vector<uf::Mesh::View> uf::Mesh::makeViews( const uf::stl::vector<uf::stl::string>& wanted ) const {
uf::stl::vector<uf::Mesh::View> uf::Mesh::makeViews( const uf::stl::vector<uf::stl::string>& wanted, size_t lod ) const {
uf::stl::vector<uf::Mesh::View> views;
if ( indirect.count > 0 ) {
for ( auto i = 0; i < indirect.count; i++ ) views.emplace_back(makeView(i, wanted));
for ( auto i = 0; i < indirect.count; i++ ) views.emplace_back(makeView(i, wanted, lod));
} else {
views.emplace_back( makeView(wanted) );
views.emplace_back( makeView(wanted, lod) );
}
return views;
}
uf::Mesh::Input uf::Mesh::remapInput( const uf::Mesh::Input& input, size_t i ) const {
uf::Mesh::Input uf::Mesh::remapInput( const uf::Mesh::Input& input, size_t i, size_t lod ) const {
uf::Mesh::Input res = input;
UF_ASSERT( &input == &vertex || &input == &index );
UF_ASSERT( i < indirect.count );
const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect).data())[i];
const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i];
res.first = &input == &vertex ? drawCommand.vertexID : drawCommand.indexID;
res.count = &input == &vertex ? drawCommand.vertices : drawCommand.indices;
return res;
}
uf::Mesh::Input uf::Mesh::remapVertexInput( size_t i ) const {
uf::Mesh::Input uf::Mesh::remapVertexInput( size_t i, size_t lod ) const {
uf::Mesh::Input res = vertex;
UF_ASSERT( i < indirect.count );
const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect).data())[i];
const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i];
res.first = drawCommand.vertexID;
res.count = drawCommand.vertices;
return res;
}
uf::Mesh::Input uf::Mesh::remapIndexInput( size_t i ) const {
uf::Mesh::Input uf::Mesh::remapIndexInput( size_t i, size_t lod ) const {
uf::Mesh::Input res = index;
UF_ASSERT( i < indirect.count );
const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect).data())[i];
const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i];
res.first = drawCommand.indexID;
res.count = drawCommand.indices;
@ -482,7 +482,13 @@ void uf::Mesh::_updateDescriptor( uf::Mesh::Input& input ) {
auto& buffer = buffers[interleaved ? input.interleaved : attribute.buffer];
attribute.length = buffer.size();
attribute.pointer = buffer.data() + attribute.offset;
input.size += attribute.descriptor.size;
if ( &input == &index || &input == &indirect ) {
input.size = attribute.descriptor.size;
} else {
input.size += attribute.descriptor.size;
}
if ( interleaved ) {
attribute.pointer = static_cast<uint8_t*>(attribute.pointer) + attribute.descriptor.offset;
}
@ -588,8 +594,8 @@ void uf::Mesh::_insertIs( uf::Mesh::Input& dstInput, const uf::Mesh& mesh, const
// both meshes are de-interleaved, just copy directly
} else if ( !isInterleaved(dstInput.interleaved) && !isInterleaved(srcInput.interleaved) ) {
for ( auto i = 0; i < dstInput.attributes.size(); ++i ) {
auto& src = mesh.getBuffer( srcInput );
auto& dst = getBuffer( dstInput );
auto& src = mesh.getBuffer( srcInput, i );
auto& dst = getBuffer( dstInput, i );
dst.insert( dst.end(), src.begin(), src.end() );
}