// Copyright 2021 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "user/user_api.h" #include "user/user_composite.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cc/array_safety.h" #include "engine/engine_io.h" #include "engine/engine_util_blas.h" #include "engine/engine_util_errmem.h" #include "engine/engine_util_misc.h" #include "user/user_model.h" #include "user/user_objects.h" #include "user/user_util.h" #include "xml/xml_util.h" namespace { namespace mju = ::mujoco::util; using std::vector; using std::string; } // namespace // strncpy with 0, return false static bool comperr(char* error, const char* msg, int error_sz) { mju_strncpy(error, msg, error_sz); return false; } // constructor mjCComposite::mjCComposite(void) { // common properties prefix.clear(); type = mjCOMPTYPE_PARTICLE; count[0] = count[1] = count[2] = 1; spacing = 0; mjuu_setvec(offset, 0, 0, 0); pin.clear(); flatinertia = 0; mj_defaultSolRefImp(solrefsmooth, solimpsmooth); plugin_instance = nullptr; // cable curve[0] = curve[1] = curve[2] = mjCOMPSHAPE_ZERO; mjuu_setvec(size, 1, 0, 0); initial = "ball"; // skin skin = false; skintexcoord = false; skinmaterial.clear(); mjuu_setvec(skinrgba, 1, 1, 1, 1); skininflate = 0; skinsubgrid = 0; skingroup = 0; // clear add flags for (int i=0; i1) { tmpdim++; } } // set all deafult groups to 3 for (int i=0; i1) { return comperr(error, "Either vertex or count can be specified, not both", error_sz); } count[0] = uservert.size()/3; count[1] = 1; } // determine dimensionality, check singleton order bool first = false; for (int i=0; i<3; i++) { if (count[i]==1) { first = true; } else { dim++; if (first) { return comperr(error, "Singleton counts must come last", error_sz); } } } // require 3x3 for subgrid if (skin && skinsubgrid>0 && type!=mjCOMPTYPE_CABLE) { if (count[0]<3 || count[1]<3) { return comperr(error, "At least 3x3 required for skin subgrid", error_sz); } } // dispatch switch (type) { case mjCOMPTYPE_PARTICLE: return MakeParticle(model, body, error, error_sz); case mjCOMPTYPE_GRID: return MakeGrid(model, body, error, error_sz); case mjCOMPTYPE_ROPE: return comperr(error, "The \"rope\" composite type is deprecated. Please use " "\"cable\" instead.", error_sz); case mjCOMPTYPE_LOOP: mju_warning( "The \"loop\" composite type is deprecated. Please use \"cable\" " "instead."); return MakeRope(model, body, error, error_sz); case mjCOMPTYPE_CABLE: return MakeCable(model, body, error, error_sz); case mjCOMPTYPE_CLOTH: return comperr(error, "The \"cloth\" composite type is deprecated. Please use " "\"shell\" instead.", error_sz); case mjCOMPTYPE_BOX: case mjCOMPTYPE_CYLINDER: case mjCOMPTYPE_ELLIPSOID: return MakeBox(model, body, error, error_sz); default: return comperr(error, "Uknown shape in composite", error_sz); } } bool mjCComposite::MakeParticle(mjCModel* model, mjmBody* body, char* error, int error_sz) { char txt[100]; std::vector face; // populate vertices and names if (uservert.empty()) { if (spacing < mju_max(def[0].geom.size[0], mju_max(def[0].geom.size[1], def[0].geom.size[2]))) return comperr(error, "Spacing must be larger than geometry size", error_sz); for (int ix=0; ix volume(uservert.size()/3); mjtNum t = 1; if (dim == 2 && plugin_instance) { try { t = std::stod(plugin_instance->config_attribs["thickness"], nullptr); } catch (const std::invalid_argument& e) { return comperr(error, "Invalid thickness attribute", error_sz); } } if (!userface.empty()) { mjXUtil::String2Vector(userface, face); for (int j=0; jname, username[i].c_str()); } else { mju::sprintf_arr(txt, "%sB%d", prefix.c_str(), i); mjm_setString(b->name, txt); } // set body position b->pos[0] = offset[0] + uservert[3*i]; b->pos[1] = offset[1] + uservert[3*i+1]; b->pos[2] = offset[2] + uservert[3*i+2]; // add slider joints if none defined if (!add[mjCOMPKIND_PARTICLE]) { for (int i=0; i<3; i++) { mjCJoint* jnt = (mjCJoint*)mjm_addJoint(b, &defjoint[mjCOMPKIND_JOINT][0]); jnt->def = (mjCDef*)mjm_getDefault(body->element); jnt->type = mjJNT_SLIDE; mjuu_setvec(jnt->pos, 0, 0, 0); mjuu_setvec(jnt->axis, 0, 0, 0); jnt->axis[i] = 1; } } // add user-specified joints else { for (auto defjnt : defjoint[mjCOMPKIND_PARTICLE]) { mjCJoint* jnt = (mjCJoint*)mjm_addJoint(b, &defjnt); jnt->def = (mjCDef*)mjm_getDefault(body->element); } } // add geom mjCGeom* g = (mjCGeom*)mjm_addGeom(b, def); g->def = (mjCDef*)mjm_getDefault(body->element); // add site mjmSite* s = mjm_addSite(b, def); mjm_setDefault(s->element, mjm_getDefault(body->element)); s->type = mjGEOM_SPHERE; mju::sprintf_arr(txt, "%sS%d", prefix.c_str(), i); mjm_setString(s->name, txt); // add plugin if (plugin_instance) { mjmPlugin* plugin = &b->plugin; plugin->active = true; plugin->instance = (mjElement)plugin_instance; mjm_setString(plugin->instance_name, plugin_instance_name.c_str()); mjm_setString(plugin->name, plugin_name.c_str()); if (i==0 && !plugin_instance->config_attribs["face"].empty()) { return comperr(error, "Face attribute already exists in plugin", error_sz); } plugin_instance->config_attribs["face"] = userface; plugin_instance->config_attribs["edge"] = ""; // update density if (dim == 2) { g->density *= volume[i] / (4./3. * mjPI * pow(g->size[0], 3)); } } } // add isometry constraints if (dim==2) { char txt0[100], txt1[100], txt2[100]; std::vector> edge; // create edges for (int i=0; iAddTendon(def + mjCOMPKIND_TENDON); ten->def = model->defaults[0]; ten->name = txt0; ten->group = 4; ten->WrapSite(txt1); ten->WrapSite(txt2); // add equality constraint mjCEquality* eq = model->AddEquality(def + mjCOMPKIND_TENDON); eq->def = model->defaults[0]; eq->type = mjEQ_TENDON; eq->name1 = ten->name; } } if (skin && dim==3) { MakeSkin3(model); } if (skin && dim==2) { if (skinsubgrid>0) { MakeSkin2Subgrid(model, skininflate); } else { MakeSkin2(model, skininflate); } } return true; } // make grid connected with tendons bool mjCComposite::MakeGrid(mjCModel* model, mjmBody* body, char* error, int error_sz) { char txt[100], txt1[100], txt2[100]; // check dimensionality if (dim>2) { return comperr(error, "Grid can only be 1D or 2D", error_sz); } // check shear dimensionality if (add[mjCOMPKIND_SHEAR] && dim!=2) { return comperr(error, "Shear requires 2D grid", error_sz); } // check skin dimensionality if (skin && dim!=2) { return comperr(error, "Skin requires 2D grid", error_sz); } // create bodies, joints, geoms, sites for (int ix=0; ixname, txt); // set body position b->pos[0] = offset[0] + spacing*(ix - 0.5*count[0]); b->pos[1] = offset[1] + spacing*(iy - 0.5*count[1]); b->pos[2] = offset[2]; // add geom mjCGeom* g = (mjCGeom*)mjm_addGeom(b, def); g->def = (mjCDef*)mjm_getDefault(body->element); g->type = mjGEOM_SPHERE; mju::sprintf_arr(txt, "%sG%d_%d", prefix.c_str(), ix, iy); g->name = txt; // add site mjmSite* s = mjm_addSite(b, def); mjm_setDefault(s->element, mjm_getDefault(body->element)); s->type = mjGEOM_SPHERE; mju::sprintf_arr(txt, "%sS%d_%d", prefix.c_str(), ix, iy); mjm_setString(s->name, txt); // skip pinned elements bool skip = false; for (int ip=0; ipdef = (mjCDef*)mjm_getDefault(body->element); mju::sprintf_arr(txt, "%sJ%d_%d_%d", prefix.c_str(), i, ix, iy); jnt[i]->name = txt; jnt[i]->type = mjJNT_SLIDE; mjuu_setvec(jnt[i]->pos, 0, 0, 0); mjuu_setvec(jnt[i]->axis, 0, 0, 0); jnt[i]->axis[i] = 1; } } } // create tendons and equality constraints for (int i=0; i<2; i++) { for (int ix=0; ixAddTendon(def + mjCOMPKIND_TENDON); ten->def = model->defaults[0]; mju::sprintf_arr(txt, "%sT%d_%d_%d", prefix.c_str(), i, ix, iy); ten->name = txt; ten->WrapSite(txt1); ten->WrapSite(txt2); // add equality constraint mjCEquality* eq = model->AddEquality(def + mjCOMPKIND_TENDON); eq->def = model->defaults[0]; eq->type = mjEQ_TENDON; eq->name1 = ten->name; } } } // shear for 2D if (add[mjCOMPKIND_SHEAR]) { MakeShear(model); } // skin if (skin) { if (skinsubgrid>0) { MakeSkin2Subgrid(model, skininflate); } else { MakeSkin2(model, skininflate); } } return true; } bool mjCComposite::MakeCable(mjCModel* model, mjmBody* body, char* error, int error_sz) { // check dim if (dim!=1) { return comperr(error, "Cable must be one-dimensional", error_sz); } // check geom type if (def[0].geom.type!=mjGEOM_CYLINDER && def[0].geom.type!=mjGEOM_CAPSULE && def[0].geom.type!=mjGEOM_BOX) { return comperr(error, "Cable geom type must be sphere, capsule or box", error_sz); } // add name to model mjCText* pte = model->AddText(); pte->name = "composite_" + prefix; pte->data = "rope_" + prefix; // populate uservert if not specified if (uservert.empty()) { for (int ix=0; ix0) { count[1]+=2; MakeSkin2Subgrid(model, 2*def[0].geom.size[2]); count[1]-=2; } else { count[1]++; MakeSkin2(model, 2*def[0].geom.size[2]); count[1]--; } } return true; } mjmBody* mjCComposite::AddCableBody(mjCModel* model, mjmBody* body, int ix, mjtNum normal[3], mjtNum prev_quat[4]) { char txt_geom[100], txt_site[100], txt_slide[100]; char this_body[100], next_body[100], this_joint[100]; mjtNum dquat[4], this_quat[4]; // set flags int lastidx = count[0]-2; bool first = ix==0; bool last = ix==lastidx; bool secondlast = ix==lastidx-1; // compute edge and tangent vectors mjtNum edge[3], tprev[3], tnext[3]; mjuu_setvec(edge, uservert[3*(ix+1)+0]-uservert[3*ix+0], uservert[3*(ix+1)+1]-uservert[3*ix+1], uservert[3*(ix+1)+2]-uservert[3*ix+2]); if (!first) { mjuu_setvec(tprev, uservert[3*ix+0]-uservert[3*(ix-1)+0], uservert[3*ix+1]-uservert[3*(ix-1)+1], uservert[3*ix+2]-uservert[3*(ix-1)+2]); mjuu_normvec(tprev, 3); } if (!last) { mjuu_setvec(tnext, uservert[3*(ix+2)+0]-uservert[3*(ix+1)+0], uservert[3*(ix+2)+1]-uservert[3*(ix+1)+1], uservert[3*(ix+2)+2]-uservert[3*(ix+1)+2]); mjuu_normvec(tnext, 3); } // update moving frame mjtNum length = mju_updateFrame(this_quat, normal, edge, tprev, tnext, first); // create body, joint, and geom names if (first) { mju::sprintf_arr(this_body, "%sB_first", prefix.c_str()); mju::sprintf_arr(next_body, "%sB_%d", prefix.c_str(), ix+1); mju::sprintf_arr(this_joint, "%sJ_first", prefix.c_str()); mju::sprintf_arr(txt_site, "%sS_first", prefix.c_str()); } else if (last) { mju::sprintf_arr(this_body, "%sB_last", prefix.c_str()); mju::sprintf_arr(next_body, "%sB_first", prefix.c_str()); mju::sprintf_arr(this_joint, "%sJ_last", prefix.c_str()); mju::sprintf_arr(txt_site, "%sS_last", prefix.c_str()); } else if (secondlast){ mju::sprintf_arr(this_body, "%sB_%d", prefix.c_str(), ix); mju::sprintf_arr(next_body, "%sB_last", prefix.c_str()); mju::sprintf_arr(this_joint, "%sJ_%d", prefix.c_str(), ix); } else { mju::sprintf_arr(this_body, "%sB_%d", prefix.c_str(), ix); mju::sprintf_arr(next_body, "%sB_%d", prefix.c_str(), ix+1); mju::sprintf_arr(this_joint, "%sJ_%d", prefix.c_str(), ix); } mju::sprintf_arr(txt_geom, "%sG%d", prefix.c_str(), ix); mju::sprintf_arr(txt_slide, "%sJs%d", prefix.c_str(), ix); // add body body = mjm_addBody(body, 0); mjm_setString(body->name, this_body); if (first) { mjuu_setvec(body->pos, offset[0]+uservert[3*ix], offset[1]+uservert[3*ix+1], offset[2]+uservert[3*ix+2]); mjuu_copyvec(body->quat, this_quat, 4); } else { mjuu_setvec(body->pos, length, 0, 0); mjtNum negquat[4] = {prev_quat[0], -prev_quat[1], -prev_quat[2], -prev_quat[3]}; mjuu_mulquat(dquat, negquat, this_quat); mjuu_copyvec(body->quat, dquat, 4); } // add geom mjCGeom* geom = (mjCGeom*)mjm_addGeom(body, def); geom->def = (mjCDef*)mjm_getDefault(body->element); geom->name = txt_geom; if (def[0].geom.type==mjGEOM_CYLINDER || def[0].geom.type==mjGEOM_CAPSULE) { mjuu_zerovec(geom->fromto, 6); geom->fromto[3] = length; } else if (def[0].geom.type==mjGEOM_BOX) { mjuu_zerovec(geom->pos, 3); geom->pos[0] = length/2; geom->size[0] = length/2; } // add plugin if (plugin_instance) { mjmPlugin* plugin = &body->plugin; plugin->active = true; plugin->instance = (mjElement)plugin_instance; mjm_setString(plugin->name, plugin_name.c_str()); mjm_setString(plugin->instance_name, plugin_instance_name.c_str()); } // update orientation mjuu_copyvec(prev_quat, this_quat, 4); // add curvature joint if (!first || strcmp(initial.c_str(), "none")) { mjCJoint* jnt = (mjCJoint*)mjm_addJoint(body, &defjoint[mjCOMPKIND_JOINT][0]); jnt->def = (mjCDef*)mjm_getDefault(body->element); jnt->type = (first && strcmp(initial.c_str(), "free")==0) ? mjJNT_FREE : mjJNT_BALL; jnt->damping = jnt->type==mjJNT_FREE ? 0 : jnt->damping; jnt->armature = jnt->type==mjJNT_FREE ? 0 : jnt->armature; jnt->frictionloss = jnt->type==mjJNT_FREE ? 0 : jnt->frictionloss; jnt->name = this_joint; } // exclude contact pair if (!last) { mjCBodyPair* exclude = model->AddExclude(); exclude->bodyname1 = this_body; exclude->bodyname2 = next_body; } // add site at the boundary if (last || first) { mjmSite* site = mjm_addSite(body, def); mjm_setDefault(site->element, mjm_getDefault(body->element)); mjm_setString(site->name, txt_site); mjuu_setvec(site->pos, last ? length : 0, 0, 0); mjuu_setvec(site->quat, 1, 0, 0, 0); } return body; } // make rope bool mjCComposite::MakeRope(mjCModel* model, mjmBody* body, char* error, int error_sz) { // check dim if (dim!=1) { return comperr(error, "Rope must be one-dimensional", error_sz); } // check root body name prefix char txt[200]; mju::sprintf_arr(txt, "%sB", prefix.c_str()); std::string body_name = mjm_getString(body->name); if (std::strncmp(txt, body_name.substr(0, strlen(txt)).c_str(), mju::sizeof_arr(txt))) { mju::strcat_arr(txt, " must be the beginning of root body name"); return comperr(error, txt, error_sz); } // read origin coordinate from root body mju::strcpy_arr(txt, body_name.substr(strlen(txt)).c_str()); int ox = -1; if (sscanf(txt, "%d", &ox)!=1) { return comperr(error, "Root body name must contain X coordinate", error_sz); } if (ox<0 || ox>=count[0]) { return comperr(error, "Root body coordinate out of range", error_sz); } // add origin AddRopeBody(model, body, ox, ox); // add elements: right mjmBody* pbody = body; for (int ix=ox; ix0; ix--) { pbody = AddRopeBody(model, pbody, ix, ix-1); } // close loop if (type==mjCOMPTYPE_LOOP) { char txt2[200]; // add equality constraint mjCEquality* eq = model->AddEquality(); eq->type = mjEQ_CONNECT; mju::sprintf_arr(txt, "%sB0", prefix.c_str()); mju::sprintf_arr(txt2, "%sB%d", prefix.c_str(), count[0]-1); eq->name1 = txt; eq->name2 = txt2; mjuu_setvec(eq->data, -0.5*spacing, 0, 0); mju_copy(eq->solref, solrefsmooth, mjNREF); mju_copy(eq->solimp, solimpsmooth, mjNIMP); // remove contact between connected bodies mjCBodyPair* pair = model->AddExclude(); pair->bodyname1 = txt; pair->bodyname2 = txt2; } return true; } // add child body for cloth mjmBody* mjCComposite::AddRopeBody(mjCModel* model, mjmBody* body, int ix, int ix1) { char txt[100]; bool isroot = (ix==ix1); double dx = spacing*(ix1-ix); // add child if not root if (!isroot) { body = mjm_addBody(body, 0); mju::sprintf_arr(txt, "%sB%d", prefix.c_str(), ix1); mjm_setString(body->name, txt); // loop if (type==mjCOMPTYPE_LOOP) { double alpha = 2*mjPI/count[0]; double R = 0.5*spacing*sin(mjPI-alpha)/sin(0.5*alpha); if (ix1>ix) { mjuu_setvec(body->pos, R*cos(0.5*alpha), R*sin(0.5*alpha), 0); mjuu_setvec(body->quat, cos(0.5*alpha), 0, 0, sin(0.5*alpha)); } else { mjuu_setvec(body->pos, -R*cos(0.5*alpha), R*sin(0.5*alpha), 0); mjuu_setvec(body->quat, cos(-0.5*alpha), 0, 0, sin(-0.5*alpha)); } } // no loop else { mjuu_setvec(body->pos, dx, 0, 0); } } // add geom mjCGeom* geom = (mjCGeom*)mjm_addGeom(body, def); geom->def = (mjCDef*)mjm_getDefault(body->element); mju::sprintf_arr(txt, "%sG%d", prefix.c_str(), ix1); geom->name = txt; mjuu_setvec(geom->pos, 0, 0, 0); mjuu_setvec(geom->quat, sqrt(0.5), 0, sqrt(0.5), 0); // root: no joints if (isroot) { return body; } // add main joint for (int i=0; i<2; i++) { // add joint mjCJoint* jnt = (mjCJoint*)mjm_addJoint(body, &defjoint[mjCOMPKIND_JOINT][0]); jnt->def = (mjCDef*)mjm_getDefault(body->element); mju::sprintf_arr(txt, "%sJ%d_%d", prefix.c_str(), i, ix1); jnt->name = txt; jnt->type = mjJNT_HINGE; mjuu_setvec(jnt->pos, -0.5*dx, 0, 0); mjuu_setvec(jnt->axis, 0, 0, 0); jnt->axis[i+1] = 1; } // add twist joint if (add[mjCOMPKIND_TWIST]) { // add joint mjCJoint* jnt = (mjCJoint*)mjm_addJoint(body, &defjoint[mjCOMPKIND_TWIST][0]); jnt->def = (mjCDef*)mjm_getDefault(body->element); mju::sprintf_arr(txt, "%sJT%d", prefix.c_str(), ix1); jnt->name = txt; jnt->type = mjJNT_HINGE; mjuu_setvec(jnt->pos, -0.5*dx, 0, 0); mjuu_setvec(jnt->axis, 1, 0, 0); // add constraint mjCEquality* eq = model->AddEquality(def + mjCOMPKIND_TWIST); eq->def = model->defaults[0]; eq->type = mjEQ_JOINT; eq->name1 = jnt->name; } // add stretch joint if (add[mjCOMPKIND_STRETCH]) { // add joint mjCJoint* jnt = (mjCJoint*)mjm_addJoint(body, &defjoint[mjCOMPKIND_STRETCH][0]); jnt->def = (mjCDef*)mjm_getDefault(body->element); mju::sprintf_arr(txt, "%sJS%d", prefix.c_str(), ix1); jnt->name = txt; jnt->type = mjJNT_SLIDE; mjuu_setvec(jnt->pos, -0.5*dx, 0, 0); mjuu_setvec(jnt->axis, 1, 0, 0); // add constraint mjCEquality* eq = model->AddEquality(def + mjCOMPKIND_STRETCH); eq->def = model->defaults[0]; eq->type = mjEQ_JOINT; eq->name1 = jnt->name; } return body; } // 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 = mju_max(mju_abs(pos[0]), mju_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, mjmBody* 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 mjCGeom* geom = (mjCGeom*)mjm_addGeom(body, def); geom->def = (mjCDef*)mjm_getDefault(body->element); geom->type = mjGEOM_SPHERE; mju::sprintf_arr(txt, "%sGcenter", prefix.c_str()); 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->def = model->defaults[0]; 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 mjuu_copyvec(b->alt.zaxis, b->pos, 3); mjuu_normvec(b->alt.zaxis, 3); // add geom mjCGeom* g = (mjCGeom*) mjm_addGeom(b, def); g->def = (mjCDef*)mjm_getDefault(body->element); mju::sprintf_arr(txt, "%sG%d_%d_%d", prefix.c_str(), ix, iy, iz); 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 mjCJoint* jnt = (mjCJoint*)mjm_addJoint(b, &defjoint[mjCOMPKIND_JOINT][0]); jnt->def = (mjCDef*)mjm_getDefault(body->element); mju::sprintf_arr(txt, "%sJ%d_%d_%d", prefix.c_str(), ix, iy, iz); jnt->name = txt; jnt->type = mjJNT_SLIDE; mjuu_setvec(jnt->pos, 0, 0, 0); mjuu_setvec(jnt->axis, 0, 0, 1); // add fix constraint mjCEquality* eq = model->AddEquality(def + mjCOMPKIND_JOINT); eq->def = model->defaults[0]; eq->type = mjEQ_JOINT; eq->name1 = jnt->name; // add joint to tendon ten->WrapJoint(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); mjCEquality* eqn = model->AddEquality(); mju_copy(eqn->solref, solrefsmooth, mjNREF); mju_copy(eqn->solimp, solimpsmooth, mjNIMP); eqn->type = mjEQ_JOINT; eqn->name1 = txt; eqn->name2 = txt2; } } } } } } // finalize fixed tendon mjCEquality* eqt = model->AddEquality(def + mjCOMPKIND_TENDON); eqt->def = model->defaults[0]; eqt->type = mjEQ_TENDON; eqt->name1 = ten->name; // skin if (skin) { MakeSkin3(model); } return true; } // add shear tendons to 2D void mjCComposite::MakeShear(mjCModel* model) { char txt[100], txt1[100], txt2[100]; for (int ix=0; ixAddTendon(def + mjCOMPKIND_SHEAR); ten->def = model->defaults[0]; ten->WrapSite(txt1); ten->WrapSite(txt2); // name tendon mju::sprintf_arr(txt, "%sTS%d_%d", prefix.c_str(), ix, iy); ten->name = txt; // equality constraint mjCEquality* eq = model->AddEquality(def + mjCOMPKIND_SHEAR); eq->def = model->defaults[0]; eq->type = mjEQ_TENDON; eq->name1 = txt; } } } // add skin to 2D void mjCComposite::MakeSkin2(mjCModel* model, mjtNum inflate) { char txt[100]; int N = count[0]*count[1]; // add skin, set name and material mjCSkin* skin = model->AddSkin(); mju::sprintf_arr(txt, "%sSkin", prefix.c_str()); skin->name = txt; skin->set_material(skinmaterial); mjuu_copyvec(skin->rgba, skinrgba, 4); skin->inflate = inflate; skin->group = skingroup; // copy skin from existing mesh if (type==mjCOMPTYPE_PARTICLE && username.empty()) { std::vector face; mjXUtil::String2Vector(userface, face); int nvert = uservert.size()/3; for (int j=0; j<2; j++) { for (int i=0; ivert.push_back(0); skin->vert.push_back(0); skin->vert.push_back(0); mju::sprintf_arr(txt, "%sB%d", prefix.c_str(), i); skin->bodyname.push_back(txt); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); vector vertid; vector vertweight; vertid.push_back(j*nvert+i); vertweight.push_back(1); skin->vertid.push_back(vertid); skin->vertweight.push_back(vertweight); } for (int i=0; iface.push_back(j*nvert+face[3*i]); skin->face.push_back(j*nvert+face[3*i+(j==0 ? 1 : 2)]); skin->face.push_back(j*nvert+face[3*i+(j==0 ? 2 : 1)]); } } return; } // populate mesh: two sides for (int i=0; i<2; i++) { for (int ix=0; ixvert.push_back(0); skin->vert.push_back(0); skin->vert.push_back(0); // texture coordinate if (skintexcoord) { skin->texcoord.push_back(ix/(float)(count[0]-1)); skin->texcoord.push_back(iy/(float)(count[1]-1)); } // face if (ixface.push_back(i*N + ix*count[1]+iy); skin->face.push_back(i*N + (ix+1)*count[1]+iy+(i==1)); skin->face.push_back(i*N + (ix+1)*count[1]+iy+(i==0)); skin->face.push_back(i*N + ix*count[1]+iy); skin->face.push_back(i*N + (ix+(i==0))*count[1]+iy+1); skin->face.push_back(i*N + (ix+(i==1))*count[1]+iy+1); } } } } // add thin triangles: X direction, iy = 0 for (int ix=0; ixface.push_back(ix*count[1]); skin->face.push_back(N + (ix+1)*count[1]); skin->face.push_back((ix+1)*count[1]); skin->face.push_back(ix*count[1]); skin->face.push_back(N + ix*count[1]); skin->face.push_back(N + (ix+1)*count[1]); } // add thin triangles: X direction, iy = count[1]-1 for (int ix=0; ixface.push_back(ix*count[1] + count[1]-1); skin->face.push_back((ix+1)*count[1] + count[1]-1); skin->face.push_back(N + (ix+1)*count[1] + count[1]-1); skin->face.push_back(ix*count[1] + count[1]-1); skin->face.push_back(N + (ix+1)*count[1] + count[1]-1); skin->face.push_back(N + ix*count[1] + count[1]-1); } // add thin triangles: Y direction, ix = 0 for (int iy=0; iyface.push_back(iy); skin->face.push_back(iy+1); skin->face.push_back(N + iy+1); skin->face.push_back(iy); skin->face.push_back(N + iy+1); skin->face.push_back(N + iy); } // add thin triangles: Y direction, ix = count[0]-1 for (int iy=0; iyface.push_back(iy + (count[0]-1)*count[1]); skin->face.push_back(N + iy+1 + (count[0]-1)*count[1]); skin->face.push_back(iy+1 + (count[0]-1)*count[1]); skin->face.push_back(iy + (count[0]-1)*count[1]); skin->face.push_back(N + iy + (count[0]-1)*count[1]); skin->face.push_back(N + iy+1 + (count[0]-1)*count[1]); } // couple with bones if (type==mjCOMPTYPE_PARTICLE || type==mjCOMPTYPE_GRID) { MakeClothBones(model, skin); } else if (type==mjCOMPTYPE_CABLE) { MakeCableBones(model, skin); } } // add bones in 2D void mjCComposite::MakeClothBones(mjCModel* model, mjCSkin* skin) { char txt[100]; int N = count[0]*count[1]; // populate bones for (int ix=0; ixbodyname.push_back(txt); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); // create vertid and vertweight vector vertid; vector vertweight; vertid.push_back(ix*count[1]+iy); vertid.push_back(N + ix*count[1]+iy); vertweight.push_back(1); vertweight.push_back(1); skin->vertid.push_back(vertid); skin->vertweight.push_back(vertweight); } } } void mjCComposite::MakeClothBonesSubgrid(mjCModel* model, mjCSkin* skin) { char txt[100]; // populate bones for (int ix=0; ixbodyname.push_back(txt); skin->bindpos.push_back(ix*spacing); skin->bindpos.push_back(iy*spacing); skin->bindpos.push_back(0); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); // empty vertid and vertweight vector vertid; vector vertweight; skin->vertid.push_back(vertid); skin->vertweight.push_back(vertweight); } } } // add bones to 1D void mjCComposite::MakeCableBones(mjCModel* model, mjCSkin* skin) { char this_body[100]; int N = count[0]*count[1]; // populate bones for (int ix=0; ix=count[0]-2) { mju::sprintf_arr(this_body, "%sB_last", prefix.c_str()); } else { mju::sprintf_arr(this_body, "%sB_%d", prefix.c_str(), ix); } // bind pose if (iy==0) { skin->bodyname.push_back(this_body); skin->bindpos.push_back((ix==count[0]-1) ? -2*def[0].geom.size[0] : 0); skin->bindpos.push_back(-def[0].geom.size[1]); skin->bindpos.push_back(0); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); } else { skin->bodyname.push_back(this_body); skin->bindpos.push_back((ix==count[0]-1) ? -2*def[0].geom.size[0] : 0); skin->bindpos.push_back(def[0].geom.size[1]); skin->bindpos.push_back(0); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); } // create vertid and vertweight skin->vertid.push_back({ix*count[1]+iy, N + ix*count[1]+iy}); skin->vertweight.push_back({1, 1}); } } } void mjCComposite::MakeCableBonesSubgrid(mjCModel* model, mjCSkin* skin) { // populate bones for (int ix=0; ix=count[0]-2) { mju::sprintf_arr(txt, "%sB_last", prefix.c_str()); } else { mju::sprintf_arr(txt, "%sB_%d", prefix.c_str(), ix); } // bind pose if (iy==0) { skin->bindpos.push_back((ix==count[0]-1) ? -2*def[0].geom.size[0] : 0); skin->bindpos.push_back(-def[0].geom.size[1]); skin->bindpos.push_back(0); } else if (iy==2) { skin->bindpos.push_back((ix==count[0]-1) ? -2*def[0].geom.size[0] : 0); skin->bindpos.push_back(def[0].geom.size[1]); skin->bindpos.push_back(0); } else { skin->bindpos.push_back((ix==count[0]-1) ? -2*def[0].geom.size[0] : 0); skin->bindpos.push_back(0); skin->bindpos.push_back(0); } skin->bodyname.push_back(txt); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); // empty vertid and vertweight skin->vertid.push_back({}); skin->vertweight.push_back({}); } } } //------------------------------------- subgrid matrices // C = W * [f; f_x; f_y; f_xy] static const mjtNum subW[16*16] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1, 0, 0, 0, 0, 2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1, 0, 0, 0, 0, 2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1, -3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0, 9, -9, 9, -9, 6, 3, -3, -6, 6, -6, -3, 3, 4, 2, 1, 2, -6, 6, -6, 6, -4, -2, 2, 4, -3, 3, 3, -3, -2, -1, -1, -2, 2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0, -6, 6, -6, 6, -3, -3, 3, 3, -4, 4, 2, -2, -2, -2, -1, -1, 4, -4, 4, -4, 2, 2, -2, -2, 2, -2, -2, 2, 1, 1, 1, 1 }; // left-bottom static const mjtNum subD00[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 5, -1, 9, 1, -1, // f_x 5, -0.5, 13, 0.5, -1, 6, -0.5, 14, 0.5, -1, 6, -1, 10, 1, -1, 5, -1, 6, 1, -1, // f_y 9, -1, 10, 1, -1, 9, -0.5, 11, 0.5, -1, 5, -0.5, 7, 0.5, -1, 9, -1, 6, -1, 5, 1, 10, 1, -1, // f_xy 13, -0.5, 6, -0.5, 5, 0.5, 14, 0.5, -1, 13, -0.25, 7, -0.25, 5, 0.25, 15, 0.25, -1, 9, -0.5, 7, -0.5, 5, 0.5, 11, 0.5, -1 }; // center-bottom static const mjtNum subD10[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 1, -0.5, 9, 0.5, -1, // f_x 5, -0.5, 13, 0.5, -1, 6, -0.5, 14, 0.5, -1, 2, -0.5, 10, 0.5, -1, 5, -1, 6, 1, -1, // f_y 9, -1, 10, 1, -1, 9, -0.5, 11, 0.5, -1, 5, -0.5, 7, 0.5, -1, 9, -0.5, 2, -0.5, 1, 0.5, 10, 0.5, -1, // f_xy 13, -0.5, 6, -0.5, 5, 0.5, 14, 0.5, -1, 13, -0.25, 7, -0.25, 5, 0.25, 15, 0.25, -1, 9, -0.25, 3, -0.25, 1, 0.25, 11, 0.25, -1 }; // right-bottom static const mjtNum subD20[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 1, -0.5, 9, 0.5, -1, // f_x 5, -1, 9, 1, -1, 6, -1, 10, 1, -1, 2, -0.5, 10, 0.5, -1, 5, -1, 6, 1, -1, // f_y 9, -1, 10, 1, -1, 9, -0.5, 11, 0.5, -1, 5, -0.5, 7, 0.5, -1, 9, -0.5, 2, -0.5, 1, 0.5, 10, 0.5, -1, // f_xy 9, -1, 6, -1, 5, 1, 10, 1, -1, 9, -0.5, 7, -0.5, 5, 0.5, 11, 0.5, -1, 9, -0.25, 3, -0.25, 1, 0.25, 11, 0.25, -1 }; // left-center static const mjtNum subD01[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 5, -1, 9, 1, -1, // f_x 5, -0.5, 13, 0.5, -1, 6, -0.5, 14, 0.5, -1, 6, -1, 10, 1, -1, 4, -0.5, 6, 0.5, -1, // f_y 8, -0.5, 10, 0.5, -1, 9, -0.5, 11, 0.5, -1, 5, -0.5, 7, 0.5, -1, 8, -0.5, 6, -0.5, 4, 0.5, 10, 0.5, -1, // f_xy 12, -0.25, 6, -0.25, 4, 0.25, 14, 0.25, -1, 13, -0.25, 7, -0.25, 5, 0.25, 15, 0.25, -1, 9, -0.5, 7, -0.5, 5, 0.5, 11, 0.5, -1 }; // center-center static const mjtNum subD11[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 1, -0.5, 9, 0.5, -1, // f_x 5, -0.5, 13, 0.5, -1, 6, -0.5, 14, 0.5, -1, 2, -0.5, 10, 0.5, -1, 4, -0.5, 6, 0.5, -1, // f_y 8, -0.5, 10, 0.5, -1, 9, -0.5, 11, 0.5, -1, 5, -0.5, 7, 0.5, -1, 8, -0.25, 2, -0.25, 0, 0.25, 10, 0.25, -1, // f_xy 12, -0.25, 6, -0.25, 4, 0.25, 14, 0.25, -1, 13, -0.25, 7, -0.25, 5, 0.25, 15, 0.25, -1, 9, -0.25, 3, -0.25, 1, 0.25, 11, 0.25, -1 }; // right-center static const mjtNum subD21[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 1, -0.5, 9, 0.5, -1, // f_x 5, -1, 9, 1, -1, 6, -1, 10, 1, -1, 2, -0.5, 10, 0.5, -1, 4, -0.5, 6, 0.5, -1, // f_y 8, -0.5, 10, 0.5, -1, 9, -0.5, 11, 0.5, -1, 5, -0.5, 7, 0.5, -1, 8, -0.25, 2, -0.25, 0, 0.25, 10, 0.25, -1, // f_xy 8, -0.5, 6, -0.5, 4, 0.5, 10, 0.5, -1, 9, -0.5, 7, -0.5, 5, 0.5, 11, 0.5, -1, 9, -0.25, 3, -0.25, 1, 0.25, 11, 0.25, -1 }; // left-top static const mjtNum subD02[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 5, -1, 9, 1, -1, // f_x 5, -0.5, 13, 0.5, -1, 6, -0.5, 14, 0.5, -1, 6, -1, 10, 1, -1, 4, -0.5, 6, 0.5, -1, // f_y 8, -0.5, 10, 0.5, -1, 9, -1, 10, 1, -1, 5, -1, 6, 1, -1, 8, -0.5, 6, -0.5, 4, 0.5, 10, 0.5, -1, // f_xy 12, -0.25, 6, -0.25, 4, 0.25, 14, 0.25, -1, 13, -0.5, 6, -0.5, 5, 0.5, 14, 0.5, -1, 9, -1, 6, -1, 5, 1, 10, 1, -1 }; // center-top static const mjtNum subD12[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 1, -0.5, 9, 0.5, -1, // f_x 5, -0.5, 13, 0.5, -1, 6, -0.5, 14, 0.5, -1, 2, -0.5, 10, 0.5, -1, 4, -0.5, 6, 0.5, -1, // f_y 8, -0.5, 10, 0.5, -1, 9, -1, 10, 1, -1, 5, -1, 6, 1, -1, 8, -0.25, 2, -0.25, 0, 0.25, 10, 0.25, -1, // f_xy 12, -0.25, 6, -0.25, 4, 0.25, 14, 0.25, -1, 13, -0.5, 6, -0.5, 5, 0.5, 14, 0.5, -1, 9, -0.5, 2, -0.5, 1, 0.5, 10, 0.5, -1 }; // right-top static const mjtNum subD22[] = { 5, 1, -1, // f 9, 1, -1, 10, 1, -1, 6, 1, -1, 1, -0.5, 9, 0.5, -1, // f_x 5, -1, 9, 1, -1, 6, -1, 10, 1, -1, 2, -0.5, 10, 0.5, -1, 4, -0.5, 6, 0.5, -1, // f_y 8, -0.5, 10, 0.5, -1, 9, -1, 10, 1, -1, 5, -1, 6, 1, -1, 8, -0.25, 2, -0.25, 0, 0.25, 10, 0.25, -1, // f_xy 8, -0.5, 6, -0.5, 4, 0.5, 10, 0.5, -1, 9, -1, 6, -1, 5, 1, 10, 1, -1, 9, -0.5, 2, -0.5, 1, 0.5, 10, 0.5, -1 }; // add skin to 2D, with subgrid void mjCComposite::MakeSkin2Subgrid(mjCModel* model, mjtNum inflate) { // assemble pointers to Dxx matrices const mjtNum* Dp[3][3] = { {subD00, subD01, subD02}, {subD10, subD11, subD12}, {subD20, subD21, subD22} }; // allocate const int N = (2+skinsubgrid)*(2+skinsubgrid); mjtNum* XY = (mjtNum*) mju_malloc(N*16*sizeof(mjtNum)); mjtNum* XY_W = (mjtNum*) mju_malloc(N*16*sizeof(mjtNum)); mjtNum* Weight = (mjtNum*) mju_malloc(9*N*16*sizeof(mjtNum)); mjtNum* D = (mjtNum*) mju_malloc(16*16*sizeof(mjtNum)); // XY matrix const mjtNum step = 1.0/(1+skinsubgrid); int rxy = 0; for (int sx=0; sx<=1+skinsubgrid; sx++) { for (int sy=0; sy<=1+skinsubgrid; sy++) { // compute x, y mjtNum x = sx*step; mjtNum y = sy*step; // make XY-row XY[16*rxy + 0] = 1; XY[16*rxy + 1] = y; XY[16*rxy + 2] = y*y; XY[16*rxy + 3] = y*y*y; XY[16*rxy + 4] = x*1; XY[16*rxy + 5] = x*y; XY[16*rxy + 6] = x*y*y; XY[16*rxy + 7] = x*y*y*y; XY[16*rxy + 8] = x*x*1; XY[16*rxy + 9] = x*x*y; XY[16*rxy + 10] = x*x*y*y; XY[16*rxy + 11] = x*x*y*y*y; XY[16*rxy + 12] = x*x*x*1; XY[16*rxy + 13] = x*x*x*y; XY[16*rxy + 14] = x*x*x*y*y; XY[16*rxy + 15] = x*x*x*y*y*y; // advance row rxy++; } } // XY_W = XY * W mju_mulMatMat(XY_W, XY, subW, N, 16, 16); // Weight matrices for (int dx=0; dx<3; dx++) { for (int dy=0; dy<3; dy++) { // make dense D mju_zero(D, 16*16); int cnt = 0; int r = 0, c; while (r<16) { // scan row while ((c = mju_round(Dp[dx][dy][cnt]))!=-1) { D[r*16+c] = Dp[dx][dy][cnt+1]; cnt +=2; } // advance r++; cnt++; } // Weight(d) = XY * W * D(d) mju_mulMatMat(Weight + (dx*3+dy)*N*16, XY_W, D, N, 16, 16); } } // add skin, set name and material char txt[100]; mjCSkin* skin = model->AddSkin(); mju::sprintf_arr(txt, "%sSkin", prefix.c_str()); skin->name = txt; skin->set_material(skinmaterial); mjuu_copyvec(skin->rgba, skinrgba, 4); skin->inflate = inflate; skin->group = skingroup; // populate mesh: two sides mjtNum S = spacing/(1+skinsubgrid); int C0 = count[0] + (count[0]-1)*skinsubgrid; int C1 = count[1] + (count[1]-1)*skinsubgrid; int NN = C0*C1; for (int i=0; i<2; i++) { for (int ix=0; ixvert.push_back(ix*S); skin->vert.push_back(iy*S); skin->vert.push_back(0); // texture coordinate if (skintexcoord) { skin->texcoord.push_back(ix/(float)(C0-1)); skin->texcoord.push_back(iy/(float)(C1-1)); } // face if (ixface.push_back(i*NN + ix*C1+iy); skin->face.push_back(i*NN + (ix+1)*C1+iy+(i==1)); skin->face.push_back(i*NN + (ix+1)*C1+iy+(i==0)); skin->face.push_back(i*NN + ix*C1+iy); skin->face.push_back(i*NN + (ix+(i==0))*C1+iy+1); skin->face.push_back(i*NN + (ix+(i==1))*C1+iy+1); } } } } // add thin triangles: X direction, iy = 0 for (int ix=0; ixface.push_back(ix*C1); skin->face.push_back(NN + (ix+1)*C1); skin->face.push_back((ix+1)*C1); skin->face.push_back(ix*C1); skin->face.push_back(NN + ix*C1); skin->face.push_back(NN + (ix+1)*C1); } // add thin triangles: X direction, iy = C1-1 for (int ix=0; ixface.push_back(ix*C1 + C1-1); skin->face.push_back((ix+1)*C1 + C1-1); skin->face.push_back(NN + (ix+1)*C1 + C1-1); skin->face.push_back(ix*C1 + C1-1); skin->face.push_back(NN + (ix+1)*C1 + C1-1); skin->face.push_back(NN + ix*C1 + C1-1); } // add thin triangles: Y direction, ix = 0 for (int iy=0; iyface.push_back(iy); skin->face.push_back(iy+1); skin->face.push_back(NN + iy+1); skin->face.push_back(iy); skin->face.push_back(NN + iy+1); skin->face.push_back(NN + iy); } // add thin triangles: Y direction, ix = C0-1 for (int iy=0; iyface.push_back(iy + (C0-1)*C1); skin->face.push_back(NN + iy+1 + (C0-1)*C1); skin->face.push_back(iy+1 + (C0-1)*C1); skin->face.push_back(iy + (C0-1)*C1); skin->face.push_back(NN + iy + (C0-1)*C1); skin->face.push_back(NN + iy+1 + (C0-1)*C1); } if (type==mjCOMPTYPE_PARTICLE || type==mjCOMPTYPE_GRID) { MakeClothBonesSubgrid(model, skin); } else if (type==mjCOMPTYPE_CABLE) { MakeCableBonesSubgrid(model, skin); } // bind vertices to bones: one big square at a time for (int ix=0; ixvertid[boneid[bi]].push_back(vid); skin->vertid[boneid[bi]].push_back(vid+NN); skin->vertweight[boneid[bi]].push_back((float)w); skin->vertweight[boneid[bi]].push_back((float)w); } } } } } } // free allocations mju_free(XY); mju_free(XY_W); mju_free(Weight); 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]; 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 mjCSkin* skin = model->AddSkin(); mju::sprintf_arr(txt, "%sSkin", prefix.c_str()); skin->name = txt; skin->set_material(skinmaterial); 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_" + 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_" + 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" + 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; ixvert.push_back(0); skin->vert.push_back(0); skin->vert.push_back(0); // texture coordinate if (skintexcoord) { float X = 0, Y = 0; if (xedge) { X = iy/(float)(count[1]-1); Y = iz/(float)(count[2]-1); } else { X = ix/(float)(count[0]-1); Y = iz/(float)(count[2]-1); } skin->texcoord.push_back(X); skin->texcoord.push_back(Y); } // save vertex id in map vmap[txt] = vcnt; vcnt++; } } } } // y-faces MakeSkin3Smooth(skin, count[0], count[2], 0, vmap, "%sB%d_0_%d"); fmt = "%sB%d_" + string(cnt1) + "_%d"; MakeSkin3Smooth(skin, count[0], count[2], 1, vmap, fmt.c_str()); // x-faces MakeSkin3Smooth(skin, count[1], count[2], 1, vmap, "%sB0_%d_%d"); fmt = "%sB" + string(cnt0) + "_%d_%d"; MakeSkin3Smooth(skin, count[1], count[2], 0, vmap, fmt.c_str()); // z-faces, boxy-type MakeSkin3Box(skin, count[0], count[1], 1, vcnt, "%sB%d_%d_0"); fmt = "%sB%d_%d_" + string(cnt2); MakeSkin3Box(skin, count[0], count[1], 0, vcnt, fmt.c_str()); } // smooth else { // generate vertices in map for (int ix=0; ixvert.push_back(0); skin->vert.push_back(0); skin->vert.push_back(0); // texture coordinate if (skintexcoord) { float X = 0, Y = 0; if (xedge) { X = iy/(float)(count[1]-1); Y = iz/(float)(count[2]-1); } else if (yedge) { X = ix/(float)(count[0]-1); Y = iz/(float)(count[2]-1); } else { X = ix/(float)(count[0]-1); Y = iy/(float)(count[1]-1); } skin->texcoord.push_back(X); skin->texcoord.push_back(Y); } // save vertex id in map vmap[txt] = vcnt; vcnt++; } } } } // z-faces MakeSkin3Smooth(skin, count[0], count[1], 1, vmap, "%sB%d_%d_0"); fmt = "%sB%d_%d_" + string(cnt2); MakeSkin3Smooth(skin, count[0], count[1], 0, vmap, fmt.c_str()); // y-faces MakeSkin3Smooth(skin, count[0], count[2], 0, vmap, "%sB%d_0_%d"); fmt = "%sB%d_" + string(cnt1) + "_%d"; MakeSkin3Smooth(skin, count[0], count[2], 1, vmap, fmt.c_str()); // x-faces MakeSkin3Smooth(skin, count[1], count[2], 1, vmap, "%sB0_%d_%d"); fmt = "%sB" + string(cnt0) + "_%d_%d"; MakeSkin3Smooth(skin, count[1], count[2], 0, vmap, fmt.c_str()); } } // make one face of 3D skin, box void mjCComposite::MakeSkin3Box(mjCSkin* skin, int c0, int c1, int side, int& vcnt, const char* format) { char txt[100]; // loop over bodies/vertices of specified face for (int i0=0; i0vert.push_back(0); skin->vert.push_back(0); skin->vert.push_back(0); // texture coordinate if (skintexcoord) { skin->texcoord.push_back(i0/(float)(c0-1)); skin->texcoord.push_back(i1/(float)(c1-1)); } // face if (i0face.push_back(vcnt + i0*c1+i1); skin->face.push_back(vcnt + (i0+1)*c1+i1+(side==1)); skin->face.push_back(vcnt + (i0+1)*c1+i1+(side==0)); skin->face.push_back(vcnt + i0*c1+i1); skin->face.push_back(vcnt + (i0+(side==0))*c1+i1+1); skin->face.push_back(vcnt + (i0+(side==1))*c1+i1+1); } // body name mju::sprintf_arr(txt, format, prefix.c_str(), i0, i1); // bind pose: origin skin->bodyname.push_back(txt); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); // vertid and vertweight vector vertid; vector vertweight; vertid.push_back(vcnt + i0*c1+i1); vertweight.push_back(1); skin->vertid.push_back(vertid); skin->vertweight.push_back(vertweight); } } // update vertex count vcnt += c0*c1; } // make one face of 3D skin, smooth void mjCComposite::MakeSkin3Smooth(mjCSkin* 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; i0face.push_back(vmap.find(txt00)->second); skin->face.push_back(vmap.find(txt10)->second); skin->face.push_back(vmap.find(txt11)->second); skin->face.push_back(vmap.find(txt00)->second); skin->face.push_back(vmap.find(txt11)->second); skin->face.push_back(vmap.find(txt01)->second); } else { skin->face.push_back(vmap.find(txt00)->second); skin->face.push_back(vmap.find(txt01)->second); skin->face.push_back(vmap.find(txt11)->second); skin->face.push_back(vmap.find(txt00)->second); skin->face.push_back(vmap.find(txt11)->second); skin->face.push_back(vmap.find(txt10)->second); } } // bind pose: origin skin->bodyname.push_back(txt00); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindpos.push_back(0); skin->bindquat.push_back(1); skin->bindquat.push_back(0); skin->bindquat.push_back(0); skin->bindquat.push_back(0); // vertid and vertweight vector vertid; vector vertweight; vertid.push_back(vmap.find(txt00)->second); vertweight.push_back(1); skin->vertid.push_back(vertid); skin->vertweight.push_back(vertweight); } } }