diff --git a/doc/changelog.rst b/doc/changelog.rst index 71604777..029e8bb0 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -86,6 +86,11 @@ Bug fixes in-gap contacts on multi-geom bodies. - Fixed a bug in the mesh compiler where normals were scaled as vectors rather than covectors. +.. admonition:: Breaking ABI changes + :class: attention + + - Added ``texid``, ``texuniform`` and ``texrepeat`` fields to ``mjvGeom``. + Version 3.10.0 (June 22, 2026) ------------------------------ diff --git a/doc/includes/references.h b/doc/includes/references.h index 776361c2..0bfb5bc5 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -3219,6 +3219,8 @@ typedef struct mjvGeom_ { // abstract geom int objid; // mujoco object id; -1 for decor int category; // visual category int matid; // material id; -1: no textured material + int texid; // texture id; -1: none + int texuniform; // uniform cube mapping int texcoord; // mesh or flex geom has texture coordinates int segid; // segmentation id; -1: not shown @@ -3233,6 +3235,7 @@ typedef struct mjvGeom_ { // abstract geom float specular; // specular coef float shininess; // shininess coef float reflectance; // reflectance coef + float texrepeat[2]; // texture repetition for 2d mapping char label[100]; // text label diff --git a/include/mujoco/mjvisualize.h b/include/mujoco/mjvisualize.h index f9a4840f..9c483553 100644 --- a/include/mujoco/mjvisualize.h +++ b/include/mujoco/mjvisualize.h @@ -231,6 +231,8 @@ typedef struct mjvGeom_ { // abstract geom int objid; // mujoco object id; -1 for decor int category; // visual category int matid; // material id; -1: no textured material + int texid; // texture id; -1: none + int texuniform; // uniform cube mapping int texcoord; // mesh or flex geom has texture coordinates int segid; // segmentation id; -1: not shown @@ -245,6 +247,7 @@ typedef struct mjvGeom_ { // abstract geom float specular; // specular coef float shininess; // shininess coef float reflectance; // reflectance coef + float texrepeat[2]; // texture repetition for 2d mapping char label[100]; // text label diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index 4a7f3c85..e048677a 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -10371,6 +10371,16 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='int'), doc='material id; -1: no textured material', ), + StructFieldDecl( + name='texid', + type=ValueType(name='int'), + doc='texture id; -1: none', + ), + StructFieldDecl( + name='texuniform', + type=ValueType(name='int'), + doc='uniform cube mapping', + ), StructFieldDecl( name='texcoord', type=ValueType(name='int'), @@ -10433,6 +10443,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='float'), doc='reflectance coef', ), + StructFieldDecl( + name='texrepeat', + type=ArrayType( + inner_type=ValueType(name='float'), + extents=(2,), + ), + doc='texture repetition for 2d mapping', + ), StructFieldDecl( name='label', type=ArrayType( diff --git a/python/mujoco/structs.cc b/python/mujoco/structs.cc index 35ce2b10..ad9f4ba4 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -1186,6 +1186,8 @@ This is useful for example when the MJB is not available as a file on disk.)")); X(objid); X(category); X(matid); + X(texid); + X(texuniform); X(texcoord); X(segid); X(emission); @@ -1202,6 +1204,7 @@ This is useful for example when the MJB is not available as a file on disk.)")); X(pos); X(mat); X(rgba); + X(texrepeat); #undef X DefinePyStr(mjvGeom, "label", &raw::MjvGeom::label); diff --git a/python/mujoco/structs.h b/python/mujoco/structs.h index f8b72049..450c7dbf 100644 --- a/python/mujoco/structs.h +++ b/python/mujoco/structs.h @@ -832,6 +832,7 @@ class MjWrapper : public WrapperBase { X(pos); X(mat); X(rgba); + X(texrepeat); #undef X }; diff --git a/python/mujoco/structs_wrappers.cc b/python/mujoco/structs_wrappers.cc index 60bd3bff..4e47446b 100644 --- a/python/mujoco/structs_wrappers.cc +++ b/python/mujoco/structs_wrappers.cc @@ -1193,7 +1193,8 @@ MjvGeomWrapper::MjWrapper() static_assert(sizeof(ptr_->mat) == sizeof(ptr_->mat[0]) * 9); return InitPyArray(std::array{3, 3}, ptr_->mat, owner_); }()), - X(rgba) { + X(rgba), + X(texrepeat) { mjv_initGeom(ptr_, mjGEOM_NONE, nullptr, nullptr, nullptr, nullptr); } @@ -1205,7 +1206,8 @@ MjvGeomWrapper::MjWrapper(raw::MjvGeom* ptr, py::handle owner) static_assert(sizeof(ptr_->mat) == sizeof(ptr_->mat[0]) * 9); return InitPyArray(std::array{3, 3}, ptr_->mat, owner_); }()), - X(rgba) {} + X(rgba), + X(texrepeat) {} #undef X MjvGeomWrapper::MjWrapper(const MjvGeomWrapper& other) : MjvGeomWrapper() { diff --git a/src/engine/engine_vis_visualize.c b/src/engine/engine_vis_visualize.c index 9109dec7..229e26ee 100644 --- a/src/engine/engine_vis_visualize.c +++ b/src/engine/engine_vis_visualize.c @@ -241,6 +241,10 @@ static void setMaterial(const mjModel* m, mjvGeom* geom, int matid, const float* // set texture if (flags[mjVIS_TEXTURE] && matid >= 0) { geom->matid = matid; + geom->texid = m->mat_texid[matid*mjNTEXROLE + mjTEXROLE_RGB]; + geom->texuniform = m->mat_texuniform[matid]; + geom->texrepeat[0] = m->mat_texrepeat[2*matid]; + geom->texrepeat[1] = m->mat_texrepeat[2*matid+1]; } // scale alpha for dynamic geoms only @@ -379,6 +383,10 @@ void mjv_initGeom(mjvGeom* geom, int type, const mjtNum* size, // set defaults that cannot be assigned via this function geom->dataid = -1; geom->matid = -1; + geom->texid = -1; + geom->texuniform = 0; + geom->texrepeat[0] = 0; + geom->texrepeat[1] = 0; geom->texcoord = 0; geom->emission = 0; geom->specular = 0.5; diff --git a/src/experimental/filament/compat/scene_geom_util.cc b/src/experimental/filament/compat/scene_geom_util.cc index 997ee92d..90f9d455 100644 --- a/src/experimental/filament/compat/scene_geom_util.cc +++ b/src/experimental/filament/compat/scene_geom_util.cc @@ -174,6 +174,9 @@ static void UpdateGeomMaterial(mjrfRenderable* renderable, const mjvGeom& geom, material.roughness_texture = get_texture(mjTEXROLE_ROUGHNESS); material.occlusion_texture = get_texture(mjTEXROLE_OCCLUSION); } + if (geom.texid >= 0) { + material.color_texture = model_objs->GetTexture(geom.texid); + } material.reflectance = geom.reflectance; material.emissive = geom.emission; @@ -200,14 +203,21 @@ static void UpdateGeomMaterial(mjrfRenderable* renderable, const mjvGeom& geom, // the programmatic UVs. if (material.color_texture) { - const bool tex_uniform = model->mat_texuniform[geom.matid]; + bool tex_uniform; + float tex_repeat[2]; + if (geom.texid >= 0) { + tex_uniform = geom.texuniform; + tex_repeat[0] = geom.texrepeat[0]; + tex_repeat[1] = geom.texrepeat[1]; + } else { + tex_uniform = model->mat_texuniform[geom.matid]; + tex_repeat[0] = model->mat_texrepeat[(geom.matid * 2) + 0]; + tex_repeat[1] = model->mat_texrepeat[(geom.matid * 2) + 1]; + } if (mjrf_getTextureSamplerType(material.color_texture) == mjTEXTURE_2D) { // For 2D textures, `tex_repeat` specifies how many times the texture // image is repeated. The `tex_uniform` flag determines if the repetition // is applied at in object space (false) or in world space (true). - float tex_repeat[2]; - tex_repeat[0] = model->mat_texrepeat[(geom.matid * 2) + 0]; - tex_repeat[1] = model->mat_texrepeat[(geom.matid * 2) + 1]; material.uv_scale[0] = tex_repeat[0]; material.uv_scale[1] = tex_repeat[1]; diff --git a/src/render/classic/render_gl3.c b/src/render/classic/render_gl3.c index 30d246ff..f986c0ff 100644 --- a/src/render/classic/render_gl3.c +++ b/src/render/classic/render_gl3.c @@ -61,10 +61,19 @@ enum { // enable/disable texture mapping static void settexture(int type, int state, const mjrContext* con, const mjvGeom* geom) { float plane[4], scl[2]; - int texid = -1; + int texid = -1, texuniform = 0; + float texrepeat[2] = {0, 0}; if (geom) { - if (geom->matid >= 0) { + if (geom->texid >= 0) { + texid = geom->texid; + texuniform = geom->texuniform; + texrepeat[0] = geom->texrepeat[0]; + texrepeat[1] = geom->texrepeat[1]; + } else if (geom->matid >= 0) { texid = con->mat_texid[mjNTEXROLE * geom->matid + mjTEXROLE_RGB]; + texuniform = con->mat_texuniform[geom->matid]; + texrepeat[0] = con->mat_texrepeat[geom->matid*2]; + texrepeat[1] = con->mat_texrepeat[geom->matid*2+1]; } } @@ -119,8 +128,8 @@ static void settexture(int type, int state, const mjrContext* con, const mjvGeom glBindTexture(GL_TEXTURE_2D, con->texture[texid]); // determine scaling, adjust for pre-scaled geoms - scl[0] = con->mat_texrepeat[geom->matid*2]; - scl[1] = con->mat_texrepeat[geom->matid*2+1]; + scl[0] = texrepeat[0]; + scl[1] = texrepeat[1]; if (geom->dataid >= 0) { if (geom->size[0] > 0) { scl[0] = scl[0] / mju_max(mjMINVAL, geom->size[0]); @@ -132,7 +141,7 @@ static void settexture(int type, int state, const mjrContext* con, const mjvGeom } // uniform: repeat relative to spatial units rather than object - if (con->mat_texuniform[geom->matid]) { + if (texuniform) { if (geom->size[0] > 0) { scl[0] = scl[0] * geom->size[0]; } @@ -171,11 +180,11 @@ static void settexture(int type, int state, const mjrContext* con, const mjvGeom // set mapping : cube if (type == mjtexREGULAR) { - mjr_setf4(plane, con->mat_texuniform[geom->matid] ? geom->size[0] : 1, 0, 0, 0); + mjr_setf4(plane, texuniform ? geom->size[0] : 1, 0, 0, 0); glTexGenfv(GL_S, GL_OBJECT_PLANE, plane); - mjr_setf4(plane, 0, con->mat_texuniform[geom->matid] ? geom->size[1] : 1, 0, 0); + mjr_setf4(plane, 0, texuniform ? geom->size[1] : 1, 0, 0); glTexGenfv(GL_T, GL_OBJECT_PLANE, plane); - mjr_setf4(plane, 0, 0, con->mat_texuniform[geom->matid] ? geom->size[2] : 1, 0); + mjr_setf4(plane, 0, 0, texuniform ? geom->size[2] : 1, 0); glTexGenfv(GL_R, GL_OBJECT_PLANE, plane); } @@ -1317,7 +1326,7 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { if (con->textureType[i] == mjTEXTURE_SKYBOX) { // save first skybox texture id in tempgeom memset(&tempgeom, 0, sizeof(mjvGeom)); - tempgeom.matid = mjMAXMATERIAL - 1; + tempgeom.texid = i; // modify settings glDisable(GL_LIGHTING); diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 14d2305b..631e77e2 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -5974,6 +5974,8 @@ public unsafe struct mjvGeom_ { public int objid; public int category; public int matid; + public int texid; + public int texuniform; public int texcoord; public int segid; public fixed float size[3]; @@ -5984,6 +5986,7 @@ public unsafe struct mjvGeom_ { public float specular; public float shininess; public float reflectance; + public fixed float texrepeat[2]; public fixed char label[100]; public float camdist; public float modelrbound; diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index 48f381f4..293fcb1f 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -6099,6 +6099,9 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("size", &MjvGeom::size) .property("specular", &MjvGeom::specular, &MjvGeom::set_specular, reference()) .property("texcoord", &MjvGeom::texcoord, &MjvGeom::set_texcoord, reference()) + .property("texid", &MjvGeom::texid, &MjvGeom::set_texid, reference()) + .property("texrepeat", &MjvGeom::texrepeat) + .property("texuniform", &MjvGeom::texuniform, &MjvGeom::set_texuniform, reference()) .property("transparent", &MjvGeom::transparent, &MjvGeom::set_transparent, reference()) .property("type", &MjvGeom::type, &MjvGeom::set_type, reference()); emscripten::class_("MjvLight") diff --git a/wasm/codegen/generated/bindings.h b/wasm/codegen/generated/bindings.h index fe193bd3..7fc08e80 100644 --- a/wasm/codegen/generated/bindings.h +++ b/wasm/codegen/generated/bindings.h @@ -1850,6 +1850,18 @@ struct MjvGeom { void set_matid(int value) { ptr_->matid = value; } + int texid() const { + return ptr_->texid; + } + void set_texid(int value) { + ptr_->texid = value; + } + int texuniform() const { + return ptr_->texuniform; + } + void set_texuniform(int value) { + ptr_->texuniform = value; + } int texcoord() const { return ptr_->texcoord; } @@ -1898,6 +1910,9 @@ struct MjvGeom { void set_reflectance(float value) { ptr_->reflectance = value; } + emscripten::val texrepeat() const { + return emscripten::val(emscripten::typed_memory_view(2, ptr_->texrepeat)); + } emscripten::val label() const { return emscripten::val(emscripten::typed_memory_view(100, ptr_->label)); }