diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 523b0d43..69c8751a 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -3566,9 +3566,10 @@ saving the XML: :at:`texcoord`: :at-val:`real(2*npoint), optional` Texture coordinates of each point, passed through to the automatically-generated flex. Note that flexcomp does not - generate texture coordinates automatically, except for 2D grids. For all other types, the user can specify explicit - texture coordinates here, even if the points themselves were generated automatically. This requires understanding of - the layout of the automatically-generated points and how they correspond to the texture referenced by the material. + generate texture coordinates automatically, except for 2D grids, box, cylinder and ellipsoid. For all other types, + the user can specify explicit texture coordinates here, even if the points themselves were generated automatically. + This requires understanding of the layout of the automatically-generated points and how they correspond to the + texture referenced by the material. .. _body-flexcomp-mass: diff --git a/doc/changelog.rst b/doc/changelog.rst index 8c2ccf60..a414cd39 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -17,6 +17,8 @@ General - Sorting now uses a faster, native sort function (fixes :github:issue:`1638`). - The PBR texture layers introduced in 3.2.1 were refactored from seperate sub-elements to a single :ref:`layer` sub-element. +- The composite types box, cylinder, and sphere have been removed. Users should instead use the equivalent types + available in :ref:`flexcomp`. MJX ^^^ @@ -33,6 +35,7 @@ Bug fixes several places leading to incorrect computations of constraint inertia, readings of affected force/torque sensors and runtime enabling/disabling of such constraints. - Fixed a bug in slider-crank :ref:`transmission`. The bug was introduced in 3.0.0. +- Fixed a bug in flex texture coordinates that prevented the correct allocation of textures in mjModel. Documentation diff --git a/doc/modeling.rst b/doc/modeling.rst index 066c5b95..9f3bc202 100644 --- a/doc/modeling.rst +++ b/doc/modeling.rst @@ -1280,62 +1280,11 @@ scenario (e.g. a stretched rubber band). The cloth is deprecated. It is recommended to use 2D flex :ref:`deformable objects ` for simulating thin elastic structures. -**Box**. +**Box, cylinder and ellipsoid**. -|image14| |image15| -.. code-block:: xml - - - - - - - - - -The box type, as well as the cylinder and ellipsoid types below, are used to model soft 3D objects. The element bodies -form a grid along the outer shell, thus the number of element bodies scales with the square of the linear dimension. -This is much more efficient than simulating a 3D grid. The parent body within which :el:`composite` appears is at the -center of the soft object. All element bodies are children of the parent. Each element body has a single sliding joint -pointing away from the parent. These joints allow the surface of the soft object to compress and expand at any point. -The joints are equality-constrained to their initial position, so as to maintain the shape. In addition each joint is -equality-constrained to its neighbor joints, so that when the soft objects deforms, the deformation is smooth. -Finally, there is a tendon equality constraint specifying that the sum of all joints should remain constant. This -attempts to preserve the volume of the soft object approximately. If the object is squeezed from all sides it will -compress and the volume will decrease, but otherwise some element bodies will stick out to compensate for squeezing -elsewhere. The plot on the left shows this effect; we are using the capsule probe to compress one corner, and the -opposite sides of the cube expand a bit, while the deformations remain smooth. The :at:`count` attribute determines -the number of element bodies in each dimension, so if the counts are different the resulting object will be a -rectangular box and not a cube. The geoms attached to the element bodies can be spheres, capsules or ellipsoids. -Spheres are faster for collision detection, but they result in a thin shell, allowing other bodies to "get under the -skin" of the soft object. When capsules or ellipsoids are used, they are automatically oriented so that the long axis -points to the outside, thus creating a thicker shell which is harder to penetrate. - -**Cylinder and ellipsoid**. - -|image16| |image17| - -.. code-block:: xml - - - - - - - - - -Cylinders and ellipsoids are created in the same way as boxes. The only difference is that the reference positions of -the element bodies (relative to the parent) are projected on a cylinder or ellipsoid, with size implied by the -:at:`count` attribute. The automatic skin generator is aware of the smooth surfaces, and adjusts the skin normals -accordingly. In the plots we have used the capsule probe to press on each body, then paused the simulation and moved the -probe away (which is possible because the probe is a mocap body which can move independent of the physics). In this way -we can see the indentation made by the probe, and the resulting deformation in the rest of the body. By changing the -solref and solimp attributes of the equality constraints that hold the soft object together, one can adjust the behavior -of the system making it softer or harder, damped or springy, etc. Note that box, cylinder and ellipsoid objects do not -involve long kinematic chains, and can be simulated at large timesteps -- similar to particle and grid, and unlike rope -and cloth. +The box type, as well as the cylinder and ellipsoid types, are now deprecated in favor of 3D flex :ref:`deformable +objects ``. element. .. _CDeformable: diff --git a/model/composite/softbox.xml b/model/composite/softbox.xml index ab129dfd..55d708de 100644 --- a/model/composite/softbox.xml +++ b/model/composite/softbox.xml @@ -14,10 +14,6 @@ --> - diff --git a/src/user/user_composite.cc b/src/user/user_composite.cc index 1330d886..73c1227d 100644 --- a/src/user/user_composite.cc +++ b/src/user/user_composite.cc @@ -192,25 +192,6 @@ void mjCComposite::SetDefault(void) { case mjCOMPTYPE_CLOTH: // cloth break; - case mjCOMPTYPE_BOX: // 3D - case mjCOMPTYPE_CYLINDER: - case mjCOMPTYPE_ELLIPSOID: - - // no self-collisions - def[0].spec.geom->contype = 0; - - // soft smoothing - AdjustSoft(solrefsmooth, solimpsmooth, 1); - - // soft fix everywhere - for (int i=0; isolref, def[i].spec.equality->solimp, 1); - } - - // hard main tendon fix - AdjustSoft(def[mjCOMPKIND_TENDON].spec.equality->solref, - def[mjCOMPKIND_TENDON].spec.equality->solimp, 0); - break; default: // SHOULD NOT OCCUR mju_error("Invalid composite type: %d", type); @@ -347,11 +328,6 @@ bool mjCComposite::Make(mjSpec* spec, mjsBody* body, char* error, int error_sz) "\"shell\" instead.", error_sz); - case mjCOMPTYPE_BOX: - case mjCOMPTYPE_CYLINDER: - case mjCOMPTYPE_ELLIPSOID: - return MakeBox(model, body, error, error_sz); - default: return comperr(error, "Unknown shape in composite", error_sz); } @@ -919,166 +895,6 @@ mjsBody* mjCComposite::AddRopeBody(mjCModel* model, mjsBody* body, int ix, int i -// project from box to other shape -void mjCComposite::BoxProject(double* pos) { - // determine sizes - double size[3] = { - 0.5*spacing*(count[0]-1), - 0.5*spacing*(count[1]-1), - 0.5*spacing*(count[2]-1) - }; - - // box - if (type==mjCOMPTYPE_BOX) { - pos[0] *= size[0]; - pos[1] *= size[1]; - pos[2] *= size[2]; - } - - // cylinder - else if (type==mjCOMPTYPE_CYLINDER) { - double L0 = std::max(std::abs(pos[0]), std::abs(pos[1])); - mjuu_normvec(pos, 2); - pos[0] *= size[0]*L0; - pos[1] *= size[1]*L0; - pos[2] *= size[2]; - } - - // ellipsoid - else if (type==mjCOMPTYPE_ELLIPSOID) { - mjuu_normvec(pos, 3); - pos[0] *= size[0]; - pos[1] *= size[1]; - pos[2] *= size[2]; - } -} - - - -// make 3d box, ellipsoid or cylinder -bool mjCComposite::MakeBox(mjCModel* model, mjsBody* body, char* error, int error_sz) { - char txt[100]; - - // check dim - if (dim!=3) { - return comperr(error, "Box and ellipsoid must be three-dimensional", error_sz); - } - - // center geom: two times bigger - mjsGeom* geom = mjs_addGeom(body, &def[0].spec); - mjs_setDefault(geom->element, mjs_getDefault(body->element)); - geom->type = mjGEOM_SPHERE; - mju::sprintf_arr(txt, "%sGcenter", prefix.c_str()); - mjs_setString(geom->name, txt); - mjuu_setvec(geom->pos, 0, 0, 0); - geom->size[0] *= 2; - geom->size[1] = 0; - geom->size[2] = 0; - - // fixed tendon for all joints - mjCTendon* ten = model->AddTendon(def + mjCOMPKIND_TENDON); - ten->classname = model->Default()->name; - mju::sprintf_arr(txt, "%sT", prefix.c_str()); - ten->name = txt; - - // create bodies, geoms and joints: outside shell only - for (int ix=0; ixname, txt); - - // set body position (+/- 1) - b->pos[0] = 2.0*ix/(count[0]-1) - 1; - b->pos[1] = 2.0*iy/(count[1]-1) - 1; - b->pos[2] = 2.0*iz/(count[2]-1) - 1; - - // reshape - BoxProject(b->pos); - - // reorient body - b->alt.type = mjORIENTATION_ZAXIS; - mjuu_copyvec(b->alt.zaxis, b->pos, 3); - mjuu_normvec(b->alt.zaxis, 3); - - // add geom - mjsGeom* g = mjs_addGeom(b, &def[0].spec); - mjs_setDefault(g->element, mjs_getDefault(body->element)); - mju::sprintf_arr(txt, "%sG%d_%d_%d", prefix.c_str(), ix, iy, iz); - mjs_setString(g->name, txt); - - // offset inwards, enforce sphere or capsule - if (g->type==mjGEOM_CAPSULE) { - g->pos[2] = -(g->size[0] + g->size[1]); - } else { - g->type = mjGEOM_SPHERE; - g->pos[2] = -g->size[0]; - } - - // add slider joint - mjsJoint* jnt = mjs_addJoint(b, &defjoint[mjCOMPKIND_JOINT][0].spec); - mjs_setDefault(jnt->element, mjs_getDefault(body->element)); - mju::sprintf_arr(txt, "%sJ%d_%d_%d", prefix.c_str(), ix, iy, iz); - mjs_setString(jnt->name, txt); - jnt->type = mjJNT_SLIDE; - mjuu_setvec(jnt->pos, 0, 0, 0); - mjuu_setvec(jnt->axis, 0, 0, 1); - - // add fix constraint - mjsEquality* eq = mjs_addEquality(&model->spec, &def[mjCOMPKIND_JOINT].spec); - mjs_setDefault(eq->element, &model->Default()->spec); - eq->type = mjEQ_JOINT; - mjs_setString(eq->name1, mjs_getString(jnt->name)); - - // add joint to tendon - ten->WrapJoint(std::string(mjs_getString(jnt->name)), 1); - - // add neighbor constraints - for (int i=0; i<3; i++) { - int ix1 = mjMIN(ix+(i==0), count[0]-1); - int iy1 = mjMIN(iy+(i==1), count[1]-1); - int iz1 = mjMIN(iz+(i==2), count[2]-1); - if ((ix1==0 || ix1==count[0]-1 || - iy1==0 || iy1==count[1]-1 || - iz1==0 || iz1==count[2]-1) && - (ix!=ix1 || iy!=iy1 || iz!=iz1)) { - char txt2[200]; - mju::sprintf_arr(txt2, - "%sJ%d_%d_%d", prefix.c_str(), ix1, iy1, iz1); - mjsEquality* eqn = mjs_addEquality(&model->spec, 0); - mju_copy(eqn->solref, solrefsmooth, mjNREF); - mju_copy(eqn->solimp, solimpsmooth, mjNIMP); - eqn->type = mjEQ_JOINT; - mjs_setString(eqn->name1, txt); - mjs_setString(eqn->name2, txt2); - } - } - } - } - } - } - - // finalize fixed tendon - mjsEquality* eqt = mjs_addEquality(&model->spec, &def[mjCOMPKIND_TENDON].spec); - mjs_setDefault(eqt->element, &model->Default()->spec); - eqt->type = mjEQ_TENDON; - mjs_setString(eqt->name1, ten->name.c_str()); - - // skin - if (skin) { - MakeSkin3(model); - } - - return true; -} - - - // add shear tendons to 2D void mjCComposite::MakeShear(mjCModel* model) { char txt[100], txt1[100], txt2[100]; @@ -1838,272 +1654,3 @@ void mjCComposite::MakeSkin2Subgrid(mjCModel* model, mjtNum inflate) { mju_free(D); } - - -// add skin to 3D -void mjCComposite::MakeSkin3(mjCModel* model) { - int vcnt = 0; - std::map vmap; - char txt[100], cnt0[10], cnt1[10], cnt2[10]; - std::string fmt; - - // string counts - mju::sprintf_arr(cnt0, "%d", count[0]-1); - mju::sprintf_arr(cnt1, "%d", count[1]-1); - mju::sprintf_arr(cnt2, "%d", count[2]-1); - - // add skin, set name and material - mjsSkin* skin = mjs_addSkin(&model->spec); - mju::sprintf_arr(txt, "%sSkin", prefix.c_str()); - mjs_setString(skin->name, txt); - mjs_setString(skin->material, skinmaterial.c_str()); - mjuu_copyvec(skin->rgba, skinrgba, 4); - skin->inflate = skininflate; - skin->group = skingroup; - - // box - if (type==mjCOMPTYPE_BOX || type==mjCOMPTYPE_PARTICLE) { - // z-faces - MakeSkin3Box(skin, count[0], count[1], 1, vcnt, "%sB%d_%d_0"); - fmt = "%sB%d_%d_" + std::string(cnt2); - MakeSkin3Box(skin, count[0], count[1], 0, vcnt, fmt.c_str()); - - // y-faces - MakeSkin3Box(skin, count[0], count[2], 0, vcnt, "%sB%d_0_%d"); - fmt = "%sB%d_" + std::string(cnt1) + "_%d"; - MakeSkin3Box(skin, count[0], count[2], 1, vcnt, fmt.c_str()); - - // x-faces - MakeSkin3Box(skin, count[1], count[2], 1, vcnt, "%sB0_%d_%d"); - fmt = "%sB" + std::string(cnt0) + "_%d_%d"; - MakeSkin3Box(skin, count[1], count[2], 0, vcnt, fmt.c_str()); - } - - // cylinder - else if (type==mjCOMPTYPE_CYLINDER) { - // generate vertices in map - for (int ix=0; ixbodyname, txt); - bindpos.push_back(0); - bindpos.push_back(0); - bindpos.push_back(0); - bindquat.push_back(1); - bindquat.push_back(0); - bindquat.push_back(0); - bindquat.push_back(0); - - // vertid and vertweight - vertid.push_back({vcnt + i0*c1+i1}); - vertweight.push_back({1}); - } - } - - // update vertex count - vcnt += c0*c1; -} - - - -// make one face of 3D skin, smooth -void mjCComposite::MakeSkin3Smooth(mjsSkin* skin, int c0, int c1, int side, - const std::map& vmap, - const char* format) { - char txt00[100], txt01[100], txt10[100], txt11[100]; - - // loop over bodies/vertices of specified face - for (int i0=0; i0second); - face.push_back(vmap.find(txt10)->second); - face.push_back(vmap.find(txt11)->second); - - face.push_back(vmap.find(txt00)->second); - face.push_back(vmap.find(txt11)->second); - face.push_back(vmap.find(txt01)->second); - } else { - face.push_back(vmap.find(txt00)->second); - face.push_back(vmap.find(txt01)->second); - face.push_back(vmap.find(txt11)->second); - - face.push_back(vmap.find(txt00)->second); - face.push_back(vmap.find(txt11)->second); - face.push_back(vmap.find(txt10)->second); - } - } - - // bind pose: origin - mjs_appendString(skin->bodyname, txt00); - bindpos.push_back(0); - bindpos.push_back(0); - bindpos.push_back(0); - bindquat.push_back(1); - bindquat.push_back(0); - bindquat.push_back(0); - bindquat.push_back(0); - - // vertid and vertweight - vertid.push_back({vmap.find(txt00)->second}); - vertweight.push_back({1}); - } - } -} diff --git a/src/user/user_composite.h b/src/user/user_composite.h index 446ef1d4..add71636 100644 --- a/src/user/user_composite.h +++ b/src/user/user_composite.h @@ -31,9 +31,6 @@ typedef enum _mjtCompType { mjCOMPTYPE_ROPE, mjCOMPTYPE_LOOP, mjCOMPTYPE_CLOTH, - mjCOMPTYPE_BOX, - mjCOMPTYPE_CYLINDER, - mjCOMPTYPE_ELLIPSOID, mjNCOMPTYPES } mjtCompType; @@ -75,7 +72,6 @@ class mjCComposite { bool MakeGrid(mjCModel* model, mjsBody* body, char* error, int error_sz); bool MakeRope(mjCModel* model, mjsBody* body, char* error, int error_sz); bool MakeCable(mjCModel* model, mjsBody* body, char* error, int error_sz); - bool MakeBox(mjCModel* model, mjsBody* body, char* error, int error_sz); void MakeShear(mjCModel* model); void MakeSkin2(mjCModel* model, mjtNum inflate); @@ -85,13 +81,6 @@ class mjCComposite { void MakeCableBones(mjCModel* model, mjsSkin* skin); void MakeCableBonesSubgrid(mjCModel* model, mjsSkin* skin); - void MakeSkin3(mjCModel* model); - void MakeSkin3Box(mjsSkin* skin, int c0, int c1, int side, int& vcnt, const char* format); - void MakeSkin3Smooth(mjsSkin* skin, int c0, int c1, int side, - const std::map& vmap, const char* format); - - void BoxProject(double* pos); - // common properties std::string prefix; // name prefix mjtCompType type; // composite type diff --git a/src/user/user_flexcomp.cc b/src/user/user_flexcomp.cc index 732d87ba..67443e22 100644 --- a/src/user/user_flexcomp.cc +++ b/src/user/user_flexcomp.cc @@ -763,6 +763,7 @@ bool mjCFlexcomp::MakeSquare(char* error, int error_sz) { // make 3d box, ellipsoid or cylinder bool mjCFlexcomp::MakeBox(char* error, int error_sz) { double pos[3]; + bool needtex = texcoord.empty() && !std::string(mjs_getString(def.spec.flex->material)).empty(); // set 3D def.spec.flex->dim = 3; @@ -772,6 +773,12 @@ bool mjCFlexcomp::MakeBox(char* error, int error_sz) { point.push_back(0); point.push_back(0); + // add texture coordinates, if not specified explicitly + if (needtex) { + texcoord.push_back(0); + texcoord.push_back(0); + } + // iz=0/max for (int iz=0; iz < count[2]; iz+=count[2]-1) { for (int ix=0; ix < count[0]; ix++) { @@ -782,6 +789,12 @@ bool mjCFlexcomp::MakeBox(char* error, int error_sz) { point.push_back(pos[1]); point.push_back(pos[2]); + // add texture coordinates, if not specified explicitly + if (needtex) { + texcoord.push_back(ix/(float)std::max(count[0]-1, 1)); + texcoord.push_back(iy/(float)std::max(count[1]-1, 1)); + } + // add elements if (ix < count[0]-1 && iy < count[1]-1) { element.push_back(0); @@ -808,6 +821,12 @@ bool mjCFlexcomp::MakeBox(char* error, int error_sz) { point.push_back(pos[0]); point.push_back(pos[1]); point.push_back(pos[2]); + + // add texture coordinates + if (needtex) { + texcoord.push_back(ix/(float)std::max(count[0]-1, 1)); + texcoord.push_back(iz/(float)std::max(count[2]-1, 1)); + } } // add elements @@ -836,6 +855,12 @@ bool mjCFlexcomp::MakeBox(char* error, int error_sz) { point.push_back(pos[0]); point.push_back(pos[1]); point.push_back(pos[2]); + + // add texture coordinates + if (needtex) { + texcoord.push_back(iy/(float)std::max(count[1]-1, 1)); + texcoord.push_back(iz/(float)std::max(count[2]-1, 1)); + } } // add elements diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 257a394a..94b3ec3d 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -1613,6 +1613,7 @@ void mjCModel::SetSizes() { nflexelemedge += flexes_[i]->nelem * mjCFlex::kNumEdges[flexes_[i]->dim - 1]; nflexshelldata += (int)flexes_[i]->shell.size(); nflexevpair += (int)flexes_[i]->evpair.size()/2; + nflextexcoord += (flexes_[i]->HasTexcoord() ? flexes_[i]->get_texcoord().size()/2 : 0); } // mesh counts diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 0037a77a..9a8ad20d 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -742,10 +742,7 @@ const mjMap comp_map[mjNCOMPTYPES] = { {"rope", mjCOMPTYPE_ROPE}, {"loop", mjCOMPTYPE_LOOP}, {"cable", mjCOMPTYPE_CABLE}, - {"cloth", mjCOMPTYPE_CLOTH}, - {"box", mjCOMPTYPE_BOX}, - {"cylinder", mjCOMPTYPE_CYLINDER}, - {"ellipsoid", mjCOMPTYPE_ELLIPSOID} + {"cloth", mjCOMPTYPE_CLOTH} }; diff --git a/test/engine/testdata/skingroup.xml b/test/engine/testdata/skingroup.xml index 93053736..6a2da654 100644 --- a/test/engine/testdata/skingroup.xml +++ b/test/engine/testdata/skingroup.xml @@ -27,17 +27,15 @@ - - + - + - - + - + diff --git a/test/user/user_composite_test.cc b/test/user/user_composite_test.cc index 1d0dabf1..72f2297a 100644 --- a/test/user/user_composite_test.cc +++ b/test/user/user_composite_test.cc @@ -43,7 +43,7 @@ TEST_F(UserCompositeTest, MultipleJointsNotAllowedUnlessParticle) { - + diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index 4a287b6e..219ef5e1 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -1681,13 +1681,13 @@ TEST_F(XMLReaderTest, ReadsSkinGroups) { - + - + @@ -1698,8 +1698,8 @@ TEST_F(XMLReaderTest, ReadsSkinGroups) { 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"); + int geomid1 = mj_name2id(model, mjOBJ_GEOM, "B0G0_0"); + int geomid2 = mj_name2id(model, mjOBJ_GEOM, "B1G0_0"); EXPECT_THAT(model->geom_group[geomid1], 2); EXPECT_THAT(model->skin_group[0], 4); EXPECT_THAT(model->geom_group[geomid2], 4); @@ -1712,7 +1712,7 @@ TEST_F(XMLReaderTest, InvalidSkinGroup) { - +