diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 43f159ce0c..64a0344424 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -3916,9 +3916,14 @@ cases, the user will specify a :el:`flexcomp` which will then automatically cons .. _deformable-flex-texcoord: -:at:`texcoord`: :at-val:`real(2*nvert), optional` - Texture coordinates for each vertex. If omitted, texture mapping for this flex is disabled, even if a texture is - specified in the material. +:at:`texcoord`: :at-val:`real(2*vert or ntexcoord), optional` + Texture coordinates. If omitted, texture mapping for this flex is disabled, even if a texture is specified in the + material. + +.. _deformable-flex-facetexcoord: + +:at:`facetexcoord`: :at-val:`int((dim+1)*nelem), optional` + Texture indices for each face. If omitted, texture are assumed to be vertex-based. .. _deformable-flex-element: diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index 15c962e551..1d2d31e0d8 100644 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -485,7 +485,9 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`material` | :ref:`rgba` | :ref:`flatskin` | :ref:`body` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`vertex` | :ref:`element` | :ref:`texcoord` | :ref:`node` | | +| | | | :ref:`vertex` | :ref:`element` | :ref:`texcoord` | :ref:`facetexcoord` | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`node` | | | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_2| flex |br| |_2| |L| | | .. table:: | diff --git a/doc/includes/references.h b/doc/includes/references.h index ae0c13c623..d59a397b32 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1177,6 +1177,7 @@ struct mjModel_ { int* flex_vertbodyid; // vertex body ids (nflexvert x 1) int* flex_edge; // edge vertex ids (2 per edge) (nflexedge x 2) int* flex_elem; // element vertex ids (dim+1 per elem) (nflexelemdata x 1) + int* flex_elemtexcoord; // element texture coordinates (dim+1) (nflexelemdata x 1) int* flex_elemedge; // element edge ids (nflexelemedge x 1) int* flex_elemlayer; // element distance from surface, 3D only (nflexelem x 1) int* flex_shell; // shell fragment vertex ids (dim per frag) (nflexshelldata x 1) @@ -2009,6 +2010,7 @@ typedef struct mjsFlex_ { // flex specification mjDoubleVec* vert; // vertex positions mjIntVec* elem; // element vertex ids mjFloatVec* texcoord; // vertex texture coordinates + mjIntVec* facetexcoord; // face texture coordinates // other mjString* info; // message appended to compiler errors @@ -2997,6 +2999,7 @@ struct mjvSceneState_ { int* flex_vertadr; int* flex_vertnum; int* flex_elem; + int* flex_elemtexcoord; int* flex_elemlayer; int* flex_elemadr; int* flex_elemnum; diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index d7ce62fce5..5fddbec0cf 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -885,6 +885,7 @@ struct mjModel_ { int* flex_vertbodyid; // vertex body ids (nflexvert x 1) int* flex_edge; // edge vertex ids (2 per edge) (nflexedge x 2) int* flex_elem; // element vertex ids (dim+1 per elem) (nflexelemdata x 1) + int* flex_elemtexcoord; // element texture coordinates (dim+1) (nflexelemdata x 1) int* flex_elemedge; // element edge ids (nflexelemedge x 1) int* flex_elemlayer; // element distance from surface, 3D only (nflexelem x 1) int* flex_shell; // shell fragment vertex ids (dim per frag) (nflexshelldata x 1) diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index 9fd1046f55..77ce5ddc57 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -446,6 +446,7 @@ typedef struct mjsFlex_ { // flex specification mjDoubleVec* vert; // vertex positions mjIntVec* elem; // element vertex ids mjFloatVec* texcoord; // vertex texture coordinates + mjIntVec* facetexcoord; // face texture coordinates // other mjString* info; // message appended to compiler errors diff --git a/include/mujoco/mjvisualize.h b/include/mujoco/mjvisualize.h index 96574ae00e..19dad80c2a 100644 --- a/include/mujoco/mjvisualize.h +++ b/include/mujoco/mjvisualize.h @@ -523,6 +523,7 @@ struct mjvSceneState_ { int* flex_vertadr; int* flex_vertnum; int* flex_elem; + int* flex_elemtexcoord; int* flex_elemlayer; int* flex_elemadr; int* flex_elemnum; diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 699506950f..a86dca46d0 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -350,6 +350,7 @@ X ( int, flex_vertbodyid, nflexvert, 1 ) \ X ( int, flex_edge, nflexedge, 2 ) \ XMJV( int, flex_elem, nflexelemdata, 1 ) \ + XMJV( int, flex_elemtexcoord, nflexelemdata, 1 ) \ X ( int, flex_elemedge, nflexelemedge, 1 ) \ XMJV( int, flex_elemlayer, nflexelem, 1 ) \ XMJV( int, flex_shell, nflexshelldata,1 ) \ diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index 6c77566078..41c94673f5 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -2620,6 +2620,14 @@ doc='element vertex ids (dim+1 per elem)', array_extent=('nflexelemdata',), ), + StructFieldDecl( + name='flex_elemtexcoord', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='element texture coordinates (dim+1)', + array_extent=('nflexelemdata',), + ), StructFieldDecl( name='flex_elemedge', type=PointerType( @@ -7636,6 +7644,13 @@ ), doc='', ), + StructFieldDecl( + name='flex_elemtexcoord', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='', + ), StructFieldDecl( name='flex_elemlayer', type=PointerType( @@ -10440,6 +10455,13 @@ ), doc='vertex texture coordinates', ), + StructFieldDecl( + name='facetexcoord', + type=PointerType( + inner_type=ValueType(name='mjIntVec'), + ), + doc='face texture coordinates', + ), StructFieldDecl( name='info', type=PointerType( diff --git a/src/engine/engine_vis_visualize.c b/src/engine/engine_vis_visualize.c index 22ecf14592..1e665a2c2b 100644 --- a/src/engine/engine_vis_visualize.c +++ b/src/engine/engine_vis_visualize.c @@ -2527,15 +2527,16 @@ void mjv_updateActiveFlex(const mjModel* m, mjData* d, mjvScene* scn, const mjvO if (dim == 2 || m->flex_elemlayer[m->flex_elemadr[f]+e] == opt->flex_layer) { // get element data const int* edata = m->flex_elem + m->flex_elemdataadr[f] + e*(dim+1); + const int* tdata = m->flex_elemtexcoord + m->flex_elemdataadr[f] + e*(dim+1); // triangles: two faces per element if (dim == 2) { makeFace(face, normal, radius, vertxpos, nface, edata[0], edata[1], edata[2]); - copyTex(texdst, texsrc, nface, edata[0], edata[1], edata[2]); + copyTex(texdst, texsrc, nface, tdata[0], tdata[1], tdata[2]); nface++; makeFace(face, normal, radius, vertxpos, nface, edata[0], edata[2], edata[1]); - copyTex(texdst, texsrc, nface, edata[0], edata[2], edata[1]); + copyTex(texdst, texsrc, nface, tdata[0], tdata[2], tdata[1]); nface++; } @@ -2543,22 +2544,22 @@ void mjv_updateActiveFlex(const mjModel* m, mjData* d, mjvScene* scn, const mjvO else { makeFace(face, normal, radius, vertxpos, nface, edata[0], edata[1], edata[2]); - copyTex(texdst, texsrc, nface, edata[0], edata[1], edata[2]); + copyTex(texdst, texsrc, nface, tdata[0], tdata[1], tdata[2]); nface++; makeFace(face, normal, radius, vertxpos, nface, edata[0], edata[2], edata[3]); - copyTex(texdst, texsrc, nface, edata[0], edata[2], edata[3]); + copyTex(texdst, texsrc, nface, tdata[0], tdata[2], tdata[3]); nface++; makeFace(face, normal, radius, vertxpos, nface, edata[0], edata[3], edata[1]); - copyTex(texdst, texsrc, nface, edata[0], edata[3], edata[1]); + copyTex(texdst, texsrc, nface, tdata[0], tdata[3], tdata[1]); nface++; makeFace(face, normal, radius, vertxpos, nface, edata[1], edata[3], edata[2]); - copyTex(texdst, texsrc, nface, edata[1], edata[3], edata[2]); + copyTex(texdst, texsrc, nface, tdata[1], tdata[3], tdata[2]); nface++; } } @@ -2598,13 +2599,14 @@ void mjv_updateActiveFlex(const mjModel* m, mjData* d, mjvScene* scn, const mjvO if (dim == 2) { for (int e=0; e < m->flex_elemnum[f]; e++) { const int* edata = m->flex_elem + m->flex_elemdataadr[f] + e*(dim+1); + const int* tdata = m->flex_elemtexcoord + m->flex_elemdataadr[f] + e*(dim+1); makeSmooth(face, normal, radius, flg_flat, vertnorm, vertxpos, nface, edata[0], edata[1], edata[2]); - copyTex(texdst, texsrc, nface, edata[0], edata[1], edata[2]); + copyTex(texdst, texsrc, nface, tdata[0], tdata[1], tdata[2]); nface++; makeSmooth(face, normal, -radius, flg_flat, vertnorm, vertxpos, nface, edata[0], edata[2], edata[1]); - copyTex(texdst, texsrc, nface, edata[0], edata[2], edata[1]); + copyTex(texdst, texsrc, nface, tdata[0], tdata[2], tdata[1]); nface++; } } else { diff --git a/src/user/user_flexcomp.cc b/src/user/user_flexcomp.cc index 93167cea45..fb2d7fba7c 100644 --- a/src/user/user_flexcomp.cc +++ b/src/user/user_flexcomp.cc @@ -413,6 +413,7 @@ bool mjCFlexcomp::Make(mjsBody* body, char* error, int error_sz) { mjs_setString(pf->name, name.c_str()); mjs_setInt(pf->elem, element.data(), element.size()); mjs_setFloat(pf->texcoord, texcoord.data(), texcoord.size()); + mjs_setInt(pf->facetexcoord, facetexcoord.data(), facetexcoord.size()); if (!centered) { mjs_setDouble(pf->vert, point.data(), point.size()); } @@ -1055,6 +1056,11 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, char* error, int error_sz) { point[i] = (double) mesh.Vert(i); } + if (mesh.HasTexcoord()) { + texcoord = mesh.Texcoord(); + facetexcoord = mesh.FaceTexcoord(); + } + // copy faces or create 3D mesh if (def.spec.flex->dim == 2) { element = mesh.Face(); diff --git a/src/user/user_flexcomp.h b/src/user/user_flexcomp.h index 063aaa2538..de25d0d884 100644 --- a/src/user/user_flexcomp.h +++ b/src/user/user_flexcomp.h @@ -106,6 +106,7 @@ class mjCFlexcomp { std::vector used; // is point used by any element (false: skip) std::vector element; // flex elements std::vector texcoord; // vertex texture coordinates + std::vector facetexcoord; // face texture coordinates (OBJ only) // plugin support std::string plugin_name; diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 83b8fb7bec..0132c60ee4 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -3184,6 +3184,7 @@ void mjCFlex::PointToLocal() { spec.vert = &spec_vert_; spec.node = &spec_node_; spec.texcoord = &spec_texcoord_; + spec.facetexcoord = &spec_facetexcoord_; spec.elem = &spec_elem_; spec.info = &info; material = nullptr; @@ -3192,6 +3193,7 @@ void mjCFlex::PointToLocal() { vert = nullptr; node = nullptr; texcoord = nullptr; + facetexcoord = nullptr; elem = nullptr; } @@ -3217,6 +3219,7 @@ void mjCFlex::CopyFromSpec() { vert_ = spec_vert_; node_ = spec_node_; texcoord_ = spec_texcoord_; + facetexcoord_ = spec_facetexcoord_; elem_ = spec_elem_; // clear precompiled asset. TODO: use asset cache @@ -3320,10 +3323,16 @@ void mjCFlex::Compile(const mjVFS* vfs) { } // check texcoord - if (!texcoord_.empty() && texcoord_.size()!=2*nvert) { + if (!texcoord_.empty() && texcoord_.size()!=2*nvert && facetexcoord_.empty()) { throw mjCError(this, "two texture coordinates per vertex expected"); } + // no facetexcoord: copy from faces + if (facetexcoord_.empty() && !texcoord_.empty()) { + facetexcoord_.assign(3*nelem, 0); + memcpy(facetexcoord_.data(), elem_.data(), 3*nelem*sizeof(int)); + } + // resolve material name mjCBase* pmat = model->FindObject(mjOBJ_MATERIAL, material_); if (pmat) { diff --git a/src/user/user_model.cc b/src/user/user_model.cc index d5382a931a..1bdbc7016c 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -2911,10 +2911,13 @@ void mjCModel::CopyObjects(mjModel* m) { } if (pfl->texcoord_.empty()) { m->flex_texcoordadr[i] = -1; + memcpy(m->flex_elemtexcoord + elemdata_adr, pfl->elem_.data(), pfl->elem_.size()*sizeof(int)); } else { m->flex_texcoordadr[i] = texcoord_adr; memcpy(m->flex_texcoord + 2*texcoord_adr, pfl->texcoord_.data(), pfl->texcoord_.size()*sizeof(float)); + memcpy(m->flex_elemtexcoord + elemdata_adr, pfl->facetexcoord_.data(), + pfl->facetexcoord_.size()*sizeof(int)); } m->flex_elemnum[i] = pfl->nelem; memcpy(m->flex_elem + elemdata_adr, pfl->elem_.data(), pfl->elem_.size()*sizeof(int)); diff --git a/src/user/user_objects.h b/src/user/user_objects.h index 2c5af46ab6..8b233a208f 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -776,6 +776,7 @@ class mjCFlex_ : public mjCBase { std::vector node_; // node positions std::vector elem_; // element vertex ids std::vector texcoord_; // vertex texture coordinates + std::vector facetexcoord_; // face texture coordinates (OBJ only) std::string material_; // name of material used for rendering std::string spec_material_; @@ -785,6 +786,7 @@ class mjCFlex_ : public mjCBase { std::vector spec_node_; std::vector spec_elem_; std::vector spec_texcoord_; + std::vector spec_facetexcoord_; }; class mjCFlex: public mjCFlex_, private mjsFlex { @@ -815,6 +817,7 @@ class mjCFlex: public mjCFlex_, private mjsFlex { const std::vector& get_elemaabb() const { return elemaabb_; } const std::vector& get_elem() const { return elem_; } const std::vector& get_texcoord() const { return texcoord_; } + const std::vector& get_facetexcoord() const { return facetexcoord_; } const std::vector& get_nodebody() const { return nodebody_; } bool HasTexcoord() const; // texcoord not null @@ -927,6 +930,8 @@ class mjCMesh: public mjCMesh_, private mjsMesh { float Vert(int i) const { return vert_[i]; } const std::vector& UserVert() const { return spec_vert_; } const std::vector& UserNormal() const { return spec_normal_; } + const std::vector& Texcoord() const { return texcoord_; } + const std::vector& FaceTexcoord() const { return facetexcoord_; } const std::vector& UserTexcoord() const { return spec_texcoord_; } const std::vector& Face() const { return face_; } const std::vector& UserFace() const { return spec_face_; } diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 1925ebef49..00f806498d 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -324,8 +324,8 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"deformable", "*", "0"}, {"<"}, - {"flex", "*", "12", "name", "group", "dim", "radius", "material", - "rgba", "flatskin", "body", "vertex", "element", "texcoord", "node"}, + {"flex", "*", "13", "name", "group", "dim", "radius", "material", + "rgba", "flatskin", "body", "vertex", "element", "texcoord", "facetexcoord", "node"}, {"<"}, {"contact", "?", "13", "contype", "conaffinity", "condim", "priority", "friction", "solmix", "solref", "solimp", "margin", "gap", @@ -1354,6 +1354,10 @@ void mjXReader::OneFlex(XMLElement* elem, mjsFlex* flex) { if (texcoord.has_value()) { mjs_setFloat(flex->texcoord, texcoord->data(), texcoord->size()); } + auto facetexcoord = ReadAttrVec(elem, "facetexcoord"); + if (facetexcoord.has_value()) { + mjs_setInt(flex->facetexcoord, facetexcoord->data(), facetexcoord->size()); + } // contact subelement XMLElement* cont = FirstChildElement(elem, "contact"); diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 1bbe7a54d5..1889e3fd1a 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -158,6 +158,10 @@ void mjXWriter::OneFlex(XMLElement* elem, const mjCFlex* flex) { text = VectorToString(flex->get_texcoord()); WriteAttrTxt(elem, "texcoord", text); } + if (!flex->get_facetexcoord().empty()) { + text = VectorToString(flex->get_facetexcoord()); + WriteAttrTxt(elem, "facetexcoord", text); + } if (!flex->get_nodebody().empty()) { text = VectorToString(flex->get_nodebody()); WriteAttrTxt(elem, "node", text); diff --git a/test/user/testdata/textured_torus_flex.xml b/test/user/testdata/textured_torus_flex.xml new file mode 100644 index 0000000000..df07435ca1 --- /dev/null +++ b/test/user/testdata/textured_torus_flex.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/test/user/user_flex_test.cc b/test/user/user_flex_test.cc index 7ee2fcaee0..67dfb8f603 100644 --- a/test/user/user_flex_test.cc +++ b/test/user/user_flex_test.cc @@ -435,6 +435,22 @@ TEST_F(UserFlexTest, StiffnessMatrix) { mj_deleteModel(m); } +TEST_F(UserFlexTest, LoadTexture) { + const std::string xml_path = + GetTestDataFilePath("user/testdata/textured_torus_flex.xml"); + std::array error; + mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + ASSERT_THAT(m, NotNull()) << error.data(); + EXPECT_THAT(m->nflextexcoord, 637); + EXPECT_THAT(m->flex_elemtexcoord[0], 0); + EXPECT_THAT(m->flex_elemtexcoord[1], 1); + EXPECT_THAT(m->flex_elemtexcoord[2], 2); + EXPECT_THAT(m->flex_elemtexcoord[3], 0); + EXPECT_THAT(m->flex_elemtexcoord[4], 2); + EXPECT_THAT(m->flex_elemtexcoord[5], 3); + mj_deleteModel(m); +} + TEST_F(UserFlexTest, LoadMSHBinary_41_Success) { const std::string xml_path = GetTestDataFilePath("user/testdata/cube_41_binary_vol_gmshApp.xml"); diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 2e7d92145a..b152a71153 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -5437,6 +5437,7 @@ public unsafe struct mjModel_ { public int* flex_vertbodyid; public int* flex_edge; public int* flex_elem; + public int* flex_elemtexcoord; public int* flex_elemedge; public int* flex_elemlayer; public int* flex_shell; @@ -6324,6 +6325,7 @@ public unsafe struct model { public int* flex_vertadr; public int* flex_vertnum; public int* flex_elem; + public int* flex_elemtexcoord; public int* flex_elemlayer; public int* flex_elemadr; public int* flex_elemnum;