From 2d0995b47c18a54163a65dc1d98913919820e479 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Fri, 1 Jul 2022 04:56:46 -0700 Subject: [PATCH] Add visualisation groups to skins. PiperOrigin-RevId: 458441645 Change-Id: I14324e8efda5142b2718a5cc9215228c7556dbf7 --- doc/APIreference.rst | 15 ++- include/mujoco/mjmodel.h | 1 + include/mujoco/mjvisualize.h | 19 +-- include/mujoco/mjxmacro.h | 1 + python/mujoco/structs.cc | 2 + python/mujoco/structs.h | 1 + sample/simulate.cc | 7 + src/engine/engine_vis_init.c | 1 + src/engine/engine_vis_visualize.c | 201 +++++++++++++++-------------- src/engine/engine_vis_visualize.h | 3 + src/user/user_composite.cc | 4 + src/user/user_composite.h | 1 + src/user/user_mesh.cc | 2 +- src/user/user_model.cc | 1 + src/user/user_objects.h | 1 + src/xml/xml_native_reader.cc | 15 ++- src/xml/xml_native_writer.cc | 1 + test/engine/testdata/skingroup.xml | 44 +++++++ test/xml/xml_native_reader_test.cc | 51 ++++++++ 19 files changed, 256 insertions(+), 115 deletions(-) create mode 100644 test/engine/testdata/skingroup.xml diff --git a/doc/APIreference.rst b/doc/APIreference.rst index 6c09693e..f64fa973 100644 --- a/doc/APIreference.rst +++ b/doc/APIreference.rst @@ -2222,16 +2222,17 @@ mjvOption .. code-block:: C - struct _mjvOption // abstract visualization options + struct _mjvOption // abstract visualization options { - int label; // what objects to label (mjtLabel) - int frame; // which frame to show (mjtFrame) - mjtByte geomgroup[mjNGROUP]; // geom visualization by group - mjtByte sitegroup[mjNGROUP]; // site visualization by group - mjtByte jointgroup[mjNGROUP]; // joint visualization by group + int label; // what objects to label (mjtLabel) + int frame; // which frame to show (mjtFrame) + mjtByte geomgroup[mjNGROUP]; // geom visualization by group + mjtByte sitegroup[mjNGROUP]; // site visualization by group + mjtByte jointgroup[mjNGROUP]; // joint visualization by group mjtByte tendongroup[mjNGROUP]; // tendon visualization by group mjtByte actuatorgroup[mjNGROUP]; // actuator visualization by group - mjtByte flags[mjNVISFLAG]; // visualization flags (indexed by mjtVisFlag) + mjtByte skingroup[mjNGROUP]; // skin visualization by group + mjtByte flags[mjNVISFLAG]; // visualization flags (indexed by mjtVisFlag) }; typedef struct _mjvOption mjvOption; diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 336b03ca..b93fdd57 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -732,6 +732,7 @@ struct mjModel_ { // skins int* skin_matid; // skin material id; -1: none (nskin x 1) + int* skin_group; // group for visibility (nskin x 1) float* skin_rgba; // skin rgba (nskin x 4) float* skin_inflate; // inflate skin in normal direction (nskin x 1) int* skin_vertadr; // first vertex address (nskin x 1) diff --git a/include/mujoco/mjvisualize.h b/include/mujoco/mjvisualize.h index ec8870ed..4237165b 100644 --- a/include/mujoco/mjvisualize.h +++ b/include/mujoco/mjvisualize.h @@ -18,7 +18,7 @@ #include #include -#define mjNGROUP 6 // number of geom, site, joint groups with visflags +#define mjNGROUP 6 // number of geom, site, joint, skin groups with visflags #define mjMAXLIGHT 100 // maximum number of lights in a scene #define mjMAXOVERLAY 500 // maximum number of characters in overlay text #define mjMAXLINE 100 // maximum number of lines per plot @@ -250,15 +250,16 @@ typedef struct mjvLight_ mjvLight; //---------------------------------- mjvOption ----------------------------------------------------- -struct mjvOption_ { // abstract visualization options - int label; // what objects to label (mjtLabel) - int frame; // which frame to show (mjtFrame) - mjtByte geomgroup[mjNGROUP]; // geom visualization by group - mjtByte sitegroup[mjNGROUP]; // site visualization by group - mjtByte jointgroup[mjNGROUP]; // joint visualization by group - mjtByte tendongroup[mjNGROUP]; // tendon visualization by group +struct mjvOption_ { // abstract visualization options + int label; // what objects to label (mjtLabel) + int frame; // which frame to show (mjtFrame) + mjtByte geomgroup[mjNGROUP]; // geom visualization by group + mjtByte sitegroup[mjNGROUP]; // site visualization by group + mjtByte jointgroup[mjNGROUP]; // joint visualization by group + mjtByte tendongroup[mjNGROUP]; // tendon visualization by group mjtByte actuatorgroup[mjNGROUP]; // actuator visualization by group - mjtByte flags[mjNVISFLAG]; // visualization flags (indexed by mjtVisFlag) + mjtByte skingroup[mjNGROUP]; // skin visualization by group + mjtByte flags[mjNVISFLAG]; // visualization flags (indexed by mjtVisFlag) }; typedef struct mjvOption_ mjvOption; diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index c4091869..b275573d 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -267,6 +267,7 @@ X( int, mesh_face, nmeshface, 3 ) \ X( int, mesh_graph, nmeshgraph, 1 ) \ X( int, skin_matid, nskin, 1 ) \ + X( int, skin_group, nskin, 1 ) \ X( float, skin_rgba, nskin, 4 ) \ X( float, skin_inflate, nskin, 1 ) \ X( int, skin_vertadr, nskin, 1 ) \ diff --git a/python/mujoco/structs.cc b/python/mujoco/structs.cc index 03f0ee26..89660f49 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -1053,6 +1053,7 @@ MjvOptionWrapper::MjWrapper() X(jointgroup), X(tendongroup), X(actuatorgroup), + X(skingroup), X(flags) {} #undef X @@ -2037,6 +2038,7 @@ This is useful for example when the MJB is not available as a file on disk.)")); X(jointgroup); X(tendongroup); X(actuatorgroup); + X(skingroup); X(flags); #undef X diff --git a/python/mujoco/structs.h b/python/mujoco/structs.h index a7c5be42..aa2e8f1c 100644 --- a/python/mujoco/structs.h +++ b/python/mujoco/structs.h @@ -700,6 +700,7 @@ class MjWrapper : public WrapperBase { X(jointgroup); X(tendongroup); X(actuatorgroup); + X(skingroup); X(flags); #undef X }; diff --git a/sample/simulate.cc b/sample/simulate.cc index 5a54ca4d..633582b6 100644 --- a/sample/simulate.cc +++ b/sample/simulate.cc @@ -877,6 +877,13 @@ void makegroup(int oldstate) { {mjITEM_CHECKBYTE, "Actuator 3", 2, vopt.actuatorgroup+3, ""}, {mjITEM_CHECKBYTE, "Actuator 4", 2, vopt.actuatorgroup+4, ""}, {mjITEM_CHECKBYTE, "Actuator 5", 2, vopt.actuatorgroup+5, ""}, + {mjITEM_SEPARATOR, "Skin groups", 1}, + {mjITEM_CHECKBYTE, "Skin 0", 2, vopt.skingroup, ""}, + {mjITEM_CHECKBYTE, "Skin 1", 2, vopt.skingroup+1, ""}, + {mjITEM_CHECKBYTE, "Skin 2", 2, vopt.skingroup+2, ""}, + {mjITEM_CHECKBYTE, "Skin 3", 2, vopt.skingroup+3, ""}, + {mjITEM_CHECKBYTE, "Skin 4", 2, vopt.skingroup+4, ""}, + {mjITEM_CHECKBYTE, "Skin 5", 2, vopt.skingroup+5, ""}, {mjITEM_END} }; diff --git a/src/engine/engine_vis_init.c b/src/engine/engine_vis_init.c index dae95f25..d83caeba 100644 --- a/src/engine/engine_vis_init.c +++ b/src/engine/engine_vis_init.c @@ -217,6 +217,7 @@ void mjv_defaultOption(mjvOption* vopt) { vopt->jointgroup[i] = state; vopt->tendongroup[i] = state; vopt->actuatorgroup[i] = state; + vopt->skingroup[i] = state; } for (int i=0; inskin; i++) { // get info @@ -1782,111 +1791,113 @@ void mjv_updateSkin(const mjModel* m, mjData* d, mjvScene* scn) { memset(scn->skinvert + 3*vertadr, 0, 3*vertnum*sizeof(float)); memset(scn->skinnormal + 3*vertadr, 0, 3*vertnum*sizeof(float)); - // accumulate positions from all bones - for (int j=m->skin_boneadr[i]; - jskin_boneadr[i]+m->skin_bonenum[i]; - j++) { - // get bind pose - mjtNum bindpos[3] = { - (mjtNum) m->skin_bonebindpos[3*j], - (mjtNum) m->skin_bonebindpos[3*j+1], - (mjtNum) m->skin_bonebindpos[3*j+2] - }; - mjtNum bindquat[4] = { - (mjtNum) m->skin_bonebindquat[4*j], - (mjtNum) m->skin_bonebindquat[4*j+1], - (mjtNum) m->skin_bonebindquat[4*j+2], - (mjtNum) m->skin_bonebindquat[4*j+3] - }; - - // compute rotation - int bodyid = m->skin_bonebodyid[j]; - mjtNum quat[4], quatneg[4], rotate[9]; - mju_negQuat(quatneg, bindquat); - mju_mulQuat(quat, d->xquat+4*bodyid, quatneg); - mju_quat2Mat(rotate, quat); - - // compute translation - mjtNum translate[3]; - mju_rotVecMat(translate, bindpos, rotate); - mju_sub3(translate, d->xpos+3*bodyid, translate); - - // process all bone vertices - for (int k=m->skin_bonevertadr[j]; - kskin_bonevertadr[j]+m->skin_bonevertnum[j]; - k++) { - // vertex id and weight - int vid = m->skin_bonevertid[k]; - float vweight = m->skin_bonevertweight[k]; - - // get original position - mjtNum pos[3] = { - (mjtNum) m->skin_vert[3*(vertadr+vid)], - (mjtNum) m->skin_vert[3*(vertadr+vid)+1], - (mjtNum) m->skin_vert[3*(vertadr+vid)+2], + if (opt->skingroup[m->skin_group[i]]) { + // accumulate positions from all bones + for (int j=m->skin_boneadr[i]; + jskin_boneadr[i]+m->skin_bonenum[i]; + j++) { + // get bind pose + mjtNum bindpos[3] = { + (mjtNum) m->skin_bonebindpos[3*j], + (mjtNum) m->skin_bonebindpos[3*j+1], + (mjtNum) m->skin_bonebindpos[3*j+2] + }; + mjtNum bindquat[4] = { + (mjtNum) m->skin_bonebindquat[4*j], + (mjtNum) m->skin_bonebindquat[4*j+1], + (mjtNum) m->skin_bonebindquat[4*j+2], + (mjtNum) m->skin_bonebindquat[4*j+3] }; - // transform - mjtNum pos1[3]; - mju_rotVecMat(pos1, pos, rotate); - mju_addTo3(pos1, translate); + // compute rotation + int bodyid = m->skin_bonebodyid[j]; + mjtNum quat[4], quatneg[4], rotate[9]; + mju_negQuat(quatneg, bindquat); + mju_mulQuat(quat, d->xquat+4*bodyid, quatneg); + mju_quat2Mat(rotate, quat); - // accumulate position - scn->skinvert[3*(vertadr+vid)] += vweight*(float)pos1[0]; - scn->skinvert[3*(vertadr+vid)+1] += vweight*(float)pos1[1]; - scn->skinvert[3*(vertadr+vid)+2] += vweight*(float)pos1[2]; - } - } + // compute translation + mjtNum translate[3]; + mju_rotVecMat(translate, bindpos, rotate); + mju_sub3(translate, d->xpos+3*bodyid, translate); - // compute vertex normals from face normals - for (int k=faceadr; kskin_face[3*k], - m->skin_face[3*k+1], - m->skin_face[3*k+2] - }; + // process all bone vertices + for (int k=m->skin_bonevertadr[j]; + kskin_bonevertadr[j]+m->skin_bonevertnum[j]; + k++) { + // vertex id and weight + int vid = m->skin_bonevertid[k]; + float vweight = m->skin_bonevertweight[k]; - // get triangle edges - mjtNum vec01[3], vec02[3]; - for (int r=0; r<3; r++) { - vec01[r] = scn->skinvert[3*(vertadr+vid[1])+r] - scn->skinvert[3*(vertadr+vid[0])+r]; - vec02[r] = scn->skinvert[3*(vertadr+vid[2])+r] - scn->skinvert[3*(vertadr+vid[0])+r]; - } + // get original position + mjtNum pos[3] = { + (mjtNum) m->skin_vert[3*(vertadr+vid)], + (mjtNum) m->skin_vert[3*(vertadr+vid)+1], + (mjtNum) m->skin_vert[3*(vertadr+vid)+2], + }; - // compute face normal - mjtNum nrm[3]; - mju_cross(nrm, vec01, vec02); + // transform + mjtNum pos1[3]; + mju_rotVecMat(pos1, pos, rotate); + mju_addTo3(pos1, translate); - // add normal to each vertex with weight = area - for (int r=0; r<3; r++) { - for (int t=0; t<3; t++) { - scn->skinnormal[3*(vertadr+vid[r])+t] += nrm[t]; + // accumulate position + scn->skinvert[3*(vertadr+vid)] += vweight*(float)pos1[0]; + scn->skinvert[3*(vertadr+vid)+1] += vweight*(float)pos1[1]; + scn->skinvert[3*(vertadr+vid)+2] += vweight*(float)pos1[2]; } } - } - // normalize normals - for (int k=vertadr; kskinnormal[3*k]*scn->skinnormal[3*k] + - scn->skinnormal[3*k+1]*scn->skinnormal[3*k+1] + - scn->skinnormal[3*k+2]*scn->skinnormal[3*k+2] - ); - float scl = 1/mjMAX(mjMINVAL, s); + // compute vertex normals from face normals + for (int k=faceadr; kskin_face[3*k], + m->skin_face[3*k+1], + m->skin_face[3*k+2] + }; - scn->skinnormal[3*k] *= scl; - scn->skinnormal[3*k+1] *= scl; - scn->skinnormal[3*k+2] *= scl; - } + // get triangle edges + mjtNum vec01[3], vec02[3]; + for (int r=0; r<3; r++) { + vec01[r] = scn->skinvert[3*(vertadr+vid[1])+r] - scn->skinvert[3*(vertadr+vid[0])+r]; + vec02[r] = scn->skinvert[3*(vertadr+vid[2])+r] - scn->skinvert[3*(vertadr+vid[0])+r]; + } - // inflate - if (m->skin_inflate[i]) { - float inflate = m->skin_inflate[i]; + // compute face normal + mjtNum nrm[3]; + mju_cross(nrm, vec01, vec02); + + // add normal to each vertex with weight = area + for (int r=0; r<3; r++) { + for (int t=0; t<3; t++) { + scn->skinnormal[3*(vertadr+vid[r])+t] += nrm[t]; + } + } + } + + // normalize normals for (int k=vertadr; kskinvert[3*k] += inflate*scn->skinnormal[3*k]; - scn->skinvert[3*k+1] += inflate*scn->skinnormal[3*k+1]; - scn->skinvert[3*k+2] += inflate*scn->skinnormal[3*k+2]; + float s = sqrtf( + scn->skinnormal[3*k]*scn->skinnormal[3*k] + + scn->skinnormal[3*k+1]*scn->skinnormal[3*k+1] + + scn->skinnormal[3*k+2]*scn->skinnormal[3*k+2] + ); + float scl = 1/mjMAX(mjMINVAL, s); + + scn->skinnormal[3*k] *= scl; + scn->skinnormal[3*k+1] *= scl; + scn->skinnormal[3*k+2] *= scl; + } + + // inflate + if (m->skin_inflate[i]) { + float inflate = m->skin_inflate[i]; + for (int k=vertadr; kskinvert[3*k] += inflate*scn->skinnormal[3*k]; + scn->skinvert[3*k+1] += inflate*scn->skinnormal[3*k+1]; + scn->skinvert[3*k+2] += inflate*scn->skinnormal[3*k+2]; + } } } } @@ -1909,6 +1920,6 @@ void mjv_updateScene(const mjModel* m, mjData* d, const mjvOption* opt, // update skins if (opt->flags[mjVIS_SKIN]) { - mjv_updateSkin(m, d, scn); + mjv_updateActiveSkin(m, d, scn, opt); } } diff --git a/src/engine/engine_vis_visualize.h b/src/engine/engine_vis_visualize.h index fa34710c..97645a4e 100644 --- a/src/engine/engine_vis_visualize.h +++ b/src/engine/engine_vis_visualize.h @@ -51,6 +51,9 @@ MJAPI void mjv_updateCamera(const mjModel* m, mjData* d, mjvCamera* cam, mjvScen // update skins only MJAPI void mjv_updateSkin(const mjModel* m, mjData* d, mjvScene* scn); +// update visible skins only +MJAPI void mjv_updateActiveSkin(const mjModel* m, mjData* d, mjvScene* scn, const mjvOption* opt); + #ifdef __cplusplus } #endif diff --git a/src/user/user_composite.cc b/src/user/user_composite.cc index 11c362b0..1648ac1c 100644 --- a/src/user/user_composite.cc +++ b/src/user/user_composite.cc @@ -67,6 +67,7 @@ mjCComposite::mjCComposite(void) { mjuu_setvec(skinrgba, 1, 1, 1, 1); skininflate = 0; skinsubgrid = 0; + skingroup = 0; // clear add flags for (int i=0; imaterial = skinmaterial; mjuu_copyvec(skin->rgba, skinrgba, 4); skin->inflate = skininflate; + skin->group = skingroup; // populate mesh: two sides for (int i=0; i<2; i++) { @@ -1473,6 +1475,7 @@ void mjCComposite::MakeSkin2Subgrid(mjCModel* model) { skin->material = skinmaterial; mjuu_copyvec(skin->rgba, skinrgba, 4); skin->inflate = skininflate; + skin->group = skingroup; // populate mesh: two sides mjtNum S = spacing/(1+skinsubgrid); @@ -1643,6 +1646,7 @@ void mjCComposite::MakeSkin3(mjCModel* model) { skin->material = skinmaterial; mjuu_copyvec(skin->rgba, skinrgba, 4); skin->inflate = skininflate; + skin->group = skingroup; // box if (type==mjCOMPTYPE_BOX) { diff --git a/src/user/user_composite.h b/src/user/user_composite.h index 2d5af0a2..36fcc5ad 100644 --- a/src/user/user_composite.h +++ b/src/user/user_composite.h @@ -94,6 +94,7 @@ class mjCComposite { float skinrgba[4]; // skin rgba float skininflate; // inflate skin int skinsubgrid; // number of skin subgrid points; 0: none (2D only) + int skingroup; // skin group of the composite object // element options bool add[mjNCOMPKINDS]; // add element diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 8d99c79f..15b0e220 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -1408,6 +1408,7 @@ mjCSkin::mjCSkin(mjCModel* _model) { rgba[0] = rgba[1] = rgba[2] = 0.5f; rgba[3] = 1.0f; inflate = 0; + group = 0; vert.clear(); texcoord.clear(); @@ -1530,7 +1531,6 @@ void mjCSkin::Compile(const mjVFS* vfs) { throw mjCError(this, "unkown material '%s' in skin", material.c_str()); } - // set total vertex weights to 0 vector vw; size_t nvert = vert.size()/3; diff --git a/src/user/user_model.cc b/src/user/user_model.cc index fbb0e82e..6611b362 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -1628,6 +1628,7 @@ void mjCModel::CopyObjects(mjModel* m) { // set fields m->skin_matid[i] = psk->matid; copyvec(m->skin_rgba+4*i, psk->rgba, 4); + m->skin_group[i] = psk->group; m->skin_inflate[i] = psk->inflate; m->skin_vertadr[i] = vert_adr; m->skin_vertnum[i] = psk->vert.size()/3; diff --git a/src/user/user_objects.h b/src/user/user_objects.h index 413ecbc5..9a3dd6d6 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -504,6 +504,7 @@ class mjCSkin: public mjCBase { std::string material; // name of material used for rendering float rgba[4]; // rgba when material is omitted float inflate; // inflate in normal direction + int group; // group for visualization // mesh std::vector vert; // vertex positions diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 0600512c..83f3cce0 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -22,6 +22,7 @@ #include #include +#include #include "engine/engine_macro.h" #include "engine/engine_util_errmem.h" #include "engine/engine_util_misc.h" @@ -165,8 +166,8 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = { {"hfield", "*", "5", "name", "file", "nrow", "ncol", "size"}, {"mesh", "*", "11", "name", "class", "file", "vertex", "normal", "texcoord", "face", "refpos", "refquat", "scale", "smoothnormal"}, - {"skin", "*", "8", "name", "file", "material", "rgba", "inflate", - "vertex", "texcoord", "face"}, + {"skin", "*", "9", "name", "file", "material", "rgba", "inflate", + "vertex", "texcoord", "face", "group"}, {"<"}, {"bone", "*", "5", "body", "bindpos", "bindquat", "vertid", "vertweight"}, {">"}, @@ -210,7 +211,7 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = { "limited", "range", "margin", "solreflimit", "solimplimit", "frictionloss", "solreffriction", "solimpfriction", "material", "rgba", "width"}, - {"skin", "?", "5", "texcoord", "material", "rgba", "inflate", "subgrid"}, + {"skin", "?", "6", "texcoord", "material", "group", "rgba", "inflate", "subgrid"}, {"geom", "?", "17", "type", "contype", "conaffinity", "condim", "group", "priority", "size", "material", "rgba", "friction", "mass", "density", "solmix", "solref", "solimp", "margin", "gap"}, @@ -983,6 +984,10 @@ void mjXReader::OneSkin(XMLElement* elem, mjCSkin* pskin) { ReadAttrTxt(elem, "name", pskin->name); ReadAttrTxt(elem, "file", pskin->file); ReadAttrTxt(elem, "material", pskin->material); + ReadAttrInt(elem, "group", &pskin->group); + if (pskin->group<0 || pskin->group>=mjNGROUP) { + throw mjXError(elem, "skin group must be between 0 and 5"); + } ReadAttr(elem, "rgba", 4, pskin->rgba, text); ReadAttr(elem, "inflate", 1, &pskin->inflate, text); @@ -1617,6 +1622,10 @@ void mjXReader::OneComposite(XMLElement* elem, mjCBody* pbody, mjCDef* def) { ReadAttr(eskin, "rgba", 4, comp.skinrgba, text); ReadAttr(eskin, "inflate", 1, &comp.skininflate, text); ReadAttrInt(eskin, "subgrid", &comp.skinsubgrid); + ReadAttrInt(eskin, "group", &comp.skingroup, 0); + if (comp.skingroup<0 || comp.skingroup>=mjNGROUP) { + throw mjXError(eskin, "skin group must be between 0 and 5"); + } } // set type-specific defaults diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 9d0926c2..6ffb3230 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -125,6 +125,7 @@ void mjXWriter::OneSkin(XMLElement* elem, mjCSkin* pskin) { WriteAttrTxt(elem, "name", pskin->name); WriteAttrTxt(elem, "file", pskin->file); WriteAttrTxt(elem, "material", pskin->material); + WriteAttrInt(elem, "group", pskin->group, 0); WriteAttr(elem, "rgba", 4, pskin->rgba, mydef.geom.rgba); WriteAttr(elem, "inflate", 1, &pskin->inflate, &zero); diff --git a/test/engine/testdata/skingroup.xml b/test/engine/testdata/skingroup.xml new file mode 100644 index 00000000..93053736 --- /dev/null +++ b/test/engine/testdata/skingroup.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index 0d6e8121..b9f0090e 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -379,6 +379,57 @@ TEST_F(UserDataTest, PositiveControlRange) { EXPECT_THAT(error.data(), HasSubstr("control range cannot be negative")); } +TEST_F(UserDataTest, ReadsSkinGroups) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + + + + )"; + std::array error; + mjModel* model = LoadModelFromString(xml, error.data(), error.size()); + ASSERT_THAT(model, NotNull()); + int geomid1 = mj_name2id(model, mjOBJ_GEOM, "B0G0_0_0"); + int geomid2 = mj_name2id(model, mjOBJ_GEOM, "B1G0_0_0"); + EXPECT_THAT(model->geom_group[geomid1], 2); + EXPECT_THAT(model->skin_group[0], 4); + EXPECT_THAT(model->geom_group[geomid2], 4); + EXPECT_THAT(model->skin_group[1], 2); + mj_deleteModel(model); +} + +TEST_F(UserDataTest, InvalidSkinGroup) { + static constexpr char xml[] = R"( + + + + + + + + + + + )"; + std::array error; + mjModel* model = LoadModelFromString(xml, error.data(), error.size()); + ASSERT_THAT(model, IsNull()); + EXPECT_THAT(error.data(), HasSubstr("skin group must be between 0 and 5\nElement 'skin', line 7")); + mj_deleteModel(model); +} + // ------------- test relative frame sensor parsing ---------------------------- using RelativeFrameSensorParsingTest = MujocoTest;