@@ -1366,6 +1366,167 @@ TEST_F(MjCMeshTest, OctreeIsBalanced) {
1366
1366
mj_deleteModel (model);
1367
1367
}
1368
1368
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
+
1369
1530
TEST_F (MjCMeshTest, OctreeNotComputedForNonSDF) {
1370
1531
const std::string xml_path = GetTestDataFilePath (kTorusPath );
1371
1532
std::array<char , 1024 > error;
0 commit comments