Skip to content

Commit 5bbda21

Browse files
quaglacopybara-github
authored andcommitted
Make the Octree interpolation continuous.
This is done by detecting the hanging nodes and compute the function at those location by interpolating the corresponding coarse vertices. PiperOrigin-RevId: 807700228 Change-Id: I27dcb85361ca445c2099dd07b280cae5c92d3131
1 parent 0438089 commit 5bbda21

File tree

4 files changed

+300
-5
lines changed

4 files changed

+300
-5
lines changed

src/user/user_mesh.cc

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <cstddef>
2121
#include <cstdio>
2222
#include <cstring>
23+
#include <deque>
2324
#include <functional>
2425
#include <limits>
2526
#include <map>
@@ -784,11 +785,44 @@ void mjCMesh::TryCompile(const mjVFS* vfs) {
784785
tmd::TriangleMeshDistance sdf(vert_.data(), nvert(), face_.data(), nface());
785786

786787
std::vector<double> coeffs(octree_.NumVerts());
787-
for (int i = 0; i < octree_.NumVerts(); ++i) {
788-
coeffs[i] = sdf.signed_distance(octree_.Vert(i)).distance;
788+
std::vector<bool> processed(octree_.NumVerts(), false);
789+
std::deque<int> queue;
790+
791+
if (octree_.NumNodes() > 0) {
792+
queue.push_back(0); // start traversal from the root node
793+
}
794+
795+
while (!queue.empty()) {
796+
int node_idx = queue.front();
797+
queue.pop_front();
798+
799+
for (int j = 0; j < 8; ++j) {
800+
int vert_id = octree_.VertId(node_idx, j);
801+
if (processed[vert_id]) {
802+
continue;
803+
}
804+
if (octree_.Hang(vert_id).empty()) {
805+
coeffs[vert_id] = sdf.signed_distance(octree_.Vert(vert_id)).distance;
806+
} else {
807+
double sum_coeff = 0;
808+
for (int dep_id : octree_.Hang(vert_id)) {
809+
sum_coeff += coeffs[dep_id];
810+
if (!processed[dep_id]) {
811+
throw mjCError(this, "sdf coefficient computation failed");
812+
}
813+
}
814+
coeffs[vert_id] = sum_coeff / octree_.Hang(vert_id).size();
815+
}
816+
processed[vert_id] = true;
817+
}
818+
819+
for (int child_idx : octree_.Children(node_idx)) {
820+
if (child_idx != -1) {
821+
queue.push_back(child_idx);
822+
}
823+
}
789824
}
790825

791-
// TODO: the value at hanging vertices should be computed from the parent
792826
for (int i = 0; i < octree_.NumNodes(); ++i) {
793827
for (int j = 0; j < 8; j++) {
794828
octree_.AddCoeff(i, j, coeffs[octree_.VertId(i, j)]);

src/user/user_objects.cc

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ void mjCOctree::CreateOctree(const double aamm[6]) {
629629
[](Triangle& triangle) { return &triangle; });
630630
std::unordered_map<Point, int> vert_map;
631631
MakeOctree(elements_ptrs, box, vert_map);
632+
MarkHangingNodes();
632633
}
633634

634635

@@ -871,6 +872,101 @@ void mjCOctree::BalanceOctree(std::unordered_map<Point, int>& vert_map) {
871872
}
872873

873874

875+
// mark all hanging vertices in the octree
876+
void mjCOctree::MarkHangingNodes() {
877+
hang_.assign(nvert_, std::vector<int>());
878+
879+
std::vector<int> leaves;
880+
for (int i = 0; i < nnode_; ++i) {
881+
if (node_[i].child[0] == -1) {
882+
leaves.push_back(i);
883+
}
884+
}
885+
886+
for (int leaf_idx : leaves) {
887+
for (int dir = 0; dir < 6; ++dir) {
888+
int neighbor_idx = FindNeighbor(leaf_idx, dir);
889+
if (neighbor_idx == -1 ||
890+
node_[neighbor_idx].level >= node_[leaf_idx].level) {
891+
continue;
892+
}
893+
894+
// coarser neighbor found, this leaf's face has hanging nodes
895+
int dim = dir / 2;
896+
int side = dir % 2;
897+
898+
// iterate over the 4 vertices of the leaf's face
899+
for (int i = 0; i < 4; ++i) {
900+
// construct vertex index on the face
901+
int v_idx = side << dim;
902+
int d1 = (dim + 1) % 3;
903+
int d2 = (dim + 2) % 3;
904+
v_idx |= (i & 1) << d1;
905+
v_idx |= ((i >> 1) & 1) << d2;
906+
907+
int hv_id = node_[leaf_idx].vertid[v_idx];
908+
if (!hang_[hv_id].empty()) {
909+
continue; // already processed
910+
}
911+
912+
const double* hv_pos = vert_[hv_id].p.data();
913+
const auto& neighbor_aamm = node_[neighbor_idx].aamm;
914+
915+
bool is_min[3], is_max[3];
916+
int on_boundary_planes = 0;
917+
for (int d = 0; d < 3; ++d) {
918+
is_min[d] = std::abs(hv_pos[d] - neighbor_aamm[d]) < 1e-9;
919+
is_max[d] = std::abs(hv_pos[d] - neighbor_aamm[d + 3]) < 1e-9;
920+
if (is_min[d] || is_max[d]) {
921+
on_boundary_planes++;
922+
}
923+
}
924+
925+
if (on_boundary_planes == 2) { // edge hanging
926+
int d_mid = -1;
927+
for (int d = 0; d < 3; ++d) {
928+
if (!is_min[d] && !is_max[d]) {
929+
d_mid = d;
930+
break;
931+
}
932+
}
933+
934+
int bits[3];
935+
bits[d_mid] = 0; // this will be toggled
936+
bits[(d_mid + 1) % 3] = is_max[(d_mid + 1) % 3];
937+
bits[(d_mid + 2) % 3] = is_max[(d_mid + 2) % 3];
938+
939+
int nv_idx1 = (bits[2] << 2) | (bits[1] << 1) | bits[0];
940+
bits[d_mid] = 1;
941+
int nv_idx2 = (bits[2] << 2) | (bits[1] << 1) | bits[0];
942+
943+
hang_[hv_id].push_back(node_[neighbor_idx].vertid[nv_idx1]);
944+
hang_[hv_id].push_back(node_[neighbor_idx].vertid[nv_idx2]);
945+
} else if (on_boundary_planes == 1) { // face hanging
946+
int d_face = -1;
947+
for (int d = 0; d < 3; ++d) {
948+
if (is_min[d] || is_max[d]) {
949+
d_face = d;
950+
break;
951+
}
952+
}
953+
954+
int bits[3];
955+
bits[d_face] = is_max[d_face];
956+
957+
for (int j = 0; j < 4; ++j) {
958+
bits[(d_face + 1) % 3] = j & 1;
959+
bits[(d_face + 2) % 3] = (j >> 1) & 1;
960+
int nv_idx = (bits[2] << 2) | (bits[1] << 1) | bits[0];
961+
hang_[hv_id].push_back(node_[neighbor_idx].vertid[nv_idx]);
962+
}
963+
}
964+
}
965+
}
966+
}
967+
}
968+
969+
874970
void mjCOctree::MakeOctree(const std::vector<Triangle*>& elements, const double aamm[6],
875971
std::unordered_map<Point, int>& vert_map) {
876972
std::deque<OctreeTask> queue;

src/user/user_objects.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,9 @@ struct mjCOctree_ {
270270
int nnode_ = 0;
271271
int nvert_ = 0;
272272
std::vector<OctNode> node_;
273-
std::vector<Triangle> face_; // mesh faces (nmeshface x 3)
274-
std::vector<Point> vert_; // octree vertices (nvert x 3)
273+
std::vector<Triangle> face_; // mesh faces (nmeshface x 3)
274+
std::vector<Point> vert_; // octree vertices (nvert x 3)
275+
std::vector<std::vector<int>> hang_; // hanging nodes status (nvert x 1)
275276
double ipos_[3] = {0, 0, 0};
276277
double iquat_[4] = {1, 0, 0, 0};
277278
};
@@ -287,7 +288,9 @@ class mjCOctree : public mjCOctree_ {
287288
void CopyAabb(mjtNum* aabb) const;
288289
void CopyCoeff(mjtNum* coeff) const;
289290
const double* Vert(int i) const { return vert_[i].p.data(); }
291+
const std::vector<int>& Hang(int i) const { return hang_[i]; }
290292
int VertId(int n, int v) const { return node_[n].vertid[v]; }
293+
const std::array<int, 8>& Children(int i) const { return node_[i].child; }
291294
void SetFace(const std::vector<double>& vert, const std::vector<int>& face);
292295
int Size() const {
293296
return sizeof(OctNode) * node_.size() + sizeof(Triangle) * face_.size() +
@@ -310,6 +313,7 @@ class mjCOctree : public mjCOctree_ {
310313
int FindNeighbor(int node_idx, int dir);
311314
int FindCoarseNeighbor(int node_idx, int dir);
312315
void BalanceOctree(std::unordered_map<Point, int>& vert_map);
316+
void MarkHangingNodes();
313317
};
314318

315319

test/user/user_mesh_test.cc

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,167 @@ TEST_F(MjCMeshTest, OctreeIsBalanced) {
13661366
mj_deleteModel(model);
13671367
}
13681368

1369+
TEST_F(MjCMeshTest, OctreeHangingNodeInterpolation) {
1370+
const std::string xml_path = GetTestDataFilePath(kTorusPath);
1371+
std::array<char, 1024> error;
1372+
mjSpec* spec = mj_parseXML(xml_path.c_str(), 0, error.data(), error.size());
1373+
mjsGeom* geom = mjs_asGeom(mjs_firstElement(spec, mjOBJ_GEOM));
1374+
geom->type = mjGEOM_SDF;
1375+
mjModel* model = mj_compile(spec, 0);
1376+
ASSERT_THAT(model, NotNull()) << error.data();
1377+
EXPECT_GT(model->mesh_octnum[0], 0);
1378+
double kEps = 1e-6;
1379+
1380+
const int octree_adr = model->mesh_octadr[0];
1381+
const int noct = model->mesh_octnum[0];
1382+
const mjtNum* sdf = model->oct_coeff + octree_adr * 8;
1383+
1384+
// find all leaves in the octree
1385+
std::vector<int> leaves;
1386+
for (int i = 0; i < noct; ++i) {
1387+
bool is_leaf = true;
1388+
for (int j = 0; j < 8; ++j) {
1389+
if (model->oct_child[(octree_adr + i) * 8 + j] != -1) {
1390+
is_leaf = false;
1391+
break;
1392+
}
1393+
}
1394+
if (is_leaf) {
1395+
leaves.push_back(i);
1396+
}
1397+
}
1398+
1399+
// do a n^2 check of all pairs of leaves in the octree
1400+
// for each pair, check if they are adjacent and if so, check that all hanging
1401+
// nodes within the octree can be interpolated from their parent nodes
1402+
int hanging_nodes_checked = 0;
1403+
int interpolation_failures = 0;
1404+
for (int i = 0; i < leaves.size(); ++i) {
1405+
for (int j = i + 1; j < leaves.size(); ++j) {
1406+
const int node1_idx = leaves[i];
1407+
const int node2_idx = leaves[j];
1408+
const mjtNum* aabb1 = &model->oct_aabb[(octree_adr + node1_idx) * 6];
1409+
const mjtNum* aabb2 = &model->oct_aabb[(octree_adr + node2_idx) * 6];
1410+
1411+
if (AreAabbsAdjacent(aabb1, aabb2)) {
1412+
const int level1 = model->oct_depth[octree_adr + node1_idx];
1413+
const int level2 = model->oct_depth[octree_adr + node2_idx];
1414+
1415+
if (level1 == level2) {
1416+
continue;
1417+
}
1418+
1419+
// decide which node is finer and which is coarser
1420+
const int finer_node_idx = (level1 > level2) ? node1_idx : node2_idx;
1421+
const int coarser_node_idx =
1422+
(level1 > level2) ? node2_idx : node1_idx;
1423+
const mjtNum* coarser_aabb =
1424+
&model->oct_aabb[(octree_adr + coarser_node_idx) * 6];
1425+
const mjtNum* finer_aabb =
1426+
&model->oct_aabb[(octree_adr + finer_node_idx) * 6];
1427+
1428+
mjtNum coarser_corners[8][3];
1429+
for (int c = 0; c < 8; ++c) {
1430+
int sx = (c & 1) ? 1 : -1;
1431+
int sy = (c & 2) ? 1 : -1;
1432+
int sz = (c & 4) ? 1 : -1;
1433+
coarser_corners[c][0] = coarser_aabb[0] + sx * coarser_aabb[3];
1434+
coarser_corners[c][1] = coarser_aabb[1] + sy * coarser_aabb[4];
1435+
coarser_corners[c][2] = coarser_aabb[2] + sz * coarser_aabb[5];
1436+
}
1437+
1438+
// for all vertices in the finer node, check if they are hanging and
1439+
// can be interpolated
1440+
for (int v_idx = 0; v_idx < 8; ++v_idx) {
1441+
mjtNum v_pos[3];
1442+
int sx = (v_idx & 1) ? 1 : -1;
1443+
int sy = (v_idx & 2) ? 1 : -1;
1444+
int sz = (v_idx & 4) ? 1 : -1;
1445+
v_pos[0] = finer_aabb[0] + sx * finer_aabb[3];
1446+
v_pos[1] = finer_aabb[1] + sy * finer_aabb[4];
1447+
v_pos[2] = finer_aabb[2] + sz * finer_aabb[5];
1448+
1449+
// skip finer vertices that are also coarse corners
1450+
bool is_coarse_corner = false;
1451+
for (int c = 0; c < 8; ++c) {
1452+
if (mju_dist3(v_pos, coarser_corners[c]) < 1e-6) {
1453+
is_coarse_corner = true;
1454+
break;
1455+
}
1456+
}
1457+
if (is_coarse_corner) {
1458+
continue;
1459+
}
1460+
1461+
// skip vertices that are not on the boundary
1462+
mjtNum p_local[3];
1463+
bool outside = false;
1464+
for (int d = 0; d < 3; ++d) {
1465+
p_local[d] = (v_pos[d] - coarser_aabb[d]) / coarser_aabb[d + 3];
1466+
if (std::abs(p_local[d]) > 1.0 + kEps) {
1467+
outside = true;
1468+
break;
1469+
}
1470+
}
1471+
if (outside) {
1472+
continue;
1473+
}
1474+
1475+
// count the number of dimensions that are on the boundary
1476+
int num_dim = 0;
1477+
for (int d = 0; d < 3; ++d) {
1478+
if (std::abs(p_local[d] - 1.0) < kEps) {
1479+
num_dim++;
1480+
} else if (std::abs(p_local[d] + 1.0) < kEps) {
1481+
num_dim++;
1482+
}
1483+
}
1484+
1485+
double interpolated_sdf = 0;
1486+
const mjtNum* coarser_sdf = sdf + coarser_node_idx * 8;
1487+
1488+
// for edge or face nodes, try to interpolate the hanging nodes
1489+
if (num_dim == 1 || num_dim == 2) {
1490+
for (int k = 0; k < 8; ++k) {
1491+
int sx = (k & 1) ? 1 : -1;
1492+
int sy = (k & 2) ? 1 : -1;
1493+
int sz = (k & 4) ? 1 : -1;
1494+
double weight = (1 + p_local[0] * sx) / 2.0 *
1495+
(1 + p_local[1] * sy) / 2.0 *
1496+
(1 + p_local[2] * sz) / 2.0;
1497+
if (weight > kEps && num_dim == 1) {
1498+
ASSERT_NEAR(weight, 0.25, kEps);
1499+
} else if (weight > kEps && num_dim == 2) {
1500+
ASSERT_NEAR(weight, 0.5, kEps);
1501+
}
1502+
interpolated_sdf += weight * coarser_sdf[k];
1503+
}
1504+
} else {
1505+
continue;
1506+
}
1507+
1508+
// if the values do not match, log an error
1509+
const mjtNum* finer_sdf = sdf + finer_node_idx * 8;
1510+
if (std::abs(finer_sdf[v_idx] - interpolated_sdf) > kEps) {
1511+
if (interpolation_failures < 10) {
1512+
EXPECT_NEAR(finer_sdf[v_idx], interpolated_sdf, kEps);
1513+
}
1514+
interpolation_failures++;
1515+
}
1516+
hanging_nodes_checked++;
1517+
}
1518+
}
1519+
}
1520+
}
1521+
EXPECT_GT(hanging_nodes_checked, 0);
1522+
EXPECT_EQ(interpolation_failures, 0)
1523+
<< "Found " << interpolation_failures
1524+
<< " hanging node interpolation failures.";
1525+
1526+
mj_deleteSpec(spec);
1527+
mj_deleteModel(model);
1528+
}
1529+
13691530
TEST_F(MjCMeshTest, OctreeNotComputedForNonSDF) {
13701531
const std::string xml_path = GetTestDataFilePath(kTorusPath);
13711532
std::array<char, 1024> error;

0 commit comments

Comments
 (0)