Skip to content

Commit

Permalink
simplify: Add input & output error to simplifySloppy
Browse files Browse the repository at this point in the history
This change makes simplifySloppy interface match that of simplify, so
that an error limit can be specified up front, and the resulting error
is returned when requested.

As a consequence, simplifySloppy can no longer guarantee that the output
will be at or under the requested triangle count; this unifies the
interface between simplify & simplifySloppy further and gives us some
opportunity to compute triangle count in a more approximate way in the
future.

As a result we may need to compute the triangle count for the min. grid
size before simplification starts; this sometimes increases the number
of passes we need to do by 1, but in aggregate actually *decreases* the
number of passes as the interpolation search gets more data and can be
more precise.
  • Loading branch information
zeux committed Dec 31, 2020
1 parent 9fc841f commit b1b414b
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 b1b414b

Please sign in to comment.