Fix flexcomp empty cell detection that was causing missing cells.
The previous implementation only checked whether grid cells contained mesh vertices to determine occupancy. For coarse meshes with large faces, most cells were incorrectly marked empty and pruned—even cells fully inside the object volume. This change improves the algorithm by: - Marking cells that overlap with any mesh element's AABB as non-empty. - For surface meshes, running a flood-fill from the grid boundary through non-overlapping cells to identify truly exterior cells. This preserves empty interior cells, preventing incorrect pruning of the object's core. - For volumetric meshes, defaulting to element-AABB overlap detection directly. Limitations for non-watertight meshes: If the mesh contains holes larger than the grid cell size, the flood-fill will leak into the interior. In this case, all non-element cells (including interior ones) will be marked as empty. PiperOrigin-RevId: 902675331 Change-Id: I5a84303a33d5ca7436213e7ce9aca3806c9f5a0f
This commit is contained in:
committed by
Copybara-Service
parent
476e2e909e
commit
188196603d
+106
-19
@@ -13,12 +13,14 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -108,27 +110,112 @@ void mjCFlexcomp::MarkEmptyCells(mjCFlex* flex, const double* points,
|
||||
int ncells = cx * cy * cz;
|
||||
int order = flex->spec.order;
|
||||
|
||||
// determine which cells contain mesh vertices
|
||||
flex->cell_empty.assign(ncells, true);
|
||||
for (int i = 0; i < npnt; i++) {
|
||||
// compute parametric coordinates of mesh vertex in [0, 1]^3
|
||||
// for flat meshes (zero extent along an axis), default to 0.5
|
||||
double dx = minmax[3] - minmax[0];
|
||||
double dy = minmax[4] - minmax[1];
|
||||
double dz = minmax[5] - minmax[2];
|
||||
double sx = dx > 0 ? (points[3*i+0] - minmax[0]) / dx : 0.5;
|
||||
double sy = dy > 0 ? (points[3*i+1] - minmax[1]) / dy : 0.5;
|
||||
double sz = dz > 0 ? (points[3*i+2] - minmax[2]) / dz : 0.5;
|
||||
// determine which cells contain mesh elements (not just vertices)
|
||||
// for each element, compute its AABB and mark all overlapping cells
|
||||
std::vector<bool> has_element(ncells, false);
|
||||
|
||||
// find containing cell
|
||||
int ci = std::min((int)(sx * cx), cx - 1);
|
||||
int cj = std::min((int)(sy * cy), cy - 1);
|
||||
int ck = std::min((int)(sz * cz), cz - 1);
|
||||
ci = std::max(ci, 0);
|
||||
cj = std::max(cj, 0);
|
||||
ck = std::max(ck, 0);
|
||||
double dx = minmax[3] - minmax[0];
|
||||
double dy = minmax[4] - minmax[1];
|
||||
double dz = minmax[5] - minmax[2];
|
||||
|
||||
flex->cell_empty[ci * cy * cz + cj * cz + ck] = false;
|
||||
// vertices per element: dim+1 (edges=2, triangles=3, tets=4)
|
||||
int nvpe = flex->spec.dim + 1;
|
||||
|
||||
if (nvpe > 0 && !element.empty()) {
|
||||
int nelem = element.size() / nvpe;
|
||||
for (int e = 0; e < nelem; e++) {
|
||||
// compute element AABB
|
||||
double elo[3] = {1e30, 1e30, 1e30};
|
||||
double ehi[3] = {-1e30, -1e30, -1e30};
|
||||
for (int v = 0; v < nvpe; v++) {
|
||||
int vid = element[nvpe * e + v];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
elo[j] = std::min(elo[j], points[3 * vid + j]);
|
||||
ehi[j] = std::max(ehi[j], points[3 * vid + j]);
|
||||
}
|
||||
}
|
||||
|
||||
// map element AABB to cell range
|
||||
auto cellIdx = [](double coord, double lo, double d, int nc) {
|
||||
if (d <= 0) return 0;
|
||||
int c = (int)((coord - lo) / d * nc);
|
||||
return std::max(0, std::min(nc - 1, c));
|
||||
};
|
||||
|
||||
int ci0 = cellIdx(elo[0], minmax[0], dx, cx);
|
||||
int ci1 = cellIdx(ehi[0], minmax[0], dx, cx);
|
||||
int cj0 = cellIdx(elo[1], minmax[1], dy, cy);
|
||||
int cj1 = cellIdx(ehi[1], minmax[1], dy, cy);
|
||||
int ck0 = cellIdx(elo[2], minmax[2], dz, cz);
|
||||
int ck1 = cellIdx(ehi[2], minmax[2], dz, cz);
|
||||
|
||||
// mark all overlapping cells as containing elements
|
||||
for (int ci = ci0; ci <= ci1; ci++) {
|
||||
for (int cj = cj0; cj <= cj1; cj++) {
|
||||
for (int ck = ck0; ck <= ck1; ck++) {
|
||||
has_element[ci * cy * cz + cj * cz + ck] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default: all cells non-empty (only exterior cells will be empty)
|
||||
flex->cell_empty.assign(ncells, false);
|
||||
|
||||
// for dim=2 (surface mesh): check watertightness and flood-fill
|
||||
if (flex->spec.dim == 2 && nvpe == 3 && !element.empty()) {
|
||||
// flood-fill from grid boundary to find exterior cells
|
||||
// cells reachable from the boundary through non-element cells
|
||||
// are outside the mesh volume; cells NOT reachable are interior
|
||||
std::vector<bool> visited(ncells, false);
|
||||
std::queue<std::array<int, 3>> bfs;
|
||||
|
||||
// seed BFS from boundary cells that have no elements
|
||||
for (int ci = 0; ci < cx; ci++) {
|
||||
for (int cj = 0; cj < cy; cj++) {
|
||||
for (int ck = 0; ck < cz; ck++) {
|
||||
if (ci == 0 || ci == cx - 1 ||
|
||||
cj == 0 || cj == cy - 1 ||
|
||||
ck == 0 || ck == cz - 1) {
|
||||
int idx = ci * cy * cz + cj * cz + ck;
|
||||
if (!has_element[idx] && !visited[idx]) {
|
||||
visited[idx] = true;
|
||||
flex->cell_empty[idx] = true;
|
||||
bfs.push({ci, cj, ck});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BFS: spread through non-element cells
|
||||
const int dirs[6][3] = {
|
||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0},
|
||||
{0, 1, 0}, {0, 0, -1}, {0, 0, 1}};
|
||||
while (!bfs.empty()) {
|
||||
auto [ci, cj, ck] = bfs.front();
|
||||
bfs.pop();
|
||||
for (auto& d : dirs) {
|
||||
int ni = ci + d[0], nj = cj + d[1], nk = ck + d[2];
|
||||
if (ni < 0 || ni >= cx ||
|
||||
nj < 0 || nj >= cy ||
|
||||
nk < 0 || nk >= cz) {
|
||||
continue;
|
||||
}
|
||||
int nidx = ni * cy * cz + nj * cz + nk;
|
||||
if (!visited[nidx] && !has_element[nidx]) {
|
||||
visited[nidx] = true;
|
||||
flex->cell_empty[nidx] = true;
|
||||
bfs.push({ni, nj, nk});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dim!=2 (e.g., tet mesh): cells without element overlap are empty
|
||||
for (int c = 0; c < ncells; c++) {
|
||||
flex->cell_empty[c] = !has_element[c];
|
||||
}
|
||||
}
|
||||
|
||||
// pin nodes that belong exclusively to empty cells
|
||||
|
||||
Reference in New Issue
Block a user