From 3965d3f71919b0dced9e6db61d61d6f53a06cefc Mon Sep 17 00:00:00 2001 From: ecker Date: Tue, 28 Apr 2026 22:56:10 -0500 Subject: [PATCH] cleaned up GUI system ((re)added simple anchoring system, although I need to actually utilize it), fixed SDFs looking like shit --- bin/data/config.json | 12 +- bin/data/entities/burger.json | 4 +- bin/data/entities/gui/hud/hud.json | 2 +- bin/data/entities/gui/hud/overlay.json | 11 +- bin/data/entities/gui/hud/scripts/hud.lua | 32 +- bin/data/entities/gui/hud/text.json | 25 + bin/data/entities/gui/text/string.json | 42 - bin/data/entities/hud.json | 9 +- bin/data/entities/player.json | 1 + bin/data/entities/prop.json | 2 +- .../scenes/sourceengine/mds_mcdonalds.json | 4 +- bin/data/shaders/graph/cull/comp.glsl | 2 +- bin/data/shaders/gui/gui/frag.h | 2 +- bin/data/shaders/gui/gui/gui.h | 2 +- dep/include/meshoptimizer.h | 1157 +++++++++++++---- engine/inc/uf/ext/meshopt/meshopt.h | 2 +- engine/src/engine/ext/gui/behavior.cpp | 192 ++- engine/src/engine/ext/gui/behavior.h | 10 +- engine/src/engine/ext/gui/glyph/behavior.cpp | 540 ++++---- engine/src/engine/ext/gui/glyph/behavior.h | 5 +- engine/src/engine/graph/graph.cpp | 5 + engine/src/ext/gltf/processPrimitives.inl | 14 - engine/src/ext/meshopt/meshopt.cpp | 17 +- 23 files changed, 1316 insertions(+), 776 deletions(-) create mode 100644 bin/data/entities/gui/hud/text.json delete mode 100644 bin/data/entities/gui/text/string.json diff --git a/bin/data/config.json b/bin/data/config.json index 30553382..0a0820f6 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -60,7 +60,7 @@ }, "graph": { "initial buffer elements": 128, - "global storage": true + "global storage": false }, "ext": { "vulkan": { @@ -109,7 +109,7 @@ "invariant": { "default stage buffers": true, "default defer buffer destroy": true, - "default command buffer immediate": true, + "default command buffer immediate": false, "multithreaded recording": true }, "pipelines": { @@ -117,10 +117,10 @@ "gui": true, "vsync": true, // vsync on vulkan side rather than engine-side "hdr": true, - "vxgi": false, + "vxgi": true, "culling": true, - "bloom": false, - "dof": false, + "bloom": true, + "dof": true, "rt": false, "fsr": false, "postProcess": false // "postProcess.chromab" // false @@ -363,7 +363,7 @@ "render modes": { "gui": true, "deferred": true }, "limiters": { "deltaTime": 5, - "framerate": "auto" // "auto" // for some reason drops to 60 + "framerate": 300 // "auto" // for some reason drops to 60 }, "threads": { "workers" : "auto", diff --git a/bin/data/entities/burger.json b/bin/data/entities/burger.json index 231b16a8..9384aae8 100644 --- a/bin/data/entities/burger.json +++ b/bin/data/entities/burger.json @@ -29,7 +29,7 @@ "physics": { "gravity": [ 0, -9.81, 0 ], - + "inertia": null, "mass": 10, "type": "bounding box", "recenter": false @@ -51,7 +51,7 @@ }, "tags": { "/^.*/": { - "optimize meshlets": { "simplify": 0.125, "print": true } + "optimize meshlets": { "simplify": 0.5, "lods": true, "print": true } } } } diff --git a/bin/data/entities/gui/hud/hud.json b/bin/data/entities/gui/hud/hud.json index a9a3af99..637e3ad5 100644 --- a/bin/data/entities/gui/hud/hud.json +++ b/bin/data/entities/gui/hud/hud.json @@ -15,6 +15,6 @@ }, "metadata": { "clickable": false, - "hoverable": false, + "hoverable": false } } \ No newline at end of file diff --git a/bin/data/entities/gui/hud/overlay.json b/bin/data/entities/gui/hud/overlay.json index 2f75c02b..23f45d3a 100644 --- a/bin/data/entities/gui/hud/overlay.json +++ b/bin/data/entities/gui/hud/overlay.json @@ -3,7 +3,7 @@ "type": "Gui", "ignore": false, "assets": [ - { "filename": "./textures/mp.png", "hash": "68e7c459f9aecd6815ff7df1e2eefa82db60a23713b0134f0bfc15d82f55453d" } + // { "filename": "./textures/mp.png", "hash": "68e7c459f9aecd6815ff7df1e2eefa82db60a23713b0134f0bfc15d82f55453d" } // { "filename": "./textures/ss2.png" } ], "transform": { @@ -20,16 +20,9 @@ "hoverable": false, "uv": [ 0, 0, 1, 1 ], - // "color": [ 0.8, 0.8, 1, 1 ], - // "color": [ 0.416, 0.573, 0.667, 1 ], "color": [ 1, 1, 1, 1 ], - "location": "", "scaling": "relative", - // "depth": 0.1, - // "alpha": 0.5, "alpha": 0.75, - "mode": 1, - "gui layer": true, - "only model": true + "mode": 1 } } \ No newline at end of file diff --git a/bin/data/entities/gui/hud/scripts/hud.lua b/bin/data/entities/gui/hud/scripts/hud.lua index b04077af..49f106ae 100644 --- a/bin/data/entities/gui/hud/scripts/hud.lua +++ b/bin/data/entities/gui/hud/scripts/hud.lua @@ -10,6 +10,7 @@ for i=1, visorLayers do end local soundEmitter = ent:loadChild("./sound.json",true) +local text = ent:loadChild("./text.json",true) local timer = Timer.new() if not timer:running() then timer:start() end @@ -51,9 +52,9 @@ local rotate = function( delta ) end end -local windowSize = masterdata["system"]["config"]["window"]["size"]; +--local windowSize = masterdata["system"]["config"]["window"]["size"]; local entTransform = ent:getComponent("Transform") -entTransform.scale.x = entTransform.scale.x * windowSize.x / windowSize.y; +--entTransform.scale.x = entTransform.scale.x * windowSize.x / windowSize.y; for k, obj in pairs(children) do local transform = obj:getComponent("Transform") @@ -105,7 +106,17 @@ ent:addHook( "window:Mouse.Moved", function( payload ) end ) ]] +local fpsCounter = { + frames = 0, + time = 0, + freq = 0.1 +} +text:callHook( "gui:UpdateText.%UID%", { + string = "" +} ) + ent:bind( "tick", function(self) + --[[ for k, obj in pairs(children) do local metadata = obj:getComponent("Metadata") local glow = math.sin(time.current()) * 0.5 + 0.5 -- constrained to [0,1] @@ -113,16 +124,33 @@ ent:bind( "tick", function(self) metadata["alpha"] = glow obj:setComponent("Metadata", metadata) end + ]] + + if fpsCounter["time"] > fpsCounter["freq"] then + -- update text + text:callHook( "gui:UpdateText.%UID%", { + string = tostring(math.floor(fpsCounter["frames"] / fpsCounter["time"])) + } ) + + fpsCounter["time"] = 0 + fpsCounter["frames"] = 0 + else + fpsCounter["frames"] = fpsCounter["frames"] + 1 + fpsCounter["time"] = fpsCounter["time"] + time.delta() + end + local controllerTransform = controller:getComponent("Transform") local controllerCamera = controller:getComponent("Camera") local controllerCameraTransform = controllerCamera:getTransform() local transform = ent:getComponent("Transform") +--[[ local speed = 2.5 if lerper.a == 1 then return end lerper.a = lerper.a + time.delta() * speed if lerper.a > 1 then lerper.a = 1 end +]] transform.orientation = lerper.from:slerp( lerper.to, lerper.a ) local orientation = transform.orientation diff --git a/bin/data/entities/gui/hud/text.json b/bin/data/entities/gui/hud/text.json new file mode 100644 index 00000000..5d94321c --- /dev/null +++ b/bin/data/entities/gui/hud/text.json @@ -0,0 +1,25 @@ +{ + "name": "HUD Overlay", + "type": "Gui", + "ignore": false, + "assets": [ + + ], + "transform": { + "position": [ -0.9, -0.9, 0 ], + "rotation": { + "axis": [ 0, 0, 1 ], + "angle": 0 + }, + "scale": [ 1, 1, 1 ] + }, + "metadata": { + "clickable": false, + "hoverable": false, + + "uv": [ 0, 0, 1, 1 ], + "color": [ 1, 1, 1, 1 ], + "scaling": "relative", + "string": "1234567890" + } +} \ No newline at end of file diff --git a/bin/data/entities/gui/text/string.json b/bin/data/entities/gui/text/string.json deleted file mode 100644 index daa8d10f..00000000 --- a/bin/data/entities/gui/text/string.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "Gui: Text", - "type": "Gui", - "ignore": true, - "transform": { - "position": [ 0, 0, 0 ], - "rotation": { - "axis": [ 0, 0, 1 ], - "angle": 0 - }, - "scale": [ 1, 1, 1 ] - }, - "metadata": { - "legacy": false, - "padding": [ 1, 1 ], - "spread": 4, - "weight": 0.48, - // "size": 36, "scale": 3, - // "size": 45, "scale": 2, - "size": 60, "scale": 1.5, - // "size": 72, "scale": 1.25, - // "size": 90, "scale": 1, - "sdf": false, - "font": "TAZUGANEGOTHICSTDN-BOLD.otf", - "kerning": 24, - "scaling": "none", - // "font": "Coolvetica.ttf", - - "stroke": [ 0, 0, 0, 0 ], - "color": [ 1, 1, 1, 1 ], - - "direction": "down", - "align": "left", - "origin": [ 0, 0 ], - - "string": "", - - "world": false, - // "depth": 0, - "wrap": true - } -} \ No newline at end of file diff --git a/bin/data/entities/hud.json b/bin/data/entities/hud.json index 1d99139b..f2265938 100644 --- a/bin/data/entities/hud.json +++ b/bin/data/entities/hud.json @@ -17,13 +17,6 @@ }, "metadata": { "uv": [ 0, 0, 1, 1 ], - "location": "", - "scaling": "relative", - "text settings": { - "stroke": [ 1, 0.749, 0.368, 1 ], - "color": [ 1, 0.749, 0.368, 1 ], - "depth": 0.5, - "string": "." - } + "scaling": "relative" } } \ No newline at end of file diff --git a/bin/data/entities/player.json b/bin/data/entities/player.json index 0ca53fbd..4cc07e44 100644 --- a/bin/data/entities/player.json +++ b/bin/data/entities/player.json @@ -21,6 +21,7 @@ // "./playerModel.json", "./playerLight.json", // "./playerHands.json", + "./gui/hud/hud.json", "./scripts/player.lua" ], "system": { diff --git a/bin/data/entities/prop.json b/bin/data/entities/prop.json index d8412781..e3db7789 100644 --- a/bin/data/entities/prop.json +++ b/bin/data/entities/prop.json @@ -6,7 +6,7 @@ "metadata": { "holdable": true, "physics": { - "mass": 100, + "mass": 100000, "inertia": false, "type": "bounding box" // "type": "mesh" diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index cee95459..35a1d902 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -2,8 +2,8 @@ "import": "./base_sourceengine.json", "assets": [ // { "filename": "./models/mds_mcdonalds.glb" } - { "filename": "./models/mds_mcdonalds/graph.json" } - // ,{ "filename": "/burger.json", "delay": 1 } + { "filename": "./models/mds_mcdonalds/graph.json" }, + { "filename": "/burger.json", "delay": 1 } ], "metadata": { "graph": { diff --git a/bin/data/shaders/graph/cull/comp.glsl b/bin/data/shaders/graph/cull/comp.glsl index 464d89f5..931f234f 100644 --- a/bin/data/shaders/graph/cull/comp.glsl +++ b/bin/data/shaders/graph/cull/comp.glsl @@ -11,7 +11,7 @@ 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 1 // currently whack +#define OCCLUSION_CULLING 0 // currently whack #define LODS 1 #define MAX_LODS 4 diff --git a/bin/data/shaders/gui/gui/frag.h b/bin/data/shaders/gui/gui/frag.h index 19fefb01..f298cf47 100644 --- a/bin/data/shaders/gui/gui/frag.h +++ b/bin/data/shaders/gui/gui/frag.h @@ -30,7 +30,7 @@ void main() { const float sampled = texture(samplerTexture, inUv).r; const float smoothing = ( inGlyph.spread > 0 && inGlyph.scale > 0 ) ? 0.25 / (inGlyph.spread * inGlyph.scale) : 0.25 / (4 * 1.5); - const float outlining = smoothstep(0.5 - smoothing, 0.5 + smoothing, sampled); + const float outlining = smoothstep(inGlyph.fillWeight - smoothing, inGlyph.fillWeight + smoothing, sampled); const float alpha = smoothstep(inGlyph.weight - smoothing, inGlyph.weight + smoothing, sampled); if ( alpha < 0.001 || alpha > 1 ) discard; C = mix(inGlyph.stroke, inGui.color, outlining); diff --git a/bin/data/shaders/gui/gui/gui.h b/bin/data/shaders/gui/gui/gui.h index 3100fb66..6f4c211a 100644 --- a/bin/data/shaders/gui/gui/gui.h +++ b/bin/data/shaders/gui/gui/gui.h @@ -15,8 +15,8 @@ struct Glyph { int spread; float weight; + float fillWeight; float scale; - uint padding1; uint padding2; uint padding3; }; \ No newline at end of file diff --git a/dep/include/meshoptimizer.h b/dep/include/meshoptimizer.h index 665e27ef..073ae14c 100644 --- a/dep/include/meshoptimizer.h +++ b/dep/include/meshoptimizer.h @@ -1,7 +1,7 @@ /** - * meshoptimizer - version 0.15 + * meshoptimizer - version 1.1 * - * Copyright (C) 2016-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://github.com/zeux/meshoptimizer * * This library is distributed under the MIT License. See notice at the end of this file. @@ -12,24 +12,36 @@ #include /* Version macro; major * 1000 + minor * 10 + patch */ -#define MESHOPTIMIZER_VERSION 150 /* 0.15 */ +#define MESHOPTIMIZER_VERSION 1010 /* 1.1 */ /* If no API is defined, assume default */ #ifndef MESHOPTIMIZER_API #define MESHOPTIMIZER_API #endif +/* Set the calling-convention for alloc/dealloc function pointers */ +#ifndef MESHOPTIMIZER_ALLOC_CALLCONV +#ifdef _MSC_VER +#define MESHOPTIMIZER_ALLOC_CALLCONV __cdecl +#else +#define MESHOPTIMIZER_ALLOC_CALLCONV +#endif +#endif + /* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */ +#ifndef MESHOPTIMIZER_EXPERIMENTAL #define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API +#endif /* C interface */ #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif /** - * Vertex attribute stream, similar to glVertexPointer - * Each element takes size bytes, with stride controlling the spacing between successive elements. + * Vertex attribute stream + * Each element takes size bytes, beginning at data, with stride controlling the spacing between successive elements (stride >= size). */ struct meshopt_Stream { @@ -42,22 +54,38 @@ struct meshopt_Stream * Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence. * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer. + * Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized. * * destination must contain enough space for the resulting remap table (vertex_count elements) * indices can be NULL if the input is unindexed */ -MESHOPTIMIZER_API size_t meshopt_generateVertexRemap(uint32_t* destination, const uint32_t* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size); +MESHOPTIMIZER_API size_t meshopt_generateVertexRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size); /** * Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence. * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer. * To remap vertex buffers, you will need to call meshopt_remapVertexBuffer for each vertex stream. + * Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized. * * destination must contain enough space for the resulting remap table (vertex_count elements) * indices can be NULL if the input is unindexed + * stream_count must be <= 16 */ -MESHOPTIMIZER_API size_t meshopt_generateVertexRemapMulti(uint32_t* destination, const uint32_t* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count); +MESHOPTIMIZER_API size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count); + +/** + * Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices + * As a result, all vertices that are equivalent map to the same (new) location, with no gaps in the resulting sequence. + * Equivalence is checked in two steps: vertex positions are compared for equality, and then the user-specified equality function is called (if provided). + * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer. + * + * destination must contain enough space for the resulting remap table (vertex_count elements) + * indices can be NULL if the input is unindexed + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * callback can be NULL if no additional equality check is needed; otherwise, it should return 1 if vertices with specified indices are equivalent and 0 if they are not + */ +MESHOPTIMIZER_API size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, int (*callback)(void*, unsigned int, unsigned int), void* context); /** * Generates vertex buffer from the source vertex buffer and remap table generated by meshopt_generateVertexRemap @@ -65,7 +93,7 @@ MESHOPTIMIZER_API size_t meshopt_generateVertexRemapMulti(uint32_t* destination, * destination must contain enough space for the resulting vertex buffer (unique_vertex_count elements, returned by meshopt_generateVertexRemap) * vertex_count should be the initial vertex count and not the value returned by meshopt_generateVertexRemap */ -MESHOPTIMIZER_API void meshopt_remapVertexBuffer(void* destination, const void* vertices, size_t vertex_count, size_t vertex_size, const uint32_t* remap); +MESHOPTIMIZER_API void meshopt_remapVertexBuffer(void* destination, const void* vertices, size_t vertex_count, size_t vertex_size, const unsigned int* remap); /** * Generate index buffer from the source index buffer and remap table generated by meshopt_generateVertexRemap @@ -73,34 +101,89 @@ MESHOPTIMIZER_API void meshopt_remapVertexBuffer(void* destination, const void* * destination must contain enough space for the resulting index buffer (index_count elements) * indices can be NULL if the input is unindexed */ -MESHOPTIMIZER_API void meshopt_remapIndexBuffer(uint32_t* destination, const uint32_t* indices, size_t index_count, const uint32_t* remap); +MESHOPTIMIZER_API void meshopt_remapIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const unsigned int* remap); /** * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary * All vertices that are binary equivalent (wrt first vertex_size bytes) map to the first vertex in the original vertex buffer. * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering. + * Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized. * * destination must contain enough space for the resulting index buffer (index_count elements) */ -MESHOPTIMIZER_API void meshopt_generateShadowIndexBuffer(uint32_t* destination, const uint32_t* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride); +MESHOPTIMIZER_API void meshopt_generateShadowIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride); /** * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary * All vertices that are binary equivalent (wrt specified streams) map to the first vertex in the original vertex buffer. * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering. + * Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized. * * destination must contain enough space for the resulting index buffer (index_count elements) + * stream_count must be <= 16 */ -MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(uint32_t* destination, const uint32_t* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count); +MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count); + +/** + * Generates a remap table that maps all vertices with the same position to the same (existing) index. + * Similarly to meshopt_generateShadowIndexBuffer, this can be helpful to pre-process meshes for position-only rendering. + * This can also be used to implement algorithms that require positional-only connectivity, such as hierarchical simplification. + * + * destination must contain enough space for the resulting remap table (vertex_count elements) + * vertex_positions should have float3 position in the first 12 bytes of each vertex + */ +MESHOPTIMIZER_API void meshopt_generatePositionRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + +/** + * Generate index buffer that can be used as a geometry shader input with triangle adjacency topology + * Each triangle is converted into a 6-vertex patch with the following layout: + * - 0, 2, 4: original triangle vertices + * - 1, 3, 5: vertices adjacent to edges 02, 24 and 40 + * The resulting patch can be rendered with geometry shaders using e.g. VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY. + * This can be used to implement algorithms like silhouette detection/expansion and other forms of GS-driven rendering. + * + * destination must contain enough space for the resulting index buffer (index_count*2 elements) + * vertex_positions should have float3 position in the first 12 bytes of each vertex + */ +MESHOPTIMIZER_API void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + +/** + * Generate index buffer that can be used for PN-AEN tessellation with crack-free displacement + * Each triangle is converted into a 12-vertex patch with the following layout: + * - 0, 1, 2: original triangle vertices + * - 3, 4: opposing edge for edge 0, 1 + * - 5, 6: opposing edge for edge 1, 2 + * - 7, 8: opposing edge for edge 2, 0 + * - 9, 10, 11: dominant vertices for corners 0, 1, 2 + * The resulting patch can be rendered with hardware tessellation using PN-AEN and displacement mapping. + * See "Tessellation on Any Budget" (John McDonald, GDC 2011) for implementation details. + * + * destination must contain enough space for the resulting index buffer (index_count*4 elements) + * vertex_positions should have float3 position in the first 12 bytes of each vertex + */ +MESHOPTIMIZER_API void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + +/** + * Generate index buffer that can be used for visibility buffer rendering and returns the size of the reorder table + * Each triangle's provoking vertex index is equal to primitive id; this allows passing it to the fragment shader using flat/nointerpolation attribute. + * This is important for performance on hardware where primitive id can't be accessed efficiently in fragment shader. + * The reorder table stores the original vertex id for each vertex in the new index buffer, and should be used in the vertex shader to load vertex data. + * The provoking vertex is assumed to be the first vertex in the triangle; if this is not the case (OpenGL), rotate each triangle (abc -> bca) before rendering. + * For maximum efficiency the input index buffer should be optimized for vertex cache first. + * + * destination must contain enough space for the resulting index buffer (index_count elements) + * reorder must contain enough space for the worst case reorder table (vertex_count + index_count/3 elements) + */ +MESHOPTIMIZER_API size_t meshopt_generateProvokingIndexBuffer(unsigned int* destination, unsigned int* reorder, const unsigned int* indices, size_t index_count, size_t vertex_count); /** * Vertex transform cache optimizer * Reorders indices to reduce the number of GPU vertex shader invocations - * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually. + * If index buffer contains multiple ranges for multiple draw calls, this function needs to be called on each range individually. * * destination must contain enough space for the resulting index buffer (index_count elements) */ -MESHOPTIMIZER_API void meshopt_optimizeVertexCache(uint32_t* destination, const uint32_t* indices, size_t index_count, size_t vertex_count); +MESHOPTIMIZER_API void meshopt_optimizeVertexCache(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count); /** * Vertex transform cache optimizer for strip-like caches @@ -109,41 +192,41 @@ MESHOPTIMIZER_API void meshopt_optimizeVertexCache(uint32_t* destination, const * * destination must contain enough space for the resulting index buffer (index_count elements) */ -MESHOPTIMIZER_API void meshopt_optimizeVertexCacheStrip(uint32_t* destination, const uint32_t* indices, size_t index_count, size_t vertex_count); +MESHOPTIMIZER_API void meshopt_optimizeVertexCacheStrip(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count); /** * Vertex transform cache optimizer for FIFO caches * Reorders indices to reduce the number of GPU vertex shader invocations * Generally takes ~3x less time to optimize meshes but produces inferior results compared to meshopt_optimizeVertexCache - * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually. + * If index buffer contains multiple ranges for multiple draw calls, this function needs to be called on each range individually. * * destination must contain enough space for the resulting index buffer (index_count elements) * cache_size should be less than the actual GPU cache size to avoid cache thrashing */ -MESHOPTIMIZER_API void meshopt_optimizeVertexCacheFifo(uint32_t* destination, const uint32_t* indices, size_t index_count, size_t vertex_count, uint32_t cache_size); +MESHOPTIMIZER_API void meshopt_optimizeVertexCacheFifo(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size); /** * Overdraw optimizer * Reorders indices to reduce the number of GPU vertex shader invocations and the pixel overdraw - * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually. + * If index buffer contains multiple ranges for multiple draw calls, this function needs to be called on each range individually. * * destination must contain enough space for the resulting index buffer (index_count elements) * indices must contain index data that is the result of meshopt_optimizeVertexCache (*not* the original mesh indices!) - * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer + * vertex_positions should have float3 position in the first 12 bytes of each vertex * threshold indicates how much the overdraw optimizer can degrade vertex cache efficiency (1.05 = up to 5%) to reduce overdraw more efficiently */ -MESHOPTIMIZER_API void meshopt_optimizeOverdraw(uint32_t* destination, const uint32_t* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold); +MESHOPTIMIZER_API void meshopt_optimizeOverdraw(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold); /** * Vertex fetch cache optimizer * Reorders vertices and changes indices to reduce the amount of GPU memory fetches during vertex processing * Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused - * This functions works for a single vertex stream; for multiple vertex streams, use meshopt_optimizeVertexFetchRemap + meshopt_remapVertexBuffer for each stream. + * This function works for a single vertex stream; for multiple vertex streams, use meshopt_optimizeVertexFetchRemap + meshopt_remapVertexBuffer for each stream. * * destination must contain enough space for the resulting vertex buffer (vertex_count elements) * indices is used both as an input and as an output index buffer */ -MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetch(void* destination, uint32_t* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size); +MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetch(void* destination, unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size); /** * Vertex fetch cache optimizer @@ -153,7 +236,7 @@ MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetch(void* destination, uint32_t * * destination must contain enough space for the resulting remap table (vertex_count elements) */ -MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetchRemap(uint32_t* destination, const uint32_t* indices, size_t index_count, size_t vertex_count); +MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count); /** * Index buffer encoder @@ -164,14 +247,15 @@ MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetchRemap(uint32_t* destination, * * buffer must contain enough space for the encoded index buffer (use meshopt_encodeIndexBufferBound to compute worst case size) */ -MESHOPTIMIZER_API size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const uint32_t* indices, size_t index_count); +MESHOPTIMIZER_API size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count); MESHOPTIMIZER_API size_t meshopt_encodeIndexBufferBound(size_t index_count, size_t vertex_count); /** - * Experimental: Set index encoder format version + * Set index encoder format version (defaults to 1) + * * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.14+) */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeIndexVersion(int version); +MESHOPTIMIZER_API void meshopt_encodeIndexVersion(int version); /** * Index buffer decoder @@ -184,15 +268,22 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeIndexVersion(int version); MESHOPTIMIZER_API int meshopt_decodeIndexBuffer(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size); /** - * Experimental: Index sequence encoder + * Get encoded index format version + * Returns format version of the encoded index buffer/sequence, or -1 if the buffer header is invalid + * Note that a non-negative value doesn't guarantee that the buffer will be decoded correctly if the input is malformed. + */ +MESHOPTIMIZER_API int meshopt_decodeIndexVersion(const unsigned char* buffer, size_t buffer_size); + +/** + * Index sequence encoder * Encodes index sequence into an array of bytes that is generally smaller and compresses better compared to original. * Input index sequence can represent arbitrary topology; for triangle lists meshopt_encodeIndexBuffer is likely to be better. * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space * * buffer must contain enough space for the encoded index sequence (use meshopt_encodeIndexSequenceBound to compute worst case size) */ -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const uint32_t* indices, size_t index_count); -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count); +MESHOPTIMIZER_API size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count); +MESHOPTIMIZER_API size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count); /** * Index sequence decoder @@ -202,24 +293,73 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeIndexSequenceBound(size_t index_ * * destination must contain enough space for the resulting index sequence (index_count elements) */ -MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size); +MESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size); + +/** + * Experimental: Meshlet encoder + * Encodes meshlet data into an array of bytes that is generally smaller and compresses better compared to original. + * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space + * This function encodes a single meshlet; when encoding multiple meshlets, additional headers may be necessary to store vertex/triangle count and encoded size. + * For maximum efficiency the meshlet being encoded should be optimized using meshopt_optimizeMeshletLevel with level 1+ (3 recommended); additionally, vertex reference data should be optimized for locality (fetch). + * + * buffer must contain enough space for the encoded meshlet (use meshopt_encodeMeshletBound to compute worst case size) + * vertices may be NULL, in which case vertex_count must be 0 and only triangle data is encoded + * vertex_count and triangle_count must be <= 256. + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count); +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triangles); + +/** + * Experimental: Meshlet decoder + * Decodes meshlet data from an array of bytes generated by meshopt_encodeMeshlet + * Returns 0 if decoding was successful, and an error code otherwise + * The decoder is safe to use for untrusted input, but it may produce garbage data. + * + * vertices must contain enough space for the resulting vertex data, aligned to 4 bytes (align(vertex_count * vertex_size, 4) bytes) + * vertex_size must be 2 (16-bit vertex references) or 4 (32-bit vertex references) + * triangles must contain enough space for the resulting triangle data, aligned to 4 bytes (align(triangle_count * triangle_size, 4) bytes) + * triangle_size must be 3 (8-bit triangle indices) or 4 (32-bit packed triangles, stored as (a) | (b << 8) | (c << 16)) + * vertex_count, triangle_count match those used during encoding exactly; buffer_size must be equal to the encoded size returned by meshopt_encodeMeshlet. + * vertices may be NULL, in which case vertex_count must be 0 and the meshlet must contain just triangle data + * + * When using "raw" decoding (meshopt_decodeMeshletRaw), both vertices and triangles should have available space further aligned to 16 bytes for efficient SIMD decoding. + */ +MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t vertex_size, void* triangles, size_t triangle_count, size_t triangle_size, const unsigned char* buffer, size_t buffer_size); +MESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count, unsigned int* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size); /** * Vertex buffer encoder * Encodes vertex data into an array of bytes that is generally smaller and compresses better compared to original. * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space * This function works for a single vertex stream; for multiple vertex streams, call meshopt_encodeVertexBuffer for each stream. + * Note that all vertex_size bytes of each vertex are encoded verbatim, including padding which should be zero-initialized. + * For maximum efficiency the vertex buffer being encoded has to be quantized and optimized for locality of reference (cache/fetch) first. * * buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size) + * vertex_size must be a multiple of 4 (and <= 256) */ MESHOPTIMIZER_API size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size); MESHOPTIMIZER_API size_t meshopt_encodeVertexBufferBound(size_t vertex_count, size_t vertex_size); /** - * Experimental: Set vertex encoder format version - * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) + * Vertex buffer encoder + * Encodes vertex data just like meshopt_encodeVertexBuffer, but allows to override compression level. + * For compression level to take effect, the vertex encoding version must be set to 1. + * The default compression level implied by meshopt_encodeVertexBuffer is 2. + * + * buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size) + * vertex_size must be a multiple of 4 (and <= 256) + * level should be in the range [0, 3] with 0 being the fastest and 3 being the slowest and producing the best compression ratio. + * version should be -1 to use the default version (specified via meshopt_encodeVertexVersion), or 0/1 to override the version; per above, level won't take effect if version is 0. */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeVertexVersion(int version); +MESHOPTIMIZER_API size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level, int version); + +/** + * Set vertex encoder format version (defaults to 1) + * + * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.23+) + */ +MESHOPTIMIZER_API void meshopt_encodeVertexVersion(int version); /** * Vertex buffer decoder @@ -228,65 +368,224 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeVertexVersion(int version); * The decoder is safe to use for untrusted input, but it may produce garbage data. * * destination must contain enough space for the resulting vertex buffer (vertex_count * vertex_size bytes) + * vertex_size must be a multiple of 4 (and <= 256) */ MESHOPTIMIZER_API int meshopt_decodeVertexBuffer(void* destination, size_t vertex_count, size_t vertex_size, const unsigned char* buffer, size_t buffer_size); +/** + * Get encoded vertex format version + * Returns format version of the encoded vertex buffer, or -1 if the buffer header is invalid + * Note that a non-negative value doesn't guarantee that the buffer will be decoded correctly if the input is malformed. + */ +MESHOPTIMIZER_API int meshopt_decodeVertexVersion(const unsigned char* buffer, size_t buffer_size); + /** * Vertex buffer filters * These functions can be used to filter output of meshopt_decodeVertexBuffer in-place. - * count must be aligned by 4 and stride is fixed for each function to facilitate SIMD implementation. * - * meshopt_decodeFilterOct decodes octahedral encoding of a unit vector with K-bit (K <= 16) signed X/Y as an input; Z must store 1.0f. + * meshopt_decodeFilterOct decodes octahedral encoding of a unit vector with K-bit signed X/Y as an input; Z must store 1.0f. * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is. * - * meshopt_decodeFilterQuat decodes 3-component quaternion encoding with K-bit (4 <= K <= 16) component encoding and a 2-bit component index indicating which component to reconstruct. + * meshopt_decodeFilterQuat decodes 3-component quaternion encoding with K-bit component encoding and a 2-bit component index indicating which component to reconstruct. * Each component is stored as an 16-bit integer; stride must be equal to 8. * * meshopt_decodeFilterExp decodes exponential encoding of floating-point data with 8-bit exponent and 24-bit integer mantissa as 2^E*M. * Each 32-bit component is decoded in isolation; stride must be divisible by 4. + * + * meshopt_decodeFilterColor decodes RGBA colors from YCoCg (+A) color encoding where RGB is converted to YCoCg space with K-bit component encoding, and A is stored using K-1 bits. + * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterOct(void* buffer, size_t vertex_count, size_t vertex_size); -MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterQuat(void* buffer, size_t vertex_count, size_t vertex_size); -MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t vertex_count, size_t vertex_size); +MESHOPTIMIZER_API void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride); +MESHOPTIMIZER_API void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride); +MESHOPTIMIZER_API void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride); +MESHOPTIMIZER_API void meshopt_decodeFilterColor(void* buffer, size_t count, size_t stride); /** - * Experimental: Mesh simplifier + * Vertex buffer filter encoders + * These functions can be used to encode data in a format that meshopt_decodeFilter can decode + * + * meshopt_encodeFilterOct encodes unit vectors with K-bit (2 <= K <= 16) signed X/Y as an output. + * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. Z will store 1.0f, W is preserved as is. + * Input data must contain 4 floats for every vector (count*4 total). + * + * meshopt_encodeFilterQuat encodes unit quaternions with K-bit (4 <= K <= 16) component encoding. + * Each component is stored as an 16-bit integer; stride must be equal to 8. + * Input data must contain 4 floats for every quaternion (count*4 total). + * + * meshopt_encodeFilterExp encodes arbitrary (finite) floating-point data with 8-bit exponent and K-bit integer mantissa (1 <= K <= 24). + * Exponent can be shared between all components of a given vector as defined by stride or all values of a given component; stride must be divisible by 4. + * Input data must contain stride/4 floats for every vector (count*stride/4 total). + * + * meshopt_encodeFilterColor encodes RGBA color data by converting RGB to YCoCg color space with K-bit (2 <= K <= 16) component encoding; A is stored using K-1 bits. + * Each component is stored as an 8-bit or 16-bit integer; stride must be equal to 4 or 8. + * Input data must contain 4 floats for every color (count*4 total). + */ +enum meshopt_EncodeExpMode +{ + /* When encoding exponents, use separate values for each component (maximum quality) */ + meshopt_EncodeExpSeparate, + /* When encoding exponents, use shared value for all components of each vector (better compression) */ + meshopt_EncodeExpSharedVector, + /* When encoding exponents, use shared value for each component of all vectors (best compression) */ + meshopt_EncodeExpSharedComponent, + /* When encoding exponents, use separate values for each component, but clamp to 0 (good quality if very small values are not important) */ + meshopt_EncodeExpClamped, +}; + +MESHOPTIMIZER_API void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data); +MESHOPTIMIZER_API void meshopt_encodeFilterQuat(void* destination, size_t count, size_t stride, int bits, const float* data); +MESHOPTIMIZER_API void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data, enum meshopt_EncodeExpMode mode); +MESHOPTIMIZER_API void meshopt_encodeFilterColor(void* destination, size_t count, size_t stride, int bits, const float* data); + +/** + * Simplification options + */ +enum +{ + /* Do not move vertices that are located on the topological border (vertices on triangle edges that don't have a paired triangle). Useful for simplifying portions of the larger mesh. */ + meshopt_SimplifyLockBorder = 1 << 0, + /* Improve simplification performance assuming input indices are a sparse subset of the mesh. Note that error becomes relative to subset extents. */ + meshopt_SimplifySparse = 1 << 1, + /* Treat error limit and resulting error as absolute instead of relative to mesh extents. */ + meshopt_SimplifyErrorAbsolute = 1 << 2, + /* Remove disconnected parts of the mesh during simplification incrementally, regardless of the topological restrictions inside components. */ + meshopt_SimplifyPrune = 1 << 3, + /* Produce more regular triangle sizes and shapes during simplification, at some cost to geometric and attribute quality. */ + meshopt_SimplifyRegularize = 1 << 4, + /* Experimental: Allow collapses across attribute discontinuities, except for vertices that are tagged with meshopt_SimplifyVertex_Protect in vertex_lock. */ + meshopt_SimplifyPermissive = 1 << 5, + /* Experimental: Produce more regular triangle sizes and shapes during simplification, at a small cost to geometric and attribute quality. */ + meshopt_SimplifyRegularizeLight = 1 << 6, +}; + +/** + * Simplification vertex flags/locks, for use in `vertex_lock` arrays in simplification APIs + */ +enum +{ + /* Do not move this vertex. */ + meshopt_SimplifyVertex_Lock = 1 << 0, + /* Protect attribute discontinuity at this vertex; must be used together with meshopt_SimplifyPermissive option. */ + meshopt_SimplifyVertex_Protect = 1 << 1, + /* Experimental: Increase priority for this vertex, making it more likely that it's preserved during simplification. */ + meshopt_SimplifyVertex_Priority = 1 << 2, +}; + +/** + * Mesh simplifier * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error. - * If not all attributes from the input mesh are required, it's recommended to reindex the mesh using meshopt_generateShadowIndexBuffer prior to simplification. + * If not all attributes from the input mesh are needed, it's recommended to reindex the mesh without them prior to simplification. * Returns the number of indices after simplification, with destination containing new index data - * The resulting index buffer references vertices from the original vertex buffer. - * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. * - * destination must contain enough space for the *source* index buffer (since optimization is iterative, this means index_count elements - *not* target_index_count!) - * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer + * The resulting index buffer references vertices from the original vertex buffer. + * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. + * + * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)! + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1] + * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default + * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification */ -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplify(uint32_t* destination, const uint32_t* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error); +MESHOPTIMIZER_API size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* result_error); /** - * Experimental: Mesh simplifier (sloppy) - * Reduces the number of triangles in the mesh, sacrificing mesh apperance for simplification performance - * The algorithm doesn't preserve mesh topology but is always able to reach target triangle count. + * Mesh simplifier with attribute metric + * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible. + * Similar to meshopt_simplify, but incorporates attribute values into the error metric used to prioritize simplification order. + * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error. + * If not all attributes from the input mesh are needed, it's recommended to reindex the mesh without them prior to simplification. * Returns the number of indices after simplification, with destination containing new index data - * The resulting index buffer references vertices from the original vertex buffer. - * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. * - * destination must contain enough space for the target index buffer - * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer + * The resulting index buffer references vertices from the original vertex buffer. + * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. + * Note that the number of attributes with non-zero weights affects memory requirements and running time. + * + * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)! + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * vertex_attributes should have attribute_count floats for each vertex + * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position + * attribute_count must be <= 32 + * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex composed of meshopt_SimplifyVertex_* flags + * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1] + * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default + * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification */ -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(uint32_t* destination, const uint32_t* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count); +MESHOPTIMIZER_API size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error); /** - * Experimental: Point cloud simplifier + * Mesh simplifier with position/attribute update + * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible. + * Similar to meshopt_simplifyWithAttributes, but destructively updates positions and attribute values for optimal appearance. + * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error. + * If not all attributes from the input mesh are needed, it's recommended to reindex the mesh without them prior to simplification. + * Returns the number of indices after simplification, indices are destructively updated with new index data + * + * The updated index buffer references vertices from the original vertex buffer, however the vertex positions and attributes are updated in-place. + * Creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended; if the original vertex data is needed, it should be copied before simplification. + * Note that the number of attributes with non-zero weights affects memory requirements and running time. Attributes with zero weights are not updated. + * + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * vertex_attributes should have attribute_count floats for each vertex + * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position + * attribute_count must be <= 32 + * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex composed of meshopt_SimplifyVertex_* flags + * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1] + * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default + * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification + */ +MESHOPTIMIZER_API size_t meshopt_simplifyWithUpdate(unsigned int* indices, size_t index_count, float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error); + +/** + * Mesh simplifier (sloppy) + * Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance + * The algorithm doesn't preserve mesh topology but can stop short of the target goal based on target error. + * Returns the number of indices after simplification, with destination containing new index data + * The resulting index buffer references vertices from the original vertex buffer. + * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. + * + * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)! + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; vertices that can't be moved should set 1 consistently for all indices with the same position + * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1] + * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification + */ +MESHOPTIMIZER_API size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const unsigned char* vertex_lock, size_t target_index_count, float target_error, float* result_error); + +/** + * Mesh simplifier (pruner) + * Reduces the number of triangles in the mesh by removing small isolated parts of the mesh + * Returns the number of indices after simplification, with destination containing new index data + * The resulting index buffer references vertices from the original vertex buffer. + * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. + * + * destination must contain enough space for the target index buffer, worst case is index_count elements + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1] + */ +MESHOPTIMIZER_API size_t meshopt_simplifyPrune(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float target_error); + +/** + * Point cloud simplifier * Reduces the number of points in the cloud to reach the given target * Returns the number of points after simplification, with destination containing new index data * The resulting index buffer references vertices from the original vertex buffer. - * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. + * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended. * - * destination must contain enough space for the target index buffer - * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer + * destination must contain enough space for the target index buffer (target_vertex_count elements) + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * vertex_colors can be NULL; when it's not NULL, it should have float3 color in the first 12 bytes of each vertex + * color_weight determines relative priority of color wrt position; 1.0 is a safe default */ -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(uint32_t* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count); +MESHOPTIMIZER_API size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_colors, size_t vertex_colors_stride, float color_weight, size_t target_vertex_count); + +/** + * Returns the error scaling factor used by the simplifier to convert between absolute and relative extents + * + * Absolute error must be *divided* by the scaling factor before passing it to meshopt_simplify as target_error + * Relative error returned by meshopt_simplify via result_error must be *multiplied* by the scaling factor to get absolute error. + */ +MESHOPTIMIZER_API float meshopt_simplifyScale(const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); /** * Mesh stripifier @@ -298,7 +597,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(uint32_t* destination, * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound * restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles */ -MESHOPTIMIZER_API size_t meshopt_stripify(uint32_t* destination, const uint32_t* indices, size_t index_count, size_t vertex_count, uint32_t restart_index); +MESHOPTIMIZER_API size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index); MESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count); /** @@ -308,13 +607,13 @@ MESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count); * * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound */ -MESHOPTIMIZER_API size_t meshopt_unstripify(uint32_t* destination, const uint32_t* indices, size_t index_count, uint32_t restart_index); +MESHOPTIMIZER_API size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index); MESHOPTIMIZER_API size_t meshopt_unstripifyBound(size_t index_count); struct meshopt_VertexCacheStatistics { - uint32_t vertices_transformed; - uint32_t warps_executed; + unsigned int vertices_transformed; + unsigned int warps_executed; float acmr; /* transformed vertices / triangle count; best case 0.5, worst case 3.0, optimum depends on topology */ float atvr; /* transformed vertices / vertex count; best case 1.0, worst case 6.0, optimum is 1.0 (each vertex is transformed once) */ }; @@ -324,27 +623,11 @@ struct meshopt_VertexCacheStatistics * Returns cache hit statistics using a simplified FIFO model * Results may not match actual GPU performance */ -MESHOPTIMIZER_API struct meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const uint32_t* indices, size_t index_count, size_t vertex_count, uint32_t cache_size, uint32_t warp_size, uint32_t primgroup_size); - -struct meshopt_OverdrawStatistics -{ - uint32_t pixels_covered; - uint32_t pixels_shaded; - float overdraw; /* shaded pixels / covered pixels; best case 1.0 */ -}; - -/** - * Overdraw analyzer - * Returns overdraw statistics using a software rasterizer - * Results may not match actual GPU performance - * - * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer - */ -MESHOPTIMIZER_API struct meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const uint32_t* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API struct meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size); struct meshopt_VertexFetchStatistics { - uint32_t bytes_fetched; + unsigned int bytes_fetched; float overfetch; /* fetched bytes / vertex buffer size; best case 1.0 (each byte is fetched once) */ }; @@ -353,27 +636,120 @@ struct meshopt_VertexFetchStatistics * Returns cache hit statistics using a simplified direct mapped model * Results may not match actual GPU performance */ -MESHOPTIMIZER_API struct meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const uint32_t* indices, size_t index_count, size_t vertex_count, size_t vertex_size); +MESHOPTIMIZER_API struct meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size); -struct meshopt_Meshlet +struct meshopt_OverdrawStatistics { - uint32_t vertices[64]; - unsigned char indices[126][3]; - unsigned char triangle_count; - unsigned char vertex_count; + unsigned int pixels_covered; + unsigned int pixels_shaded; + float overdraw; /* shaded pixels / covered pixels; best case 1.0 */ }; /** - * Experimental: Meshlet builder + * Overdraw analyzer + * Returns overdraw statistics using a software rasterizer + * Results may not match actual GPU performance + * + * vertex_positions should have float3 position in the first 12 bytes of each vertex + */ +MESHOPTIMIZER_API struct meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + +struct meshopt_CoverageStatistics +{ + float coverage[3]; + float extent; /* viewport size in mesh coordinates */ +}; + +/** + * Coverage analyzer + * Returns coverage statistics (ratio of viewport pixels covered from each axis) using a software rasterizer + * + * vertex_positions should have float3 position in the first 12 bytes of each vertex + */ +MESHOPTIMIZER_API struct meshopt_CoverageStatistics meshopt_analyzeCoverage(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + +/** + * Meshlet is a small mesh cluster (subset) that consists of: + * - triangles, an 8-bit micro triangle (index) buffer, that for each triangle specifies three local vertices to use; + * - vertices, a 32-bit vertex indirection buffer, that for each local vertex specifies which mesh vertex to fetch vertex attributes from. + * + * For efficiency, meshlet triangles and vertices are packed into two large arrays; this structure contains offsets and counts to access the data. + */ +struct meshopt_Meshlet +{ + /* offsets within meshlet_vertices and meshlet_triangles arrays with meshlet data */ + unsigned int vertex_offset; + unsigned int triangle_offset; + + /* number of vertices and triangles used in the meshlet; data is stored in consecutive range [offset..offset+count) for vertices and [offset..offset+count*3) for triangles */ + unsigned int vertex_count; + unsigned int triangle_count; +}; + +/** + * Meshlet builder * Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer * The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers. - * For maximum efficiency the index buffer being converted has to be optimized for vertex cache first. + * When targeting mesh shading hardware, for maximum efficiency meshlets should be further optimized using meshopt_optimizeMeshlet. + * When using buildMeshlets, vertex positions need to be provided to minimize the size of the resulting clusters. + * When using buildMeshletsScan, for maximum efficiency the index buffer being converted has to be optimized for vertex cache first. * - * destination must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound - * max_vertices and max_triangles can't exceed limits statically declared in meshopt_Meshlet (max_vertices <= 64, max_triangles <= 126) + * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound + * meshlet_vertices must contain enough space for all meshlets, worst case is index_count elements (*not* vertex_count!) + * meshlet_triangles must contain enough space for all meshlets, worst case is index_count elements + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512) + * cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency */ -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshlets(struct meshopt_Meshlet* destination, const uint32_t* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles); -MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles); +MESHOPTIMIZER_API size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight); +MESHOPTIMIZER_API size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles); +MESHOPTIMIZER_API size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles); + +/** + * Meshlet builder with flexible cluster sizes + * Splits the mesh into a set of meshlets, similarly to meshopt_buildMeshlets, but allows to specify minimum and maximum number of triangles per meshlet. + * Clusters between min and max triangle counts are split when the cluster size would have exceeded the expected cluster size by more than split_factor. + * + * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound using min_triangles (*not* max!) + * meshlet_vertices must contain enough space for all meshlets, worst case is index_count elements (*not* vertex_count!) + * meshlet_triangles must contain enough space for all meshlets, worst case is index_count elements + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * max_vertices, min_triangles and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512; min_triangles <= max_triangles) + * cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency + * split_factor should be set to a non-negative value; when greater than 0, clusters that have large bounds may be split unless they are under the min_triangles threshold + */ +MESHOPTIMIZER_API size_t meshopt_buildMeshletsFlex(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float cone_weight, float split_factor); + +/** + * Meshlet builder that produces clusters optimized for raytracing + * Splits the mesh into a set of meshlets, similarly to meshopt_buildMeshlets, but optimizes cluster subdivision for raytracing and allows to specify minimum and maximum number of triangles per meshlet. + * + * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound using min_triangles (*not* max!) + * meshlet_vertices must contain enough space for all meshlets, worst case is index_count elements (*not* vertex_count!) + * meshlet_triangles must contain enough space for all meshlets, worst case is index_count elements + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * max_vertices, min_triangles and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512; min_triangles <= max_triangles) + * fill_weight allows to prioritize clusters that are closer to maximum size at some cost to SAH quality; 0.5 is a safe default + */ +MESHOPTIMIZER_API size_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight); + +/** + * Meshlet optimizer + * Reorders meshlet vertices and triangles to maximize locality which can improve rasterizer throughput or ray tracing performance when using fast-build modes. + * + * meshlet_triangles and meshlet_vertices must refer to meshlet data; when buildMeshlets* is used, these need to be computed from meshlet's vertex_offset and triangle_offset + * triangle_count and vertex_count must not exceed implementation limits (vertex_count <= 256, triangle_count <= 512) + */ +MESHOPTIMIZER_API void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count); + +/** + * Experimental: Meshlet optimizer + * Reorders meshlet vertices and triangles to maximize locality, with higher levels resulting in smaller compressed size at the cost of optimization time. + * At level 0 the result is equivalent to meshopt_optimizeMeshlet; levels >= 1 may rotate triangle corners to improve compression (which can change provoking vertex and affect OMM data). + * + * level should be in the range [0, 9] with 0 equivalent to meshopt_optimizeMeshlet and 9 being the slowest; the sweet spot for compression ratio is around 3 + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_optimizeMeshletLevel(unsigned int* meshlet_vertices, size_t vertex_count, unsigned char* meshlet_triangles, size_t triangle_count, int level); struct meshopt_Bounds { @@ -392,13 +768,13 @@ struct meshopt_Bounds }; /** - * Experimental: Cluster bounds generator + * Cluster bounds generator * Creates bounding volumes that can be used for frustum, backface and occlusion culling. * * For backface culling with orthographic projection, use the following formula to reject backfacing clusters: * dot(view, cone_axis) >= cone_cutoff * - * For perspective projection, you can the formula that needs cone apex in addition to axis & cutoff: + * For perspective projection, you can use the formula that needs cone apex in addition to axis & cutoff: * dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff * * Alternatively, you can use the formula that doesn't need cone apex and uses bounding sphere instead: @@ -407,31 +783,157 @@ struct meshopt_Bounds * dot(center - camera_position, cone_axis) >= cone_cutoff * length(center - camera_position) + radius * * The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere - * to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable. + * to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable (for derivation see + * Real-Time Rendering 4th Edition, section 19.3). * - * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer - * index_count should be less than or equal to 256*3 (the function assumes clusters of limited size) + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * vertex_count should specify the number of vertices in the entire mesh, not cluster or meshlet + * indices should have at most 256 unique vertex indices + * index_count/3 and triangle_count must not exceed implementation limits (<= 512) */ -MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeClusterBounds(const uint32_t* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); -MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeMeshletBounds(const struct meshopt_Meshlet* meshlet, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); /** - * Experimental: Spatial sorter + * Sphere bounds generator + * Creates bounding sphere around a set of points or a set of spheres; returns the center and radius of the sphere, with other fields of the result set to 0. + * + * positions should have float3 position in the first 12 bytes of each element + * radii can be NULL; when it's not NULL, it should have a non-negative float radius in the first 4 bytes of each element + */ +MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_t count, size_t positions_stride, const float* radii, size_t radii_stride); + +/** + * Experimental: Extract meshlet-local vertex and triangle indices from absolute cluster indices. + * Fills triangles[] and vertices[] such that vertices[triangles[i]] == indices[i], and returns the number of unique vertices. + * + * indices should have at most 256 unique vertex indices + * index_count/3 must not exceed implementation limits (<= 512) + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count); + +/** + * Cluster partitioner + * Partitions clusters into groups of similar size, prioritizing grouping clusters that share vertices or are close to each other. + * When vertex positions are not provided, only clusters that share vertices will be grouped together, which may result in small partitions for some inputs. + * + * destination must contain enough space for the resulting partition data (cluster_count elements) + * destination[i] will contain the partition id for cluster i, with the total number of partitions returned by the function + * cluster_indices should have the vertex indices referenced by each cluster, stored sequentially + * cluster_index_counts should have the number of indices in each cluster; sum of all cluster_index_counts must be equal to total_index_count + * vertex_positions can be NULL; when it's not NULL, it should have float3 position in the first 12 bytes of each vertex + * target_partition_size is a target size for each partition, in clusters; the resulting partitions may be smaller or larger (up to target + target/3) + */ +MESHOPTIMIZER_API size_t meshopt_partitionClusters(unsigned int* destination, const unsigned int* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size); + +/** + * Spatial sorter * Generates a remap table that can be used to reorder points for spatial locality. * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer. * * destination must contain enough space for the resulting remap table (vertex_count elements) + * vertex_positions should have float3 position in the first 12 bytes of each vertex */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortRemap(uint32_t* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API void meshopt_spatialSortRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); /** - * Experimental: Spatial sorter + * Spatial sorter * Reorders triangles for spatial locality, and generates a new index buffer. The resulting index buffer can be used with other functions like optimizeVertexCache. * * destination must contain enough space for the resulting index buffer (index_count elements) - * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer + * vertex_positions should have float3 position in the first 12 bytes of each vertex */ -MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles(uint32_t* destination, const uint32_t* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +MESHOPTIMIZER_API void meshopt_spatialSortTriangles(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); + +/** + * Spatial clusterizer + * Reorders points into clusters optimized for spatial locality, and generates a new index buffer. + * Ensures the output can be split into cluster_size chunks where each chunk has good positional locality. Only the last chunk will be smaller than cluster_size. + * + * destination must contain enough space for the resulting index buffer (vertex_count elements) + * vertex_positions should have float3 position in the first 12 bytes of each vertex + */ +MESHOPTIMIZER_API void meshopt_spatialClusterPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t cluster_size); + +/** + * Experimental: Opacity micromap generator (measure) + * Computes a subdivision level for each input triangle, as well as deduplicating the triangles that reference the same UVs to reduce rasterization requests. + * Returns the number of OMM entries. + * + * levels and sources must contain enough space for the worst case output (index_count/3 elements, one per resulting OMM entry) + * levels[i] will contain the subdivision level for entry i, with the total number of entries returned by the function; each entry should be rasterized from triangle index sources[i] + * omm_indices must contain enough space for the resulting OMM indices (index_count/3 elements, one per triangle) + * vertex_uvs should have float2 texture coordinate in the first 8 bytes of each vertex + * max_level specifies the maximum subdivision level (0..12) + * target_edge can be 0; when >0, triangle subdivision is adaptive and targets target_edge^2 texel area + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapMeasure(unsigned char* levels, unsigned int* sources, int* omm_indices, const unsigned int* indices, size_t index_count, const float* vertex_uvs, size_t vertex_count, size_t vertex_uvs_stride, unsigned int texture_width, unsigned int texture_height, int max_level, float target_edge); + +/** + * Experimental: Opacity micromap generator (rasterize) + * Rasterizes opacity state for a single triangle entry by sampling the alpha texture, using bilinear filtering and 0.5 alpha cutoff. + * + * result should contain enough space for the output opacity data (which can be computed using meshopt_opacityMapEntrySize) + * level specifies the subdivision level (0..12) + * states should be 2 for 2-state format (opaque/transparent) and 4 for 4-state format (opaque/transparent/unknown) + * uv0/uv1/uv2 should refer to a float2 texture coordinate for each triangle corner; note that micromap data is sensitive to the corner order + * texture_data should point to the alpha channel of the first pixel, encoded as UNORM8 + * texture_stride specifies the distance in bytes between consecutive pixels, e.g. 4 for RGBA input + * texture_pitch specifies the distance in bytes between consecutive rows, e.g. 4*texture_width for tightly packed RGBA input + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_opacityMapRasterize(unsigned char* result, int level, int states, const float* uv0, const float* uv1, const float* uv2, const unsigned char* texture_data, size_t texture_stride, size_t texture_pitch, unsigned int texture_width, unsigned int texture_height); +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapEntrySize(int level, int states); + +/** + * Experimental: Opacity micromap generator (compact) + * Compacts and deduplicates opacity data, merging identical micromap entries and replacing micromap states with special indices (-4..-1) when possible. + * Returns the number of OMM entries after compaction; the data array should be trimmed using the last offset/size. + * + * data should contain opacity data for all input/output entries + * levels should contain subdivision levels for all input/output entries + * offsets should contain offset into data[] for each entry + * levels[i] and offsets[i] will be updated with post-compaction level/offset for entry i, with the total number of entries returned by the function + * omm_indices should contain indices into the original OMM data, and will be updated with a new index or a special index (-4..-1) when possible + * states should be 2 for 2-state format (opaque/transparent) and 4 for 4-state format (opaque/transparent/unknown) + */ +MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapCompact(unsigned char* data, size_t data_size, unsigned char* levels, unsigned int* offsets, size_t omm_count, int* omm_indices, size_t triangle_count, int states); + +/** + * Experimental: Tangent space generator + * Computes per-corner tangent vectors following the MikkTSpace algorithm; for each corner, computes normalized tangent vector (xyz) and orientation (w, +/-1). + * Bitangent can be reconstructed via cross(normal, tangent.xyz) * tangent.w. + * To apply tangents to the mesh, either deindex and reindex it with the tangent stream, or copy tangents to existing vertex data while duplicating + * vertices with different tangent vectors (e.g. on UV mirror seams). + * Input can be indexed or unindexed (indices=NULL); this does not affect the resulting tangents, but indexed inputs are ~30% faster to process. + * + * result must contain enough space for the output tangent data (index_count*4 elements) + * indices can be NULL if the input is unindexed + * vertex_positions should have float3 position in the first 12 bytes of each vertex + * vertex_normals should have unit float3 normal in the first 12 bytes of each vertex + * vertex_uvs should have float2 texture coordinate in the first 8 bytes of each vertex + */ +MESHOPTIMIZER_EXPERIMENTAL void meshopt_generateTangents(float* result, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_normals, size_t vertex_normals_stride, const float* vertex_uvs, size_t vertex_uvs_stride); + +/** + * Quantize a float into half-precision (as defined by IEEE-754 fp16) floating point value + * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest + * Representable magnitude range: [6e-5; 65504] + * Maximum relative reconstruction error: 5e-4 + */ +MESHOPTIMIZER_API unsigned short meshopt_quantizeHalf(float v); + +/** + * Quantize a float into a floating point value with a limited number of significant mantissa bits, preserving the IEEE-754 fp32 binary representation + * Preserves infinities/NaN, flushes denormals to zero, rounds to nearest + * Assumes N is in a valid mantissa precision range, which is 1..23 + */ +MESHOPTIMIZER_API float meshopt_quantizeFloat(float v, int N); + +/** + * Reverse quantization of a half-precision (as defined by IEEE-754 fp16) floating point value + * Preserves Inf/NaN, flushes denormals to zero + */ +MESHOPTIMIZER_API float meshopt_dequantizeHalf(unsigned short h); /** * Set allocation callbacks @@ -439,13 +941,13 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles(uint32_t* destinati * Note that all algorithms only allocate memory for temporary use. * allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first. */ -MESHOPTIMIZER_API void meshopt_setAllocator(void* (*allocate)(size_t), void (*deallocate)(void*)); +MESHOPTIMIZER_API void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*)); #ifdef __cplusplus } /* extern "C" */ #endif -/* Quantization into commonly supported data formats */ +/* Quantization into fixed point normalized formats; these are only available as inline C++ functions */ #ifdef __cplusplus /** * Quantize a float in [0..1] range into an N-bit fixed point unorm value @@ -460,21 +962,6 @@ inline int meshopt_quantizeUnorm(float v, int N); * Maximum reconstruction error: 1/2^N */ inline int meshopt_quantizeSnorm(float v, int N); - -/** - * Quantize a float into half-precision floating point value - * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest - * Representable magnitude range: [6e-5; 65504] - * Maximum relative reconstruction error: 5e-4 - */ -inline unsigned short meshopt_quantizeHalf(float v); - -/** - * Quantize a float into a floating point value with a limited number of significant mantissa bits - * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest - * Assumes N is in a valid mantissa precision range, which is 1..23 - */ -inline float meshopt_quantizeFloat(float v, int N); #endif /** @@ -482,30 +969,40 @@ inline float meshopt_quantizeFloat(float v, int N); * * These functions mirror the C interface the library provides, providing template-based overloads so that * the caller can use an arbitrary type for the index data, both for input and output. - * When the supplied type is the same size as that of uint32_t, the wrappers are zero-cost; when it's not, + * When the supplied type is the same size as that of unsigned int, the wrappers are zero-cost; when it's not, * the wrappers end up allocating memory and copying index data to convert from one type to another. */ #if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS) template -inline size_t meshopt_generateVertexRemap(uint32_t* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size); +inline size_t meshopt_generateVertexRemap(unsigned int* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size); template -inline size_t meshopt_generateVertexRemapMulti(uint32_t* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count); +inline size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count); +template +inline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback); +template +inline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback); template -inline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const uint32_t* remap); +inline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const unsigned int* remap); template inline void meshopt_generateShadowIndexBuffer(T* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride); template inline void meshopt_generateShadowIndexBufferMulti(T* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count); template +inline void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +template +inline void meshopt_generateTessellationIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +template +inline size_t meshopt_generateProvokingIndexBuffer(T* destination, unsigned int* reorder, const T* indices, size_t index_count, size_t vertex_count); +template inline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count); template inline void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, size_t index_count, size_t vertex_count); template -inline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, uint32_t cache_size); +inline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size); template inline void meshopt_optimizeOverdraw(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold); template -inline size_t meshopt_optimizeVertexFetchRemap(uint32_t* destination, const T* indices, size_t index_count, size_t vertex_count); +inline size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count); template inline size_t meshopt_optimizeVertexFetch(void* destination, T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size); template @@ -516,26 +1013,49 @@ template inline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count); template inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size); +template +inline int meshopt_decodeMeshlet(V* vertices, size_t vertex_count, T* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size); +inline size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level); template -inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error); +inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL); template -inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count); +inline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL); +template +inline size_t meshopt_simplifyWithUpdate(T* indices, size_t index_count, float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL); +template +inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error = NULL); +template +inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const unsigned char* vertex_lock, size_t target_index_count, float target_error, float* result_error = NULL); +template +inline size_t meshopt_simplifyPrune(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float target_error); template inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index); template inline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count, T restart_index); template -inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, uint32_t cache_size, uint32_t warp_size, uint32_t buffer_size); -template -inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size); template inline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const T* indices, size_t index_count, size_t vertex_count, size_t vertex_size); template -inline size_t meshopt_buildMeshlets(meshopt_Meshlet* destination, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles); +inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +template +inline meshopt_CoverageStatistics meshopt_analyzeCoverage(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +template +inline size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight); +template +inline size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles); +template +inline size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float cone_weight, float split_factor); +template +inline size_t meshopt_buildMeshletsSpatial(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight); template inline meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); template +inline size_t meshopt_partitionClusters(unsigned int* destination, const T* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size); +template inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride); +template +inline void meshopt_generateTangents(float* result, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_normals, size_t vertex_normals_stride, const float* vertex_uvs, size_t vertex_uvs_stride); #endif /* Inline implementation */ @@ -561,50 +1081,6 @@ inline int meshopt_quantizeSnorm(float v, int N) return int(v * scale + round); } - -inline unsigned short meshopt_quantizeHalf(float v) -{ - union { float f; uint32_t ui; } u = {v}; - uint32_t ui = u.ui; - - int s = (ui >> 16) & 0x8000; - int em = ui & 0x7fffffff; - - /* bias exponent and round to nearest; 112 is relative exponent bias (127-15) */ - int h = (em - (112 << 23) + (1 << 12)) >> 13; - - /* underflow: flush to zero; 113 encodes exponent -14 */ - h = (em < (113 << 23)) ? 0 : h; - - /* overflow: infinity; 143 encodes exponent 16 */ - h = (em >= (143 << 23)) ? 0x7c00 : h; - - /* NaN; note that we convert all types of NaN to qNaN */ - h = (em > (255 << 23)) ? 0x7e00 : h; - - return (unsigned short)(s | h); -} - -inline float meshopt_quantizeFloat(float v, int N) -{ - union { float f; uint32_t ui; } u = {v}; - uint32_t ui = u.ui; - - const int mask = (1 << (23 - N)) - 1; - const int round = (1 << (23 - N)) >> 1; - - int e = ui & 0x7f800000; - uint32_t rui = (ui + round) & ~mask; - - /* round all numbers except inf/nan; this is important to make sure nan doesn't overflow into -0 */ - ui = e == 0x7f800000 ? ui : rui; - - /* flush denormals to zero */ - ui = e == 0 ? 0 : ui; - - u.ui = ui; - return u.f; -} #endif /* Internal implementation helpers */ @@ -612,65 +1088,76 @@ inline float meshopt_quantizeFloat(float v, int N) class meshopt_Allocator { public: - template - struct StorageT + struct Storage { - static void* (*allocate)(size_t); - static void (*deallocate)(void*); + void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t); + void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*); }; - typedef StorageT Storage; +#ifdef MESHOPTIMIZER_ALLOC_EXPORT + MESHOPTIMIZER_API static Storage& storage(); +#else + static Storage& storage() + { + static Storage s = {::operator new, ::operator delete }; + return s; + } +#endif meshopt_Allocator() - : blocks() - , count(0) + : blocks() + , count(0) { } ~meshopt_Allocator() { for (size_t i = count; i > 0; --i) - Storage::deallocate(blocks[i - 1]); + storage().deallocate(blocks[i - 1]); } - template T* allocate(size_t size) + template + T* allocate(size_t size) { assert(count < sizeof(blocks) / sizeof(blocks[0])); - T* result = static_cast(Storage::allocate(size > size_t(-1) / sizeof(T) ? size_t(-1) : size * sizeof(T))); + T* result = static_cast(storage().allocate(size > size_t(-1) / sizeof(T) ? size_t(-1) : size * sizeof(T))); blocks[count++] = result; return result; } + void deallocate(void* ptr) + { + assert(count > 0 && blocks[count - 1] == ptr); + storage().deallocate(ptr); + count--; + } + private: void* blocks[24]; size_t count; }; - -// This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker -template void* (*meshopt_Allocator::StorageT::allocate)(size_t) = operator new; -template void (*meshopt_Allocator::StorageT::deallocate)(void*) = operator delete; #endif /* Inline implementation for C++ templated wrappers */ #if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS) -template +template struct meshopt_IndexAdapter; template struct meshopt_IndexAdapter { T* result; - uint32_t* data; + unsigned int* data; size_t count; meshopt_IndexAdapter(T* result_, const T* input, size_t count_) : result(result_) - , data(0) + , data(NULL) , count(count_) { - size_t size = count > size_t(-1) / sizeof(uint32_t) ? size_t(-1) : count * sizeof(uint32_t); + size_t size = count > size_t(-1) / sizeof(unsigned int) ? size_t(-1) : count * sizeof(unsigned int); - data = static_cast(meshopt_Allocator::Storage::allocate(size)); + data = static_cast(meshopt_Allocator::storage().allocate(size)); if (input) { @@ -687,51 +1174,75 @@ struct meshopt_IndexAdapter result[i] = T(data[i]); } - meshopt_Allocator::Storage::deallocate(data); + meshopt_Allocator::storage().deallocate(data); } }; template struct meshopt_IndexAdapter { - uint32_t* data; + unsigned int* data; meshopt_IndexAdapter(T* result, const T* input, size_t) - : data(reinterpret_cast(result ? result : const_cast(input))) + : data(reinterpret_cast(result ? result : const_cast(input))) { } }; template -inline size_t meshopt_generateVertexRemap(uint32_t* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size) +inline size_t meshopt_generateVertexRemap(unsigned int* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size) { - meshopt_IndexAdapter in(0, indices, indices ? index_count : 0); + meshopt_IndexAdapter in(NULL, indices, indices ? index_count : 0); - return meshopt_generateVertexRemap(destination, indices ? in.data : 0, index_count, vertices, vertex_count, vertex_size); + return meshopt_generateVertexRemap(destination, indices ? in.data : NULL, index_count, vertices, vertex_count, vertex_size); } template -inline size_t meshopt_generateVertexRemapMulti(uint32_t* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count) +inline size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count) { - meshopt_IndexAdapter in(0, indices, indices ? index_count : 0); + meshopt_IndexAdapter in(NULL, indices, indices ? index_count : 0); - return meshopt_generateVertexRemapMulti(destination, indices ? in.data : 0, index_count, vertex_count, streams, stream_count); + return meshopt_generateVertexRemapMulti(destination, indices ? in.data : NULL, index_count, vertex_count, streams, stream_count); +} + +template +inline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback) +{ + struct Call + { + static int compare(void* context, unsigned int lhs, unsigned int rhs) { return (*static_cast(context))(lhs, rhs) ? 1 : 0; } + }; + + return meshopt_generateVertexRemapCustom(destination, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, &Call::compare, &callback); +} + +template +inline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback) +{ + struct Call + { + static int compare(void* context, unsigned int lhs, unsigned int rhs) { return (*static_cast(context))(lhs, rhs) ? 1 : 0; } + }; + + meshopt_IndexAdapter in(NULL, indices, indices ? index_count : 0); + + return meshopt_generateVertexRemapCustom(destination, indices ? in.data : NULL, index_count, vertex_positions, vertex_count, vertex_positions_stride, &Call::compare, &callback); } template -inline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const uint32_t* remap) +inline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const unsigned int* remap) { - meshopt_IndexAdapter in(0, indices, indices ? index_count : 0); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, indices ? index_count : 0); + meshopt_IndexAdapter out(destination, NULL, index_count); - meshopt_remapIndexBuffer(out.data, indices ? in.data : 0, index_count, remap); + meshopt_remapIndexBuffer(out.data, indices ? in.data : NULL, index_count, remap); } template inline void meshopt_generateShadowIndexBuffer(T* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); meshopt_generateShadowIndexBuffer(out.data, in.data, index_count, vertices, vertex_count, vertex_size, vertex_stride); } @@ -739,17 +1250,48 @@ inline void meshopt_generateShadowIndexBuffer(T* destination, const T* indices, template inline void meshopt_generateShadowIndexBufferMulti(T* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); meshopt_generateShadowIndexBufferMulti(out.data, in.data, index_count, vertex_count, streams, stream_count); } +template +inline void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count * 2); + + meshopt_generateAdjacencyIndexBuffer(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); +} + +template +inline void meshopt_generateTessellationIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count * 4); + + meshopt_generateTessellationIndexBuffer(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); +} + +template +inline size_t meshopt_generateProvokingIndexBuffer(T* destination, unsigned int* reorder, const T* indices, size_t index_count, size_t vertex_count) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); + + size_t bound = vertex_count + (index_count / 3); + assert(bound == 0 || size_t(T(bound - 1)) == bound - 1); // bound - 1 must fit in T + (void)bound; + + return meshopt_generateProvokingIndexBuffer(out.data, reorder, in.data, index_count, vertex_count); +} + template inline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); meshopt_optimizeVertexCache(out.data, in.data, index_count, vertex_count); } @@ -757,17 +1299,17 @@ inline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t template inline void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, size_t index_count, size_t vertex_count) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); meshopt_optimizeVertexCacheStrip(out.data, in.data, index_count, vertex_count); } template -inline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, uint32_t cache_size) +inline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); meshopt_optimizeVertexCacheFifo(out.data, in.data, index_count, vertex_count, cache_size); } @@ -775,16 +1317,16 @@ inline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, si template inline void meshopt_optimizeOverdraw(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); meshopt_optimizeOverdraw(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, threshold); } template -inline size_t meshopt_optimizeVertexFetchRemap(uint32_t* destination, const T* indices, size_t index_count, size_t vertex_count) +inline size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count) { - meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); return meshopt_optimizeVertexFetchRemap(destination, in.data, index_count, vertex_count); } @@ -800,7 +1342,7 @@ inline size_t meshopt_optimizeVertexFetch(void* destination, T* indices, size_t template inline size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count) { - meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); return meshopt_encodeIndexBuffer(buffer, buffer_size, in.data, index_count); } @@ -817,7 +1359,7 @@ inline int meshopt_decodeIndexBuffer(T* destination, size_t index_count, const u template inline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count) { - meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); return meshopt_encodeIndexSequence(buffer, buffer_size, in.data, index_count); } @@ -831,29 +1373,78 @@ inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const return meshopt_decodeIndexSequence(destination, index_count, sizeof(T), buffer, buffer_size); } -template -inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error) +template +inline int meshopt_decodeMeshlet(V* vertices, size_t vertex_count, T* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + char types_valid[(sizeof(V) == 2 || sizeof(V) == 4) && (sizeof(T) == 1 || sizeof(T) == 4) ? 1 : -1]; + (void)types_valid; - return meshopt_simplify(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error); + return meshopt_decodeMeshlet(vertices, vertex_count, sizeof(V), triangles, triangle_count, sizeof(T) == 1 ? 3 : 4, buffer, buffer_size); +} + +inline size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level) +{ + return meshopt_encodeVertexBufferLevel(buffer, buffer_size, vertices, vertex_count, vertex_size, level, -1); } template -inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count) +inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* result_error) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, target_index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); - return meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count); + return meshopt_simplify(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error, options, result_error); +} + +template +inline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); + + return meshopt_simplifyWithAttributes(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_attributes, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options, result_error); +} + +template +inline size_t meshopt_simplifyWithUpdate(T* indices, size_t index_count, float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error) +{ + meshopt_IndexAdapter inout(indices, indices, index_count); + + return meshopt_simplifyWithUpdate(inout.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_attributes, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options, result_error); +} + +template +inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); + + return meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, NULL, target_index_count, target_error, result_error); +} + +template +inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const unsigned char* vertex_lock, size_t target_index_count, float target_error, float* result_error) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); + + return meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_lock, target_index_count, target_error, result_error); +} + +template +inline size_t meshopt_simplifyPrune(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float target_error) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); + + return meshopt_simplifyPrune(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_error); } template inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, (index_count / 3) * 5); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, (index_count / 3) * 5); return meshopt_stripify(out.data, in.data, index_count, vertex_count, unsigned(restart_index)); } @@ -861,64 +1452,112 @@ inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_co template inline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count, T restart_index) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, (index_count - 2) * 3); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count == 0 ? 0 : (index_count - 2) * 3); return meshopt_unstripify(out.data, in.data, index_count, unsigned(restart_index)); } template -inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, uint32_t cache_size, uint32_t warp_size, uint32_t buffer_size) +inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size) { - meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); - return meshopt_analyzeVertexCache(in.data, index_count, vertex_count, cache_size, warp_size, buffer_size); -} - -template -inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) -{ - meshopt_IndexAdapter in(0, indices, index_count); - - return meshopt_analyzeOverdraw(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); + return meshopt_analyzeVertexCache(in.data, index_count, vertex_count, cache_size, warp_size, primgroup_size); } template inline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const T* indices, size_t index_count, size_t vertex_count, size_t vertex_size) { - meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); return meshopt_analyzeVertexFetch(in.data, index_count, vertex_count, vertex_size); } template -inline size_t meshopt_buildMeshlets(meshopt_Meshlet* destination, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles) +inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) { - meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); - return meshopt_buildMeshlets(destination, in.data, index_count, vertex_count, max_vertices, max_triangles); + return meshopt_analyzeOverdraw(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); +} + +template +inline meshopt_CoverageStatistics meshopt_analyzeCoverage(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + + return meshopt_analyzeCoverage(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); +} + +template +inline size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + + return meshopt_buildMeshlets(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, max_triangles, cone_weight); +} + +template +inline size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + + return meshopt_buildMeshletsScan(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_count, max_vertices, max_triangles); +} + +template +inline size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float cone_weight, float split_factor) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + + return meshopt_buildMeshletsFlex(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, min_triangles, max_triangles, cone_weight, split_factor); +} + +template +inline size_t meshopt_buildMeshletsSpatial(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight) +{ + meshopt_IndexAdapter in(NULL, indices, index_count); + + return meshopt_buildMeshletsSpatial(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, min_triangles, max_triangles, fill_weight); } template inline meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) { - meshopt_IndexAdapter in(0, indices, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); return meshopt_computeClusterBounds(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); } +template +inline size_t meshopt_partitionClusters(unsigned int* destination, const T* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size) +{ + meshopt_IndexAdapter in(NULL, cluster_indices, total_index_count); + + return meshopt_partitionClusters(destination, in.data, total_index_count, cluster_index_counts, cluster_count, vertex_positions, vertex_count, vertex_positions_stride, target_partition_size); +} + template inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride) { - meshopt_IndexAdapter in(0, indices, index_count); - meshopt_IndexAdapter out(destination, 0, index_count); + meshopt_IndexAdapter in(NULL, indices, index_count); + meshopt_IndexAdapter out(destination, NULL, index_count); meshopt_spatialSortTriangles(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride); } + +template +inline void meshopt_generateTangents(float* result, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_normals, size_t vertex_normals_stride, const float* vertex_uvs, size_t vertex_uvs_stride) +{ + meshopt_IndexAdapter in(NULL, indices, indices ? index_count : 0); + + meshopt_generateTangents(result, indices ? in.data : NULL, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_normals, vertex_normals_stride, vertex_uvs, vertex_uvs_stride); +} #endif /** - * Copyright (c) 2016-2020 Arseny Kapoulkine + * Copyright (c) 2016-2026 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation diff --git a/engine/inc/uf/ext/meshopt/meshopt.h b/engine/inc/uf/ext/meshopt/meshopt.h index 3b093385..bc28ba15 100644 --- a/engine/inc/uf/ext/meshopt/meshopt.h +++ b/engine/inc/uf/ext/meshopt/meshopt.h @@ -42,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), indicesCount * simplify, targetError); + size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), indicesCount * simplify, targetError, 0, &realError); // size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetIndices); if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError); diff --git a/engine/src/engine/ext/gui/behavior.cpp b/engine/src/engine/ext/gui/behavior.cpp index d5f41ac8..66fcad79 100644 --- a/engine/src/engine/ext/gui/behavior.cpp +++ b/engine/src/engine/ext/gui/behavior.cpp @@ -28,14 +28,6 @@ #define EXT_COLOR_FLOATS 1 namespace { - struct { - bool initialized = false; - - uf::Mesh mesh; - uf::Image image; - uf::Serializer settings; - } defaults; - struct Mesh { pod::Vector3f position; pod::Vector2f uv; @@ -77,20 +69,33 @@ UF_VERTEX_DESCRIPTOR(Mesh, ) namespace { - uf::Image& generateImage( uf::Image& image ) { - image.open(uf::io::root+"/textures/missing.png"); - return image; + // default to center + pod::Vector2f parseAnchor( const uf::stl::string& anchor, const pod::Vector2f& def = {0.5f, 0.5f} ) { + if ( anchor == "top-center" || anchor == "top" ) return {0.5f, 0.0f}; + if ( anchor == "top-left" ) return {0.0f, 0.0f}; + if ( anchor == "top-right" ) return {1.0f, 0.0f}; + + if ( anchor == "center" ) return {0.5f, 0.5f}; + if ( anchor == "center-left" || anchor == "left" ) return {0.0f, 0.5f}; + if ( anchor == "center-right" || anchor == "right" ) return {1.0f, 0.5f}; + + if ( anchor == "bottom-center" || anchor == "bottom" ) return {0.5f, 1.0f}; + if ( anchor == "bottom-left" ) return {0.0f, 1.0f}; + if ( anchor == "bottom-right" ) return {1.0f, 1.0f}; + + return def; } - uf::Mesh& generateMesh( uf::Mesh& mesh, const pod::Vector4f& color = {1, 1, 1, 1} ) { + + uf::Mesh& generateMesh( uf::Mesh& mesh, const pod::Vector4f& color, const pod::Vector2f& center ) { mesh.bind<::Mesh, uint16_t>(); mesh.insertVertices<::Mesh>({ - { pod::Vector3f{-1.0f, 1.0f, 0.0f}, pod::Vector2f{0.0f, 0.0f}, color }, - { pod::Vector3f{-1.0f, -1.0f, 0.0f}, pod::Vector2f{0.0f, 1.0f}, color }, - { pod::Vector3f{ 1.0f, -1.0f, 0.0f}, pod::Vector2f{1.0f, 1.0f}, color }, + { pod::Vector3f{-1.0f, 1.0f, 0.0f} - center, pod::Vector2f{0.0f, 0.0f}, color }, + { pod::Vector3f{-1.0f, -1.0f, 0.0f} - center, pod::Vector2f{0.0f, 1.0f}, color }, + { pod::Vector3f{ 1.0f, -1.0f, 0.0f} - center, pod::Vector2f{1.0f, 1.0f}, color }, - { pod::Vector3f{ 1.0f, -1.0f, 0.0f}, pod::Vector2f{1.0f, 1.0f}, color }, - { pod::Vector3f{ 1.0f, 1.0f, 0.0f}, pod::Vector2f{1.0f, 0.0f}, color }, - { pod::Vector3f{-1.0f, 1.0f, 0.0f}, pod::Vector2f{0.0f, 0.0f}, color }, + { pod::Vector3f{ 1.0f, -1.0f, 0.0f} - center, pod::Vector2f{1.0f, 1.0f}, color }, + { pod::Vector3f{ 1.0f, 1.0f, 0.0f} - center, pod::Vector2f{1.0f, 0.0f}, color }, + { pod::Vector3f{-1.0f, 1.0f, 0.0f} - center, pod::Vector2f{0.0f, 0.0f}, color }, }); mesh.insertIndices({ 0, 1, 2, 3, 4, 5 @@ -98,6 +103,10 @@ namespace { return mesh; } + + uf::Mesh& generateMesh( uf::Mesh& mesh, const pod::Vector4f& color = {1, 1, 1, 1}, const uf::stl::string& alignment = "center" ) { + return ::generateMesh( mesh, color, ::parseAnchor( alignment ) * 2.0f - 1.0f ); + } } // YUCK @@ -120,54 +129,11 @@ void ext::GuiBehavior::initialize( uf::Object& self ) { auto& scene = uf::scene::getCurrentScene(); - if ( !::defaults.initialized ) { - ::defaults.initialized = true; - - ::generateImage( ::defaults.image ); - ::generateMesh( ::defaults.mesh ); - } -/* - if ( ext::json::isNull( ::defaults.settings["metadata"] ) ) ::defaults.settings.readFromFile(uf::io::root+"/entities/gui.json"); - // set defaults - if ( metadataJson["string"].is() ) { - auto copyMetadataJson = metadataJson; - ext::json::forEach(::defaults.settings["metadata"], [&]( const uf::stl::string& key, ext::json::Value& value ){ - if ( ext::json::isNull( copyMetadataJson[key] ) ) { - metadataJson[key] = value; - } - }); - } -*/ - UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson); this->addHook( "gui:Update.%UID%", [&](ext::payloads::GuiInitializationPayload& payload){ auto& graphic = this->getComponent(); - /* - if ( metadataJson["mode"].as() == "flat" ) { - if ( ext::json::isNull(metadataJson["projection"]) ) metadataJson["projection"] = false; - if ( ext::json::isNull(metadataJson["flip uv"]) ) metadataJson["flip uv"] = true; - if ( ext::json::isNull(metadataJson["front face"]) ) metadataJson["front face"] = "ccw"; - } else { - if ( ext::json::isNull(metadataJson["projection"]) ) metadataJson["projection"] = true; - if ( ext::json::isNull(metadataJson["flip uv"]) ) metadataJson["flip uv"] = false; - if ( ext::json::isNull(metadataJson["front face"]) ) metadataJson["front face"] = "cw"; - } - - if ( metadataJson["world"].as() ) { - // metadataJson["gui layer"] = false; - } else { - #if UF_USE_OPENGL - if ( ext::json::isNull(metadataJson["cull mode"]) ) metadataJson["cull mode"] = "front"; - if ( uf::matrix::reverseInfiniteProjection ) metadata.depth = 1 - metadata.depth; - // transform.position.z = metadata.depth; - #else - // if ( metadataJson["flip uv"].as() ) for ( auto& v : vertices ) v.uv.y = 1 - v.uv.y; - #endif - // for ( auto& v : vertices ) v.position.z = metadata.depth; - } - */ if ( ext::json::isNull(metadataJson["cull mode"]) ) metadataJson["cull mode"] = "back"; // @@ -186,11 +152,6 @@ void ext::GuiBehavior::initialize( uf::Object& self ) { auto& texture = graphic.material.textures.emplace_back(); texture.loadFromImage( image ); - - // update transform - { - - } if ( payload.free ) { delete payload.image; @@ -263,6 +224,16 @@ void ext::GuiBehavior::initialize( uf::Object& self ) { } } + if ( metadata.space == "screen" ) { + auto anchor = ::parseAnchor( metadata.anchor, metadata.pivot ) * 2.0f - 1.0f; + transform.position.x += anchor.x; + transform.position.y += anchor.y; + /* + transform.position.x += anchor.x * ((float) metadata.size.x / uf::renderer::settings::width) * transform.scale.x; + transform.position.y += anchor.y * ((float) metadata.size.y / uf::renderer::settings::height) * transform.scale.y; + */ + } + metadata.initialized = true; }); @@ -274,7 +245,7 @@ void ext::GuiBehavior::initialize( uf::Object& self ) { auto& image = uf::asset::get( payload ); // generate default mesh - ::generateMesh( mesh, metadata.color ); + ::generateMesh( mesh, metadata.color, metadata.alignment ); { ext::payloads::GuiInitializationPayload payload; @@ -291,12 +262,9 @@ void ext::GuiBehavior::initialize( uf::Object& self ) { if ( !clickTimer.running() ) clickTimer.start(); this->addHook( "window:Mouse.Click", [&](pod::payloads::windowMouseClick& payload){ - if ( metadata.world ) return; - //if ( !metadata.boxMin && !metadata.boxMax ) return; + if ( metadata.space != "screen" ) return; if ((metadata.boxMin.x > metadata.boxMax.x)||(metadata.boxMin.y > metadata.boxMax.y)) return; - // uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); - // pod::Vector2ui guiSize = manager ? manager->getComponent().size : pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; bool clicked = false; @@ -386,12 +354,9 @@ void ext::GuiBehavior::initialize( uf::Object& self ) { hoverTimer.start( uf::Time<>(-1000000) ); this->addHook( "window:Mouse.Moved", [&](pod::payloads::windowMouseMoved& payload){ - if ( metadata.world ) return; - //if ( !metadata.boxMin && !metadata.boxMax ) return; + if ( metadata.space != "screen" ) return; if ((metadata.boxMin.x > metadata.boxMax.x)||(metadata.boxMin.y > metadata.boxMax.y)) return; - // uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); - // pod::Vector2ui guiSize = manager ? manager->getComponent().size : pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; bool hovered = false; @@ -488,22 +453,18 @@ void ext::GuiBehavior::tick( uf::Object& self ) { auto& controller = scene.getController(); auto& camera = controller.getComponent(); -// uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); -// pod::Vector2ui guiSize = manager ? manager->getComponent().size : pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; - pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; + if ( metadata.space == "screen" ) { + pod::Vector2f guiSize = pod::Vector2f{ uf::renderer::settings::width, uf::renderer::settings::height }; + pod::Vector2f scale = { 1, 1 }; - if ( metadata.scaleMode == "fixed" || metadata.scaleMode == "fixed-x" ) { - flatten.scale.x *= (float) metadata.size.x / (float) guiSize.x; - } - if ( metadata.scaleMode == "fixed" || metadata.scaleMode == "fixed-y" ) { - flatten.scale.y *= (float) metadata.size.x / (float) guiSize.y; - } + if ( metadata.scaling == "fixed" || metadata.scaling == "fixed-x" ) scale.x = (float) metadata.size.x / guiSize.x; + if ( metadata.scaling == "fixed" || metadata.scaling == "fixed-y" ) scale.y = (float) metadata.size.x / guiSize.y; - if ( metadata.scaleMode == "relative" || metadata.scaleMode == "relative-x" ) { - flatten.scale.x *= (float) guiSize.y / (float) guiSize.x; - } - if ( metadata.scaleMode == "relative" || metadata.scaleMode == "relative-y" ) { - flatten.scale.y *= (float) guiSize.x / (float) guiSize.y; + if ( metadata.scaling == "relative" || metadata.scaling == "relative-x" ) scale.x = (float) guiSize.y / guiSize.x; + if ( metadata.scaling == "relative" || metadata.scaling == "relative-y" ) scale.y = (float) guiSize.x / guiSize.y; + + flatten.scale.x *= scale.x; + flatten.scale.y *= scale.y; } // bind UBO @@ -619,33 +580,48 @@ void ext::GuiBehavior::tick( uf::Object& self ) { void ext::GuiBehavior::render( uf::Object& self ){} void ext::GuiBehavior::destroy( uf::Object& self ){} void ext::GuiBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){ - serializer["color"] = uf::vector::encode( /*this->*/color ); - serializer["uv"] = uf::vector::encode( /*this->*/uv ); -// serializer["scaling"] = uf::vector::encode( /*this->*/scaling ); - serializer["depth"] = /*this->*/depth; serializer["mode"] = /*this->*/mode; - serializer["renderMode"] = /*this->*/renderMode; - serializer["scaling"] = /*this->*/scaleMode; serializer["clickable"] = /*this->*/clickable; serializer["clicked"] = /*this->*/clicked; - serializer["hoverable"] = /*this->*/hoverable; serializer["hovered"] = /*this->*/hovered; + + serializer["size"] = uf::vector::encode( /*this->*/size ); + serializer["uv"] = uf::vector::encode( /*this->*/uv ); + serializer["color"] = uf::vector::encode( /*this->*/color ); + + serializer["renderMode"] = /*this->*/renderMode; + serializer["scaling"] = /*this->*/scaling; + // + serializer["anchor"] = /*this->*/anchor; + serializer["alignment"] = /*this->*/alignment; + serializer["space"] = /*this->*/space; + serializer["pivot"] = uf::vector::encode(/*this->*/pivot); + } void ext::GuiBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){ - /*this->*/color = uf::vector::decode( serializer["color"], /*this->*/color ); - /*this->*/uv = uf::vector::decode( serializer["uv"], /*this->*/uv ); -// /*this->*/scaling = uf::vector::decode( serializer["scaling"], /*this->*/scaling ); - /*this->*/depth = serializer["depth"].as( /*this->*/depth ); /*this->*/mode = serializer["mode"].as( /*this->*/mode ); - /*this->*/renderMode = serializer["renderMode"].as( /*this->*/renderMode ); - /*this->*/scaleMode = serializer["scaling"].as( /*this->*/scaleMode ); - + /*this->*/clickable = serializer["clickable"].as( /*this->*/clickable ); +// /*this->*/clicked = serializer["clicked"].as( /*this->*/clicked ); /*this->*/hoverable = serializer["hoverable"].as( /*this->*/hoverable ); +// /*this->*/hovered = serializer["hovered"].as( /*this->*/hovered ); + + /*this->*/size = uf::vector::decode( serializer["size"], /*this->*/size ); + /*this->*/uv = uf::vector::decode( serializer["uv"], /*this->*/uv ); + /*this->*/color = uf::vector::decode( serializer["color"], /*this->*/color ); + + /*this->*/renderMode = serializer["renderMode"].as( /*this->*/renderMode ); + /*this->*/scaling = serializer["scaling"].as( /*this->*/scaling ); + + /*this->*/anchor = serializer["anchor"].as( /*this->*/anchor ); + /*this->*/alignment = serializer["alignment"].as( /*this->*/alignment ); + /*this->*/space = serializer["space"].as( /*this->*/space ); + /*this->*/pivot = uf::vector::decode( serializer["pivot"], /*this->*/pivot ); + } #undef this @@ -653,7 +629,6 @@ void ext::GuiBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& #include UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::GuiBehavior::Metadata, UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::initialized), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::world), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::depth), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::mode), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::clickable), @@ -661,11 +636,14 @@ UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::GuiBehavior::Metadata, UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::hoverable), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::hovered), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::size), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::scale), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::uv), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::color), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::renderMode), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::scaleMode), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::scaling), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::boxMin), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::boxMax) + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::boxMax), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::anchor), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::alignment), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::space), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::pivot) ) \ No newline at end of file diff --git a/engine/src/engine/ext/gui/behavior.h b/engine/src/engine/ext/gui/behavior.h index 0b0045b6..4683d829 100644 --- a/engine/src/engine/ext/gui/behavior.h +++ b/engine/src/engine/ext/gui/behavior.h @@ -20,8 +20,6 @@ namespace ext { UF_BEHAVIOR_DEFINE_METADATA( bool initialized = false; - bool world = false; - float depth = 0; size_t mode = 0; @@ -31,15 +29,19 @@ namespace ext { bool hovered = false; pod::Vector2ui size = {}; - pod::Vector2f scale = { 1, 1 }; pod::Vector4f uv = { 0, 0, 1, 1 }; pod::Vector4f color = { 1, 1, 1, 1 }; uf::stl::string renderMode = "Gui"; - uf::stl::string scaleMode = "fixed"; + uf::stl::string scaling = "fixed"; pod::Vector2f boxMin = { 1, 1 }; pod::Vector2f boxMax = { 1, 1 }; + + uf::stl::string space = "screen"; + pod::Vector2f pivot = {0.5f, 0.5f}; // if anchor is not a valid string + uf::stl::string anchor = ""; // blank as the default changes between normal GUI and glyph + uf::stl::string alignment = ""; // ^ ); } } \ No newline at end of file diff --git a/engine/src/engine/ext/gui/glyph/behavior.cpp b/engine/src/engine/ext/gui/glyph/behavior.cpp index 2bc5274a..1dcc9800 100644 --- a/engine/src/engine/ext/gui/glyph/behavior.cpp +++ b/engine/src/engine/ext/gui/glyph/behavior.cpp @@ -108,226 +108,268 @@ namespace { return seed; } - uf::stl::vector<::GlyphBox> generateGlyphs( uf::Object& self, const uf::stl::string& _string ) { - uf::stl::vector<::GlyphBox> gs; - - #if UF_USE_FREETYPE - auto& glyphs = ::glyphs; - auto& metadata = this->getComponent(); - auto& metadataGui = this->getComponent(); - auto& transform = this->getComponent>(); + // default to left + pod::Vector2f parseAnchor( const uf::stl::string& anchor, const pod::Vector2f& def = {0.0f, 0.0f} ) { + if ( anchor == "top-center" || anchor == "top" ) return {0.5f, 0.0f}; + if ( anchor == "top-left" ) return {0.0f, 0.0f}; + if ( anchor == "top-right" ) return {1.0f, 0.0f}; + if ( anchor == "center" ) return {0.5f, 0.5f}; + if ( anchor == "center-left" || anchor == "left" ) return {0.0f, 0.5f}; + if ( anchor == "center-right" || anchor == "right" ) return {1.0f, 0.5f}; - auto string = _string == "" ? metadata.string : _string; - auto font = uf::io::root+"/fonts/" + metadata.font; - auto color = metadataGui.color; + if ( anchor == "bottom-center" || anchor == "bottom" ) return {0.5f, 1.0f}; + if ( anchor == "bottom-left" ) return {0.0f, 1.0f}; + if ( anchor == "bottom-right" ) return {1.0f, 1.0f}; - auto origin = uf::stl::string("top"); - auto align = uf::stl::string("center"); - auto direction = uf::stl::string("down"); + return def; + } - if ( glyphs.cache[font].empty() ) ext::freetype::initialize( glyphs.glyph, font ); + struct TextToken { + uf::stl::string text; + pod::Vector3f color; + }; - struct { - struct { - float x = 0; - float y = 0; - } origin; - - struct { - float x = 0; - float y = 0; - } cursor; + float hexToFloat( const uf::stl::string& str ) { + int value; + uf::stl::stringstream stream; + stream << str; + stream >> std::hex >> value; + return value / 255.0f; + } - struct { - float sum = 0; - float len = 0; - float proc = 0; - float tab = 4; - } average; - - struct { - float x = 0; - float y = 0; - } biggest; + // parses a text for special tokens, associating strings with color + // should maybe be utf8, but in theory it shouldn't matter since tags are ASCII + uf::stl::vector parseTextTokens( const uf::stl::string& text, pod::Vector3f color ) { + uf::stl::vector tokens; - struct { - float w = 0; - float h = 0; - } box; + auto tagLength = 10; // hard-coded cringe + bool colorChanged = false; + size_t currentPos = 0; + size_t textLength = text.length(); - struct { - uf::stl::vector container; - size_t index = 0; - } colors; - } stat; + TextToken currentToken; + currentToken.color = color; - stat.colors.container.push_back(color); + while ( currentPos < textLength ) { + size_t tagStart = text.find("${#", currentPos); - // grab escaped color markers: ${#RRGGBB} - { - uf::stl::unordered_map colors; - uf::stl::string text = string; - - std::regex regex("\\$\\{\\#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})\\}"); - std::smatch match; - - bool matched = false; - - int maxTries = 128; - while ( (matched = std::regex_search( text, match, regex )) && --maxTries > 0 ) { - struct { - uf::stl::string str; - int dec; - } r, g, b; - r.str = match[1].str(); - g.str = match[2].str(); - b.str = match[3].str(); - - { uf::stl::stringstream stream; stream << r.str; stream >> std::hex >> r.dec; } - { uf::stl::stringstream stream; stream << g.str; stream >> std::hex >> g.dec; } - { uf::stl::stringstream stream; stream << b.str; stream >> std::hex >> b.dec; } - - pod::Vector3f color = { r.dec / 255.0f, g.dec / 255.0f, b.dec / 255.0f }; - - stat.colors.container.push_back(color); - text = uf::string::replace( text, "${" + r.str + g.str + b.str + "}", "\x7F" ); + if ( tagStart == uf::stl::string::npos ) { + currentToken.text += text.substr(currentPos); + if ( !currentToken.text.empty() || colorChanged ) { + tokens.emplace_back(currentToken); + } + break; + } + + if ( tagStart > currentPos ) { + currentToken.text += text.substr( currentPos, tagStart - currentPos ); // append everything up to the tag + tokens.emplace_back( currentToken ); // commit token + currentToken.text = ""; // reset text + } + + // validate tag + if ( tagStart + tagLength <= textLength && text[tagStart + tagLength - 1] == '}' ) { + uf::stl::string rHex = text.substr(tagStart + 3, 2); + uf::stl::string gHex = text.substr(tagStart + 5, 2); + uf::stl::string bHex = text.substr(tagStart + 7, 2); + + // change color + currentToken.color = { hexToFloat(rHex), hexToFloat(gHex), hexToFloat(bHex) }; + colorChanged = true; + + // advance past the tag + currentPos = tagStart + tagLength; + } else { + currentToken.text += "${#"; // treat it as normal text + currentPos = tagStart + 3; } - if ( maxTries == 0 ) text += "\n(error formatting)"; - string = text; } - #if 0 - std::wstring_convert, wchar_t> convert; - std::wstring str = convert.from_bytes(string); - #else - std::u8string str(string.begin(), string.end()); - #endif - if ( str.size() == 0 ) return gs; - // Calculate statistics - { - // Find tallest glyph for new line - for ( auto it = str.begin(); it != str.end(); ++it ) { - uint64_t c = *it; if ( c == '\n' ) continue; if ( c == '\t' ) continue; if ( c == 0x01 ) continue; - auto key = hashGlyphSettings( c, metadata ); - auto& glyph = glyphs.cache[font][key]; + return tokens; + } + // compute the boxes for a given string and settings + uf::stl::vector calculateGlyphLayout( uf::Object& self, const uf::stl::vector& tokens, const ext::GuiGlyphBehavior::Metadata& metadata, const ext::GuiBehavior::Metadata& metadataGui, const uf::stl::string& font ) { + uf::stl::vector layout; + auto& glyphsCache = ::glyphs.cache[font]; + + if ( glyphsCache.empty() ) { + ext::freetype::initialize( ::glyphs.glyph, font ); + } + + pod::Vector2f anchor = ::parseAnchor( metadataGui.alignment ); + pod::Vector2f cursor = { 0.0f, 0.0f }; + float maxTextWidth = 0.0f; + float maxTextHeight = 0.0f; + float tallestGlyphY = 0.0f; + float averageTabWidth = 0.0f; + float totalWidth = 0.0f; + int charCount = 0; + + // generate glyph and line height + for ( const auto& token : tokens ) { + std::u8string str(token.text.begin(), token.text.end()); + for ( uint64_t c : str ) { + if ( c == '\n' || c == '\t' ) continue; // special characters + + auto key = hashGlyphSettings(c, metadata); + auto& glyph = glyphsCache[key]; + + // generate glyph if ( !glyph.generated() ) { - glyph.setPadding( { metadata.padding[0], metadata.padding[1] } ); - glyph.setSpread( metadata.spread ); - glyph.useSdf( metadata.sdf ); - glyph.generate( glyphs.glyph, c, metadata.size ); + glyph.setPadding({ metadata.padding[0], metadata.padding[1] }); + glyph.setSpread(metadata.spread); + glyph.useSdf(metadata.sdf); + glyph.generate(::glyphs.glyph, c, metadata.size); } - - stat.biggest.x = std::max( (float) stat.biggest.x, (float) glyph.getSize().x); - stat.biggest.y = std::max( (float) stat.biggest.y, (float) glyph.getSize().y); - - stat.average.sum += glyph.getSize().x; - ++stat.average.len; + tallestGlyphY = std::max(tallestGlyphY, (float) glyph.getSize().y); + totalWidth += glyph.getSize().x; // should probably be reset on new-line to find the widest line + charCount++; } - stat.average.proc = stat.average.sum / stat.average.len; - stat.average.tab *= stat.average.proc; + } - // Calculate box: Second pass required because of tab - stat.cursor.x = 0; - stat.cursor.y = 0; - stat.origin.x = 0; - stat.origin.y = 0; + if ( charCount > 0 ) averageTabWidth = (totalWidth / charCount) * 4.0f; + cursor.y = tallestGlyphY; - for ( auto it = str.begin(); it != str.end(); ++it ) { - uint64_t c = *it; if ( c == '\n' ) { - stat.cursor.y += stat.biggest.y; - stat.cursor.x = 0; + // calculate positions + for ( const auto& token : tokens ) { + std::u8string str( token.text.begin(), token.text.end() ); + for ( uint64_t c : str ) { + // advance cursor on special characters + if ( c == '\n' ) { + cursor.y += tallestGlyphY; + cursor.x = 0; continue; } else if ( c == '\t' ) { - // Fixed movement vs Real Tabbing - if ( false ) { - stat.cursor.x += stat.average.tab; - } else { - stat.cursor.x = ((stat.cursor.x / stat.average.tab) + 1) * stat.average.tab; - } + cursor.x = ((int)(cursor.x / averageTabWidth) + 1) * averageTabWidth; continue; - } else if ( c == COLOR_CTRL ) { + } else if ( c == ' ' ) { + cursor.x += averageTabWidth / 4.0f; continue; } - auto key = hashGlyphSettings( c, metadata ); - auto& glyph = glyphs.cache[font][key]; - ::GlyphBox g; - g.box.w = glyph.getSize().x; - g.box.h = glyph.getSize().y; - - g.box.x = stat.cursor.x + glyph.getBearing().x; - g.box.y = stat.cursor.y - glyph.getBearing().y; // - (glyph.getSize().y - glyph.getBearing().y); + // retrieve glyph + auto key = hashGlyphSettings(c, metadata); + auto& glyph = glyphsCache[key]; + auto& g = layout.emplace_back(GlyphBox{ + .box = { + .x = cursor.x + glyph.getBearing().x, + .y = cursor.y - glyph.getBearing().y, + .w = glyph.getSize().x, + .h = glyph.getSize().y, + }, + .color = token.color, + .code = c, + }); - stat.cursor.x += (glyph.getAdvance().x); - + // advance cursor + cursor.x += glyph.getAdvance().x; + + // advance bounding box + maxTextWidth = std::max(maxTextWidth, g.box.x + g.box.w); + maxTextHeight = std::max(maxTextHeight, g.box.y + g.box.h); } - - stat.origin.x = transform.position.x * ::defaults.size.x; // ( !metadataGui.world && transform.position.x != (int) transform.position.x ) ? transform.position.x * ::defaults.size.x : transform.position.x; - stat.origin.y = transform.position.y * ::defaults.size.y; // ( !metadataGui.world && transform.position.y != (int) transform.position.y ) ? transform.position.y * ::defaults.size.y : transform.position.y; - - if ( origin == "top" ) stat.origin.y = ::defaults.size.y - stat.origin.y - stat.biggest.y;// else stat.origin.y = stat.origin.y; - if ( align == "right" ) stat.origin.x = ::defaults.size.x - stat.origin.x - stat.box.w;// else stat.origin.x = stat.origin.x; - else if ( align == "center" ) - stat.origin.x -= stat.box.w * 0.5f; } - // Render Glyphs - stat.cursor.x = 0; - stat.cursor.y = stat.biggest.y; - for ( auto it = str.begin(); it != str.end(); ++it ) { - uint64_t c = *it; if ( c == '\n' ) { - if ( direction == "down" ) stat.cursor.y += stat.biggest.y; else stat.cursor.y -= stat.biggest.y; - stat.cursor.x = 0; - continue; - } else if ( c == '\t' ) { - // Fixed movement vs Real Tabbing - if ( false ) { - stat.cursor.x += stat.average.tab; - } else { - stat.cursor.x = ((stat.cursor.x / stat.average.tab) + 1) * stat.average.tab; - } - continue; - } else if ( c == ' ' ) { - stat.cursor.x += stat.average.tab / 4.0f; - continue; - } else if ( c == COLOR_CTRL ) { - ++stat.colors.index; - continue; - } - auto key = hashGlyphSettings( c, metadata ); - auto& glyph = glyphs.cache[font][key]; + // calculate offset based on anchor + float offsetX = maxTextWidth * anchor.x; + float offsetY = maxTextHeight * anchor.y; - ::GlyphBox g; - g.code = c; - - g.box.w = glyph.getSize().x; - g.box.h = glyph.getSize().y; - - g.box.x = stat.cursor.x + (glyph.getBearing().x); - g.box.y = stat.cursor.y - glyph.getBearing().y; // - (glyph.getSize().y - glyph.getBearing().y); - - stat.cursor.x += (glyph.getAdvance().x); - - if ( stat.colors.index < stat.colors.container.size() ) { - g.color = stat.colors.container.at(stat.colors.index); - } else { - g.color = metadataGui.color; - } + // adjust all glyphs for our offset + for ( auto& g : layout ) { + g.box.x -= offsetX; + g.box.y -= offsetY; + // normalize g.box.x /= ::defaults.size.x; g.box.w /= ::defaults.size.x; g.box.y /= ::defaults.size.y; g.box.h /= ::defaults.size.y; + } + return layout; + } - gs.push_back(g); + // generate the mesh and texture atlas + void generateAtlasAndMesh( const uf::stl::vector& layout, const ext::GuiGlyphBehavior::Metadata& metadata, const uf::stl::string& font, uf::Atlas& atlas, uf::Mesh& mesh ) { + auto& glyphs_cache = ::glyphs.cache[font]; + uf::stl::unordered_map glyph_atlas_map; + + #if UF_USE_FREETYPE + // generate atlas + { + atlas.clear(); + + // generate atlas + for ( const auto& g : layout ) { + auto key = hashGlyphSettings(g.code, metadata); + auto& glyph = glyphs_cache[key]; + + // already in atlas map + if ( glyph_atlas_map.find(key) != glyph_atlas_map.end() ) continue; + + const uint8_t* buffer = glyph.getBuffer(); + uf::Image::container_t pixels; + size_t len = glyph.getSize().x * glyph.getSize().y; + + if ( metadata.sdf ) { + glyph_atlas_map[key] = atlas.addImage(glyph.getBuffer(), glyph.getSize(), 8, 1, true); + } else { + pixels.reserve(len * 4); + for ( size_t i = 0; i < len; ++i ) { + pixels.emplace_back(buffer[i]); // R + pixels.emplace_back(buffer[i]); // G + pixels.emplace_back(buffer[i]); // B + pixels.emplace_back(buffer[i]); // A + } + glyph_atlas_map[key] = atlas.addImage(&pixels[0], glyph.getSize(), 8, 4, true); + } + } + + atlas.generate(); + atlas.clear(false); } #endif + // generate mesh + { + mesh.destroy(); + mesh.bind<::Mesh, uint16_t>(); - return gs; + uf::stl::vector<::Mesh> vertices; + uf::stl::vector indices; + vertices.reserve(layout.size() * 4); + indices.reserve(layout.size() * 6); + + for ( const auto& g : layout ) { + auto key = hashGlyphSettings(g.code, metadata); + auto hash = glyph_atlas_map[key]; + + #if EXT_COLOR_FLOATS + auto& color = g.color; + #else + pod::ColorRgba color = { + (uint8_t)(g.color[0] * 255), + (uint8_t)(g.color[1] * 255), + (uint8_t)(g.color[2] * 255), + (uint8_t)(g.color[3] * 255) + }; + #endif + // insert indices + uint16_t idx = (uint16_t) vertices.size(); + indices.insert( indices.end(), { idx, idx + 1, idx + 2, idx, idx + 2, idx + 3 }); + + // insert vertices + vertices.emplace_back(::Mesh{pod::Vector3f{ g.box.x, g.box.y + g.box.h, 0 }, atlas.mapUv(pod::Vector2f{ 0.0f, 0.0f }, hash), color}); + vertices.emplace_back(::Mesh{pod::Vector3f{ g.box.x, g.box.y , 0 }, atlas.mapUv(pod::Vector2f{ 0.0f, 1.0f }, hash), color}); + vertices.emplace_back(::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y , 0 }, atlas.mapUv(pod::Vector2f{ 1.0f, 1.0f }, hash), color}); + vertices.emplace_back(::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y + g.box.h, 0 }, atlas.mapUv(pod::Vector2f{ 1.0f, 0.0f }, hash), color}); + } + + mesh.insertVertices(vertices); + mesh.insertIndices(indices); + } } } @@ -336,29 +378,14 @@ void ext::GuiGlyphBehavior::initialize( uf::Object& self ) { auto& metadataGui = this->getComponent(); auto& metadataJson = this->getComponent(); -/* - if ( ext::json::isNull( ::defaults.settings["metadata"] ) ) ::defaults.settings.readFromFile(uf::io::root+"/entities/gui/text/string.json"); - // set defaults - if ( metadataJson["string"].is() ) { - auto copyMetadataJson = metadataJson; - ext::json::forEach(::defaults.settings["metadata"], [&]( const uf::stl::string& key, ext::json::Value& value ){ - if ( ext::json::isNull( copyMetadataJson[key] ) ) { - metadataJson[key] = value; - } - }); - } -*/ - -// if ( metadataJson["scaling"] == "auto" ) metadataJson["scaling"] = "none"; - this->addHook( "gui:UpdateText.%UID%", [&](ext::json::Value& payload){ auto string = payload["string"].as(metadata.string); auto font = uf::io::root+"/fonts/" + payload["font"].as(metadata.font); bool forced = payload["force"].as(false); - metadata.sdf = false; - - auto glyphs = ::generateGlyphs( self, string ); + // override + // metadata.sdf = false; + metadataGui.scaling = "none"; auto& scene = uf::scene::getCurrentScene(); auto& mesh = this->getComponent(); @@ -368,110 +395,12 @@ void ext::GuiGlyphBehavior::initialize( uf::Object& self ) { uf::stl::unordered_map glyph_atlas_map; - metadataGui.scaleMode = "none"; - - // generate texture - #if UF_USE_FREETYPE - { - atlas.clear(); - - for ( auto& g : glyphs ) { - auto key = ::hashGlyphSettings( g.code, metadata ); - auto& glyph = glyphs_cache[font][key]; - - const uint8_t* buffer = glyph.getBuffer(); - uf::Image::container_t pixels; - size_t len = glyph.getSize().x * glyph.getSize().y; - - if ( metadata.sdf ) { - glyph_atlas_map[key] = atlas.addImage( glyph.getBuffer(), glyph.getSize(), 8, 1, true ); - } else { - pixels.reserve( len * 4 ); - for ( size_t i = 0; i < len; ++i ) { - pixels.emplace_back( buffer[i] ); - pixels.emplace_back( buffer[i] ); - pixels.emplace_back( buffer[i] ); - pixels.emplace_back( buffer[i] ); - // pixels.emplace_back( buffer[i] > 127 ? 255 : 0 ); - } - glyph_atlas_map[key] = atlas.addImage( &pixels[0], glyph.getSize(), 8, 4, true ); - #if 0 - pixels.reserve( len * 2 ); - for ( size_t i = 0; i < len; ++i ) { - pixels.emplace_back( buffer[i] ); - pixels.emplace_back( buffer[i] ); - // pixels.emplace_back( buffer[i] > 127 ? 255 : 0 ); - } - glyph_atlas_map[key] = atlas.addImage( &pixels[0], glyph.getSize(), 8, 2, true ); - #endif - } - } - - atlas.generate(); - atlas.clear(false); - } - #endif - - // generate mesh - { - mesh.destroy(); - mesh.bind<::Mesh, uint16_t>(); - - uf::stl::vector<::Mesh> vertices; vertices.reserve( glyphs.size() * 6 ); - uf::stl::vector indices; indices.reserve( glyphs.size() * 6 ); - - - // pod::Vector2f min = { 0, 0 }; - // pod::Vector2f max = { 0, 0 }; - - for ( auto& g : glyphs ) { - auto key = ::hashGlyphSettings( g.code, metadata ); - auto& glyph = glyphs_cache[font][key]; - auto hash = glyph_atlas_map[key]; - - #if EXT_COLOR_FLOATS - auto& color = g.color; - #else - pod::ColorRgba color = { g.color[0] * 255, g.color[1] * 255, g.color[2] * 255, g.color[3] * 255 }; - #endif - // add vertices - vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x, g.box.y + g.box.h, 0 }, atlas.mapUv( pod::Vector2f{ 0.0f, 0.0f }, hash ), color}); indices.emplace_back( indices.size() ); - vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x, g.box.y , 0 }, atlas.mapUv( pod::Vector2f{ 0.0f, 1.0f }, hash ), color}); indices.emplace_back( indices.size() ); - vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y , 0 }, atlas.mapUv( pod::Vector2f{ 1.0f, 1.0f }, hash ), color}); indices.emplace_back( indices.size() ); - - vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x, g.box.y + g.box.h, 0 }, atlas.mapUv( pod::Vector2f{ 0.0f, 0.0f }, hash ), color}); indices.emplace_back( indices.size() ); - vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y , 0 }, atlas.mapUv( pod::Vector2f{ 1.0f, 1.0f }, hash ), color}); indices.emplace_back( indices.size() ); - vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y + g.box.h, 0 }, atlas.mapUv( pod::Vector2f{ 1.0f, 0.0f }, hash ), color}); indices.emplace_back( indices.size() ); - } - - /* - pod::Vector2f min = { std::numeric_limits::max(), std::numeric_limits::max() }; - pod::Vector2f max = { -std::numeric_limits::max(), -std::numeric_limits::max() }; - - for ( auto& vertex : vertices ) { - min.x = std::min( min.x, vertex.position.x * ::defaults.size.x * transform.scale.x ); - min.y = std::min( min.y, vertex.position.y * ::defaults.size.y * transform.scale.y ); - max.x = std::max( max.x, vertex.position.x * ::defaults.size.x * transform.scale.x ); - max.y = std::max( max.y, vertex.position.y * ::defaults.size.y * transform.scale.y ); - - #if UF_USE_OPENGL - vertex.position.y = -vertex.position.y; - #endif - } - - metadataGui.size = { - max.x - min.x, - max.y - min.y, - }; - */ - - mesh.insertVertices( vertices ); - mesh.insertIndices( indices ); - - // mesh.resizeVertices( metadata.reserve * 6 ); - // mesh.resizeIndices( metadata.reserve * 6 ); - } + + auto tokens = ::parseTextTokens(string, metadataGui.color); + auto layout = ::calculateGlyphLayout(self, tokens, metadata, metadataGui, font); + ::generateAtlasAndMesh( layout, metadata, font, atlas, mesh ); + // set proper shaders if ( metadata.sdf ) { metadataJson["shaders"]["vertex"] = uf::io::root+"/shaders/gui/text/vert.spv"; metadataJson["shaders"]["fragment"] = uf::io::root+"/shaders/gui/text/frag.spv"; @@ -518,22 +447,19 @@ void ext::GuiGlyphBehavior::tick( uf::Object& self ) { /*alignas(4)*/ int32_t spread; /*alignas(4)*/ float weight; + /*alignas(4)*/ float fillWeight; /*alignas(4)*/ float scale; /*alignas(4)*/ float padding1; /*alignas(4)*/ float padding2; - /*alignas(4)*/ float padding3; } ubo = { .stroke = metadata.shader.stroke, .range = metadata.shader.range, .spread = metadata.spread, .weight = metadata.shader.weight, + .fillWeight = metadata.shader.fillWeight, .scale = metadata.shader.scale, - }; - - // if ( metadataGlyph.sdf ) uniforms.gui.mode &= 1 << 1; - // if ( metadataGlyph.shadowbox ) uniforms.gui.mode &= 1 << 2; - + }; shader.updateBuffer( (const void*) &ubo, sizeof(ubo), uniformBuffer ); } @@ -554,6 +480,7 @@ void ext::GuiGlyphBehavior::Metadata::serialize( uf::Object& self, uf::Serialize serializer["scale"] = /*this->*/shader.scale; serializer["weight"] = /*this->*/shader.weight; + serializer["fillWeight"] = /*this->*/shader.fillWeight; serializer["stroke"] = uf::vector::encode( /*this->*/shader.stroke); serializer["range"] = uf::vector::encode( /*this->*/shader.range); } @@ -568,6 +495,7 @@ void ext::GuiGlyphBehavior::Metadata::deserialize( uf::Object& self, uf::Seriali /*this->*/shader.scale = serializer["scale"].as(/*this->*/shader.scale); /*this->*/shader.weight = serializer["weight"].as(/*this->*/shader.weight); + /*this->*/shader.fillWeight = serializer["fillWeight"].as(/*this->*/shader.fillWeight); /*this->*/shader.stroke = uf::vector::decode(serializer["stroke"], /*this->*/shader.stroke); /*this->*/shader.range = uf::vector::decode(serializer["range"], /*this->*/shader.range); diff --git a/engine/src/engine/ext/gui/glyph/behavior.h b/engine/src/engine/ext/gui/glyph/behavior.h index 01f580b9..6c4b1b63 100644 --- a/engine/src/engine/ext/gui/glyph/behavior.h +++ b/engine/src/engine/ext/gui/glyph/behavior.h @@ -16,15 +16,16 @@ namespace ext { bool sdf = true; float size = 96; - float spread = 2; + float spread = 8; - pod::Vector2ui padding = { 2, 2 }; + pod::Vector2ui padding = { 8, 8 }; size_t reserve = 128; struct { float scale = 1.5; float weight = 0.45; + float fillWeight = 0.5; pod::Vector4f stroke = { 0, 0, 0, 1 }; pod::Vector2i range = { -1, -1 }; } shader; diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 5b60373b..5da94bfa 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -535,6 +535,7 @@ namespace { } void bindInstanceAddresses( uf::renderer::Graphic& graphic, uf::Mesh& mesh, uf::stl::vector& addresses ) { + #if UF_USE_VULKAN if ( !uf::renderer::settings::invariant::deviceAddressing || !mesh.indirect.count ) return; addresses.resize( mesh.indirect.count ); @@ -574,6 +575,7 @@ namespace { instanceAddresses.drawID = drawID; } } + #endif } } @@ -1576,6 +1578,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) { if ( !soft ) { storage.buffers.camera.destroy(true); storage.buffers.drawCommands.destroy(true); + storage.buffers.lodMetadata.destroy(true); storage.buffers.instance.destroy(true); storage.buffers.instanceAddresses.destroy(true); storage.buffers.joint.destroy(true); @@ -1583,6 +1586,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) { storage.buffers.material.destroy(true); storage.buffers.texture.destroy(true); storage.buffers.light.destroy(true); + storage.buffers.depthPyramid.destroy(true); } uf::renderer::states::rebuild = true; @@ -1777,6 +1781,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) { drawCommands[drawID].vertexID = 0; } else { drawCommands[drawID] = primitives[drawID].drawCommand; + // to-do: LOD pick here for OpenGL } } diff --git a/engine/src/ext/gltf/processPrimitives.inl b/engine/src/ext/gltf/processPrimitives.inl index a0831838..26bf7bfb 100644 --- a/engine/src/ext/gltf/processPrimitives.inl +++ b/engine/src/ext/gltf/processPrimitives.inl @@ -62,22 +62,8 @@ for ( auto& p : m.primitives ) { if ( attribute.name == "POSITION" ) { meshlet.vertices.resize(accessor.count); - if ( !graph.metadata["renderer"]["invert"].as(true) ){ - meshlet.primitive.instance.bounds.min = pod::Vector3f{ accessor.minValues[0], accessor.minValues[1], accessor.minValues[2] }; - meshlet.primitive.instance.bounds.max = pod::Vector3f{ accessor.maxValues[0], accessor.maxValues[1], accessor.maxValues[2] }; - } else { - meshlet.primitive.instance.bounds.min = pod::Vector3f{ std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max() }; - meshlet.primitive.instance.bounds.max = pod::Vector3f{ -std::numeric_limits::max(), -std::numeric_limits::max(), -std::numeric_limits::max() }; - } - /* meshlet.primitive.instance.bounds.min = pod::Vector3f{ accessor.minValues[0], accessor.minValues[1], accessor.minValues[2] }; meshlet.primitive.instance.bounds.max = pod::Vector3f{ accessor.maxValues[0], accessor.maxValues[1], accessor.maxValues[2] }; - - if ( graph.metadata["renderer"]["invert"].as(true) ){ - meshlet.primitive.instance.bounds.min.x = -meshlet.primitive.instance.bounds.min.x; - meshlet.primitive.instance.bounds.max.x = -meshlet.primitive.instance.bounds.max.x; - } - */ } switch ( accessor.componentType ) { diff --git a/engine/src/ext/meshopt/meshopt.cpp b/engine/src/ext/meshopt/meshopt.cpp index ea537ad7..f0acbf01 100644 --- a/engine/src/ext/meshopt/meshopt.cpp +++ b/engine/src/ext/meshopt/meshopt.cpp @@ -1,6 +1,7 @@ #include #if UF_USE_MESHOPT #include +#include bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verbose ) { if ( mesh.isInterleaved() ) { @@ -53,7 +54,7 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verb if ( 0.0f < simplify && simplify < 1.0f ) { uf::stl::vector indicesSimplified(indicesCount); - float targetError = 0.1; // 1e-2f / simplify; + float targetError = FLT_MAX; // 1e-2f / simplify; float realError = 0.0f; size_t realIndices = meshopt_simplify( @@ -64,8 +65,8 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verb mesh.vertex.count, positionsView.stride(), indicesCount * simplify, - targetError - //,0, &realError + targetError, + 0, &realError ); if ( verbose ) { @@ -156,24 +157,26 @@ uf::stl::vector ext::meshopt::generateLODs( uf::Mesh& mesh, co meshopt_optimizeVertexCache(&baseIndices[0], &baseIndices[0], baseIndicesCount, mesh.vertex.count); + size_t previousIndicesCount = baseIndicesCount; for ( size_t lodIdx = 0; lodIdx < numLODs; ++lodIdx ) { float simplify = lodFactors[lodIdx]; uf::stl::vector lodIndices = baseIndices; size_t currentIndicesCount = baseIndicesCount; if ( simplify < 1.0f ) { - float targetError = 0.1; // 1e-2f / simplify; + float targetError = FLT_MAX; // 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 + baseIndicesCount * simplify, targetError, + 0, &realError ); - if ( baseIndicesCount == currentIndicesCount ) { + if ( previousIndicesCount == currentIndicesCount ) { continue; } + previousIndicesCount = currentIndicesCount; if ( verbose ) { UF_MSG_DEBUG("[View {} Simplified LOD {}] indices: {} -> {} | error: {} -> {}", viewIdx, lodIdx, baseIndicesCount, currentIndicesCount, targetError, realError);