Add mjmFlex to C API.

PiperOrigin-RevId: 606264589
Change-Id: Ifd94f5241a3eaaf23653b136cece81c054f43202
This commit is contained in:
Alessio Quaglino
2024-02-12 08:46:57 -08:00
committed by Copybara-Service
parent ab209c41be
commit b224367a9b
11 changed files with 368 additions and 203 deletions
+48 -2
View File
@@ -19,6 +19,7 @@
#include <mujoco/mujoco.h>
#include "user/user_model.h"
#include "user/user_objects.h"
#include "xml/xml_util.h"
@@ -114,6 +115,15 @@ mjmLight* mjm_addLight(mjmBody* bodyspec, void* defspec) {
// add flex to model
mjmFlex* mjm_addFlex(void* model) {
mjCModel* modelC = static_cast<mjCModel*>(model);
mjCFlex* flex = modelC->AddFlex();
return &flex->spec;
}
// add frame to body
mjmFrame* mjm_addFrame(mjmBody* bodyspec, mjmFrame* parentframe) {
mjCFrame* parentframeC = 0;
@@ -292,8 +302,44 @@ void mjm_setString(mjString dest, const char* text) {
// split text and copy into string array
void mjm_setStringVec(mjStringVec dest, const char* text) {
std::vector<std::string>* v = reinterpret_cast<std::vector<std::string>*>(dest);
mjXUtil::String2Vector(text, *v);
}
// add text entry to destination string vector
void mjm_addToStringVec(mjStringVec dest, const char* text) {
std::vector<std::string>* v = reinterpret_cast<std::vector<std::string>*>(dest);
v->push_back(std::string(text));
}
// set int array
void mjm_setInt(mjIntVec dest, const int* array, int size) {
std::vector<int>* v = reinterpret_cast<std::vector<int>*>(dest);
v->assign(size, 0.0);
for (int i = 0; i < size; ++i) {
(*v)[i] = array[i];
}
}
// set float array
void mjm_setFloat(mjFloatVec dest, const float* array, int size) {
std::vector<float>* v = reinterpret_cast<std::vector<float>*>(dest);
v->assign(size, 0.0);
for (int i = 0; i < size; ++i) {
(*v)[i] = array[i];
}
}
// set double array
void mjm_setDouble(mjDouble dest, const double* array, int size) {
void mjm_setDouble(mjDoubleVec dest, const double* array, int size) {
std::vector<double>* v = reinterpret_cast<std::vector<double>*>(dest);
v->assign(size, 0.0);
for (int i = 0; i < size; ++i) {
@@ -312,7 +358,7 @@ const char* mjm_getString(const mjString source) {
// get double array
const double* mjm_getDouble(const mjDouble source, int* size) {
const double* mjm_getDouble(const mjDoubleVec source, int* size) {
std::vector<double>* v = reinterpret_cast<std::vector<double>*>(source);
if (size) {
*size = v->size();
+73 -11
View File
@@ -30,7 +30,10 @@ extern "C" {
typedef struct _mjElement* mjElement;
typedef struct _mjString* mjString;
typedef struct _mjDouble* mjDouble;
typedef struct _mjStringVec* mjStringVec;
typedef struct _mjIntVec* mjIntVec;
typedef struct _mjDoubleVec* mjDoubleVec;
typedef struct _mjFloatVec* mjFloatVec;
//---------------------------------- enum types (mjt) ----------------------------------------------
@@ -80,7 +83,7 @@ typedef struct _mjmBody { // body specification
// other
mjtByte mocap; // is this a mocap body
double gravcomp; // gravity compensation
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjtByte explicitinertial; // whether to save the body with explicit inertial clause
mjmPlugin plugin; // passive force plugin
mjString info; // message appended to compiler errors
@@ -131,7 +134,7 @@ typedef struct _mjmJoint { // joint specification
// other
int group; // group
double urdfeffort; // effort (urdf)
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjString info; // message appended to compiler errors
} mjmJoint;
@@ -179,7 +182,7 @@ typedef struct _mjmGeom { // geom specification
mjString hfieldname; // heightfield attached to geom
mjString meshname; // mesh attached to geom
double fitscale; // scale mesh uniformly
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjmPlugin plugin; // sdf plugin
mjString info; // message appended to compiler errors
} mjmGeom;
@@ -204,7 +207,7 @@ typedef struct _mjmSite { // site specification
float rgba[4]; // rgba when material is omitted
// other
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjString info; // message appended to compiler errors
} mjmSite;
@@ -233,7 +236,7 @@ typedef struct _mjmCamera { // camera specification
float principal_pixel[2]; // principal point (pixel)
// other
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjString info; // message appended to compiler errors
} mjmCamera;
@@ -265,6 +268,47 @@ typedef struct _mjmLight { // light specification
} mjmLight;
typedef struct _mjmFlex {
mjElement element; // internal, do not modify
mjString name; // name
mjString classname; // class name
// contact properties
int contype; // contact type
int conaffinity; // contact affinity
int condim; // contact dimensionality
int priority; // contact priority
double friction[3]; // one-sided friction coefficients: slide, roll, spin
double solmix; // solver mixing for contact pairs
mjtNum solref[mjNREF]; // solver reference
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist<margin-gap
// other properties
int dim; // element dimensionality
double radius; // radius around primitive element
bool internal; // enable internal collisions
bool flatskin; // render flex skin with flat shading
int selfcollide; // mode for flex self colllision
int activelayers; // number of active element layers in 3D
int group; // group for visualizatioh
double edgestiffness; // edge stiffness
double edgedamping; // edge damping
float rgba[4]; // rgba when material is omitted
mjString material; // name of material used for rendering
// mesh properties
mjStringVec vertbody; // vertex body names
mjDoubleVec vert; // vertex positions
mjIntVec elem; // element vertex ids
mjFloatVec texcoord; // vertex texture coordinates
// other
mjString info; // message appended to compiler errors
} mjmFlex;
typedef struct _mjmMaterial { // material specification
mjElement element; // internal, do not modify
mjString name; // name
@@ -323,7 +367,7 @@ typedef struct _mjmTendon { // tendon specification
int group; // group
// other
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjString info; // message appended to errors
} mjmTendon;
@@ -373,7 +417,7 @@ typedef struct _mjmActuator { // actuator specification
// other
int group; // group
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjmPlugin plugin; // actuator plugin
mjString info; // message appended to compiler errors
} mjmActuator;
@@ -401,7 +445,7 @@ typedef struct _mjmSensor { // sensor specfication
double noise; // noise stdev
// other
mjDouble userdata; // user data
mjDoubleVec userdata; // user data
mjmPlugin plugin; // sensor plugin
mjString info; // message appended to compiler errors
} mjmSensor;
@@ -442,6 +486,9 @@ MJAPI mjmLight* mjm_addLight(mjmBody* body, void* defspec);
// Add frame to body.
MJAPI mjmFrame* mjm_addFrame(mjmBody* body, mjmFrame* parentframe);
// Add flex to model.
MJAPI mjmFlex* mjm_addFlex(void* model);
// Add material to model.
MJAPI mjmMaterial* mjm_addMaterial(void* model, void* defspec);
@@ -490,14 +537,26 @@ MJAPI int mjm_getId(mjElement element);
// Copy text to destination string.
MJAPI void mjm_setString(mjString dest, const char* text);
// Split text to entries and copy to destination string vector.
MJAPI void mjm_setStringVec(mjStringVec dest, const char* text);
// Add text entry to destination string vector.
MJAPI void mjm_addToStringVec(mjStringVec dest, const char* text);
// Copy int array to destination vector.
MJAPI void mjm_setInt(mjIntVec dest, const int* array, int size);
// Copy float array to destination vector.
MJAPI void mjm_setFloat(mjFloatVec dest, const float* array, int size);
// Copy double array to destination vector.
MJAPI void mjm_setDouble(mjDouble dest, const double* array, int size);
MJAPI void mjm_setDouble(mjDoubleVec dest, const double* array, int size);
// Get string contents.
MJAPI const char* mjm_getString(mjString source);
// Get double array contents and optionally its size.
MJAPI const double* mjm_getDouble(mjDouble source, int* size);
MJAPI const double* mjm_getDouble(mjDoubleVec source, int* size);
// Set default.
MJAPI void mjm_setDefault(mjElement element, void* defspec);
@@ -532,6 +591,9 @@ MJAPI void mjm_defaultCamera(mjmCamera& camera);
// Default light attributes.
MJAPI void mjm_defaultLight(mjmLight& light);
// Default flex attributes.
MJAPI void mjm_defaultFlex(mjmFlex& flex);
// Default material attributes.
MJAPI void mjm_defaultMaterial(mjmMaterial& material);
+34 -27
View File
@@ -78,6 +78,8 @@ mjCFlexcomp::mjCFlexcomp(void) {
// make flexcomp object
bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz) {
def.flex.CopyFromSpec();
bool radial = (type==mjFCOMPTYPE_BOX ||
type==mjFCOMPTYPE_CYLINDER ||
type==mjFCOMPTYPE_ELLIPSOID);
@@ -98,7 +100,7 @@ bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz
}
// check spacing
double minspace = 2*def.flex.radius + def.flex.margin;
double minspace = 2*def.flex.spec.radius + def.flex.spec.margin;
if (!direct) {
if (spacing[0]<minspace ||
spacing[1]<minspace ||
@@ -156,7 +158,7 @@ bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz
}
// get dim and check
int dim = def.flex.dim;
int dim = def.flex.spec.dim;
if (dim<1 || dim>3) {
return comperr(error, "Invalid dim, must be between 1 and 3", error_sz);
}
@@ -164,7 +166,7 @@ bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz
// force flatskin shading for box, cylinder and 3D grid
if (type==mjFCOMPTYPE_BOX || type==mjFCOMPTYPE_CYLINDER ||
(type==mjFCOMPTYPE_GRID && dim==3)) {
def.flex.flatskin = true;
def.flex.spec.flatskin = true;
}
// check pin sizes
@@ -368,21 +370,22 @@ bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz
}
// create flex, copy parameters
mjCFlex* pf = model->AddFlex();
int id = pf->id;
*pf = def.flex;
pf->model = model;
pf->id = id;
pf->name = name;
pf->elem = element;
if (!centered) {
pf->vert = point;
}
pf->texcoord = texcoord;
mjCFlex* flex = model->AddFlex();
mjmFlex* pf = &flex->spec;
int id = flex->id;
*flex = def.flex;
flex->PointToLocal();
flex->model = model;
flex->id = id;
mjm_setString(pf->name, name.c_str());
mjm_setInt(pf->elem, element.data(), element.size());
mjm_setFloat(pf->texcoord, texcoord.data(), texcoord.size());
// rigid: set parent name, nothing else to do
if (rigid) {
pf->vertbody.push_back(std::string(mjm_getString(body->name)).c_str());
mjm_addToStringVec(pf->vertbody, mjm_getString(body->name));
return true;
}
@@ -399,7 +402,7 @@ bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz
// pinned: parent body
if (pinned[i]) {
pf->vertbody.push_back(std::string(mjm_getString(body->name)).c_str());
mjm_addToStringVec(pf->vertbody, mjm_getString(body->name));
// add plugin
if (plugin_instance) {
@@ -456,13 +459,13 @@ bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz
char txt[100];
mju::sprintf_arr(txt, "%s_%d", name.c_str(), i);
mjm_setString(pb->name, txt);
pf->vertbody.push_back(std::string(mjm_getString(pb->name)).c_str());
mjm_addToStringVec(pf->vertbody, mjm_getString(pb->name));
// clear flex vertex coordinates if allocated
if (!centered) {
pf->vert[3*i] = 0;
pf->vert[3*i+1] = 0;
pf->vert[3*i+2] = 0;
point[3*i] = 0;
point[3*i+1] = 0;
point[3*i+2] = 0;
}
// add plugin
@@ -476,6 +479,10 @@ bool mjCFlexcomp::Make(mjCModel* model, mjmBody* body, char* error, int error_sz
}
}
if (!centered) {
mjm_setDouble(pf->vert, point.data(), point.size());
}
// create edge equality constraint
if (equality) {
mjmEquality* pe = mjm_addEquality(model, &def);
@@ -502,7 +509,7 @@ int mjCFlexcomp::GridID(int ix, int iy, int iz) {
// make grid
bool mjCFlexcomp::MakeGrid(char* error, int error_sz) {
int dim = def.flex.dim;
int dim = def.flex.spec.dim;
bool hastex = texcoord.empty();
// 1D
@@ -683,7 +690,7 @@ bool mjCFlexcomp::MakeBox(char* error, int error_sz) {
double pos[3];
// set 3D
def.flex.dim = 3;
def.flex.spec.dim = 3;
// add center point
point.push_back(0);
@@ -815,7 +822,7 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, char* error, int error_sz) {
}
// check dim
if (def.flex.dim!=2) {
if (def.flex.spec.dim!=2) {
return comperr(error, "Flex dim must be 2 in for mesh", error_sz);
}
@@ -1027,7 +1034,7 @@ void mjCFlexcomp::LoadGMSH(mjCModel* model, mjResource* resource) {
if (entityDim<1 || entityDim>3) {
throw mjCError(NULL, "Entity must be 1D, 2D or 3D");
}
def.flex.dim = entityDim;
def.flex.spec.dim = entityDim;
// read and discard node tags; require range from minNodeTag to maxNodeTag
for (size_t i=0; i<numNodes; i++) {
@@ -1079,7 +1086,7 @@ void mjCFlexcomp::LoadGMSH(mjCModel* model, mjResource* resource) {
if (entityDim<1 || entityDim>3) {
throw mjCError(NULL, "Entity must be 1D, 2D or 3D");
}
def.flex.dim = entityDim;
def.flex.spec.dim = entityDim;
// check section byte size
if (nodeend-nodebegin < 52+numNodes*4*8) {
@@ -1127,7 +1134,7 @@ void mjCFlexcomp::LoadGMSH(mjCModel* model, mjResource* resource) {
}
// dimensionality must be same as nodes
if (entityDim!=def.flex.dim) {
if (entityDim!=def.flex.spec.dim) {
throw mjCError(NULL, "Inconsistent dimensionality in Elements");
}
@@ -1176,7 +1183,7 @@ void mjCFlexcomp::LoadGMSH(mjCModel* model, mjResource* resource) {
}
// dimensionality must be same as nodes
if (entityDim!=def.flex.dim) {
if (entityDim!=def.flex.spec.dim) {
throw mjCError(NULL, "Inconsistent dimensionality in Elements");
}
+25 -1
View File
@@ -167,7 +167,31 @@ void mjm_defaultLight(mjmLight& light) {
// Default material attributes.
// default flex attributes
void mjm_defaultFlex(mjmFlex& flex) {
memset(&flex, 0, sizeof(mjmFlex));
// set contact defaults
flex.contype = 1;
flex.conaffinity = 1;
flex.condim = 3;
mjuu_setvec(flex.friction, 1, 0.005, 0.0001);
flex.solmix = 1.0;
mj_defaultSolRefImp(flex.solref, flex.solimp);
// set other defaults
flex.dim = 2;
flex.radius = 0.005;
flex.internal = true;
flex.selfcollide = mjFLEXSELF_AUTO;
flex.activelayers = 1;
flex.rgba[0] = flex.rgba[1] = flex.rgba[2] = 0.5f;
flex.rgba[3] = 1.0f;
}
// default material attributes
void mjm_defaultMaterial(mjmMaterial& material) {
memset(&material, 0, sizeof(mjmMaterial));
material.texuniform = false;
+77 -66
View File
@@ -2222,34 +2222,11 @@ constexpr int eledge[3][6][2] = {{{ 0, 1}, {-1, -1}, {-1, -1},
// constructor
mjCFlex::mjCFlex(mjCModel* _model) {
mjm_defaultFlex(spec);
// set model
model = _model;
// set contact defaults
contype = 1;
conaffinity = 1;
condim = 3;
priority = 0;
mjuu_setvec(friction, 1, 0.005, 0.0001);
solmix = 1.0;
mj_defaultSolRefImp(solref, solimp);
margin = 0;
gap = 0;
// set other defaults
dim = 2;
radius = 0.005;
internal = true;
flatskin = false;
selfcollide = mjFLEXSELF_AUTO;
activelayers = 1;
group = 0;
edgestiffness = 0;
edgedamping = 0;
material_.clear();
rgba[0] = rgba[1] = rgba[2] = 0.5f;
rgba[3] = 1.0f;
// clear internal variables
nvert = 0;
nedge = 0;
@@ -2257,50 +2234,84 @@ mjCFlex::mjCFlex(mjCModel* _model) {
matid = -1;
rigid = false;
centered = false;
PointToLocal();
CopyFromSpec();
}
void mjCFlex::PointToLocal() {
spec.element = (mjElement)this;
spec.name = (mjString)&name;
spec.classname = (mjString)&classname;
spec.material = (mjString)&spec_material_;
spec.vertbody = (mjStringVec)&spec_vertbody_;
spec.vert = (mjDoubleVec)&spec_vert_;
spec.texcoord = (mjFloatVec)&spec_texcoord_;
spec.elem = (mjIntVec)&spec_elem_;
spec.info = (mjString)&info;
}
void mjCFlex::CopyFromSpec() {
*static_cast<mjmFlex*>(this) = spec;
spec.info = (mjString)&info;
material_ = spec_material_;
vertbody_ = spec_vertbody_;
vert_ = spec_vert_;
texcoord_ = spec_texcoord_;
elem_ = spec_elem_;
material = (mjString)&material_;
vertbody = (mjStringVec)&vertbody_;
vert = (mjDoubleVec)&vert_;
texcoord = (mjFloatVec)&texcoord_;
elem = (mjIntVec)&elem_;
}
bool mjCFlex::HasTexcoord() const {
return !texcoord.empty();
return !texcoord_.empty();
}
void mjCFlex::DelTexcoord() {
texcoord.clear();
texcoord_.clear();
}
// compiler
void mjCFlex::Compile(const mjVFS* vfs) {
CopyFromSpec();
// set nelem; check sizes
if (dim<1 || dim>3) {
throw mjCError(this, "dim must be 1, 2 or 3");
}
if (elem.empty()) {
if (elem_.empty()) {
throw mjCError(this, "elem is empty");
}
if (elem.size() % (dim+1)) {
if (elem_.size() % (dim+1)) {
throw mjCError(this, "elem size must be multiple of (dim+1)");
}
if (vertbody.empty()) {
if (vertbody_.empty()) {
throw mjCError(this, "vertbody is empty");
}
if (vert.size() % 3) {
if (vert_.size() % 3) {
throw mjCError(this, "vert size must be a multiple of 3");
}
if (edgestiffness>0 && dim>1) {
throw mjCError(this, "edge stiffness only available for dim=1, please use elasticity plugins");
}
nelem = (int)elem.size()/(dim+1);
nelem = (int)elem_.size()/(dim+1);
// set nvert, rigid, centered; check size
if (vert.empty()) {
if (vert_.empty()) {
centered = true;
nvert = (int)vertbody.size();
nvert = (int)vertbody_.size();
}
else {
nvert = (int)vert.size()/3;
if (vertbody.size()==1) {
nvert = (int)vert_.size()/3;
if (vertbody_.size()==1) {
rigid = true;
}
}
@@ -2309,14 +2320,14 @@ void mjCFlex::Compile(const mjVFS* vfs) {
}
// check elem vertex ids
for (int i=0; i<(int)elem.size(); i++) {
if (elem[i]<0 || elem[i]>=nvert) {
for (int i=0; i<(int)elem_.size(); i++) {
if (elem_[i]<0 || elem_[i]>=nvert) {
throw mjCError(this, "elem vertex id out of range");
}
}
// check texcoord
if (!texcoord.empty() && texcoord.size()!=2*nvert) {
if (!texcoord_.empty() && texcoord_.size()!=2*nvert) {
throw mjCError(this, "two texture coordinates per vertex expected");
}
@@ -2329,20 +2340,20 @@ void mjCFlex::Compile(const mjVFS* vfs) {
}
// resolve body ids
for (int i=0; i<(int)vertbody.size(); i++) {
mjCBase* pbody = model->FindObject(mjOBJ_BODY, vertbody[i]);
for (int i=0; i<(int)vertbody_.size(); i++) {
mjCBase* pbody = model->FindObject(mjOBJ_BODY, vertbody_[i]);
if (pbody) {
vertbodyid.push_back(pbody->id);
} else {
throw mjCError(this, "unkown body '%s' in flex", vertbody[i].c_str());
throw mjCError(this, "unkown body '%s' in flex", vertbody_[i].c_str());
}
}
// process elements
for (int e=0; e<(int)elem.size()/(dim+1); e++) {
for (int e=0; e<(int)elem_.size()/(dim+1); e++) {
// make sorted copy of element
vector<int> el;
el.assign(elem.begin()+e*(dim+1), elem.begin()+(e+1)*(dim+1));
el.assign(elem_.begin()+e*(dim+1), elem_.begin()+(e+1)*(dim+1));
std::sort(el.begin(), el.end());
// check for repeated vertices
@@ -2367,8 +2378,8 @@ void mjCFlex::Compile(const mjVFS* vfs) {
// determine centered if not already set
if (!centered) {
centered = true;
for (int i=0; i<(int)vert.size(); i++) {
if (vert[i]!=0) {
for (int i=0; i<(int)vert_.size(); i++) {
if (vert_[i]!=0) {
centered = false;
break;
}
@@ -2385,7 +2396,7 @@ void mjCFlex::Compile(const mjVFS* vfs) {
// add vertex offset within body if not centered
if (!centered) {
mjtNum offset[3];
mju_rotVecQuat(offset, vert.data()+3*i, model->bodies[b]->xquat0);
mju_rotVecQuat(offset, vert_.data()+3*i, model->bodies[b]->xquat0);
mju_addTo3(vertxpos.data()+3*i, offset);
}
}
@@ -2394,7 +2405,7 @@ void mjCFlex::Compile(const mjVFS* vfs) {
// faces are (0,1,2); (0,2,3); (0,3,1); (1,3,2)
if (dim==3) {
for (int e=0; e<nelem; e++) {
const int* edata = elem.data() + e*(dim+1);
const int* edata = elem_.data() + e*(dim+1);
mjtNum* v0 = vertxpos.data() + 3*edata[0];
mjtNum* v1 = vertxpos.data() + 3*edata[1];
mjtNum* v2 = vertxpos.data() + 3*edata[2];
@@ -2408,22 +2419,22 @@ void mjCFlex::Compile(const mjVFS* vfs) {
mju_cross(nrm, v01, v02);
if (mju_dot3(nrm, v03)>0) {
// flip orientation
int tmp = elem[e*(dim+1)+1];
elem[e*(dim+1)+1] = elem[e*(dim+1)+2];
elem[e*(dim+1)+2] = tmp;
int tmp = elem_[e*(dim+1)+1];
elem_[e*(dim+1)+1] = elem_[e*(dim+1)+2];
elem_[e*(dim+1)+2] = tmp;
}
}
}
// create edges
std::vector<int> edgeidx(elem.size()*kNumEdges[dim-1]);
std::vector<int> edgeidx(elem_.size()*kNumEdges[dim-1]);
// map from edge vertices to their index in `edges` vector
std::unordered_map<std::pair<int, int>, int, PairHash> edge_indices;
// insert local edges into global vector
for (int f = 0; f < (int)elem.size()/(dim+1); f++) {
int* v = elem.data() + f*(dim+1);
for (int f = 0; f < (int)elem_.size()/(dim+1); f++) {
int* v = elem_.data() + f*(dim+1);
for (int e = 0; e < kNumEdges[dim-1]; e++) {
auto pair = std::pair(
std::min(v[eledge[dim-1][e][0]], v[eledge[dim-1][e][1]]),
@@ -2447,7 +2458,7 @@ void mjCFlex::Compile(const mjVFS* vfs) {
// add plugins
std::string userface, useredge;
mjXUtil::Vector2String(userface, elem);
mjXUtil::Vector2String(userface, elem_);
mjXUtil::Vector2String(useredge, edgeidx);
for (int i=0; i<(int)vertbodyid.size(); i++) {
@@ -2472,12 +2483,12 @@ void mjCFlex::CreateBVH(void) {
int nbvh = 0;
// allocate element bounding boxes
elemaabb.resize(6*nelem);
elemaabb_.resize(6*nelem);
tree.AllocateBoundingVolumes(nelem);
// construct element bounding boxes, add to hierarchy
for (int e=0; e<nelem; e++) {
const int* edata = elem.data() + e*(dim+1);
const int* edata = elem_.data() + e*(dim+1);
// skip inactive in 3D
if (dim==3 && elemlayer[e]>=activelayers) {
@@ -2496,12 +2507,12 @@ void mjCFlex::CreateBVH(void) {
}
// compute aabb (center, size)
elemaabb[6*e+0] = 0.5*(xmax[0]+xmin[0]);
elemaabb[6*e+1] = 0.5*(xmax[1]+xmin[1]);
elemaabb[6*e+2] = 0.5*(xmax[2]+xmin[2]);
elemaabb[6*e+3] = 0.5*(xmax[0]-xmin[0]) + radius;
elemaabb[6*e+4] = 0.5*(xmax[1]-xmin[1]) + radius;
elemaabb[6*e+5] = 0.5*(xmax[2]-xmin[2]) + radius;
elemaabb_[6*e+0] = 0.5*(xmax[0]+xmin[0]);
elemaabb_[6*e+1] = 0.5*(xmax[1]+xmin[1]);
elemaabb_[6*e+2] = 0.5*(xmax[2]+xmin[2]);
elemaabb_[6*e+3] = 0.5*(xmax[0]-xmin[0]) + radius;
elemaabb_[6*e+4] = 0.5*(xmax[1]-xmin[1]) + radius;
elemaabb_[6*e+5] = 0.5*(xmax[2]-xmin[2]) + radius;
// add bounding volume for this element
mjCBoundingVolume* bv = tree.GetBoundingVolume(nbvh++);
@@ -2509,7 +2520,7 @@ void mjCFlex::CreateBVH(void) {
bv->conaffinity = conaffinity;
bv->quat = NULL;
bv->SetId(e);
bv->aabb = elemaabb.data() + 6*e;
bv->aabb = elemaabb_.data() + 6*e;
bv->pos = bv->aabb;
}
@@ -2533,7 +2544,7 @@ void mjCFlex::CreateShellPair(void) {
// element vertices in original (unsorted) order
vector<int> el;
el.assign(elem.begin()+n, elem.begin()+n+dim+1);
el.assign(elem_.begin()+n, elem_.begin()+n+dim+1);
// line: 2 vertex fragments
if (dim==1) {
@@ -2702,7 +2713,7 @@ void mjCFlex::CreateShellPair(void) {
// process both elements
for (int ei=0; ei<2; ei++) {
const int* edata = elem.data() + connectspec[n][ei]*(dim+1);
const int* edata = elem_.data() + connectspec[n][ei]*(dim+1);
// find element vertex that is not in the common fragment
for (int i=0; i<=dim; i++) {
+5 -5
View File
@@ -1995,15 +1995,15 @@ void mjCModel::CopyObjects(mjModel* m) {
m->flex_evpairnum[i] = (int)pfl->evpair.size()/2;
memcpy(m->flex_evpair + 2*evpair_adr, pfl->evpair.data(), pfl->evpair.size()*sizeof(int));
}
if (pfl->texcoord.empty()) {
if (pfl->texcoord_.empty()) {
m->flex_texcoordadr[i] = -1;
} else {
m->flex_texcoordadr[i] = texcoord_adr;
memcpy(m->flex_texcoord + 2*texcoord_adr,
pfl->texcoord.data(), pfl->texcoord.size()*sizeof(float));
pfl->texcoord_.data(), pfl->texcoord_.size()*sizeof(float));
}
m->flex_elemnum[i] = pfl->nelem;
memcpy(m->flex_elem + elemdata_adr, pfl->elem.data(), pfl->elem.size()*sizeof(int));
memcpy(m->flex_elem + elemdata_adr, pfl->elem_.data(), pfl->elem_.size()*sizeof(int));
memcpy(m->flex_elemlayer + elem_adr, pfl->elemlayer.data(), pfl->nelem*sizeof(int));
if (m->flex_shellnum[i]) {
memcpy(m->flex_shell + shelldata_adr, pfl->shell.data(), pfl->shell.size()*sizeof(int));
@@ -2042,7 +2042,7 @@ void mjCModel::CopyObjects(mjModel* m) {
mju_zero(m->flex_vert + 3*vert_adr, 3*pfl->nvert);
}
else {
memcpy(m->flex_vert + 3*vert_adr, pfl->vert.data(), 3*pfl->nvert*sizeof(mjtNum));
memcpy(m->flex_vert + 3*vert_adr, pfl->vert_.data(), 3*pfl->nvert*sizeof(mjtNum));
}
// copy or set vertbodyid
@@ -2073,7 +2073,7 @@ void mjCModel::CopyObjects(mjModel* m) {
elemdata_adr += (pfl->dim+1) * pfl->nelem;
shelldata_adr += (int)pfl->shell.size();
evpair_adr += (int)pfl->evpair.size()/2;
texcoord_adr += (int)pfl->texcoord.size()/2;
texcoord_adr += (int)pfl->texcoord_.size()/2;
bvh_adr += pfl->tree.nbvh;
}
+16 -16
View File
@@ -596,7 +596,7 @@ void mjCBody::PointToLocal() {
spec.element = (mjElement)this;
spec.name = (mjString)&name;
spec.classname = (mjString)&classname;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
spec.plugin.name = (mjString)&plugin_name;
spec.plugin.instance_name = (mjString)&plugin_instance_name;
}
@@ -605,7 +605,7 @@ void mjCBody::PointToLocal() {
void mjCBody::CopyFromSpec() {
*static_cast<mjmBody*>(this) = spec;
userdata_ = spec_userdata_;
userdata = (mjDouble)&userdata_;
userdata = (mjDoubleVec)&userdata_;
spec.info = (mjString)&info;
mju_copy4(alt_.axisangle, alt.axisangle);
mju_copy(alt_.xyaxes, alt.xyaxes, 6);
@@ -1262,7 +1262,7 @@ void mjCJoint::PointToLocal() {
spec.element = (mjElement)this;
spec.name = (mjString)&name;
spec.classname = (mjString)&classname;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
spec.info = (mjString)&info;
}
@@ -1271,7 +1271,7 @@ void mjCJoint::PointToLocal() {
void mjCJoint::CopyFromSpec() {
*static_cast<mjmJoint*>(this) = spec;
userdata_ = spec_userdata_;
userdata = (mjDouble)&spec_userdata_;
userdata = (mjDoubleVec)&spec_userdata_;
}
@@ -1460,7 +1460,7 @@ void mjCGeom::PointToLocal(void) {
spec.name = (mjString)&name;
spec.info = (mjString)&info;
spec.classname = (mjString)&classname;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
spec.material = (mjString)&spec_material_;
spec.meshname = (mjString)&spec_meshname_;
spec.hfieldname = (mjString)&spec_hfieldname_;
@@ -1476,7 +1476,7 @@ void mjCGeom::CopyFromSpec() {
hfieldname_ = spec_hfieldname_;
meshname_ = spec_meshname_;
material_ = spec_material_;
userdata = (mjDouble)&userdata_;
userdata = (mjDoubleVec)&userdata_;
hfieldname = (mjString)&hfieldname_;
meshname = (mjString)&meshname_;
material = (mjString)&material_;
@@ -2061,7 +2061,7 @@ void mjCSite::PointToLocal() {
spec.info = (mjString)&info;
spec.classname = (mjString)&classname;
spec.material = (mjString)&spec_material_;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
}
@@ -2070,7 +2070,7 @@ void mjCSite::CopyFromSpec() {
*static_cast<mjmSite*>(this) = spec;
userdata_ = spec_userdata_;
material_ = spec_material_;
userdata = (mjDouble)&userdata_;
userdata = (mjDoubleVec)&userdata_;
material = (mjString)&material_;
mju_copy4(alt_.axisangle, alt.axisangle);
mju_copy(alt_.xyaxes, alt.xyaxes, 6);
@@ -2201,7 +2201,7 @@ void mjCCamera::PointToLocal() {
spec.element = (mjElement)this;
spec.name = (mjString)&name;
spec.classname = (mjString)&classname;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
spec.targetbody = (mjString)&spec_targetbody_;
spec.info = (mjString)&info;
}
@@ -2212,7 +2212,7 @@ void mjCCamera::CopyFromSpec() {
*static_cast<mjmCamera*>(this) = spec;
userdata_ = spec_userdata_;
targetbody_ = spec_targetbody_;
userdata = (mjDouble)&userdata_;
userdata = (mjDoubleVec)&userdata_;
targetbody = (mjString)&targetbody_;
mju_copy4(alt_.axisangle, alt.axisangle);
mju_copy(alt_.xyaxes, alt.xyaxes, 6);
@@ -3717,7 +3717,7 @@ void mjCTendon::PointToLocal() {
spec.name = (mjString)&name;
spec.classname = (mjString)&classname;
spec.material = (mjString)&spec_material_;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
spec.info = (mjString)&info;
}
@@ -3728,7 +3728,7 @@ void mjCTendon::CopyFromSpec() {
material_ = spec_material_;
userdata_ = spec_userdata_;
material = (mjString)&material_;
userdata = (mjDouble)&userdata_;
userdata = (mjDoubleVec)&userdata_;
}
@@ -4097,7 +4097,7 @@ void mjCActuator::MakePointerLocal() {
spec.element = (mjElement)this;
spec.name = (mjString)&name;
spec.classname = (mjString)&classname;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
spec.target = (mjString)&spec_target_;
spec.refsite = (mjString)&spec_refsite_;
spec.slidersite = (mjString)&spec_slidersite_;
@@ -4114,7 +4114,7 @@ void mjCActuator::CopyFromSpec() {
target_ = spec_target_;
refsite_ = spec_refsite_;
slidersite_ = spec_slidersite_;
userdata = (mjDouble)&userdata_;
userdata = (mjDoubleVec)&userdata_;
target = (mjString)&target_;
refsite = (mjString)&refsite_;
slidersite = (mjString)&slidersite_;
@@ -4359,7 +4359,7 @@ void mjCSensor::MakePointerLocal() {
spec.element = (mjElement)this;
spec.name = (mjString)&name;
spec.classname = (mjString)&classname;
spec.userdata = (mjDouble)&spec_userdata_;
spec.userdata = (mjDoubleVec)&spec_userdata_;
spec.objname = (mjString)&spec_objname_;
spec.refname = (mjString)&spec_refname_;
spec.plugin.name = (mjString)&plugin_name;
@@ -4374,7 +4374,7 @@ void mjCSensor::CopyFromSpec() {
userdata_ = spec_userdata_;
objname_ = spec_objname_;
refname_ = spec_refname_;
userdata = (mjDouble)&userdata_;
userdata = (mjDoubleVec)&userdata_;
objname = (mjString)&objname_;
refname = (mjString)&refname_;
plugin.active = spec.plugin.active;
+29 -33
View File
@@ -566,7 +566,7 @@ class mjCLight : public mjCBase, private mjmLight {
//------------------------- class mjCFlex ----------------------------------------------------------
// Describes a flex
class mjCFlex: public mjCBase {
class mjCFlex: public mjCBase, private mjmFlex {
friend class mjCDef;
friend class mjCModel;
friend class mjCFlexcomp;
@@ -574,41 +574,24 @@ class mjCFlex: public mjCBase {
friend class mjXWriter;
public:
void set_material(std::string _material) { material_ = _material; }
std::string& get_material() { return material_; }
mjmFlex spec;
using mjCBase::name;
using mjCBase::classname;
using mjCBase::info;
// contact properties
int contype; // contact type
int conaffinity; // contact affinity
int condim; // contact dimensionality
int priority; // contact priority
double friction[3]; // one-sided friction coefficients: slide, roll, spin
double solmix; // solver mixing for contact pairs
mjtNum solref[mjNREF]; // solver reference
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist<margin-gap
void CopyFromSpec(void);
void PointToLocal(void);
// other properties
int dim; // element dimensionality
double radius; // radius around primitive element
bool internal; // enable internal collisions
bool flatskin; // render flex skin with flat shading
int selfcollide; // mode for flex self colllision
int activelayers; // number of active element layers in 3D
int group; // group for visualizatioh
double edgestiffness; // edge stiffness
double edgedamping; // edge damping
float rgba[4]; // rgba when material is omitted
// used by mjXWriter and mjCModel
const std::string& get_material() { return material_; }
const std::vector<std::string>& get_vertbody() { return vertbody_; }
const std::vector<double>& get_vert() { return vert_; }
const std::vector<double>& get_elemaabb() { return elemaabb_; }
const std::vector<int>& get_elem() { return elem_; }
const std::vector<float>& get_texcoord() { return texcoord_; }
std::vector<std::string> vertbody; // vertex body names
std::vector<mjtNum> vert; // vertex positions
std::vector<mjtNum> elemaabb; // element bounding volume
std::vector<int> elem; // element vertex ids
std::vector<float> texcoord; // vertex texture coordinates
bool HasTexcoord() const; // texcoord not null
void DelTexcoord(); // delete texcoord
bool HasTexcoord() const; // texcoord not null
void DelTexcoord(); // delete texcoord
private:
mjCFlex(mjCModel* = 0);
@@ -629,7 +612,20 @@ class mjCFlex: public mjCBase {
std::vector<int> evpair; // element-vertex pairs
std::vector<mjtNum> vertxpos; // global vertex positions
mjCBoundingVolumeHierarchy tree; // bounding volume hierarchy
std::vector<mjtNum> elemaabb_; // element bounding volume
// variable-size data
std::vector<std::string> vertbody_; // vertex body names
std::vector<mjtNum> vert_; // vertex positions
std::vector<int> elem_; // element vertex ids
std::vector<float> texcoord_; // vertex texture coordinates
std::string material_; // name of material used for rendering
std::string spec_material_;
std::vector<std::string> spec_vertbody_;
std::vector<mjtNum> spec_vert_;
std::vector<int> spec_elem_;
std::vector<float> spec_texcoord_;
};
+52 -33
View File
@@ -1275,14 +1275,22 @@ void mjXReader::Statistic(XMLElement* section) {
//---------------------------------- one-element parsers -------------------------------------------
// flex element parser
void mjXReader::OneFlex(XMLElement* elem, mjCFlex* pflex) {
string text;
void mjXReader::OneFlex(XMLElement* elem, mjmFlex* pflex) {
string text, name, classname, material;
int n;
// read attributes
ReadAttrTxt(elem, "name", pflex->name);
if (ReadAttrTxt(elem, "name", name)) {
mjm_setString(pflex->name, name.c_str());
}
if (ReadAttrTxt(elem, "classname", classname)) {
mjm_setString(pflex->classname, classname.c_str());
}
if (ReadAttrTxt(elem, "material", material)) {
mjm_setString(pflex->material, material.c_str());
}
ReadAttr(elem, "radius", 1, &pflex->radius, text);
ReadAttrTxt(elem, "material", pflex->get_material());
ReadAttr(elem, "rgba", 4, pflex->rgba, text);
if (MapValue(elem, "flatskin", &n, bool_map, 2)) {
pflex->flatskin = (n==1);
@@ -1292,16 +1300,22 @@ void mjXReader::OneFlex(XMLElement* elem, mjCFlex* pflex) {
// read data vectors
if (ReadAttrTxt(elem, "body", text, true)) {
String2Vector(text, pflex->vertbody);
mjm_setStringVec(pflex->vertbody, text.c_str());
}
if (ReadAttrTxt(elem, "vertex", text)) {
String2Vector(text, pflex->vert);
std::vector<double> vert;
String2Vector(text, vert);
mjm_setDouble(pflex->vert, vert.data(), vert.size());
}
if (ReadAttrTxt(elem, "element", text, true)) {
String2Vector(text, pflex->elem);
std::vector<int> elem;
String2Vector(text, elem);
mjm_setInt(pflex->elem, elem.data(), elem.size());
}
if (ReadAttrTxt(elem, "texcoord", text)) {
String2Vector(text, pflex->texcoord);
std::vector<float> texcoord;
String2Vector(text, texcoord);
mjm_setFloat(pflex->texcoord, texcoord.data(), texcoord.size());
}
// contact subelement
@@ -1331,7 +1345,9 @@ void mjXReader::OneFlex(XMLElement* elem, mjCFlex* pflex) {
ReadAttr(edge, "damping", 1, &pflex->edgedamping, text);
}
GetXMLPos(elem, pflex);
// write error info
mjm_setString(pflex->info,
std::string("line = " + std::to_string(elem->GetLineNum()) + ", column = -1").c_str());
}
@@ -2400,7 +2416,7 @@ void mjXReader::OneComposite(XMLElement* elem, mjmBody* pbody, mjCDef* def) {
// make flexcomp
void mjXReader::OneFlexcomp(XMLElement* elem, mjmBody* pbody) {
string text;
string text, material;
int n;
// create out-of-DOM element
@@ -2417,14 +2433,16 @@ void mjXReader::OneFlexcomp(XMLElement* elem, mjmBody* pbody) {
ReadAttr(elem, "mass", 1, &fcomp.mass, text);
ReadAttr(elem, "inertiabox", 1, &fcomp.inertiabox, text);
ReadAttrTxt(elem, "file", fcomp.file);
ReadAttrTxt(elem, "material", fcomp.def.flex.get_material());
ReadAttr(elem, "rgba", 4, fcomp.def.flex.rgba, text);
if (MapValue(elem, "flatskin", &n, bool_map, 2)) {
fcomp.def.flex.flatskin = (n==1);
if (ReadAttrTxt(elem, "material", material)) {
mjm_setString(fcomp.def.flex.spec.material, material.c_str());
}
ReadAttrInt(elem, "dim", &fcomp.def.flex.dim);
ReadAttr(elem, "radius", 1, &fcomp.def.flex.radius, text);
ReadAttrInt(elem, "group", &fcomp.def.flex.group);
ReadAttr(elem, "rgba", 4, fcomp.def.flex.spec.rgba, text);
if (MapValue(elem, "flatskin", &n, bool_map, 2)) {
fcomp.def.flex.spec.flatskin = (n==1);
}
ReadAttrInt(elem, "dim", &fcomp.def.flex.spec.dim);
ReadAttr(elem, "radius", 1, &fcomp.def.flex.spec.radius, text);
ReadAttrInt(elem, "group", &fcomp.def.flex.spec.group);
// pose
ReadAttr(elem, "pos", 3, fcomp.pos, text);
@@ -2453,28 +2471,28 @@ void mjXReader::OneFlexcomp(XMLElement* elem, mjmBody* pbody) {
}
ReadAttr(edge, "solref", mjNREF, fcomp.def.equality.spec.solref, text, false, false);
ReadAttr(edge, "solimp", mjNIMP, fcomp.def.equality.spec.solimp, text, false, false);
ReadAttr(edge, "stiffness", 1, &fcomp.def.flex.edgestiffness, text);
ReadAttr(edge, "damping", 1, &fcomp.def.flex.edgedamping, text);
ReadAttr(edge, "stiffness", 1, &fcomp.def.flex.spec.edgestiffness, text);
ReadAttr(edge, "damping", 1, &fcomp.def.flex.spec.edgedamping, text);
}
// contact
XMLElement* cont = FirstChildElement(elem, "contact");
if (cont) {
ReadAttrInt(cont, "contype", &fcomp.def.flex.contype);
ReadAttrInt(cont, "conaffinity", &fcomp.def.flex.conaffinity);
ReadAttrInt(cont, "condim", &fcomp.def.flex.condim);
ReadAttrInt(cont, "priority", &fcomp.def.flex.priority);
ReadAttr(cont, "friction", 3, fcomp.def.flex.friction, text, false, false);
ReadAttr(cont, "solmix", 1, &fcomp.def.flex.solmix, text);
ReadAttr(cont, "solref", mjNREF, fcomp.def.flex.solref, text, false, false);
ReadAttr(cont, "solimp", mjNIMP, fcomp.def.flex.solimp, text, false, false);
ReadAttr(cont, "margin", 1, &fcomp.def.flex.margin, text);
ReadAttr(cont, "gap", 1, &fcomp.def.flex.gap, text);
ReadAttrInt(cont, "contype", &fcomp.def.flex.spec.contype);
ReadAttrInt(cont, "conaffinity", &fcomp.def.flex.spec.conaffinity);
ReadAttrInt(cont, "condim", &fcomp.def.flex.spec.condim);
ReadAttrInt(cont, "priority", &fcomp.def.flex.spec.priority);
ReadAttr(cont, "friction", 3, fcomp.def.flex.spec.friction, text, false, false);
ReadAttr(cont, "solmix", 1, &fcomp.def.flex.spec.solmix, text);
ReadAttr(cont, "solref", mjNREF, fcomp.def.flex.spec.solref, text, false, false);
ReadAttr(cont, "solimp", mjNIMP, fcomp.def.flex.spec.solimp, text, false, false);
ReadAttr(cont, "margin", 1, &fcomp.def.flex.spec.margin, text);
ReadAttr(cont, "gap", 1, &fcomp.def.flex.spec.gap, text);
if (MapValue(cont, "internal", &n, bool_map, 2)) {
fcomp.def.flex.internal = (n==1);
fcomp.def.flex.spec.internal = (n==1);
}
MapValue(cont, "selfcollide", &fcomp.def.flex.selfcollide, flexself_map, 5);
ReadAttrInt(cont, "activelayers", &fcomp.def.flex.activelayers);
MapValue(cont, "selfcollide", &fcomp.def.flex.spec.selfcollide, flexself_map, 5);
ReadAttrInt(cont, "activelayers", &fcomp.def.flex.spec.activelayers);
}
// pin
@@ -2639,6 +2657,7 @@ void mjXReader::Default(XMLElement* section, int parentid) {
mjm_finalize(def->material.spec.element);
mjm_finalize(def->equality.spec.element);
mjm_finalize(def->tendon.spec.element);
mjm_finalize(def->flex.spec.element);
// advance
elem = NextSiblingElement(elem);
@@ -3385,7 +3404,7 @@ void mjXReader::Deformable(XMLElement* section) {
// flex sub-element
if (name=="flex") {
// create flex and parse
mjCFlex* pflex = model->AddFlex();
mjmFlex* pflex = mjm_addFlex(model);
OneFlex(elem, pflex);
}
+1 -1
View File
@@ -53,7 +53,7 @@ class mjXReader : public mjXBase {
void Keyframe(tinyxml2::XMLElement* section); // keyframe section
// single element parsers, used in defaults and main body
void OneFlex(tinyxml2::XMLElement* elem, mjCFlex* pflex);
void OneFlex(tinyxml2::XMLElement* elem, mjmFlex* pflex);
void OneMesh(tinyxml2::XMLElement* elem, mjCMesh* pmesh);
void OneSkin(tinyxml2::XMLElement* elem, mjCSkin* pskin);
void OneMaterial(tinyxml2::XMLElement* elem, mjmMaterial* pmaterial);
+8 -8
View File
@@ -100,20 +100,20 @@ void mjXWriter::OneFlex(XMLElement* elem, mjCFlex* pflex) {
WriteAttrInt(elem, "group", pflex->group, defflex.group);
// data vectors
if (!pflex->vertbody.empty()) {
Vector2String(text, pflex->vertbody);
if (!pflex->get_vertbody().empty()) {
Vector2String(text, pflex->get_vertbody());
WriteAttrTxt(elem, "body", text);
}
if (!pflex->vert.empty()) {
Vector2String(text, pflex->vert);
if (!pflex->get_vert().empty()) {
Vector2String(text, pflex->get_vert());
WriteAttrTxt(elem, "vertex", text);
}
if (!pflex->elem.empty()) {
Vector2String(text, pflex->elem);
if (!pflex->get_elem().empty()) {
Vector2String(text, pflex->get_elem());
WriteAttrTxt(elem, "element", text);
}
if (!pflex->texcoord.empty()) {
Vector2String(text, pflex->texcoord);
if (!pflex->get_texcoord().empty()) {
Vector2String(text, pflex->get_texcoord());
WriteAttrTxt(elem, "texcoord", text);
}