From 8cef5bb978986f88bc929143fdb8a2ea5378d432 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Fri, 8 May 2026 06:35:07 -0700 Subject: [PATCH] Add configurable octree max depth for meshes. The maximum depth of the octree used for SDF generation can now be specified in the mesh definition via `mjsMesh::octree_maxdepth`. The default value is 6. PiperOrigin-RevId: 912494999 Change-Id: I6d828d1d3999b99d1210a6829a05b04fac591e1f --- doc/includes/references.h | 1 + include/mujoco/mjspec.h | 1 + python/mujoco/introspect/structs.py | 5 +++++ src/user/user_init.c | 1 + src/user/user_mesh.cc | 2 ++ src/user/user_objects.cc | 2 +- src/user/user_objects.h | 6 ++++++ test/user/user_mesh_test.cc | 4 ++++ wasm/codegen/generated/bindings.cc | 7 +++++++ 9 files changed, 28 insertions(+), 1 deletion(-) diff --git a/doc/includes/references.h b/doc/includes/references.h index 6342e21b..d106b4b6 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -2294,6 +2294,7 @@ typedef struct mjsMesh_ { // mesh specification mjIntVec* userfacetexcoord; // user texcoord indices mjsPlugin plugin; // sdf plugin mjString* material; // name of material + int octree_maxdepth; // max octree depth mjString* info; // message appended to compiler errors } mjsMesh; typedef struct mjsHField_ { // height field specification diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index 73d7c74f..5dbf1908 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -490,6 +490,7 @@ typedef struct mjsMesh_ { // mesh specification mjIntVec* userfacetexcoord; // user texcoord indices mjsPlugin plugin; // sdf plugin mjString* material; // name of material + int octree_maxdepth; // max octree depth mjString* info; // message appended to compiler errors } mjsMesh; diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index 6ce4cb62..84ff1341 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -8461,6 +8461,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='name of material', ), + StructFieldDecl( + name='octree_maxdepth', + type=ValueType(name='int'), + doc='max octree depth', + ), StructFieldDecl( name='info', type=PointerType( diff --git a/src/user/user_init.c b/src/user/user_init.c index 1e522776..9a7274e8 100644 --- a/src/user/user_init.c +++ b/src/user/user_init.c @@ -243,6 +243,7 @@ void mjs_defaultMesh(mjsMesh* mesh) { mesh->scale[0] = mesh->scale[1] = mesh->scale[2] = 1; mesh->maxhullvert = -1; mesh->inertia = mjMESH_INERTIA_LEGACY; + mesh->octree_maxdepth = 6; } diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 8db7a9f5..23d71d90 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -782,6 +782,7 @@ void mjCMesh::TryCompile(const mjVFS* vfs) { } else if (octree_.NumNodes() == 0) { std::vector dvert(vert_.begin(), vert_.end()); octree_.SetFace(dvert, face_); + octree_.SetMaxDepth(spec.octree_maxdepth); octree_.CreateOctree(aamm_); if (!plugin.active) { octree_.ComputeSdfCoeffs(dvert.data(), nvert(), face_.data(), nface(), tree_); @@ -1532,6 +1533,7 @@ void mjCMesh::Process() { // make octree if (needsdf) { octree_.SetFace(dvert, face_); + octree_.SetMaxDepth(spec.octree_maxdepth); octree_.CreateOctree(aamm_); if (!plugin.active) { diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index d4f54ec6..b4d76a10 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -1285,7 +1285,7 @@ void mjCOctree::MakeOctree(const std::vector& elements, const double } // skip if the box is empty - if (colliding.empty() || task.lev >= 6) { + if (colliding.empty() || task.lev >= max_depth_) { continue; } diff --git a/src/user/user_objects.h b/src/user/user_objects.h index c0d1be94..a2566b4d 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -270,6 +270,7 @@ struct OctreeTask { struct mjCOctree_ { int nnode_ = 0; int nvert_ = 0; + int max_depth_ = 6; // max octree depth (default 6) std::vector node_; std::vector face_; // mesh faces (nmeshface x 3) std::vector vert_; // octree vertices (nvert x 3) @@ -308,6 +309,10 @@ class mjCOctree : public mjCOctree_ { void AddCoeff(int n, int v, double coeff) { node_[n].coeff[v] = coeff; } double Coeff(int n, int v) const { return node_[n].coeff[v]; } + // Set max octree depth (default 6) + void SetMaxDepth(int depth) { max_depth_ = depth; } + int MaxDepth() const { return max_depth_; } + // Set number of Laplacian smoothing iterations (0 = disabled, default) void SetSmoothingIterations(int iterations) { smoothing_iterations_ = iterations; } int SmoothingIterations() const { return smoothing_iterations_; } @@ -1208,6 +1213,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh { // octree const mjCOctree& octree() { return octree_; } + mjCOctree& mutable_octree() { return octree_; } void Compile(const mjVFS* vfs); // compiler double* GetPosPtr(); // get position diff --git a/test/user/user_mesh_test.cc b/test/user/user_mesh_test.cc index af5bc552..185b2678 100644 --- a/test/user/user_mesh_test.cc +++ b/test/user/user_mesh_test.cc @@ -1501,6 +1501,8 @@ TEST_F(MjCMeshTest, OctreeIsBalanced) { mjSpec* spec = mj_parseXML(xml_path.c_str(), 0, error.data(), error.size()); mjsGeom* geom = mjs_asGeom(mjs_firstElement(spec, mjOBJ_GEOM)); geom->type = mjGEOM_SDF; + mjsMesh* mesh = mjs_asMesh(mjs_firstElement(spec, mjOBJ_MESH)); + mesh->octree_maxdepth = 5; mjModel* model = mj_compile(spec, 0); ASSERT_THAT(model, NotNull()) << error.data(); EXPECT_GT(model->mesh_octnum[0], 0); @@ -1564,6 +1566,8 @@ TEST_F(MjCMeshTest, OctreeHangingNodeInterpolation) { mjSpec* spec = mj_parseXML(xml_path.c_str(), 0, error.data(), error.size()); mjsGeom* geom = mjs_asGeom(mjs_firstElement(spec, mjOBJ_GEOM)); geom->type = mjGEOM_SDF; + mjsMesh* mesh = mjs_asMesh(mjs_firstElement(spec, mjOBJ_MESH)); + mesh->octree_maxdepth = 5; mjModel* model = mj_compile(spec, 0); ASSERT_THAT(model, NotNull()) << error.data(); EXPECT_GT(model->mesh_octnum[0], 0); diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index 324092ef..f0c11146 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -6246,6 +6246,12 @@ struct MjsMesh { *(ptr_->material) = value; } } + int octree_maxdepth() const { + return ptr_->octree_maxdepth; + } + void set_octree_maxdepth(int value) { + ptr_->octree_maxdepth = value; + } mjString info() const { return (ptr_ && ptr_->info) ? *(ptr_->info) : ""; } @@ -12782,6 +12788,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("material", &MjsMesh::material, &MjsMesh::set_material, reference()) .property("maxhullvert", &MjsMesh::maxhullvert, &MjsMesh::set_maxhullvert, reference()) .property("needsdf", &MjsMesh::needsdf, &MjsMesh::set_needsdf, reference()) + .property("octree_maxdepth", &MjsMesh::octree_maxdepth, &MjsMesh::set_octree_maxdepth, reference()) .property("plugin", &MjsMesh::plugin, reference()) .property("refpos", &MjsMesh::refpos) .property("refquat", &MjsMesh::refquat)