From 372aa021d09a4fbf80acf704eae53479b18c18d4 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Mon, 18 Mar 2024 09:24:36 -0700 Subject: [PATCH] Add copy constructor and assignment to mjCModel. PiperOrigin-RevId: 616850780 Change-Id: I7fedab2efa2a90fd7ce952eac4e195f77c5ff5bc --- src/user/user_api.cc | 8 + src/user/user_api.h | 3 + src/user/user_mesh.cc | 39 ++++ src/user/user_model.cc | 197 ++++++++++++++-- src/user/user_model.h | 309 +++++++++++++------------ src/user/user_objects.cc | 460 ++++++++++++++++++++++++++----------- src/user/user_objects.h | 26 ++- test/user/user_api_test.cc | 57 +++-- 8 files changed, 766 insertions(+), 333 deletions(-) diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 8068b1a0..f14e75e3 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -36,6 +36,14 @@ mjSpec* mjm_createSpec() { +// copy model +mjSpec* mjm_copySpec(const mjSpec* s) { + mjCModel* modelC = new mjCModel(*reinterpret_cast(s->element)); + return &modelC->spec; +} + + + // copy back model void mjm_copyBack(mjSpec* s, const mjModel* m) { mjCModel* modelC = reinterpret_cast(s->element); diff --git a/src/user/user_api.h b/src/user/user_api.h index ce164cc3..d5ffb06b 100644 --- a/src/user/user_api.h +++ b/src/user/user_api.h @@ -731,6 +731,9 @@ typedef struct _mjmDefault { // default specification // Create model. MJAPI mjSpec* mjm_createSpec(); +// Copy model. +MJAPI mjSpec* mjm_copySpec(const mjSpec* s); + // Copy back model. MJAPI void mjm_copyBack(mjSpec* s, const mjModel* m); diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index e678cbec..59aad976 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -200,41 +200,62 @@ mjCMesh& mjCMesh::operator=(const mjCMesh& other) { size_t nvert = 3*other.nvert_*sizeof(float); this->vert_ = (float*)mju_malloc(nvert); memcpy(this->vert_, other.vert_, nvert); + } else { + this->vert_ = NULL; } if (other.normal_) { size_t nnormal = 3*other.nnormal_*sizeof(float); this->normal_ = (float*)mju_malloc(nnormal); memcpy(this->normal_, other.normal_, nnormal); + } else { + this->normal_ = NULL; } if (other.center_) { size_t ncenter = 3*other.nface_*sizeof(double); this->center_ = (double*)mju_malloc(ncenter); memcpy(this->center_, other.center_, ncenter); + } else { + this->center_ = NULL; } if (other.texcoord_) { size_t ntexcoord = 2*other.ntexcoord_*sizeof(float); this->texcoord_ = (float*)mju_malloc(ntexcoord); memcpy(this->texcoord_, other.texcoord_, ntexcoord); + } else { + this->texcoord_ = NULL; } if (other.face_) { size_t nface = 3*other.nface_*sizeof(int); this->face_ = (int*)mju_malloc(nface); memcpy(this->face_, other.face_, nface); + } else { + this->face_ = NULL; } if (other.facenormal_) { size_t nfacenormal = 3*other.nface_*sizeof(int); this->facenormal_ = (int*)mju_malloc(nfacenormal); memcpy(this->facenormal_, other.facenormal_, nfacenormal); + } else { + this->facenormal_ = NULL; } if (other.facetexcoord_) { size_t nfacetexcoord = 3*other.nface_*sizeof(int); this->facetexcoord_ = (int*)mju_malloc(nfacetexcoord); memcpy(this->facetexcoord_, other.facetexcoord_, nfacetexcoord); + } else { + this->facetexcoord_ = NULL; } if (other.graph_) { size_t szgraph = szgraph_*sizeof(int); this->graph_ = (int*)mju_malloc(szgraph); memcpy(this->graph_, other.graph_, szgraph); + } else { + this->graph_ = NULL; + } + if (other.plugin.instance) { + mjCPlugin* new_plugin = new mjCPlugin(*reinterpret_cast(other.plugin.instance)); + plugin = new_plugin->spec; + model->plugins.push_back(new_plugin); } } PointToLocal(); @@ -2061,6 +2082,24 @@ mjCSkin::mjCSkin(mjCModel* _model) { +mjCSkin::mjCSkin(const mjCSkin& other) { + *this = other; +} + + + +mjCSkin& mjCSkin::operator=(const mjCSkin& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCSkin::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; diff --git a/src/user/user_model.cc b/src/user/user_model.cc index c5426ac0..89960178 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -138,6 +138,175 @@ mjCModel::mjCModel() { world->def = defaults[0]; bodies.push_back(world); + // create mjCBase lists from children lists + CreateObjectLists(); + + // point to model from spec + PointToLocal(); + + // this class allocated the plugins + plugin_owner = true; +} + + + +mjCModel::mjCModel(const mjCModel& other) { + *this = other; +} + + + +mjCModel& mjCModel::operator=(const mjCModel& other) { + if (this != &other) { + plugin_owner = false; + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + std::map def_map; + for (int i = 0; i < other.defaults.size(); i++) { + defaults.push_back(new mjCDef(*other.defaults[i])); + def_map[other.defaults[i]] = i; + } + for (const mjCFlex* flex : other.flexes) { + flexes.push_back(new mjCFlex(*flex)); + flexes.back()->model = this; + flexes.back()->def = defaults[def_map[flex->def]]; + } + for (const mjCMesh* mesh : other.meshes) { + meshes.push_back(new mjCMesh(*mesh)); + meshes.back()->model = this; + meshes.back()->def = defaults[def_map[mesh->def]]; + } + for (const mjCSkin* skin : other.skins) { + skins.push_back(new mjCSkin(*skin)); + skins.back()->model = this; + skins.back()->def = defaults[def_map[skin->def]]; + } + for (const mjCHField* hfield : other.hfields) { + hfields.push_back(new mjCHField(*hfield)); + hfields.back()->model = this; + hfields.back()->def = defaults[def_map[hfield->def]]; + } + for (const mjCTexture* texture : other.textures) { + textures.push_back(new mjCTexture(*texture)); + textures.back()->model = this; + textures.back()->def = defaults[def_map[texture->def]]; + } + for (const mjCMaterial* material : other.materials) { + materials.push_back(new mjCMaterial(*material)); + materials.back()->model = this; + materials.back()->def = defaults[def_map[material->def]]; + } + for (const mjCPair* pair : other.pairs) { + pairs.push_back(new mjCPair(*pair)); + pairs.back()->model = this; + pairs.back()->def = defaults[def_map[pair->def]]; + } + for (const mjCBodyPair* exclude : other.excludes) { + excludes.push_back(new mjCBodyPair(*exclude)); + excludes.back()->model = this; + excludes.back()->def = defaults[def_map[exclude->def]]; + } + for (const mjCEquality* equality : other.equalities) { + equalities.push_back(new mjCEquality(*equality)); + equalities.back()->model = this; + equalities.back()->def = defaults[def_map[equality->def]]; + } + for (const mjCTendon* tendon : other.tendons) { + tendons.push_back(new mjCTendon(*tendon)); + tendons.back()->SetModel(this); + tendons.back()->def = defaults[def_map[tendon->def]]; + } + for (const mjCActuator* actuator : other.actuators) { + actuators.push_back(new mjCActuator(*actuator)); + actuators.back()->model = this; + actuators.back()->def = defaults[def_map[actuator->def]]; + } + for (const mjCSensor* sensor : other.sensors) { + sensors.push_back(new mjCSensor(*sensor)); + sensors.back()->model = this; + sensors.back()->def = defaults[def_map[sensor->def]]; + } + for (const mjCNumeric* numeric : other.numerics) { + numerics.push_back(new mjCNumeric(*numeric)); + numerics.back()->model = this; + numerics.back()->def = defaults[def_map[numeric->def]]; + } + for (const mjCText* text : other.texts) { + texts.push_back(new mjCText(*text)); + texts.back()->model = this; + texts.back()->def = defaults[def_map[text->def]]; + } + for (const mjCTuple* tuple : other.tuples) { + tuples.push_back(new mjCTuple(*tuple)); + tuples.back()->model = this; + tuples.back()->def = defaults[def_map[tuple->def]]; + } + for (const mjCKey* key : other.keys) { + keys.push_back(new mjCKey(*key)); + keys.back()->model = this; + keys.back()->def = defaults[def_map[key->def]]; + } + + // plugins are global + plugins = other.plugins; + active_plugins = other.active_plugins; + + // the world copy constructor takes care of copying the tree + mjCBody* world = new mjCBody(*other.bodies[0], this); + + // create global lists + bodies.push_back(world); + MakeLists(bodies[0]); + + // update defaults for the copied objects + for (int i = 1; i < other.bodies.size(); i++) { + bodies[i]->def = defaults[def_map[other.bodies[i]->def]]; + } + for (int i = 0; i < other.joints.size(); i++) { + joints[i]->def = defaults[def_map[other.joints[i]->def]]; + } + for (int i = 0; i < other.geoms.size(); i++) { + geoms[i]->def = defaults[def_map[other.geoms[i]->def]]; + } + for (int i = 0; i < other.sites.size(); i++) { + sites[i]->def = defaults[def_map[other.sites[i]->def]]; + } + for (int i = 0; i < other.cameras.size(); i++) { + cameras[i]->def = defaults[def_map[other.cameras[i]->def]]; + } + for (int i = 0; i < other.lights.size(); i++) { + lights[i]->def = defaults[def_map[other.lights[i]->def]]; + } + + // copy name maps + for (int i=0; i*) &tuples; object_lists[mjOBJ_KEY] = (std::vector*) &keys; object_lists[mjOBJ_PLUGIN] = (std::vector*) &plugins; - - - // point to model from spec - PointToLocal(); -} - - - -mjCModel::mjCModel(const mjCModel& other) { - *this = other; - PointToLocal(); } @@ -230,9 +388,12 @@ mjCModel::~mjCModel() { for (int i=0; ihfield_adr[i] = data_adr; // copy elevation data - memcpy(m->hfield_data + data_adr, phf->data, phf->nrow*phf->ncol*sizeof(float)); + memcpy(m->hfield_data + data_adr, phf->data.data(), phf->nrow*phf->ncol*sizeof(float)); // advance counter data_adr += phf->nrow*phf->ncol; @@ -2163,7 +2316,7 @@ void mjCModel::CopyObjects(mjModel* m) { m->tex_adr[i] = data_adr; // copy rgb data - memcpy(m->tex_rgb + data_adr, ptex->rgb, 3*ptex->width*ptex->height); + memcpy(m->tex_rgb + data_adr, ptex->rgb.data(), 3*ptex->width*ptex->height); // advance counter data_adr += 3*ptex->width*ptex->height; diff --git a/src/user/user_model.h b/src/user/user_model.h index 629ec578..01b9b962 100644 --- a/src/user/user_model.h +++ b/src/user/user_model.h @@ -33,133 +33,9 @@ typedef std::map > mjKeyMap; typedef std::array mjListKeyMap; -// mjCModel contains everything needed to generate the low-level model. -// It can be constructed manually by calling 'Add' functions and setting -// the public fields of the various objects. Alternatively it can constructed -// by loading an XML file via mjCXML. Once an mjCModel object is -// constructed, 'Compile' can be called to generate the corresponding mjModel object -// (which is the low-level model). The mjCModel object can then be deleted. -class mjCModel : private mjSpec { - friend class mjCBody; - friend class mjCCamera; - friend class mjCGeom; - friend class mjCFlex; - friend class mjCHField; - friend class mjCFrame; - friend class mjCJoint; - friend class mjCEquality; - friend class mjCMesh; - friend class mjCSkin; - friend class mjCSite; - friend class mjCTendon; - friend class mjCTexture; - friend class mjCActuator; - friend class mjCSensor; - friend class mjCDef; - friend class mjXReader; - friend class mjXWriter; - - public: - mjCModel(); - mjCModel(const mjCModel& other); - ~mjCModel(); - void CopyFromSpec(); // copy spec to private attributes - void PointToLocal(); - - mjSpec spec; - - mjModel* Compile(const mjVFS* vfs = nullptr); // construct mjModel - bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back - void FuseStatic(); // fuse static bodies with parent - void FuseReindex(mjCBody* body); // reindex elements during fuse - - // API for adding model elements - mjCFlex* AddFlex(); - mjCMesh* AddMesh(mjCDef* def = nullptr); - mjCSkin* AddSkin(); - mjCHField* AddHField(); - mjCTexture* AddTexture(); - mjCMaterial* AddMaterial(mjCDef* def = nullptr); - mjCPair* AddPair(mjCDef* def = nullptr); // geom pair for inclusion - mjCBodyPair* AddExclude(); // body pair for exclusion - mjCEquality* AddEquality(mjCDef* def = nullptr); // equality constraint - mjCTendon* AddTendon(mjCDef* def = nullptr); - mjCActuator* AddActuator(mjCDef* def = nullptr); - mjCSensor* AddSensor(); - mjCNumeric* AddNumeric(); - mjCText* AddText(); - mjCTuple* AddTuple(); - mjCKey* AddKey(); - mjCPlugin* AddPlugin(); - - // delete elements marked as discard=true - template void Delete(std::vector& elements, - const std::vector& discard); - - // delete all elements - template void DeleteAll(std::vector& elements); - - // API for access to model elements (outside tree) - int NumObjects(mjtObj type); // number of objects in specified list - mjCBase* GetObject(mjtObj type, int id); // pointer to specified object - - // API for access to other variables - bool IsCompiled(); // is model already compiled - int GetFixed(); // number of fixed massless bodies - const mjCError& GetError(void); // get reference of error object - mjCBody* GetWorld(); // pointer to world body - mjCDef* FindDef(std::string name); // find default class name - mjCDef* AddDef(std::string name, int parentid); // add default class to array - mjCBase* FindObject(mjtObj type, std::string name); // find object given type and name - bool IsNullPose(const mjtNum* pos, const mjtNum* quat); // detect null pose - - // accessors - std::string get_meshdir(void) const { return meshdir_; } - std::string get_texturedir(void) const { return texturedir_; } - - // resolve plugin instance, create a new one if needed - void ResolvePlugin(mjCBase* obj, const std::string& plugin_name, - const std::string& plugin_instance_name, - mjCPlugin** plugin_instance); - - // settings for each defaults class - std::vector defaults; - - // list of active plugins - std::vector> active_plugins; - - private: - void TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs); - mjModel* _Compile(const mjVFS* vfs); - - // clear objects allocated by Compile - void Clear(void); - - // add object of any type - template T* AddObject(std::vector& list, std::string type); - - // add object of any type, with def parameter - template T* AddObjectDef(std::vector& list, std::string type, - mjCDef* def); - - // if asset name is missing, set to filename - template void SetDefaultNames(std::vector& assets); - - // delete material from object - template void DeleteMaterial(std::vector& list, - std::string_view name = ""); - - // compile phases - void MakeLists(mjCBody* body); // make lists of bodies, geoms, joints, sites - void IndexAssets(bool discard); // convert asset names into indices - void CheckEmptyNames(); // check empty names - void SetSizes(); // compute sizes - void AutoSpringDamper(mjModel*); // automatic stiffness and damping computation - void LengthRange(mjModel*, mjData*); // compute actuator lengthrange - void CopyNames(mjModel*); // copy names, compute name addresses - void CopyPaths(mjModel*); // copy paths, compute path addresses - void CopyObjects(mjModel*); // copy objects outside kinematic tree - void CopyTree(mjModel*); // copy objects inside kinematic tree +class mjCModel_ { + protected: + bool compiled; // already compiled flag // sizes set from object list lengths int nbody; // number of bodies @@ -224,6 +100,158 @@ class mjCModel : private mjSpec { int nD; // number of non-zeros in sparse dof-dof matrix int nB; // number of non-zeros in sparse body-dof matrix + // statistics, as computed by mj_setConst + double meaninertia_auto; // mean diagonal inertia, as computed by mj_setConst + double meanmass_auto; // mean body mass, as computed by mj_setConst + double meansize_auto; // mean body size, as computed by mj_setConst + double extent_auto; // spatial extent, as computed by mj_setConst + double center_auto[3]; // center of model, as computed by mj_setConst + + // save qpos0, to recognize changed key_qpos in write + std::vector qpos0; + + // variable-size attributes + std::string comment_; // comment at top of XML + std::string modelfiledir_; // path to model file + std::string modelname_; + std::string meshdir_; + std::string texturedir_; + std::string spec_comment_; + std::string spec_modelfiledir_; + std::string spec_modelname_; + std::string spec_meshdir_; + std::string spec_texturedir_; +}; + +// mjCModel contains everything needed to generate the low-level model. +// It can be constructed manually by calling 'Add' functions and setting +// the public fields of the various objects. Alternatively it can constructed +// by loading an XML file via mjCXML. Once an mjCModel object is +// constructed, 'Compile' can be called to generate the corresponding mjModel object +// (which is the low-level model). The mjCModel object can then be deleted. +class mjCModel : public mjCModel_, private mjSpec { + friend class mjCBase; + friend class mjCBody; + friend class mjCCamera; + friend class mjCGeom; + friend class mjCFlex; + friend class mjCHField; + friend class mjCFrame; + friend class mjCJoint; + friend class mjCEquality; + friend class mjCMesh; + friend class mjCSkin; + friend class mjCSite; + friend class mjCTendon; + friend class mjCTexture; + friend class mjCActuator; + friend class mjCSensor; + friend class mjCDef; + friend class mjXReader; + friend class mjXWriter; + + public: + mjCModel(); + mjCModel(const mjCModel& other); + mjCModel& operator=(const mjCModel& other); + ~mjCModel(); + void CopyFromSpec(); // copy spec to private attributes + void PointToLocal(); + + mjSpec spec; + + mjModel* Compile(const mjVFS* vfs = nullptr); // construct mjModel + bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back + void FuseStatic(); // fuse static bodies with parent + void FuseReindex(mjCBody* body); // reindex elements during fuse + + // API for adding model elements + mjCFlex* AddFlex(); + mjCMesh* AddMesh(mjCDef* def = nullptr); + mjCSkin* AddSkin(); + mjCHField* AddHField(); + mjCTexture* AddTexture(); + mjCMaterial* AddMaterial(mjCDef* def = nullptr); + mjCPair* AddPair(mjCDef* def = nullptr); // geom pair for inclusion + mjCBodyPair* AddExclude(); // body pair for exclusion + mjCEquality* AddEquality(mjCDef* def = nullptr); // equality constraint + mjCTendon* AddTendon(mjCDef* def = nullptr); + mjCActuator* AddActuator(mjCDef* def = nullptr); + mjCSensor* AddSensor(); + mjCNumeric* AddNumeric(); + mjCText* AddText(); + mjCTuple* AddTuple(); + mjCKey* AddKey(); + mjCPlugin* AddPlugin(); + + // delete elements marked as discard=true + template void Delete(std::vector& elements, + const std::vector& discard); + + // delete all elements + template void DeleteAll(std::vector& elements); + + // API for access to model elements (outside tree) + int NumObjects(mjtObj type); // number of objects in specified list + mjCBase* GetObject(mjtObj type, int id); // pointer to specified object + + // API for access to other variables + bool IsCompiled(); // is model already compiled + const mjCError& GetError(void); // get reference of error object + mjCBody* GetWorld(); // pointer to world body + mjCDef* FindDef(std::string name); // find default class name + mjCDef* AddDef(std::string name, int parentid); // add default class to array + mjCBase* FindObject(mjtObj type, std::string name); // find object given type and name + bool IsNullPose(const mjtNum* pos, const mjtNum* quat); // detect null pose + + // accessors + std::string get_meshdir(void) const { return meshdir_; } + std::string get_texturedir(void) const { return texturedir_; } + + // resolve plugin instance, create a new one if needed + void ResolvePlugin(mjCBase* obj, const std::string& plugin_name, + const std::string& plugin_instance_name, + mjCPlugin** plugin_instance); + + // settings for each defaults class + std::vector defaults; + + // list of active plugins + std::vector> active_plugins; + + private: + void TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs); + mjModel* _Compile(const mjVFS* vfs); + + // clear objects allocated by Compile + void Clear(void); + + // add object of any type + template T* AddObject(std::vector& list, std::string type); + + // add object of any type, with def parameter + template T* AddObjectDef(std::vector& list, std::string type, + mjCDef* def); + + // if asset name is missing, set to filename + template void SetDefaultNames(std::vector& assets); + + // delete material from object + template void DeleteMaterial(std::vector& list, + std::string_view name = ""); + + // compile phases + void MakeLists(mjCBody* body); // make lists of bodies, geoms, joints, sites + void IndexAssets(bool discard); // convert asset names into indices + void CheckEmptyNames(); // check empty names + void SetSizes(); // compute sizes + void AutoSpringDamper(mjModel*); // automatic stiffness and damping computation + void LengthRange(mjModel*, mjData*); // compute actuator lengthrange + void CopyNames(mjModel*); // copy names, compute name addresses + void CopyPaths(mjModel*); // copy paths, compute path addresses + void CopyObjects(mjModel*); // copy objects outside kinematic tree + void CopyTree(mjModel*); // copy objects inside kinematic tree + // objects created here std::vector flexes; // list of flexes std::vector meshes; // list of meshes @@ -255,32 +283,11 @@ class mjCModel : private mjSpec { // array of pointers to each object list (enumerated by type) std::array*, mjNOBJECT> object_lists; - // statistics, as computed by mj_setConst - double meaninertia_auto; // mean diagonal inertia, as computed by mj_setConst - double meanmass_auto; // mean body mass, as computed by mj_setConst - double meansize_auto; // mean body size, as computed by mj_setConst - double extent_auto; // spatial extent, as computed by mj_setConst - double center_auto[3]; // center of model, as computed by mj_setConst - + // create mjCBase lists from children lists + void CreateObjectLists(); mjListKeyMap ids; // map from object names to ids - bool compiled; // already compiled flag (cannot be compiled again) mjCError errInfo; // last error info - int fixCount; // how many bodies have been fixed - - // save qpos0, to recognize changed key_qpos in write - std::vector qpos0; - - // variable-size attributes - std::string comment_; // comment at top of XML - std::string modelfiledir_; // path to model file - std::string modelname_; - std::string meshdir_; - std::string texturedir_; - std::string spec_comment_; - std::string spec_modelfiledir_; - std::string spec_modelname_; - std::string spec_meshdir_; - std::string spec_texturedir_; + bool plugin_owner; // this class allocated the plugins }; #endif // MUJOCO_SRC_USER_USER_MODEL_H_ diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index 997bcde4..adc45bc5 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -570,12 +570,6 @@ mjCBase::mjCBase(const mjCBase& other) { mjCBase& mjCBase::operator=(const mjCBase& other) { if (this != &other) { *static_cast(this) = static_cast(other); - if (other.def) { - def = new mjCDef(*other.def); - } - if (other.frame) { - frame = new mjCFrame(*other.frame); - } } return *this; } @@ -663,7 +657,8 @@ mjCBody::mjCBody(mjCModel* _model) { -mjCBody::mjCBody(const mjCBody& other) { +mjCBody::mjCBody(const mjCBody& other, mjCModel* _model) { + model = _model; *this = other; } @@ -671,43 +666,63 @@ mjCBody::mjCBody(const mjCBody& other) { mjCBody& mjCBody::operator=(const mjCBody& other) { if (this != &other) { - this->spec = other.spec; + spec = other.spec; *static_cast(this) = static_cast(other); *static_cast(this) = static_cast(other); - this->bodies.clear(); - this->frames.clear(); - this->geoms.clear(); - this->joints.clear(); - this->sites.clear(); - this->cameras.clear(); - this->lights.clear(); + std::map fmap; + mjCFrame *np = nullptr; + bodies.clear(); + frames.clear(); + geoms.clear(); + joints.clear(); + sites.clear(); + cameras.clear(); + lights.clear(); + + // create frames and map them to the old ones + for (int i=0; imodel = model; + fmap[other.frames[i]] = i; + } // copy all children for (int i=0; ibodies.push_back(new mjCBody(*other.bodies[i])); // triggers recursive call + bodies.push_back(new mjCBody(*other.bodies[i], model)); // triggers recursive call + bodies.back()->frame = other.bodies[i]->frame ? frames[fmap[other.bodies[i]->frame]] : np; } for (int i=0; iframes.push_back(new mjCFrame(*other.frames[i])); + frames[i]->frame = other.frames[i]->frame ? frames[fmap[other.frames[i]->frame]] : np; } for (int i=0; igeoms.push_back(new mjCGeom(*other.geoms[i])); - this->geoms.back()->body = this; + geoms.push_back(new mjCGeom(*other.geoms[i])); + geoms.back()->body = this; + geoms.back()->model = model; + geoms.back()->frame = other.geoms[i]->frame ? frames[fmap[other.geoms[i]->frame]] : np; } for (int i=0; ijoints.push_back(new mjCJoint(*other.joints[i])); - this->joints.back()->body = this; + joints.push_back(new mjCJoint(*other.joints[i])); + joints.back()->body = this; + joints.back()->model = model; + joints.back()->frame = other.joints[i]->frame ? frames[fmap[other.joints[i]->frame]] : np; } for (int i=0; isites.push_back(new mjCSite(*other.sites[i])); - this->sites.back()->body = this; + sites.push_back(new mjCSite(*other.sites[i])); + sites.back()->body = this; + sites.back()->model = model; + sites.back()->frame = other.sites[i]->frame ? frames[fmap[other.sites[i]->frame]] : np; } for (int i=0; icameras.push_back(new mjCCamera(*other.cameras[i])); - this->cameras.back()->body = this; + cameras.push_back(new mjCCamera(*other.cameras[i])); + cameras.back()->body = this; + cameras.back()->model = model; + cameras.back()->frame = other.cameras[i]->frame ? frames[fmap[other.cameras[i]->frame]] : np; } for (int i=0; ilights.push_back(new mjCLight(*other.lights[i])); - this->lights.back()->body = this; + lights.push_back(new mjCLight(*other.lights[i])); + lights.back()->body = this; + lights.back()->model = model; + lights.back()->frame = other.lights[i]->frame ? frames[fmap[other.lights[i]->frame]] : np; } } PointToLocal(); @@ -2618,7 +2633,7 @@ mjCHField::mjCHField(mjCModel* _model) { model = _model; // clear variables - data = 0; + data.clear(); spec_file_.clear(); spec_userdata_.clear(); @@ -2631,6 +2646,24 @@ mjCHField::mjCHField(mjCModel* _model) { +mjCHField::mjCHField(const mjCHField& other) { + *this = other; +} + + + +mjCHField& mjCHField::operator=(const mjCHField& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCHField::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; @@ -2652,10 +2685,7 @@ void mjCHField::CopyFromSpec() { userdata = (mjFloatVec)&userdata_; // clear precompiled asset. TODO: use asset cache - if (data) { - mju_free(data); - data = 0; - } + data.clear(); if (!file_.empty()) { nrow = 0; ncol = 0; @@ -2666,9 +2696,7 @@ void mjCHField::CopyFromSpec() { // destructor mjCHField::~mjCHField() { - if (data) { - mju_free(data); - } + data.clear(); userdata_.clear(); spec_userdata_.clear(); } @@ -2708,13 +2736,13 @@ void mjCHField::LoadCustom(mjResource* resource) { } // allocate - data = (float*) mju_malloc(nrow*ncol*sizeof(float)); - if (!data) { + data.assign(nrow*ncol, 0); + if (data.empty()) { throw mjCError(this, "could not allocate buffers in hfield"); } // copy data - memcpy(data, (void*)(pint+2), nrow*ncol*sizeof(float)); + memcpy(data.data(), (void*)(pint+2), nrow*ncol*sizeof(float)); } @@ -2747,8 +2775,8 @@ void mjCHField::LoadPNG(mjResource* resource) { } // allocate - data = (float*) mju_malloc(w*h*sizeof(float)); - if (!data) { + data.assign(w*h, 0); + if (data.empty()) { throw mjCError(this, "could not allocate buffers in hfield"); } @@ -2770,11 +2798,11 @@ void mjCHField::Compile(const mjVFS* vfs) { // copy userdata into data if (!userdata_.empty()) { - data = (float*) mju_malloc(nrow*ncol*sizeof(float)); - if (!data) { + data.assign(nrow*ncol, 0); + if (data.empty()) { throw mjCError(this, "could not allocate buffers in hfield"); } - memcpy(data, userdata_.data(), nrow*ncol*sizeof(float)); + memcpy(data.data(), userdata_.data(), nrow*ncol*sizeof(float)); } // check size parameters @@ -2791,7 +2819,7 @@ void mjCHField::Compile(const mjVFS* vfs) { // load from file if specified if (!file_.empty()) { // make sure hfield was not already specified manually - if (nrow || ncol || data) { + if (nrow || ncol || !data.empty()) { throw mjCError(this, "hfield '%s' (id = %d) specified from file and manually", name.c_str(), id); } @@ -2824,7 +2852,7 @@ void mjCHField::Compile(const mjVFS* vfs) { } // make sure hfield was specified (from file or manually) - if (nrow<1 || ncol<1 || data==0) { + if (nrow<1 || ncol<1 || data.empty()) { throw mjCError(this, "hfield '%s' (id = %d) not specified", name.c_str(), id); } @@ -2864,7 +2892,7 @@ mjCTexture::mjCTexture(mjCModel* _model) { spec_cubefiles_.assign(6, ""); // clear internal variables - rgb = 0; + rgb.clear(); // point to local PointToLocal(); @@ -2875,6 +2903,23 @@ mjCTexture::mjCTexture(mjCModel* _model) { +mjCTexture::mjCTexture(const mjCTexture& other) { + *this = other; +} + + + +mjCTexture& mjCTexture::operator=(const mjCTexture& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCTexture::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; @@ -2897,20 +2942,14 @@ void mjCTexture::CopyFromSpec() { cubefiles = (mjStringVec)&cubefiles_; // clear precompiled asset. TODO: use asset cache - if (rgb) { - mju_free(rgb); - rgb = 0; - } + rgb.clear(); } // free data storage allocated by lodepng mjCTexture::~mjCTexture() { - if (rgb) { - mju_free(rgb); - rgb = 0; - } + rgb.clear(); } @@ -2997,21 +3036,21 @@ void mjCTexture::Builtin2D(void) { double pos = 2*sqrt(x*x+y*y) - 1; // interpolate through sigmoid - interp(rgb + 3*(r*width+c), rgb2, rgb1, pos); + interp(rgb.data() + 3*(r*width+c), rgb2, rgb1, pos); } } } // checker else if (builtin==mjBUILTIN_CHECKER) { - checker(rgb, RGB1, RGB2, width, height); + checker(rgb.data(), RGB1, RGB2, width, height); } // flat else if (builtin==mjBUILTIN_FLAT) { for (int r=0; r0) { - randomdot(rgb, markrgb, width, height, random); + randomdot(rgb.data(), markrgb, width, height, random); } } @@ -3051,65 +3090,67 @@ void mjCTexture::Builtin2D(void) { // make builtin: Cube void mjCTexture::BuiltinCube(void) { unsigned char RGB1[3], RGB2[3], RGBm[3], RGBi[3]; + int w = width; + int ww = width*width; // convert fixed colors - for (int j=0; j<3; j++) { - RGB1[j] = (mjtByte)(255*rgb1[j]); - RGB2[j] = (mjtByte)(255*rgb2[j]); - RGBm[j] = (mjtByte)(255*markrgb[j]); + for (int j = 0; j < 3; j++) { + RGB1[j] = (mjtByte)(255 * rgb1[j]); + RGB2[j] = (mjtByte)(255 * rgb2[j]); + RGBm[j] = (mjtByte)(255 * markrgb[j]); } //------------------ faces // gradient - if (builtin==mjBUILTIN_GRADIENT) { - for (int r=0; r0) { - randomdot(rgb, markrgb, width, height, random); + else if (mark == mjMARK_RANDOM && random > 0) { + randomdot(rgb.data(), markrgb, w, height, random); } } - - // load PNG file void mjCTexture::LoadPNG(mjResource* resource, std::vector& image, @@ -3307,12 +3346,12 @@ void mjCTexture::Load2D(string filename, const mjVFS* vfs) { height = h; // allocate and copy data - rgb = (mjtByte*) mju_malloc(3*width*height); - if (!rgb) { + rgb.assign(3*width*height, 0); + if (rgb.empty()) { throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)", (const char*)file_.c_str(), id); } - memcpy(rgb, image.data(), 3*width*height); + memcpy(rgb.data(), image.data(), 3*width*height); image.clear(); } @@ -3348,8 +3387,8 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { } // allocate data - rgb = (mjtByte*) mju_malloc(3*width*height); - if (!rgb) { + rgb.assign(3*width*height, 0); + if (rgb.empty()) { throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)", (const char*)file_.c_str(), id); @@ -3357,7 +3396,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { // copy: repeated if (gridsize[0]==1 && gridsize[1]==1) { - memcpy(rgb, image.data(), 3*width*width); + memcpy(rgb.data(), image.data(), 3*width*width); } // copy: grid @@ -3391,7 +3430,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { int rstart = width*(k/gridsize[1]); int cstart = width*(k%gridsize[1]); for (int j=0; jspec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCBodyPair::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; @@ -4114,8 +4171,12 @@ mjCTendon::mjCTendon(const mjCTendon& other) { mjCTendon& mjCTendon::operator=(const mjCTendon& other) { if (this != &other) { this->spec = other.spec; + *static_cast(this) = static_cast(other); *static_cast(this) = static_cast(other); - *static_cast(this) = static_cast(other); + for (int i=0; itendon = this; + } } PointToLocal(); return *this; @@ -4166,6 +4227,15 @@ mjCTendon::~mjCTendon() { +void mjCTendon::SetModel(mjCModel* _model) { + model = _model; + for (int i=0; imodel = _model; + } +} + + + // add site as wrap object void mjCTendon::WrapSite(string name, std::string_view info) { // create wrap object @@ -4395,6 +4465,25 @@ mjCWrap::mjCWrap(mjCModel* _model, mjCTendon* _tendon) { +mjCWrap::mjCWrap(const mjCWrap& other) { + *this = other; +} + + + +mjCWrap& mjCWrap::operator=(const mjCWrap& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + obj = nullptr; + } + PointToLocal(); + return *this; +} + + + void mjCWrap::PointToLocal() { spec.element = (mjElement)this; spec.info = (mjString)&info; @@ -4822,12 +4911,30 @@ mjCSensor::mjCSensor(mjCModel* _model) { CopyFromSpec(); // point to local - MakePointerLocal(); + PointToLocal(); } -void mjCSensor::MakePointerLocal() { +mjCSensor::mjCSensor(const mjCSensor& other) { + *this = other; +} + + + +mjCSensor& mjCSensor::operator=(const mjCSensor& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + +void mjCSensor::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; spec.classname = (mjString)&classname; @@ -5271,6 +5378,24 @@ mjCNumeric::mjCNumeric(mjCModel* _model) { +mjCNumeric::mjCNumeric(const mjCNumeric& other) { + *this = other; +} + + + +mjCNumeric& mjCNumeric::operator=(const mjCNumeric& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCNumeric::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; @@ -5341,6 +5466,24 @@ mjCText::mjCText(mjCModel* _model) { +mjCText::mjCText(const mjCText& other) { + *this = other; +} + + + +mjCText& mjCText::operator=(const mjCText& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCText::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; @@ -5402,6 +5545,24 @@ mjCTuple::mjCTuple(mjCModel* _model) { +mjCTuple::mjCTuple(const mjCTuple& other) { + *this = other; +} + + + +mjCTuple& mjCTuple::operator=(const mjCTuple& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCTuple::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; @@ -5502,6 +5663,24 @@ mjCKey::mjCKey(mjCModel* _model) { +mjCKey::mjCKey(const mjCKey& other) { + *this = other; +} + + + +mjCKey& mjCKey::operator=(const mjCKey& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + *static_cast(this) = static_cast(other); + } + PointToLocal(); + return *this; +} + + + void mjCKey::PointToLocal() { spec.element = (mjElement)this; spec.name = (mjString)&name; @@ -5653,6 +5832,23 @@ mjCPlugin::mjCPlugin(mjCModel* _model) { +mjCPlugin::mjCPlugin(const mjCPlugin& other) { + *this = other; +} + + + +mjCPlugin& mjCPlugin::operator=(const mjCPlugin& other) { + if (this != &other) { + this->spec = other.spec; + *static_cast(this) = static_cast(other); + parent = this; + } + return *this; +} + + + // compiler void mjCPlugin::Compile(void) { const mjpPlugin* plugin = mjp_getPluginAtSlot(spec.plugin_slot); diff --git a/src/user/user_objects.h b/src/user/user_objects.h index 01333478..f553df97 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -177,7 +177,6 @@ class mjCBase_ { std::string classname; // defaults class name int id; // object id std::string info; // error message info set by the user - mjCModel* model; // pointer to model that created object }; class mjCBase : public mjCBase_ { @@ -202,6 +201,7 @@ class mjCBase : public mjCBase_ { mjCDef* def; // defaults class used to init this object mjCFrame* frame; // pointer to frame transformation + mjCModel* model; // pointer to model that created object protected: mjCBase(); // constructor @@ -296,10 +296,10 @@ class mjCBody : public mjCBody_, private mjmBody { const std::vector& get_userdata() { return userdata_; } private: - mjCBody(mjCModel*); // constructor - mjCBody(const mjCBody& other); // copy constructor - mjCBody& operator=(const mjCBody& other); // copy assignment - ~mjCBody(); // destructor + mjCBody(mjCModel*); // constructor + mjCBody(const mjCBody& other, mjCModel* _model); // copy constructor + mjCBody& operator=(const mjCBody& other); // copy assignment + ~mjCBody(); // destructor void Compile(void); // compiler void GeomFrame(void); // get inertial info from geoms @@ -755,6 +755,7 @@ class mjCMesh_ : public mjCBase { }; class mjCMesh: public mjCMesh_, private mjmMesh { + friend class mjCModel; friend class mjCFlexcomp; friend class mjXWriter; public: @@ -932,6 +933,8 @@ class mjCSkin: public mjCSkin_, private mjmSkin { class mjCHField_ : public mjCBase { protected: + std::vector data; // elevation data, row-major format + std::string file_; std::string content_type_; std::vector userdata_; @@ -964,7 +967,6 @@ class mjCHField : public mjCHField_, private mjmHField { mjCHField& operator=(const mjCHField& other); // copy assignment ~mjCHField(); // destructor - float* data; // elevation data, row-major format void Compile(const mjVFS* vfs); // compiler void LoadCustom(mjResource* resource); // load from custom format @@ -978,6 +980,8 @@ class mjCHField : public mjCHField_, private mjmHField { class mjCTexture_ : public mjCBase { protected: + std::vector rgb; // rgb data + std::string file_; std::string content_type_; std::vector cubefiles_; @@ -1029,8 +1033,6 @@ class mjCTexture : public mjCTexture_, private mjmTexture { void LoadCustom(mjResource* resource, std::vector& image, unsigned int& w, unsigned int& h); - - mjtByte* rgb; // rgb data }; @@ -1247,6 +1249,7 @@ class mjCTendon : public mjCTendon_, private mjmTendon { void CopyFromSpec(); void PointToLocal(); + void SetModel(mjCModel* _model); bool is_limited() const; @@ -1315,10 +1318,10 @@ class mjCPlugin : public mjCPlugin_ { public: mjmPlugin spec; mjCBase* parent; // parent object (only used when generating error message) + mjCPlugin(const mjCPlugin& other); // copy constructor private: mjCPlugin(mjCModel*); // constructor - mjCPlugin(const mjCPlugin& other); // copy constructor mjCPlugin& operator=(const mjCPlugin& other); // copy assignment void Compile(void); // compiler @@ -1385,6 +1388,7 @@ class mjCActuator : public mjCActuator_, private mjmActuator { class mjCSensor_ : public mjCBase { protected: int refid; // id of reference frame + mjCBase* obj; // sensorized object // variable-size data std::string plugin_name; @@ -1420,9 +1424,7 @@ class mjCSensor : public mjCSensor_, private mjmSensor { void Compile(void); // compiler void CopyFromSpec(); - void MakePointerLocal(); - - mjCBase* obj; // sensorized object + void PointToLocal(); }; diff --git a/test/user/user_api_test.cc b/test/user/user_api_test.cc index 926e8dc3..de1f9186 100644 --- a/test/user/user_api_test.cc +++ b/test/user/user_api_test.cc @@ -65,6 +65,9 @@ TEST_F(MujocoTest, ReadWriteData) { // ------------------- test recompilation multiple files ---------------------- TEST_F(PluginTest, RecompileCompare) { + mjtNum tol = 0; + std::string field = ""; + // full precision float printing FullFloatPrecision increase_precision; @@ -85,33 +88,55 @@ TEST_F(PluginTest, RecompileCompare) { continue; } - // load model - std::array error; - mjSpec* spec = - mjParseXML(xml.c_str(), nullptr, error.data(), error.size()); + // load spec + std::array err; + mjSpec* s = mjParseXML(xml.c_str(), nullptr, err.data(), err.size()); - // compile twice - mjModel* m_old = mjm_compile(spec, nullptr); - mjModel* m_new = mjm_compile(spec, nullptr); + // copy spec + mjSpec* s_copy = mjm_copySpec(s); + + // compile twice and compare + mjModel* m_old = mjm_compile(s, nullptr); + mjModel* m_new = mjm_compile(s, nullptr); + mjModel* m_copy = mjm_compile(s_copy, nullptr); ASSERT_THAT(m_old, NotNull()) - << "Failed to compile " << xml << ": " << error.data(); + << "Failed to compile " << xml << ": " << err.data(); ASSERT_THAT(m_new, NotNull()) - << "Failed to recompile " << xml << ": " << error.data(); + << "Failed to recompile " << xml << ": " << err.data(); + ASSERT_THAT(m_copy, NotNull()) + << "Failed to compile " << xml << ": " << err.data(); - // compare and delete - std::string field = ""; - mjtNum result = CompareModel(m_old, m_new, field); - mjtNum tol = 0; - EXPECT_LE(result, tol) - << "Loaded and saved models are different!\n" + EXPECT_LE(CompareModel(m_old, m_new, field), tol) + << "Compiled and recompiled models are different!\n" + << "Affected file " << p.path().string() << '\n' + << "Different field: " << field << '\n'; + + EXPECT_LE(CompareModel(m_old, m_copy, field), tol) + << "Original and copied models are different!\n" + << "Affected file " << p.path().string() << '\n' + << "Different field: " << field << '\n'; + + // copy to a new spec, compile and compare + mjSpec* s_copy2 = mjm_copySpec(s); + mjModel* m_copy2 = mjm_compile(s_copy2, nullptr); + + ASSERT_THAT(m_copy2, NotNull()) + << "Failed to compile " << xml << ": " << err.data(); + + EXPECT_LE(CompareModel(m_old, m_copy2, field), tol) + << "Original and re-copied models are different!\n" << "Affected file " << p.path().string() << '\n' << "Different field: " << field << '\n'; // delete models - mjm_deleteSpec(spec); + mjm_deleteSpec(s); + mjm_deleteSpec(s_copy); + mjm_deleteSpec(s_copy2); mj_deleteModel(m_old); mj_deleteModel(m_new); + mj_deleteModel(m_copy); + mj_deleteModel(m_copy2); } } }