Track authored flags for global attributes

PiperOrigin-RevId: 931585539
Change-Id: Ifdc8c59de6c5a553daf6e0af09d8192aff6b0610
This commit is contained in:
Yuval Tassa
2026-06-13 02:57:52 -07:00
committed by Copybara-Service
parent 0ea9c7cb3a
commit 67a1ea6dca
21 changed files with 823 additions and 195 deletions
+232 -182
View File
@@ -25,17 +25,14 @@
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include <mujoco/mujoco.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjplugin.h>
#include <mujoco/mjtype.h>
#include <mujoco/mjvisualize.h>
#include "engine/engine_plugin.h"
#include "engine/engine_support.h"
#include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h"
#include <mujoco/mjspec.h>
#include "user/user_api.h"
@@ -53,6 +50,81 @@ using std::vector;
using mujoco::user::FilePath;
using tinyxml2::XMLElement;
//---------------------------------- helper utilities ----------------------------------------------
// GetAttrPtr: overload for scalar and pointer fields
template <typename T>
inline auto GetAttrPtr(T& val) -> std::enable_if_t<!std::is_array_v<T>, decltype(&val)> {
if constexpr (std::is_pointer_v<T>) {
return val;
} else {
return &val;
}
}
// GetAttrPtr: overload for array fields
template <typename T, size_t N>
inline T* GetAttrPtr(T (&arr)[N]) { return arr; }
// helper class for reading attributes while recording authored bits
struct Reader {
Reader(XMLElement* xml_node, const void* elem) : xml_node_(xml_node), elem_(elem) {}
template <typename T>
int operator()(const char* attr, int len, T& data, bool required = false, bool exact = true) {
int res = mjXReader::ReadAttr(xml_node_, attr, len, GetAttrPtr(data), text_, required, exact);
if (res) mjs_setAuthored(elem_, &data, 1);
return res;
}
template <typename T>
bool operator()(const char* attr, T& data, const mjMap* map, int mapsz, bool required = false) {
int map_val_temp;
bool res = mjXReader::MapValue(xml_node_, attr, &map_val_temp, map, mapsz, required);
if (res) {
data = static_cast<T>(map_val_temp);
mjs_setAuthored(elem_, &data, 1);
}
return res;
}
bool operator()(const char* attr, int& data, bool required = false) {
bool res = mjXUtil::ReadAttrInt(xml_node_, attr, &data, required);
if (res) mjs_setAuthored(elem_, &data, 1);
return res;
}
bool operator()(const char* attr, mjString* target) {
std::string txt_temp;
bool res = mjXUtil::ReadAttrTxt(xml_node_, attr, txt_temp);
if (res) {
mjs_setString(target, txt_temp.c_str());
mjs_setAuthored(elem_, target, 1);
}
return res;
}
template <typename T>
bool txt(const char* attr, T& target, void (&set_func)(T&, const char*)) {
std::string txt_temp;
bool res = mjXUtil::ReadAttrTxt(xml_node_, attr, txt_temp);
if (res) {
set_func(target, txt_temp.c_str());
mjs_setAuthored(elem_, &target, 1);
}
return res;
}
void set_node(XMLElement* node) { xml_node_ = node; }
XMLElement* xml_node_;
const void* elem_;
std::string text_;
};
void ReadPluginConfigs(tinyxml2::XMLElement* elem, mjsPlugin* p) {
std::map<string, string, std::less<> > config_attribs;
XMLElement* child = FirstChildElement(elem);
@@ -1023,7 +1095,7 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
for (XMLElement* section = FirstChildElement(root, "option"); section;
section = NextSiblingElement(section, "option")) {
Option(section, &spec->option);
Option(section, spec, &spec->option);
}
for (XMLElement* section = FirstChildElement(root, "size"); section;
@@ -1116,73 +1188,49 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
// compiler section parser
void mjXReader::Compiler(XMLElement* section, mjSpec* s) {
string text;
int n;
Reader read(section, s);
// top-level attributes
if (MapValue(section, "autolimits", &n, bool_map, 2)) {
s->compiler.autolimits = (n == 1);
}
ReadAttr(section, "boundmass", 1, &s->compiler.boundmass, text);
ReadAttr(section, "boundinertia", 1, &s->compiler.boundinertia, text);
ReadAttr(section, "settotalmass", 1, &s->compiler.settotalmass, text);
if (MapValue(section, "balanceinertia", &n, bool_map, 2)) {
s->compiler.balanceinertia = (n == 1);
}
if (MapValue(section, "strippath", &n, bool_map, 2)) {
s->strippath = (n == 1);
}
if (MapValue(section, "fitaabb", &n, bool_map, 2)) {
s->compiler.fitaabb = (n == 1);
}
if (MapValue(section, "coordinate", &n, coordinate_map, 2)) {
read("autolimits", s->compiler.autolimits, bool_map, 2);
read("boundmass", 1, s->compiler.boundmass);
read("boundinertia", 1, s->compiler.boundinertia);
read("settotalmass", 1, s->compiler.settotalmass);
read("balanceinertia", s->compiler.balanceinertia, bool_map, 2);
read("strippath", s->strippath, bool_map, 2);
read("fitaabb", s->compiler.fitaabb, bool_map, 2);
if (int n = 0; MapValue(section, "coordinate", &n, coordinate_map, 2)) {
if (n == 1) {
throw mjXError(section, "global coordinates no longer supported. To convert existing models, "
"load and save them in MuJoCo 2.3.3 or older");
}
}
if (MapValue(section, "angle", &n, angle_map, 2)) {
s->compiler.degree = (n == 1);
}
if (ReadAttrTxt(section, "eulerseq", text)) {
if (text.size() != 3) {
read("angle", s->compiler.degree, angle_map, 2);
if (ReadAttrTxt(section, "eulerseq", read.text_)) {
if (read.text_.size() != 3) {
throw mjXError(section, "euler format must have length 3");
}
memcpy(s->compiler.eulerseq, text.c_str(), 3);
memcpy(s->compiler.eulerseq, read.text_.c_str(), 3);
mjs_setAuthored(s, &s->compiler.eulerseq, 1);
}
if (ReadAttrTxt(section, "assetdir", text)) {
mjs_setString(s->compiler.meshdir, text.c_str());
mjs_setString(s->compiler.texturedir, text.c_str());
if (ReadAttrTxt(section, "assetdir", read.text_)) {
mjs_setString(s->compiler.meshdir, read.text_.c_str());
mjs_setString(s->compiler.texturedir, read.text_.c_str());
}
// meshdir and texturedir take precedence over assetdir
string meshdir, texturedir;
if (ReadAttrTxt(section, "meshdir", meshdir)) {
mjs_setString(s->compiler.meshdir, meshdir.c_str());
};
if (ReadAttrTxt(section, "texturedir", texturedir)) {
mjs_setString(s->compiler.texturedir, texturedir.c_str());
}
if (MapValue(section, "discardvisual", &n, bool_map, 2)) {
s->compiler.discardvisual = (n == 1);
}
if (MapValue(section, "usethread", &n, bool_map, 2)) {
s->compiler.usethread = (n == 1);
}
if (MapValue(section, "fusestatic", &n, bool_map, 2)) {
s->compiler.fusestatic = (n == 1);
}
MapValue(section, "inertiafromgeom", &s->compiler.inertiafromgeom, TFAuto_map, 3);
ReadAttr(section, "inertiagrouprange", 2, s->compiler.inertiagrouprange, text);
if (MapValue(section, "alignfree", &n, bool_map, 2)) {
s->compiler.alignfree = (n == 1);
}
if (MapValue(section, "saveinertial", &n, bool_map, 2)) {
s->compiler.saveinertial = (n == 1);
}
read("meshdir", s->compiler.meshdir);
read("texturedir", s->compiler.texturedir);
read("discardvisual", s->compiler.discardvisual, bool_map, 2);
read("usethread", s->compiler.usethread, bool_map, 2);
read("fusestatic", s->compiler.fusestatic, bool_map, 2);
read("inertiafromgeom", s->compiler.inertiafromgeom, TFAuto_map, 3);
read("inertiagrouprange", 2, s->compiler.inertiagrouprange);
read("alignfree", s->compiler.alignfree, bool_map, 2);
read("saveinertial", s->compiler.saveinertial, bool_map, 2);
// lengthrange subelement
XMLElement* elem = FindSubElem(section, "lengthrange");
if (elem) {
int n;
mjLROpt* opt = &(s->compiler.LRopt);
// flags
@@ -1195,6 +1243,7 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* s) {
}
// algorithm parameters
string text;
ReadAttr(elem, "accel", 1, &opt->accel, text);
ReadAttr(elem, "maxforce", 1, &opt->maxforce, text);
ReadAttr(elem, "timeconst", 1, &opt->timeconst, text);
@@ -1208,45 +1257,44 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* s) {
// option section parser
void mjXReader::Option(XMLElement* section, mjOption* opt) {
string text;
int n;
void mjXReader::Option(XMLElement* section, mjSpec* s, mjOption* opt) {
Reader read(section, s);
// read options
ReadAttr(section, "timestep", 1, &opt->timestep, text);
ReadAttr(section, "impratio", 1, &opt->impratio, text);
ReadAttr(section, "tolerance", 1, &opt->tolerance, text);
ReadAttr(section, "ls_tolerance", 1, &opt->ls_tolerance, text);
ReadAttr(section, "noslip_tolerance", 1, &opt->noslip_tolerance, text);
ReadAttr(section, "ccd_tolerance", 1, &opt->ccd_tolerance, text);
ReadAttr(section, "sleep_tolerance", 1, &opt->sleep_tolerance, text);
ReadAttr(section, "gravity", 3, opt->gravity, text);
ReadAttr(section, "wind", 3, opt->wind, text);
ReadAttr(section, "magnetic", 3, opt->magnetic, text);
ReadAttr(section, "density", 1, &opt->density, text);
ReadAttr(section, "viscosity", 1, &opt->viscosity, text);
read("timestep", 1, opt->timestep);
read("impratio", 1, opt->impratio);
read("tolerance", 1, opt->tolerance);
read("ls_tolerance", 1, opt->ls_tolerance);
read("noslip_tolerance", 1, opt->noslip_tolerance);
read("ccd_tolerance", 1, opt->ccd_tolerance);
read("sleep_tolerance", 1, opt->sleep_tolerance);
read("gravity", 3, opt->gravity);
read("wind", 3, opt->wind);
read("magnetic", 3, opt->magnetic);
read("density", 1, opt->density);
read("viscosity", 1, opt->viscosity);
ReadAttr(section, "o_margin", 1, &opt->o_margin, text);
ReadAttr(section, "o_solref", mjNREF, opt->o_solref, text, false, false);
ReadAttr(section, "o_solimp", mjNIMP, opt->o_solimp, text, false, false);
ReadAttr(section, "o_friction", 5, opt->o_friction, text, false, false);
read("o_margin", 1, opt->o_margin);
read("o_solref", mjNREF, opt->o_solref, false, false);
read("o_solimp", mjNIMP, opt->o_solimp, false, false);
read("o_friction", 5, opt->o_friction, false, false);
MapValue(section, "integrator", &opt->integrator, integrator_map, integrator_sz);
MapValue(section, "cone", &opt->cone, cone_map, cone_sz);
MapValue(section, "jacobian", &opt->jacobian, jac_map, jac_sz);
MapValue(section, "solver", &opt->solver, solver_map, solver_sz);
ReadAttrInt(section, "iterations", &opt->iterations);
ReadAttrInt(section, "ls_iterations", &opt->ls_iterations);
ReadAttrInt(section, "noslip_iterations", &opt->noslip_iterations);
ReadAttrInt(section, "ccd_iterations", &opt->ccd_iterations);
ReadAttrInt(section, "sdf_iterations", &opt->sdf_iterations);
ReadAttrInt(section, "sdf_initpoints", &opt->sdf_initpoints);
read("integrator", opt->integrator, integrator_map, integrator_sz);
read("cone", opt->cone, cone_map, cone_sz);
read("jacobian", opt->jacobian, jac_map, jac_sz);
read("solver", opt->solver, solver_map, solver_sz);
read("iterations", opt->iterations);
read("ls_iterations", opt->ls_iterations);
read("noslip_iterations", opt->noslip_iterations);
read("ccd_iterations", opt->ccd_iterations);
read("sdf_iterations", opt->sdf_iterations);
read("sdf_initpoints", opt->sdf_initpoints);
// actuatorgroupdisable
constexpr int num_bitflags = 31;
int disabled_act_groups[num_bitflags];
int num_found = ReadAttr(section, "actuatorgroupdisable", num_bitflags, disabled_act_groups,
text, false, false);
int num_found = read("actuatorgroupdisable", num_bitflags,
disabled_act_groups, false, false);
for (int i=0; i < num_found; i++) {
int group = disabled_act_groups[i];
if (group < 0) {
@@ -1256,15 +1304,19 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) {
throw mjXError(section, "disabled actuator group value cannot exceed 30");
}
opt->disableactuator |= (1 << group);
s->authored.disableactuator |= (1 << group);
}
// read disable sub-element
XMLElement* elem = FindSubElem(section, "flag");
if (elem) {
#define READDSBL(NAME, MASK) \
if (MapValue(elem, NAME, &n, enable_map, 2)) { \
opt->disableflags ^= (opt->disableflags & MASK); \
opt->disableflags |= (n ? 0 : MASK); }
int n = 0;
#define READDSBL(NAME, MASK) \
if (MapValue(elem, NAME, &n, enable_map, 2)) { \
opt->disableflags ^= (opt->disableflags & MASK); \
opt->disableflags |= (n ? 0 : MASK); \
s->authored.disableflags |= MASK; \
}
READDSBL("constraint", mjDSBL_CONSTRAINT)
READDSBL("equality", mjDSBL_EQUALITY)
@@ -1288,10 +1340,12 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) {
READDSBL("multiccd", mjDSBL_MULTICCD)
#undef READDSBL
#define READENBL(NAME, MASK) \
if (MapValue(elem, NAME, &n, enable_map, 2)) { \
opt->enableflags ^= (opt->enableflags & MASK); \
opt->enableflags |= (n ? MASK : 0); }
#define READENBL(NAME, MASK) \
if (MapValue(elem, NAME, &n, enable_map, 2)) { \
opt->enableflags ^= (opt->enableflags & MASK); \
opt->enableflags |= (n ? MASK : 0); \
s->authored.enableflags |= MASK; \
}
READENBL("override", mjENBL_OVERRIDE)
READENBL("energy", mjENBL_ENERGY)
@@ -3247,129 +3301,125 @@ void mjXReader::Custom(XMLElement* section) {
// visual section parser
void mjXReader::Visual(XMLElement* section) {
string text, name;
string name;
XMLElement* elem;
mjVisual* vis = &spec->visual;
int n;
Reader read(section, spec);
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get sub-element name
name = elem->Value();
read.set_node(elem);
// global sub-element
if (name == "global") {
ReadAttrInt(elem, "cameraid", &vis->global.cameraid);
if (MapValue(elem, "orthographic", &n, bool_map, 2)) {
vis->global.orthographic = (n == 1);
}
ReadAttr(elem, "fovy", 1, &vis->global.fovy, text);
ReadAttr(elem, "ipd", 1, &vis->global.ipd, text);
ReadAttr(elem, "azimuth", 1, &vis->global.azimuth, text);
ReadAttr(elem, "elevation", 1, &vis->global.elevation, text);
ReadAttr(elem, "linewidth", 1, &vis->global.linewidth, text);
ReadAttr(elem, "glow", 1, &vis->global.glow, text);
ReadAttrInt(elem, "offwidth", &vis->global.offwidth);
ReadAttrInt(elem, "offheight", &vis->global.offheight);
if (ReadAttr(elem, "realtime", 1, &vis->global.realtime, text)) {
read("cameraid", vis->global.cameraid);
read("orthographic", vis->global.orthographic, bool_map, 2);
read("fovy", 1, vis->global.fovy);
read("ipd", 1, vis->global.ipd);
read("azimuth", 1, vis->global.azimuth);
read("elevation", 1, vis->global.elevation);
read("linewidth", 1, vis->global.linewidth);
read("glow", 1, vis->global.glow);
read("offwidth", vis->global.offwidth);
read("offheight", vis->global.offheight);
if (read("realtime", 1, vis->global.realtime)) {
if (vis->global.realtime <= 0) {
throw mjXError(elem, "realtime must be greater than 0");
}
}
if (MapValue(elem, "ellipsoidinertia", &n, bool_map, 2)) {
vis->global.ellipsoidinertia = (n == 1);
}
if (MapValue(elem, "bvactive", &n, bool_map, 2)) {
vis->global.bvactive = (n == 1);
}
read("ellipsoidinertia", vis->global.ellipsoidinertia, bool_map, 2);
read("bvactive", vis->global.bvactive, bool_map, 2);
}
// quality sub-element
else if (name == "quality") {
ReadAttrInt(elem, "shadowsize", &vis->quality.shadowsize);
ReadAttrInt(elem, "offsamples", &vis->quality.offsamples);
ReadAttrInt(elem, "numslices", &vis->quality.numslices);
ReadAttrInt(elem, "numstacks", &vis->quality.numstacks);
ReadAttrInt(elem, "numquads", &vis->quality.numquads);
read("shadowsize", vis->quality.shadowsize);
read("offsamples", vis->quality.offsamples);
read("numslices", vis->quality.numslices);
read("numstacks", vis->quality.numstacks);
read("numquads", vis->quality.numquads);
}
// headlight sub-element
else if (name == "headlight") {
ReadAttr(elem, "ambient", 3, vis->headlight.ambient, text);
ReadAttr(elem, "diffuse", 3, vis->headlight.diffuse, text);
ReadAttr(elem, "specular", 3, vis->headlight.specular, text);
ReadAttrInt(elem, "active", &vis->headlight.active);
read("ambient", 3, vis->headlight.ambient);
read("diffuse", 3, vis->headlight.diffuse);
read("specular", 3, vis->headlight.specular);
read("active", vis->headlight.active);
}
// map sub-element
else if (name == "map") {
ReadAttr(elem, "stiffness", 1, &vis->map.stiffness, text);
ReadAttr(elem, "stiffnessrot", 1, &vis->map.stiffnessrot, text);
ReadAttr(elem, "force", 1, &vis->map.force, text);
ReadAttr(elem, "torque", 1, &vis->map.torque, text);
ReadAttr(elem, "alpha", 1, &vis->map.alpha, text);
ReadAttr(elem, "fogstart", 1, &vis->map.fogstart, text);
ReadAttr(elem, "fogend", 1, &vis->map.fogend, text);
ReadAttr(elem, "znear", 1, &vis->map.znear, text);
if (vis->map.znear <= 0) {
throw mjXError(elem, "znear must be strictly positive");
read("stiffness", 1, vis->map.stiffness);
read("stiffnessrot", 1, vis->map.stiffnessrot);
read("force", 1, vis->map.force);
read("torque", 1, vis->map.torque);
read("alpha", 1, vis->map.alpha);
read("fogstart", 1, vis->map.fogstart);
read("fogend", 1, vis->map.fogend);
if (read("znear", 1, vis->map.znear)) {
if (vis->map.znear <= 0) {
throw mjXError(elem, "znear must be strictly positive");
}
}
ReadAttr(elem, "zfar", 1, &vis->map.zfar, text);
ReadAttr(elem, "haze", 1, &vis->map.haze, text);
ReadAttr(elem, "shadowclip", 1, &vis->map.shadowclip, text);
ReadAttr(elem, "shadowscale", 1, &vis->map.shadowscale, text);
ReadAttr(elem, "actuatortendon", 1, &vis->map.actuatortendon, text);
read("zfar", 1, vis->map.zfar);
read("haze", 1, vis->map.haze);
read("shadowclip", 1, vis->map.shadowclip);
read("shadowscale", 1, vis->map.shadowscale);
read("actuatortendon", 1, vis->map.actuatortendon);
}
// scale sub-element
else if (name == "scale") {
ReadAttr(elem, "forcewidth", 1, &vis->scale.forcewidth, text);
ReadAttr(elem, "contactwidth", 1, &vis->scale.contactwidth, text);
ReadAttr(elem, "contactheight", 1, &vis->scale.contactheight, text);
ReadAttr(elem, "connect", 1, &vis->scale.connect, text);
ReadAttr(elem, "com", 1, &vis->scale.com, text);
ReadAttr(elem, "camera", 1, &vis->scale.camera, text);
ReadAttr(elem, "light", 1, &vis->scale.light, text);
ReadAttr(elem, "selectpoint", 1, &vis->scale.selectpoint, text);
ReadAttr(elem, "jointlength", 1, &vis->scale.jointlength, text);
ReadAttr(elem, "jointwidth", 1, &vis->scale.jointwidth, text);
ReadAttr(elem, "actuatorlength", 1, &vis->scale.actuatorlength, text);
ReadAttr(elem, "actuatorwidth", 1, &vis->scale.actuatorwidth, text);
ReadAttr(elem, "framelength", 1, &vis->scale.framelength, text);
ReadAttr(elem, "framewidth", 1, &vis->scale.framewidth, text);
ReadAttr(elem, "constraint", 1, &vis->scale.constraint, text);
ReadAttr(elem, "slidercrank", 1, &vis->scale.slidercrank, text);
ReadAttr(elem, "frustum", 1, &vis->scale.frustum, text);
read("forcewidth", 1, vis->scale.forcewidth);
read("contactwidth", 1, vis->scale.contactwidth);
read("contactheight", 1, vis->scale.contactheight);
read("connect", 1, vis->scale.connect);
read("com", 1, vis->scale.com);
read("camera", 1, vis->scale.camera);
read("light", 1, vis->scale.light);
read("selectpoint", 1, vis->scale.selectpoint);
read("jointlength", 1, vis->scale.jointlength);
read("jointwidth", 1, vis->scale.jointwidth);
read("actuatorlength", 1, vis->scale.actuatorlength);
read("actuatorwidth", 1, vis->scale.actuatorwidth);
read("framelength", 1, vis->scale.framelength);
read("framewidth", 1, vis->scale.framewidth);
read("constraint", 1, vis->scale.constraint);
read("slidercrank", 1, vis->scale.slidercrank);
read("frustum", 1, vis->scale.frustum);
}
// rgba sub-element
else if (name == "rgba") {
ReadAttr(elem, "fog", 4, vis->rgba.fog, text);
ReadAttr(elem, "haze", 4, vis->rgba.haze, text);
ReadAttr(elem, "force", 4, vis->rgba.force, text);
ReadAttr(elem, "inertia", 4, vis->rgba.inertia, text);
ReadAttr(elem, "joint", 4, vis->rgba.joint, text);
ReadAttr(elem, "actuator", 4, vis->rgba.actuator, text);
ReadAttr(elem, "actuatornegative", 4, vis->rgba.actuatornegative, text);
ReadAttr(elem, "actuatorpositive", 4, vis->rgba.actuatorpositive, text);
ReadAttr(elem, "com", 4, vis->rgba.com, text);
ReadAttr(elem, "camera", 4, vis->rgba.camera, text);
ReadAttr(elem, "light", 4, vis->rgba.light, text);
ReadAttr(elem, "selectpoint", 4, vis->rgba.selectpoint, text);
ReadAttr(elem, "connect", 4, vis->rgba.connect, text);
ReadAttr(elem, "contactpoint", 4, vis->rgba.contactpoint, text);
ReadAttr(elem, "contactforce", 4, vis->rgba.contactforce, text);
ReadAttr(elem, "contactfriction", 4, vis->rgba.contactfriction, text);
ReadAttr(elem, "contacttorque", 4, vis->rgba.contacttorque, text);
ReadAttr(elem, "contactgap", 4, vis->rgba.contactgap, text);
ReadAttr(elem, "rangefinder", 4, vis->rgba.rangefinder, text);
ReadAttr(elem, "constraint", 4, vis->rgba.constraint, text);
ReadAttr(elem, "slidercrank", 4, vis->rgba.slidercrank, text);
ReadAttr(elem, "crankbroken", 4, vis->rgba.crankbroken, text);
ReadAttr(elem, "frustum", 4, vis->rgba.frustum, text);
ReadAttr(elem, "bv", 4, vis->rgba.bv, text);
ReadAttr(elem, "bvactive", 4, vis->rgba.bvactive, text);
read("fog", 4, vis->rgba.fog);
read("haze", 4, vis->rgba.haze);
read("force", 4, vis->rgba.force);
read("inertia", 4, vis->rgba.inertia);
read("joint", 4, vis->rgba.joint);
read("actuator", 4, vis->rgba.actuator);
read("actuatornegative", 4, vis->rgba.actuatornegative);
read("actuatorpositive", 4, vis->rgba.actuatorpositive);
read("com", 4, vis->rgba.com);
read("camera", 4, vis->rgba.camera);
read("light", 4, vis->rgba.light);
read("selectpoint", 4, vis->rgba.selectpoint);
read("connect", 4, vis->rgba.connect);
read("contactpoint", 4, vis->rgba.contactpoint);
read("contactforce", 4, vis->rgba.contactforce);
read("contactfriction", 4, vis->rgba.contactfriction);
read("contacttorque", 4, vis->rgba.contacttorque);
read("contactgap", 4, vis->rgba.contactgap);
read("rangefinder", 4, vis->rgba.rangefinder);
read("constraint", 4, vis->rgba.constraint);
read("slidercrank", 4, vis->rgba.slidercrank);
read("crankbroken", 4, vis->rgba.crankbroken);
read("frustum", 4, vis->rgba.frustum);
read("bv", 4, vis->rgba.bv);
read("bvactive", 4, vis->rgba.bvactive);
}
// advance to next element