Table-driven attribute writing: rebase the writer on the shared rows.
The writer consumes the same generated rows as the reader. mjXWriter::WriteAttrTable drives the mechanical attributes of an element from its mjXAttr rows: each bound field is compared against the class default at the same offset -- the default object is the same struct type, so the rows carry no comparison values -- and attributes equal to their default are skipped. A null default object means the element has no defaults, and every defined value is written. Ranged-arity rows write with trailing-default trimming, which the reader makes round-trip exact by refilling from the same default. Call sites upcast to the private mjs base (the friend declarations permit it; mjCMesh gains the friendship its siblings had); the comparison object is def->X().spec, a freshly-defaulted struct for the sections, or zero-initialized for size, whose spec defaults (-1, auto) are resolved by compilation. Converted: pair, geom, site, joint, camera, light, material, the equality family, both tendon types (the fixed rows are the spatial rows without appearance attributes -- exactly the tag difference), the actuator, flex with its three sub-elements, mesh, skin, option, the six visual sub-sections, statistic and size. The remnants keep names, files, resolved reference strings (the mjC classes null their private base's string pointers; resolved names live behind accessors), and the writing=custom policies the schema declares: compile directives never saved (fromto, springdamper, fitscale), type-dependent lengths and attributes (sizes, joint pos/axis/limited, shellinertia), and alternatives (mass/density, fovy-versus-intrinsics, the plugin-gated gain/bias family). Compiler keeps its write-if-nonzero policy; keyframe keeps its model-sized vectors. Saved files are canonical: attributes follow schema declaration order with remnants trailing, and sections follow the schema's dependency order (statistic before visual, deformable before the contact and equality sections that name flexes, tendon before the equality constraints that name tendons, custom demoted to the data tail). Uniform behavior fixes fall out: default-equal positionals are dropped, dynprm is trimmed like every other ranged vector, and mesh material -- read into the spec but never written -- now survives save/load round trips. Changelog entries ride along. Verified: full suite, doc_test, and the two-tier A/B harness -- saved XML reorders attributes, and every corpus model reloads to a byte-identical binary. PiperOrigin-RevId: 958255003 Change-Id: I5fe7346014450db88b2f3f8680a8f616f7d31266
This commit is contained in:
committed by
Copybara-Service
parent
760477f406
commit
5e99ca6cb8
+155
-368
@@ -38,6 +38,9 @@
|
||||
#include "xml/xml_util.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
// typed attribute rows, generated from mjcf.schema; shared with the reader
|
||||
#include "mjcf_read_table.inc"
|
||||
|
||||
namespace {
|
||||
|
||||
using std::string;
|
||||
@@ -133,14 +136,12 @@ void mjXWriter::OneFlex(XMLElement* elem, const mjCFlex* flex) {
|
||||
|
||||
// common attributes
|
||||
WriteAttrTxt(elem, "name", flex->name);
|
||||
WriteAttr(elem, "radius", 1, &flex->radius, &defflex.radius);
|
||||
WriteAttrTable(elem, static_cast<const mjsFlex*>(flex),
|
||||
static_cast<const mjsFlex*>(&defflex), kFlexAttrs,
|
||||
kFlexAttrsN);
|
||||
if (flex->get_material() != defflex.get_material()) {
|
||||
WriteAttrTxt(elem, "material", flex->get_material());
|
||||
}
|
||||
WriteAttr(elem, "rgba", 4, flex->rgba, defflex.rgba);
|
||||
WriteAttrKey(elem, "flatskin", bool_map, 2, flex->flatskin, defflex.flatskin);
|
||||
WriteAttrInt(elem, "dim", flex->dim, defflex.dim);
|
||||
WriteAttrInt(elem, "group", flex->group, defflex.group);
|
||||
WriteAttr(elem, "cellcount", 3, flex->spec.cellcount, defflex.spec.cellcount);
|
||||
if (flex->spec.order != defflex.spec.order) {
|
||||
string dof_str = "full";
|
||||
@@ -177,20 +178,9 @@ void mjXWriter::OneFlex(XMLElement* elem, const mjCFlex* flex) {
|
||||
|
||||
// contact subelement
|
||||
XMLElement* cont = InsertEnd(elem, "contact");
|
||||
WriteAttrInt(cont, "contype", flex->contype, defflex.contype);
|
||||
WriteAttrInt(cont, "conaffinity", flex->conaffinity, defflex.conaffinity);
|
||||
WriteAttrInt(cont, "condim", flex->condim, defflex.condim);
|
||||
WriteAttrInt(cont, "priority", flex->priority, defflex.priority);
|
||||
WriteAttr(cont, "friction", 3, flex->friction, defflex.friction);
|
||||
WriteAttr(cont, "solmix", 1, &flex->solmix, &defflex.solmix);
|
||||
WriteAttr(cont, "solref", mjNREF, flex->solref, defflex.solref);
|
||||
WriteAttr(cont, "solimp", mjNIMP, flex->solimp, defflex.solimp);
|
||||
WriteAttr(cont, "margin", 1, &flex->margin, &defflex.margin);
|
||||
WriteAttr(cont, "gap", 1, &flex->gap, &defflex.gap);
|
||||
WriteAttrKey(cont, "internal", bool_map, 2, flex->internal, defflex.internal);
|
||||
WriteAttrKey(cont, "selfcollide", flexself_map, 5, flex->selfcollide, defflex.selfcollide);
|
||||
WriteAttrInt(cont, "activelayers", flex->activelayers, defflex.activelayers);
|
||||
WriteAttrKey(cont, "passive", bool_map, 2, flex->passive, defflex.passive);
|
||||
WriteAttrTable(cont, static_cast<const mjsFlex*>(flex),
|
||||
static_cast<const mjsFlex*>(&defflex), kFlexcomp_contactAttrs,
|
||||
kFlexcomp_contactAttrsN);
|
||||
|
||||
// remove contact is no attributes
|
||||
if (!cont->FirstAttribute()) {
|
||||
@@ -199,16 +189,15 @@ void mjXWriter::OneFlex(XMLElement* elem, const mjCFlex* flex) {
|
||||
|
||||
// elasticity subelement
|
||||
XMLElement* elastic = InsertEnd(elem, "elasticity");
|
||||
WriteAttr(elastic, "young", 1, &flex->young, &defflex.young);
|
||||
WriteAttr(elastic, "poisson", 1, &flex->poisson, &defflex.poisson);
|
||||
WriteAttr(elastic, "thickness", 1, &flex->thickness, &defflex.thickness);
|
||||
WriteAttr(elastic, "damping", 1, &flex->damping, &defflex.damping);
|
||||
WriteAttrKey(elastic, "elastic2d", elastic2d_map, 4, flex->elastic2d, defflex.elastic2d);
|
||||
WriteAttrTable(elastic, static_cast<const mjsFlex*>(flex),
|
||||
static_cast<const mjsFlex*>(&defflex), kElasticityAttrs,
|
||||
kElasticityAttrsN);
|
||||
|
||||
// edge subelement
|
||||
XMLElement* edge = InsertEnd(elem, "edge");
|
||||
WriteAttr(edge, "stiffness", 1, &flex->edgestiffness, &defflex.edgestiffness);
|
||||
WriteAttr(edge, "damping", 1, &flex->edgedamping, &defflex.edgedamping);
|
||||
WriteAttrTable(edge, static_cast<const mjsFlex*>(flex),
|
||||
static_cast<const mjsFlex*>(&defflex), kFlex_edgeAttrs,
|
||||
kFlex_edgeAttrsN);
|
||||
|
||||
// remove edge if no attributes
|
||||
if (!edge->FirstAttribute()) {
|
||||
@@ -257,14 +246,11 @@ void mjXWriter::OneMesh(XMLElement* elem, const mjCMesh* mesh, mjCDef* def) {
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
if (mesh->Inertia() != def->Mesh().Inertia()) {
|
||||
WriteAttrTxt(elem, "inertia", FindValue(meshinertia_map, 4, mesh->Inertia()));
|
||||
WriteAttrTable(elem, static_cast<const mjsMesh*>(mesh), &def->Mesh().spec,
|
||||
kMeshAttrs, kMeshAttrsN);
|
||||
if (mesh->Material() != def->Mesh().Material()) {
|
||||
WriteAttrTxt(elem, "material", mesh->Material());
|
||||
}
|
||||
WriteAttr(elem, "refpos", 3, mesh->Refpos(), def->Mesh().Refpos());
|
||||
WriteAttr(elem, "refquat", 4, mesh->Refquat(), def->Mesh().Refquat());
|
||||
WriteAttr(elem, "scale", 3, mesh->Scale(), def->Mesh().Scale());
|
||||
WriteAttrKey(elem, "smoothnormal", bool_map, 2, mesh->SmoothNormal(),
|
||||
def->Mesh().SmoothNormal());
|
||||
}
|
||||
|
||||
|
||||
@@ -272,16 +258,16 @@ void mjXWriter::OneMesh(XMLElement* elem, const mjCMesh* mesh, mjCDef* def) {
|
||||
// write skin
|
||||
void mjXWriter::OneSkin(XMLElement* elem, const mjCSkin* skin) {
|
||||
string text;
|
||||
mjCDef mydef;
|
||||
float zero = 0;
|
||||
mjCSkin defskin;
|
||||
|
||||
// write attributes
|
||||
WriteAttrTxt(elem, "name", skin->name);
|
||||
WriteAttrTxt(elem, "file", skin->File());
|
||||
WriteAttrTxt(elem, "material", skin->get_material());
|
||||
WriteAttrInt(elem, "group", skin->group, 0);
|
||||
WriteAttr(elem, "rgba", 4, skin->rgba, mydef.Geom().rgba);
|
||||
WriteAttr(elem, "inflate", 1, &skin->inflate, &zero);
|
||||
WriteAttrTable(elem, static_cast<const mjsSkin*>(skin),
|
||||
static_cast<const mjsSkin*>(&defskin), kSkinAttrs,
|
||||
kSkinAttrsN);
|
||||
|
||||
// write data if no file
|
||||
if (skin->File().empty()) {
|
||||
@@ -358,15 +344,8 @@ void mjXWriter::OneMaterial(XMLElement* elem, const mjCMaterial* material, mjCDe
|
||||
}
|
||||
}
|
||||
|
||||
WriteAttrKey(elem, "texuniform", bool_map, 2, material->texuniform, def->Material().texuniform);
|
||||
WriteAttr(elem, "texrepeat", 2, material->texrepeat, def->Material().texrepeat);
|
||||
WriteAttr(elem, "emission", 1, &material->emission, &def->Material().emission);
|
||||
WriteAttr(elem, "specular", 1, &material->specular, &def->Material().specular);
|
||||
WriteAttr(elem, "shininess", 1, &material->shininess, &def->Material().shininess);
|
||||
WriteAttr(elem, "reflectance", 1, &material->reflectance, &def->Material().reflectance);
|
||||
WriteAttr(elem, "metallic", 1, &material->metallic, &def->Material().metallic);
|
||||
WriteAttr(elem, "roughness", 1, &material->roughness, &def->Material().roughness);
|
||||
WriteAttr(elem, "rgba", 4, material->rgba, def->Material().rgba);
|
||||
WriteAttrTable(elem, static_cast<const mjsMaterial*>(material),
|
||||
&def->Material().spec, kMaterialAttrs, kMaterialAttrsN);
|
||||
}
|
||||
|
||||
|
||||
@@ -374,8 +353,6 @@ void mjXWriter::OneMaterial(XMLElement* elem, const mjCMaterial* material, mjCDe
|
||||
// write joint
|
||||
void mjXWriter::OneJoint(XMLElement* elem, const mjCJoint* joint, mjCDef* def,
|
||||
string_view classname) {
|
||||
double zero = 0;
|
||||
|
||||
// regular
|
||||
if (!writingdefaults) {
|
||||
WriteAttrTxt(elem, "name", joint->name);
|
||||
@@ -391,43 +368,15 @@ void mjXWriter::OneJoint(XMLElement* elem, const mjCJoint* joint, mjCDef* def,
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
if (joint->type != def->Joint().type) {
|
||||
WriteAttrTxt(elem, "type", FindValue(jointtype_map, jointtype_sz, joint->type));
|
||||
}
|
||||
WriteAttrInt(elem, "group", joint->group, def->Joint().group);
|
||||
WriteAttr(elem, "ref", 1, &joint->ref, &zero);
|
||||
WriteAttr(elem, "springref", 1, &joint->springref, &zero);
|
||||
WriteAttr(elem, "solreflimit", mjNREF, joint->solref_limit, def->Joint().solref_limit, true);
|
||||
WriteAttr(elem, "solimplimit", mjNIMP, joint->solimp_limit, def->Joint().solimp_limit, true);
|
||||
WriteAttr(elem, "solreffriction", mjNREF, joint->solref_friction, def->Joint().solref_friction,
|
||||
true);
|
||||
WriteAttr(elem, "solimpfriction", mjNIMP, joint->solimp_friction, def->Joint().solimp_friction,
|
||||
true);
|
||||
{
|
||||
int nstiff = 1+mjNPOLY;
|
||||
while (nstiff > 1 && joint->stiffness[nstiff-1] == 0
|
||||
&& def->Joint().stiffness[nstiff-1] == 0) nstiff--;
|
||||
WriteAttr(elem, "stiffness", nstiff, joint->stiffness, def->Joint().stiffness);
|
||||
}
|
||||
WriteAttrTable(elem, static_cast<const mjsJoint*>(joint), &def->Joint().spec,
|
||||
kJointAttrs, kJointAttrsN);
|
||||
if (joint->type != mjJNT_FREE) {
|
||||
WriteAttrKey(elem, "limited", FalseTrueAuto_map, 3, joint->limited, def->Joint().limited);
|
||||
}
|
||||
WriteAttr(elem, "range", 2, joint->range, def->Joint().range);
|
||||
if (joint->type != mjJNT_FREE && joint->type != mjJNT_BALL) {
|
||||
WriteAttrKey(elem, "actuatorfrclimited", FalseTrueAuto_map, 3, joint->actfrclimited,
|
||||
def->Joint().actfrclimited);
|
||||
}
|
||||
WriteAttrKey(elem, "actuatorgravcomp", bool_map, 2, joint->actgravcomp, def->Joint().actgravcomp);
|
||||
WriteAttr(elem, "actuatorfrcrange", 2, joint->actfrcrange, def->Joint().actfrcrange);
|
||||
WriteAttr(elem, "margin", 1, &joint->margin, &def->Joint().margin);
|
||||
WriteAttr(elem, "armature", 1, &joint->armature, &def->Joint().armature);
|
||||
{
|
||||
int ndamp = 1+mjNPOLY;
|
||||
while (ndamp > 1 && joint->damping[ndamp-1] == 0
|
||||
&& def->Joint().damping[ndamp-1] == 0) ndamp--;
|
||||
WriteAttr(elem, "damping", ndamp, joint->damping, def->Joint().damping);
|
||||
}
|
||||
WriteAttr(elem, "frictionloss", 1, &joint->frictionloss, &def->Joint().frictionloss);
|
||||
|
||||
// userdata
|
||||
if (writingdefaults) {
|
||||
@@ -485,23 +434,10 @@ void mjXWriter::OneGeom(XMLElement* elem, const mjCGeom* geom, mjCDef* def, stri
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrKey(elem, "type", geomtype_map, mjNGEOMTYPES, geom->type, def->Geom().type);
|
||||
WriteAttrInt(elem, "contype", geom->contype, def->Geom().contype);
|
||||
WriteAttrInt(elem, "conaffinity", geom->conaffinity, def->Geom().conaffinity);
|
||||
WriteAttrInt(elem, "condim", geom->condim, def->Geom().condim);
|
||||
WriteAttrInt(elem, "group", geom->group, def->Geom().group);
|
||||
WriteAttrInt(elem, "priority", geom->priority, def->Geom().priority);
|
||||
WriteAttr(elem, "friction", 3, geom->friction, def->Geom().friction, true);
|
||||
WriteAttr(elem, "solmix", 1, &geom->solmix, &def->Geom().solmix);
|
||||
WriteAttr(elem, "solref", mjNREF, geom->solref, def->Geom().solref, true);
|
||||
WriteAttr(elem, "solimp", mjNIMP, geom->solimp, def->Geom().solimp, true);
|
||||
WriteAttr(elem, "margin", 1, &geom->margin, &def->Geom().margin);
|
||||
WriteAttr(elem, "gap", 1, &geom->gap, &def->Geom().gap);
|
||||
WriteAttr(elem, "surfacevel", 6, geom->surfacevel, def->Geom().surfacevel, true);
|
||||
WriteAttr(elem, "adhesion", 1, &geom->adhesion, &def->Geom().adhesion);
|
||||
WriteAttrTable(elem, static_cast<const mjsGeom*>(geom), &def->Geom().spec,
|
||||
kGeomAttrs, kGeomAttrsN);
|
||||
WriteAttrKey(elem, "fluidshape",
|
||||
fluidshape_map, 2, geom->fluid_ellipsoid, def->Geom().fluid_ellipsoid);
|
||||
WriteAttr(elem, "fluidcoef", 5, geom->fluid_coefs, def->Geom().fluid_coefs);
|
||||
if (geom->type != mjGEOM_MESH) {
|
||||
WriteAttrKey(elem, "shellinertia", bool_map, 2, geom->typeinertia,
|
||||
def->Geom().typeinertia);
|
||||
@@ -514,7 +450,6 @@ void mjXWriter::OneGeom(XMLElement* elem, const mjCGeom* geom, mjCDef* def, stri
|
||||
if (geom->get_material() != def->Geom().get_material()) {
|
||||
WriteAttrTxt(elem, "material", geom->get_material());
|
||||
}
|
||||
WriteAttr(elem, "rgba", 4, geom->rgba, def->Geom().rgba);
|
||||
|
||||
// hfield and mesh attributes
|
||||
if (geom->type == mjGEOM_HFIELD) {
|
||||
@@ -547,7 +482,6 @@ void mjXWriter::OneSite(XMLElement* elem, const mjCSite* site, mjCDef* def, stri
|
||||
if (classname != site->classname && site->classname != "main") {
|
||||
WriteAttrTxt(elem, "class", site->classname);
|
||||
}
|
||||
WriteAttr(elem, "pos", 3, site->pos);
|
||||
WriteAttr(elem, "quat", 4, site->quat, unitq);
|
||||
if (mjGEOMINFO[site->type]) {
|
||||
WriteAttr(elem, "size", mjGEOMINFO[site->type], site->size, def->Site().size);
|
||||
@@ -557,12 +491,11 @@ void mjXWriter::OneSite(XMLElement* elem, const mjCSite* site, mjCDef* def, stri
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrInt(elem, "group", site->group, def->Site().group);
|
||||
WriteAttrKey(elem, "type", geomtype_map, mjNGEOMTYPES, site->type, def->Site().type);
|
||||
WriteAttrTable(elem, static_cast<const mjsSite*>(site), &def->Site().spec,
|
||||
kSiteAttrs, kSiteAttrsN);
|
||||
if (site->get_material() != def->Site().get_material()) {
|
||||
WriteAttrTxt(elem, "material", site->get_material());
|
||||
}
|
||||
WriteAttr(elem, "rgba", 4, site->rgba, def->Site().rgba);
|
||||
|
||||
// userdata
|
||||
if (writingdefaults) {
|
||||
@@ -584,28 +517,12 @@ void mjXWriter::OneCamera(XMLElement* elem, const mjCCamera* camera, mjCDef* def
|
||||
WriteAttrTxt(elem, "class", camera->classname);
|
||||
}
|
||||
WriteAttrTxt(elem, "target", camera->get_targetbody());
|
||||
WriteAttr(elem, "pos", 3, camera->pos);
|
||||
WriteAttr(elem, "quat", 4, camera->quat, unitq);
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttr(elem, "ipd", 1, &camera->ipd, &def->Camera().ipd);
|
||||
WriteAttrKey(elem, "mode", camlight_map, camlight_sz, camera->mode, def->Camera().mode);
|
||||
WriteAttr(elem, "resolution", 2, camera->resolution, def->Camera().resolution);
|
||||
|
||||
// write output attribute if different from default
|
||||
if (camera->output != def->Camera().output) {
|
||||
int data[mjNCAMOUT];
|
||||
int ndata = 0;
|
||||
for (int i = 0; i < mjNCAMOUT; i++) {
|
||||
if (camera->output & camout_map[i].value) {
|
||||
data[ndata++] = camout_map[i].value;
|
||||
}
|
||||
}
|
||||
WriteAttrKeys(elem, "output", camout_map, camout_sz, data, ndata, 0);
|
||||
}
|
||||
|
||||
WriteAttrKey(elem, "projection", projection_map, projection_sz, camera->proj, def->Camera().proj);
|
||||
WriteAttrTable(elem, static_cast<const mjsCamera*>(camera),
|
||||
&def->Camera().spec, kCameraAttrs, kCameraAttrsN);
|
||||
|
||||
// camera intrinsics if specified
|
||||
if (camera->sensor_size[0] > 0 && camera->sensor_size[1] > 0) {
|
||||
@@ -636,28 +553,86 @@ void mjXWriter::OneLight(XMLElement* elem, const mjCLight* light, mjCDef* def,
|
||||
WriteAttrTxt(elem, "class", light->classname);
|
||||
}
|
||||
WriteAttrTxt(elem, "target", light->get_targetbody());
|
||||
WriteAttr(elem, "pos", 3, light->pos);
|
||||
WriteAttr(elem, "dir", 3, light->dir);
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttr(elem, "bulbradius", 1, &light->bulbradius, &def->Light().bulbradius);
|
||||
WriteAttr(elem, "intensity", 1, &light->intensity, &def->Light().intensity);
|
||||
WriteAttr(elem, "range", 1, &light->range, &def->Light().range);
|
||||
WriteAttrTable(elem, static_cast<const mjsLight*>(light), &def->Light().spec,
|
||||
kLightAttrs, kLightAttrsN);
|
||||
WriteAttrKey(elem, "type", lighttype_map, lighttype_sz, light->type, def->Light().type);
|
||||
WriteAttrTxt(elem, "texture", light->get_texture());
|
||||
WriteAttrKey(elem, "castshadow", bool_map, 2, light->castshadow, def->Light().castshadow);
|
||||
WriteAttrKey(elem, "active", bool_map, 2, light->active, def->Light().active);
|
||||
WriteAttr(elem, "attenuation", 3, light->attenuation, def->Light().attenuation);
|
||||
WriteAttr(elem, "cutoff", 1, &light->cutoff, &def->Light().cutoff);
|
||||
WriteAttr(elem, "exponent", 1, &light->exponent, &def->Light().exponent);
|
||||
WriteAttr(elem, "ambient", 3, light->ambient, def->Light().ambient);
|
||||
WriteAttr(elem, "diffuse", 3, light->diffuse, def->Light().diffuse);
|
||||
WriteAttr(elem, "specular", 3, light->specular, def->Light().specular);
|
||||
WriteAttrKey(elem, "mode", camlight_map, camlight_sz, light->mode, def->Light().mode);
|
||||
}
|
||||
|
||||
// write pair
|
||||
// write the mechanical attributes of an element, driven by the same
|
||||
// generated rows the reader uses; see the declaration for the contract
|
||||
template <typename T>
|
||||
void mjXWriter::WriteAttrTable(XMLElement* elem, const T* obj, const T* def,
|
||||
const mjXAttr* rows, int nrow) {
|
||||
const char* live = reinterpret_cast<const char*>(obj);
|
||||
const char* dflt = reinterpret_cast<const char*>(def);
|
||||
for (int i = 0; i < nrow; i++) {
|
||||
const mjXAttr& row = rows[i];
|
||||
if (row.handwrite || (writingdefaults && row.nodefault)) {
|
||||
continue;
|
||||
}
|
||||
const char* base = live + row.offset;
|
||||
// a null default object means the element has no defaults to compare
|
||||
// against: every defined value is written
|
||||
const char* dbase = dflt ? dflt + row.offset : nullptr;
|
||||
const int dkey = dflt ? 0 : -12345; // WriteAttrKey's write-always default
|
||||
switch (row.kind) {
|
||||
case mjXAttr::kInt:
|
||||
WriteAttr(elem, row.attr, row.len, (const int*)base, (const int*)dbase,
|
||||
/*trim=*/!row.exact);
|
||||
break;
|
||||
case mjXAttr::kDouble:
|
||||
WriteAttr(elem, row.attr, row.len, (const double*)base,
|
||||
(const double*)dbase, /*trim=*/!row.exact);
|
||||
break;
|
||||
case mjXAttr::kNum:
|
||||
WriteAttr(elem, row.attr, row.len, (const mjtNum*)base,
|
||||
(const mjtNum*)dbase, /*trim=*/!row.exact);
|
||||
break;
|
||||
case mjXAttr::kFloat:
|
||||
WriteAttr(elem, row.attr, row.len, (const float*)base,
|
||||
(const float*)dbase, /*trim=*/!row.exact);
|
||||
break;
|
||||
case mjXAttr::kEnum:
|
||||
WriteAttrKey(elem, row.attr, row.map, row.mapsz, *(const int*)base,
|
||||
dbase ? *(const int*)dbase : dkey);
|
||||
break;
|
||||
case mjXAttr::kEnumByte:
|
||||
WriteAttrKey(elem, row.attr, row.map, row.mapsz, *(const mjtByte*)base,
|
||||
dbase ? *(const mjtByte*)dbase : dkey);
|
||||
break;
|
||||
case mjXAttr::kBool:
|
||||
WriteAttrKey(elem, row.attr, bool_map, 2, *(const mjtByte*)base,
|
||||
dbase ? *(const mjtByte*)dbase : dkey);
|
||||
break;
|
||||
case mjXAttr::kFlags:
|
||||
if (!dbase || *(const int*)base != *(const int*)dbase) {
|
||||
int value = *(const int*)base;
|
||||
std::vector<int> data;
|
||||
for (int j = 0; j < row.mapsz; j++) {
|
||||
if (value & row.map[j].value) {
|
||||
data.push_back(row.map[j].value);
|
||||
}
|
||||
}
|
||||
if (!data.empty()) {
|
||||
WriteAttrKeys(elem, row.attr, row.map, row.mapsz, data.data(),
|
||||
data.size(), 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// names, strings, files and custom-read attributes: OneX() remnant
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void mjXWriter::OnePair(XMLElement* elem, const mjCPair* pair, mjCDef* def) {
|
||||
// regular
|
||||
if (!writingdefaults) {
|
||||
@@ -670,15 +645,8 @@ void mjXWriter::OnePair(XMLElement* elem, const mjCPair* pair, mjCDef* def) {
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrTxt(elem, "name", pair->name);
|
||||
WriteAttrInt(elem, "condim", pair->condim, def->Pair().spec.condim);
|
||||
WriteAttr(elem, "margin", 1, &pair->margin, &def->Pair().spec.margin);
|
||||
WriteAttr(elem, "gap", 1, &pair->gap, &def->Pair().spec.gap);
|
||||
WriteAttr(elem, "adhesion", 1, &pair->adhesion, &def->Pair().spec.adhesion);
|
||||
WriteAttr(elem, "solref", mjNREF, pair->solref, def->Pair().spec.solref, true);
|
||||
WriteAttr(elem, "solreffriction", mjNREF, pair->solreffriction, def->Pair().spec.solreffriction,
|
||||
true);
|
||||
WriteAttr(elem, "solimp", mjNIMP, pair->solimp, def->Pair().spec.solimp, true);
|
||||
WriteAttr(elem, "friction", 5, pair->friction, def->Pair().spec.friction); // all 5 values
|
||||
WriteAttrTable(elem, static_cast<const mjsPair*>(pair), &def->Pair().spec,
|
||||
kPairAttrs, kPairAttrsN);
|
||||
}
|
||||
|
||||
|
||||
@@ -745,9 +713,9 @@ void mjXWriter::OneEquality(XMLElement* elem, const mjCEquality* equality, mjCDe
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrKey(elem, "active", bool_map, 2, equality->active, def->Equality().active);
|
||||
WriteAttr(elem, "solref", mjNREF, equality->solref, def->Equality().solref, true);
|
||||
WriteAttr(elem, "solimp", mjNIMP, equality->solimp, def->Equality().solimp, true);
|
||||
WriteAttrTable(elem, static_cast<const mjsEquality*>(equality),
|
||||
&def->Equality().spec, kEqualityBaseAttrs,
|
||||
kEqualityBaseAttrsN);
|
||||
}
|
||||
|
||||
|
||||
@@ -764,46 +732,23 @@ void mjXWriter::OneTendon(XMLElement* elem, const mjCTendon* tendon, mjCDef* def
|
||||
}
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrInt(elem, "group", tendon->group, def->Tendon().group);
|
||||
WriteAttr(elem, "solreflimit", mjNREF, tendon->solref_limit, def->Tendon().solref_limit, true);
|
||||
WriteAttr(elem, "solimplimit", mjNIMP, tendon->solimp_limit, def->Tendon().solimp_limit, true);
|
||||
WriteAttr(elem, "solreffriction", mjNREF, tendon->solref_friction, def->Tendon().solref_friction,
|
||||
true);
|
||||
WriteAttr(elem, "solimpfriction", mjNIMP, tendon->solimp_friction, def->Tendon().solimp_friction,
|
||||
true);
|
||||
WriteAttrKey(elem, "limited", FalseTrueAuto_map, 3, tendon->limited, def->Tendon().limited);
|
||||
WriteAttrKey(elem, "actuatorfrclimited", FalseTrueAuto_map, 3, tendon->actfrclimited, def->Tendon().actfrclimited);
|
||||
WriteAttr(elem, "range", 2, tendon->range, def->Tendon().range);
|
||||
WriteAttr(elem, "actuatorfrcrange", 2, tendon->actfrcrange, def->Tendon().actfrcrange);
|
||||
WriteAttr(elem, "margin", 1, &tendon->margin, &def->Tendon().margin);
|
||||
{
|
||||
int nstiff = 1+mjNPOLY;
|
||||
while (nstiff > 1 && tendon->stiffness[nstiff-1] == 0
|
||||
&& def->Tendon().stiffness[nstiff-1] == 0) nstiff--;
|
||||
WriteAttr(elem, "stiffness", nstiff, tendon->stiffness, def->Tendon().stiffness);
|
||||
// defaults and regular; the fixed rows are the spatial rows without the
|
||||
// appearance attributes, which is exactly the tag difference
|
||||
if (fixed) {
|
||||
WriteAttrTable(elem, static_cast<const mjsTendon*>(tendon),
|
||||
&def->Tendon().spec, kFixedAttrs, kFixedAttrsN);
|
||||
} else {
|
||||
WriteAttrTable(elem, static_cast<const mjsTendon*>(tendon),
|
||||
&def->Tendon().spec, kSpatialAttrs, kSpatialAttrsN);
|
||||
}
|
||||
{
|
||||
int ndamp = 1+mjNPOLY;
|
||||
while (ndamp > 1 && tendon->damping[ndamp-1] == 0
|
||||
&& def->Tendon().damping[ndamp-1] == 0) ndamp--;
|
||||
WriteAttr(elem, "damping", ndamp, tendon->damping, def->Tendon().damping);
|
||||
}
|
||||
WriteAttr(elem, "armature", 1, &tendon->armature, &def->Tendon().armature);
|
||||
WriteAttr(elem, "frictionloss", 1, &tendon->frictionloss, &def->Tendon().frictionloss);
|
||||
if (tendon->springlength[0] != tendon->springlength[1] ||
|
||||
def->Tendon().springlength[0] != def->Tendon().springlength[1]) {
|
||||
WriteAttr(elem, "springlength", 2, tendon->springlength, def->Tendon().springlength);
|
||||
} else {
|
||||
WriteAttr(elem, "springlength", 1, tendon->springlength, def->Tendon().springlength);
|
||||
}
|
||||
// spatial only
|
||||
if (!fixed) {
|
||||
if (tendon->get_material() != def->Tendon().get_material()) {
|
||||
WriteAttrTxt(elem, "material", tendon->get_material());
|
||||
}
|
||||
WriteAttr(elem, "width", 1, &tendon->width, &def->Tendon().width);
|
||||
WriteAttr(elem, "rgba", 4, tendon->rgba, def->Tendon().rgba);
|
||||
if (!fixed && tendon->get_material() != def->Tendon().get_material()) {
|
||||
WriteAttrTxt(elem, "material", tendon->get_material());
|
||||
}
|
||||
|
||||
// userdata
|
||||
@@ -859,28 +804,9 @@ void mjXWriter::OneActuator(XMLElement* elem, const mjCActuator* actuator, mjCDe
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrInt(elem, "group", actuator->group, def->Actuator().group);
|
||||
WriteAttrInt(elem, "nsample", actuator->nsample, def->Actuator().nsample);
|
||||
WriteAttrKey(elem, "interp", interp_map, interp_sz, actuator->interp, def->Actuator().interp);
|
||||
WriteAttr(elem, "delay", 1, &actuator->delay, &def->Actuator().delay);
|
||||
WriteAttrKey(elem, "ctrllimited", FalseTrueAuto_map, 3, actuator->ctrllimited, def->Actuator().ctrllimited);
|
||||
WriteAttr(elem, "ctrlrange", 2, actuator->ctrlrange, def->Actuator().ctrlrange);
|
||||
WriteAttrKey(elem, "forcelimited", FalseTrueAuto_map, 3, actuator->forcelimited, def->Actuator().forcelimited);
|
||||
WriteAttr(elem, "forcerange", 2, actuator->forcerange, def->Actuator().forcerange);
|
||||
WriteAttrKey(elem, "actlimited", FalseTrueAuto_map, 3, actuator->actlimited, def->Actuator().actlimited);
|
||||
WriteAttr(elem, "actrange", 2, actuator->actrange, def->Actuator().actrange);
|
||||
WriteAttr(elem, "lengthrange", 2, actuator->lengthrange, def->Actuator().lengthrange);
|
||||
WriteAttr(elem, "gear", 6, actuator->gear, def->Actuator().gear);
|
||||
{
|
||||
int ndamp = 1+mjNPOLY;
|
||||
while (ndamp > 1 && actuator->damping[ndamp-1] == 0
|
||||
&& def->Actuator().damping[ndamp-1] == 0) ndamp--;
|
||||
WriteAttr(elem, "damping", ndamp, actuator->damping, def->Actuator().damping);
|
||||
}
|
||||
WriteAttr(elem, "armature", 1, &actuator->armature, &def->Actuator().armature);
|
||||
WriteAttrTable(elem, static_cast<const mjsActuator*>(actuator),
|
||||
&def->Actuator().spec, kGeneralAttrs, kGeneralAttrsN);
|
||||
WriteAttr(elem, "cranklength", 1, &actuator->cranklength, &def->Actuator().cranklength);
|
||||
WriteAttrKey(elem, "actearly", bool_map, 2, actuator->actearly,
|
||||
def->Actuator().actearly);
|
||||
// special handling of actdim which has default value of -1
|
||||
if (writingdefaults) {
|
||||
WriteAttrInt(elem, "actdim", actuator->actdim, def->Actuator().actdim);
|
||||
@@ -888,8 +814,6 @@ void mjXWriter::OneActuator(XMLElement* elem, const mjCActuator* actuator, mjCDe
|
||||
int default_actdim = (actuator->dyntype != mjDYN_NONE && actuator->dyntype != mjDYN_DCMOTOR);
|
||||
WriteAttrInt(elem, "actdim", actuator->actdim, default_actdim);
|
||||
}
|
||||
WriteAttrKey(elem, "dyntype", dyn_map, dyn_sz, actuator->dyntype, def->Actuator().dyntype);
|
||||
WriteAttr(elem, "dynprm", mjNDYN, actuator->dynprm, def->Actuator().dynprm);
|
||||
|
||||
// plugins: write config attributes
|
||||
if (actuator->plugin.active) {
|
||||
@@ -911,8 +835,6 @@ void mjXWriter::OneActuator(XMLElement* elem, const mjCActuator* actuator, mjCDe
|
||||
}
|
||||
WriteAttrTxt(elem, "input", tokens);
|
||||
}
|
||||
WriteAttr(elem, "velrange", 2, actuator->velrange, def->Actuator().velrange);
|
||||
WriteAttr(elem, "ffrange", 2, actuator->ffrange, def->Actuator().ffrange);
|
||||
WriteAttrKey(elem, "biastype", bias_map, bias_sz, actuator->biastype, def->Actuator().biastype);
|
||||
WriteAttr(elem, "gainprm", mjNGAIN, actuator->gainprm, def->Actuator().gainprm, true);
|
||||
WriteAttr(elem, "biasprm", mjNBIAS, actuator->biasprm, def->Actuator().biasprm, true);
|
||||
@@ -1000,21 +922,21 @@ string mjXWriter::Write(char *error, size_t error_sz) {
|
||||
Compiler(root);
|
||||
Option(root);
|
||||
Size(root);
|
||||
Visual(root);
|
||||
Statistic(root);
|
||||
Visual(root);
|
||||
writingdefaults = true;
|
||||
Default(root, model->Default());
|
||||
writingdefaults = false;
|
||||
Extension(root);
|
||||
Custom(root);
|
||||
Asset(root);
|
||||
Body(InsertEnd(root, "worldbody"), model->GetWorld(), nullptr);
|
||||
Contact(root);
|
||||
Deformable(root);
|
||||
Equality(root);
|
||||
Contact(root);
|
||||
Tendon(root);
|
||||
Equality(root);
|
||||
Actuator(root);
|
||||
Sensor(root);
|
||||
Custom(root);
|
||||
Keyframe(root);
|
||||
|
||||
return WriteDoc(doc, error, error_sz);
|
||||
@@ -1063,39 +985,8 @@ void mjXWriter::Option(XMLElement* root) {
|
||||
|
||||
XMLElement* section = InsertEnd(root, "option");
|
||||
|
||||
// option
|
||||
WriteAttr(section, "timestep", 1, &model->option.timestep, &opt.timestep);
|
||||
WriteAttr(section, "impratio", 1, &model->option.impratio, &opt.impratio);
|
||||
WriteAttr(section, "tolerance", 1, &model->option.tolerance, &opt.tolerance);
|
||||
WriteAttr(section, "ls_tolerance", 1, &model->option.ls_tolerance, &opt.ls_tolerance);
|
||||
WriteAttr(section, "noslip_tolerance", 1, &model->option.noslip_tolerance, &opt.noslip_tolerance);
|
||||
WriteAttr(section, "ccd_tolerance", 1, &model->option.ccd_tolerance, &opt.ccd_tolerance);
|
||||
WriteAttr(section, "sleep_tolerance", 1, &model->option.sleep_tolerance, &opt.sleep_tolerance);
|
||||
WriteAttr(section, "gravity", 3, model->option.gravity, opt.gravity);
|
||||
WriteAttr(section, "wind", 3, model->option.wind, opt.wind);
|
||||
WriteAttr(section, "magnetic", 3, model->option.magnetic, opt.magnetic);
|
||||
WriteAttr(section, "density", 1, &model->option.density, &opt.density);
|
||||
WriteAttr(section, "viscosity", 1, &model->option.viscosity, &opt.viscosity);
|
||||
|
||||
WriteAttr(section, "o_margin", 1, &model->option.o_margin, &opt.o_margin);
|
||||
WriteAttr(section, "o_solref", mjNREF, model->option.o_solref, opt.o_solref);
|
||||
WriteAttr(section, "o_solimp", mjNIMP, model->option.o_solimp, opt.o_solimp);
|
||||
WriteAttr(section, "o_friction", 5, model->option.o_friction, opt.o_friction);
|
||||
|
||||
WriteAttrKey(section, "integrator", integrator_map, integrator_sz,
|
||||
model->option.integrator, opt.integrator);
|
||||
WriteAttrKey(section, "cone", cone_map, cone_sz,
|
||||
model->option.cone, opt.cone);
|
||||
WriteAttrKey(section, "jacobian", jacobian_map, jacobian_sz,
|
||||
model->option.jacobian, opt.jacobian);
|
||||
WriteAttrKey(section, "solver", solver_map, solver_sz,
|
||||
model->option.solver, opt.solver);
|
||||
WriteAttrInt(section, "iterations", model->option.iterations, opt.iterations);
|
||||
WriteAttrInt(section, "ls_iterations", model->option.ls_iterations, opt.ls_iterations);
|
||||
WriteAttrInt(section, "noslip_iterations", model->option.noslip_iterations, opt.noslip_iterations);
|
||||
WriteAttrInt(section, "ccd_iterations", model->option.ccd_iterations, opt.ccd_iterations);
|
||||
WriteAttrInt(section, "sdf_iterations", model->option.sdf_iterations, opt.sdf_iterations);
|
||||
WriteAttrInt(section, "sdf_initpoints", model->option.sdf_initpoints, opt.sdf_initpoints);
|
||||
// option; the freshly-defaulted struct is the comparison object
|
||||
WriteAttrTable(section, &model->option, &opt, kOptionAttrs, kOptionAttrsN);
|
||||
|
||||
// actuator group disable
|
||||
int disabled_groups[31];
|
||||
@@ -1165,20 +1056,16 @@ void mjXWriter::Size(XMLElement* root) {
|
||||
WriteAttrTxt(section, "memory", mju_writeNumBytes(model->memory));
|
||||
}
|
||||
|
||||
// write sizes
|
||||
// deprecated sizes, hand-read into locals with range checks
|
||||
WriteAttrInt(section, "njmax", model->njmax, -1);
|
||||
WriteAttrInt(section, "nconmax", model->nconmax, -1);
|
||||
WriteAttrInt(section, "nstack", model->nstack, -1);
|
||||
WriteAttrInt(section, "nuserdata", model->nuserdata, 0);
|
||||
WriteAttrInt(section, "nkey", model->nkey, 0);
|
||||
WriteAttrInt(section, "nuser_body", model->nuser_body, 0);
|
||||
WriteAttrInt(section, "nuser_jnt", model->nuser_jnt, 0);
|
||||
WriteAttrInt(section, "nuser_geom", model->nuser_geom, 0);
|
||||
WriteAttrInt(section, "nuser_site", model->nuser_site, 0);
|
||||
WriteAttrInt(section, "nuser_cam", model->nuser_cam, 0);
|
||||
WriteAttrInt(section, "nuser_tendon", model->nuser_tendon, 0);
|
||||
WriteAttrInt(section, "nuser_actuator", model->nuser_actuator, 0);
|
||||
WriteAttrInt(section, "nuser_sensor", model->nuser_sensor, 0);
|
||||
|
||||
// write sizes; the spec defaults are -1 (auto), but compilation resolves
|
||||
// them to the actual counts, so the comparison object is zero-initialized
|
||||
mjSpec zerospec = {};
|
||||
WriteAttrTable(section, static_cast<const mjSpec*>(model), &zerospec,
|
||||
kSizeAttrs, kSizeAttrsN);
|
||||
|
||||
// remove entire section if no attributes
|
||||
if (!section->FirstAttribute()) root->DeleteChild(section);
|
||||
@@ -1189,13 +1076,11 @@ void mjXWriter::Size(XMLElement* root) {
|
||||
// statistic section
|
||||
void mjXWriter::Statistic(XMLElement* root) {
|
||||
XMLElement* section = InsertEnd(root, "statistic");
|
||||
mjStatistic* s = &model->stat;
|
||||
|
||||
if (mjuu_defined(s->meaninertia)) WriteAttr(section, "meaninertia", 1, &s->meaninertia);
|
||||
if (mjuu_defined(s->meanmass)) WriteAttr(section, "meanmass", 1, &s->meanmass);
|
||||
if (mjuu_defined(s->meansize)) WriteAttr(section, "meansize", 1, &s->meansize);
|
||||
if (mjuu_defined(s->extent)) WriteAttr(section, "extent", 1, &s->extent);
|
||||
if (mjuu_defined(s->center[0])) WriteAttr(section, "center", 3, s->center);
|
||||
// statistics are unset (mjNAN) rather than defaulted: there is nothing to
|
||||
// compare against, and WriteAttr skips the undefined values by itself
|
||||
WriteAttrTable(section, &model->stat, (const mjStatistic*)nullptr,
|
||||
kStatisticAttrs, kStatisticAttrsN);
|
||||
|
||||
// remove entire section if no attributes
|
||||
if (!section->FirstAttribute()) root->DeleteChild(section);
|
||||
@@ -1207,123 +1092,25 @@ void mjXWriter::Statistic(XMLElement* root) {
|
||||
void mjXWriter::Visual(XMLElement* root) {
|
||||
mjVisual visdef, *vis = &model->visual;
|
||||
mj_defaultVisual(&visdef);
|
||||
XMLElement* elem;
|
||||
|
||||
XMLElement* section = InsertEnd(root, "visual");
|
||||
|
||||
// global
|
||||
elem = InsertEnd(section, "global");
|
||||
WriteAttrInt(elem, "cameraid", vis->global.cameraid, visdef.global.cameraid);
|
||||
WriteAttrKey(elem, "orthographic",
|
||||
bool_map, 2, vis->global.orthographic, visdef.global.orthographic);
|
||||
WriteAttr(elem, "fovy", 1, &vis->global.fovy, &visdef.global.fovy);
|
||||
WriteAttr(elem, "ipd", 1, &vis->global.ipd, &visdef.global.ipd);
|
||||
WriteAttr(elem, "azimuth", 1, &vis->global.azimuth, &visdef.global.azimuth);
|
||||
WriteAttr(elem, "elevation", 1, &vis->global.elevation, &visdef.global.elevation);
|
||||
WriteAttr(elem, "linewidth", 1, &vis->global.linewidth, &visdef.global.linewidth);
|
||||
WriteAttr(elem, "glow", 1, &vis->global.glow, &visdef.global.glow);
|
||||
WriteAttr(elem, "realtime", 1, &vis->global.realtime, &visdef.global.realtime);
|
||||
WriteAttrInt(elem, "offwidth", vis->global.offwidth, visdef.global.offwidth);
|
||||
WriteAttrInt(elem, "offheight", vis->global.offheight, visdef.global.offheight);
|
||||
WriteAttrKey(elem, "ellipsoidinertia",
|
||||
bool_map, 2, vis->global.ellipsoidinertia, visdef.global.ellipsoidinertia);
|
||||
WriteAttrKey(elem, "bvactive", bool_map, 2, vis->global.bvactive, visdef.global.bvactive);
|
||||
if (!elem->FirstAttribute()) {
|
||||
section->DeleteChild(elem);
|
||||
}
|
||||
|
||||
// quality
|
||||
elem = InsertEnd(section, "quality");
|
||||
WriteAttrInt(elem, "shadowsize", vis->quality.shadowsize, visdef.quality.shadowsize);
|
||||
WriteAttrInt(elem, "offsamples", vis->quality.offsamples, visdef.quality.offsamples);
|
||||
WriteAttrInt(elem, "numslices", vis->quality.numslices, visdef.quality.numslices);
|
||||
WriteAttrInt(elem, "numstacks", vis->quality.numstacks, visdef.quality.numstacks);
|
||||
WriteAttrInt(elem, "numquads", vis->quality.numquads, visdef.quality.numquads);
|
||||
if (!elem->FirstAttribute()) {
|
||||
section->DeleteChild(elem);
|
||||
}
|
||||
|
||||
// headlight
|
||||
elem = InsertEnd(section, "headlight");
|
||||
WriteAttr(elem, "ambient", 3, vis->headlight.ambient, visdef.headlight.ambient);
|
||||
WriteAttr(elem, "diffuse", 3, vis->headlight.diffuse, visdef.headlight.diffuse);
|
||||
WriteAttr(elem, "specular", 3, vis->headlight.specular, visdef.headlight.specular);
|
||||
WriteAttrInt(elem, "active", vis->headlight.active, visdef.headlight.active);
|
||||
if (!elem->FirstAttribute()) {
|
||||
section->DeleteChild(elem);
|
||||
}
|
||||
|
||||
// map
|
||||
elem = InsertEnd(section, "map");
|
||||
WriteAttr(elem, "stiffness", 1, &vis->map.stiffness, &visdef.map.stiffness);
|
||||
WriteAttr(elem, "stiffnessrot", 1, &vis->map.stiffnessrot, &visdef.map.stiffnessrot);
|
||||
WriteAttr(elem, "force", 1, &vis->map.force, &visdef.map.force);
|
||||
WriteAttr(elem, "torque", 1, &vis->map.torque, &visdef.map.torque);
|
||||
WriteAttr(elem, "alpha", 1, &vis->map.alpha, &visdef.map.alpha);
|
||||
WriteAttr(elem, "fogstart", 1, &vis->map.fogstart, &visdef.map.fogstart);
|
||||
WriteAttr(elem, "fogend", 1, &vis->map.fogend, &visdef.map.fogend);
|
||||
WriteAttr(elem, "znear", 1, &vis->map.znear, &visdef.map.znear);
|
||||
WriteAttr(elem, "zfar", 1, &vis->map.zfar, &visdef.map.zfar);
|
||||
WriteAttr(elem, "haze", 1, &vis->map.haze, &visdef.map.haze);
|
||||
WriteAttr(elem, "shadowclip", 1, &vis->map.shadowclip, &visdef.map.shadowclip);
|
||||
WriteAttr(elem, "shadowscale", 1, &vis->map.shadowscale, &visdef.map.shadowscale);
|
||||
WriteAttr(elem, "actuatortendon", 1, &vis->map.actuatortendon, &visdef.map.actuatortendon);
|
||||
if (!elem->FirstAttribute()) {
|
||||
section->DeleteChild(elem);
|
||||
}
|
||||
|
||||
// scale
|
||||
elem = InsertEnd(section, "scale");
|
||||
WriteAttr(elem, "forcewidth", 1, &vis->scale.forcewidth, &visdef.scale.forcewidth);
|
||||
WriteAttr(elem, "contactwidth", 1, &vis->scale.contactwidth, &visdef.scale.contactwidth);
|
||||
WriteAttr(elem, "contactheight", 1, &vis->scale.contactheight, &visdef.scale.contactheight);
|
||||
WriteAttr(elem, "connect", 1, &vis->scale.connect, &visdef.scale.connect);
|
||||
WriteAttr(elem, "com", 1, &vis->scale.com, &visdef.scale.com);
|
||||
WriteAttr(elem, "camera", 1, &vis->scale.camera, &visdef.scale.camera);
|
||||
WriteAttr(elem, "light", 1, &vis->scale.light, &visdef.scale.light);
|
||||
WriteAttr(elem, "selectpoint", 1, &vis->scale.selectpoint, &visdef.scale.selectpoint);
|
||||
WriteAttr(elem, "jointlength", 1, &vis->scale.jointlength, &visdef.scale.jointlength);
|
||||
WriteAttr(elem, "jointwidth", 1, &vis->scale.jointwidth, &visdef.scale.jointwidth);
|
||||
WriteAttr(elem, "actuatorlength", 1, &vis->scale.actuatorlength, &visdef.scale.actuatorlength);
|
||||
WriteAttr(elem, "actuatorwidth", 1, &vis->scale.actuatorwidth, &visdef.scale.actuatorwidth);
|
||||
WriteAttr(elem, "framelength", 1, &vis->scale.framelength, &visdef.scale.framelength);
|
||||
WriteAttr(elem, "framewidth", 1, &vis->scale.framewidth, &visdef.scale.framewidth);
|
||||
WriteAttr(elem, "constraint", 1, &vis->scale.constraint, &visdef.scale.constraint);
|
||||
WriteAttr(elem, "slidercrank", 1, &vis->scale.slidercrank, &visdef.scale.slidercrank);
|
||||
WriteAttr(elem, "frustum", 1, &vis->scale.frustum, &visdef.scale.frustum);
|
||||
if (!elem->FirstAttribute()) {
|
||||
section->DeleteChild(elem);
|
||||
}
|
||||
|
||||
// rgba
|
||||
elem = InsertEnd(section, "rgba");
|
||||
WriteAttr(elem, "fog", 4, vis->rgba.fog, visdef.rgba.fog);
|
||||
WriteAttr(elem, "haze", 4, vis->rgba.haze, visdef.rgba.haze);
|
||||
WriteAttr(elem, "force", 4, vis->rgba.force, visdef.rgba.force);
|
||||
WriteAttr(elem, "inertia", 4, vis->rgba.inertia, visdef.rgba.inertia);
|
||||
WriteAttr(elem, "joint", 4, vis->rgba.joint, visdef.rgba.joint);
|
||||
WriteAttr(elem, "actuator", 4, vis->rgba.actuator, visdef.rgba.actuator);
|
||||
WriteAttr(elem, "actuatornegative", 4, vis->rgba.actuatornegative, visdef.rgba.actuatornegative);
|
||||
WriteAttr(elem, "actuatorpositive", 4, vis->rgba.actuatorpositive, visdef.rgba.actuatorpositive);
|
||||
WriteAttr(elem, "com", 4, vis->rgba.com, visdef.rgba.com);
|
||||
WriteAttr(elem, "camera", 4, vis->rgba.camera, visdef.rgba.camera);
|
||||
WriteAttr(elem, "light", 4, vis->rgba.light, visdef.rgba.light);
|
||||
WriteAttr(elem, "selectpoint", 4, vis->rgba.selectpoint, visdef.rgba.selectpoint);
|
||||
WriteAttr(elem, "connect", 4, vis->rgba.connect, visdef.rgba.connect);
|
||||
WriteAttr(elem, "contactpoint", 4, vis->rgba.contactpoint, visdef.rgba.contactpoint);
|
||||
WriteAttr(elem, "contactforce", 4, vis->rgba.contactforce, visdef.rgba.contactforce);
|
||||
WriteAttr(elem, "contactfriction", 4, vis->rgba.contactfriction, visdef.rgba.contactfriction);
|
||||
WriteAttr(elem, "contacttorque", 4, vis->rgba.contacttorque, visdef.rgba.contacttorque);
|
||||
WriteAttr(elem, "contactgap", 4, vis->rgba.contactgap, visdef.rgba.contactgap);
|
||||
WriteAttr(elem, "rangefinder", 4, vis->rgba.rangefinder, visdef.rgba.rangefinder);
|
||||
WriteAttr(elem, "constraint", 4, vis->rgba.constraint, visdef.rgba.constraint);
|
||||
WriteAttr(elem, "slidercrank", 4, vis->rgba.slidercrank, visdef.rgba.slidercrank);
|
||||
WriteAttr(elem, "crankbroken", 4, vis->rgba.crankbroken, visdef.rgba.crankbroken);
|
||||
WriteAttr(elem, "frustum", 4, vis->rgba.frustum, visdef.rgba.frustum);
|
||||
WriteAttr(elem, "bv", 4, vis->rgba.bv, visdef.rgba.bv);
|
||||
WriteAttr(elem, "bvactive", 4, vis->rgba.bvactive, visdef.rgba.bvactive);
|
||||
if (!elem->FirstAttribute()) {
|
||||
section->DeleteChild(elem);
|
||||
// the sub-sections are projections into mjVisual: their rows carry
|
||||
// member-path offsets, so one struct pair drives them all
|
||||
struct { const char* tag; const mjXAttr* rows; int n; } subs[] = {
|
||||
{"global", kGlobalAttrs, kGlobalAttrsN},
|
||||
{"quality", kQualityAttrs, kQualityAttrsN},
|
||||
{"headlight", kHeadlightAttrs, kHeadlightAttrsN},
|
||||
{"map", kMapAttrs, kMapAttrsN},
|
||||
{"scale", kScaleAttrs, kScaleAttrsN},
|
||||
{"rgba", kRgbaAttrs, kRgbaAttrsN},
|
||||
};
|
||||
for (const auto& sub : subs) {
|
||||
XMLElement* elem = InsertEnd(section, sub.tag);
|
||||
WriteAttrTable(elem, vis, &visdef, sub.rows, sub.n);
|
||||
if (!elem->FirstAttribute()) {
|
||||
section->DeleteChild(elem);
|
||||
}
|
||||
}
|
||||
|
||||
// remove entire section if no elements
|
||||
|
||||
@@ -62,6 +62,16 @@ class mjXWriter : public mjXBase {
|
||||
// body/world section
|
||||
void Body(tinyxml2::XMLElement* elem, mjCBody* body, mjCFrame* frame, std::string_view childclass = "");
|
||||
|
||||
// table-driven attribute writing: the mechanical attributes of an element,
|
||||
// driven by the same generated mjXAttr rows the reader uses. obj and def
|
||||
// are the element's bound spec struct and its class-default counterpart;
|
||||
// each bound field is compared against the default at the same offset, so
|
||||
// attributes equal to their default are skipped. Strings, files and
|
||||
// custom-read attributes remain in the OneX() remnants.
|
||||
template <typename T>
|
||||
void WriteAttrTable(tinyxml2::XMLElement* elem, const T* obj, const T* def,
|
||||
const struct mjXAttr* rows, int nrow);
|
||||
|
||||
// single element writers, used in defaults and main body
|
||||
void OneFlex(tinyxml2::XMLElement* elem, const mjCFlex* pflex);
|
||||
void OneMesh(tinyxml2::XMLElement* elem, const mjCMesh* pmesh, mjCDef* def);
|
||||
|
||||
@@ -1270,6 +1270,10 @@ void mjXUtil::WriteAttrKey(XMLElement* elem, std::string name,
|
||||
// write attribute- space-separated keywords
|
||||
void mjXUtil::WriteAttrKeys(XMLElement* elem, std::string name, const mjMap* map,
|
||||
int mapsz, int* data, int ndata, int def) {
|
||||
if (ndata <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip default
|
||||
if (ndata == 1 && data[0] == def) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user