Files
bgfx/3rdparty/meshoptimizer/src/remesher.cpp
T

752 lines
25 KiB
C++

// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
#include <assert.h>
#include <float.h>
#include <math.h>
#include <string.h>
#ifndef TRACE
#define TRACE 0
#endif
#if TRACE
#include <stdio.h>
#endif
// Note: this is only exposed for development purposes; do *not* use
enum
{
meshopt_RemeshInternalDebug = 1 << 30
};
// This work is based on:
// William Lorensen, Harvey Cline. Marching Cubes: A High Resolution 3D Surface Construction Algorithm. 1987
// Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
namespace meshopt
{
// triangles are encoded as three vertex codes in three nibbles
// vertex codes match the bit order XYZ (e.g. 3 maps to X=1 Y=1 Z=0)
struct Case
{
unsigned char code;
unsigned char altmode;
unsigned short triangles[5]; // up to 4 triangles and a null terminator
unsigned short alternate[5]; // alternate triangulation, restricted to have <= triangles vs primary
};
// the base cases establish a canonical triangulation; unlike classical Marching Cubes, we connect cube *corners* instead of edges
// each case may have an alternate triangulation, used based on deciders; crucially, open boundary edges must stay consistent as decider varies per cell
static const Case kBaseCases[] = {
{0x00, 0, {}, {}},
{0x01, 0, {}, {}},
{0x03, 0, {}, {}},
{0x06, 0, {}, {}},
{0x07, 0, {0x120}, {}},
{0x0f, 0, {0x130, 0x320}, {}},
{0x16, 1, {0x124, 0x142}, {}},
{0x17, 0, {0x124}, {}},
{0x18, 0, {}, {}},
{0x19, 1, {0x043, 0x403}, {}},
{0x1b, 1, {0x134, 0x430}, {0x410, 0x013}},
{0x1d, 1, {0x243, 0x340}, {0x320, 0x024}},
{0x1e, 1, {0x142, 0x432, 0x341}, {0x132}},
{0x1f, 2, {0x412, 0x213}, {0x134, 0x432}},
{0x3c, 1, {0x345, 0x324, 0x543, 0x423}, {}},
{0x3d, 1, {0x253, 0x524, 0x350}, {0x240, 0x504, 0x320}},
{0x3f, 0, {0x253, 0x452}, {}},
{0x69, 1, {0x035, 0x560, 0x306, 0x536}, {}},
{0x6b, 1, {0x063, 0x605, 0x365}, {0x305}},
{0x6f, 1, {0x365, 0x560}, {0x530, 0x036}},
{0x7e, 0, {0x142, 0x536}, {}},
{0x7f, 0, {0x365}, {}},
{0xf0, 0, {0x547, 0x746}, {}},
{0xff, 0, {}, {}},
};
// for each cube case, a triangulation is derived from the base cases via 90 degree rotations
static unsigned short kTriangleTable[256][2][5];
static unsigned char kTriangleCount[256];
static unsigned char kTriangleAlt[256];
static bool buildRemeshTables()
{
// corner remap for 90 degree rotations around X and Y
static const unsigned char rotations[2][8] = {{2, 3, 6, 7, 0, 1, 4, 5}, {4, 0, 6, 2, 5, 1, 7, 3}};
unsigned char filled[256] = {};
// copy base cases as is; we will generate the rest via rotations
for (size_t i = 0; i < sizeof(kBaseCases) / sizeof(kBaseCases[0]); ++i)
{
const Case& c = kBaseCases[i];
memcpy(kTriangleTable[c.code][0], c.triangles, sizeof(c.triangles));
memcpy(kTriangleTable[c.code][1], c.altmode ? c.alternate : c.triangles, sizeof(c.triangles));
kTriangleAlt[c.code] = c.altmode;
filled[kBaseCases[i].code] = 1;
}
// propagate base cases iteratively to fill all cube cases via rotations
// note: the worst case is 5 passes but in practice we only need 3 to fill all cases
for (int pass = 0; pass < 3; ++pass)
for (int code = 0; code < 256; ++code)
{
if (filled[code] != 1)
continue;
for (int r = 0; r < 2; ++r)
{
unsigned char rotated = 0;
for (int i = 0; i < 8; ++i)
rotated |= ((code >> i) & 1) << rotations[r][i];
if (filled[rotated])
continue;
for (int k = 0; k < 2; ++k)
for (int i = 0; kTriangleTable[code][k][i]; ++i)
{
unsigned short tri = kTriangleTable[code][k][i];
kTriangleTable[rotated][k][i] = (rotations[r][(tri >> 8) & 0xf] << 8) | (rotations[r][(tri >> 4) & 0xf] << 4) | rotations[r][tri & 0xf];
}
kTriangleAlt[rotated] = kTriangleAlt[code];
filled[rotated] = 1; // mark as pending
}
filled[code] = 2; // mark as processed
}
// finalize auxiliary tables
for (int code = 0; code < 256; ++code)
{
assert(filled[code]);
int count = 0;
while (kTriangleTable[code][0][count])
count++;
kTriangleCount[code] = (unsigned char)count;
// counting pass does not use alternate triangulations, so they need to stay at or below primary triangle count
assert(kTriangleTable[code][1][count] == 0);
// quadric decider expects a canonical quad encoding (0xabc 0xcbd)
if (kTriangleAlt[code] == 2)
assert(count == 2 && (kTriangleTable[code][0][0] & 0xf0) == (kTriangleTable[code][0][1] & 0xf0) && (kTriangleTable[code][0][0] & 0xf) == (kTriangleTable[code][0][1] >> 8));
}
return true;
}
static bool gRemeshTablesInitialized = buildRemeshTables();
struct Voxel
{
unsigned int coord;
unsigned char octants;
float px, py, pz;
float w;
// a00*x^2 + a11*y^2 + a22*z^2 + 2*a10*xy + 2*a20*xz + 2*a21*yz + 2*b0*x + 2*b1*y + 2*b2*z + c
float a00, a11, a22;
float a10, a20, a21;
float b0, b1, b2, c;
};
static void voxelAccumulate(Voxel& vox, float px, float py, float pz, float w)
{
vox.px += px * w;
vox.py += py * w;
vox.pz += pz * w;
vox.w += w;
}
static void voxelAccumulateQ(Voxel& vox, float a, float b, float c, float d, float w)
{
float aw = a * w;
float bw = b * w;
float cw = c * w;
float dw = d * w;
vox.a00 += a * aw;
vox.a11 += b * bw;
vox.a22 += c * cw;
vox.a10 += a * bw;
vox.a20 += a * cw;
vox.a21 += b * cw;
vox.b0 += a * dw;
vox.b1 += b * dw;
vox.b2 += c * dw;
vox.c += d * dw;
}
static float voxelError(const Voxel& vox, float x, float y, float z)
{
float rx = (vox.b0 + vox.a10 * y) * 2.f + vox.a00 * x;
float ry = (vox.b1 + vox.a21 * z) * 2.f + vox.a11 * y;
float rz = (vox.b2 + vox.a20 * x) * 2.f + vox.a22 * z;
return fabsf(vox.c + rx * x + ry * y + rz * z);
}
static bool voxelSolve(float& rx, float& ry, float& rz, const Voxel& vox, float lambda)
{
// solve A*p = -b where A is the quadric matrix and b is the linear term
float rw = lambda * vox.w;
float a00 = rw + vox.a00, a11 = rw + vox.a11, a22 = rw + vox.a22;
float a10 = vox.a10, a20 = vox.a20, a21 = vox.a21;
float x0 = lambda * vox.px - vox.b0, x1 = lambda * vox.py - vox.b1, x2 = lambda * vox.pz - vox.b2;
float eps = 1e-6f * vox.w;
// LDL decomposition: A = LDL^T
float d0 = a00;
float l10 = a10 / d0;
float l20 = a20 / d0;
float d1 = a11 - a10 * l10;
float dl21 = a21 - a20 * l10;
float l21 = dl21 / d1;
float d2 = a22 - a20 * l20 - dl21 * l21;
// solve L*y = x
float y0 = x0;
float y1 = x1 - l10 * y0;
float y2 = x2 - l20 * y0 - l21 * y1;
// solve D*z = y
float z0 = y0 / d0;
float z1 = y1 / d1;
float z2 = y2 / d2;
// substitute L^T*p = z
rz = z2;
ry = z1 - l21 * rz;
rx = z0 - l10 * ry - l20 * rz;
return fabsf(d0) > eps && fabsf(d1) > eps && fabsf(d2) > eps;
}
static float measureGrid(const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, int resolution, float* out_offset)
{
size_t vertex_stride_float = vertex_positions_stride / sizeof(float);
float minv[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
float maxv[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
for (size_t i = 0; i < vertex_count; ++i)
{
const float* v = vertex_positions_data + i * vertex_stride_float;
for (int j = 0; j < 3; ++j)
{
float vj = v[j];
minv[j] = minv[j] > vj ? vj : minv[j];
maxv[j] = maxv[j] < vj ? vj : maxv[j];
}
}
float extent = 0.f;
extent = (maxv[0] - minv[0]) < extent ? extent : (maxv[0] - minv[0]);
extent = (maxv[1] - minv[1]) < extent ? extent : (maxv[1] - minv[1]);
extent = (maxv[2] - minv[2]) < extent ? extent : (maxv[2] - minv[2]);
// small subvoxel padding keeps vertices inside the last valid voxel to avoid clamping correctness issues
extent *= (resolution + 1e-2f) / float(resolution);
// rescale extents to [0..resolution - 2], because the first and last voxels are used as empty padding
float scale = extent == 0 ? 0.f : (resolution - 2) / extent;
// center mesh in the grid; this improves voxelization for symmetric inputs
for (int j = 0; j < 3; ++j)
out_offset[j] = minv[j] - (extent - (maxv[j] - minv[j])) * 0.5f;
return scale;
}
static void voxelize(unsigned char* grid, Voxel* voxels, const unsigned int* voxel_rows, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, int resolution, float scale, const float offset[3], unsigned int options)
{
(void)vertex_count;
size_t vertex_stride_float = vertex_positions_stride / sizeof(float);
for (size_t i = 0; i < index_count; i += 3)
{
unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];
assert(a < vertex_count && b < vertex_count && c < vertex_count);
const float* va = vertex_positions + a * vertex_stride_float;
const float* vb = vertex_positions + b * vertex_stride_float;
const float* vc = vertex_positions + c * vertex_stride_float;
float ex = vb[0] - va[0], ey = vb[1] - va[1], ez = vb[2] - va[2];
float fx = vc[0] - va[0], fy = vc[1] - va[1], fz = vc[2] - va[2];
float gx = vc[0] - vb[0], gy = vc[1] - vb[1], gz = vc[2] - vb[2];
// use maximum edge length to establish sampling rate
// TODO: this is wasteful for thin triangles
float el = sqrtf(ex * ex + ey * ey + ez * ez);
float fl = sqrtf(fx * fx + fy * fy + fz * fz);
float gl = sqrtf(gx * gx + gy * gy + gz * gz);
float max_edge = el > fl ? el : fl;
max_edge = max_edge > gl ? max_edge : gl;
// we target 2 samples per voxel edge which should be enough to hit all voxels at any rotation
int samples = int(max_edge * scale * 2.f);
samples = samples > 1 ? samples : 1;
samples = samples < resolution * 2 ? samples : resolution * 2;
// normal is used to compute area for weighting as well as for the quadric
float nx = ey * fz - ez * fy, ny = ez * fx - ex * fz, nz = ex * fy - ey * fx;
float area = sqrtf(nx * nx + ny * ny + nz * nz);
float ns = area == 0.f ? 0.f : 1.f / area;
nx *= ns;
ny *= ns;
nz *= ns;
float sx = va[0] - offset[0], sy = va[1] - offset[1], sz = va[2] - offset[2];
float sr = 1.f / float(samples);
float weight = area / float((samples + 1) * (samples + 2));
// skip degenerate triangles; they don't contribute to voxelization and may result in NaN when computing voxel centroid
if (weight == 0.f)
continue;
for (int u = 0; u <= samples; ++u)
for (int v = 0; v <= samples - u; ++v)
{
float su = float(u) * sr, sv = float(v) * sr;
float px = sx + su * ex + sv * fx;
float py = sy + su * ey + sv * fy;
float pz = sz + su * ez + sv * fz;
// compute coordinates with an extra bit to use in octant mask
int hx = int(px * (scale * 2));
int hy = int(py * (scale * 2));
int hz = int(pz * (scale * 2));
int x = hx >> 1, y = hy >> 1, z = hz >> 1;
// safety: rounding errors and non-finite inputs may produce out of bounds coordinates, so we clamp them
int cutoff = resolution - 3;
x = unsigned(x) < unsigned(cutoff) ? x : cutoff;
y = unsigned(y) < unsigned(cutoff) ? y : cutoff;
z = unsigned(z) < unsigned(cutoff) ? z : cutoff;
size_t row = (y + 1) + size_t(resolution) * (z + 1);
size_t idx = (x + 1) + size_t(resolution) * row;
if (voxels)
{
assert(grid[idx] != 0 && grid[idx] != 0xff);
Voxel& vox = voxels[voxel_rows[row] + (grid[idx] - 1)];
vox.coord = (unsigned(x) << 20) | (unsigned(y) << 10) | unsigned(z);
vox.octants |= 1 << ((hx & 1) | ((hy & 1) << 1) | ((hz & 1) << 2));
voxelAccumulate(vox, px, py, pz, weight);
if (options & meshopt_RemeshSolve)
{
float distance = nx * px + ny * py + nz * pz;
voxelAccumulateQ(vox, nx, ny, nz, -distance, weight);
}
}
else
{
grid[idx] = 1;
}
}
}
}
static size_t rowpack(unsigned char* grid, unsigned int* voxel_rows, int resolution)
{
size_t result = 0;
size_t slice = size_t(resolution) * size_t(resolution);
for (size_t i = 0; i < slice; ++i)
{
unsigned char* data = grid + i * size_t(resolution);
int count = 0;
for (int x = 0; x < resolution; ++x)
{
assert(data[x] <= 1); // voxelize only produces 0/1 values
count += data[x];
data[x] = (data[x] != 0) ? (unsigned char)count : 0;
}
assert(count < 255); // we store offsets in a single byte, with 0 reserved for empty voxels and 0xff reserved for interior voxels
voxel_rows[i] = unsigned(result);
result += count;
}
return result;
}
static void solidifyQueue(unsigned int row, unsigned int* worklist, unsigned char* queued, size_t& pending)
{
if (queued[row])
return;
queued[row] = 1;
worklist[pending++] = row;
}
static void solidify(unsigned char* grid, unsigned int* worklist, unsigned char* queued, int resolution)
{
size_t pending = 0;
memset(queued, 0, size_t(resolution) * size_t(resolution));
// mark the interior empty voxels as 'inside'; we will propagate 'empty' state from the boundary
for (int z = 1; z < resolution - 1; ++z)
for (int y = 1; y < resolution - 1; ++y)
{
unsigned char* data = grid + size_t(resolution) * (y + size_t(resolution) * z);
for (int x = 1; x < resolution - 1; ++x)
data[x] = (data[x] == 0) ? 0xff : data[x];
}
// queue all rows for processing; note that boundary rows are always empty but we need them to propagate into neighboring rows
for (int z = 0; z < resolution; ++z)
for (int y = 0; y < resolution; ++y)
solidifyQueue(y + size_t(resolution) * z, worklist, queued, pending);
while (pending)
{
unsigned int row = worklist[--pending];
assert(queued[row]);
queued[row] = 0;
unsigned char* data = grid + size_t(resolution) * row;
// propagate outside state to the interior within row
for (int x = 1; x < resolution - 1; ++x)
data[x] = (data[x] == 0xff && data[x - 1] == 0) ? 0 : data[x];
for (int x = resolution - 2; x >= 1; --x)
data[x] = (data[x] == 0xff && data[x + 1] == 0) ? 0 : data[x];
// propagate outside state to the interior into neighboring rows
int y = row % resolution, z = row / resolution;
for (int k = 0; k < 4; ++k)
{
int yn = y + (k == 0 ? -1 : (k == 1 ? 1 : 0));
int zn = z + (k == 2 ? -1 : (k == 3 ? 1 : 0));
if (yn < 1 || yn >= resolution - 1 || zn < 1 || zn >= resolution - 1)
continue;
unsigned char* datan = grid + size_t(resolution) * (yn + size_t(resolution) * zn);
unsigned char changed = 0;
for (int x = 1; x < resolution - 1; ++x)
{
unsigned char rep = ((data[x] == 0) & (datan[x] == 0xff)) ? 0 : datan[x];
changed |= rep ^ datan[x];
datan[x] = rep;
}
if (changed)
solidifyQueue(yn + size_t(resolution) * zn, worklist, queued, pending);
}
}
}
static void solve(Voxel* voxels, size_t voxel_count, float scale, unsigned int options)
{
// regularization factor is a tradeoff between solve stability/uniformity and adherence to the original surface
float factor = 3e-2f;
// cutoff is a squared distance in model space; sqrt(3)/scale is the voxel diagonal
float cutoff = 3.f / (scale * scale);
float rscale = 1.f / scale;
size_t solved = 0;
for (size_t i = 0; i < voxel_count; ++i)
{
Voxel& vox = voxels[i];
float px = vox.px / vox.w, py = vox.py / vox.w, pz = vox.pz / vox.w;
// voxel corner, used for clamping and debug visualization
float cx = int((vox.coord >> 20) & 1023) * rscale;
float cy = int((vox.coord >> 10) & 1023) * rscale;
float cz = int((vox.coord >> 0) & 1023) * rscale;
if (options & meshopt_RemeshSolve)
{
// compute minimizing point; this includes regularization to stabilize the solve
float sx, sy, sz;
if (voxelSolve(sx, sy, sz, vox, factor))
{
float d = (sx - px) * (sx - px) + (sy - py) * (sy - py) + (sz - pz) * (sz - pz);
// reject solutions that move the vertex too far from voxel centroid; this is a safety net in case regularization is insufficient
if (d < cutoff)
{
// use a clamped solution to reduce intersections
px = sx > cx ? sx : cx;
px = px < cx + rscale ? px : cx + rscale;
py = sy > cy ? sy : cy;
py = py < cy + rscale ? py : cy + rscale;
pz = sz > cz ? sz : cz;
pz = pz < cz + rscale ? pz : cz + rscale;
solved++;
}
}
}
if (options & meshopt_RemeshInternalDebug)
{
px = cx + 0.5f * rscale;
py = cy + 0.5f * rscale;
pz = cz + 0.5f * rscale;
}
vox.px = px;
vox.py = py;
vox.pz = pz;
}
(void)solved;
#if TRACE
if (options & meshopt_RemeshSolve)
printf("remesher: solved %zu/%zu voxels\n", solved, voxel_count);
#endif
}
static void emitVertex(float* result, int x, int y, int z, int corner, const unsigned char* grid, const Voxel* voxels, const unsigned int* voxel_rows, int resolution, const float offset[3])
{
int ox = corner & 1, oy = (corner >> 1) & 1, oz = (corner >> 2) & 1;
size_t row = (y + oy) + size_t(resolution) * (z + oz);
size_t idx = (x + ox) + size_t(resolution) * row;
assert(grid[idx] != 0 && grid[idx] != 0xff);
const Voxel& vox = voxels[voxel_rows[row] + (grid[idx] - 1)];
result[0] = vox.px + offset[0];
result[1] = vox.py + offset[1];
result[2] = vox.pz + offset[2];
}
static bool octantDecider(int x, int y, int z, int cube, const unsigned char* grid, const Voxel* voxels, const unsigned int* voxel_rows, int resolution)
{
for (int c = 0; c < 8; ++c)
if (cube & (1 << c))
{
int ox = c & 1, oy = (c >> 1) & 1, oz = (c >> 2) & 1;
size_t row = (y + oy) + size_t(resolution) * (z + oz);
size_t idx = (x + ox) + size_t(resolution) * row;
assert(grid[idx] != 0);
// note: unlike vertex emission, which never consults interior occupied corners, here we need to skip corners without an occupied voxel
if (grid[idx] == 0xff)
continue;
const Voxel& vox = voxels[voxel_rows[row] + (grid[idx] - 1)];
// test octant contained within the cell (opposite of the corner index)
if (vox.octants & (1 << (7 - c)))
return false;
}
// select alternate configuration if *all* octants are empty
return true;
}
static bool quadricDecider(int x, int y, int z, int cube, const unsigned char* grid, const Voxel* voxels, const unsigned int* voxel_rows, int resolution)
{
// quads are encoded implicitly as 0xabc 0xcbd
unsigned int quad = (kTriangleTable[cube][0][0] << 4) | (kTriangleTable[cube][0][1] & 0xf);
const Voxel* corner[4];
for (int i = 0; i < 4; ++i)
{
int c = (quad >> (12 - i * 4)) & 0xf;
size_t row = (y + ((c >> 1) & 1)) + size_t(resolution) * (z + ((c >> 2) & 1));
size_t idx = (x + (c & 1)) + size_t(resolution) * row;
assert(grid[idx] != 0 && grid[idx] != 0xff);
corner[i] = &voxels[voxel_rows[row] + (grid[idx] - 1)];
}
// evaluate error for midpoints of primary (bc) and alternate (ad) diagonal
float mx = (corner[1]->px + corner[2]->px) * 0.5f, my = (corner[1]->py + corner[2]->py) * 0.5f, mz = (corner[1]->pz + corner[2]->pz) * 0.5f;
float nx = (corner[0]->px + corner[3]->px) * 0.5f, ny = (corner[0]->py + corner[3]->py) * 0.5f, nz = (corner[0]->pz + corner[3]->pz) * 0.5f;
float error0 = 0, error1 = 0;
for (int i = 0; i < 4; ++i)
{
error0 += voxelError(*corner[i], mx, my, mz);
error1 += voxelError(*corner[i], nx, ny, nz);
}
// select alternate configuration if it is clearly better
return error1 < error0 * 0.9f;
}
static size_t polygonize(float* destination, size_t max_triangle_count, const unsigned char* grid, const Voxel* voxels, const unsigned int* voxel_rows, int resolution, const float offset[3], unsigned int options)
{
size_t result = 0;
size_t slice = size_t(resolution) * size_t(resolution);
assert(gRemeshTablesInitialized);
for (int z = 0; z < resolution - 1; ++z)
for (int y = 0; y < resolution - 1; ++y)
{
const unsigned char* data = grid + size_t(resolution) * (y + size_t(resolution) * z);
// we track each slice as a 8-bit code (matching cube indexing) as we iterate through the row to avoid extra lookups
int last = (data[0] != 0) | ((data[resolution] != 0) << 2) | ((data[slice] != 0) << 4) | ((data[slice + resolution] != 0) << 6);
for (int x = 0; x < resolution - 1; ++x)
{
int next = (data[x + 1] != 0) | ((data[x + 1 + resolution] != 0) << 2) | ((data[x + 1 + slice] != 0) << 4) | ((data[x + 1 + slice + resolution] != 0) << 6);
// next slice bits are in position for the next iteration so we need to shift them to retrieve current cube code
int cube = last | (next << 1);
last = next;
if (cube == 0 || cube == 0xff)
continue;
if (!destination)
{
// fast path: we can statically determine the upper bound on triangles the loop below will output based on cube configuration
result += kTriangleCount[cube];
continue;
}
// deciders are only evaluated for cells with an alternate configuration to reduce overhead
int alt = 0;
if (kTriangleAlt[cube] == 1)
alt = octantDecider(x, y, z, cube, grid, voxels, voxel_rows, resolution);
else if (kTriangleAlt[cube] == 2 && (options & meshopt_RemeshSolve))
alt = quadricDecider(x, y, z, cube, grid, voxels, voxel_rows, resolution);
const unsigned short* tris = kTriangleTable[cube][alt];
for (int i = 0; tris[i]; ++i)
{
// note: we only emit the triangle if we have space for it, but we count it regardless for consistent capacity estimation
if (result < max_triangle_count)
{
unsigned short tri = tris[i];
emitVertex(&destination[result * 9 + 0], x, y, z, (tri >> 8) & 0xf, grid, voxels, voxel_rows, resolution, offset);
emitVertex(&destination[result * 9 + 3], x, y, z, (tri >> 4) & 0xf, grid, voxels, voxel_rows, resolution, offset);
emitVertex(&destination[result * 9 + 6], x, y, z, (tri >> 0) & 0xf, grid, voxels, voxel_rows, resolution, offset);
}
result++;
}
}
}
return result;
}
} // namespace meshopt
size_t meshopt_remesh(float* destination, size_t max_triangle_count, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, int resolution, unsigned int options)
{
using namespace meshopt;
assert(destination || max_triangle_count == 0);
assert(index_count % 3 == 0);
assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
assert(vertex_positions_stride % sizeof(float) == 0);
assert(resolution >= 4 && resolution <= 256);
meshopt_Allocator allocator;
// measure voxel grid to compute position => voxel mapping
float offset[3] = {};
float scale = measureGrid(vertex_positions, vertex_count, vertex_positions_stride, resolution, offset);
// rasterize triangles into the voxel grid: in the first pass, this simply tags occupied voxels
unsigned char* grid = allocator.allocate<unsigned char>(size_t(resolution) * size_t(resolution) * size_t(resolution));
memset(grid, 0, size_t(resolution) * size_t(resolution) * size_t(resolution));
voxelize(grid, NULL, NULL, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, resolution, scale, offset, options);
// allocate additional voxel data for each occupied voxel; this can be filled in the second pass to compute positions
// note that we only do this if we need to compute output triangles; counting runs skip it for performance
Voxel* voxels = NULL;
unsigned int* voxel_rows = NULL;
size_t voxel_count = 0;
if (destination)
{
voxel_rows = allocator.allocate<unsigned int>(size_t(resolution) * size_t(resolution));
voxel_count = rowpack(grid, voxel_rows, resolution);
voxels = allocator.allocate<Voxel>(voxel_count);
memset(voxels, 0, voxel_count * sizeof(Voxel));
#if TRACE
printf("remesher: %zu voxels occupied\n", voxel_count);
#endif
}
// fill in empty voxels that are not reachable from the grid boundary; the inside empty voxels are marked with 0xff
// note that these voxels do *not* have the associated Voxel data as they were never voxelized
if ((options & meshopt_RemeshShell) == 0)
{
unsigned int* worklist = allocator.allocate<unsigned int>(size_t(resolution) * size_t(resolution));
unsigned char* queued = allocator.allocate<unsigned char>(size_t(resolution) * size_t(resolution));
solidify(grid, worklist, queued, resolution);
#if TRACE
size_t inside_count = 0, occupied_count = 0;
for (size_t i = 0; i < size_t(resolution) * size_t(resolution) * size_t(resolution); ++i)
{
inside_count += (grid[i] == 0xff);
occupied_count += (grid[i] != 0 && grid[i] != 0xff);
}
printf("remesher: %zu voxels occupied, %zu voxels inside\n", occupied_count, inside_count);
#endif
}
// accumulate voxel positions: in the second pass, this computes enough data in each voxel to calculate positions
if (voxels)
voxelize(grid, voxels, voxel_rows, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, resolution, scale, offset, options);
// compute final voxel positions; each voxel has a single resulting position that will be emitted during polygonization
if (voxels)
solve(voxels, voxel_count, scale, options);
// output triangles from the voxel grid; if destination is NULL, this still counts the number of triangles that would be generated
size_t result = polygonize(destination, max_triangle_count, grid, voxels, voxel_rows, resolution, offset, options);
#if TRACE
printf("remesher: %zu triangles (%zu capacity)\n", result, max_triangle_count);
#endif
return result;
}