Skip to content

Commit

Permalink
Merge pull request #217 from zeux/sloppy-error
Browse files Browse the repository at this point in the history
simplify: Add input & output error to simplifySloppy
  • Loading branch information
zeux authored Jan 1, 2021
2 parents e4e43fe + b1b414b commit 067f87d
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 30 deletions.
12 changes: 8 additions & 4 deletions demo/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -471,18 +471,22 @@ void simplifySloppy(const Mesh& mesh, float threshold = 0.2f)
double start = timestamp();

size_t target_index_count = size_t(mesh.indices.size() * threshold);
float target_error = 1e-1f;
float result_error = 0;

lod.indices.resize(target_index_count); // note: simplifySloppy, unlike simplify, is guaranteed to output results that don't exceed the requested target_index_count
lod.indices.resize(meshopt_simplifySloppy(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count));
lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count
lod.indices.resize(meshopt_simplifySloppy(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, &result_error));

lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()
lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));

double end = timestamp();

printf("%-9s: %d triangles => %d triangles in %.2f msec\n",
printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n",
"SimplifyS",
int(mesh.indices.size() / 3), int(lod.indices.size() / 3), (end - start) * 1000);
int(mesh.indices.size() / 3), int(lod.indices.size() / 3),
result_error * 100,
(end - start) * 1000);
}

void simplifyPoints(const Mesh& mesh, float threshold = 0.2f)
Expand Down
6 changes: 4 additions & 2 deletions demo/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -740,11 +740,13 @@ static void simplifySloppyStuck()
const float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
const unsigned int ib[] = {0, 1, 2, 0, 1, 2};

unsigned int* target = NULL;

// simplifying down to 0 triangles results in 0 immediately
assert(meshopt_simplifySloppy(0, ib, 3, vb, 3, 12, 0) == 0);
assert(meshopt_simplifySloppy(target, ib, 3, vb, 3, 12, 0, 0.f) == 0);

// simplifying down to 2 triangles given that all triangles are degenerate results in 0 as well
assert(meshopt_simplifySloppy(0, ib, 6, vb, 3, 12, 6) == 0);
assert(meshopt_simplifySloppy(target, ib, 6, vb, 3, 12, 6, 0.f) == 0);
}

static void simplifyPointsStuck()
Expand Down
7 changes: 4 additions & 3 deletions gltf/mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ static void simplifyMesh(Mesh& mesh, float threshold, bool aggressive)

size_t target_index_count = size_t(double(mesh.indices.size() / 3) * threshold) * 3;
float target_error = 1e-2f;
float target_error_aggressive = 1e-1f;

if (target_index_count < 1)
return;
Expand All @@ -526,10 +527,10 @@ static void simplifyMesh(Mesh& mesh, float threshold, bool aggressive)
// Note: if the simplifier got stuck, we can try to reindex without normals/tangents and retry
// For now we simply fall back to aggressive simplifier instead

// if the mesh is complex enough and the precise simplifier got "stuck", we'll try to simplify using the sloppy simplifier which is guaranteed to reach the target count
if (aggressive && target_index_count > 50 * 3 && mesh.indices.size() > target_index_count)
// if the precise simplifier got "stuck", we'll try to simplify using the sloppy simplifier; this is only used when aggressive simplification is enabled as it breaks attribute discontinuities
if (aggressive && mesh.indices.size() > target_index_count)
{
indices.resize(meshopt_simplifySloppy(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count));
indices.resize(meshopt_simplifySloppy(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count, target_error_aggressive));
mesh.indices.swap(indices);
}
}
Expand Down
20 changes: 11 additions & 9 deletions src/meshoptimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t ver
* 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!)
* 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
* result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification
Expand All @@ -272,15 +272,17 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplify(unsigned int* destination, co
/**
* 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.
* 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 required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
*
* destination must contain enough space for the target index buffer
* 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
* 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);
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);

/**
* Experimental: Point cloud simplifier
Expand All @@ -289,7 +291,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(unsigned int* destinati
* 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
* 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
*/
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);
Expand Down Expand Up @@ -533,7 +535,7 @@ inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const
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);
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);
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>
inline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index);
template <typename T>
Expand Down Expand Up @@ -855,12 +857,12 @@ inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_co
}

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)
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<T> in(0, indices, index_count);
meshopt_IndexAdapter<T> out(destination, 0, target_index_count);
meshopt_IndexAdapter<T> out(destination, 0, index_count);

return meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count);
return meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error, result_error);
}

template <typename T>
Expand Down
38 changes: 26 additions & 12 deletions src/simplifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1400,7 +1400,7 @@ size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices,
return result_count;
}

size_t meshopt_simplifySloppy(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)
size_t meshopt_simplifySloppy(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)
{
using namespace meshopt;

Expand All @@ -1412,9 +1412,6 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
// we expect to get ~2 triangles/vertex in the output
size_t target_cell_count = target_index_count / 6;

if (target_cell_count == 0)
return 0;

meshopt_Allocator allocator;

Vector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);
Expand All @@ -1431,18 +1428,25 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
const int kInterpolationPasses = 5;

// invariant: # of triangles in min_grid <= target_count
int min_grid = 0;
int min_grid = int(1.f / (target_error < 1e-3f ? 1e-3f : target_error));
int max_grid = 1025;
size_t min_triangles = 0;
size_t max_triangles = index_count / 3;

// when we're error-limited, we compute the triangle count for the min. size; this accelerates convergence and provides the correct answer when we can't use a larger grid
if (min_grid > 1)
{
computeVertexIds(vertex_ids, vertex_positions, vertex_count, min_grid);
min_triangles = countTriangles(vertex_ids, indices, index_count);
}

// instead of starting in the middle, let's guess as to what the answer might be! triangle count usually grows as a square of grid size...
int next_grid_size = int(sqrtf(float(target_cell_count)) + 0.5f);

for (int pass = 0; pass < 10 + kInterpolationPasses; ++pass)
{
assert(min_triangles < target_index_count / 3);
assert(max_grid - min_grid > 1);
if (min_triangles >= target_index_count / 3 || max_grid - min_grid <= 1)
break;

// we clamp the prediction of the grid size to make sure that the search converges
int grid_size = next_grid_size;
Expand Down Expand Up @@ -1471,16 +1475,18 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
max_triangles = triangles;
}

if (triangles == target_index_count / 3 || max_grid - min_grid <= 1)
break;

// we start by using interpolation search - it usually converges faster
// however, interpolation search has a worst case of O(N) so we switch to binary search after a few iterations which converges in O(logN)
next_grid_size = (pass < kInterpolationPasses) ? int(tip + 0.5f) : (min_grid + max_grid) / 2;
}

if (min_triangles == 0)
{
if (out_result_error)
*out_result_error = 1.f;

return 0;
}

// build vertex->cell association by mapping all vertices with the same quantized position to the same cell
size_t table_size = hashBuckets2(vertex_count);
Expand All @@ -1503,18 +1509,26 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind

fillCellRemap(cell_remap, cell_errors, cell_count, vertex_cells, cell_quadrics, vertex_positions, vertex_count);

// compute error
float result_error = 0.f;

for (size_t i = 0; i < cell_count; ++i)
result_error = result_error < cell_errors[i] ? cell_errors[i] : result_error;

// collapse triangles!
// note that we need to filter out triangles that we've already output because we very frequently generate redundant triangles between cells :(
size_t tritable_size = hashBuckets2(min_triangles);
unsigned int* tritable = allocator.allocate<unsigned int>(tritable_size);

size_t write = filterTriangles(destination, tritable, tritable_size, indices, index_count, vertex_cells, cell_remap);
assert(write <= target_index_count);

#if TRACE
printf("result: %d cells, %d triangles (%d unfiltered)\n", int(cell_count), int(write / 3), int(min_triangles));
printf("result: %d cells, %d triangles (%d unfiltered), error %e\n", int(cell_count), int(write / 3), int(min_triangles), sqrtf(result_error));
#endif

if (out_result_error)
*out_result_error = sqrtf(result_error);

return write;
}

Expand Down

0 comments on commit 067f87d

Please sign in to comment.