Browse Source

Merge pull request #70446 from akien-mga/meshoptimizer-4a287848f

meshoptimizer: Sync with upstream commit 4a287848f
Rémi Verschelde 2 years ago
parent
commit
64f0dad2dc

+ 1 - 1
COPYRIGHT.txt

@@ -288,7 +288,7 @@ License: Apache-2.0
 
 
 Files: ./thirdparty/meshoptimizer/
 Files: ./thirdparty/meshoptimizer/
 Comment: meshoptimizer
 Comment: meshoptimizer
-Copyright: 2016-2021, Arseny Kapoulkine
+Copyright: 2016-2022, Arseny Kapoulkine
 License: Expat
 License: Expat
 
 
 Files: ./thirdparty/minimp3/
 Files: ./thirdparty/minimp3/

+ 3 - 2
scene/3d/occluder_instance_3d.cpp

@@ -542,13 +542,14 @@ void OccluderInstance3D::_bake_surface(const Transform3D &p_transform, Array p_s
 		float error = -1.0f;
 		float error = -1.0f;
 		int target_index_count = MIN(indices.size(), 36);
 		int target_index_count = MIN(indices.size(), 36);
 
 
+		const int simplify_options = SurfaceTool::SIMPLIFY_LOCK_BORDER;
+
 		uint32_t index_count = SurfaceTool::simplify_func(
 		uint32_t index_count = SurfaceTool::simplify_func(
 				(unsigned int *)indices.ptrw(),
 				(unsigned int *)indices.ptrw(),
 				(unsigned int *)indices.ptr(),
 				(unsigned int *)indices.ptr(),
 				indices.size(),
 				indices.size(),
 				vertices_f32.ptr(), vertices.size(), sizeof(float) * 3,
 				vertices_f32.ptr(), vertices.size(), sizeof(float) * 3,
-				target_index_count, target_error, &error);
-
+				target_index_count, target_error, simplify_options, &error);
 		indices.resize(index_count);
 		indices.resize(index_count);
 	}
 	}
 
 

+ 2 - 0
scene/resources/importer_mesh.cpp

@@ -452,6 +452,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
 			new_indices.resize(index_count);
 			new_indices.resize(index_count);
 
 
 			Vector<float> merged_normals_f32 = vector3_to_float32_array(merged_normals.ptr(), merged_normals.size());
 			Vector<float> merged_normals_f32 = vector3_to_float32_array(merged_normals.ptr(), merged_normals.size());
+			const int simplify_options = SurfaceTool::SIMPLIFY_LOCK_BORDER;
 
 
 			size_t new_index_count = SurfaceTool::simplify_with_attrib_func(
 			size_t new_index_count = SurfaceTool::simplify_with_attrib_func(
 					(unsigned int *)new_indices.ptrw(),
 					(unsigned int *)new_indices.ptrw(),
@@ -460,6 +461,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
 					sizeof(float) * 3, // Vertex stride
 					sizeof(float) * 3, // Vertex stride
 					index_target,
 					index_target,
 					max_mesh_error,
 					max_mesh_error,
+					simplify_options,
 					&mesh_error,
 					&mesh_error,
 					merged_normals_f32.ptr(),
 					merged_normals_f32.ptr(),
 					normal_weights.ptr(), 3);
 					normal_weights.ptr(), 3);

+ 2 - 1
scene/resources/surface_tool.cpp

@@ -1307,7 +1307,8 @@ Vector<int> SurfaceTool::generate_lod(float p_threshold, int p_target_index_coun
 	}
 	}
 
 
 	float error;
 	float error;
-	uint32_t index_count = simplify_func((unsigned int *)lod.ptrw(), (unsigned int *)index_array.ptr(), index_array.size(), vertices.ptr(), vertex_array.size(), sizeof(float) * 3, p_target_index_count, p_threshold, &error);
+	const int simplify_options = SIMPLIFY_LOCK_BORDER;
+	uint32_t index_count = simplify_func((unsigned int *)lod.ptrw(), (unsigned int *)index_array.ptr(), index_array.size(), vertices.ptr(), vertex_array.size(), sizeof(float) * 3, p_target_index_count, p_threshold, simplify_options, &error);
 	ERR_FAIL_COND_V(index_count == 0, lod);
 	ERR_FAIL_COND_V(index_count == 0, lod);
 	lod.resize(index_count);
 	lod.resize(index_count);
 
 

+ 7 - 2
scene/resources/surface_tool.h

@@ -74,11 +74,16 @@ public:
 		SKIN_8_WEIGHTS
 		SKIN_8_WEIGHTS
 	};
 	};
 
 
+	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. */
+		SIMPLIFY_LOCK_BORDER = 1 << 0, // From meshopt_SimplifyLockBorder
+	};
+
 	typedef void (*OptimizeVertexCacheFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
 	typedef void (*OptimizeVertexCacheFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
 	static OptimizeVertexCacheFunc optimize_vertex_cache_func;
 	static OptimizeVertexCacheFunc optimize_vertex_cache_func;
-	typedef size_t (*SimplifyFunc)(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, float *r_error);
+	typedef size_t (*SimplifyFunc)(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 *r_error);
 	static SimplifyFunc simplify_func;
 	static SimplifyFunc simplify_func;
-	typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, float *result_error, const float *attributes, const float *attribute_weights, size_t attribute_count);
+	typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, unsigned int options, float *result_error, const float *attributes, const float *attribute_weights, size_t attribute_count);
 	static SimplifyWithAttribFunc simplify_with_attrib_func;
 	static SimplifyWithAttribFunc simplify_with_attrib_func;
 	typedef float (*SimplifyScaleFunc)(const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
 	typedef float (*SimplifyScaleFunc)(const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
 	static SimplifyScaleFunc simplify_scale_func;
 	static SimplifyScaleFunc simplify_scale_func;

+ 1 - 1
thirdparty/README.md

@@ -376,7 +376,7 @@ File extracted from upstream release tarball:
 ## meshoptimizer
 ## meshoptimizer
 
 
 - Upstream: https://github.com/zeux/meshoptimizer
 - Upstream: https://github.com/zeux/meshoptimizer
-- Version: git (ea4558d1c0f217f1d67ed7fe0b07896ece88ae18, 2022)
+- Version: git (4a287848fd664ae1c3fc8e5e008560534ceeb526, 2022)
 - License: MIT
 - License: MIT
 
 
 Files extracted from upstream repository:
 Files extracted from upstream repository:

+ 1 - 1
thirdparty/meshoptimizer/LICENSE.md

@@ -1,6 +1,6 @@
 MIT License
 MIT License
 
 
-Copyright (c) 2016-2021 Arseny Kapoulkine
+Copyright (c) 2016-2022 Arseny Kapoulkine
 
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 of this software and associated documentation files (the "Software"), to deal

+ 90 - 62
thirdparty/meshoptimizer/clusterizer.cpp

@@ -283,6 +283,79 @@ static bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, unsigned int
 	return result;
 	return result;
 }
 }
 
 
+static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Cone* meshlet_cone, unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, const unsigned char* used, float meshlet_expected_radius, float cone_weight, unsigned int* out_extra)
+{
+	unsigned int best_triangle = ~0u;
+	unsigned int best_extra = 5;
+	float best_score = FLT_MAX;
+
+	for (size_t i = 0; i < meshlet.vertex_count; ++i)
+	{
+		unsigned int index = meshlet_vertices[meshlet.vertex_offset + i];
+
+		unsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];
+		size_t neighbors_size = adjacency.counts[index];
+
+		for (size_t j = 0; j < neighbors_size; ++j)
+		{
+			unsigned int triangle = neighbors[j];
+			unsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];
+
+			unsigned int extra = (used[a] == 0xff) + (used[b] == 0xff) + (used[c] == 0xff);
+
+			// triangles that don't add new vertices to meshlets are max. priority
+			if (extra != 0)
+			{
+				// artificially increase the priority of dangling triangles as they're expensive to add to new meshlets
+				if (live_triangles[a] == 1 || live_triangles[b] == 1 || live_triangles[c] == 1)
+					extra = 0;
+
+				extra++;
+			}
+
+			// since topology-based priority is always more important than the score, we can skip scoring in some cases
+			if (extra > best_extra)
+				continue;
+
+			float score = 0;
+
+			// caller selects one of two scoring functions: geometrical (based on meshlet cone) or topological (based on remaining triangles)
+			if (meshlet_cone)
+			{
+				const Cone& tri_cone = triangles[triangle];
+
+				float distance2 =
+				    (tri_cone.px - meshlet_cone->px) * (tri_cone.px - meshlet_cone->px) +
+				    (tri_cone.py - meshlet_cone->py) * (tri_cone.py - meshlet_cone->py) +
+				    (tri_cone.pz - meshlet_cone->pz) * (tri_cone.pz - meshlet_cone->pz);
+
+				float spread = tri_cone.nx * meshlet_cone->nx + tri_cone.ny * meshlet_cone->ny + tri_cone.nz * meshlet_cone->nz;
+
+				score = getMeshletScore(distance2, spread, cone_weight, meshlet_expected_radius);
+			}
+			else
+			{
+				// each live_triangles entry is >= 1 since it includes the current triangle we're processing
+				score = float(live_triangles[a] + live_triangles[b] + live_triangles[c] - 3);
+			}
+
+			// note that topology-based priority is always more important than the score
+			// this helps maintain reasonable effectiveness of meshlet data and reduces scoring cost
+			if (extra < best_extra || score < best_score)
+			{
+				best_triangle = triangle;
+				best_extra = extra;
+				best_score = score;
+			}
+		}
+	}
+
+	if (out_extra)
+		*out_extra = best_extra;
+
+	return best_triangle;
+}
+
 struct KDNode
 struct KDNode
 {
 {
 	union
 	union
@@ -464,13 +537,15 @@ size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_ve
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices);
 	assert(max_vertices >= 3 && max_vertices <= kMeshletMaxVertices);
 	assert(max_triangles >= 1 && max_triangles <= kMeshletMaxTriangles);
 	assert(max_triangles >= 1 && max_triangles <= kMeshletMaxTriangles);
 	assert(max_triangles % 4 == 0); // ensures the caller will compute output space properly as index data is 4b aligned
 	assert(max_triangles % 4 == 0); // ensures the caller will compute output space properly as index data is 4b aligned
 
 
+	assert(cone_weight >= 0 && cone_weight <= 1);
+
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;
 
 
 	TriangleAdjacency2 adjacency = {};
 	TriangleAdjacency2 adjacency = {};
@@ -511,65 +586,18 @@ size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_ve
 
 
 	for (;;)
 	for (;;)
 	{
 	{
-		unsigned int best_triangle = ~0u;
-		unsigned int best_extra = 5;
-		float best_score = FLT_MAX;
-
 		Cone meshlet_cone = getMeshletCone(meshlet_cone_acc, meshlet.triangle_count);
 		Cone meshlet_cone = getMeshletCone(meshlet_cone_acc, meshlet.triangle_count);
 
 
-		for (size_t i = 0; i < meshlet.vertex_count; ++i)
-		{
-			unsigned int index = meshlet_vertices[meshlet.vertex_offset + i];
-
-			unsigned int* neighbours = &adjacency.data[0] + adjacency.offsets[index];
-			size_t neighbours_size = adjacency.counts[index];
-
-			for (size_t j = 0; j < neighbours_size; ++j)
-			{
-				unsigned int triangle = neighbours[j];
-				assert(!emitted_flags[triangle]);
-
-				unsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];
-				assert(a < vertex_count && b < vertex_count && c < vertex_count);
-
-				unsigned int extra = (used[a] == 0xff) + (used[b] == 0xff) + (used[c] == 0xff);
-
-				// triangles that don't add new vertices to meshlets are max. priority
-				if (extra != 0)
-				{
-					// artificially increase the priority of dangling triangles as they're expensive to add to new meshlets
-					if (live_triangles[a] == 1 || live_triangles[b] == 1 || live_triangles[c] == 1)
-						extra = 0;
-
-					extra++;
-				}
-
-				// since topology-based priority is always more important than the score, we can skip scoring in some cases
-				if (extra > best_extra)
-					continue;
-
-				const Cone& tri_cone = triangles[triangle];
-
-				float distance2 =
-				    (tri_cone.px - meshlet_cone.px) * (tri_cone.px - meshlet_cone.px) +
-				    (tri_cone.py - meshlet_cone.py) * (tri_cone.py - meshlet_cone.py) +
-				    (tri_cone.pz - meshlet_cone.pz) * (tri_cone.pz - meshlet_cone.pz);
-
-				float spread = tri_cone.nx * meshlet_cone.nx + tri_cone.ny * meshlet_cone.ny + tri_cone.nz * meshlet_cone.nz;
+		unsigned int best_extra = 0;
+		unsigned int best_triangle = getNeighborTriangle(meshlet, &meshlet_cone, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, cone_weight, &best_extra);
 
 
-				float score = getMeshletScore(distance2, spread, cone_weight, meshlet_expected_radius);
-
-				// note that topology-based priority is always more important than the score
-				// this helps maintain reasonable effectiveness of meshlet data and reduces scoring cost
-				if (extra < best_extra || score < best_score)
-				{
-					best_triangle = triangle;
-					best_extra = extra;
-					best_score = score;
-				}
-			}
+		// if the best triangle doesn't fit into current meshlet, the spatial scoring we've used is not very meaningful, so we re-select using topological scoring
+		if (best_triangle != ~0u && (meshlet.vertex_count + best_extra > max_vertices || meshlet.triangle_count >= max_triangles))
+		{
+			best_triangle = getNeighborTriangle(meshlet, NULL, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, 0.f, NULL);
 		}
 		}
 
 
+		// when we run out of neighboring triangles we need to switch to spatial search; we currently just pick the closest triangle irrespective of connectivity
 		if (best_triangle == ~0u)
 		if (best_triangle == ~0u)
 		{
 		{
 			float position[3] = {meshlet_cone.px, meshlet_cone.py, meshlet_cone.pz};
 			float position[3] = {meshlet_cone.px, meshlet_cone.py, meshlet_cone.pz};
@@ -604,16 +632,16 @@ size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_ve
 		{
 		{
 			unsigned int index = indices[best_triangle * 3 + k];
 			unsigned int index = indices[best_triangle * 3 + k];
 
 
-			unsigned int* neighbours = &adjacency.data[0] + adjacency.offsets[index];
-			size_t neighbours_size = adjacency.counts[index];
+			unsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];
+			size_t neighbors_size = adjacency.counts[index];
 
 
-			for (size_t i = 0; i < neighbours_size; ++i)
+			for (size_t i = 0; i < neighbors_size; ++i)
 			{
 			{
-				unsigned int tri = neighbours[i];
+				unsigned int tri = neighbors[i];
 
 
 				if (tri == best_triangle)
 				if (tri == best_triangle)
 				{
 				{
-					neighbours[i] = neighbours[neighbours_size - 1];
+					neighbors[i] = neighbors[neighbors_size - 1];
 					adjacency.counts[index]--;
 					adjacency.counts[index]--;
 					break;
 					break;
 				}
 				}
@@ -687,7 +715,7 @@ meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
 	assert(index_count / 3 <= kMeshletMaxTriangles);
 	assert(index_count / 3 <= kMeshletMaxTriangles);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	(void)vertex_count;
 	(void)vertex_count;
@@ -839,7 +867,7 @@ meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(triangle_count <= kMeshletMaxTriangles);
 	assert(triangle_count <= kMeshletMaxTriangles);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	unsigned int indices[kMeshletMaxTriangles * 3];
 	unsigned int indices[kMeshletMaxTriangles * 3];

+ 3 - 3
thirdparty/meshoptimizer/indexgenerator.cpp

@@ -187,7 +187,7 @@ size_t meshopt_generateVertexRemap(unsigned int* destination, const unsigned int
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(indices || index_count == vertex_count);
 	assert(indices || index_count == vertex_count);
-	assert(index_count % 3 == 0);
+	assert(!indices || index_count % 3 == 0);
 	assert(vertex_size > 0 && vertex_size <= 256);
 	assert(vertex_size > 0 && vertex_size <= 256);
 
 
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;
@@ -412,7 +412,7 @@ void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsig
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;
@@ -483,7 +483,7 @@ void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const un
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;

+ 40 - 30
thirdparty/meshoptimizer/meshoptimizer.h

@@ -1,7 +1,7 @@
 /**
 /**
- * meshoptimizer - version 0.17
+ * meshoptimizer - version 0.18
  *
  *
- * Copyright (C) 2016-2021, by Arseny Kapoulkine ([email protected])
+ * Copyright (C) 2016-2022, by Arseny Kapoulkine ([email protected])
  * Report bugs and download new versions at https://github.com/zeux/meshoptimizer
  * 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.
  * This library is distributed under the MIT License. See notice at the end of this file.
@@ -12,7 +12,7 @@
 #include <stddef.h>
 #include <stddef.h>
 
 
 /* Version macro; major * 1000 + minor * 10 + patch */
 /* Version macro; major * 1000 + minor * 10 + patch */
-#define MESHOPTIMIZER_VERSION 170 /* 0.17 */
+#define MESHOPTIMIZER_VERSION 180 /* 0.18 */
 
 
 /* If no API is defined, assume default */
 /* If no API is defined, assume default */
 #ifndef MESHOPTIMIZER_API
 #ifndef MESHOPTIMIZER_API
@@ -37,8 +37,8 @@ extern "C" {
 #endif
 #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
 struct meshopt_Stream
 {
 {
@@ -115,7 +115,7 @@ MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(unsigned int* dest
  * This can be used to implement algorithms like silhouette detection/expansion and other forms of GS-driven rendering.
  * 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)
  * 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 - similar to glVertexPointer
+ * 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);
 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);
 
 
@@ -131,7 +131,7 @@ MESHOPTIMIZER_API void meshopt_generateAdjacencyIndexBuffer(unsigned int* destin
  * See "Tessellation on Any Budget" (John McDonald, GDC 2011) for implementation details.
  * 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)
  * 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 - similar to glVertexPointer
+ * 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);
 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);
 
 
@@ -171,7 +171,7 @@ MESHOPTIMIZER_API void meshopt_optimizeVertexCacheFifo(unsigned int* destination
  *
  *
  * destination must contain enough space for the resulting index buffer (index_count elements)
  * 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!)
  * 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
  * 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(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);
 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);
@@ -313,7 +313,21 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterQuat(void* destination, size
 MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data);
 MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data);
 
 
 /**
 /**
- * Experimental: Mesh simplifier
+ * 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,
+};
+
+/**
+ * Experimental: Mesh simplifier with attribute metric; attributes follow xyz position data atm (vertex data must contain 3 + attribute_count floats per vertex)
+ */
+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, unsigned int options, float* result_error, const float* attributes, const float* attribute_weights, size_t attribute_count);
+
+/**
+ * Mesh simplifier
  * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible
  * 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.
  * 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 required, it's recommended to reindex the mesh using meshopt_generateShadowIndexBuffer prior to simplification.
@@ -322,16 +336,12 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_
  * 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 required, 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)!
  * 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 - similar to glVertexPointer
- * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation
+ * 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
  * 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(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, float* result_error);
-
-/**
- * Experimental: Mesh simplifier with attribute metric; attributes follow xyz position data atm (vertex data must contain 3 + attribute_count floats per vertex)
- */
-MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, float* result_error, const float* attributes, const float* attribute_weights, size_t attribute_count);
+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)
  * Experimental: Mesh simplifier (sloppy)
@@ -342,8 +352,8 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* d
  * 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 required, 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)!
  * 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 - similar to glVertexPointer
- * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation
+ * 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]
  * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification
  * 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(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, float* result_error);
 MESHOPTIMIZER_EXPERIMENTAL 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, size_t target_index_count, float target_error, float* result_error);
@@ -356,17 +366,17 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(unsigned int* destinati
  * 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 required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
  *
  *
  * destination must contain enough space for the target index buffer (target_vertex_count elements)
  * 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 - similar to glVertexPointer
+ * vertex_positions should have float3 position in the first 12 bytes of each vertex
  */
  */
 MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count);
 MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count);
 
 
 /**
 /**
- * Experimental: Returns the error scaling factor used by the simplifier to convert between absolute and relative extents
+ * 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
  * 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.
  * Relative error returned by meshopt_simplify via result_error must be *multiplied* by the scaling factor to get absolute error.
  */
  */
-MESHOPTIMIZER_EXPERIMENTAL float meshopt_simplifyScale(const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
+MESHOPTIMIZER_API float meshopt_simplifyScale(const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
 
 
 /**
 /**
  * Mesh stripifier
  * Mesh stripifier
@@ -418,7 +428,7 @@ struct meshopt_OverdrawStatistics
  * Returns overdraw statistics using a software rasterizer
  * Returns overdraw statistics using a software rasterizer
  * Results may not match actual GPU performance
  * Results may not match actual GPU performance
  *
  *
- * 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_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);
 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);
 
 
@@ -456,7 +466,7 @@ struct meshopt_Meshlet
  * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound
  * 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 size is equal to max_meshlets * max_vertices
  * meshlet_vertices must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_vertices
  * meshlet_triangles must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_triangles * 3
  * meshlet_triangles must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_triangles * 3
- * 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
  * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 255 - not 256!, max_triangles <= 512)
  * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 255 - not 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
  * 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
  */
  */
@@ -498,7 +508,7 @@ struct meshopt_Bounds
  * The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere
  * 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.
  *
  *
- * 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
  * index_count/3 should be less than or equal to 512 (the function assumes clusters of limited size)
  * index_count/3 should be less than or equal to 512 (the function assumes clusters of limited size)
  */
  */
 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_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
@@ -518,7 +528,7 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortRemap(unsigned int* destinati
  * Reorders triangles for spatial locality, and generates a new index buffer. The resulting index buffer can be used with other functions like optimizeVertexCache.
  * 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)
  * 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(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
 MESHOPTIMIZER_EXPERIMENTAL 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);
 
 
@@ -610,7 +620,7 @@ inline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_s
 template <typename T>
 template <typename T>
 inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size);
 inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size);
 template <typename T>
 template <typename T>
-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, float* result_error = 0);
+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 = 0);
 template <typename T>
 template <typename T>
 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 = 0);
 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 = 0);
 template <typename T>
 template <typename T>
@@ -945,12 +955,12 @@ inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const
 }
 }
 
 
 template <typename T>
 template <typename T>
-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, float* result_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, float* result_error)
 {
 {
 	meshopt_IndexAdapter<T> in(0, indices, index_count);
 	meshopt_IndexAdapter<T> in(0, indices, index_count);
 	meshopt_IndexAdapter<T> out(destination, 0, index_count);
 	meshopt_IndexAdapter<T> out(destination, 0, index_count);
 
 
-	return meshopt_simplify(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error, result_error);
+	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 <typename T>
 template <typename T>
@@ -1039,7 +1049,7 @@ inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_
 #endif
 #endif
 
 
 /**
 /**
- * Copyright (c) 2016-2021 Arseny Kapoulkine
+ * Copyright (c) 2016-2022 Arseny Kapoulkine
  *
  *
  * Permission is hereby granted, free of charge, to any person
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
  * obtaining a copy of this software and associated documentation

+ 1 - 1
thirdparty/meshoptimizer/overdrawanalyzer.cpp

@@ -147,7 +147,7 @@ meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const unsigned int* indices,
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;

+ 1 - 1
thirdparty/meshoptimizer/overdrawoptimizer.cpp

@@ -272,7 +272,7 @@ void meshopt_optimizeOverdraw(unsigned int* destination, const unsigned int* ind
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;

+ 15 - 15
thirdparty/meshoptimizer/patches/attribute-aware-simplify-distance-only-metric.patch

@@ -1,5 +1,5 @@
 diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp
 diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp
-index 5e92e2dc73..e40c141e76 100644
+index d8d4a67391..3847afc736 100644
 --- a/thirdparty/meshoptimizer/simplifier.cpp
 --- a/thirdparty/meshoptimizer/simplifier.cpp
 +++ b/thirdparty/meshoptimizer/simplifier.cpp
 +++ b/thirdparty/meshoptimizer/simplifier.cpp
 @@ -20,7 +20,7 @@
 @@ -20,7 +20,7 @@
@@ -11,7 +11,7 @@ index 5e92e2dc73..e40c141e76 100644
  
  
  // This work is based on:
  // This work is based on:
  // Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
  // Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
-@@ -453,6 +453,7 @@ struct Collapse
+@@ -458,6 +458,7 @@ struct Collapse
  		float error;
  		float error;
  		unsigned int errorui;
  		unsigned int errorui;
  	};
  	};
@@ -19,7 +19,7 @@ index 5e92e2dc73..e40c141e76 100644
  };
  };
  
  
  static float normalize(Vector3& v)
  static float normalize(Vector3& v)
-@@ -533,6 +534,34 @@ static float quadricError(const Quadric& Q, const Vector3& v)
+@@ -538,6 +539,34 @@ static float quadricError(const Quadric& Q, const Vector3& v)
  	return fabsf(r) * s;
  	return fabsf(r) * s;
  }
  }
  
  
@@ -54,7 +54,7 @@ index 5e92e2dc73..e40c141e76 100644
  static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w)
  static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w)
  {
  {
  	float aw = a * w;
  	float aw = a * w;
-@@ -688,7 +717,7 @@ static void quadricUpdateAttributes(Quadric& Q, const Vector3& p0, const Vector3
+@@ -693,7 +722,7 @@ static void quadricUpdateAttributes(Quadric& Q, const Vector3& p0, const Vector3
  }
  }
  #endif
  #endif
  
  
@@ -63,7 +63,7 @@ index 5e92e2dc73..e40c141e76 100644
  {
  {
  	for (size_t i = 0; i < index_count; i += 3)
  	for (size_t i = 0; i < index_count; i += 3)
  	{
  	{
-@@ -698,6 +727,9 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
+@@ -703,6 +732,9 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
  
  
  		Quadric Q;
  		Quadric Q;
  		quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f);
  		quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f);
@@ -73,7 +73,7 @@ index 5e92e2dc73..e40c141e76 100644
  
  
  #if ATTRIBUTES
  #if ATTRIBUTES
  		quadricUpdateAttributes(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], Q.w);
  		quadricUpdateAttributes(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], Q.w);
-@@ -708,7 +740,7 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
+@@ -713,7 +745,7 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
  	}
  	}
  }
  }
  
  
@@ -82,7 +82,7 @@ index 5e92e2dc73..e40c141e76 100644
  {
  {
  	for (size_t i = 0; i < index_count; i += 3)
  	for (size_t i = 0; i < index_count; i += 3)
  	{
  	{
-@@ -752,6 +784,9 @@ static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
+@@ -757,6 +789,9 @@ static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
  
  
  			quadricAdd(vertex_quadrics[remap[i0]], Q);
  			quadricAdd(vertex_quadrics[remap[i0]], Q);
  			quadricAdd(vertex_quadrics[remap[i1]], Q);
  			quadricAdd(vertex_quadrics[remap[i1]], Q);
@@ -92,7 +92,7 @@ index 5e92e2dc73..e40c141e76 100644
  		}
  		}
  	}
  	}
  }
  }
-@@ -856,7 +891,7 @@ static size_t pickEdgeCollapses(Collapse* collapses, const unsigned int* indices
+@@ -861,7 +896,7 @@ static size_t pickEdgeCollapses(Collapse* collapses, const unsigned int* indices
  	return collapse_count;
  	return collapse_count;
  }
  }
  
  
@@ -101,7 +101,7 @@ index 5e92e2dc73..e40c141e76 100644
  {
  {
  	for (size_t i = 0; i < collapse_count; ++i)
  	for (size_t i = 0; i < collapse_count; ++i)
  	{
  	{
-@@ -876,10 +911,14 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
+@@ -881,10 +916,14 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
  		float ei = quadricError(qi, vertex_positions[i1]);
  		float ei = quadricError(qi, vertex_positions[i1]);
  		float ej = quadricError(qj, vertex_positions[j1]);
  		float ej = quadricError(qj, vertex_positions[j1]);
  
  
@@ -116,7 +116,7 @@ index 5e92e2dc73..e40c141e76 100644
  	}
  	}
  }
  }
  
  
-@@ -976,7 +1015,7 @@ static void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapse
+@@ -981,7 +1020,7 @@ static void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapse
  	}
  	}
  }
  }
  
  
@@ -125,7 +125,7 @@ index 5e92e2dc73..e40c141e76 100644
  {
  {
  	size_t edge_collapses = 0;
  	size_t edge_collapses = 0;
  	size_t triangle_collapses = 0;
  	size_t triangle_collapses = 0;
-@@ -1038,6 +1077,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
+@@ -1043,6 +1082,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
  		assert(collapse_remap[r1] == r1);
  		assert(collapse_remap[r1] == r1);
  
  
  		quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);
  		quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);
@@ -133,7 +133,7 @@ index 5e92e2dc73..e40c141e76 100644
  
  
  		if (vertex_kind[i0] == Kind_Complex)
  		if (vertex_kind[i0] == Kind_Complex)
  		{
  		{
-@@ -1075,7 +1115,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
+@@ -1080,7 +1120,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
  		triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2;
  		triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2;
  		edge_collapses++;
  		edge_collapses++;
  
  
@@ -142,7 +142,7 @@ index 5e92e2dc73..e40c141e76 100644
  	}
  	}
  
  
  #if TRACE
  #if TRACE
-@@ -1463,9 +1503,11 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
+@@ -1469,9 +1509,11 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
  
  
  	Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);
  	Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);
  	memset(vertex_quadrics, 0, vertex_count * sizeof(Quadric));
  	memset(vertex_quadrics, 0, vertex_count * sizeof(Quadric));
@@ -156,7 +156,7 @@ index 5e92e2dc73..e40c141e76 100644
  
  
  	if (result != indices)
  	if (result != indices)
  		memcpy(result, indices, index_count * sizeof(unsigned int));
  		memcpy(result, indices, index_count * sizeof(unsigned int));
-@@ -1496,7 +1538,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
+@@ -1502,7 +1544,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
  		if (edge_collapse_count == 0)
  		if (edge_collapse_count == 0)
  			break;
  			break;
  
  
@@ -165,7 +165,7 @@ index 5e92e2dc73..e40c141e76 100644
  
  
  #if TRACE > 1
  #if TRACE > 1
  		dumpEdgeCollapses(edge_collapses, edge_collapse_count, vertex_kind);
  		dumpEdgeCollapses(edge_collapses, edge_collapse_count, vertex_kind);
-@@ -1515,7 +1557,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
+@@ -1521,7 +1563,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
  		printf("pass %d: ", int(pass_count++));
  		printf("pass %d: ", int(pass_count++));
  #endif
  #endif
  
  

+ 25 - 24
thirdparty/meshoptimizer/patches/attribute-aware-simplify.patch

@@ -1,21 +1,21 @@
 diff --git a/thirdparty/meshoptimizer/meshoptimizer.h b/thirdparty/meshoptimizer/meshoptimizer.h
 diff --git a/thirdparty/meshoptimizer/meshoptimizer.h b/thirdparty/meshoptimizer/meshoptimizer.h
-index be4b765d97..463fad29da 100644
+index d95725dd71..46d28d3ea3 100644
 --- a/thirdparty/meshoptimizer/meshoptimizer.h
 --- a/thirdparty/meshoptimizer/meshoptimizer.h
 +++ b/thirdparty/meshoptimizer/meshoptimizer.h
 +++ b/thirdparty/meshoptimizer/meshoptimizer.h
-@@ -328,6 +328,11 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_
-  */
- MESHOPTIMIZER_EXPERIMENTAL 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, float* result_error);
+@@ -321,6 +321,11 @@ enum
+     meshopt_SimplifyLockBorder = 1 << 0,
+ };
  
  
 +/**
 +/**
 + * Experimental: Mesh simplifier with attribute metric; attributes follow xyz position data atm (vertex data must contain 3 + attribute_count floats per vertex)
 + * Experimental: Mesh simplifier with attribute metric; attributes follow xyz position data atm (vertex data must contain 3 + attribute_count floats per vertex)
 + */
 + */
-+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, float* result_error, const float* attributes, const float* attribute_weights, size_t attribute_count);
++MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, unsigned int options, float* result_error, const float* attributes, const float* attribute_weights, size_t attribute_count);
 +
 +
  /**
  /**
-  * Experimental: Mesh simplifier (sloppy)
-  * Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance
+  * Mesh simplifier
+  * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible
 diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp
 diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp
-index a74b08a97d..5e92e2dc73 100644
+index 5f0e9bac31..797329b010 100644
 --- a/thirdparty/meshoptimizer/simplifier.cpp
 --- a/thirdparty/meshoptimizer/simplifier.cpp
 +++ b/thirdparty/meshoptimizer/simplifier.cpp
 +++ b/thirdparty/meshoptimizer/simplifier.cpp
 @@ -20,6 +20,8 @@
 @@ -20,6 +20,8 @@
@@ -27,7 +27,7 @@ index a74b08a97d..5e92e2dc73 100644
  // This work is based on:
  // This work is based on:
  // Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
  // Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
  // Michael Garland. Quadric-based polygonal surface simplification. 1999
  // Michael Garland. Quadric-based polygonal surface simplification. 1999
-@@ -371,6 +373,10 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
+@@ -376,6 +378,10 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
  struct Vector3
  struct Vector3
  {
  {
  	float x, y, z;
  	float x, y, z;
@@ -38,7 +38,7 @@ index a74b08a97d..5e92e2dc73 100644
  };
  };
  
  
  static float rescalePositions(Vector3* result, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride)
  static float rescalePositions(Vector3* result, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride)
-@@ -427,6 +433,13 @@ struct Quadric
+@@ -432,6 +438,13 @@ struct Quadric
  	float a10, a20, a21;
  	float a10, a20, a21;
  	float b0, b1, b2, c;
  	float b0, b1, b2, c;
  	float w;
  	float w;
@@ -52,7 +52,7 @@ index a74b08a97d..5e92e2dc73 100644
  };
  };
  
  
  struct Collapse
  struct Collapse
-@@ -469,6 +482,16 @@ static void quadricAdd(Quadric& Q, const Quadric& R)
+@@ -474,6 +487,16 @@ static void quadricAdd(Quadric& Q, const Quadric& R)
  	Q.b2 += R.b2;
  	Q.b2 += R.b2;
  	Q.c += R.c;
  	Q.c += R.c;
  	Q.w += R.w;
  	Q.w += R.w;
@@ -69,7 +69,7 @@ index a74b08a97d..5e92e2dc73 100644
  }
  }
  
  
  static float quadricError(const Quadric& Q, const Vector3& v)
  static float quadricError(const Quadric& Q, const Vector3& v)
-@@ -494,6 +517,17 @@ static float quadricError(const Quadric& Q, const Vector3& v)
+@@ -499,6 +522,17 @@ static float quadricError(const Quadric& Q, const Vector3& v)
  	r += ry * v.y;
  	r += ry * v.y;
  	r += rz * v.z;
  	r += rz * v.z;
  
  
@@ -87,7 +87,7 @@ index a74b08a97d..5e92e2dc73 100644
  	float s = Q.w == 0.f ? 0.f : 1.f / Q.w;
  	float s = Q.w == 0.f ? 0.f : 1.f / Q.w;
  
  
  	return fabsf(r) * s;
  	return fabsf(r) * s;
-@@ -517,6 +551,13 @@ static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, flo
+@@ -522,6 +556,13 @@ static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, flo
  	Q.b2 = c * dw;
  	Q.b2 = c * dw;
  	Q.c = d * dw;
  	Q.c = d * dw;
  	Q.w = w;
  	Q.w = w;
@@ -101,7 +101,7 @@ index a74b08a97d..5e92e2dc73 100644
  }
  }
  
  
  static void quadricFromPoint(Quadric& Q, float x, float y, float z, float w)
  static void quadricFromPoint(Quadric& Q, float x, float y, float z, float w)
-@@ -569,6 +610,84 @@ static void quadricFromTriangleEdge(Quadric& Q, const Vector3& p0, const Vector3
+@@ -574,6 +615,84 @@ static void quadricFromTriangleEdge(Quadric& Q, const Vector3& p0, const Vector3
  	quadricFromPlane(Q, normal.x, normal.y, normal.z, -distance, length * weight);
  	quadricFromPlane(Q, normal.x, normal.y, normal.z, -distance, length * weight);
  }
  }
  
  
@@ -186,7 +186,7 @@ index a74b08a97d..5e92e2dc73 100644
  static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap)
  static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap)
  {
  {
  	for (size_t i = 0; i < index_count; i += 3)
  	for (size_t i = 0; i < index_count; i += 3)
-@@ -580,6 +699,9 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
+@@ -585,6 +704,9 @@ static void fillFaceQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
  		Quadric Q;
  		Quadric Q;
  		quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f);
  		quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f);
  
  
@@ -196,29 +196,30 @@ index a74b08a97d..5e92e2dc73 100644
  		quadricAdd(vertex_quadrics[remap[i0]], Q);
  		quadricAdd(vertex_quadrics[remap[i0]], Q);
  		quadricAdd(vertex_quadrics[remap[i1]], Q);
  		quadricAdd(vertex_quadrics[remap[i1]], Q);
  		quadricAdd(vertex_quadrics[remap[i2]], Q);
  		quadricAdd(vertex_quadrics[remap[i2]], Q);
-@@ -1273,13 +1395,19 @@ MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoopBack = 0;
+@@ -1278,14 +1400,20 @@ MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoopBack = 0;
  #endif
  #endif
  
  
- size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* out_result_error)
+ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)
 +{
 +{
-+	return meshopt_simplifyWithAttributes(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, target_index_count, target_error, out_result_error, 0, 0, 0);
++	return meshopt_simplifyWithAttributes(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, target_index_count, target_error, options, out_result_error, 0, 0, 0);
 +}
 +}
 +
 +
-+size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, float* out_result_error, const float* attributes, const float* attribute_weights, size_t attribute_count)
++size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, unsigned int options, float* out_result_error, const float* attributes, const float* attribute_weights, size_t attribute_count)
  {
  {
  	using namespace meshopt;
  	using namespace meshopt;
  
  
  	assert(index_count % 3 == 0);
  	assert(index_count % 3 == 0);
--	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+-	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 -	assert(vertex_positions_stride % sizeof(float) == 0);
 -	assert(vertex_positions_stride % sizeof(float) == 0);
-+	assert(vertex_stride > 0 && vertex_stride <= 256);
++	assert(vertex_stride >= 12 && vertex_stride <= 256);
 +	assert(vertex_stride % sizeof(float) == 0);
 +	assert(vertex_stride % sizeof(float) == 0);
  	assert(target_index_count <= index_count);
  	assert(target_index_count <= index_count);
+ 	assert((options & ~(meshopt_SimplifyLockBorder)) == 0);
 +	assert(attribute_count <= ATTRIBUTES);
 +	assert(attribute_count <= ATTRIBUTES);
  
  
  	meshopt_Allocator allocator;
  	meshopt_Allocator allocator;
  
  
-@@ -1293,7 +1421,7 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
+@@ -1299,7 +1427,7 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
  	// build position remap that maps each vertex to the one with identical position
  	// build position remap that maps each vertex to the one with identical position
  	unsigned int* remap = allocator.allocate<unsigned int>(vertex_count);
  	unsigned int* remap = allocator.allocate<unsigned int>(vertex_count);
  	unsigned int* wedge = allocator.allocate<unsigned int>(vertex_count);
  	unsigned int* wedge = allocator.allocate<unsigned int>(vertex_count);
@@ -227,7 +228,7 @@ index a74b08a97d..5e92e2dc73 100644
  
  
  	// classify vertices; vertex kind determines collapse rules, see kCanCollapse
  	// classify vertices; vertex kind determines collapse rules, see kCanCollapse
  	unsigned char* vertex_kind = allocator.allocate<unsigned char>(vertex_count);
  	unsigned char* vertex_kind = allocator.allocate<unsigned char>(vertex_count);
-@@ -1317,7 +1445,21 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
+@@ -1323,7 +1451,21 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
  #endif
  #endif
  
  
  	Vector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);
  	Vector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);
@@ -250,7 +251,7 @@ index a74b08a97d..5e92e2dc73 100644
  
  
  	Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);
  	Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);
  	memset(vertex_quadrics, 0, vertex_count * sizeof(Quadric));
  	memset(vertex_quadrics, 0, vertex_count * sizeof(Quadric));
-@@ -1409,7 +1551,9 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
+@@ -1415,7 +1557,9 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
  
  
  	// result_error is quadratic; we need to remap it back to linear
  	// result_error is quadratic; we need to remap it back to linear
  	if (out_result_error)
  	if (out_result_error)

+ 15 - 9
thirdparty/meshoptimizer/simplifier.cpp

@@ -254,7 +254,7 @@ static bool hasEdge(const EdgeAdjacency& adjacency, unsigned int a, unsigned int
 	return false;
 	return false;
 }
 }
 
 
-static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned int* loopback, size_t vertex_count, const EdgeAdjacency& adjacency, const unsigned int* remap, const unsigned int* wedge)
+static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned int* loopback, size_t vertex_count, const EdgeAdjacency& adjacency, const unsigned int* remap, const unsigned int* wedge, unsigned int options)
 {
 {
 	memset(loop, -1, vertex_count * sizeof(unsigned int));
 	memset(loop, -1, vertex_count * sizeof(unsigned int));
 	memset(loopback, -1, vertex_count * sizeof(unsigned int));
 	memset(loopback, -1, vertex_count * sizeof(unsigned int));
@@ -364,6 +364,11 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
 		}
 		}
 	}
 	}
 
 
+	if (options & meshopt_SimplifyLockBorder)
+		for (size_t i = 0; i < vertex_count; ++i)
+			if (result[i] == Kind_Border)
+				result[i] = Kind_Locked;
+
 #if TRACE
 #if TRACE
 	printf("locked: many open edges %d, disconnected seam %d, many seam edges %d, many wedges %d\n",
 	printf("locked: many open edges %d, disconnected seam %d, many seam edges %d, many wedges %d\n",
 	    int(stats[0]), int(stats[1]), int(stats[2]), int(stats[3]));
 	    int(stats[0]), int(stats[1]), int(stats[2]), int(stats[3]));
@@ -1434,19 +1439,20 @@ MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoop = 0;
 MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoopBack = 0;
 MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoopBack = 0;
 #endif
 #endif
 
 
-size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* out_result_error)
+size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)
 {
 {
-	return meshopt_simplifyWithAttributes(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, target_index_count, target_error, out_result_error, 0, 0, 0);
+	return meshopt_simplifyWithAttributes(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, target_index_count, target_error, options, out_result_error, 0, 0, 0);
 }
 }
 
 
-size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, float* out_result_error, const float* attributes, const float* attribute_weights, size_t attribute_count)
+size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, unsigned int options, float* out_result_error, const float* attributes, const float* attribute_weights, size_t attribute_count)
 {
 {
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_stride > 0 && vertex_stride <= 256);
+	assert(vertex_stride >= 12 && vertex_stride <= 256);
 	assert(vertex_stride % sizeof(float) == 0);
 	assert(vertex_stride % sizeof(float) == 0);
 	assert(target_index_count <= index_count);
 	assert(target_index_count <= index_count);
+	assert((options & ~(meshopt_SimplifyLockBorder)) == 0);
 	assert(attribute_count <= ATTRIBUTES);
 	assert(attribute_count <= ATTRIBUTES);
 
 
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;
@@ -1467,7 +1473,7 @@ size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned
 	unsigned char* vertex_kind = allocator.allocate<unsigned char>(vertex_count);
 	unsigned char* vertex_kind = allocator.allocate<unsigned char>(vertex_count);
 	unsigned int* loop = allocator.allocate<unsigned int>(vertex_count);
 	unsigned int* loop = allocator.allocate<unsigned int>(vertex_count);
 	unsigned int* loopback = allocator.allocate<unsigned int>(vertex_count);
 	unsigned int* loopback = allocator.allocate<unsigned int>(vertex_count);
-	classifyVertices(vertex_kind, loop, loopback, vertex_count, adjacency, remap, wedge);
+	classifyVertices(vertex_kind, loop, loopback, vertex_count, adjacency, remap, wedge, options);
 
 
 #if TRACE
 #if TRACE
 	size_t unique_positions = 0;
 	size_t unique_positions = 0;
@@ -1605,7 +1611,7 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(target_index_count <= index_count);
 	assert(target_index_count <= index_count);
 
 
@@ -1736,7 +1742,7 @@ size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_pos
 {
 {
 	using namespace meshopt;
 	using namespace meshopt;
 
 
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(target_vertex_count <= vertex_count);
 	assert(target_vertex_count <= vertex_count);
 
 
@@ -1848,7 +1854,7 @@ float meshopt_simplifyScale(const float* vertex_positions, size_t vertex_count,
 {
 {
 	using namespace meshopt;
 	using namespace meshopt;
 
 
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	float extent = rescalePositions(NULL, vertex_positions, vertex_count, vertex_positions_stride);
 	float extent = rescalePositions(NULL, vertex_positions, vertex_count, vertex_positions_stride);

+ 2 - 2
thirdparty/meshoptimizer/spatialorder.cpp

@@ -113,7 +113,7 @@ void meshopt_spatialSortRemap(unsigned int* destination, const float* vertex_pos
 {
 {
 	using namespace meshopt;
 	using namespace meshopt;
 
 
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	meshopt_Allocator allocator;
 	meshopt_Allocator allocator;
@@ -144,7 +144,7 @@ void meshopt_spatialSortTriangles(unsigned int* destination, const unsigned int*
 	using namespace meshopt;
 	using namespace meshopt;
 
 
 	assert(index_count % 3 == 0);
 	assert(index_count % 3 == 0);
-	assert(vertex_positions_stride > 0 && vertex_positions_stride <= 256);
+	assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 	assert(vertex_positions_stride % sizeof(float) == 0);
 
 
 	(void)vertex_count;
 	(void)vertex_count;

+ 14 - 14
thirdparty/meshoptimizer/vcacheoptimizer.cpp

@@ -110,7 +110,7 @@ static unsigned int getNextVertexDeadEnd(const unsigned int* dead_end, unsigned
 	return ~0u;
 	return ~0u;
 }
 }
 
 
-static unsigned int getNextVertexNeighbour(const unsigned int* next_candidates_begin, const unsigned int* next_candidates_end, const unsigned int* live_triangles, const unsigned int* cache_timestamps, unsigned int timestamp, unsigned int cache_size)
+static unsigned int getNextVertexNeighbor(const unsigned int* next_candidates_begin, const unsigned int* next_candidates_end, const unsigned int* live_triangles, const unsigned int* cache_timestamps, unsigned int timestamp, unsigned int cache_size)
 {
 {
 	unsigned int best_candidate = ~0u;
 	unsigned int best_candidate = ~0u;
 	int best_priority = -1;
 	int best_priority = -1;
@@ -281,16 +281,16 @@ void meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned
 		{
 		{
 			unsigned int index = indices[current_triangle * 3 + k];
 			unsigned int index = indices[current_triangle * 3 + k];
 
 
-			unsigned int* neighbours = &adjacency.data[0] + adjacency.offsets[index];
-			size_t neighbours_size = adjacency.counts[index];
+			unsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];
+			size_t neighbors_size = adjacency.counts[index];
 
 
-			for (size_t i = 0; i < neighbours_size; ++i)
+			for (size_t i = 0; i < neighbors_size; ++i)
 			{
 			{
-				unsigned int tri = neighbours[i];
+				unsigned int tri = neighbors[i];
 
 
 				if (tri == current_triangle)
 				if (tri == current_triangle)
 				{
 				{
-					neighbours[i] = neighbours[neighbours_size - 1];
+					neighbors[i] = neighbors[neighbors_size - 1];
 					adjacency.counts[index]--;
 					adjacency.counts[index]--;
 					break;
 					break;
 				}
 				}
@@ -314,10 +314,10 @@ void meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned
 			vertex_scores[index] = score;
 			vertex_scores[index] = score;
 
 
 			// update scores of vertex triangles
 			// update scores of vertex triangles
-			const unsigned int* neighbours_begin = &adjacency.data[0] + adjacency.offsets[index];
-			const unsigned int* neighbours_end = neighbours_begin + adjacency.counts[index];
+			const unsigned int* neighbors_begin = &adjacency.data[0] + adjacency.offsets[index];
+			const unsigned int* neighbors_end = neighbors_begin + adjacency.counts[index];
 
 
-			for (const unsigned int* it = neighbours_begin; it != neighbours_end; ++it)
+			for (const unsigned int* it = neighbors_begin; it != neighbors_end; ++it)
 			{
 			{
 				unsigned int tri = *it;
 				unsigned int tri = *it;
 				assert(!emitted_flags[tri]);
 				assert(!emitted_flags[tri]);
@@ -412,11 +412,11 @@ void meshopt_optimizeVertexCacheFifo(unsigned int* destination, const unsigned i
 	{
 	{
 		const unsigned int* next_candidates_begin = &dead_end[0] + dead_end_top;
 		const unsigned int* next_candidates_begin = &dead_end[0] + dead_end_top;
 
 
-		// emit all vertex neighbours
-		const unsigned int* neighbours_begin = &adjacency.data[0] + adjacency.offsets[current_vertex];
-		const unsigned int* neighbours_end = neighbours_begin + adjacency.counts[current_vertex];
+		// emit all vertex neighbors
+		const unsigned int* neighbors_begin = &adjacency.data[0] + adjacency.offsets[current_vertex];
+		const unsigned int* neighbors_end = neighbors_begin + adjacency.counts[current_vertex];
 
 
-		for (const unsigned int* it = neighbours_begin; it != neighbours_end; ++it)
+		for (const unsigned int* it = neighbors_begin; it != neighbors_end; ++it)
 		{
 		{
 			unsigned int triangle = *it;
 			unsigned int triangle = *it;
 
 
@@ -461,7 +461,7 @@ void meshopt_optimizeVertexCacheFifo(unsigned int* destination, const unsigned i
 		const unsigned int* next_candidates_end = &dead_end[0] + dead_end_top;
 		const unsigned int* next_candidates_end = &dead_end[0] + dead_end_top;
 
 
 		// get next vertex
 		// get next vertex
-		current_vertex = getNextVertexNeighbour(next_candidates_begin, next_candidates_end, &live_triangles[0], &cache_timestamps[0], timestamp, cache_size);
+		current_vertex = getNextVertexNeighbor(next_candidates_begin, next_candidates_end, &live_triangles[0], &cache_timestamps[0], timestamp, cache_size);
 
 
 		if (current_vertex == ~0u)
 		if (current_vertex == ~0u)
 		{
 		{

+ 71 - 17
thirdparty/meshoptimizer/vertexcodec.cpp

@@ -50,6 +50,12 @@
 #define SIMD_TARGET
 #define SIMD_TARGET
 #endif
 #endif
 
 
+// When targeting AArch64/x64, optimize for latency to allow decoding of individual 16-byte groups to overlap
+// We don't do this for 32-bit systems because we need 64-bit math for this and this will hurt in-order CPUs
+#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64)
+#define SIMD_LATENCYOPT
+#endif
+
 #endif // !MESHOPTIMIZER_NO_SIMD
 #endif // !MESHOPTIMIZER_NO_SIMD
 
 
 #ifdef SIMD_SSE
 #ifdef SIMD_SSE
@@ -472,6 +478,18 @@ static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi
 		typedef int unaligned_int;
 		typedef int unaligned_int;
 #endif
 #endif
 
 
+#ifdef SIMD_LATENCYOPT
+		unsigned int data32;
+		memcpy(&data32, data, 4);
+		data32 &= data32 >> 1;
+
+		// arrange bits such that low bits of nibbles of data64 contain all 2-bit elements of data32
+		unsigned long long data64 = ((unsigned long long)data32 << 30) | (data32 & 0x3fffffff);
+
+		// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3
+		int datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);
+#endif
+
 		__m128i sel2 = _mm_cvtsi32_si128(*reinterpret_cast<const unaligned_int*>(data));
 		__m128i sel2 = _mm_cvtsi32_si128(*reinterpret_cast<const unaligned_int*>(data));
 		__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 4));
 		__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 4));
 
 
@@ -490,11 +508,25 @@ static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi
 
 
 		_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);
 		_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);
 
 
+#ifdef SIMD_LATENCYOPT
+		return data + 4 + datacnt;
+#else
 		return data + 4 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
 		return data + 4 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
+#endif
 	}
 	}
 
 
 	case 2:
 	case 2:
 	{
 	{
+#ifdef SIMD_LATENCYOPT
+		unsigned long long data64;
+		memcpy(&data64, data, 8);
+		data64 &= data64 >> 1;
+		data64 &= data64 >> 2;
+
+		// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3
+		int datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);
+#endif
+
 		__m128i sel4 = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(data));
 		__m128i sel4 = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(data));
 		__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 8));
 		__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 8));
 
 
@@ -512,7 +544,11 @@ static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi
 
 
 		_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);
 		_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);
 
 
+#ifdef SIMD_LATENCYOPT
+		return data + 8 + datacnt;
+#else
 		return data + 8 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
 		return data + 8 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
+#endif
 	}
 	}
 
 
 	case 3:
 	case 3:
@@ -604,24 +640,13 @@ static uint8x16_t shuffleBytes(unsigned char mask0, unsigned char mask1, uint8x8
 
 
 static void neonMoveMask(uint8x16_t mask, unsigned char& mask0, unsigned char& mask1)
 static void neonMoveMask(uint8x16_t mask, unsigned char& mask0, unsigned char& mask1)
 {
 {
-	static const unsigned char byte_mask_data[16] = {1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};
-
-	uint8x16_t byte_mask = vld1q_u8(byte_mask_data);
-	uint8x16_t masked = vandq_u8(mask, byte_mask);
+	// magic constant found using z3 SMT assuming mask has 8 groups of 0xff or 0x00
+	const uint64_t magic = 0x000103070f1f3f80ull;
 
 
-#ifdef __aarch64__
-	// aarch64 has horizontal sums; MSVC doesn't expose this via arm64_neon.h so this path is exclusive to clang/gcc
-	mask0 = vaddv_u8(vget_low_u8(masked));
-	mask1 = vaddv_u8(vget_high_u8(masked));
-#else
-	// we need horizontal sums of each half of masked, which can be done in 3 steps (yielding sums of sizes 2, 4, 8)
-	uint8x8_t sum1 = vpadd_u8(vget_low_u8(masked), vget_high_u8(masked));
-	uint8x8_t sum2 = vpadd_u8(sum1, sum1);
-	uint8x8_t sum3 = vpadd_u8(sum2, sum2);
+	uint64x2_t mask2 = vreinterpretq_u64_u8(mask);
 
 
-	mask0 = vget_lane_u8(sum3, 0);
-	mask1 = vget_lane_u8(sum3, 1);
-#endif
+	mask0 = uint8_t((vgetq_lane_u64(mask2, 0) * magic) >> 56);
+	mask1 = uint8_t((vgetq_lane_u64(mask2, 1) * magic) >> 56);
 }
 }
 
 
 static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int bitslog2)
 static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int bitslog2)
@@ -639,6 +664,18 @@ static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi
 
 
 	case 1:
 	case 1:
 	{
 	{
+#ifdef SIMD_LATENCYOPT
+		unsigned int data32;
+		memcpy(&data32, data, 4);
+		data32 &= data32 >> 1;
+
+		// arrange bits such that low bits of nibbles of data64 contain all 2-bit elements of data32
+		unsigned long long data64 = ((unsigned long long)data32 << 30) | (data32 & 0x3fffffff);
+
+		// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3
+		int datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);
+#endif
+
 		uint8x8_t sel2 = vld1_u8(data);
 		uint8x8_t sel2 = vld1_u8(data);
 		uint8x8_t sel22 = vzip_u8(vshr_n_u8(sel2, 4), sel2).val[0];
 		uint8x8_t sel22 = vzip_u8(vshr_n_u8(sel2, 4), sel2).val[0];
 		uint8x8x2_t sel2222 = vzip_u8(vshr_n_u8(sel22, 2), sel22);
 		uint8x8x2_t sel2222 = vzip_u8(vshr_n_u8(sel22, 2), sel22);
@@ -655,11 +692,25 @@ static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi
 
 
 		vst1q_u8(buffer, result);
 		vst1q_u8(buffer, result);
 
 
+#ifdef SIMD_LATENCYOPT
+		return data + 4 + datacnt;
+#else
 		return data + 4 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
 		return data + 4 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
+#endif
 	}
 	}
 
 
 	case 2:
 	case 2:
 	{
 	{
+#ifdef SIMD_LATENCYOPT
+		unsigned long long data64;
+		memcpy(&data64, data, 8);
+		data64 &= data64 >> 1;
+		data64 &= data64 >> 2;
+
+		// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3
+		int datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);
+#endif
+
 		uint8x8_t sel4 = vld1_u8(data);
 		uint8x8_t sel4 = vld1_u8(data);
 		uint8x8x2_t sel44 = vzip_u8(vshr_n_u8(sel4, 4), vand_u8(sel4, vdup_n_u8(15)));
 		uint8x8x2_t sel44 = vzip_u8(vshr_n_u8(sel4, 4), vand_u8(sel4, vdup_n_u8(15)));
 		uint8x16_t sel = vcombine_u8(sel44.val[0], sel44.val[1]);
 		uint8x16_t sel = vcombine_u8(sel44.val[0], sel44.val[1]);
@@ -675,7 +726,11 @@ static const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsi
 
 
 		vst1q_u8(buffer, result);
 		vst1q_u8(buffer, result);
 
 
+#ifdef SIMD_LATENCYOPT
+		return data + 8 + datacnt;
+#else
 		return data + 8 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
 		return data + 8 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];
+#endif
 	}
 	}
 
 
 	case 3:
 	case 3:
@@ -715,7 +770,6 @@ static void wasmMoveMask(v128_t mask, unsigned char& mask0, unsigned char& mask1
 	// magic constant found using z3 SMT assuming mask has 8 groups of 0xff or 0x00
 	// magic constant found using z3 SMT assuming mask has 8 groups of 0xff or 0x00
 	const uint64_t magic = 0x000103070f1f3f80ull;
 	const uint64_t magic = 0x000103070f1f3f80ull;
 
 
-	// TODO: This can use v8x16_bitmask in the future
 	mask0 = uint8_t((wasm_i64x2_extract_lane(mask, 0) * magic) >> 56);
 	mask0 = uint8_t((wasm_i64x2_extract_lane(mask, 0) * magic) >> 56);
 	mask1 = uint8_t((wasm_i64x2_extract_lane(mask, 1) * magic) >> 56);
 	mask1 = uint8_t((wasm_i64x2_extract_lane(mask, 1) * magic) >> 56);
 }
 }

+ 1 - 1
thirdparty/meshoptimizer/vertexfilter.cpp

@@ -931,7 +931,7 @@ void meshopt_encodeFilterExp(void* destination_, size_t count, size_t stride, in
 		const float* v = &data[i * stride_float];
 		const float* v = &data[i * stride_float];
 		unsigned int* d = &destination[i * stride_float];
 		unsigned int* d = &destination[i * stride_float];
 
 
-		// use maximum exponent to encode values; this guarantess that mantissa is [-1, 1]
+		// use maximum exponent to encode values; this guarantees that mantissa is [-1, 1]
 		int exp = -100;
 		int exp = -100;
 
 
 		for (size_t j = 0; j < stride_float; ++j)
 		for (size_t j = 0; j < stride_float; ++j)