9d5063799f
Previously, it was assumed that attaching was the last operation performed on the mjSpec, so keyframes resulted in the incorrect size. In order to prevent information loss, we now only allow the resize function to expand the keyframe array, since attach should never remove a degree of freedom. PiperOrigin-RevId: 804917828 Change-Id: Icfacd207ced1c6aac600d514f8d9065ceff2d347
5491 lines
164 KiB
C++
5491 lines
164 KiB
C++
// Copyright 2021 DeepMind Technologies Limited
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "user/user_model.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cmath>
|
|
#include <array>
|
|
#include <csetjmp>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <filesystem> // NOLINT(build/c++17)
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <mujoco/mjdata.h>
|
|
#include <mujoco/mjmacro.h>
|
|
#include <mujoco/mjmodel.h>
|
|
#include <mujoco/mjspec.h>
|
|
#include <mujoco/mjtnum.h>
|
|
#include <mujoco/mjplugin.h>
|
|
#include "cc/array_safety.h"
|
|
#include "engine/engine_forward.h"
|
|
#include "engine/engine_io.h"
|
|
#include "engine/engine_name.h"
|
|
#include "engine/engine_plugin.h"
|
|
#include "engine/engine_setconst.h"
|
|
#include "engine/engine_support.h"
|
|
#include "engine/engine_util_errmem.h"
|
|
#include "engine/engine_util_misc.h"
|
|
#include "user/user_api.h"
|
|
#include "user/user_objects.h"
|
|
#include "user/user_util.h"
|
|
|
|
namespace {
|
|
namespace mju = ::mujoco::util;
|
|
using std::string;
|
|
using std::vector;
|
|
constexpr int kMaxCompilerThreads = 16;
|
|
|
|
|
|
|
|
//---------------------------------- LOCAL UTILITY FUNCTIONS ---------------------------------------
|
|
|
|
constexpr double kFrameEps = 1e-6; // difference below which frames are considered equal
|
|
|
|
// return true if two 3-vectors are element-wise less than kFrameEps apart
|
|
template <typename T>
|
|
bool IsSameVec(const T pos1[3], const T pos2[3]) {
|
|
static_assert(std::is_floating_point_v<T>);
|
|
return std::abs(pos1[0] - pos2[0]) < kFrameEps &&
|
|
std::abs(pos1[1] - pos2[1]) < kFrameEps &&
|
|
std::abs(pos1[2] - pos2[2]) < kFrameEps;
|
|
}
|
|
|
|
// return true if two quaternions are element-wise less than kFrameEps apart, including double-cover
|
|
template <typename T>
|
|
bool IsSameQuat(const T quat1[4], const T quat2[4]) {
|
|
static_assert(std::is_floating_point_v<T>);
|
|
bool same_quat_minus = std::abs(quat1[0] - quat2[0]) < kFrameEps &&
|
|
std::abs(quat1[1] - quat2[1]) < kFrameEps &&
|
|
std::abs(quat1[2] - quat2[2]) < kFrameEps &&
|
|
std::abs(quat1[3] - quat2[3]) < kFrameEps;
|
|
|
|
bool same_quat_plus = std::abs(quat1[0] + quat2[0]) < kFrameEps &&
|
|
std::abs(quat1[1] + quat2[1]) < kFrameEps &&
|
|
std::abs(quat1[2] + quat2[2]) < kFrameEps &&
|
|
std::abs(quat1[3] + quat2[3]) < kFrameEps;
|
|
|
|
return same_quat_minus || same_quat_plus;
|
|
}
|
|
|
|
|
|
// compare two poses
|
|
template <typename T>
|
|
bool IsSamePose(const T pos1[3], const T pos2[3], const T quat1[4], const T quat2[4]) {
|
|
// check position if given
|
|
if (pos1 && pos2 && !IsSameVec(pos1, pos2)) {
|
|
return false;
|
|
}
|
|
|
|
// check orientation if given
|
|
if (quat1 && quat2 && !IsSameQuat(quat1, quat2)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// detect null pose
|
|
template <typename T>
|
|
bool IsNullPose(const T pos[3], const T quat[4]) {
|
|
T zero[3] = {0, 0, 0};
|
|
T qunit[4] = {1, 0, 0, 0};
|
|
return IsSamePose(pos, zero, quat, qunit);
|
|
}
|
|
|
|
// get body id from wrap object
|
|
int GetBodyIdFromWrap(const mjCWrap* wrap) {
|
|
if (!wrap || !wrap->obj) return -1;
|
|
switch (wrap->type) {
|
|
case mjWRAP_SITE:
|
|
return static_cast<mjCSite*>(wrap->obj)->Body()->id;
|
|
case mjWRAP_CYLINDER:
|
|
case mjWRAP_SPHERE:
|
|
return static_cast<mjCGeom*>(wrap->obj)->GetParent()->id;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
//---------------------------------- CONSTRUCTOR AND DESTRUCTOR ------------------------------------
|
|
|
|
// constructor
|
|
mjCModel::mjCModel() {
|
|
mjs_defaultSpec(&spec);
|
|
elemtype = mjOBJ_MODEL;
|
|
spec_comment_.clear();
|
|
spec_modelfiledir_.clear();
|
|
spec_meshdir_.clear();
|
|
spec_texturedir_.clear();
|
|
spec_modelname_ = "MuJoCo Model";
|
|
|
|
//------------------------ auto-computed statistics
|
|
#ifndef MEMORY_SANITIZER
|
|
// initializing as best practice, but want MSAN to catch uninitialized use
|
|
meaninertia_auto = 0;
|
|
meanmass_auto = 0;
|
|
meansize_auto = 0;
|
|
extent_auto = 0;
|
|
center_auto[0] = center_auto[1] = center_auto[2] = 0;
|
|
#endif
|
|
|
|
deepcopy_ = false;
|
|
nplugin = 0;
|
|
Clear();
|
|
|
|
//------------------------ master default set
|
|
defaults_.push_back(new mjCDef(this));
|
|
defaults_.back()->name = "main";
|
|
|
|
// point to model from spec
|
|
PointToLocal();
|
|
|
|
// world body
|
|
mjCBody* world = new mjCBody(this);
|
|
mjuu_zerovec(world->pos, 3);
|
|
mjuu_setvec(world->quat, 1, 0, 0, 0);
|
|
world->mass = 0;
|
|
mjuu_zerovec(world->inertia, 3);
|
|
world->id = 0;
|
|
world->parent = nullptr;
|
|
world->weldid = 0;
|
|
world->name = "world";
|
|
world->classname = "main";
|
|
def_map["main"] = Default();
|
|
bodies_.push_back(world);
|
|
|
|
// create mjCBase lists from children lists
|
|
CreateObjectLists();
|
|
|
|
// set the signature
|
|
spec.element->signature = 0;
|
|
}
|
|
|
|
|
|
|
|
mjCModel::mjCModel(const mjCModel& other) {
|
|
CreateObjectLists();
|
|
*this = other;
|
|
}
|
|
|
|
|
|
|
|
mjCModel& mjCModel::operator=(const mjCModel& other) {
|
|
deepcopy_ = true;
|
|
if (this != &other) {
|
|
this->spec = other.spec;
|
|
*static_cast<mjCModel_*>(this) = static_cast<const mjCModel_&>(other);
|
|
*static_cast<mjSpec*>(this) = static_cast<const mjSpec&>(other);
|
|
|
|
// copy attached specs first so that we can resolve references to them
|
|
for (const auto* s : other.specs_) {
|
|
specs_.push_back(mj_copySpec(s));
|
|
compiler2spec_[&s->compiler] = specs_.back();
|
|
}
|
|
|
|
// the world copy constructor takes care of copying the tree
|
|
mjCBody* world = new mjCBody(*other.bodies_[0], this);
|
|
bodies_.push_back(world);
|
|
|
|
// update tree lists
|
|
ResetTreeLists();
|
|
MakeTreeLists();
|
|
|
|
// add everything else
|
|
*this += other;
|
|
|
|
// add keyframes
|
|
CopyList(keys_, other.keys_);
|
|
|
|
// create new default tree
|
|
mjCDef* subtree = new mjCDef(*other.defaults_[0]);
|
|
*this += *subtree;
|
|
|
|
// copy name maps
|
|
for (int i=0; i < mjNOBJECT; i++) {
|
|
ids[i] = other.ids[i];
|
|
}
|
|
|
|
// update signature after we updated everything
|
|
spec.element->signature = Signature();
|
|
}
|
|
deepcopy_ = other.deepcopy_;
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
// copy vector of elements from another model to this model
|
|
template <class T>
|
|
void mjCModel::CopyList(std::vector<T*>& dest,
|
|
const std::vector<T*>& source) {
|
|
// loop over the elements from the other model
|
|
int nsource = (int)source.size();
|
|
for (int i = 0; i < nsource; i++) {
|
|
T* candidate = deepcopy_ ? new T(*source[i]) : source[i];
|
|
try {
|
|
// try to find the referenced object in this model
|
|
mjCModel* source_model = source[i]->model;
|
|
candidate->model = this;
|
|
candidate->NameSpace(source_model);
|
|
candidate->CopyFromSpec();
|
|
candidate->ResolveReferences(this);
|
|
} catch (mjCError err) {
|
|
// if not present, skip the element
|
|
// TODO: do not skip elements that contain user errors
|
|
if (deepcopy_) {
|
|
candidate->model = nullptr;
|
|
delete candidate;
|
|
}
|
|
continue;
|
|
}
|
|
// copy the element from the other model to this model
|
|
if (deepcopy_) {
|
|
source[i]->ForgetKeyframes();
|
|
} else {
|
|
candidate->AddRef();
|
|
}
|
|
mjSpec* origin = FindSpec(source[i]->compiler);
|
|
dest.push_back(candidate);
|
|
dest.back()->model = this;
|
|
dest.back()->compiler = origin ? &origin->compiler : &spec.compiler;
|
|
dest.back()->id = -1;
|
|
dest.back()->CopyPlugin();
|
|
}
|
|
if (!dest.empty()) {
|
|
ProcessList_(ids, dest, dest[0]->elemtype);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
static void resetlist(std::vector<T*>& list) {
|
|
for (auto element : list) {
|
|
element->id = -1;
|
|
}
|
|
list.clear();
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::ResetTreeLists() {
|
|
mjCBody *world = bodies_[0];
|
|
resetlist(bodies_);
|
|
resetlist(joints_);
|
|
resetlist(geoms_);
|
|
resetlist(sites_);
|
|
resetlist(cameras_);
|
|
resetlist(lights_);
|
|
resetlist(frames_);
|
|
world->id = 0;
|
|
bodies_.push_back(world);
|
|
}
|
|
|
|
|
|
|
|
// save associated state addresses in related elements
|
|
void mjCModel::SaveDofOffsets(bool computesize) {
|
|
int qposadr = 0;
|
|
int dofadr = 0;
|
|
int actadr = 0;
|
|
int mocapadr = 0;
|
|
|
|
for (auto joint : joints_) {
|
|
joint->qposadr_ = qposadr;
|
|
joint->dofadr_ = dofadr;
|
|
qposadr += joint->nq();
|
|
dofadr += joint->nv();
|
|
}
|
|
|
|
for (auto actuator : actuators_) {
|
|
if (actuator->spec.actdim > 0) {
|
|
actuator->actdim_ = actuator->spec.actdim;
|
|
} else {
|
|
actuator->actdim_ = (actuator->spec.dyntype != mjDYN_NONE);
|
|
}
|
|
actuator->actadr_ = actuator->actdim_ ? actadr : -1;
|
|
actadr += actuator->actdim_;
|
|
}
|
|
|
|
for (mjCBody* body : bodies_) {
|
|
if (body->spec.mocap) {
|
|
body->mocapid = mocapadr++;
|
|
} else {
|
|
body->mocapid = -1;
|
|
}
|
|
}
|
|
|
|
if (computesize) {
|
|
nq = qposadr;
|
|
nv = dofadr;
|
|
na = actadr;
|
|
nu = (int)actuators_.size();
|
|
nmocap = mocapadr;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
void mjCModel::CopyExplicitPlugin(T* obj) {
|
|
if (!obj->plugin.active || !obj->plugin_instance_name.empty() || !obj->spec.plugin.element) {
|
|
return;
|
|
}
|
|
mjCPlugin* origin = static_cast<mjCPlugin*>(obj->spec.plugin.element);
|
|
mjCPlugin* candidate = deepcopy_ ? new mjCPlugin(*origin) : origin;
|
|
candidate->id = plugins_.size();
|
|
candidate->model = this;
|
|
if (!deepcopy_) {
|
|
candidate->AddRef();
|
|
}
|
|
plugins_.push_back(candidate);
|
|
obj->spec.plugin.element = candidate;
|
|
}
|
|
|
|
template void mjCModel::CopyExplicitPlugin<mjCBody>(mjCBody* obj);
|
|
template void mjCModel::CopyExplicitPlugin<mjCGeom>(mjCGeom* obj);
|
|
template void mjCModel::CopyExplicitPlugin<mjCMesh>(mjCMesh* obj);
|
|
template void mjCModel::CopyExplicitPlugin<mjCActuator>(mjCActuator* obj);
|
|
template void mjCModel::CopyExplicitPlugin<mjCSensor>(mjCSensor* obj);
|
|
|
|
|
|
|
|
template <class T>
|
|
void mjCModel::CopyPlugin(const std::vector<mjCPlugin*>& source,
|
|
const std::vector<T*>& list) {
|
|
// store elements that reference a plugin instance
|
|
std::unordered_map<std::string, T*> instances;
|
|
for (const auto& element : list) {
|
|
if (!element->plugin_instance_name.empty()) {
|
|
instances[element->plugin_instance_name] = element;
|
|
}
|
|
}
|
|
|
|
// only copy plugins that are referenced
|
|
for (const auto& plugin : source) {
|
|
if (plugin->name.empty() && plugin->model == this) {
|
|
continue;
|
|
}
|
|
mjCPlugin* candidate = new mjCPlugin(*plugin);
|
|
candidate->model = this;
|
|
candidate->NameSpace(plugin->model);
|
|
bool referenced = instances.find(candidate->name) != instances.end();
|
|
auto same_name = [candidate](const mjCPlugin* dest) {
|
|
return dest->name == candidate->name;
|
|
};
|
|
bool instance_exists = std::find_if(plugins_.begin(), plugins_.end(),
|
|
same_name) != plugins_.end();
|
|
if (referenced && !instance_exists) {
|
|
plugins_.push_back(candidate);
|
|
instances.at(candidate->name)->spec.plugin.element = candidate;
|
|
} else {
|
|
delete candidate;
|
|
}
|
|
}
|
|
|
|
// update other elements in the list in case of multiple references
|
|
for (auto& element : list) {
|
|
if (!element->plugin_instance_name.empty()) {
|
|
element->spec.plugin.element =
|
|
instances.at(element->plugin_instance_name)->spec.plugin.element;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// return true if the plugin is already in the list of active plugins
|
|
static bool IsPluginActive(
|
|
const mjpPlugin* plugin,
|
|
const std::vector<std::pair<const mjpPlugin*, int> >& active_plugins) {
|
|
return std::find_if(
|
|
active_plugins.begin(), active_plugins.end(),
|
|
[&plugin](const std::pair<const mjpPlugin*, int>& element) {
|
|
return element.first == plugin;
|
|
}) != active_plugins.end();
|
|
}
|
|
|
|
|
|
|
|
mjCModel& mjCModel::operator+=(const mjCModel& other) {
|
|
// create global lists
|
|
ResetTreeLists();
|
|
MakeTreeLists();
|
|
ProcessLists(/*checkrepeat=*/false);
|
|
|
|
// copy all elements not in the tree
|
|
if (this != &other) {
|
|
// do not copy assets for self-attach
|
|
// TODO: asset should be copied only when referenced
|
|
CopyList(meshes_, other.meshes_);
|
|
CopyList(skins_, other.skins_);
|
|
CopyList(hfields_, other.hfields_);
|
|
CopyList(textures_, other.textures_);
|
|
CopyList(materials_, other.materials_);
|
|
for (const auto& key : other.key_pending_) {
|
|
key_pending_.push_back(key);
|
|
}
|
|
CopyList(numerics_, other.numerics_);
|
|
CopyList(texts_, other.texts_);
|
|
}
|
|
CopyList(flexes_, other.flexes_);
|
|
CopyList(pairs_, other.pairs_);
|
|
CopyList(excludes_, other.excludes_);
|
|
CopyList(tendons_, other.tendons_);
|
|
CopyList(equalities_, other.equalities_);
|
|
CopyList(actuators_, other.actuators_);
|
|
CopyList(sensors_, other.sensors_);
|
|
CopyList(tuples_, other.tuples_);
|
|
|
|
// create new plugins and map them
|
|
CopyPlugin(other.plugins_, bodies_);
|
|
CopyPlugin(other.plugins_, geoms_);
|
|
CopyPlugin(other.plugins_, meshes_);
|
|
CopyPlugin(other.plugins_, actuators_);
|
|
CopyPlugin(other.plugins_, sensors_);
|
|
for (const auto& [plugin, slot] : other.active_plugins_) {
|
|
if (!IsPluginActive(plugin, active_plugins_)) {
|
|
active_plugins_.emplace_back(std::make_pair(plugin, slot));
|
|
}
|
|
}
|
|
|
|
// resize keyframes in the parent model
|
|
ExpandAllKeyframes();
|
|
|
|
// update pointers to local elements
|
|
PointToLocal();
|
|
|
|
// update signature after we updated the tree lists and we updated the pointers
|
|
spec.element->signature = Signature();
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
void mjCModel::RemoveFromList(std::vector<T*>& list, const mjCModel& other) {
|
|
int nlist = (int)list.size();
|
|
int removed = 0;
|
|
for (int i = 0; i < nlist; i++) {
|
|
T* element = list[i];
|
|
element->id -= removed;
|
|
try {
|
|
// check if the element contains an error
|
|
element->NameSpace(&other);
|
|
element->CopyFromSpec();
|
|
element->ResolveReferences(&other);
|
|
} catch (mjCError err) {
|
|
continue;
|
|
}
|
|
try {
|
|
// check if the element references something that was removed
|
|
element->NameSpace(this);
|
|
element->CopyFromSpec();
|
|
element->ResolveReferences(this);
|
|
} catch (mjCError err) {
|
|
ids[element->elemtype].erase(element->name);
|
|
element->Release();
|
|
list.erase(list.begin() + i);
|
|
nlist--;
|
|
i--;
|
|
removed++;
|
|
}
|
|
}
|
|
if (removed > 0 && !list.empty()) {
|
|
// if any elements were removed, update ids using processlist
|
|
ProcessList_(ids, list, list[0]->elemtype, /*checkrepeat=*/false);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <>
|
|
void mjCModel::DeleteAll<mjCKey>(std::vector<mjCKey*>& elements) {
|
|
for (mjCKey* element : elements) {
|
|
element->Release();
|
|
}
|
|
elements.clear();
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
void mjCModel::MarkPluginInstance(std::unordered_map<std::string, bool>& instances,
|
|
const std::vector<T*>& list) {
|
|
for (const auto& element : list) {
|
|
if (!element->plugin_instance_name.empty()) {
|
|
instances[element->plugin_instance_name] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::RemovePlugins() {
|
|
// store elements that reference a plugin instance
|
|
std::unordered_map<std::string, bool> instances;
|
|
MarkPluginInstance(instances, bodies_);
|
|
MarkPluginInstance(instances, geoms_);
|
|
MarkPluginInstance(instances, meshes_);
|
|
MarkPluginInstance(instances, actuators_);
|
|
MarkPluginInstance(instances, sensors_);
|
|
|
|
// remove plugins that are not referenced
|
|
int nlist = (int)plugins_.size();
|
|
int removed = 0;
|
|
for (int i = 0; i < nlist; i++) {
|
|
if (plugins_[i]->name.empty()) {
|
|
continue;
|
|
}
|
|
if (instances.find(plugins_[i]->name) == instances.end()) {
|
|
ids[plugins_[i]->elemtype].erase(plugins_[i]->name);
|
|
plugins_[i]->Release();
|
|
plugins_.erase(plugins_.begin() + i);
|
|
nlist--;
|
|
i--;
|
|
removed++;
|
|
}
|
|
}
|
|
|
|
// if any elements were removed, update ids using processlist
|
|
if (removed > 0 && !plugins_.empty()) {
|
|
ProcessList_(ids, plugins_, plugins_[0]->elemtype, /*checkrepeat=*/false);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
mjCModel& mjCModel::operator-=(const mjCBody& subtree) {
|
|
mjCModel oldmodel(*this);
|
|
|
|
// create global lists in the old model if not compiled
|
|
if (!oldmodel.IsCompiled()) {
|
|
oldmodel.ProcessLists(/*checkrepeat=*/false);
|
|
}
|
|
|
|
// create global lists in this model if not compiled
|
|
if (!IsCompiled()) {
|
|
ProcessLists(/*checkrepeat=*/false);
|
|
}
|
|
|
|
// all keyframes are now pending and they will be resized
|
|
StoreKeyframes(this);
|
|
DeleteAll(keys_);
|
|
|
|
// remove body from tree
|
|
mjCBody* world = bodies_[0];
|
|
*world -= subtree;
|
|
|
|
// update global lists
|
|
ResetTreeLists();
|
|
MakeTreeLists();
|
|
ProcessLists(/*checkrepeat=*/false);
|
|
|
|
// check if we have to remove anything else
|
|
RemoveFromList(pairs_, oldmodel);
|
|
RemoveFromList(excludes_, oldmodel);
|
|
RemoveFromList(tendons_, oldmodel);
|
|
RemoveFromList(equalities_, oldmodel);
|
|
RemoveFromList(actuators_, oldmodel);
|
|
RemoveFromList(sensors_, oldmodel);
|
|
RemovePlugins();
|
|
|
|
// update signature before we reset the tree lists
|
|
spec.element->signature = Signature();
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
// add default tree to this model
|
|
mjCModel& mjCModel::operator+=(mjCDef& subtree) {
|
|
defaults_.push_back(&subtree);
|
|
def_map[subtree.name] = &subtree;
|
|
subtree.model = this;
|
|
|
|
// set parent to the main default if this is not the only default in the model
|
|
if (!subtree.parent && &subtree != defaults_[0]) {
|
|
subtree.parent = defaults_[0];
|
|
defaults_[0]->child.push_back(&subtree);
|
|
}
|
|
|
|
for (auto def : subtree.child) {
|
|
*this += *def; // triggers recursive call
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
// remove default class from array
|
|
mjCModel& mjCModel::operator-=(const mjCDef& subtree) {
|
|
|
|
// check we aren't trying to remove the 'main' default
|
|
if (subtree.id == 0) {
|
|
throw mjCError(0, "cannot remove the global default ('main')");
|
|
}
|
|
|
|
// remove this default from parent's child list
|
|
mjCDef* parent = subtree.parent;
|
|
if (parent) {
|
|
for (int i = 0; i < parent->child.size(); ++i) {
|
|
if (parent->child[i] == &subtree) {
|
|
parent->child.erase(parent->child.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// traverse tree to find all descendants starting from subtree.id
|
|
std::vector<int> default_ids_to_remove;
|
|
std::vector<int> stack;
|
|
stack.push_back(subtree.id);
|
|
while (!stack.empty()) {
|
|
int id = stack.back();
|
|
stack.pop_back();
|
|
default_ids_to_remove.push_back(id);
|
|
for (int i=0; i<defaults_[id]->child.size(); i++) {
|
|
stack.push_back(defaults_[id]->child[i]->id);
|
|
}
|
|
}
|
|
|
|
// remove from the tree
|
|
std::sort(default_ids_to_remove.begin(),
|
|
default_ids_to_remove.end(),
|
|
std::greater<int>());
|
|
|
|
for (int id : default_ids_to_remove) {
|
|
delete defaults_[id];
|
|
defaults_.erase(defaults_.begin() + id);
|
|
}
|
|
|
|
// reset default ids
|
|
for (int i = 0; i < defaults_.size(); ++i) {
|
|
defaults_[i]->id = i;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
void deletefromlist(std::vector<T*>* list, mjsElement* element) {
|
|
if (!list) {
|
|
return;
|
|
}
|
|
for (int j = 0; j < list->size(); ++j) {
|
|
list->at(j)->id = -1;
|
|
if (list->at(j) == element) {
|
|
list->erase(list->begin() + j);
|
|
j--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// recursively delete all plugins in the subtree
|
|
void mjCModel::DeleteSubtreePlugin(mjCBody* subtree) {
|
|
mjsPlugin* plugin = &(subtree->spec.plugin);
|
|
if (plugin->active && plugin->name->empty()) {
|
|
*this -= plugin->element;
|
|
}
|
|
for (auto* body : subtree->Bodies()) {
|
|
DeleteSubtreePlugin(body);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// remove the element from the model
|
|
void mjCModel::operator-=(mjsElement* el) {
|
|
if (el->elemtype == mjOBJ_BODY) {
|
|
mjCBody* body = static_cast<mjCBody*>(el);
|
|
*this -= *body;
|
|
}
|
|
|
|
detached_.push_back(static_cast<mjCBase*>(el));
|
|
ResetTreeLists();
|
|
|
|
if (el->elemtype != mjOBJ_DEFAULT) {
|
|
if (static_cast<mjCBase*>(el)->model != this) {
|
|
throw mjCError(nullptr, "element is not in this model");
|
|
}
|
|
} else {
|
|
if (static_cast<mjCDef*>(el)->model != this) {
|
|
throw mjCError(nullptr, "default is not in this model");
|
|
}
|
|
}
|
|
|
|
switch (el->elemtype) {
|
|
case mjOBJ_BODY:
|
|
{
|
|
MakeTreeLists(); // rebuild lists that were reset at the beginning of the function
|
|
mjCBody* subtree = static_cast<mjCBody*>(el);
|
|
DeleteSubtreePlugin(subtree);
|
|
break;
|
|
}
|
|
|
|
case mjOBJ_DEFAULT:
|
|
MakeTreeLists(); // rebuild lists that were reset at the beginning of the function
|
|
throw mjCError(nullptr, "defaults cannot be deleted, use detach instead");
|
|
break;
|
|
|
|
case mjOBJ_GEOM:
|
|
{
|
|
mjCGeom* geom = static_cast<mjCGeom*>(el);
|
|
if (geom->plugin.active && geom->plugin.name->empty()) {
|
|
*this -= geom->plugin.element;
|
|
}
|
|
deletefromlist(&(geom->body->geoms), el);
|
|
break;
|
|
}
|
|
|
|
case mjOBJ_SITE:
|
|
deletefromlist(&(static_cast<mjCSite*>(el)->body->sites), el);
|
|
break;
|
|
|
|
case mjOBJ_JOINT:
|
|
deletefromlist(&(static_cast<mjCJoint*>(el)->body->joints), el);
|
|
break;
|
|
|
|
case mjOBJ_LIGHT:
|
|
deletefromlist(&(static_cast<mjCLight*>(el)->body->lights), el);
|
|
break;
|
|
|
|
case mjOBJ_CAMERA:
|
|
deletefromlist(&(static_cast<mjCCamera*>(el)->body->cameras), el);
|
|
break;
|
|
|
|
case mjOBJ_MESH:
|
|
{
|
|
mjCMesh* mesh = static_cast<mjCMesh*>(el);
|
|
if (mesh->plugin.active && mesh->plugin.name->empty()) {
|
|
*this -= mesh->plugin.element;
|
|
}
|
|
deletefromlist(object_lists_[mjOBJ_MESH], el);
|
|
break;
|
|
}
|
|
|
|
case mjOBJ_ACTUATOR:
|
|
{
|
|
mjCActuator* actuator = static_cast<mjCActuator*>(el);
|
|
if (actuator->plugin.active && actuator->plugin.name->empty()) {
|
|
*this -= actuator->plugin.element;
|
|
}
|
|
deletefromlist(object_lists_[mjOBJ_ACTUATOR], el);
|
|
break;
|
|
}
|
|
|
|
case mjOBJ_SENSOR:
|
|
{
|
|
mjCSensor* sensor = static_cast<mjCSensor*>(el);
|
|
if (sensor->plugin.active && sensor->plugin.name->empty()) {
|
|
*this -= sensor->plugin.element;
|
|
}
|
|
deletefromlist(object_lists_[mjOBJ_SENSOR], el);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
deletefromlist(object_lists_[el->elemtype], el);
|
|
break;
|
|
}
|
|
|
|
ResetTreeLists(); // in case of a nested delete
|
|
MakeTreeLists();
|
|
ProcessLists(/*checkrepeat=*/false);
|
|
|
|
// update signature after we updated everything
|
|
spec.element->signature = Signature();
|
|
}
|
|
|
|
|
|
|
|
// TODO: we should not use C-type casting with multiple C++ inheritance
|
|
void mjCModel::CreateObjectLists() {
|
|
for (int i = 0; i < mjNOBJECT; ++i) {
|
|
object_lists_[i] = nullptr;
|
|
}
|
|
|
|
object_lists_[mjOBJ_BODY] = (std::vector<mjCBase*>*) &bodies_;
|
|
object_lists_[mjOBJ_XBODY] = (std::vector<mjCBase*>*) &bodies_;
|
|
object_lists_[mjOBJ_JOINT] = (std::vector<mjCBase*>*) &joints_;
|
|
object_lists_[mjOBJ_GEOM] = (std::vector<mjCBase*>*) &geoms_;
|
|
object_lists_[mjOBJ_SITE] = (std::vector<mjCBase*>*) &sites_;
|
|
object_lists_[mjOBJ_CAMERA] = (std::vector<mjCBase*>*) &cameras_;
|
|
object_lists_[mjOBJ_LIGHT] = (std::vector<mjCBase*>*) &lights_;
|
|
object_lists_[mjOBJ_FLEX] = (std::vector<mjCBase*>*) &flexes_;
|
|
object_lists_[mjOBJ_MESH] = (std::vector<mjCBase*>*) &meshes_;
|
|
object_lists_[mjOBJ_SKIN] = (std::vector<mjCBase*>*) &skins_;
|
|
object_lists_[mjOBJ_HFIELD] = (std::vector<mjCBase*>*) &hfields_;
|
|
object_lists_[mjOBJ_TEXTURE] = (std::vector<mjCBase*>*) &textures_;
|
|
object_lists_[mjOBJ_MATERIAL] = (std::vector<mjCBase*>*) &materials_;
|
|
object_lists_[mjOBJ_PAIR] = (std::vector<mjCBase*>*) &pairs_;
|
|
object_lists_[mjOBJ_EXCLUDE] = (std::vector<mjCBase*>*) &excludes_;
|
|
object_lists_[mjOBJ_EQUALITY] = (std::vector<mjCBase*>*) &equalities_;
|
|
object_lists_[mjOBJ_TENDON] = (std::vector<mjCBase*>*) &tendons_;
|
|
object_lists_[mjOBJ_ACTUATOR] = (std::vector<mjCBase*>*) &actuators_;
|
|
object_lists_[mjOBJ_SENSOR] = (std::vector<mjCBase*>*) &sensors_;
|
|
object_lists_[mjOBJ_NUMERIC] = (std::vector<mjCBase*>*) &numerics_;
|
|
object_lists_[mjOBJ_TEXT] = (std::vector<mjCBase*>*) &texts_;
|
|
object_lists_[mjOBJ_TUPLE] = (std::vector<mjCBase*>*) &tuples_;
|
|
object_lists_[mjOBJ_KEY] = (std::vector<mjCBase*>*) &keys_;
|
|
object_lists_[mjOBJ_PLUGIN] = (std::vector<mjCBase*>*) &plugins_;
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::PointToLocal() {
|
|
spec.element = static_cast<mjsElement*>(this);
|
|
spec.comment = &spec_comment_;
|
|
spec.modelfiledir = &spec_modelfiledir_;
|
|
spec.modelname = &spec_modelname_;
|
|
spec.meshdir = &spec_meshdir_;
|
|
spec.texturedir = &spec_texturedir_;
|
|
comment = nullptr;
|
|
modelfiledir = nullptr;
|
|
modelname = nullptr;
|
|
meshdir = nullptr;
|
|
texturedir = nullptr;
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::CopyFromSpec() {
|
|
*static_cast<mjSpec*>(this) = spec;
|
|
comment_ = spec_comment_;
|
|
modelfiledir_ = spec_modelfiledir_;
|
|
modelname_ = spec_modelname_;
|
|
meshdir_ = spec_meshdir_;
|
|
texturedir_ = spec_texturedir_;
|
|
}
|
|
|
|
|
|
|
|
// compute sparse matrix sizes
|
|
void mjCModel::ComputeSparseSizes() {
|
|
// no dofs, quick return
|
|
if (nv == 0) {
|
|
nM = nD = nB = nC = 0;
|
|
return;
|
|
}
|
|
|
|
// 0. allocate local index vectors
|
|
std::vector<int> dof_parentid_pre(nv, -1);
|
|
std::vector<int> dof_bodyid_pre(nv);
|
|
std::vector<int> body_simple_pre(nbody);
|
|
std::vector<int> body_rootid_pre(nbody);
|
|
std::vector<int> dof_simplenum_pre(nv);
|
|
std::vector<int> body_lastdof_map(nbody);
|
|
|
|
// 1. build dof_parentid, dof_bodyid
|
|
if (nbody > 0) {
|
|
body_lastdof_map[0] = -1; // world has no parent dof
|
|
}
|
|
for (int i = 0; i < nbody; ++i) {
|
|
mjCBody* pb = bodies_[i];
|
|
mjCBody* par = pb->parent;
|
|
|
|
// the last dof of the current body's parent chain
|
|
int current_parent_dof = par ? body_lastdof_map[par->id] : -1;
|
|
|
|
for (const auto* jnt : pb->joints) {
|
|
for (int j1 = 0; j1 < jnt->nv(); ++j1) {
|
|
int dofadr = jnt->dofadr_ + j1;
|
|
if (dofadr < 0 || dofadr >= nv) {
|
|
throw mjCError(jnt, "dofadr out of bounds: dofadr=%d, nv=%d", nullptr, dofadr, nv);
|
|
}
|
|
dof_bodyid_pre[dofadr] = i;
|
|
dof_parentid_pre[dofadr] = current_parent_dof;
|
|
// the next dof in this joint is parented to the current one
|
|
current_parent_dof = dofadr;
|
|
}
|
|
}
|
|
// store the last dof added for this body
|
|
body_lastdof_map[i] = current_parent_dof;
|
|
}
|
|
|
|
// 2. compute nM
|
|
nM = 0;
|
|
for (int i = 0; i < nv; ++i) {
|
|
int j = i;
|
|
while (j != -1) {
|
|
nM++;
|
|
j = dof_parentid_pre[j];
|
|
}
|
|
}
|
|
|
|
// 3. compute nD
|
|
nD = 2 * nM - nv;
|
|
|
|
// 4. compute subtreedofs and nB
|
|
for(int i = nbody - 1; i >= 0; --i) {
|
|
bodies_[i]->subtreedofs = bodies_[i]->dofnum;
|
|
for (const auto* child : bodies_[i]->Bodies()) {
|
|
bodies_[i]->subtreedofs += child->subtreedofs;
|
|
}
|
|
}
|
|
|
|
nB = 0;
|
|
for (int i = 0; i < nbody; ++i) {
|
|
nB += bodies_[i]->subtreedofs;
|
|
mjCBody* parent = bodies_[i]->parent;
|
|
while (parent && parent->id > 0) {
|
|
nB += parent->dofnum;
|
|
parent = parent->parent;
|
|
}
|
|
}
|
|
|
|
// make sure all dofs are in world "subtree", SHOULD NOT OCCUR
|
|
if (bodies_[0]->subtreedofs != nv) {
|
|
throw mjCError(0, "all DOFs should be in world subtree");
|
|
}
|
|
|
|
// 5. compute nC
|
|
for(int i = 0; i < nbody; ++i) {
|
|
mjCBody* pb = bodies_[i];
|
|
mjCBody* par = pb->parent;
|
|
int parentid = par ? par->id : 0;
|
|
|
|
// rootid
|
|
if (i == 0 || !par || par->id == 0) {
|
|
body_rootid_pre[i] = i;
|
|
} else {
|
|
body_rootid_pre[i] = body_rootid_pre[parentid];
|
|
}
|
|
|
|
bool sameframe = IsNullPose(pb->ipos, pb->iquat);
|
|
body_simple_pre[i] = (sameframe &&
|
|
(body_rootid_pre[i] == i ||
|
|
(parentid > 0 &&
|
|
bodies_[parentid]->parent &&
|
|
bodies_[parentid]->parent->id == 0 &&
|
|
bodies_[parentid]->dofnum == 0)));
|
|
}
|
|
|
|
// a parent body is never simple (unless world)
|
|
for (int i = 1; i < nbody; ++i) {
|
|
if (bodies_[i]->parent) {
|
|
body_simple_pre[bodies_[i]->parent->id] = 0;
|
|
}
|
|
}
|
|
|
|
// joint-based demotion for body_simple_pre
|
|
const double* nulldouble = nullptr;
|
|
for (int i = 1; i < nbody; ++i) {
|
|
if (!body_simple_pre[i]) continue;
|
|
|
|
// demote if non-aligned, non-zero pos, or multiple rotational joints
|
|
mjCBody* pb = bodies_[i];
|
|
int rotfound = 0;
|
|
for (const auto* pj : pb->joints) {
|
|
bool axis_aligned = ((std::abs(pj->axis[0]) > mjEPS) +
|
|
(std::abs(pj->axis[1]) > mjEPS) +
|
|
(std::abs(pj->axis[2]) > mjEPS)) == 1;
|
|
if (rotfound || !IsNullPose(pj->pos, nulldouble) ||
|
|
((pj->type == mjJNT_HINGE || pj->type == mjJNT_SLIDE) && !axis_aligned)) {
|
|
body_simple_pre[i] = 0;
|
|
break;
|
|
}
|
|
if (pj->type == mjJNT_BALL || pj->type == mjJNT_HINGE) {
|
|
rotfound = 1;
|
|
}
|
|
}
|
|
if (!body_simple_pre[i]) continue;
|
|
|
|
// promote simple bodies with only sliders to level 2
|
|
if (pb->dofnum > 0) {
|
|
body_simple_pre[i] = 2;
|
|
for (const auto* pj : pb->joints) {
|
|
if (pj->type != mjJNT_SLIDE) {
|
|
body_simple_pre[i] = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// tendon-armature-based demotion
|
|
for (const auto* tendon : tendons_) {
|
|
if (tendon->armature > 0) {
|
|
for (const auto* wrap : tendon->path) {
|
|
int bodyId = GetBodyIdFromWrap(wrap);
|
|
if (bodyId != -1) {
|
|
body_simple_pre[bodyId] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// count dof_simplenum_pre
|
|
int count = 0;
|
|
for (int i = nv - 1; i >= 0; --i) {
|
|
if (dof_bodyid_pre[i] < 0 || dof_bodyid_pre[i] >= nbody) {
|
|
throw mjCError(0, "dof_bodyid out of bounds: dof=%d, body=%d", nullptr, i, dof_bodyid_pre[i]);
|
|
}
|
|
if (body_simple_pre[dof_bodyid_pre[i]]) {
|
|
count++;
|
|
} else {
|
|
count = 0;
|
|
}
|
|
dof_simplenum_pre[i] = count;
|
|
}
|
|
|
|
// compute nC
|
|
nC = 0;
|
|
int nOD = 0;
|
|
for (int i = 0; i < nv; ++i) {
|
|
if (!dof_simplenum_pre[i]) {
|
|
int j = i;
|
|
while (j != -1) {
|
|
if (j != i) nOD++;
|
|
j = dof_parentid_pre[j];
|
|
}
|
|
}
|
|
}
|
|
nC = nOD + nv;
|
|
}
|
|
|
|
|
|
|
|
// destructor
|
|
mjCModel::~mjCModel() {
|
|
// do not rebuild lists if we are in the process of deleting the model
|
|
compiled = false;
|
|
|
|
// delete kinematic tree and all objects allocated in it
|
|
bodies_[0]->Release();
|
|
|
|
// delete objects allocated in mjCModel
|
|
for (int i=0; i < flexes_.size(); i++) flexes_[i]->Release();
|
|
for (int i=0; i < meshes_.size(); i++) meshes_[i]->Release();
|
|
for (int i=0; i < skins_.size(); i++) skins_[i]->Release();
|
|
for (int i=0; i < hfields_.size(); i++) hfields_[i]->Release();
|
|
for (int i=0; i < textures_.size(); i++) textures_[i]->Release();
|
|
for (int i=0; i < materials_.size(); i++) materials_[i]->Release();
|
|
for (int i=0; i < pairs_.size(); i++) pairs_[i]->Release();
|
|
for (int i=0; i < excludes_.size(); i++) excludes_[i]->Release();
|
|
for (int i=0; i < equalities_.size(); i++) equalities_[i]->Release();
|
|
for (int i=0; i < tendons_.size(); i++) tendons_[i]->Release(); // also deletes wraps
|
|
for (int i=0; i < actuators_.size(); i++) actuators_[i]->Release();
|
|
for (int i=0; i < sensors_.size(); i++) sensors_[i]->Release();
|
|
for (int i=0; i < numerics_.size(); i++) numerics_[i]->Release();
|
|
for (int i=0; i < texts_.size(); i++) texts_[i]->Release();
|
|
for (int i=0; i < tuples_.size(); i++) tuples_[i]->Release();
|
|
for (int i=0; i < keys_.size(); i++) keys_[i]->Release();
|
|
for (int i=0; i < defaults_.size(); i++) delete defaults_[i];
|
|
for (int i=0; i < specs_.size(); i++) mj_deleteSpec(specs_[i]);
|
|
for (int i=0; i < plugins_.size(); i++) plugins_[i]->Release();
|
|
for (int i=0; i < detached_.size(); i++) detached_[i]->Release();
|
|
|
|
// clear sizes and pointer lists created in Compile
|
|
Clear();
|
|
}
|
|
|
|
|
|
|
|
// clear objects allocated by Compile
|
|
void mjCModel::Clear() {
|
|
// sizes set from list lengths
|
|
nbody = 0;
|
|
nbvh = 0;
|
|
nbvhstatic = 0;
|
|
nbvhdynamic = 0;
|
|
noct = 0;
|
|
njnt = 0;
|
|
ngeom = 0;
|
|
nsite = 0;
|
|
ncam = 0;
|
|
nlight = 0;
|
|
nflex = 0;
|
|
nmesh = 0;
|
|
nskin = 0;
|
|
nhfield = 0;
|
|
ntex = 0;
|
|
nmat = 0;
|
|
npair = 0;
|
|
nexclude = 0;
|
|
neq = 0;
|
|
ntendon = 0;
|
|
nsensor = 0;
|
|
nnumeric = 0;
|
|
ntext = 0;
|
|
|
|
// sizes set by Compile
|
|
nq = 0;
|
|
nv = 0;
|
|
nu = 0;
|
|
na = 0;
|
|
nflexnode = 0;
|
|
nflexvert = 0;
|
|
nflexedge = 0;
|
|
nflexelem = 0;
|
|
nflexelemdata = 0;
|
|
nflexelemedge = 0;
|
|
nflexshelldata = 0;
|
|
nflexevpair = 0;
|
|
nflextexcoord = 0;
|
|
nmeshvert = 0;
|
|
nmeshnormal = 0;
|
|
nmeshtexcoord = 0;
|
|
nmeshface = 0;
|
|
nmeshgraph = 0;
|
|
nmeshpoly = 0;
|
|
nmeshpolyvert = 0;
|
|
nmeshpolymap = 0;
|
|
nskinvert = 0;
|
|
nskintexvert = 0;
|
|
nskinface = 0;
|
|
nskinbone = 0;
|
|
nskinbonevert = 0;
|
|
nhfielddata = 0;
|
|
ntexdata = 0;
|
|
nwrap = 0;
|
|
nsensordata = 0;
|
|
nnumericdata = 0;
|
|
ntextdata = 0;
|
|
ntupledata = 0;
|
|
npluginattr = 0;
|
|
nnames = 0;
|
|
npaths = 0;
|
|
memory = -1;
|
|
nstack = -1;
|
|
nemax = 0;
|
|
nM = 0;
|
|
nD = 0;
|
|
nB = 0;
|
|
nJmom = 0;
|
|
njmax = -1;
|
|
nconmax = -1;
|
|
nmocap = 0;
|
|
|
|
// internal variables
|
|
hasImplicitPluginElem = false;
|
|
compiled = false;
|
|
errInfo = mjCError();
|
|
qpos0.clear();
|
|
}
|
|
|
|
|
|
|
|
//------------------------ API FOR ADDING MODEL ELEMENTS -------------------------------------------
|
|
|
|
// add object of any type
|
|
template <class T>
|
|
T* mjCModel::AddObject(vector<T*>& list, string type) {
|
|
T* obj = new T(this);
|
|
obj->id = (int)list.size();
|
|
list.push_back(obj);
|
|
spec.element->signature = Signature();
|
|
return obj;
|
|
}
|
|
|
|
|
|
// add object of any type, with default parameter
|
|
template <class T>
|
|
T* mjCModel::AddObjectDefault(vector<T*>& list, string type, mjCDef* def) {
|
|
T* obj = new T(this, def ? def : defaults_[0]);
|
|
obj->id = (int)list.size();
|
|
obj->classname = def ? def->name : "main";
|
|
list.push_back(obj);
|
|
spec.element->signature = Signature();
|
|
return obj;
|
|
}
|
|
|
|
|
|
// add flex
|
|
mjCFlex* mjCModel::AddFlex() {
|
|
return AddObject(flexes_, "flex");
|
|
}
|
|
|
|
|
|
// add mesh
|
|
mjCMesh* mjCModel::AddMesh(mjCDef* def) {
|
|
return AddObjectDefault(meshes_, "mesh", def);
|
|
}
|
|
|
|
|
|
// add skin
|
|
mjCSkin* mjCModel::AddSkin() {
|
|
return AddObject(skins_, "skin");
|
|
}
|
|
|
|
|
|
// add hfield
|
|
mjCHField* mjCModel::AddHField() {
|
|
return AddObject(hfields_, "hfield");
|
|
}
|
|
|
|
|
|
// add texture
|
|
mjCTexture* mjCModel::AddTexture() {
|
|
return AddObject(textures_, "texture");
|
|
}
|
|
|
|
|
|
// add material
|
|
mjCMaterial* mjCModel::AddMaterial(mjCDef* def) {
|
|
return AddObjectDefault(materials_, "material", def);
|
|
}
|
|
|
|
|
|
// add geom pair to include in collisions
|
|
mjCPair* mjCModel::AddPair(mjCDef* def) {
|
|
return AddObjectDefault(pairs_, "pair", def);
|
|
}
|
|
|
|
|
|
// add body pair to exclude from collisions
|
|
mjCBodyPair* mjCModel::AddExclude() {
|
|
return AddObject(excludes_, "exclude");
|
|
}
|
|
|
|
|
|
// add constraint
|
|
mjCEquality* mjCModel::AddEquality(mjCDef* def) {
|
|
return AddObjectDefault(equalities_, "equality", def);
|
|
}
|
|
|
|
|
|
// add tendon
|
|
mjCTendon* mjCModel::AddTendon(mjCDef* def) {
|
|
return AddObjectDefault(tendons_, "tendon", def);
|
|
}
|
|
|
|
|
|
// add actuator
|
|
mjCActuator* mjCModel::AddActuator(mjCDef* def) {
|
|
return AddObjectDefault(actuators_, "actuator", def);
|
|
}
|
|
|
|
|
|
// add sensor
|
|
mjCSensor* mjCModel::AddSensor() {
|
|
return AddObject(sensors_, "sensor");
|
|
}
|
|
|
|
|
|
|
|
// add custom
|
|
mjCNumeric* mjCModel::AddNumeric() {
|
|
return AddObject(numerics_, "numeric");
|
|
}
|
|
|
|
|
|
// add text
|
|
mjCText* mjCModel::AddText() {
|
|
return AddObject(texts_, "text");
|
|
}
|
|
|
|
|
|
// add tuple
|
|
mjCTuple* mjCModel::AddTuple() {
|
|
return AddObject(tuples_, "tuple");
|
|
}
|
|
|
|
|
|
// add keyframe
|
|
mjCKey* mjCModel::AddKey() {
|
|
return AddObject(keys_, "key");
|
|
}
|
|
|
|
|
|
// add plugin instance
|
|
mjCPlugin* mjCModel::AddPlugin() {
|
|
return AddObject(plugins_, "plugin");
|
|
}
|
|
|
|
|
|
// append spec to spec
|
|
void mjCModel::AppendSpec(mjSpec* spec, const mjsCompiler* compiler_) {
|
|
// TODO: check if the spec is already in the list
|
|
specs_.push_back(spec);
|
|
|
|
if (compiler_) {
|
|
compiler2spec_[compiler_] = spec;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//------------------------ API FOR ACCESS TO MODEL ELEMENTS ---------------------------------------
|
|
|
|
// get number of objects of specified type
|
|
int mjCModel::NumObjects(mjtObj type) {
|
|
if (!object_lists_[type]) {
|
|
return 0;
|
|
}
|
|
return (int) object_lists_[type]->size();
|
|
}
|
|
|
|
|
|
|
|
// get pointer to specified object
|
|
mjCBase* mjCModel::GetObject(mjtObj type, int id) {
|
|
if (id < 0 || id >= NumObjects(type)) {
|
|
return nullptr;
|
|
}
|
|
return (*object_lists_[type])[id];
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
static mjsElement* GetNext(std::vector<T*>& list, mjsElement* child) {
|
|
if (!child) {
|
|
if (list.empty()) {
|
|
return nullptr;
|
|
}
|
|
return list[0]->spec.element;
|
|
}
|
|
|
|
// TODO: use id for direct indexing instead of a loop
|
|
for (unsigned int i = 0; i < list.size()-1; i++) {
|
|
if (list[i]->spec.element == child) {
|
|
return list[i+1]->spec.element;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
// next object of specified type
|
|
mjsElement* mjCModel::NextObject(mjsElement* object, mjtObj type) {
|
|
if (type == mjOBJ_UNKNOWN) {
|
|
if (!object) {
|
|
throw mjCError(nullptr, "type must be specified if no element is given");
|
|
} else {
|
|
type = object->elemtype;
|
|
}
|
|
} else if (object && object->elemtype != type) {
|
|
throw mjCError(nullptr, "element is not of requested type");
|
|
}
|
|
|
|
switch (type) {
|
|
case mjOBJ_BODY:
|
|
if (!object) {
|
|
return bodies_[0]->spec.element;
|
|
} else if (object == bodies_[0]->spec.element) {
|
|
return bodies_[0]->NextChild(NULL, type, /*recursive=*/true);
|
|
} else {
|
|
return bodies_[0]->NextChild(object, type, /*recursive=*/true);
|
|
}
|
|
case mjOBJ_SITE:
|
|
case mjOBJ_GEOM:
|
|
case mjOBJ_JOINT:
|
|
case mjOBJ_CAMERA:
|
|
case mjOBJ_LIGHT:
|
|
case mjOBJ_FRAME:
|
|
return bodies_[0]->NextChild(object, type, /*recursive=*/true);
|
|
case mjOBJ_ACTUATOR:
|
|
return GetNext(actuators_, object);
|
|
case mjOBJ_SENSOR:
|
|
return GetNext(sensors_, object);
|
|
case mjOBJ_FLEX:
|
|
return GetNext(flexes_, object);
|
|
case mjOBJ_PAIR:
|
|
return GetNext(pairs_, object);
|
|
case mjOBJ_EXCLUDE:
|
|
return GetNext(excludes_, object);
|
|
case mjOBJ_EQUALITY:
|
|
return GetNext(equalities_, object);
|
|
case mjOBJ_TENDON:
|
|
return GetNext(tendons_, object);
|
|
case mjOBJ_NUMERIC:
|
|
return GetNext(numerics_, object);
|
|
case mjOBJ_TEXT:
|
|
return GetNext(texts_, object);
|
|
case mjOBJ_TUPLE:
|
|
return GetNext(tuples_, object);
|
|
case mjOBJ_KEY:
|
|
return GetNext(keys_, object);
|
|
case mjOBJ_MESH:
|
|
return GetNext(meshes_, object);
|
|
case mjOBJ_HFIELD:
|
|
return GetNext(hfields_, object);
|
|
case mjOBJ_SKIN:
|
|
return GetNext(skins_, object);
|
|
case mjOBJ_TEXTURE:
|
|
return GetNext(textures_, object);
|
|
case mjOBJ_MATERIAL:
|
|
return GetNext(materials_, object);
|
|
case mjOBJ_PLUGIN:
|
|
return GetNext(plugins_, object);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------ API FOR ACCESS TO PRIVATE VARIABLES -------------------------------------
|
|
|
|
// compiled flag
|
|
bool mjCModel::IsCompiled() const {
|
|
return compiled;
|
|
}
|
|
|
|
|
|
|
|
// get reference of error object
|
|
const mjCError& mjCModel::GetError() const {
|
|
return errInfo;
|
|
}
|
|
|
|
|
|
|
|
// pointer to world body
|
|
mjCBody* mjCModel::GetWorld() {
|
|
return bodies_[0];
|
|
}
|
|
|
|
|
|
|
|
// find default class name in array
|
|
mjCDef* mjCModel::FindDefault(string name) {
|
|
for (int i=0; i < (int)defaults_.size(); i++) {
|
|
if (defaults_[i]->name == name) {
|
|
return defaults_[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
// add default class to array
|
|
mjCDef* mjCModel::AddDefault(string name, mjCDef* parent) {
|
|
// check for repeated name
|
|
int thisid = (int)defaults_.size();
|
|
for (int i=0; i < thisid; i++) {
|
|
if (defaults_[i]->name == name) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// create new object
|
|
mjCDef* def = new mjCDef(parent->model);
|
|
defaults_.push_back(def);
|
|
def->id = thisid;
|
|
|
|
// initialize contents
|
|
if (parent && parent->id < thisid) {
|
|
parent->CopyFromSpec();
|
|
def->CopyWithoutChildren(*parent);
|
|
parent->child.push_back(def);
|
|
}
|
|
def->parent = parent;
|
|
def->name = name;
|
|
def->child.clear();
|
|
def_map[name] = def;
|
|
|
|
return def;
|
|
}
|
|
|
|
|
|
|
|
// find object by name in given list
|
|
template <class T>
|
|
static T* findobject(std::string_view name, const vector<T*>& list, const mjKeyMap& ids) {
|
|
// this can occur in the URDF parser
|
|
if (ids.empty()) {
|
|
for (unsigned int i=0; i < list.size(); i++) {
|
|
if (list[i]->name == name) {
|
|
return list[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// during model compilation
|
|
auto id = ids.find(name);
|
|
if (id == ids.end()) {
|
|
return nullptr;
|
|
}
|
|
if (id->second > (int)list.size() - 1) {
|
|
throw mjCError(0, "object not found");
|
|
}
|
|
return list[id->second];
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
mjCBase* mjCModel::FindAsset(std::string_view name, const std::vector<T*>& list) const {
|
|
for (unsigned int i=0; i < list.size(); i++) {
|
|
if (list[i]->name == name) {
|
|
return list[i];
|
|
}
|
|
if (list[i]->name.empty() &&
|
|
std::filesystem::path(list[i]->spec_file_).filename().stem() == name) {
|
|
return list[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template mjCBase* mjCModel::FindAsset<mjCTexture>(
|
|
std::string_view name, const std::vector<mjCTexture*>& list) const;
|
|
template mjCBase* mjCModel::FindAsset<mjCMesh>(
|
|
std::string_view name, const std::vector<mjCMesh*>& list) const;
|
|
|
|
|
|
|
|
// find object in global lists given string type and name
|
|
mjCBase* mjCModel::FindObject(mjtObj type, string name) const {
|
|
if (!object_lists_[type]) {
|
|
return nullptr;
|
|
}
|
|
return findobject(name, *object_lists_[type], ids[type]);
|
|
}
|
|
|
|
|
|
|
|
// find body by name
|
|
mjCBase* mjCModel::FindTree(mjCBody* body, mjtObj type, std::string name) {
|
|
switch (type) {
|
|
case mjOBJ_BODY:
|
|
if (body->name == name) {
|
|
return body;
|
|
}
|
|
break;
|
|
case mjOBJ_SITE:
|
|
for (auto site : body->sites) {
|
|
if (site->name == name) {
|
|
return site;
|
|
}
|
|
}
|
|
break;
|
|
case mjOBJ_GEOM:
|
|
for (auto geom : body->geoms) {
|
|
if (geom->name == name) {
|
|
return geom;
|
|
}
|
|
}
|
|
break;
|
|
case mjOBJ_JOINT:
|
|
for (auto joint : body->joints) {
|
|
if (joint->name == name) {
|
|
return joint;
|
|
}
|
|
}
|
|
break;
|
|
case mjOBJ_CAMERA:
|
|
for (auto camera : body->cameras) {
|
|
if (camera->name == name) {
|
|
return camera;
|
|
}
|
|
}
|
|
break;
|
|
case mjOBJ_LIGHT:
|
|
for (auto light : body->lights) {
|
|
if (light->name == name) {
|
|
return light;
|
|
}
|
|
}
|
|
break;
|
|
case mjOBJ_FRAME:
|
|
for (auto frame : body->frames) {
|
|
if (frame->name == name) {
|
|
return frame;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
|
|
for (auto child : body->bodies) {
|
|
auto candidate = FindTree(child, type, name);
|
|
if (candidate) {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
// find spec by name
|
|
mjSpec* mjCModel::FindSpec(std::string name) const {
|
|
for (auto spec : specs_) {
|
|
if (mjs_getString(spec->modelname) == name) {
|
|
return spec;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
// find spec by mjsCompiler pointer
|
|
mjSpec* mjCModel::FindSpec(const mjsCompiler* compiler_) {
|
|
if (compiler_ == &spec.compiler) {
|
|
return &spec;
|
|
}
|
|
|
|
if (compiler2spec_.find(compiler_) != compiler2spec_.end()) {
|
|
return compiler2spec_[compiler_];
|
|
}
|
|
|
|
for (auto s : specs_) {
|
|
mjSpec* source = static_cast<mjCModel*>(s->element)->FindSpec(compiler_);
|
|
if (source) {
|
|
return source;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
//------------------------------- COMPILER PHASES --------------------------------------------------
|
|
|
|
// make lists of objects in tree: bodies, geoms, joints, sites, cameras, lights
|
|
void mjCModel::MakeTreeLists(mjCBody* body) {
|
|
if (body == nullptr) {
|
|
body = bodies_[0];
|
|
}
|
|
|
|
// add this body if not world
|
|
if (body != bodies_[0]) {
|
|
bodies_.push_back(body);
|
|
}
|
|
|
|
// add body's geoms, joints, sites, cameras, lights
|
|
for (mjCGeom *geom : body->geoms) geoms_.push_back(geom);
|
|
for (mjCJoint *joint : body->joints) joints_.push_back(joint);
|
|
for (mjCSite *site : body->sites) sites_.push_back(site);
|
|
for (mjCCamera *camera : body->cameras) cameras_.push_back(camera);
|
|
for (mjCLight *light : body->lights) lights_.push_back(light);
|
|
for (mjCFrame *frame : body->frames) frames_.push_back(frame);
|
|
|
|
// recursive call to all child bodies
|
|
for (mjCBody* body : body->bodies) MakeTreeLists(body);
|
|
}
|
|
|
|
|
|
// delete material with given name or all materials if the name is omitted
|
|
template <class T>
|
|
void mjCModel::DeleteMaterial(std::vector<T*>& list, std::string_view name) {
|
|
for (T* plist : list) {
|
|
if (name.empty() || plist->get_material() == name) {
|
|
plist->del_material();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// delete all textures
|
|
template <class T>
|
|
static void DeleteAllTextures(std::vector<T*>& list) {
|
|
for (T* plist : list) {
|
|
plist->del_textures();
|
|
}
|
|
}
|
|
|
|
|
|
// delete all texture coordinates
|
|
template <class T>
|
|
static void DeleteTexcoord(std::vector<T*>& list) {
|
|
for (T* plist : list) {
|
|
if (plist->HasTexcoord()) {
|
|
plist->DelTexcoord();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// returns a vector that stores the reference correction for each entry
|
|
template <class T>
|
|
static void DeleteElements(std::vector<T*>& elements,
|
|
const std::vector<bool>& discard) {
|
|
if (elements.empty()) {
|
|
return;
|
|
}
|
|
|
|
std::vector<int> ndiscard(elements.size(), 0);
|
|
|
|
int i = 0;
|
|
for (int j=0; j < elements.size(); j++) {
|
|
if (discard[j]) {
|
|
elements[j]->Release();
|
|
} else {
|
|
elements[i] = elements[j];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// count cumulative discarded elements
|
|
for (int i=0; i < elements.size()-1; i++) {
|
|
ndiscard[i+1] = ndiscard[i] + discard[i];
|
|
}
|
|
|
|
// erase elements from vector
|
|
if (i < elements.size()) {
|
|
elements.erase(elements.begin() + i, elements.end());
|
|
}
|
|
|
|
// update elements
|
|
for (T* element : elements) {
|
|
if (element->id > 0) {
|
|
element->id -= ndiscard[element->id];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
template <>
|
|
void mjCModel::Delete<mjCGeom>(std::vector<mjCGeom*>& elements,
|
|
const std::vector<bool>& discard) {
|
|
// update bodies
|
|
for (mjCBody* body : bodies_) {
|
|
body->geoms.erase(
|
|
std::remove_if(body->geoms.begin(), body->geoms.end(),
|
|
[&discard](mjCGeom* geom) {
|
|
return discard[geom->id];
|
|
}),
|
|
body->geoms.end());
|
|
}
|
|
|
|
// remove geoms from the main vector
|
|
DeleteElements(elements, discard);
|
|
}
|
|
|
|
|
|
template <>
|
|
void mjCModel::Delete<mjCMesh>(std::vector<mjCMesh*>& elements,
|
|
const std::vector<bool>& discard) {
|
|
DeleteElements(elements, discard);
|
|
}
|
|
|
|
|
|
template <>
|
|
void mjCModel::DeleteAll<mjCMaterial>(std::vector<mjCMaterial*>& elements) {
|
|
DeleteMaterial(geoms_);
|
|
DeleteMaterial(skins_);
|
|
DeleteMaterial(sites_);
|
|
DeleteMaterial(tendons_);
|
|
for (mjCMaterial* element : elements) {
|
|
element->Release();
|
|
}
|
|
elements.clear();
|
|
}
|
|
|
|
|
|
template <>
|
|
void mjCModel::DeleteAll<mjCTexture>(std::vector<mjCTexture*>& elements) {
|
|
DeleteAllTextures(materials_);
|
|
for (mjCTexture* element : elements) {
|
|
element->Release();
|
|
}
|
|
elements.clear();
|
|
}
|
|
|
|
|
|
// set nuser fields
|
|
void mjCModel::SetNuser() {
|
|
if (nuser_body == -1) {
|
|
nuser_body = 0;
|
|
for (int i = 0; i < bodies_.size(); i++) {
|
|
nuser_body = mjMAX(nuser_body, bodies_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
if (nuser_jnt == -1) {
|
|
nuser_jnt = 0;
|
|
for (int i = 0; i < joints_.size(); i++) {
|
|
nuser_jnt = mjMAX(nuser_jnt, joints_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
if (nuser_geom == -1) {
|
|
nuser_geom = 0;
|
|
for (int i = 0; i < geoms_.size(); i++) {
|
|
nuser_geom = mjMAX(nuser_geom, geoms_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
if (nuser_site == -1) {
|
|
nuser_site = 0;
|
|
for (int i = 0; i < sites_.size(); i++) {
|
|
nuser_site = mjMAX(nuser_site, sites_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
if (nuser_cam == -1) {
|
|
nuser_cam = 0;
|
|
for (int i = 0; i < cameras_.size(); i++) {
|
|
nuser_cam = mjMAX(nuser_cam, cameras_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
if (nuser_tendon == -1) {
|
|
nuser_tendon = 0;
|
|
for (int i = 0; i < tendons_.size(); i++) {
|
|
nuser_tendon = mjMAX(nuser_tendon, tendons_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
if (nuser_actuator == -1) {
|
|
nuser_actuator = 0;
|
|
for (int i = 0; i < actuators_.size(); i++) {
|
|
nuser_actuator = mjMAX(nuser_actuator, actuators_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
if (nuser_sensor == -1) {
|
|
nuser_sensor = 0;
|
|
for (int i = 0; i < sensors_.size(); i++) {
|
|
nuser_sensor = mjMAX(nuser_sensor, sensors_[i]->spec_userdata_.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
// index assets
|
|
void mjCModel::IndexAssets(bool discard) {
|
|
// assets referenced in geoms
|
|
for (int i=0; i < geoms_.size(); i++) {
|
|
mjCGeom* geom = geoms_[i];
|
|
|
|
|
|
// find mesh by name
|
|
if (!geom->get_meshname().empty()) {
|
|
mjCMesh* mesh = static_cast<mjCMesh*>(FindObject(mjOBJ_MESH, geom->get_meshname()));
|
|
if (mesh) {
|
|
if (!geom->visual_) {
|
|
mesh->SetNotVisual(); // reset to true by mesh->Compile()
|
|
}
|
|
geom->mesh = (discard && geom->visual_) ? nullptr : mesh;
|
|
mesh->spec.needsdf |= geom->spec.type == mjGEOM_SDF;
|
|
} else {
|
|
throw mjCError(geom, "mesh '%s' not found in geom %d", geom->get_meshname().c_str(), i);
|
|
}
|
|
}
|
|
|
|
// find material by name, this has to happen after mesh assignment so that if
|
|
// the geom does not specify a material but the mesh does it can fall back.
|
|
if (!geom->get_material().empty()) {
|
|
mjCBase* material = FindObject(mjOBJ_MATERIAL, geom->get_material());
|
|
if (material) {
|
|
geom->matid = material->id;
|
|
} else {
|
|
throw mjCError(geom, "material '%s' not found in geom %d", geom->get_material().c_str(), i);
|
|
}
|
|
}
|
|
|
|
// find hfield by name
|
|
if (!geom->get_hfieldname().empty()) {
|
|
mjCBase* hfield = FindObject(mjOBJ_HFIELD, geom->get_hfieldname());
|
|
if (hfield) {
|
|
geom->hfield = (mjCHField*)hfield;
|
|
} else {
|
|
throw mjCError(geom, "hfield '%s' not found in geom %d", geom->get_hfieldname().c_str(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// assets referenced in skins
|
|
for (int i=0; i < skins_.size(); i++) {
|
|
mjCSkin* skin = skins_[i];
|
|
|
|
// find material by name
|
|
if (!skin->material_.empty()) {
|
|
mjCBase* material = FindObject(mjOBJ_MATERIAL, skin->material_);
|
|
if (material) {
|
|
skin->matid = material->id;
|
|
} else {
|
|
throw mjCError(skin, "material '%s' not found in skin %d", skin->material_.c_str(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// materials referenced in sites
|
|
for (int i=0; i < sites_.size(); i++) {
|
|
mjCSite* site = sites_[i];
|
|
|
|
// find material by name
|
|
if (!site->material_.empty()) {
|
|
mjCBase* material = FindObject(mjOBJ_MATERIAL, site->get_material());
|
|
if (material) {
|
|
site->matid = material->id;
|
|
} else {
|
|
throw mjCError(site, "material '%s' not found in site %d", site->material_.c_str(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// materials referenced in tendons
|
|
for (int i=0; i < tendons_.size(); i++) {
|
|
mjCTendon* tendon = tendons_[i];
|
|
|
|
// find material by name
|
|
if (!tendon->material_.empty()) {
|
|
mjCBase* material = FindObject(mjOBJ_MATERIAL, tendon->material_);
|
|
if (material) {
|
|
tendon->matid = material->id;
|
|
} else {
|
|
throw mjCError(tendon, "material '%s' not found in tendon %d", tendon->material_.c_str(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// textures referenced in materials
|
|
for (int i=0; i < materials_.size(); i++) {
|
|
mjCMaterial* material = materials_[i];
|
|
|
|
// find textures by name
|
|
for (int j=0; j < mjNTEXROLE; j++) {
|
|
if (!material->textures_[j].empty()) {
|
|
mjCBase* texture = FindObject(mjOBJ_TEXTURE, material->textures_[j]);
|
|
if (texture) {
|
|
material->texid[j] = texture->id;
|
|
} else {
|
|
throw mjCError(material, "texture '%s' not found in material %d", material->textures_[j].c_str(), i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (discard) {
|
|
std::vector<bool> discard_mesh(meshes_.size(), false);
|
|
std::vector<bool> discard_geom(geoms_.size(), false);
|
|
|
|
std::transform(meshes_.begin(), meshes_.end(), discard_mesh.begin(),
|
|
[](const mjCMesh* mesh) {
|
|
return mesh->IsVisual();
|
|
});
|
|
std::transform(geoms_.begin(), geoms_.end(), discard_geom.begin(),
|
|
[](const mjCGeom* geom) {
|
|
return geom->IsVisual();
|
|
});
|
|
|
|
// update inertia in bodies
|
|
for (auto body : bodies_) {
|
|
if (body->spec.explicitinertial) {
|
|
continue;
|
|
}
|
|
for (auto geom : body->geoms) {
|
|
if (geom->IsVisual()) {
|
|
if (compiler.inertiafromgeom == mjINERTIAFROMGEOM_TRUE) {
|
|
compiler.inertiafromgeom = mjINERTIAFROMGEOM_AUTO;
|
|
}
|
|
body->explicitinertial = true; // for XML writer
|
|
body->spec.explicitinertial = true;
|
|
body->spec.mass = body->mass;
|
|
mjuu_copyvec(body->spec.ipos, body->ipos, 3);
|
|
mjuu_copyvec(body->spec.iquat, body->iquat, 4);
|
|
mjuu_copyvec(body->spec.inertia, body->inertia, 3);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// discard visual meshes and geoms
|
|
Delete(meshes_, discard_mesh);
|
|
Delete(geoms_, discard_geom);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// throw error if a name is missing
|
|
void mjCModel::CheckEmptyNames(void) {
|
|
// meshes
|
|
for (int i=0; i < meshes_.size(); i++) {
|
|
if (meshes_[i]->name.empty()) {
|
|
throw mjCError(meshes_[i], "empty name in mesh");
|
|
}
|
|
}
|
|
|
|
// hfields
|
|
for (int i=0; i < hfields_.size(); i++) {
|
|
if (hfields_[i]->name.empty()) {
|
|
throw mjCError(hfields_[i], "empty name in height field");
|
|
}
|
|
}
|
|
|
|
// textures
|
|
for (int i=0; i < textures_.size(); i++) {
|
|
if (textures_[i]->name.empty() && textures_[i]->type != mjTEXTURE_SKYBOX) {
|
|
throw mjCError(textures_[i], "empty name in texture");
|
|
}
|
|
}
|
|
|
|
// materials
|
|
for (int i=0; i < materials_.size(); i++) {
|
|
if (materials_[i]->name.empty()) {
|
|
throw mjCError(materials_[i], "empty name in material");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
static size_t getpathslength(std::vector<T> list) {
|
|
size_t result = 0;
|
|
for (const auto& element : list) {
|
|
if (!element->File().empty()) {
|
|
result += element->File().length() + 1;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// set array sizes
|
|
void mjCModel::SetSizes() {
|
|
// set from object list sizes
|
|
nbody = (int)bodies_.size();
|
|
njnt = (int)joints_.size();
|
|
ngeom = (int)geoms_.size();
|
|
nsite = (int)sites_.size();
|
|
ncam = (int)cameras_.size();
|
|
nlight = (int)lights_.size();
|
|
nflex = (int)flexes_.size();
|
|
nmesh = (int)meshes_.size();
|
|
nskin = (int)skins_.size();
|
|
nhfield = (int)hfields_.size();
|
|
ntex = (int)textures_.size();
|
|
nmat = (int)materials_.size();
|
|
npair = (int)pairs_.size();
|
|
nexclude = (int)excludes_.size();
|
|
neq = (int)equalities_.size();
|
|
ntendon = (int)tendons_.size();
|
|
nsensor = (int)sensors_.size();
|
|
nnumeric = (int)numerics_.size();
|
|
ntext = (int)texts_.size();
|
|
ntuple = (int)tuples_.size();
|
|
nkey = (int)keys_.size();
|
|
nplugin = (int)plugins_.size();
|
|
nq = nv = ntree = nu = na = nmocap = 0;
|
|
|
|
// nq, nv, ntree
|
|
for (int i=0; i < njnt; i++) {
|
|
nq += joints_[i]->nq();
|
|
nv += joints_[i]->nv();
|
|
|
|
// increment ntree if this is the first joint in a moving body with static ancestry
|
|
mjCBody* parent = joints_[i]->GetParent();
|
|
bool is_first_joint = joints_[i] == parent->joints[0];
|
|
if (is_first_joint) {
|
|
// check if all ancestors are static
|
|
bool static_ancestry = true;
|
|
mjCBody* ancestor = parent;
|
|
while (ancestor != bodies_[0]) {
|
|
ancestor = ancestor->GetParent();
|
|
if (!ancestor->joints.empty()) {
|
|
static_ancestry = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if all ancestors are static, this joint starts a new kinematic tree
|
|
if (static_ancestry) {
|
|
ntree++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// nu, na
|
|
for (int i=0; i < actuators_.size(); i++) {
|
|
nu++;
|
|
na += actuators_[i]->actdim;
|
|
}
|
|
|
|
// nbvh, nbvhstatic, nbvhdynamic
|
|
for (int i=0; i < nbody; i++) {
|
|
nbvhstatic += bodies_[i]->tree.Nbvh();
|
|
}
|
|
for (int i=0; i < nmesh; i++) {
|
|
nbvhstatic += meshes_[i]->tree().Nbvh();
|
|
noct += meshes_[i]->octree().NumNodes();
|
|
}
|
|
for (int i=0; i < nflex; i++) {
|
|
nbvhdynamic += flexes_[i]->tree.Nbvh();
|
|
}
|
|
nbvh = nbvhstatic + nbvhdynamic;
|
|
|
|
// flex counts
|
|
for (int i=0; i < nflex; i++) {
|
|
nflexnode += flexes_[i]->nnode;
|
|
nflexvert += flexes_[i]->nvert;
|
|
nflexedge += flexes_[i]->nedge;
|
|
nflexelem += flexes_[i]->nelem;
|
|
nflexelemdata += flexes_[i]->nelem * (flexes_[i]->dim + 1);
|
|
nflexelemedge += flexes_[i]->nelem * mjCFlex::kNumEdges[flexes_[i]->dim - 1];
|
|
nflexshelldata += (int)flexes_[i]->shell.size();
|
|
nflexevpair += (int)flexes_[i]->evpair.size()/2;
|
|
nflextexcoord += (flexes_[i]->HasTexcoord() ? flexes_[i]->get_texcoord().size()/2 : 0);
|
|
}
|
|
|
|
// mesh counts
|
|
for (int i=0; i < nmesh; i++) {
|
|
nmeshvert += meshes_[i]->nvert();
|
|
nmeshnormal += meshes_[i]->nnormal();
|
|
nmeshface += meshes_[i]->nface();
|
|
nmeshtexcoord += (meshes_[i]->HasTexcoord() ? meshes_[i]->ntexcoord() : 0);
|
|
nmeshgraph += meshes_[i]->szgraph();
|
|
nmeshpoly += meshes_[i]->npolygon();
|
|
nmeshpolyvert += meshes_[i]->npolygonvert();
|
|
nmeshpolymap += meshes_[i]->npolygonmap();
|
|
}
|
|
|
|
// skin counts
|
|
for (int i=0; i < nskin; i++) {
|
|
nskinvert += skins_[i]->get_vert().size()/3;
|
|
nskintexvert += skins_[i]->get_texcoord().size()/2;
|
|
nskinface += skins_[i]->get_face().size()/3;
|
|
nskinbone += skins_[i]->bodyid.size();
|
|
for (int j=0; j < skins_[i]->bodyid.size(); j++) {
|
|
nskinbonevert += skins_[i]->get_vertid()[j].size();
|
|
}
|
|
}
|
|
|
|
// nhfielddata
|
|
for (int i=0; i < nhfield; i++)nhfielddata += hfields_[i]->nrow * hfields_[i]->ncol;
|
|
|
|
// ntexdata
|
|
for (int i=0; i < ntex; i++)ntexdata += textures_[i]->nchannel * textures_[i]->width * textures_[i]->height;
|
|
|
|
// nwrap
|
|
for (int i=0; i < ntendon; i++)nwrap += (int)tendons_[i]->path.size();
|
|
|
|
// nsensordata
|
|
for (int i=0; i < nsensor; i++)nsensordata += sensors_[i]->dim;
|
|
|
|
// nnumericdata
|
|
for (int i=0; i < nnumeric; i++)nnumericdata += numerics_[i]->size;
|
|
|
|
// ntextdata
|
|
for (int i=0; i < ntext; i++)ntextdata += (int)texts_[i]->data_.size() + 1;
|
|
|
|
// ntupledata
|
|
for (int i=0; i < ntuple; i++)ntupledata += (int)tuples_[i]->objtype_.size();
|
|
|
|
// npluginattr
|
|
for (int i=0; i < nplugin; i++)npluginattr += (int)plugins_[i]->flattened_attributes.size();
|
|
|
|
// nnames
|
|
nnames = (int)modelname_.size() + 1;
|
|
for (int i=0; i < nbody; i++) nnames += (int)bodies_[i]->name.length() + 1;
|
|
for (int i=0; i < njnt; i++) nnames += (int)joints_[i]->name.length() + 1;
|
|
for (int i=0; i < ngeom; i++) nnames += (int)geoms_[i]->name.length() + 1;
|
|
for (int i=0; i < nsite; i++) nnames += (int)sites_[i]->name.length() + 1;
|
|
for (int i=0; i < ncam; i++) nnames += (int)cameras_[i]->name.length() + 1;
|
|
for (int i=0; i < nlight; i++) nnames += (int)lights_[i]->name.length() + 1;
|
|
for (int i=0; i < nflex; i++) nnames += (int)flexes_[i]->name.length() + 1;
|
|
for (int i=0; i < nmesh; i++) nnames += (int)meshes_[i]->name.length() + 1;
|
|
for (int i=0; i < nskin; i++) nnames += (int)skins_[i]->name.length() + 1;
|
|
for (int i=0; i < nhfield; i++) nnames += (int)hfields_[i]->name.length() + 1;
|
|
for (int i=0; i < ntex; i++) nnames += (int)textures_[i]->name.length() + 1;
|
|
for (int i=0; i < nmat; i++) nnames += (int)materials_[i]->name.length() + 1;
|
|
for (int i=0; i < npair; i++) nnames += (int)pairs_[i]->name.length() + 1;
|
|
for (int i=0; i < nexclude; i++) nnames += (int)excludes_[i]->name.length() + 1;
|
|
for (int i=0; i < neq; i++) nnames += (int)equalities_[i]->name.length() + 1;
|
|
for (int i=0; i < ntendon; i++) nnames += (int)tendons_[i]->name.length() + 1;
|
|
for (int i=0; i < nu; i++) nnames += (int)actuators_[i]->name.length() + 1;
|
|
for (int i=0; i < nsensor; i++) nnames += (int)sensors_[i]->name.length() + 1;
|
|
for (int i=0; i < nnumeric; i++) nnames += (int)numerics_[i]->name.length() + 1;
|
|
for (int i=0; i < ntext; i++) nnames += (int)texts_[i]->name.length() + 1;
|
|
for (int i=0; i < ntuple; i++) nnames += (int)tuples_[i]->name.length() + 1;
|
|
for (int i=0; i < nkey; i++) nnames += (int)keys_[i]->name.length() + 1;
|
|
for (int i=0; i < nplugin; i++) nnames += (int)plugins_[i]->name.length() + 1;
|
|
|
|
// npaths
|
|
npaths = 0;
|
|
npaths += getpathslength(hfields_);
|
|
npaths += getpathslength(meshes_);
|
|
npaths += getpathslength(skins_);
|
|
npaths += getpathslength(textures_);
|
|
if (npaths == 0) {
|
|
npaths = 1;
|
|
}
|
|
|
|
// nemax
|
|
for (int i=0; i < neq; i++) {
|
|
if (equalities_[i]->type == mjEQ_CONNECT) {
|
|
nemax += 3;
|
|
} else if (equalities_[i]->type == mjEQ_WELD) {
|
|
nemax += 7;
|
|
} else {
|
|
nemax += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// automatic stiffness and damping computation
|
|
void mjCModel::AutoSpringDamper(mjModel* m) {
|
|
// process all joints
|
|
for (int n=0; n < m->njnt; n++) {
|
|
// get joint dof address and number of dimensions
|
|
int adr = m->jnt_dofadr[n];
|
|
int ndim = mjCJoint::nv((mjtJoint)m->jnt_type[n]);
|
|
|
|
// get timeconst and dampratio from joint specification
|
|
mjtNum timeconst = (mjtNum)joints_[n]->springdamper[0];
|
|
mjtNum dampratio = (mjtNum)joints_[n]->springdamper[1];
|
|
|
|
// skip joint if either parameter is non-positive
|
|
if (timeconst <= 0 || dampratio <= 0) {
|
|
continue;
|
|
}
|
|
|
|
// get average inertia (dof_invweight0 in free joint is different for tran and rot)
|
|
mjtNum inertia = 0;
|
|
for (int i=0; i < ndim; i++) {
|
|
inertia += m->dof_invweight0[adr+i];
|
|
}
|
|
inertia = ((mjtNum)ndim) / std::max(mjMINVAL, inertia);
|
|
|
|
// compute stiffness and damping (same as solref computation)
|
|
mjtNum stiffness = inertia / std::max(mjMINVAL, timeconst*timeconst*dampratio*dampratio);
|
|
mjtNum damping = 2 * inertia / std::max(mjMINVAL, timeconst);
|
|
|
|
// save stiffness and damping in the private mjsJoints
|
|
joints_[n]->stiffness = stiffness;
|
|
joints_[n]->damping = damping;
|
|
|
|
// assign
|
|
m->jnt_stiffness[n] = stiffness;
|
|
for (int i=0; i < ndim; i++) {
|
|
m->dof_damping[adr+i] = damping;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// arguments for lengthrange thread function
|
|
struct _LRThreadArg {
|
|
mjModel* m;
|
|
mjData* data;
|
|
int start;
|
|
int num;
|
|
const mjLROpt* LRopt;
|
|
char* error;
|
|
int error_sz;
|
|
};
|
|
typedef struct _LRThreadArg LRThreadArg;
|
|
|
|
|
|
// thread function for lengthrange computation
|
|
void* LRfunc(void* arg) {
|
|
LRThreadArg* larg = (LRThreadArg*)arg;
|
|
|
|
for (int i=larg->start; i < larg->start+larg->num; i++) {
|
|
if (i < larg->m->nu) {
|
|
if (!mj_setLengthRange(larg->m, larg->data, i, larg->LRopt, larg->error, larg->error_sz)) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
// compute actuator lengthrange
|
|
void mjCModel::LengthRange(mjModel* m, mjData* data) {
|
|
// save options and modify
|
|
mjOption saveopt = m->opt;
|
|
m->opt.disableflags = mjDSBL_FRICTIONLOSS | mjDSBL_CONTACT | mjDSBL_SPRING | mjDSBL_DAMPER |
|
|
mjDSBL_GRAVITY | mjDSBL_ACTUATION;
|
|
if (compiler.LRopt.timestep > 0) {
|
|
m->opt.timestep = compiler.LRopt.timestep;
|
|
}
|
|
|
|
// number of threads available
|
|
int hardware_threads = std::thread::hardware_concurrency();
|
|
const int nthread = mjMAX(1, mjMIN(kMaxCompilerThreads, hardware_threads/2));
|
|
|
|
// count actuators that need computation
|
|
int cnt = 0;
|
|
for (int i=0; i < m->nu; i++) {
|
|
// skip depending on mode and type
|
|
int ismuscle = (m->actuator_gaintype[i] == mjGAIN_MUSCLE ||
|
|
m->actuator_biastype[i] == mjBIAS_MUSCLE);
|
|
int isuser = (m->actuator_gaintype[i] == mjGAIN_USER ||
|
|
m->actuator_biastype[i] == mjBIAS_USER);
|
|
if ((compiler.LRopt.mode == mjLRMODE_NONE) ||
|
|
(compiler.LRopt.mode == mjLRMODE_MUSCLE && !ismuscle) ||
|
|
(compiler.LRopt.mode == mjLRMODE_MUSCLEUSER && !ismuscle && !isuser)) {
|
|
continue;
|
|
}
|
|
|
|
// use existing length range if available
|
|
if (compiler.LRopt.useexisting &&
|
|
(m->actuator_lengthrange[2*i] < m->actuator_lengthrange[2*i+1])) {
|
|
continue;
|
|
}
|
|
|
|
// count
|
|
cnt++;
|
|
}
|
|
|
|
// single thread
|
|
if (!compiler.usethread || cnt < 2 || nthread < 2) {
|
|
char err[200];
|
|
for (int i=0; i < m->nu; i++) {
|
|
if (!mj_setLengthRange(m, data, i, &compiler.LRopt, err, 200)) {
|
|
throw mjCError(0, "%s", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// multiple threads
|
|
else {
|
|
// allocate mjData for each thread
|
|
char err[kMaxCompilerThreads][200];
|
|
mjData* pdata[kMaxCompilerThreads] = {data};
|
|
for (int i=1; i < nthread; i++) {
|
|
pdata[i] = mj_makeData(m);
|
|
}
|
|
|
|
// number of actuators per thread
|
|
int num = m->nu / nthread;
|
|
while (num*nthread < m->nu) {
|
|
num++;
|
|
}
|
|
|
|
// prepare thread function arguments, clear errors
|
|
LRThreadArg arg[kMaxCompilerThreads];
|
|
for (int i=0; i < nthread; i++) {
|
|
LRThreadArg temp = {m, pdata[i], i*num, num, &compiler.LRopt, err[i], 200};
|
|
arg[i] = temp;
|
|
err[i][0] = 0;
|
|
}
|
|
|
|
// launch threads
|
|
std::thread th[kMaxCompilerThreads];
|
|
for (int i=0; i < nthread; i++) {
|
|
th[i] = std::thread(LRfunc, arg+i);
|
|
}
|
|
|
|
// wait for threads to finish
|
|
for (int i=0; i < nthread; i++) {
|
|
th[i].join();
|
|
}
|
|
|
|
// free mjData allocated here
|
|
for (int i=1; i < nthread; i++) {
|
|
mj_deleteData(pdata[i]);
|
|
}
|
|
|
|
// report first error
|
|
for (int i=0; i < nthread; i++) {
|
|
if (err[i][0]) {
|
|
throw mjCError(0, "%s", err[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore options
|
|
m->opt = saveopt;
|
|
}
|
|
|
|
|
|
// Add items to a generic list. This enables the names and paths to be stored.
|
|
// input - string to add
|
|
// adr - current address in the list
|
|
// output_adr_field - the field where the address should be stored (name_meshadr)
|
|
// output_buffer - the field where the data should be stored (i.e. names or paths)
|
|
static int addtolist(const std::string& input, int adr, int* output_adr_field, char* output_buffer) {
|
|
*output_adr_field = adr;
|
|
|
|
// copy input
|
|
memcpy(output_buffer+adr, input.c_str(), input.size());
|
|
adr += (int)input.size();
|
|
|
|
// append 0
|
|
output_buffer[adr] = 0;
|
|
adr++;
|
|
|
|
return adr;
|
|
}
|
|
|
|
// process names from one list: concatenate, compute addresses
|
|
template <class T>
|
|
static int namelist(vector<T*>& list, int adr, int* name_adr, char* names, int* map) {
|
|
// compute hash map addresses
|
|
int map_size = mjLOAD_MULTIPLE*list.size();
|
|
for (unsigned int i=0; i < list.size(); i++) {
|
|
// ignore empty strings
|
|
if (list[i]->name.empty()) {
|
|
continue;
|
|
}
|
|
|
|
uint64_t j = mj_hashString(list[i]->name.c_str(), map_size);
|
|
|
|
// find first empty slot using linear probing
|
|
for (; map[j] != -1; j=(j+1) % map_size) {}
|
|
map[j] = i;
|
|
}
|
|
|
|
for (unsigned int i=0; i < list.size(); i++) {
|
|
adr = addtolist(list[i]->name, adr, &name_adr[i], names);
|
|
}
|
|
|
|
return adr;
|
|
}
|
|
|
|
|
|
// copy names, compute name addresses
|
|
void mjCModel::CopyNames(mjModel* m) {
|
|
// start with model name
|
|
int adr = (int)modelname_.size()+1;
|
|
int* map_adr = m->names_map;
|
|
mju_strncpy(m->names, modelname_.c_str(), m->nnames);
|
|
memset(m->names_map, -1, sizeof(int) * m->nnames_map);
|
|
|
|
// process all lists
|
|
adr = namelist(bodies_, adr, m->name_bodyadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*bodies_.size();
|
|
|
|
adr = namelist(joints_, adr, m->name_jntadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*joints_.size();
|
|
|
|
adr = namelist(geoms_, adr, m->name_geomadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*geoms_.size();
|
|
|
|
adr = namelist(sites_, adr, m->name_siteadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*sites_.size();
|
|
|
|
adr = namelist(cameras_, adr, m->name_camadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*cameras_.size();
|
|
|
|
adr = namelist(lights_, adr, m->name_lightadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*lights_.size();
|
|
|
|
adr = namelist(flexes_, adr, m->name_flexadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*flexes_.size();
|
|
|
|
adr = namelist(meshes_, adr, m->name_meshadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*meshes_.size();
|
|
|
|
adr = namelist(skins_, adr, m->name_skinadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*skins_.size();
|
|
|
|
adr = namelist(hfields_, adr, m->name_hfieldadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*hfields_.size();
|
|
|
|
adr = namelist(textures_, adr, m->name_texadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*textures_.size();
|
|
|
|
adr = namelist(materials_, adr, m->name_matadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*materials_.size();
|
|
|
|
adr = namelist(pairs_, adr, m->name_pairadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*pairs_.size();
|
|
|
|
adr = namelist(excludes_, adr, m->name_excludeadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*excludes_.size();
|
|
|
|
adr = namelist(equalities_, adr, m->name_eqadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*equalities_.size();
|
|
|
|
adr = namelist(tendons_, adr, m->name_tendonadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*tendons_.size();
|
|
|
|
adr = namelist(actuators_, adr, m->name_actuatoradr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*actuators_.size();
|
|
|
|
adr = namelist(sensors_, adr, m->name_sensoradr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*sensors_.size();
|
|
|
|
adr = namelist(numerics_, adr, m->name_numericadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*numerics_.size();
|
|
|
|
adr = namelist(texts_, adr, m->name_textadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*texts_.size();
|
|
|
|
adr = namelist(tuples_, adr, m->name_tupleadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*tuples_.size();
|
|
|
|
adr = namelist(keys_, adr, m->name_keyadr, m->names, map_adr);
|
|
map_adr += mjLOAD_MULTIPLE*keys_.size();
|
|
|
|
adr = namelist(plugins_, adr, m->name_pluginadr, m->names, map_adr);
|
|
|
|
// check size, SHOULD NOT OCCUR
|
|
if (adr != nnames) {
|
|
throw mjCError(0, "size mismatch in %s: expected %d, got %d", "names", nnames, adr);
|
|
}
|
|
}
|
|
|
|
// process paths from one list: concatenate, compute addresses
|
|
template <class T>
|
|
static int pathlist(vector<T*>& list, int adr, int* path_adr, char* paths) {
|
|
for (unsigned int i = 0; i < list.size(); ++i) {
|
|
path_adr[i] = -1;
|
|
if (!list[i] || list[i]->File().empty()) {
|
|
continue;
|
|
}
|
|
adr = addtolist(list[i]->File(), adr, &path_adr[i], paths);
|
|
}
|
|
|
|
return adr;
|
|
}
|
|
|
|
void mjCModel::CopyPaths(mjModel* m) {
|
|
// start with 0 address, unlike m->names m->paths might be empty
|
|
size_t adr = 0;
|
|
m->paths[0] = 0;
|
|
adr = pathlist(hfields_, adr, m->hfield_pathadr, m->paths);
|
|
adr = pathlist(meshes_, adr, m->mesh_pathadr, m->paths);
|
|
adr = pathlist(skins_, adr, m->skin_pathadr, m->paths);
|
|
adr = pathlist(textures_, adr, m->tex_pathadr, m->paths);
|
|
}
|
|
|
|
|
|
|
|
// copy objects inside kinematic tree
|
|
void mjCModel::CopyTree(mjModel* m) {
|
|
const mjtNum* nullnum = nullptr;
|
|
|
|
int jntadr = 0; // addresses in global arrays
|
|
int dofadr = 0;
|
|
int qposadr = 0;
|
|
int bvh_adr = 0;
|
|
|
|
// main loop over bodies
|
|
for (int i=0; i < nbody; i++) {
|
|
// get body and parent pointers
|
|
mjCBody* pb = bodies_[i];
|
|
mjCBody* par = pb->parent;
|
|
|
|
// set body fields
|
|
m->body_parentid[i] = pb->parent ? pb->parent->id : 0;
|
|
m->body_weldid[i] = pb->weldid;
|
|
m->body_mocapid[i] = pb->mocapid;
|
|
m->body_jntnum[i] = (int)pb->joints.size();
|
|
m->body_jntadr[i] = (!pb->joints.empty() ? jntadr : -1);
|
|
m->body_dofnum[i] = pb->dofnum;
|
|
m->body_dofadr[i] = (pb->dofnum ? dofadr : -1);
|
|
m->body_geomnum[i] = (int)pb->geoms.size();
|
|
m->body_geomadr[i] = (!pb->geoms.empty() ? pb->geoms[0]->id : -1);
|
|
mjuu_copyvec(m->body_pos+3*i, pb->pos, 3);
|
|
mjuu_copyvec(m->body_quat+4*i, pb->quat, 4);
|
|
mjuu_copyvec(m->body_ipos+3*i, pb->ipos, 3);
|
|
mjuu_copyvec(m->body_iquat+4*i, pb->iquat, 4);
|
|
m->body_mass[i] = (mjtNum)pb->mass;
|
|
mjuu_copyvec(m->body_inertia+3*i, pb->inertia, 3);
|
|
m->body_gravcomp[i] = pb->gravcomp;
|
|
mjuu_copyvec(m->body_user+nuser_body*i, pb->get_userdata().data(), nuser_body);
|
|
|
|
m->body_contype[i] = pb->contype;
|
|
m->body_conaffinity[i] = pb->conaffinity;
|
|
m->body_margin[i] = (mjtNum)pb->margin;
|
|
|
|
// bounding volume hierarchy
|
|
m->body_bvhadr[i] = pb->tree.Nbvh() ? bvh_adr : -1;
|
|
m->body_bvhnum[i] = pb->tree.Nbvh();
|
|
if (pb->tree.Nbvh()) {
|
|
memcpy(m->bvh_aabb + 6*bvh_adr, pb->tree.Bvh().data(), 6*pb->tree.Nbvh()*sizeof(mjtNum));
|
|
memcpy(m->bvh_child + 2*bvh_adr, pb->tree.Child().data(), 2*pb->tree.Nbvh()*sizeof(int));
|
|
memcpy(m->bvh_depth + bvh_adr, pb->tree.Level().data(), pb->tree.Nbvh()*sizeof(int));
|
|
for (int i=0; i < pb->tree.Nbvh(); i++) {
|
|
m->bvh_nodeid[i + bvh_adr] = pb->tree.Nodeidptr(i) ? *(pb->tree.Nodeidptr(i)) : -1;
|
|
}
|
|
}
|
|
bvh_adr += pb->tree.Nbvh();
|
|
|
|
// count free joints
|
|
int cntfree = 0;
|
|
for (int j=0; j < (int)pb->joints.size(); j++) {
|
|
cntfree += (pb->joints[j]->type == mjJNT_FREE);
|
|
}
|
|
|
|
// check validity of free joint
|
|
if (cntfree > 1 || (cntfree == 1 && pb->joints.size() > 1)) {
|
|
throw mjCError(pb, "free joint can only appear by itself");
|
|
}
|
|
if (cntfree && par && par->name != "world") {
|
|
throw mjCError(pb, "free joint can only be used on top level");
|
|
}
|
|
|
|
// rootid: self if world or child of world, otherwise parent's rootid
|
|
if (i == 0 || (par && par->name == "world")) {
|
|
m->body_rootid[i] = i;
|
|
} else {
|
|
m->body_rootid[i] = m->body_rootid[par->id];
|
|
}
|
|
|
|
// init lastdof from parent
|
|
pb->lastdof = par ? par->lastdof : -1;
|
|
|
|
// set sameframe
|
|
mjtSameFrame sameframe;
|
|
if (IsNullPose(m->body_ipos+3*i, m->body_iquat+4*i)) {
|
|
sameframe = mjSAMEFRAME_BODY;
|
|
} else if (IsNullPose(nullnum, m->body_iquat+4*i)) {
|
|
sameframe = mjSAMEFRAME_BODYROT;
|
|
} else {
|
|
sameframe = mjSAMEFRAME_NONE;
|
|
}
|
|
m->body_sameframe[i] = sameframe;
|
|
|
|
// init simple: sameframe, and (self-root, or parent is fixed child of world)
|
|
int parentid = m->body_parentid[i];
|
|
m->body_simple[i] = (sameframe == mjSAMEFRAME_BODY &&
|
|
(m->body_rootid[i] == i ||
|
|
(m->body_parentid[parentid] == 0 &&
|
|
m->body_dofnum[parentid] == 0)));
|
|
|
|
// a parent body is never simple (unless world)
|
|
if (m->body_parentid[i] > 0) {
|
|
m->body_simple[m->body_parentid[i]] = 0;
|
|
}
|
|
|
|
// loop over joints for this body
|
|
int rotfound = 0;
|
|
for (int j=0; j < (int)pb->joints.size(); j++) {
|
|
// get pointer and id
|
|
mjCJoint* pj = pb->joints[j];
|
|
int jid = pj->id;
|
|
|
|
// set joint fields
|
|
m->jnt_type[jid] = pj->type;
|
|
m->jnt_group[jid] = pj->group;
|
|
m->jnt_limited[jid] = (mjtByte)pj->is_limited();
|
|
m->jnt_actfrclimited[jid] = (mjtByte)pj->is_actfrclimited();
|
|
m->jnt_actgravcomp[jid] = pj->actgravcomp;
|
|
m->jnt_qposadr[jid] = pj->qposadr_;
|
|
m->jnt_dofadr[jid] = pj->dofadr_;
|
|
m->jnt_bodyid[jid] = pj->body->id;
|
|
mjuu_copyvec(m->jnt_pos+3*jid, pj->pos, 3);
|
|
mjuu_copyvec(m->jnt_axis+3*jid, pj->axis, 3);
|
|
m->jnt_stiffness[jid] = (mjtNum)pj->stiffness;
|
|
mjuu_copyvec(m->jnt_range+2*jid, pj->range, 2);
|
|
mjuu_copyvec(m->jnt_actfrcrange+2*jid, pj->actfrcrange, 2);
|
|
mjuu_copyvec(m->jnt_solref+mjNREF*jid, pj->solref_limit, mjNREF);
|
|
mjuu_copyvec(m->jnt_solimp+mjNIMP*jid, pj->solimp_limit, mjNIMP);
|
|
m->jnt_margin[jid] = (mjtNum)pj->margin;
|
|
mjuu_copyvec(m->jnt_user+nuser_jnt*jid, pj->get_userdata().data(), nuser_jnt);
|
|
|
|
// not simple if: rotation already found, or pos not zero, or mis-aligned axis
|
|
bool axis_aligned = ((std::abs(pj->axis[0]) > mjEPS) +
|
|
(std::abs(pj->axis[1]) > mjEPS) +
|
|
(std::abs(pj->axis[2]) > mjEPS)) == 1;
|
|
if (rotfound || !IsNullPose(m->jnt_pos+3*jid, nullnum) ||
|
|
((pj->type == mjJNT_HINGE || pj->type == mjJNT_SLIDE) && !axis_aligned)) {
|
|
m->body_simple[i] = 0;
|
|
}
|
|
|
|
// mark rotation
|
|
if (pj->type == mjJNT_BALL || pj->type == mjJNT_HINGE) {
|
|
rotfound = 1;
|
|
}
|
|
|
|
// set qpos0 and qpos_spring, check type
|
|
switch (pj->type) {
|
|
case mjJNT_FREE:
|
|
mjuu_copyvec(m->qpos0+qposadr, pb->pos, 3);
|
|
mjuu_copyvec(m->qpos0+qposadr+3, pb->quat, 4);
|
|
mjuu_copyvec(m->qpos_spring+qposadr, m->qpos0+qposadr, 7);
|
|
break;
|
|
|
|
case mjJNT_BALL:
|
|
m->qpos0[qposadr] = 1;
|
|
m->qpos0[qposadr+1] = 0;
|
|
m->qpos0[qposadr+2] = 0;
|
|
m->qpos0[qposadr+3] = 0;
|
|
mjuu_copyvec(m->qpos_spring+qposadr, m->qpos0+qposadr, 4);
|
|
break;
|
|
|
|
case mjJNT_SLIDE:
|
|
case mjJNT_HINGE:
|
|
m->qpos0[qposadr] = (mjtNum)pj->ref;
|
|
m->qpos_spring[qposadr] = (mjtNum)pj->springref;
|
|
break;
|
|
|
|
default:
|
|
throw mjCError(pj, "unknown joint type");
|
|
}
|
|
|
|
// set dof fields for this joint
|
|
for (int j1=0; j1 < pj->nv(); j1++) {
|
|
// set attributes
|
|
m->dof_bodyid[dofadr] = pb->id;
|
|
m->dof_jntid[dofadr] = jid;
|
|
mjuu_copyvec(m->dof_solref+mjNREF*dofadr, pj->solref_friction, mjNREF);
|
|
mjuu_copyvec(m->dof_solimp+mjNIMP*dofadr, pj->solimp_friction, mjNIMP);
|
|
m->dof_frictionloss[dofadr] = (mjtNum)pj->frictionloss;
|
|
m->dof_armature[dofadr] = (mjtNum)pj->armature;
|
|
m->dof_damping[dofadr] = (mjtNum)pj->damping;
|
|
|
|
// set dof_parentid, update body.lastdof
|
|
m->dof_parentid[dofadr] = pb->lastdof;
|
|
pb->lastdof = dofadr;
|
|
|
|
// advance dof counter
|
|
dofadr++;
|
|
}
|
|
|
|
// advance joint and qpos counters
|
|
jntadr++;
|
|
qposadr += pj->nq();
|
|
}
|
|
|
|
// simple body with sliders and no rotational dofs: promote to simple level 2
|
|
if (m->body_simple[i] && m->body_dofnum[i]) {
|
|
m->body_simple[i] = 2;
|
|
for (int j=0; j < (int)pb->joints.size(); j++) {
|
|
if (pb->joints[j]->type != mjJNT_SLIDE) {
|
|
m->body_simple[i] = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// loop over geoms for this body
|
|
for (int j=0; j < (int)pb->geoms.size(); j++) {
|
|
// get pointer and id
|
|
mjCGeom* pg = pb->geoms[j];
|
|
int gid = pg->id;
|
|
|
|
// set geom fields
|
|
m->geom_type[gid] = pg->type;
|
|
m->geom_contype[gid] = pg->contype;
|
|
m->geom_conaffinity[gid] = pg->conaffinity;
|
|
m->geom_condim[gid] = pg->condim;
|
|
m->geom_bodyid[gid] = pg->body->id;
|
|
if (pg->mesh) {
|
|
m->geom_dataid[gid] = pg->mesh->id;
|
|
} else if (pg->hfield) {
|
|
m->geom_dataid[gid] = pg->hfield->id;
|
|
} else {
|
|
m->geom_dataid[gid] = -1;
|
|
}
|
|
m->geom_matid[gid] = pg->matid;
|
|
m->geom_group[gid] = pg->group;
|
|
m->geom_priority[gid] = pg->priority;
|
|
mjuu_copyvec(m->geom_size+3*gid, pg->size, 3);
|
|
mjuu_copyvec(m->geom_aabb+6*gid, pg->aabb, 6);
|
|
mjuu_copyvec(m->geom_pos+3*gid, pg->pos, 3);
|
|
mjuu_copyvec(m->geom_quat+4*gid, pg->quat, 4);
|
|
mjuu_copyvec(m->geom_friction+3*gid, pg->friction, 3);
|
|
m->geom_solmix[gid] = (mjtNum)pg->solmix;
|
|
mjuu_copyvec(m->geom_solref+mjNREF*gid, pg->solref, mjNREF);
|
|
mjuu_copyvec(m->geom_solimp+mjNIMP*gid, pg->solimp, mjNIMP);
|
|
m->geom_margin[gid] = (mjtNum)pg->margin;
|
|
m->geom_gap[gid] = (mjtNum)pg->gap;
|
|
mjuu_copyvec(m->geom_fluid+mjNFLUID*gid, pg->fluid, mjNFLUID);
|
|
mjuu_copyvec(m->geom_user+nuser_geom*gid, pg->get_userdata().data(), nuser_geom);
|
|
mjuu_copyvec(m->geom_rgba+4*gid, pg->rgba, 4);
|
|
|
|
// determine sameframe
|
|
const double* nulldouble = nullptr;
|
|
if (IsNullPose(m->geom_pos+3*gid, m->geom_quat+4*gid)) {
|
|
sameframe = mjSAMEFRAME_BODY;
|
|
} else if (IsNullPose(nullnum, m->geom_quat+4*gid)) {
|
|
sameframe = mjSAMEFRAME_BODYROT;
|
|
} else if (IsSamePose(pg->pos, pb->ipos, pg->quat, pb->iquat)) {
|
|
sameframe = mjSAMEFRAME_INERTIA;
|
|
} else if (IsSamePose(nulldouble, nulldouble, pg->quat, pb->iquat)) {
|
|
sameframe = mjSAMEFRAME_INERTIAROT;
|
|
} else {
|
|
sameframe = mjSAMEFRAME_NONE;
|
|
}
|
|
m->geom_sameframe[gid] = sameframe;
|
|
|
|
// compute rbound
|
|
m->geom_rbound[gid] = (mjtNum)pg->GetRBound();
|
|
}
|
|
|
|
// loop over sites for this body
|
|
for (int j=0; j < (int)pb->sites.size(); j++) {
|
|
// get pointer and id
|
|
mjCSite* ps = pb->sites[j];
|
|
int sid = ps->id;
|
|
|
|
// set site fields
|
|
m->site_type[sid] = ps->type;
|
|
m->site_bodyid[sid] = ps->body->id;
|
|
m->site_matid[sid] = ps->matid;
|
|
m->site_group[sid] = ps->group;
|
|
mjuu_copyvec(m->site_size+3*sid, ps->size, 3);
|
|
mjuu_copyvec(m->site_pos+3*sid, ps->pos, 3);
|
|
mjuu_copyvec(m->site_quat+4*sid, ps->quat, 4);
|
|
mjuu_copyvec(m->site_user+nuser_site*sid, ps->userdata_.data(), nuser_site);
|
|
mjuu_copyvec(m->site_rgba+4*sid, ps->rgba, 4);
|
|
|
|
// determine sameframe
|
|
const double* nulldouble = nullptr;
|
|
if (IsNullPose(m->site_pos+3*sid, m->site_quat+4*sid)) {
|
|
sameframe = mjSAMEFRAME_BODY;
|
|
} else if (IsNullPose(nullnum, m->site_quat+4*sid)) {
|
|
sameframe = mjSAMEFRAME_BODYROT;
|
|
} else if (IsSamePose(ps->pos, pb->ipos, ps->quat, pb->iquat)) {
|
|
sameframe = mjSAMEFRAME_INERTIA;
|
|
} else if (IsSamePose(nulldouble, nulldouble, ps->quat, pb->iquat)) {
|
|
sameframe = mjSAMEFRAME_INERTIAROT;
|
|
} else {
|
|
sameframe = mjSAMEFRAME_NONE;
|
|
}
|
|
m->site_sameframe[sid] = sameframe;
|
|
}
|
|
|
|
// loop over cameras for this body
|
|
for (int j=0; j < (int)pb->cameras.size(); j++) {
|
|
// get pointer and id
|
|
mjCCamera* pc = pb->cameras[j];
|
|
int cid = pc->id;
|
|
|
|
// set camera fields
|
|
m->cam_bodyid[cid] = pc->body->id;
|
|
m->cam_mode[cid] = pc->mode;
|
|
m->cam_targetbodyid[cid] = pc->targetbodyid;
|
|
mjuu_copyvec(m->cam_pos+3*cid, pc->pos, 3);
|
|
mjuu_copyvec(m->cam_quat+4*cid, pc->quat, 4);
|
|
m->cam_orthographic[cid] = pc->orthographic;
|
|
m->cam_fovy[cid] = (mjtNum)pc->fovy;
|
|
m->cam_ipd[cid] = (mjtNum)pc->ipd;
|
|
mjuu_copyvec(m->cam_resolution+2*cid, pc->resolution, 2);
|
|
mjuu_copyvec(m->cam_sensorsize+2*cid, pc->sensor_size, 2);
|
|
mjuu_copyvec(m->cam_intrinsic+4*cid, pc->intrinsic, 4);
|
|
mjuu_copyvec(m->cam_user+nuser_cam*cid, pc->get_userdata().data(), nuser_cam);
|
|
}
|
|
|
|
// loop over lights for this body
|
|
for (int j=0; j < (int)pb->lights.size(); j++) {
|
|
// get pointer and id
|
|
mjCLight* pl = pb->lights[j];
|
|
int lid = pl->id;
|
|
|
|
// set light fields
|
|
m->light_bodyid[lid] = pl->body->id;
|
|
m->light_mode[lid] = (int)pl->mode;
|
|
m->light_targetbodyid[lid] = pl->targetbodyid;
|
|
m->light_type[lid] = pl->type;
|
|
m->light_texid[lid] = pl->texid;
|
|
m->light_castshadow[lid] = (mjtByte)pl->castshadow;
|
|
m->light_active[lid] = (mjtByte)pl->active;
|
|
mjuu_copyvec(m->light_pos+3*lid, pl->pos, 3);
|
|
mjuu_copyvec(m->light_dir+3*lid, pl->dir, 3);
|
|
m->light_bulbradius[lid] = pl->bulbradius;
|
|
m->light_intensity[lid] = pl->intensity;
|
|
m->light_range[lid] = pl->range;
|
|
mjuu_copyvec(m->light_attenuation+3*lid, pl->attenuation, 3);
|
|
m->light_cutoff[lid] = pl->cutoff;
|
|
m->light_exponent[lid] = pl->exponent;
|
|
mjuu_copyvec(m->light_ambient+3*lid, pl->ambient, 3);
|
|
mjuu_copyvec(m->light_diffuse+3*lid, pl->diffuse, 3);
|
|
mjuu_copyvec(m->light_specular+3*lid, pl->specular, 3);
|
|
}
|
|
}
|
|
|
|
// check number of dof's constructed, SHOULD NOT OCCUR
|
|
if (nv != dofadr) {
|
|
throw mjCError(0, "unexpected number of DOFs");
|
|
}
|
|
|
|
// count kinematic trees under world body, compute dof_treeid
|
|
int ntree = 0;
|
|
for (int i=0; i < nv; i++) {
|
|
if (m->dof_parentid[i] == -1) {
|
|
ntree++;
|
|
}
|
|
m->dof_treeid[i] = ntree - 1;
|
|
}
|
|
|
|
// check number of trees constructed, SHOULD NOT OCCUR
|
|
if (ntree != m->ntree) {
|
|
throw mjCError(0, "unexpected number of TREEs. Counted %d, expected %d",
|
|
nullptr, ntree, m->ntree);
|
|
}
|
|
|
|
// compute body_treeid
|
|
for (int i=0; i < nbody; i++) {
|
|
int weldid = m->body_weldid[i];
|
|
if (m->body_dofnum[weldid]) {
|
|
m->body_treeid[i] = m->dof_treeid[m->body_dofadr[weldid]];
|
|
} else {
|
|
m->body_treeid[i] = -1;
|
|
}
|
|
}
|
|
|
|
// count bodies with gravity compensation, compute ngravcomp
|
|
int ngravcomp = 0;
|
|
for (int i=0; i < nbody; i++) {
|
|
ngravcomp += (m->body_gravcomp[i] > 0);
|
|
}
|
|
m->ngravcomp = ngravcomp;
|
|
|
|
// recompute nM and dof_Madr given m.dof_parentid, validate
|
|
int nM_post = 0;
|
|
for (int i=0; i < nv; i++) {
|
|
// set address of this dof
|
|
m->dof_Madr[i] = nM_post;
|
|
|
|
// count ancestor dofs including self
|
|
int j = i;
|
|
while (j >= 0) {
|
|
nM_post++;
|
|
j = m->dof_parentid[j];
|
|
}
|
|
}
|
|
if (nM_post != nM) throw mjCError(0, "nM mismatch: pre %d, post %d", nullptr, nM, nM_post);
|
|
|
|
// recompute nD, validate
|
|
int nD_post = 2 * m->nM - nv;
|
|
if (nD_post != nD) throw mjCError(0, "nD mismatch: pre %d, post %d", nullptr, nD, nD_post);
|
|
|
|
// bodies_[]->subtreedofs already computed in ComputeSparseSizes
|
|
|
|
// recompute nB given {body->subtreedofs, body->dofnum, body->parent}, validate
|
|
int nB_post = 0;
|
|
for (int i = 0; i < nbody; i++) {
|
|
// add subtree dofs (including self)
|
|
nB_post += bodies_[i]->subtreedofs;
|
|
// add dofs in ancestor bodies
|
|
int j = bodies_[i]->parent ? bodies_[i]->parent->id : 0;
|
|
while (j > 0) {
|
|
nB_post += bodies_[j]->dofnum;
|
|
j = bodies_[j]->parent ? bodies_[j]->parent->id : 0;
|
|
}
|
|
}
|
|
if (nB_post != nB) throw mjCError(0, "nB mismatch: pre %d, post %d", nullptr, nB, nB_post);
|
|
}
|
|
|
|
// copy plugin data
|
|
void mjCModel::CopyPlugins(mjModel* m) {
|
|
// assign plugin slots and copy plugin config attributes
|
|
{
|
|
int adr = 0;
|
|
for (int i = 0; i < nplugin; ++i) {
|
|
m->plugin[i] = plugins_[i]->plugin_slot;
|
|
const int size = plugins_[i]->flattened_attributes.size();
|
|
std::memcpy(m->plugin_attr + adr,
|
|
plugins_[i]->flattened_attributes.data(), size);
|
|
m->plugin_attradr[i] = adr;
|
|
adr += size;
|
|
}
|
|
}
|
|
|
|
// query and set plugin-related information
|
|
{
|
|
// set actuator_plugin to the plugin instance ID
|
|
std::vector<std::vector<int> > plugin_to_actuators(nplugin);
|
|
for (int i = 0; i < nu; ++i) {
|
|
if (actuators_[i]->plugin.active) {
|
|
int actuator_plugin = static_cast<mjCPlugin*>(actuators_[i]->plugin.element)->id;
|
|
m->actuator_plugin[i] = actuator_plugin;
|
|
plugin_to_actuators[actuator_plugin].push_back(i);
|
|
} else {
|
|
m->actuator_plugin[i] = -1;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < nbody; ++i) {
|
|
if (bodies_[i]->plugin.active) {
|
|
m->body_plugin[i] = static_cast<mjCPlugin*>(bodies_[i]->plugin.element)->id;
|
|
} else {
|
|
m->body_plugin[i] = -1;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < ngeom; ++i) {
|
|
if (geoms_[i]->plugin.active) {
|
|
m->geom_plugin[i] = static_cast<mjCPlugin*>(geoms_[i]->plugin.element)->id;
|
|
} else {
|
|
m->geom_plugin[i] = -1;
|
|
}
|
|
}
|
|
|
|
std::vector<std::vector<int> > plugin_to_sensors(nplugin);
|
|
for (int i = 0; i < nsensor; ++i) {
|
|
if (sensors_[i]->type == mjSENS_PLUGIN) {
|
|
int sensor_plugin = static_cast<mjCPlugin*>(sensors_[i]->plugin.element)->id;
|
|
m->sensor_plugin[i] = sensor_plugin;
|
|
plugin_to_sensors[sensor_plugin].push_back(i);
|
|
} else {
|
|
m->sensor_plugin[i] = -1;
|
|
}
|
|
}
|
|
|
|
// query plugin->nstate, compute and set plugin_state and plugin_stateadr
|
|
// for sensor plugins, also query plugin->nsensordata and set nsensordata
|
|
int stateadr = 0;
|
|
for (int i = 0; i < nplugin; ++i) {
|
|
const mjpPlugin* plugin = mjp_getPluginAtSlot(m->plugin[i]);
|
|
if (!plugin->nstate) {
|
|
mju_error("`nstate` is null for plugin at slot %d", m->plugin[i]);
|
|
}
|
|
int nstate = plugin->nstate(m, i);
|
|
m->plugin_stateadr[i] = stateadr;
|
|
m->plugin_statenum[i] = nstate;
|
|
stateadr += nstate;
|
|
if (plugin->capabilityflags & mjPLUGIN_SENSOR) {
|
|
for (int sensor_id : plugin_to_sensors[i]) {
|
|
if (!plugin->nsensordata) {
|
|
mju_error("`nsensordata` is null for plugin at slot %d", m->plugin[i]);
|
|
}
|
|
int nsensordata = plugin->nsensordata(m, i, sensor_id);
|
|
sensors_[sensor_id]->dim = nsensordata;
|
|
sensors_[sensor_id]->needstage =
|
|
static_cast<mjtStage>(plugin->needstage);
|
|
this->nsensordata += nsensordata;
|
|
}
|
|
}
|
|
}
|
|
m->npluginstate = stateadr;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// compute non-zeros in actuator_moment matrix
|
|
int mjCModel::CountNJmom(const mjModel* m) {
|
|
int nu = m->nu;
|
|
int nv = m->nv;
|
|
|
|
int count = 0;
|
|
for (int i = 0; i < nu; i++) {
|
|
// extract info
|
|
int id = m->actuator_trnid[2 * i];
|
|
|
|
// process according to transmission type
|
|
switch ((mjtTrn)m->actuator_trntype[i]) {
|
|
case mjTRN_JOINT:
|
|
case mjTRN_JOINTINPARENT:
|
|
switch ((mjtJoint)m->jnt_type[id]) {
|
|
case mjJNT_SLIDE:
|
|
case mjJNT_HINGE:
|
|
count += 1;
|
|
break;
|
|
|
|
case mjJNT_BALL:
|
|
count += 3;
|
|
break;
|
|
|
|
case mjJNT_FREE:
|
|
count += 6;
|
|
break;
|
|
}
|
|
break;
|
|
// TODO(taylorhowell): improve upper bounds
|
|
case mjTRN_SLIDERCRANK:
|
|
count += nv;
|
|
break;
|
|
|
|
case mjTRN_TENDON:
|
|
count += nv;
|
|
break;
|
|
|
|
case mjTRN_SITE:
|
|
count += nv;
|
|
break;
|
|
|
|
case mjTRN_BODY:
|
|
count += nv;
|
|
break;
|
|
|
|
default:
|
|
// SHOULD NOT OCCUR
|
|
throw mjCError(0, "unknown transmission type");
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
|
|
// copy objects outside kinematic tree
|
|
void mjCModel::CopyObjects(mjModel* m) {
|
|
int adr, bone_adr, vert_adr, node_adr, normal_adr, face_adr, texcoord_adr, oct_adr;
|
|
int edge_adr, elem_adr, elemdata_adr, elemedge_adr, shelldata_adr, evpair_adr;
|
|
int bonevert_adr, graph_adr, data_adr, bvh_adr;
|
|
int poly_adr, polymap_adr, polyvert_adr;
|
|
|
|
// sizes outside call to mj_makeModel
|
|
m->nemax = nemax;
|
|
m->njmax = njmax;
|
|
m->nconmax = nconmax;
|
|
m->nsensordata = nsensordata;
|
|
m->nuserdata = nuserdata;
|
|
m->na = na;
|
|
|
|
// find bvh_adr after bodies
|
|
bvh_adr = 0;
|
|
for (int i=0; i < nbody; i++) {
|
|
bvh_adr = mjMAX(bvh_adr, m->body_bvhadr[i] + m->body_bvhnum[i]);
|
|
}
|
|
|
|
// meshes
|
|
oct_adr = 0;
|
|
vert_adr = 0;
|
|
normal_adr = 0;
|
|
texcoord_adr = 0;
|
|
face_adr = 0;
|
|
graph_adr = 0;
|
|
poly_adr = 0;
|
|
polyvert_adr = 0;
|
|
polymap_adr = 0;
|
|
for (int i=0; i < nmesh; i++) {
|
|
// get pointer
|
|
mjCMesh* pme = meshes_[i];
|
|
|
|
// set fields
|
|
m->mesh_polyadr[i] = poly_adr;
|
|
m->mesh_polynum[i] = pme->npolygon();
|
|
m->mesh_vertadr[i] = vert_adr;
|
|
m->mesh_vertnum[i] = pme->nvert();
|
|
m->mesh_normaladr[i] = normal_adr;
|
|
m->mesh_normalnum[i] = pme->nnormal();
|
|
m->mesh_texcoordadr[i] = (pme->HasTexcoord() ? texcoord_adr : -1);
|
|
m->mesh_texcoordnum[i] = pme->ntexcoord();
|
|
m->mesh_faceadr[i] = face_adr;
|
|
m->mesh_facenum[i] = pme->nface();
|
|
m->mesh_graphadr[i] = (pme->szgraph() ? graph_adr : -1);
|
|
m->mesh_bvhnum[i] = pme->tree().Nbvh();
|
|
m->mesh_bvhadr[i] = pme->tree().Nbvh() ? bvh_adr : -1;
|
|
m->mesh_octnum[i] = pme->octree().NumNodes();
|
|
m->mesh_octadr[i] = pme->octree().NumNodes() ? oct_adr : -1;
|
|
mjuu_copyvec(&m->mesh_scale[3 * i], pme->Scale(), 3);
|
|
mjuu_copyvec(&m->mesh_pos[3 * i], pme->GetPosPtr(), 3);
|
|
mjuu_copyvec(&m->mesh_quat[4 * i], pme->GetQuatPtr(), 4);
|
|
|
|
// copy vertices, normals, faces, texcoords, aux data
|
|
pme->CopyVert(m->mesh_vert + 3*vert_adr);
|
|
pme->CopyNormal(m->mesh_normal + 3*normal_adr);
|
|
pme->CopyFace(m->mesh_face + 3*face_adr);
|
|
pme->CopyFaceNormal(m->mesh_facenormal + 3*face_adr);
|
|
if (pme->HasTexcoord()) {
|
|
pme->CopyTexcoord(m->mesh_texcoord + 2*texcoord_adr);
|
|
pme->CopyFaceTexcoord(m->mesh_facetexcoord + 3*face_adr);
|
|
} else {
|
|
memset(m->mesh_facetexcoord + 3*face_adr, 0, 3*pme->nface()*sizeof(int));
|
|
}
|
|
if (pme->szgraph()) {
|
|
pme->CopyGraph(m->mesh_graph + graph_adr);
|
|
}
|
|
pme->CopyPolygonNormals(m->mesh_polynormal + 3*poly_adr);
|
|
pme->CopyPolygons(m->mesh_polyvert + polyvert_adr, m->mesh_polyvertadr + poly_adr,
|
|
m->mesh_polyvertnum + poly_adr, polyvert_adr);
|
|
pme->CopyPolygonMap(m->mesh_polymap + polymap_adr, m->mesh_polymapadr + vert_adr,
|
|
m->mesh_polymapnum + vert_adr, polymap_adr);
|
|
|
|
// copy bvh data
|
|
if (pme->tree().Nbvh()) {
|
|
memcpy(m->bvh_aabb + 6*bvh_adr, pme->tree().Bvh().data(), 6*pme->tree().Nbvh()*sizeof(mjtNum));
|
|
memcpy(m->bvh_child + 2*bvh_adr, pme->tree().Child().data(), 2*pme->tree().Nbvh()*sizeof(int));
|
|
memcpy(m->bvh_depth + bvh_adr, pme->tree().Level().data(), pme->tree().Nbvh()*sizeof(int));
|
|
for (int j=0; j < pme->tree().Nbvh(); j++) {
|
|
m->bvh_nodeid[j + bvh_adr] = pme->tree().Nodeid(j) > -1 ? pme->tree().Nodeid(j) : -1;
|
|
}
|
|
}
|
|
|
|
// copy octree data
|
|
if (pme->octree().NumNodes()) {
|
|
pme->octree().CopyAabb(m->oct_aabb + 6*oct_adr);
|
|
pme->octree().CopyChild(m->oct_child + 8*oct_adr);
|
|
pme->octree().CopyLevel(m->oct_depth + oct_adr);
|
|
pme->octree().CopyCoeff(m->oct_coeff + 8*oct_adr);
|
|
}
|
|
|
|
// advance counters
|
|
poly_adr += pme->npolygon();
|
|
polyvert_adr += pme->npolygonvert();
|
|
polymap_adr += pme->npolygonmap();
|
|
vert_adr += pme->nvert();
|
|
normal_adr += pme->nnormal();
|
|
texcoord_adr += (pme->HasTexcoord() ? pme->ntexcoord() : 0);
|
|
face_adr += pme->nface();
|
|
graph_adr += pme->szgraph();
|
|
bvh_adr += pme->tree().Nbvh();
|
|
oct_adr += pme->octree().NumNodes();
|
|
}
|
|
|
|
// flexes
|
|
vert_adr = 0;
|
|
node_adr = 0;
|
|
edge_adr = 0;
|
|
elem_adr = 0;
|
|
elemdata_adr = 0;
|
|
elemedge_adr = 0;
|
|
shelldata_adr = 0;
|
|
evpair_adr = 0;
|
|
texcoord_adr = 0;
|
|
for (int i=0; i < nflex; i++) {
|
|
// get pointer
|
|
mjCFlex* pfl = flexes_[i];
|
|
|
|
// set fields: geom-like
|
|
m->flex_contype[i] = pfl->contype;
|
|
m->flex_conaffinity[i] = pfl->conaffinity;
|
|
m->flex_condim[i] = pfl->condim;
|
|
m->flex_matid[i] = pfl->matid;
|
|
m->flex_group[i] = pfl->group;
|
|
m->flex_priority[i] = pfl->priority;
|
|
m->flex_solmix[i] = (mjtNum)pfl->solmix;
|
|
mjuu_copyvec(m->flex_solref + mjNREF * i, pfl->solref, mjNREF);
|
|
mjuu_copyvec(m->flex_solimp + mjNIMP * i, pfl->solimp, mjNIMP);
|
|
m->flex_radius[i] = (mjtNum)pfl->radius;
|
|
mjuu_copyvec(m->flex_friction + 3 * i, pfl->friction, 3);
|
|
m->flex_margin[i] = (mjtNum)pfl->margin;
|
|
m->flex_gap[i] = (mjtNum)pfl->gap;
|
|
mjuu_copyvec(m->flex_rgba + 4 * i, pfl->rgba, 4);
|
|
|
|
// elasticity
|
|
if (!pfl->stiffness.empty()) {
|
|
mjuu_copyvec(m->flex_stiffness + 21 * elem_adr, pfl->stiffness.data(), pfl->stiffness.size());
|
|
} else {
|
|
mjuu_zerovec(m->flex_stiffness + 21 * elem_adr, 21 * pfl->nelem);
|
|
}
|
|
if (!pfl->bending.empty()) {
|
|
mjuu_copyvec(m->flex_bending + 17 * edge_adr, pfl->bending.data(), pfl->bending.size());
|
|
} else {
|
|
mjuu_zerovec(m->flex_bending + 17 * edge_adr, 17 * pfl->nedge);
|
|
}
|
|
m->flex_damping[i] = (mjtNum)pfl->damping;
|
|
|
|
// set fields: mesh-like
|
|
m->flex_dim[i] = pfl->dim;
|
|
m->flex_vertadr[i] = vert_adr;
|
|
m->flex_vertnum[i] = pfl->nvert;
|
|
m->flex_nodeadr[i] = node_adr;
|
|
m->flex_nodenum[i] = pfl->nnode;
|
|
m->flex_edgeadr[i] = edge_adr;
|
|
m->flex_edgenum[i] = pfl->nedge;
|
|
m->flex_elemadr[i] = elem_adr;
|
|
m->flex_elemdataadr[i] = elemdata_adr;
|
|
m->flex_elemedgeadr[i] = elemedge_adr;
|
|
m->flex_shellnum[i] = (int)pfl->shell.size()/pfl->dim;
|
|
m->flex_shelldataadr[i] = m->flex_shellnum[i] ? shelldata_adr : -1;
|
|
if (pfl->evpair.empty()) {
|
|
m->flex_evpairadr[i] = -1;
|
|
m->flex_evpairnum[i] = 0;
|
|
} else {
|
|
m->flex_evpairadr[i] = evpair_adr;
|
|
m->flex_evpairnum[i] = (int)pfl->evpair.size()/2;
|
|
memcpy(m->flex_evpair + 2*evpair_adr, pfl->evpair.data(), pfl->evpair.size()*sizeof(int));
|
|
}
|
|
if (pfl->texcoord_.empty()) {
|
|
m->flex_texcoordadr[i] = -1;
|
|
memcpy(m->flex_elemtexcoord + elemdata_adr, pfl->elem_.data(), pfl->elem_.size()*sizeof(int));
|
|
} else {
|
|
m->flex_texcoordadr[i] = texcoord_adr;
|
|
memcpy(m->flex_texcoord + 2*texcoord_adr,
|
|
pfl->texcoord_.data(), pfl->texcoord_.size()*sizeof(float));
|
|
memcpy(m->flex_elemtexcoord + elemdata_adr, pfl->elemtexcoord_.data(),
|
|
pfl->elemtexcoord_.size()*sizeof(int));
|
|
}
|
|
m->flex_elemnum[i] = pfl->nelem;
|
|
memcpy(m->flex_elem + elemdata_adr, pfl->elem_.data(), pfl->elem_.size()*sizeof(int));
|
|
memcpy(m->flex_elemedge + elemedge_adr, pfl->edgeidx_.data(), pfl->edgeidx_.size()*sizeof(int));
|
|
memcpy(m->flex_elemlayer + elem_adr, pfl->elemlayer.data(), pfl->nelem*sizeof(int));
|
|
if (m->flex_shellnum[i]) {
|
|
memcpy(m->flex_shell + shelldata_adr, pfl->shell.data(), pfl->shell.size()*sizeof(int));
|
|
}
|
|
m->flex_edgestiffness[i] = (mjtNum)pfl->edgestiffness;
|
|
m->flex_edgedamping[i] = (mjtNum)pfl->edgedamping;
|
|
m->flex_rigid[i] = pfl->rigid;
|
|
m->flex_centered[i] = pfl->centered;
|
|
m->flex_internal[i] = pfl->internal;
|
|
m->flex_flatskin[i] = pfl->flatskin;
|
|
m->flex_selfcollide[i] = pfl->selfcollide;
|
|
m->flex_activelayers[i] = pfl->activelayers;
|
|
m->flex_passive[i] = pfl->passive;
|
|
m->flex_bvhnum[i] = pfl->tree.Nbvh();
|
|
m->flex_bvhadr[i] = pfl->tree.Nbvh() ? bvh_adr : -1;
|
|
|
|
// find equality constraint referencing this flex
|
|
m->flex_edgeequality[i] = 0;
|
|
for (int k=0; k < (int)equalities_.size(); k++) {
|
|
if (equalities_[k]->type == mjEQ_FLEX && equalities_[k]->name1_ == pfl->name) {
|
|
m->flex_edgeequality[i] = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// copy bvh data (flex aabb computed dynamically in mjData)
|
|
if (pfl->tree.Nbvh()) {
|
|
memcpy(m->bvh_child + 2*bvh_adr, pfl->tree.Child().data(), 2*pfl->tree.Nbvh()*sizeof(int));
|
|
memcpy(m->bvh_depth + bvh_adr, pfl->tree.Level().data(), pfl->tree.Nbvh()*sizeof(int));
|
|
for (int i=0; i < pfl->tree.Nbvh(); i++) {
|
|
m->bvh_nodeid[i+ bvh_adr] = pfl->tree.Nodeidptr(i) ? *(pfl->tree.Nodeidptr(i)) : -1;
|
|
}
|
|
}
|
|
|
|
// copy or set vert
|
|
if (pfl->centered && !pfl->interpolated) {
|
|
mjuu_zerovec(m->flex_vert + 3*vert_adr, 3*pfl->nvert);
|
|
}
|
|
else {
|
|
mjuu_copyvec(m->flex_vert + 3*vert_adr, pfl->vert_.data(), 3*pfl->nvert);
|
|
}
|
|
|
|
// copy or set node
|
|
if (pfl->centered && pfl->interpolated) {
|
|
mjuu_zerovec(m->flex_node + 3*node_adr, 3*pfl->nnode);
|
|
}
|
|
else if (pfl->interpolated) {
|
|
mjuu_copyvec(m->flex_node + 3*node_adr, pfl->node_.data(), 3*pfl->nnode);
|
|
}
|
|
|
|
// copy vert0
|
|
mjuu_copyvec(m->flex_vert0 + 3*vert_adr, pfl->vert0_.data(), 3*pfl->nvert);
|
|
|
|
// copy node0
|
|
mjuu_copyvec(m->flex_node0 + 3*node_adr, pfl->node0_.data(), 3*pfl->nnode);
|
|
|
|
// copy or set vertbodyid
|
|
if (pfl->rigid) {
|
|
for (int k=0; k < pfl->nvert; k++) {
|
|
m->flex_vertbodyid[vert_adr + k] = pfl->vertbodyid[0];
|
|
}
|
|
}
|
|
else {
|
|
memcpy(m->flex_vertbodyid + vert_adr, pfl->vertbodyid.data(), pfl->nvert*sizeof(int));
|
|
}
|
|
|
|
// copy or set nodebodyid
|
|
if (pfl->rigid) {
|
|
for (int k=0; k < pfl->nnode; k++) {
|
|
m->flex_nodebodyid[node_adr + k] = pfl->nodebodyid[0];
|
|
}
|
|
} else {
|
|
memcpy(m->flex_nodebodyid + node_adr, pfl->nodebodyid.data(), pfl->nnode*sizeof(int));
|
|
}
|
|
|
|
// set interpolation type, only two types for now
|
|
m->flex_interp[i] = pfl->interpolated;
|
|
|
|
// convert edge pairs to int array, set edge rigid
|
|
for (int k=0; k < pfl->nedge; k++) {
|
|
m->flex_edge[2*(edge_adr+k)] = pfl->edge[k].first;
|
|
m->flex_edge[2*(edge_adr+k)+1] = pfl->edge[k].second;
|
|
if (pfl->dim == 2 && (pfl->elastic2d == 1 || pfl->elastic2d == 3)) {
|
|
m->flex_edgeflap[2*(edge_adr+k)+0] = pfl->flaps[k].vertices[2];
|
|
m->flex_edgeflap[2*(edge_adr+k)+1] = pfl->flaps[k].vertices[3];
|
|
} else {
|
|
m->flex_edgeflap[2*(edge_adr+k)+0] = -1;
|
|
m->flex_edgeflap[2*(edge_adr+k)+1] = -1;
|
|
}
|
|
|
|
if (pfl->rigid) {
|
|
m->flexedge_rigid[edge_adr+k] = 1;
|
|
} else if (!pfl->interpolated) {
|
|
// check if vertex body weldids are the same
|
|
// unsupported by trilinear interpolation
|
|
int b1 = pfl->vertbodyid[pfl->edge[k].first];
|
|
int b2 = pfl->vertbodyid[pfl->edge[k].second];
|
|
m->flexedge_rigid[edge_adr+k] = (bodies_[b1]->weldid == bodies_[b2]->weldid);
|
|
}
|
|
}
|
|
|
|
// advance counters
|
|
vert_adr += pfl->nvert;
|
|
node_adr += pfl->nnode;
|
|
edge_adr += pfl->nedge;
|
|
elem_adr += pfl->nelem;
|
|
elemdata_adr += (pfl->dim+1) * pfl->nelem;
|
|
elemedge_adr += (pfl->kNumEdges[pfl->dim-1]) * pfl->nelem;
|
|
shelldata_adr += (int)pfl->shell.size();
|
|
evpair_adr += (int)pfl->evpair.size()/2;
|
|
texcoord_adr += (int)pfl->texcoord_.size()/2;
|
|
bvh_adr += pfl->tree.Nbvh();
|
|
}
|
|
|
|
// skins
|
|
vert_adr = 0;
|
|
face_adr = 0;
|
|
texcoord_adr = 0;
|
|
bone_adr = 0;
|
|
bonevert_adr = 0;
|
|
for (int i=0; i < nskin; i++) {
|
|
// get pointer
|
|
mjCSkin* psk = skins_[i];
|
|
|
|
// set fields
|
|
m->skin_matid[i] = psk->matid;
|
|
m->skin_group[i] = psk->group;
|
|
mjuu_copyvec(m->skin_rgba+4*i, psk->rgba, 4);
|
|
m->skin_inflate[i] = psk->inflate;
|
|
m->skin_vertadr[i] = vert_adr;
|
|
m->skin_vertnum[i] = psk->get_vert().size()/3;
|
|
m->skin_texcoordadr[i] = (!psk->get_texcoord().empty() ? texcoord_adr : -1);
|
|
m->skin_faceadr[i] = face_adr;
|
|
m->skin_facenum[i] = psk->get_face().size()/3;
|
|
m->skin_boneadr[i] = bone_adr;
|
|
m->skin_bonenum[i] = psk->bodyid.size();
|
|
|
|
// copy mesh data
|
|
memcpy(m->skin_vert + 3*vert_adr, psk->get_vert().data(), psk->get_vert().size()*sizeof(float));
|
|
if (!psk->get_texcoord().empty())
|
|
memcpy(m->skin_texcoord + 2*texcoord_adr, psk->get_texcoord().data(),
|
|
psk->get_texcoord().size()*sizeof(float));
|
|
memcpy(m->skin_face + 3*face_adr, psk->get_face().data(), psk->get_face().size()*sizeof(int));
|
|
|
|
// copy bind poses and body ids
|
|
memcpy(m->skin_bonebindpos+3*bone_adr, psk->get_bindpos().data(),
|
|
psk->get_bindpos().size()*sizeof(float));
|
|
memcpy(m->skin_bonebindquat+4*bone_adr, psk->get_bindquat().data(),
|
|
psk->get_bindquat().size()*sizeof(float));
|
|
memcpy(m->skin_bonebodyid+bone_adr, psk->bodyid.data(),
|
|
psk->bodyid.size()*sizeof(int));
|
|
|
|
// copy per-bone vertex data, advance vertex counter
|
|
for (int j=0; j < m->skin_bonenum[i]; j++) {
|
|
// set fields
|
|
m->skin_bonevertadr[bone_adr+j] = bonevert_adr;
|
|
m->skin_bonevertnum[bone_adr+j] = (int)psk->get_vertid()[j].size();
|
|
|
|
// copy data
|
|
memcpy(m->skin_bonevertid+bonevert_adr, psk->get_vertid()[j].data(),
|
|
psk->get_vertid()[j].size()*sizeof(int));
|
|
memcpy(m->skin_bonevertweight+bonevert_adr, psk->get_vertweight()[j].data(),
|
|
psk->get_vertid()[j].size()*sizeof(float));
|
|
|
|
// advance counter
|
|
bonevert_adr += m->skin_bonevertnum[bone_adr+j];
|
|
}
|
|
|
|
// advance mesh and bone counters
|
|
vert_adr += m->skin_vertnum[i];
|
|
texcoord_adr += psk->get_texcoord().size()/2;
|
|
face_adr += m->skin_facenum[i];
|
|
bone_adr += m->skin_bonenum[i];
|
|
}
|
|
|
|
// hfields
|
|
data_adr = 0;
|
|
for (int i=0; i < nhfield; i++) {
|
|
// get pointer
|
|
mjCHField* phf = hfields_[i];
|
|
|
|
// set fields
|
|
mjuu_copyvec(m->hfield_size+4*i, phf->size, 4);
|
|
m->hfield_nrow[i] = phf->nrow;
|
|
m->hfield_ncol[i] = phf->ncol;
|
|
m->hfield_adr[i] = data_adr;
|
|
|
|
// copy elevation data
|
|
memcpy(m->hfield_data + data_adr, phf->data.data(), phf->nrow*phf->ncol*sizeof(float));
|
|
|
|
// advance counter
|
|
data_adr += phf->nrow*phf->ncol;
|
|
}
|
|
|
|
// textures
|
|
data_adr = 0;
|
|
for (int i=0; i < ntex; i++) {
|
|
// get pointer
|
|
mjCTexture* ptex = textures_[i];
|
|
|
|
// set fields
|
|
m->tex_type[i] = ptex->type;
|
|
m->tex_colorspace[i] = ptex->colorspace;
|
|
m->tex_height[i] = ptex->height;
|
|
m->tex_width[i] = ptex->width;
|
|
m->tex_nchannel[i] = ptex->nchannel;
|
|
m->tex_adr[i] = data_adr;
|
|
|
|
// copy rgb data
|
|
memcpy(m->tex_data + data_adr, ptex->data_.data(),
|
|
ptex->nchannel * ptex->width * ptex->height);
|
|
|
|
// advance counter
|
|
data_adr += ptex->nchannel * ptex->width * ptex->height;
|
|
}
|
|
|
|
// materials
|
|
for (int i=0; i < nmat; i++) {
|
|
// get pointer
|
|
mjCMaterial* pmat = materials_[i];
|
|
|
|
// set fields
|
|
for (int j=0; j < mjNTEXROLE; j++) {
|
|
m->mat_texid[mjNTEXROLE*i+j] = pmat->texid[j];
|
|
}
|
|
m->mat_texuniform[i] = pmat->texuniform;
|
|
mjuu_copyvec(m->mat_texrepeat+2*i, pmat->texrepeat, 2);
|
|
m->mat_emission[i] = pmat->emission;
|
|
m->mat_specular[i] = pmat->specular;
|
|
m->mat_shininess[i] = pmat->shininess;
|
|
m->mat_reflectance[i] = pmat->reflectance;
|
|
m->mat_metallic[i] = pmat->metallic;
|
|
m->mat_roughness[i] = pmat->roughness;
|
|
mjuu_copyvec(m->mat_rgba+4*i, pmat->rgba, 4);
|
|
}
|
|
|
|
// geom pairs to include
|
|
for (int i=0; i < npair; i++) {
|
|
m->pair_dim[i] = pairs_[i]->condim;
|
|
m->pair_geom1[i] = pairs_[i]->geom1->id;
|
|
m->pair_geom2[i] = pairs_[i]->geom2->id;
|
|
m->pair_signature[i] = pairs_[i]->signature;
|
|
mjuu_copyvec(m->pair_solref+mjNREF*i, pairs_[i]->solref, mjNREF);
|
|
mjuu_copyvec(m->pair_solreffriction+mjNREF*i, pairs_[i]->solreffriction, mjNREF);
|
|
mjuu_copyvec(m->pair_solimp+mjNIMP*i, pairs_[i]->solimp, mjNIMP);
|
|
m->pair_margin[i] = (mjtNum)pairs_[i]->margin;
|
|
m->pair_gap[i] = (mjtNum)pairs_[i]->gap;
|
|
mjuu_copyvec(m->pair_friction+5*i, pairs_[i]->friction, 5);
|
|
}
|
|
|
|
// body pairs to exclude
|
|
for (int i=0; i < nexclude; i++) {
|
|
m->exclude_signature[i] = excludes_[i]->signature;
|
|
}
|
|
|
|
// equality constraints
|
|
for (int i=0; i < neq; i++) {
|
|
// get pointer
|
|
mjCEquality* peq = equalities_[i];
|
|
|
|
// set fields
|
|
m->eq_type[i] = peq->type;
|
|
m->eq_obj1id[i] = peq->obj1id;
|
|
m->eq_obj2id[i] = peq->obj2id;
|
|
m->eq_objtype[i] = peq->objtype;
|
|
m->eq_active0[i] = peq->active;
|
|
mjuu_copyvec(m->eq_solref+mjNREF*i, peq->solref, mjNREF);
|
|
mjuu_copyvec(m->eq_solimp+mjNIMP*i, peq->solimp, mjNIMP);
|
|
mjuu_copyvec(m->eq_data+mjNEQDATA*i, peq->data, mjNEQDATA);
|
|
}
|
|
|
|
// tendons and wraps
|
|
adr = 0;
|
|
for (int i=0; i < ntendon; i++) {
|
|
// get pointer
|
|
mjCTendon* pte = tendons_[i];
|
|
|
|
// set fields
|
|
m->tendon_adr[i] = adr;
|
|
m->tendon_num[i] = (int)pte->path.size();
|
|
m->tendon_matid[i] = pte->matid;
|
|
m->tendon_group[i] = pte->group;
|
|
m->tendon_limited[i] = (mjtByte)pte->is_limited();
|
|
m->tendon_actfrclimited[i] = (mjtByte)pte->is_actfrclimited();
|
|
m->tendon_width[i] = (mjtNum)pte->width;
|
|
mjuu_copyvec(m->tendon_solref_lim+mjNREF*i, pte->solref_limit, mjNREF);
|
|
mjuu_copyvec(m->tendon_solimp_lim+mjNIMP*i, pte->solimp_limit, mjNIMP);
|
|
mjuu_copyvec(m->tendon_solref_fri+mjNREF*i, pte->solref_friction, mjNREF);
|
|
mjuu_copyvec(m->tendon_solimp_fri+mjNIMP*i, pte->solimp_friction, mjNIMP);
|
|
m->tendon_range[2*i] = (mjtNum)pte->range[0];
|
|
m->tendon_range[2*i+1] = (mjtNum)pte->range[1];
|
|
m->tendon_actfrcrange[2*i] = (mjtNum)pte->actfrcrange[0];
|
|
m->tendon_actfrcrange[2*i+1] = (mjtNum)pte->actfrcrange[1];
|
|
m->tendon_margin[i] = (mjtNum)pte->margin;
|
|
m->tendon_stiffness[i] = (mjtNum)pte->stiffness;
|
|
m->tendon_damping[i] = (mjtNum)pte->damping;
|
|
m->tendon_armature[i] = (mjtNum)pte->armature;
|
|
m->tendon_frictionloss[i] = (mjtNum)pte->frictionloss;
|
|
m->tendon_lengthspring[2*i] = (mjtNum)pte->springlength[0];
|
|
m->tendon_lengthspring[2*i+1] = (mjtNum)pte->springlength[1];
|
|
mjuu_copyvec(m->tendon_user+nuser_tendon*i, pte->get_userdata().data(), nuser_tendon);
|
|
mjuu_copyvec(m->tendon_rgba+4*i, pte->rgba, 4);
|
|
|
|
// set wraps
|
|
for (int j=0; j < (int)pte->path.size(); j++) {
|
|
m->wrap_type[adr+j] = pte->path[j]->type;
|
|
m->wrap_objid[adr+j] = pte->path[j]->obj ? pte->path[j]->obj->id : -1;
|
|
m->wrap_prm[adr+j] = (mjtNum)pte->path[j]->prm;
|
|
if (pte->path[j]->type == mjWRAP_SPHERE || pte->path[j]->type == mjWRAP_CYLINDER) {
|
|
m->wrap_prm[adr+j] = (mjtNum)pte->path[j]->sideid;
|
|
}
|
|
}
|
|
|
|
// advance address counter
|
|
adr += (int)pte->path.size();
|
|
}
|
|
|
|
// actuators
|
|
adr = 0;
|
|
for (int i=0; i < nu; i++) {
|
|
// get pointer
|
|
mjCActuator* pac = actuators_[i];
|
|
|
|
// set fields
|
|
m->actuator_trntype[i] = pac->trntype;
|
|
m->actuator_dyntype[i] = pac->dyntype;
|
|
m->actuator_gaintype[i] = pac->gaintype;
|
|
m->actuator_biastype[i] = pac->biastype;
|
|
m->actuator_trnid[2*i] = pac->trnid[0];
|
|
m->actuator_trnid[2*i+1] = pac->trnid[1];
|
|
m->actuator_actnum[i] = pac->actdim;
|
|
m->actuator_actadr[i] = m->actuator_actnum[i] ? adr : -1;
|
|
pac->actadr_ = m->actuator_actadr[i];
|
|
pac->actdim_ = m->actuator_actnum[i];
|
|
adr += m->actuator_actnum[i];
|
|
m->actuator_group[i] = pac->group;
|
|
m->actuator_ctrllimited[i] = (mjtByte)pac->is_ctrllimited();
|
|
m->actuator_forcelimited[i] = (mjtByte)pac->is_forcelimited();
|
|
m->actuator_actlimited[i] = (mjtByte)pac->is_actlimited();
|
|
m->actuator_actearly[i] = pac->actearly;
|
|
m->actuator_cranklength[i] = (mjtNum)pac->cranklength;
|
|
mjuu_copyvec(m->actuator_gear + 6*i, pac->gear, 6);
|
|
mjuu_copyvec(m->actuator_dynprm + mjNDYN*i, pac->dynprm, mjNDYN);
|
|
mjuu_copyvec(m->actuator_gainprm + mjNGAIN*i, pac->gainprm, mjNGAIN);
|
|
mjuu_copyvec(m->actuator_biasprm + mjNBIAS*i, pac->biasprm, mjNBIAS);
|
|
mjuu_copyvec(m->actuator_ctrlrange + 2*i, pac->ctrlrange, 2);
|
|
mjuu_copyvec(m->actuator_forcerange + 2*i, pac->forcerange, 2);
|
|
mjuu_copyvec(m->actuator_actrange + 2*i, pac->actrange, 2);
|
|
mjuu_copyvec(m->actuator_lengthrange + 2*i, pac->lengthrange, 2);
|
|
mjuu_copyvec(m->actuator_user+nuser_actuator*i, pac->get_userdata().data(), nuser_actuator);
|
|
}
|
|
|
|
// sensors
|
|
adr = 0;
|
|
for (int i=0; i < nsensor; i++) {
|
|
// get pointer
|
|
mjCSensor* psen = sensors_[i];
|
|
|
|
// set fields
|
|
m->sensor_type[i] = psen->type;
|
|
m->sensor_datatype[i] = psen->datatype;
|
|
m->sensor_needstage[i] = psen->needstage;
|
|
m->sensor_objtype[i] = psen->objtype;
|
|
m->sensor_objid[i] = psen->obj ? psen->obj->id : -1;
|
|
m->sensor_reftype[i] = psen->reftype;
|
|
m->sensor_refid[i] = psen->ref ? psen->ref->id : -1;
|
|
mjuu_copyvec(m->sensor_intprm+i*mjNSENS, psen->intprm, mjNSENS);
|
|
m->sensor_dim[i] = psen->dim;
|
|
m->sensor_cutoff[i] = (mjtNum)psen->cutoff;
|
|
m->sensor_noise[i] = (mjtNum)psen->noise;
|
|
mjuu_copyvec(m->sensor_user+nuser_sensor*i, psen->get_userdata().data(), nuser_sensor);
|
|
|
|
// calculate address and advance
|
|
m->sensor_adr[i] = adr;
|
|
adr += psen->dim;
|
|
}
|
|
|
|
// numeric fields
|
|
adr = 0;
|
|
for (int i=0; i < nnumeric; i++) {
|
|
// get pointer
|
|
mjCNumeric* pcu = numerics_[i];
|
|
|
|
// set fields
|
|
m->numeric_adr[i] = adr;
|
|
m->numeric_size[i] = pcu->size;
|
|
for (int j=0; j < (int)pcu->data_.size(); j++) {
|
|
m->numeric_data[adr+j] = (mjtNum)pcu->data_[j];
|
|
}
|
|
for (int j=(int)pcu->data_.size(); j < (int)pcu->size; j++) {
|
|
m->numeric_data[adr+j] = 0;
|
|
}
|
|
|
|
// advance address counter
|
|
adr += m->numeric_size[i];
|
|
}
|
|
|
|
// text fields
|
|
adr = 0;
|
|
for (int i=0; i < ntext; i++) {
|
|
// get pointer
|
|
mjCText* pte = texts_[i];
|
|
|
|
// set fields
|
|
m->text_adr[i] = adr;
|
|
m->text_size[i] = (int)pte->data_.size()+1;
|
|
mju_strncpy(m->text_data + adr, pte->data_.c_str(), m->ntextdata - adr);
|
|
|
|
// advance address counter
|
|
adr += m->text_size[i];
|
|
}
|
|
|
|
// tuple fields
|
|
adr = 0;
|
|
for (int i=0; i < ntuple; i++) {
|
|
// get pointer
|
|
mjCTuple* ptu = tuples_[i];
|
|
|
|
// set fields
|
|
m->tuple_adr[i] = adr;
|
|
m->tuple_size[i] = (int)ptu->objtype_.size();
|
|
for (int j=0; j < m->tuple_size[i]; j++) {
|
|
m->tuple_objtype[adr+j] = (int)ptu->objtype_[j];
|
|
m->tuple_objid[adr+j] = ptu->obj[j]->id;
|
|
m->tuple_objprm[adr+j] = (mjtNum)ptu->objprm_[j];
|
|
}
|
|
|
|
// advance address counter
|
|
adr += m->tuple_size[i];
|
|
}
|
|
|
|
// copy keyframe data
|
|
for (int i=0; i < nkey; i++) {
|
|
// copy data
|
|
m->key_time[i] = (mjtNum)keys_[i]->time;
|
|
mjuu_copyvec(m->key_qpos+i*nq, keys_[i]->qpos_.data(), nq);
|
|
mjuu_copyvec(m->key_qvel+i*nv, keys_[i]->qvel_.data(), nv);
|
|
if (na) {
|
|
mjuu_copyvec(m->key_act+i*na, keys_[i]->act_.data(), na);
|
|
}
|
|
if (nmocap) {
|
|
mjuu_copyvec(m->key_mpos + i*3*nmocap, keys_[i]->mpos_.data(), 3*nmocap);
|
|
mjuu_copyvec(m->key_mquat + i*4*nmocap, keys_[i]->mquat_.data(), 4*nmocap);
|
|
}
|
|
|
|
// normalize quaternions in m->key_qpos
|
|
for (int j=0; j < m->njnt; j++) {
|
|
if (m->jnt_type[j] == mjJNT_BALL || m->jnt_type[j] == mjJNT_FREE) {
|
|
mjuu_normvec(m->key_qpos+i*nq+m->jnt_qposadr[j]+3*(m->jnt_type[j] == mjJNT_FREE), 4);
|
|
}
|
|
}
|
|
|
|
// normalize quaternions in m->key_mquat
|
|
for (int j=0; j < nmocap; j++) {
|
|
mjuu_normvec(m->key_mquat+i*4*nmocap+4*j, 4);
|
|
}
|
|
|
|
mjuu_copyvec(m->key_ctrl+i*nu, keys_[i]->ctrl_.data(), nu);
|
|
}
|
|
|
|
// save qpos0 in user model (to recognize changed key_qpos in write)
|
|
qpos0.resize(nq);
|
|
body_pos0.resize(3*nbody);
|
|
body_quat0.resize(4*nbody);
|
|
mjuu_copyvec(qpos0.data(), m->qpos0, nq);
|
|
mjuu_copyvec(body_pos0.data(), m->body_pos, 3*nbody);
|
|
mjuu_copyvec(body_quat0.data(), m->body_quat, 4*nbody);
|
|
}
|
|
|
|
|
|
|
|
// finalize simple bodies/dofs including tendon information
|
|
void mjCModel::FinalizeSimple(mjModel* m) {
|
|
// demote bodies affected by inertia-bearing tendon to non-simple
|
|
for (int i=0; i < ntendon; i++) {
|
|
if (m->tendon_armature[i] == 0) {
|
|
continue;
|
|
}
|
|
int adr = m->tendon_adr[i];
|
|
int num = m->tendon_num[i];
|
|
for (int j=adr; j < adr+num; j++) {
|
|
int objid = m->wrap_objid[j];
|
|
if (m->wrap_type[j] == mjWRAP_SITE) {
|
|
m->body_simple[m->site_bodyid[objid]] = 0;
|
|
}
|
|
if (m->wrap_type[j] == mjWRAP_CYLINDER || m->wrap_type[j] == mjWRAP_SPHERE) {
|
|
m->body_simple[m->geom_bodyid[objid]] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set dof_simplenum
|
|
int count = 0;
|
|
for (int i=nv-1; i >= 0; i--) {
|
|
if (m->body_simple[m->dof_bodyid[i]]) {
|
|
count++; // increment counter
|
|
} else {
|
|
count = 0; // reset
|
|
}
|
|
m->dof_simplenum[i] = count;
|
|
}
|
|
|
|
// recompute nC given {dof_simplenum, dof_parentid}, validate
|
|
int nOD = 0; // number of non-simple off-diagonal parent dofs
|
|
for (int i=0; i < nv; i++) {
|
|
// count ancestor (off-diagonal) dofs
|
|
if (!m->dof_simplenum[i]) {
|
|
int j = i;
|
|
while (j >= 0) {
|
|
if (j != i) nOD++;
|
|
j = m->dof_parentid[j];
|
|
}
|
|
}
|
|
}
|
|
int nC_post = nOD + nv;
|
|
if (nC_post != nC) throw mjCError(0, "nC mismatch: pre %d, post %d", nullptr, nC, nC_post);
|
|
}
|
|
|
|
|
|
|
|
// save the current state
|
|
template <class T>
|
|
void mjCModel::SaveState(const std::string& state_name, const T* qpos, const T* qvel, const T* act,
|
|
const T* ctrl, const T* mpos, const T* mquat) {
|
|
for (auto joint : joints_) {
|
|
if (joint->qposadr_ < -1 || joint->dofadr_ < -1) {
|
|
throw mjCError(nullptr, "SaveState: joint %s has invalid address", joint->name.c_str());
|
|
}
|
|
if (qpos && joint->qposadr_ != -1) {
|
|
mjuu_copyvec(joint->qpos(state_name), qpos + joint->qposadr_, joint->nq());
|
|
}
|
|
if (qvel && joint->dofadr_ != -1) {
|
|
mjuu_copyvec(joint->qvel(state_name), qvel + joint->dofadr_, joint->nv());
|
|
}
|
|
}
|
|
|
|
for (unsigned int i=0; i < actuators_.size(); i++) {
|
|
auto actuator = actuators_[i];
|
|
if (actuator->actadr_ != -1 && actuator->actdim_ != -1 && act) {
|
|
actuator->act(state_name).assign(actuator->actdim_, 0);
|
|
mjuu_copyvec(actuator->act(state_name).data(), act + actuator->actadr_, actuator->actdim_);
|
|
}
|
|
if (ctrl) {
|
|
actuator->ctrl(state_name) = ctrl[i];
|
|
}
|
|
}
|
|
|
|
for (auto body : bodies_) {
|
|
if (!body->spec.mocap || body->mocapid == -1) {
|
|
continue;
|
|
}
|
|
if (mpos) {
|
|
mjuu_copyvec(body->mpos(state_name), mpos + 3*body->mocapid, 3);
|
|
}
|
|
if (mquat) {
|
|
mjuu_copyvec(body->mquat(state_name), mquat + 4*body->mocapid, 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// clear existing data
|
|
void mjCModel::MakeData(const mjModel* m, mjData** dest) {
|
|
mj_makeRawData(dest, m);
|
|
mjData* d = *dest;
|
|
if (d) {
|
|
mj_initPlugin(m, d);
|
|
mj_resetData(m, d);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// restore the previous state
|
|
template <class T>
|
|
void mjCModel::RestoreState(const std::string& state_name, const mjtNum* pos0,
|
|
const mjtNum* mpos0, const mjtNum* mquat0, T* qpos,
|
|
T* qvel, T* act, T* ctrl, T* mpos, T* mquat) {
|
|
for (auto joint : joints_) {
|
|
if (qpos) {
|
|
if (mjuu_defined(joint->qpos(state_name)[0])) {
|
|
mjuu_copyvec(qpos + joint->qposadr_, joint->qpos(state_name), joint->nq());
|
|
} else {
|
|
mjuu_copyvec(qpos + joint->qposadr_, pos0 + joint->qposadr_, joint->nq());
|
|
}
|
|
}
|
|
if (mjuu_defined(joint->qvel(state_name)[0]) && qvel) {
|
|
mjuu_copyvec(qvel + joint->dofadr_, joint->qvel(state_name), joint->nv());
|
|
}
|
|
}
|
|
|
|
// restore act
|
|
for (unsigned int i=0; i < actuators_.size(); i++) {
|
|
auto actuator = actuators_[i];
|
|
if (!actuator->act(state_name).empty() && mjuu_defined(actuator->act(state_name)[0]) && act) {
|
|
mjuu_copyvec(act + actuator->actadr_, actuator->act(state_name).data(), actuator->actdim_);
|
|
}
|
|
if (ctrl) {
|
|
ctrl[i] = mjuu_defined(actuator->ctrl(state_name)) ? actuator->ctrl(state_name) : 0;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i=0; i < bodies_.size(); i++) {
|
|
auto body = bodies_[i];
|
|
if (!body->spec.mocap) {
|
|
continue;
|
|
}
|
|
if (mpos) {
|
|
if (mjuu_defined(body->mpos(state_name)[0])) {
|
|
mjuu_copyvec(mpos + 3*body->mocapid, body->mpos(state_name), 3);
|
|
} else {
|
|
mjuu_copyvec(mpos + 3*body->mocapid, mpos0 + 3*i, 3);
|
|
}
|
|
}
|
|
if (mquat) {
|
|
if (mjuu_defined(body->mquat(state_name)[0])) {
|
|
mjuu_copyvec(mquat + 4*body->mocapid, body->mquat(state_name), 4);
|
|
} else {
|
|
mjuu_copyvec(mquat + 4*body->mocapid, mquat0 + 4*i, 4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// force explicit instantiations
|
|
template void mjCModel::SaveState<mjtNum>(
|
|
const std::string& name, const mjtNum* qpos, const mjtNum* qvel, const mjtNum* act,
|
|
const mjtNum* ctrl, const mjtNum* mpos, const mjtNum* mquat);
|
|
|
|
template void mjCModel::RestoreState<mjtNum>(
|
|
const std::string& name, const mjtNum* qpos0, const mjtNum* mpos0, const mjtNum* mquat0,
|
|
mjtNum* qpos, mjtNum* qvel, mjtNum* act, mjtNum* ctrl, mjtNum* mpos, mjtNum* mquat);
|
|
|
|
|
|
|
|
// resolve keyframe references
|
|
void mjCModel::StoreKeyframes(mjCModel* dest) {
|
|
if (this != dest && !key_pending_.empty()) {
|
|
mju_warning(
|
|
"Child model has pending keyframes. They will not be namespaced correctly. "
|
|
"To prevent this, compile the child model before attaching it again.");
|
|
}
|
|
|
|
// do not change compilation quantities in case the user wants to recompile preserving the state
|
|
if (!compiled) {
|
|
SaveDofOffsets(/*computesize=*/true);
|
|
ComputeReference();
|
|
}
|
|
|
|
// save keyframe info and resize keyframes
|
|
for (auto& key : keys_) {
|
|
mjKeyInfo info;
|
|
info.name = prefix + key->name + suffix;
|
|
info.time = key->spec.time;
|
|
info.qpos = !key->spec_qpos_.empty();
|
|
info.qvel = !key->spec_qvel_.empty();
|
|
info.act = !key->spec_act_.empty();
|
|
info.ctrl = !key->spec_ctrl_.empty();
|
|
info.mpos = !key->spec_mpos_.empty();
|
|
info.mquat = !key->spec_mquat_.empty();
|
|
dest->key_pending_.push_back(info);
|
|
if (!key->spec_qpos_.empty() && key->spec_qpos_.size() != nq) {
|
|
throw mjCError(nullptr, "Keyframe '%s' has invalid qpos size, got %d, should be %d",
|
|
key->name.c_str(), key->spec_qpos_.size(), nq);
|
|
}
|
|
if (!key->spec_qvel_.empty() && key->spec_qvel_.size() != nv) {
|
|
throw mjCError(nullptr, "Keyframe %s has invalid qvel size, got %d, should be %d",
|
|
key->name.c_str(), key->spec_qvel_.size(), nv);
|
|
}
|
|
if (!key->spec_act_.empty() && key->spec_act_.size() != na) {
|
|
throw mjCError(nullptr, "Keyframe %s has invalid act size, got %d, should be %d",
|
|
key->name.c_str(), key->spec_act_.size(), na);
|
|
}
|
|
if (!key->spec_ctrl_.empty() && key->spec_ctrl_.size() != nu) {
|
|
throw mjCError(nullptr, "Keyframe %s has invalid ctrl size, got %d, should be %d",
|
|
key->name.c_str(), key->spec_ctrl_.size(), nu);
|
|
}
|
|
if (!key->spec_mpos_.empty() && key->spec_mpos_.size() != 3*nmocap) {
|
|
throw mjCError(nullptr, "Keyframe %s has invalid mpos size, got %d, should be %d",
|
|
key->name.c_str(), key->spec_mpos_.size(), 3*nmocap);
|
|
}
|
|
if (!key->spec_mquat_.empty() && key->spec_mquat_.size() != 4*nmocap) {
|
|
throw mjCError(nullptr, "Keyframe %s has invalid mquat size, got %d, should be %d",
|
|
key->name.c_str(), key->spec_mquat_.size(), 4*nmocap);
|
|
}
|
|
SaveState(info.name, key->spec_qpos_.data(), key->spec_qvel_.data(),
|
|
key->spec_act_.data(), key->spec_ctrl_.data(),
|
|
key->spec_mpos_.data(), key->spec_mquat_.data());
|
|
}
|
|
|
|
if (!compiled) {
|
|
nq = nv = na = nu = nmocap = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//------------------------------- FUSE STATIC ------------------------------------------------------
|
|
|
|
template <class T>
|
|
static void makelistid(std::vector<T*>& dest, std::vector<T*>& source) {
|
|
for (int i=0; i < source.size(); i++) {
|
|
source[i]->id = (int)dest.size();
|
|
dest.push_back(source[i]);
|
|
}
|
|
}
|
|
|
|
// change frame to parent body
|
|
static void changeframe(double childpos[3], double childquat[4],
|
|
const double bodypos[3], const double bodyquat[4]) {
|
|
double pos[3], quat[4];
|
|
mjuu_copyvec(pos, bodypos, 3);
|
|
mjuu_copyvec(quat, bodyquat, 4);
|
|
mjuu_frameaccum(pos, quat, childpos, childquat);
|
|
mjuu_copyvec(childpos, pos, 3);
|
|
mjuu_copyvec(childquat, quat, 4);
|
|
}
|
|
|
|
|
|
|
|
// reindex elements during fuse
|
|
void mjCModel::FuseReindex(mjCBody* body) {
|
|
// set parentid and weldid of children
|
|
for (int i=0; i < body->bodies.size(); i++) {
|
|
body->bodies[i]->parent = body;
|
|
body->bodies[i]->weldid = (!body->bodies[i]->joints.empty() ?
|
|
body->bodies[i]->id : body->weldid);
|
|
}
|
|
|
|
makelistid(joints_, body->joints);
|
|
makelistid(geoms_, body->geoms);
|
|
makelistid(sites_, body->sites);
|
|
|
|
// process children recursively
|
|
for (int i=0; i < body->bodies.size(); i++) {
|
|
FuseReindex(body->bodies[i]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
void mjCModel::ReassignChild(std::vector<T*>& dest, std::vector<T*>& list,
|
|
mjCBody* parent, mjCBody* body) {
|
|
for (int j=0; j < list.size(); j++) {
|
|
// assign
|
|
list[j]->body = parent;
|
|
dest.push_back(list[j]);
|
|
|
|
// change frame
|
|
changeframe(list[j]->pos, list[j]->quat, body->pos, body->quat);
|
|
}
|
|
list.clear();
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
void mjCModel::ResolveReferences(std::vector<T*>& list, mjCBody* body) {
|
|
for (auto& item : list) {
|
|
item->CopyFromSpec();
|
|
item->ResolveReferences(this);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template <>
|
|
void mjCModel::ResolveReferences(std::vector<mjCSensor*>& list, mjCBody* body) {
|
|
for (auto& item : list) {
|
|
item->CopyFromSpec();
|
|
item->ResolveReferences(this);
|
|
}
|
|
for (mjCSensor* sensor : list) {
|
|
if (sensor->objtype == mjOBJ_SITE &&
|
|
(sensor->type == mjSENS_FORCE || sensor->type == mjSENS_TORQUE) &&
|
|
static_cast<mjCSite*>(sensor->obj)->body == body) {
|
|
throw mjCError(sensor, "cannot fuse a body used by a force/torque sensor");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// fuse static bodies with their parent
|
|
void mjCModel::FuseStatic(void) {
|
|
for (int i=1; i < bodies_.size(); i++) {
|
|
// check if the body can be fused
|
|
if (!bodies_[i]->name.empty()) {
|
|
ids[mjOBJ_BODY].erase(bodies_[i]->name);
|
|
|
|
// try to resolve references without the name of this body, if it fails, skip
|
|
try {
|
|
ResolveReferences(cameras_);
|
|
ResolveReferences(lights_);
|
|
ResolveReferences(skins_);
|
|
ResolveReferences(pairs_);
|
|
ResolveReferences(excludes_);
|
|
ResolveReferences(equalities_);
|
|
ResolveReferences(tendons_);
|
|
ResolveReferences(actuators_);
|
|
ResolveReferences(sensors_, bodies_[i]);
|
|
ResolveReferences(tuples_);
|
|
} catch (mjCError err) {
|
|
ids[mjOBJ_BODY].insert({bodies_[i]->name, i});
|
|
continue;
|
|
}
|
|
|
|
// put body back the body name in the map
|
|
ids[mjOBJ_BODY].insert({bodies_[i]->name, i});
|
|
}
|
|
|
|
// get body and parent
|
|
mjCBody* body = bodies_[i];
|
|
mjCBody* par = body->parent;
|
|
|
|
// skip if body has joints or mocap
|
|
if (!body->joints.empty() || body->mocap) {
|
|
continue;
|
|
}
|
|
|
|
//------------- add mass and inertia (if parent not world)
|
|
if (body->parent && body->parent->name != "world" && body->mass >= mjMINVAL) {
|
|
par->AccumulateInertia(body);
|
|
}
|
|
|
|
//------------- replace body with its children in parent body list
|
|
|
|
// change frames of child bodies
|
|
for (int j=0; j < body->bodies.size(); j++)
|
|
changeframe(body->bodies[j]->pos, body->bodies[j]->quat,
|
|
body->pos, body->quat);
|
|
|
|
// find body in parent list, insert children before it
|
|
bool found = false;
|
|
for (auto iter=par->bodies.begin(); iter != par->bodies.end(); iter++) {
|
|
if (*iter == body) {
|
|
par->bodies.insert(iter, body->bodies.begin(), body->bodies.end());
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
mju_error("Internal error: FuseStatic: body not found");
|
|
}
|
|
|
|
// find body in parent list, erase
|
|
found = false;
|
|
for (auto iter=par->bodies.begin(); iter != par->bodies.end(); iter++) {
|
|
if (*iter == body) {
|
|
par->bodies.erase(iter);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
mju_error("Internal error: FuseStatic: body not found");
|
|
}
|
|
|
|
//------------- assign geoms and sites to parent, change frames
|
|
|
|
ReassignChild(par->geoms, body->geoms, par, body);
|
|
ReassignChild(par->sites, body->sites, par, body);
|
|
|
|
//------------- remove from global body list, reduce global counts
|
|
|
|
// find in global and erase
|
|
found = false;
|
|
for (auto iter=bodies_.begin(); iter != bodies_.end(); iter++) {
|
|
if (*iter == body) {
|
|
bodies_.erase(iter);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
mju_error("Internal error: FuseStatic: body not found");
|
|
}
|
|
|
|
// reduce counts
|
|
nbody--;
|
|
nnames -= ((int)body->name.length() + 1);
|
|
|
|
//------------- re-index bodies, joints, geoms, sites
|
|
|
|
// body ids
|
|
for (int j=0; j < bodies_.size(); j++) {
|
|
bodies_[j]->id = j;
|
|
}
|
|
|
|
// everything else
|
|
joints_.clear();
|
|
geoms_.clear();
|
|
sites_.clear();
|
|
FuseReindex(bodies_[0]);
|
|
|
|
// recompute parent contype, conaffinity, and margin
|
|
par->contype = par->conaffinity = 0;
|
|
par->margin = 0;
|
|
for (const auto& geom : par->geoms) {
|
|
par->contype |= geom->contype;
|
|
par->conaffinity |= geom->conaffinity;
|
|
par->margin = std::max(par->margin, geom->margin);
|
|
}
|
|
|
|
// recompute BVH
|
|
int nbvhfuse = body->tree.Nbvh() + par->tree.Nbvh();
|
|
par->ComputeBVH();
|
|
nbvhstatic += par->tree.Nbvh() - nbvhfuse;
|
|
nbvh += par->tree.Nbvh() - nbvhfuse;
|
|
|
|
//------------- delete body (without deleting children)
|
|
|
|
// remove body name from map
|
|
if (!body->name.empty()) {
|
|
ids[mjOBJ_BODY].erase(body->name);
|
|
}
|
|
|
|
// delete allocation
|
|
body->bodies.clear();
|
|
delete body;
|
|
|
|
// check index i again (we have a new body at this index)
|
|
i--;
|
|
}
|
|
|
|
// remove empty names
|
|
ProcessList_(ids, bodies_, mjOBJ_BODY, /*checkrepeat=*/true);
|
|
}
|
|
|
|
|
|
|
|
//------------------------------- COMPILER ---------------------------------------------------------
|
|
|
|
// signature comparisons
|
|
static int comparePair(mjCPair* el1, mjCPair* el2) {
|
|
return el1->GetSignature() < el2->GetSignature();
|
|
}
|
|
static int compareBodyPair(mjCBodyPair* el1, mjCBodyPair* el2) {
|
|
return el1->GetSignature() < el2->GetSignature();
|
|
}
|
|
|
|
|
|
// reassign ids
|
|
template <class T>
|
|
static void reassignid(vector<T*>& list) {
|
|
for (int i=0; i < (int)list.size(); i++) {
|
|
list[i]->id = i;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// set object ids, check for repeated names
|
|
void mjCModel::ProcessLists(bool checkrepeat) {
|
|
for (int i = 0; i < mjNOBJECT; i++) {
|
|
if (i != mjOBJ_XBODY && object_lists_[i]) {
|
|
ids[i].clear();
|
|
ProcessList_(ids, *object_lists_[i], (mjtObj) i, checkrepeat);
|
|
}
|
|
}
|
|
|
|
// check repeated names in meta elements
|
|
ProcessList_(ids, frames_, mjOBJ_FRAME, checkrepeat);
|
|
}
|
|
|
|
|
|
|
|
// set ids, check for repeated names
|
|
template <class T>
|
|
void mjCModel::ProcessList_(mjListKeyMap& ids, vector<T*>& list,
|
|
mjtObj type, bool checkrepeat) {
|
|
// assign ids for regular elements
|
|
if (type < mjNOBJECT) {
|
|
for (size_t i=0; i < list.size(); i++) {
|
|
// check for incompatible id setting; SHOULD NOT OCCUR
|
|
if (list[i]->id != -1 && list[i]->id != i) {
|
|
throw mjCError(list[i], "incompatible id in %s array, position %d", mju_type2Str(type), i);
|
|
}
|
|
|
|
// id equals position in array
|
|
list[i]->id = i;
|
|
|
|
// add to ids map
|
|
ids[type][list[i]->name] = i;
|
|
}
|
|
}
|
|
|
|
// check for repeated names
|
|
if (checkrepeat) {
|
|
CheckRepeat(type);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// check for repeated names in list
|
|
void mjCModel::CheckRepeat(mjtObj type) {
|
|
std::vector<mjCBase*>* list = nullptr;
|
|
if (type < mjNOBJECT) {
|
|
list = object_lists_[type];
|
|
} else if (type == mjOBJ_FRAME) {
|
|
list = (std::vector<mjCBase*>*) &frames_;
|
|
}
|
|
|
|
// created vectors with all names
|
|
vector<string> allnames;
|
|
for (size_t i=0; i < list->size(); i++) {
|
|
if (!(*list)[i]->name.empty()) {
|
|
allnames.push_back((*list)[i]->name);
|
|
}
|
|
}
|
|
|
|
// sort and check for duplicates
|
|
if (allnames.size() > 1) {
|
|
std::sort(allnames.begin(), allnames.end());
|
|
auto adjacent = std::adjacent_find(allnames.begin(), allnames.end());
|
|
if (adjacent != allnames.end()) {
|
|
string msg = "repeated name '" + *adjacent + "' in " + mju_type2Str(type);
|
|
throw mjCError(nullptr, "%s", msg.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// error handler for low-level engine
|
|
constexpr int kErrorBufferSize = 500;
|
|
static thread_local std::jmp_buf error_jmp_buf;
|
|
static thread_local char errortext[kErrorBufferSize] = "";
|
|
static void errorhandler(const char* msg) {
|
|
mju::strcpy_arr(errortext, msg);
|
|
std::longjmp(error_jmp_buf, 1);
|
|
}
|
|
|
|
|
|
// warning handler for low-level engine
|
|
static thread_local char warningtext[kErrorBufferSize] = ""; // top-level warning buffer
|
|
static thread_local std::string* local_warningtext_ptr = nullptr; // sub-thread warning buffer
|
|
static void warninghandler(const char* msg) {
|
|
if (local_warningtext_ptr) {
|
|
*local_warningtext_ptr = msg;
|
|
} else {
|
|
mju::strcpy_arr(warningtext, msg);
|
|
}
|
|
}
|
|
|
|
|
|
// compiler
|
|
mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
|
|
if (compiled) {
|
|
Clear();
|
|
}
|
|
|
|
CopyFromSpec();
|
|
|
|
// The volatile keyword is necessary to prevent a possible memory leak due to
|
|
// an interaction between longjmp and compiler optimization. Specifically, at
|
|
// the point where the setjmp takes places, these pointers have never been
|
|
// reassigned from their nullptr initialization. Without the volatile keyword,
|
|
// the compiler is free to assume that these pointers remain nullptr when the
|
|
// setjmp returns, and therefore to pass nullptr directly to the
|
|
// mj_deleteModel and mj_deleteData calls in the subsequent catch block,
|
|
// without ever reading the actual pointer values.
|
|
mjModel* volatile model = (m && *m) ? *m : nullptr;
|
|
mjData* volatile data = nullptr;
|
|
|
|
// save error and warning handlers
|
|
void (*save_error)(const char*) = _mjPRIVATE__get_tls_error_fn();
|
|
void (*save_warning)(const char*) = _mjPRIVATE__get_tls_warning_fn();
|
|
|
|
// install error and warning handlers, clear error and warning
|
|
_mjPRIVATE__set_tls_error_fn(errorhandler);
|
|
_mjPRIVATE__set_tls_warning_fn(warninghandler);
|
|
|
|
errInfo = mjCError();
|
|
warningtext[0] = 0;
|
|
|
|
try {
|
|
if (attached_) {
|
|
throw mjCError(0, "cannot compile child spec if attached by reference to a parent spec");
|
|
}
|
|
if (setjmp(error_jmp_buf) != 0) {
|
|
// TryCompile resulted in an mju_error which was converted to a longjmp.
|
|
std::string error_msg = errortext;
|
|
// also include the last warning that was issued. this is useful for
|
|
// warnings that came out of plugin implementations.
|
|
if (warningtext[0]) {
|
|
error_msg += "\n";
|
|
error_msg += warningtext;
|
|
}
|
|
throw mjCError(0, "engine error: %s", error_msg.c_str());
|
|
}
|
|
TryCompile(*const_cast<mjModel**>(&model), *const_cast<mjData**>(&data), vfs);
|
|
} catch (mjCError err) {
|
|
// deallocate everything allocated in Compile
|
|
mj_deleteModel(model);
|
|
mj_deleteData(data);
|
|
Clear();
|
|
|
|
// save error info
|
|
errInfo = err;
|
|
|
|
// restore handler, return 0
|
|
_mjPRIVATE__set_tls_error_fn(save_error);
|
|
_mjPRIVATE__set_tls_warning_fn(save_warning);
|
|
return nullptr;
|
|
}
|
|
|
|
// restore error handler, mark as compiled, return mjModel
|
|
_mjPRIVATE__set_tls_error_fn(save_error);
|
|
_mjPRIVATE__set_tls_warning_fn(save_warning);
|
|
compiled = true;
|
|
return model;
|
|
}
|
|
|
|
|
|
|
|
// mesh compilation function to be used in threads
|
|
void CompileMesh(mjCMesh* mesh, const mjVFS* vfs, std::exception_ptr& exception,
|
|
std::mutex& exception_mutex, std::string* warningtext) {
|
|
// set warning text buffer to this local thread
|
|
local_warningtext_ptr = warningtext;
|
|
auto previous_handler = _mjPRIVATE__get_tls_warning_fn();
|
|
_mjPRIVATE__set_tls_warning_fn(warninghandler);
|
|
|
|
// compile the mesh, catch exception to be rethrown later
|
|
try {
|
|
mesh->Compile(vfs);
|
|
} catch (...) {
|
|
std::lock_guard<std::mutex> lock(exception_mutex);
|
|
if (!exception) {
|
|
exception = std::current_exception();
|
|
}
|
|
}
|
|
|
|
// restore warning handler to top-level
|
|
_mjPRIVATE__set_tls_warning_fn(previous_handler);
|
|
local_warningtext_ptr = nullptr;
|
|
}
|
|
|
|
|
|
|
|
// multi-threaded mesh compilation
|
|
void mjCModel::CompileMeshes(const mjVFS* vfs) {
|
|
std::vector<std::thread> threads;
|
|
int nmesh = meshes_.size();
|
|
int hardware_threads = std::thread::hardware_concurrency() / 2;
|
|
int maxthread = std::min(nmesh, std::min(kMaxCompilerThreads, hardware_threads));
|
|
int nthread = std::max(1, maxthread);
|
|
|
|
threads.reserve(nthread);
|
|
|
|
// holds an exception thrown by a worker thread
|
|
std::exception_ptr exception;
|
|
std::mutex except_mutex;
|
|
|
|
std::atomic_int next_mesh = 0;
|
|
std::vector<std::string> mesh_warningtext(nmesh);
|
|
for (int i = 0; i < nthread; ++i) {
|
|
threads.emplace_back([&] {
|
|
for (int meshid = next_mesh++; meshid < nmesh; meshid = next_mesh++) {
|
|
auto& mesh = meshes_[meshid];
|
|
CompileMesh(mesh, vfs, exception, except_mutex,
|
|
&mesh_warningtext[meshid]);
|
|
}
|
|
});
|
|
}
|
|
|
|
// join threads
|
|
for (auto& thread : threads) {
|
|
if (thread.joinable()) {
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
// concatenate all warnings from threads, copy into warningtext
|
|
std::string concatenated_warnings;
|
|
bool has_warning = false;
|
|
for (int i = 0; i < nmesh; i++) {
|
|
if (!mesh_warningtext[i].empty()) {
|
|
if (has_warning) {
|
|
concatenated_warnings += "\n";
|
|
}
|
|
concatenated_warnings += mesh_warningtext[i];
|
|
has_warning = true;
|
|
}
|
|
}
|
|
mju::strcpy_arr(warningtext, concatenated_warnings.c_str());
|
|
|
|
// if exception was caught, rethrow it
|
|
if (exception) {
|
|
std::rethrow_exception(exception);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// compute qpos0
|
|
void mjCModel::ComputeReference() {
|
|
int b = 0;
|
|
qpos0.resize(nq);
|
|
body_pos0.resize(3*bodies_.size());
|
|
body_quat0.resize(4*bodies_.size());
|
|
for (auto body : bodies_) {
|
|
mjuu_copyvec(body_pos0.data()+3*b, body->spec.pos, 3);
|
|
mjuu_copyvec(body_quat0.data()+4*b, body->spec.quat, 4);
|
|
for (auto joint : body->joints) {
|
|
switch (joint->type) {
|
|
case mjJNT_FREE:
|
|
mjuu_copyvec(qpos0.data()+joint->qposadr_, body->spec.pos, 3);
|
|
mjuu_copyvec(qpos0.data()+joint->qposadr_+3, body->spec.quat, 4);
|
|
break;
|
|
|
|
case mjJNT_BALL:
|
|
mjuu_setvec(qpos0.data()+joint->qposadr_, 1, 0, 0, 0);
|
|
break;
|
|
|
|
case mjJNT_SLIDE:
|
|
case mjJNT_HINGE:
|
|
qpos0[joint->qposadr_] = (mjtNum)joint->spec.ref;
|
|
break;
|
|
|
|
default:
|
|
throw mjCError(joint, "unknown joint type");
|
|
}
|
|
}
|
|
b++;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// resize keyframes in the model
|
|
void mjCModel::ExpandAllKeyframes() {
|
|
if (keys_.empty()) {
|
|
return;
|
|
}
|
|
SaveDofOffsets(/*computesize=*/true);
|
|
ComputeReference();
|
|
for (auto* key : keys_) {
|
|
ExpandKeyframe(key, qpos0.data(), body_pos0.data(), body_quat0.data());
|
|
}
|
|
nq = nv = na = nu = nmocap = 0;
|
|
}
|
|
|
|
|
|
|
|
// resizes a keyframe, filling in missing values
|
|
void mjCModel::ExpandKeyframe(mjCKey* key, const mjtNum* qpos0_,
|
|
const mjtNum* bpos, const mjtNum* bquat) {
|
|
if (!key->spec_qpos_.empty() && nq > key->spec_qpos_.size()) {
|
|
int nq0 = key->spec_qpos_.size();
|
|
key->spec_qpos_.resize(nq);
|
|
for (int i=nq0; i < nq; i++) {
|
|
key->spec_qpos_[i] = (double)qpos0_[i];
|
|
}
|
|
}
|
|
if (!key->spec_qvel_.empty() && nv > key->spec_qvel_.size()) {
|
|
key->spec_qvel_.resize(nv);
|
|
}
|
|
if (!key->spec_act_.empty() && na > key->spec_act_.size()) {
|
|
key->spec_act_.resize(na);
|
|
}
|
|
if (!key->spec_ctrl_.empty() && nu > key->spec_ctrl_.size()) {
|
|
key->spec_ctrl_.resize(nu);
|
|
}
|
|
if (!key->spec_mpos_.empty() && nmocap > key->spec_mpos_.size() / 3) {
|
|
int nmocap0 = key->spec_mpos_.size() / 3;
|
|
key->spec_mpos_.resize(3*nmocap);
|
|
for (unsigned int j = 0; j < bodies_.size(); j++) {
|
|
if (bodies_[j]->mocapid < nmocap0) {
|
|
continue;
|
|
}
|
|
int i = bodies_[j]->mocapid;
|
|
key->spec_mpos_[3*i+0] = (double)bpos[3*j+0];
|
|
key->spec_mpos_[3*i+1] = (double)bpos[3*j+1];
|
|
key->spec_mpos_[3*i+2] = (double)bpos[3*j+2];
|
|
}
|
|
}
|
|
if (!key->spec_mquat_.empty() && nmocap > key->spec_mquat_.size() / 4) {
|
|
int nmocap0 = key->spec_mquat_.size() / 4;
|
|
key->spec_mquat_.resize(4*nmocap);
|
|
for (unsigned int j = 0; j < bodies_.size(); j++) {
|
|
if (bodies_[j]->mocapid < nmocap0) {
|
|
continue;
|
|
}
|
|
int i = bodies_[j]->mocapid;
|
|
key->spec_mquat_[4*i+0] = (double)bquat[4*j+0];
|
|
key->spec_mquat_[4*i+1] = (double)bquat[4*j+1];
|
|
key->spec_mquat_[4*i+2] = (double)bquat[4*j+2];
|
|
key->spec_mquat_[4*i+3] = (double)bquat[4*j+3];
|
|
}
|
|
}
|
|
}
|
|
|
|
// convert pending keyframes info to actual keyframes
|
|
void mjCModel::ResolveKeyframes(const mjModel* m) {
|
|
// store dof offsets in joints and actuators
|
|
SaveDofOffsets();
|
|
|
|
// create new keyframes, fill in missing default values
|
|
for (const auto& info : key_pending_) {
|
|
mjCKey* key = (mjCKey*)FindObject(mjOBJ_KEY, info.name);
|
|
key->name = info.name;
|
|
key->spec.time = info.time;
|
|
if (info.qpos) key->spec_qpos_.assign(nq, 0);
|
|
if (info.qvel) key->spec_qvel_.assign(nv, 0);
|
|
if (info.act) key->spec_act_.assign(na, 0);
|
|
if (info.ctrl) key->spec_ctrl_.assign(nu, 0);
|
|
if (info.mpos) key->spec_mpos_.assign(3*nmocap, 0);
|
|
if (info.mquat) key->spec_mquat_.assign(4*nmocap, 0);
|
|
RestoreState(info.name, m->qpos0, m->body_pos, m->body_quat,
|
|
key->spec_qpos_.data(), key->spec_qvel_.data(),
|
|
key->spec_act_.data(), key->spec_ctrl_.data(),
|
|
key->spec_mpos_.data(), key->spec_mquat_.data());
|
|
}
|
|
|
|
// the attached keyframes have been copied into the model
|
|
key_pending_.clear();
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
|
// check if nan test works
|
|
double test = mjNAN;
|
|
if (mjuu_defined(test)) {
|
|
throw mjCError(0, "NaN test does not work for present compiler/options");
|
|
}
|
|
|
|
// check for joints in world body
|
|
if (!bodies_[0]->joints.empty()) {
|
|
throw mjCError(0, "joint found in world body");
|
|
}
|
|
|
|
// check for too many body+flex
|
|
if (bodies_.size()+flexes_.size() >= 65534) {
|
|
throw mjCError(0, "number of bodies plus flexes must be less than 65534");
|
|
}
|
|
|
|
// append directory separator
|
|
if (!meshdir_.empty()) {
|
|
int n = meshdir_.length();
|
|
if (meshdir_[n-1] != '/' && meshdir_[n-1] != '\\') {
|
|
meshdir_ += '/';
|
|
}
|
|
}
|
|
if (!texturedir_.empty()) {
|
|
int n = texturedir_.length();
|
|
if (texturedir_[n-1] != '/' && texturedir_[n-1] != '\\') {
|
|
texturedir_ += '/';
|
|
}
|
|
}
|
|
|
|
// add missing keyframes
|
|
for (int i=keys_.size(); i < nkey; i++) {
|
|
AddKey();
|
|
}
|
|
|
|
// clear subtreedofs
|
|
for (int i=0; i < bodies_.size(); i++) {
|
|
bodies_[i]->subtreedofs = 0;
|
|
}
|
|
|
|
// initialize spec signature (needed if the user changed sensor or joint types)
|
|
spec.element->signature = Signature();
|
|
|
|
// fill missing names and check that they are all filled
|
|
for (const auto& asset : meshes_) asset->CopyFromSpec();
|
|
for (const auto& asset : skins_) asset->CopyFromSpec();
|
|
for (const auto& asset : hfields_) asset->CopyFromSpec();
|
|
for (const auto& asset : textures_) asset->CopyFromSpec();
|
|
CheckEmptyNames();
|
|
|
|
// resize keyframes in case the spec was edited after the last attach
|
|
ExpandAllKeyframes();
|
|
|
|
// create pending keyframes
|
|
for (const auto& info : key_pending_) {
|
|
mjCKey* key = AddKey();
|
|
key->name = info.name;
|
|
}
|
|
|
|
// set object ids, check for repeated names
|
|
ProcessLists();
|
|
|
|
// delete visual assets
|
|
if (compiler.discardvisual) {
|
|
DeleteAll(materials_);
|
|
DeleteTexcoord(flexes_);
|
|
DeleteTexcoord(meshes_);
|
|
DeleteAll(textures_);
|
|
}
|
|
|
|
// map names to asset references
|
|
IndexAssets(/*discard=*/false);
|
|
|
|
// compile pairs for convex hull check
|
|
// TODO(quaglino): Consolidate the two calls to pair->Compile() in TryCompile.
|
|
for (auto pair : pairs_) pair->Compile();
|
|
|
|
// mark meshes that need convex hull
|
|
for (int i=0; i < geoms_.size(); i++) {
|
|
bool is_in_pair = false;
|
|
for (const mjCPair* pair : pairs_) {
|
|
if ((pair->geom1 && pair->geom1->id == geoms_[i]->id) ||
|
|
(pair->geom2 && pair->geom2->id == geoms_[i]->id)) {
|
|
is_in_pair = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (geoms_[i]->mesh &&
|
|
(geoms_[i]->spec.type == mjGEOM_MESH ||
|
|
geoms_[i]->spec.type == mjGEOM_SDF) &&
|
|
(geoms_[i]->spec.contype || geoms_[i]->spec.conaffinity || is_in_pair ||
|
|
geoms_[i]->mesh->spec.inertia == mjMESH_INERTIA_CONVEX)) {
|
|
geoms_[i]->mesh->SetNeedHull(true);
|
|
}
|
|
}
|
|
|
|
// automatically set nuser fields
|
|
SetNuser();
|
|
|
|
// compile meshes (needed for geom compilation)
|
|
if (!compiler.usethread && meshes_.size() > 1) {
|
|
// multi-threaded mesh compile
|
|
CompileMeshes(vfs);
|
|
} else {
|
|
// single-threaded mesh compile
|
|
for (int i=0; i < meshes_.size(); i++) {
|
|
meshes_[i]->Compile(vfs);
|
|
}
|
|
}
|
|
|
|
// compile objects in kinematic tree
|
|
for (int i=0; i < bodies_.size(); i++) {
|
|
bodies_[i]->Compile(); // also compiles joints, geoms, sites, cameras, lights, frames
|
|
}
|
|
|
|
// fuse static if enabled
|
|
if (compiler.fusestatic) {
|
|
FuseStatic();
|
|
for (int i=0; i < lights_.size(); i++) {
|
|
lights_[i]->Compile();
|
|
}
|
|
for (int i=0; i < cameras_.size(); i++) {
|
|
cameras_[i]->Compile();
|
|
}
|
|
}
|
|
|
|
// compile all other objects except for keyframes
|
|
for (auto flex : flexes_) flex->Compile(vfs);
|
|
for (auto skin : skins_) skin->Compile(vfs);
|
|
for (auto hfield : hfields_) hfield->Compile(vfs);
|
|
for (auto texture : textures_) texture->Compile(vfs);
|
|
for (auto material : materials_) material->Compile();
|
|
for (auto pair : pairs_) pair->Compile();
|
|
for (auto exclude : excludes_) exclude->Compile();
|
|
for (auto equality : equalities_) equality->Compile();
|
|
for (auto tendon : tendons_) tendon->Compile();
|
|
for (auto actuator : actuators_) actuator->Compile();
|
|
for (auto sensor : sensors_) sensor->Compile();
|
|
for (auto numeric : numerics_) numeric->Compile();
|
|
for (auto text : texts_) text->Compile();
|
|
for (auto tuple : tuples_) tuple->Compile();
|
|
for (auto plugin : plugins_) plugin->Compile();
|
|
|
|
// compile def: to enforce userdata length for writer
|
|
for (mjCDef* def : defaults_) {
|
|
def->Compile(this);
|
|
}
|
|
|
|
// sort pair, exclude in increasing signature order; reassign ids
|
|
std::stable_sort(pairs_.begin(), pairs_.end(), comparePair);
|
|
std::stable_sort(excludes_.begin(), excludes_.end(), compareBodyPair);
|
|
reassignid(pairs_);
|
|
reassignid(excludes_);
|
|
|
|
// resolve asset references, compute sizes
|
|
IndexAssets(compiler.discardvisual);
|
|
SetSizes();
|
|
SaveDofOffsets(/*computesize=*/false); // Populate jnt->dofadr_
|
|
|
|
// compute sparse matrix sizes
|
|
ComputeSparseSizes();
|
|
|
|
// set nmocap and body.mocapid
|
|
for (mjCBody* body : bodies_) {
|
|
if (body->mocap) {
|
|
body->mocapid = nmocap;
|
|
nmocap++;
|
|
} else {
|
|
body->mocapid = -1;
|
|
}
|
|
}
|
|
|
|
// check mass and inertia of moving bodies
|
|
for (int i=1; i < bodies_.size(); i++) {
|
|
if (!bodies_[i]->joints.empty() && !CheckBodyMassInertia(bodies_[i])) {
|
|
throw mjCError(bodies_[i], "mass and inertia of moving bodies must be larger than mjMINVAL");
|
|
}
|
|
}
|
|
|
|
// create low-level model
|
|
mj_makeModel(&m,
|
|
nq, nv, nu, na, nbody, nbvh, nbvhstatic, nbvhdynamic, noct, njnt, ntree, nM, nB, nC,
|
|
nD, ngeom, nsite, ncam, nlight, nflex, nflexnode, nflexvert, nflexedge, nflexelem,
|
|
nflexelemdata, nflexelemedge, nflexshelldata, nflexevpair, nflextexcoord,
|
|
nmesh, nmeshvert, nmeshnormal, nmeshtexcoord, nmeshface, nmeshgraph, nmeshpoly,
|
|
nmeshpolyvert, nmeshpolymap, nskin, nskinvert, nskintexvert, nskinface, nskinbone,
|
|
nskinbonevert, nhfield, nhfielddata, ntex, ntexdata, nmat, npair, nexclude,
|
|
neq, ntendon, nwrap, nsensor, nnumeric, nnumericdata, ntext, ntextdata,
|
|
ntuple, ntupledata, nkey, nmocap, nplugin, npluginattr,
|
|
nuser_body, nuser_jnt, nuser_geom, nuser_site, nuser_cam,
|
|
nuser_tendon, nuser_actuator, nuser_sensor, nnames, npaths);
|
|
if (!m) {
|
|
throw mjCError(0, "could not create mjModel");
|
|
}
|
|
|
|
// copy everything into low-level model
|
|
m->opt = option;
|
|
m->vis = visual;
|
|
CopyNames(m);
|
|
CopyPaths(m);
|
|
CopyTree(m);
|
|
CopyPlugins(m);
|
|
|
|
// keyframe compilation needs access to nq, nv, na, nmocap, qpos0
|
|
ResolveKeyframes(m);
|
|
|
|
for (int i=0; i < keys_.size(); i++) {
|
|
keys_[i]->Compile(m);
|
|
}
|
|
|
|
// copy objects outsite kinematic tree (including keyframes)
|
|
CopyObjects(m);
|
|
|
|
// finalize simple bodies/dofs including tendon information
|
|
FinalizeSimple(m);
|
|
|
|
// compute non-zeros in actuator_moment
|
|
m->nJmom = nJmom = CountNJmom(m);
|
|
|
|
// scale mass
|
|
if (compiler.settotalmass > 0) {
|
|
mj_setTotalmass(m, compiler.settotalmass);
|
|
}
|
|
|
|
// set arena size into m->narena
|
|
if (memory != -1) {
|
|
// memory size is user-specified in bytes
|
|
m->narena = memory;
|
|
} else {
|
|
const int nconmax = m->nconmax == -1 ? 100 : m->nconmax;
|
|
const int njmax = m->njmax == -1 ? 500 : m->njmax;
|
|
if (nstack != -1) {
|
|
// (legacy) stack size is user-specified as multiple of sizeof(mjtNum)
|
|
m->narena = sizeof(mjtNum) * nstack;
|
|
} else {
|
|
// use a conservative heuristic if neither memory nor nstack is specified in XML
|
|
m->narena = sizeof(mjtNum) * static_cast<size_t>(mjMAX(
|
|
1000,
|
|
5*(njmax + m->neq + m->nv)*(njmax + m->neq + m->nv) +
|
|
20*(m->nq + m->nv + m->nu + m->na + m->nbody + m->njnt +
|
|
m->ngeom + m->nsite + m->neq + m->ntendon + m->nwrap)));
|
|
}
|
|
|
|
// add an arena space equal to memory footprint prior to the introduction of the arena
|
|
const std::size_t arena_bytes = (
|
|
nconmax * sizeof(mjContact) +
|
|
njmax * (8 * sizeof(int) + 14 * sizeof(mjtNum)) +
|
|
m->nv * (3 * sizeof(int)) +
|
|
njmax * m->nv * (2 * sizeof(int) + 2 * sizeof(mjtNum)) +
|
|
njmax * njmax * (sizeof(int) + sizeof(mjtNum)));
|
|
m->narena += arena_bytes;
|
|
|
|
// round up to the nearest megabyte
|
|
constexpr std::size_t kMegabyte = 1 << 20;
|
|
std::size_t nstack_mb = m->narena / kMegabyte;
|
|
std::size_t residual_mb = m->narena % kMegabyte ? 1 : 0;
|
|
m->narena = kMegabyte * (nstack_mb + residual_mb);
|
|
}
|
|
|
|
// sparsity structures
|
|
{
|
|
std::vector<int> scratch(m->nv);
|
|
std::vector<int> count(m->nbody);
|
|
std::vector<int> M(m->nM);
|
|
|
|
// make D
|
|
mj_makeDofDofSparse(m->nv, m->nC, m->nD, m->nM, m->dof_parentid, m->dof_simplenum,
|
|
m->D_rownnz, m->D_rowadr, m->D_diag, m->D_colind,
|
|
/*reduced=*/0, /*upper=*/1, scratch.data());
|
|
|
|
// make B
|
|
mj_makeBSparse(m->nv, m->nbody, m->nB, m->body_dofnum, m->body_parentid,
|
|
m->body_dofadr, m->B_rownnz, m->B_rowadr, m->B_colind, count.data());
|
|
|
|
// make M
|
|
mj_makeDofDofSparse(m->nv, m->nC, m->nD, m->nM, m->dof_parentid, m->dof_simplenum,
|
|
m->M_rownnz, m->M_rowadr, NULL, m->M_colind,
|
|
/*reduced=*/1, /*upper=*/0, scratch.data());
|
|
|
|
// make index mappings: mapM2D, mapD2M, mapM2M
|
|
mj_makeDofDofMaps(m->nv, m->nM, m->nC, m->nD,
|
|
m->dof_Madr, m->dof_simplenum, m->dof_parentid,
|
|
m->D_rownnz, m->D_rowadr, m->D_colind,
|
|
m->M_rownnz, m->M_rowadr, m->M_colind,
|
|
m->mapM2D, m->mapD2M, m->mapM2M, M.data(), scratch.data());
|
|
}
|
|
|
|
// create data
|
|
int disableflags = m->opt.disableflags;
|
|
m->opt.disableflags |= mjDSBL_CONTACT;
|
|
mj_makeRawData(&d, m);
|
|
if (!d) {
|
|
// m will be deleted by the catch statement in mjCModel::Compile()
|
|
throw mjCError(0, "could not create mjData");
|
|
}
|
|
mj_resetData(m, d);
|
|
|
|
// normalize keyframe quaternions
|
|
for (int i=0; i < m->nkey; i++) {
|
|
mj_normalizeQuat(m, m->key_qpos+i*m->nq);
|
|
}
|
|
|
|
// set constant fields
|
|
mj_setConst(m, d);
|
|
|
|
// automatic spring-damper adjustment
|
|
AutoSpringDamper(m);
|
|
|
|
// actuator lengthrange computation
|
|
LengthRange(m, d);
|
|
|
|
// save automatically-computed statistics, to disambiguate when saving
|
|
extent_auto = m->stat.extent;
|
|
meaninertia_auto = m->stat.meaninertia;
|
|
meanmass_auto = m->stat.meanmass;
|
|
meansize_auto = m->stat.meansize;
|
|
mjuu_copyvec(center_auto, m->stat.center, 3);
|
|
|
|
// override model statistics if defined by user
|
|
if (mjuu_defined(stat.extent)) m->stat.extent = (mjtNum)stat.extent;
|
|
if (mjuu_defined(stat.meaninertia)) m->stat.meaninertia = (mjtNum)stat.meaninertia;
|
|
if (mjuu_defined(stat.meanmass)) m->stat.meanmass = (mjtNum)stat.meanmass;
|
|
if (mjuu_defined(stat.meansize)) m->stat.meansize = (mjtNum)stat.meansize;
|
|
if (mjuu_defined(stat.center[0])) mjuu_copyvec(m->stat.center, stat.center, 3);
|
|
|
|
// assert that model has valid references
|
|
const char* validationerr = mj_validateReferences(m);
|
|
if (validationerr) { // SHOULD NOT OCCUR
|
|
// m and d will be deleted by the catch statement in mjCModel::Compile()
|
|
throw mjCError(0, "%s", validationerr);
|
|
}
|
|
|
|
// delete partial mjData (no plugins), make a complete one
|
|
mj_deleteData(d);
|
|
d = nullptr;
|
|
d = mj_makeData(m);
|
|
if (!d) {
|
|
// m will be deleted by the catch statement in mjCModel::Compile()
|
|
throw mjCError(0, "could not create mjData");
|
|
}
|
|
|
|
// test forward simulation
|
|
mj_step(m, d);
|
|
|
|
// delete data
|
|
mj_deleteData(d);
|
|
m->opt.disableflags = disableflags;
|
|
d = nullptr;
|
|
|
|
// pass warning back
|
|
if (warningtext[0]) {
|
|
mju::strcpy_arr(errInfo.message, warningtext);
|
|
errInfo.warning = true;
|
|
}
|
|
|
|
// save signature
|
|
m->signature = Signature();
|
|
|
|
// special cases that are not caused by user edits
|
|
if (compiler.fusestatic || compiler.discardvisual ||
|
|
!pairs_.empty() || !excludes_.empty()) {
|
|
spec.element->signature = m->signature;
|
|
}
|
|
|
|
// check that the signature matches the spec
|
|
if (m->signature != spec.element->signature) {
|
|
throw mjCError(0, "signature mismatch"); // SHOULD NOT OCCUR
|
|
}
|
|
}
|
|
|
|
|
|
|
|
std::string mjCModel::PrintTree(const mjCBody* body, std::string indent) {
|
|
std::string tree;
|
|
tree += indent + "<body>\n";
|
|
indent += " ";
|
|
for (const auto& joint : body->joints) {
|
|
tree += indent + "<joint>" + std::to_string(joint->nq()) + "</joint>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->geoms.size(); ++i) {
|
|
tree += indent + "<geom/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->sites.size(); ++i) {
|
|
tree += indent + "<site/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->cameras.size(); ++i) {
|
|
tree += indent + "<camera/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->lights.size(); ++i) {
|
|
tree += indent + "<light/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->bodies.size(); ++i) {
|
|
tree += PrintTree(body->bodies[i], indent);
|
|
}
|
|
indent.pop_back();
|
|
indent.pop_back();
|
|
tree += indent + "</body>\n";
|
|
return tree;
|
|
}
|
|
|
|
|
|
|
|
uint64_t mjCModel::Signature() {
|
|
std::string tree = "\n" + PrintTree(bodies_[0]);
|
|
for (unsigned int i = 0; i < flexes_.size(); ++i) {
|
|
tree += "<flex/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < meshes_.size(); ++i) {
|
|
tree += "<mesh/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < skins_.size(); ++i) {
|
|
tree += "<skin/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < hfields_.size(); ++i) {
|
|
tree += "<heightfield/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < textures_.size(); ++i) {
|
|
tree += "<texture/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < materials_.size(); ++i) {
|
|
tree += "<material/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < pairs_.size(); ++i) {
|
|
tree += "<pair/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < excludes_.size(); ++i) {
|
|
tree += "<exclude/>\n";
|
|
}
|
|
for (unsigned int i = 1; i < equalities_.size(); ++i) {
|
|
tree += "<equality/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < tendons_.size(); ++i) {
|
|
tree += "<tendon/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < actuators_.size(); ++i) {
|
|
tree += "<actuator/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < sensors_.size(); ++i) {
|
|
tree += "<sensor>" + std::to_string(sensors_[i]->spec.type) + "<sensor/>\n";
|
|
}
|
|
for (unsigned int i = 0; i < keys_.size(); ++i) {
|
|
tree += "<key/>\n";
|
|
}
|
|
return mj_hashString(tree.c_str(), UINT64_MAX);
|
|
}
|
|
|
|
|
|
|
|
bool mjCModel::CheckBodyMassInertia(mjCBody* body) {
|
|
// check if body has valid mass and inertia
|
|
if (body->mass >= mjMINVAL &&
|
|
body->inertia[0] >= mjMINVAL &&
|
|
body->inertia[1] >= mjMINVAL &&
|
|
body->inertia[2] >= mjMINVAL) {
|
|
return true;
|
|
}
|
|
|
|
// body is valid if we find a single static child with valid mass and inertia
|
|
for (int i=0; i < body->Bodies().size(); i++) {
|
|
// if we find a child with a joint, time to move on to the next moving body
|
|
if (!body->Bodies()[i]->joints.empty()) {
|
|
continue;
|
|
}
|
|
if (CheckBodyMassInertia(body->Bodies()[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// we did not find a child with valid mass and inertia
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
//------------------------------- DECOMPILER -------------------------------------------------------
|
|
|
|
// get numeric data back from mjModel
|
|
bool mjCModel::CopyBack(const mjModel* m) {
|
|
// check for null pointer
|
|
if (!m) {
|
|
errInfo = mjCError(0, "mjModel pointer is null in CopyBack");
|
|
return false;
|
|
}
|
|
|
|
// make sure model has been compiled
|
|
if (!compiled) {
|
|
errInfo = mjCError(0, "mjCModel has not been compiled in CopyBack");
|
|
return false;
|
|
}
|
|
|
|
// make sure sizes match
|
|
if (nq != m->nq || nv != m->nv || nu != m->nu || na != m->na ||
|
|
nbody != m->nbody ||njnt != m->njnt || ngeom != m->ngeom || nsite != m->nsite ||
|
|
ncam != m->ncam || nlight != m->nlight || nmesh != m->nmesh ||
|
|
nskin != m->nskin || nhfield != m->nhfield ||
|
|
nmat != m->nmat || ntex != m->ntex || npair != m->npair || nexclude != m->nexclude ||
|
|
neq != m->neq || ntendon != m->ntendon || nwrap != m->nwrap || nsensor != m->nsensor ||
|
|
nnumeric != m->nnumeric || nnumericdata != m->nnumericdata || ntext != m->ntext ||
|
|
ntextdata != m->ntextdata || nnames != m->nnames ||
|
|
nM != m->nM || nD != m->nD || nC != m->nC || nB != m->nB || nJmom != m->nJmom ||
|
|
nemax != m->nemax || nconmax != m->nconmax || njmax != m->njmax ||
|
|
npaths != m->npaths) {
|
|
errInfo = mjCError(0, "incompatible models in CopyBack");
|
|
return false;
|
|
}
|
|
|
|
if (spec.element->signature != m->signature) {
|
|
errInfo = mjCError(0, "incompatible signatures in CopyBack");
|
|
return false;
|
|
}
|
|
|
|
// option and visual
|
|
option = m->opt;
|
|
visual = m->vis;
|
|
|
|
// runtime-modifiable members of mjStatistic, if different from computed values
|
|
if (m->stat.meaninertia != meaninertia_auto) stat.meaninertia = m->stat.meaninertia;
|
|
if (m->stat.meanmass != meanmass_auto) stat.meanmass = m->stat.meanmass;
|
|
if (m->stat.meansize != meansize_auto) stat.meansize = m->stat.meansize;
|
|
if (m->stat.extent != extent_auto) stat.extent = m->stat.extent;
|
|
if (m->stat.center[0] != center_auto[0] ||
|
|
m->stat.center[1] != center_auto[1] ||
|
|
m->stat.center[2] != center_auto[2]) {
|
|
mjuu_copyvec(stat.center, m->stat.center, 3);
|
|
}
|
|
|
|
// qpos0, qpos_spring
|
|
for (int i=0; i < njnt; i++) {
|
|
switch (joints_[i]->type) {
|
|
case mjJNT_FREE:
|
|
mjuu_copyvec(bodies_[m->jnt_bodyid[i]]->pos, m->qpos0+m->jnt_qposadr[i], 3);
|
|
mjuu_copyvec(bodies_[m->jnt_bodyid[i]]->quat, m->qpos0+m->jnt_qposadr[i]+3, 4);
|
|
break;
|
|
|
|
case mjJNT_SLIDE:
|
|
case mjJNT_HINGE:
|
|
joints_[i]->ref = (double)m->qpos0[m->jnt_qposadr[i]];
|
|
joints_[i]->springref = (double)m->qpos_spring[m->jnt_qposadr[i]];
|
|
break;
|
|
|
|
case mjJNT_BALL:
|
|
// nothing to do, qpos = unit quaternion always
|
|
break;
|
|
}
|
|
}
|
|
mjuu_copyvec(qpos0.data(), m->qpos0, m->nq);
|
|
|
|
// body
|
|
mjCBody* pb;
|
|
for (int i=0; i < nbody; i++) {
|
|
pb = bodies_[i];
|
|
|
|
mjuu_copyvec(pb->pos, m->body_pos+3*i, 3);
|
|
mjuu_copyvec(pb->quat, m->body_quat+4*i, 4);
|
|
mjuu_copyvec(pb->ipos, m->body_ipos+3*i, 3);
|
|
mjuu_copyvec(pb->iquat, m->body_iquat+4*i, 4);
|
|
pb->mass = (double)m->body_mass[i];
|
|
mjuu_copyvec(pb->inertia, m->body_inertia+3*i, 3);
|
|
|
|
if (nuser_body) {
|
|
mjuu_copyvec(pb->userdata_.data(), m->body_user + nuser_body*i, nuser_body);
|
|
}
|
|
}
|
|
|
|
// joint and dof
|
|
mjCJoint* pj;
|
|
for (int i=0; i < njnt; i++) {
|
|
pj = joints_[i];
|
|
|
|
// joint data
|
|
mjuu_copyvec(pj->pos, m->jnt_pos+3*i, 3);
|
|
mjuu_copyvec(pj->axis, m->jnt_axis+3*i, 3);
|
|
pj->stiffness = (double)m->jnt_stiffness[i];
|
|
mjuu_copyvec(pj->range, m->jnt_range+2*i, 2);
|
|
mjuu_copyvec(pj->solref_limit, m->jnt_solref+mjNREF*i, mjNREF);
|
|
mjuu_copyvec(pj->solimp_limit, m->jnt_solimp+mjNIMP*i, mjNIMP);
|
|
pj->margin = (double)m->jnt_margin[i];
|
|
|
|
if (nuser_jnt) {
|
|
mjuu_copyvec(pj->userdata_.data(), m->jnt_user + nuser_jnt*i, nuser_jnt);
|
|
}
|
|
|
|
// dof data
|
|
int j = m->jnt_dofadr[i];
|
|
mjuu_copyvec(pj->solref_friction, m->dof_solref+mjNREF*j, mjNREF);
|
|
mjuu_copyvec(pj->solimp_friction, m->dof_solimp+mjNIMP*j, mjNIMP);
|
|
pj->armature = (double)m->dof_armature[j];
|
|
pj->damping = (double)m->dof_damping[j];
|
|
pj->frictionloss = (double)m->dof_frictionloss[j];
|
|
}
|
|
|
|
// geom
|
|
mjCGeom* pg;
|
|
for (int i=0; i < ngeom; i++) {
|
|
pg = geoms_[i];
|
|
|
|
mjuu_copyvec(pg->size, m->geom_size+3*i, 3);
|
|
mjuu_copyvec(pg->pos, m->geom_pos+3*i, 3);
|
|
mjuu_copyvec(pg->quat, m->geom_quat+4*i, 4);
|
|
mjuu_copyvec(pg->friction, m->geom_friction+3*i, 3);
|
|
mjuu_copyvec(pg->solref, m->geom_solref+mjNREF*i, mjNREF);
|
|
mjuu_copyvec(pg->solimp, m->geom_solimp+mjNIMP*i, mjNIMP);
|
|
mjuu_copyvec(pg->rgba, m->geom_rgba+4*i, 4);
|
|
pg->solmix = (double)m->geom_solmix[i];
|
|
pg->margin = (double)m->geom_margin[i];
|
|
pg->gap = (double)m->geom_gap[i];
|
|
|
|
if (nuser_geom) {
|
|
mjuu_copyvec(pg->userdata_.data(), m->geom_user + nuser_geom*i, nuser_geom);
|
|
}
|
|
}
|
|
|
|
// mesh
|
|
mjCMesh* pm;
|
|
for (int i=0; i < nmesh; i++) {
|
|
pm = meshes_[i];
|
|
mjuu_copyvec(pm->GetPosPtr(), m->mesh_pos+3*i, 3);
|
|
mjuu_copyvec(pm->GetQuatPtr(), m->mesh_quat+4*i, 4);
|
|
}
|
|
|
|
// heightfield
|
|
mjCHField* phf;
|
|
for (int i=0; i < nhfield; i++) {
|
|
phf = hfields_[i];
|
|
int size = phf->get_userdata().size();
|
|
if (size) {
|
|
int nrow = m->hfield_nrow[i];
|
|
int ncol = m->hfield_ncol[i];
|
|
float* userdata = phf->get_userdata().data();
|
|
float* modeldata = m->hfield_data + m->hfield_adr[i];
|
|
// copy back in reverse row order
|
|
for (int j=0; j < nrow; j++) {
|
|
int flip = nrow-1-j;
|
|
mjuu_copyvec(userdata + flip*ncol, modeldata+j*ncol, ncol);
|
|
}
|
|
}
|
|
}
|
|
|
|
// sites
|
|
for (int i=0; i < nsite; i++) {
|
|
mjuu_copyvec(sites_[i]->size, m->site_size + 3 * i, 3);
|
|
mjuu_copyvec(sites_[i]->pos, m->site_pos+3*i, 3);
|
|
mjuu_copyvec(sites_[i]->quat, m->site_quat+4*i, 4);
|
|
mjuu_copyvec(sites_[i]->rgba, m->site_rgba+4*i, 4);
|
|
|
|
if (nuser_site) {
|
|
mjuu_copyvec(sites_[i]->userdata_.data(), m->site_user + nuser_site*i, nuser_site);
|
|
}
|
|
}
|
|
|
|
// cameras
|
|
for (int i=0; i < ncam; i++) {
|
|
mjuu_copyvec(cameras_[i]->pos, m->cam_pos+3*i, 3);
|
|
mjuu_copyvec(cameras_[i]->quat, m->cam_quat+4*i, 4);
|
|
cameras_[i]->fovy = (double)m->cam_fovy[i];
|
|
cameras_[i]->ipd = (double)m->cam_ipd[i];
|
|
mjuu_copyvec(cameras_[i]->resolution, m->cam_resolution+2*i, 2);
|
|
mjuu_copyvec(cameras_[i]->intrinsic, m->cam_intrinsic+4*i, 4);
|
|
|
|
if (nuser_cam) {
|
|
mjuu_copyvec(cameras_[i]->userdata_.data(), m->cam_user + nuser_cam*i, nuser_cam);
|
|
}
|
|
}
|
|
|
|
// lights
|
|
for (int i=0; i < nlight; i++) {
|
|
mjuu_copyvec(lights_[i]->pos, m->light_pos+3*i, 3);
|
|
mjuu_copyvec(lights_[i]->dir, m->light_dir+3*i, 3);
|
|
mjuu_copyvec(lights_[i]->attenuation, m->light_attenuation+3*i, 3);
|
|
lights_[i]->cutoff = m->light_cutoff[i];
|
|
lights_[i]->exponent = m->light_exponent[i];
|
|
mjuu_copyvec(lights_[i]->ambient, m->light_ambient+3*i, 3);
|
|
mjuu_copyvec(lights_[i]->diffuse, m->light_diffuse+3*i, 3);
|
|
mjuu_copyvec(lights_[i]->specular, m->light_specular+3*i, 3);
|
|
}
|
|
|
|
// materials
|
|
for (int i=0; i < nmat; i++) {
|
|
mjuu_copyvec(materials_[i]->texrepeat, m->mat_texrepeat+2*i, 2);
|
|
materials_[i]->emission = m->mat_emission[i];
|
|
materials_[i]->specular = m->mat_specular[i];
|
|
materials_[i]->shininess = m->mat_shininess[i];
|
|
materials_[i]->reflectance = m->mat_reflectance[i];
|
|
mjuu_copyvec(materials_[i]->rgba, m->mat_rgba+4*i, 4);
|
|
}
|
|
|
|
// pairs
|
|
for (int i=0; i < npair; i++) {
|
|
mjuu_copyvec(pairs_[i]->solref, m->pair_solref+mjNREF*i, mjNREF);
|
|
mjuu_copyvec(pairs_[i]->solreffriction, m->pair_solreffriction+mjNREF*i, mjNREF);
|
|
mjuu_copyvec(pairs_[i]->solimp, m->pair_solimp+mjNIMP*i, mjNIMP);
|
|
pairs_[i]->margin = (double)m->pair_margin[i];
|
|
pairs_[i]->gap = (double)m->pair_gap[i];
|
|
mjuu_copyvec(pairs_[i]->friction, m->pair_friction+5*i, 5);
|
|
}
|
|
|
|
// equality constraints
|
|
for (int i=0; i < neq; i++) {
|
|
mjuu_copyvec(equalities_[i]->data, m->eq_data+mjNEQDATA*i, mjNEQDATA);
|
|
mjuu_copyvec(equalities_[i]->solref, m->eq_solref+mjNREF*i, mjNREF);
|
|
mjuu_copyvec(equalities_[i]->solimp, m->eq_solimp+mjNIMP*i, mjNIMP);
|
|
}
|
|
|
|
// tendons
|
|
for (int i=0; i < ntendon; i++) {
|
|
mjuu_copyvec(tendons_[i]->range, m->tendon_range+2*i, 2);
|
|
mjuu_copyvec(tendons_[i]->actfrcrange, m->tendon_actfrcrange+2*i, 2);
|
|
mjuu_copyvec(tendons_[i]->solref_limit, m->tendon_solref_lim+mjNREF*i, mjNREF);
|
|
mjuu_copyvec(tendons_[i]->solimp_limit, m->tendon_solimp_lim+mjNIMP*i, mjNIMP);
|
|
mjuu_copyvec(tendons_[i]->solref_friction, m->tendon_solref_fri+mjNREF*i, mjNREF);
|
|
mjuu_copyvec(tendons_[i]->solimp_friction, m->tendon_solimp_fri+mjNIMP*i, mjNIMP);
|
|
mjuu_copyvec(tendons_[i]->rgba, m->tendon_rgba+4*i, 4);
|
|
tendons_[i]->width = (double)m->tendon_width[i];
|
|
tendons_[i]->margin = (double)m->tendon_margin[i];
|
|
tendons_[i]->stiffness = (double)m->tendon_stiffness[i];
|
|
tendons_[i]->damping = (double)m->tendon_damping[i];
|
|
tendons_[i]->armature = (double)m->tendon_armature[i];
|
|
tendons_[i]->frictionloss = (double)m->tendon_frictionloss[i];
|
|
|
|
if (nuser_tendon) {
|
|
mjuu_copyvec(tendons_[i]->userdata_.data(), m->tendon_user + nuser_tendon*i, nuser_tendon);
|
|
}
|
|
}
|
|
|
|
// actuators
|
|
mjCActuator* pa;
|
|
for (int i=0; i < nu; i++) {
|
|
pa = actuators_[i];
|
|
|
|
mjuu_copyvec(pa->dynprm, m->actuator_dynprm+i*mjNDYN, mjNDYN);
|
|
mjuu_copyvec(pa->gainprm, m->actuator_gainprm+i*mjNGAIN, mjNGAIN);
|
|
mjuu_copyvec(pa->biasprm, m->actuator_biasprm+i*mjNBIAS, mjNBIAS);
|
|
mjuu_copyvec(pa->ctrlrange, m->actuator_ctrlrange+2*i, 2);
|
|
mjuu_copyvec(pa->forcerange, m->actuator_forcerange+2*i, 2);
|
|
mjuu_copyvec(pa->actrange, m->actuator_actrange+2*i, 2);
|
|
mjuu_copyvec(pa->lengthrange, m->actuator_lengthrange+2*i, 2);
|
|
mjuu_copyvec(pa->gear, m->actuator_gear+6*i, 6);
|
|
pa->cranklength = (double)m->actuator_cranklength[i];
|
|
|
|
if (nuser_actuator) {
|
|
mjuu_copyvec(pa->userdata_.data(), m->actuator_user + nuser_actuator*i, nuser_actuator);
|
|
}
|
|
}
|
|
|
|
// sensors
|
|
for (int i=0; i < nsensor; i++) {
|
|
sensors_[i]->cutoff = (double)m->sensor_cutoff[i];
|
|
sensors_[i]->noise = (double)m->sensor_noise[i];
|
|
|
|
if (nuser_sensor) {
|
|
mjuu_copyvec(sensors_[i]->userdata_.data(), m->sensor_user + nuser_sensor*i, nuser_sensor);
|
|
}
|
|
}
|
|
|
|
// numeric data
|
|
for (int i=0; i < nnumeric; i++) {
|
|
for (int j=0; j < m->numeric_size[i]; j++) {
|
|
numerics_[i]->data_[j] = (double)m->numeric_data[m->numeric_adr[i]+j];
|
|
}
|
|
}
|
|
|
|
// tuple data
|
|
for (int i=0; i < ntuple; i++) {
|
|
for (int j=0; j < m->tuple_size[i]; j++) {
|
|
tuples_[i]->objprm_[j] = (double)m->tuple_objprm[m->tuple_adr[i]+j];
|
|
}
|
|
}
|
|
|
|
// keyframes
|
|
for (int i=0; i < m->nkey; i++) {
|
|
mjCKey* pk = keys_[i];
|
|
|
|
pk->time = (double)m->key_time[i];
|
|
mjuu_copyvec(pk->qpos_.data(), m->key_qpos + i*nq, nq);
|
|
mjuu_copyvec(pk->qvel_.data(), m->key_qvel + i*nv, nv);
|
|
if (na) {
|
|
mjuu_copyvec(pk->act_.data(), m->key_act + i*na, na);
|
|
}
|
|
if (nmocap) {
|
|
mjuu_copyvec(pk->mpos_.data(), m->key_mpos + i*3*nmocap, 3*nmocap);
|
|
mjuu_copyvec(pk->mquat_.data(), m->key_mquat + i*4*nmocap, 4*nmocap);
|
|
}
|
|
if (nu) {
|
|
mjuu_copyvec(pk->ctrl_.data(), m->key_ctrl + i*nu, nu);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::ActivatePlugin(const mjpPlugin* plugin, int slot) {
|
|
bool already_declared = false;
|
|
for (const auto& [existing_plugin, existing_slot] : active_plugins_) {
|
|
if (plugin == existing_plugin) {
|
|
already_declared = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!already_declared) {
|
|
active_plugins_.emplace_back(std::make_pair(plugin, slot));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::ResolvePlugin(mjCBase* obj, const std::string& plugin_name,
|
|
const std::string& plugin_instance_name, mjCPlugin** plugin_instance) {
|
|
std::string pname = plugin_name;
|
|
|
|
// if the plugin name is not specified by the user, infer it from the plugin instance
|
|
if (plugin_name.empty() && !plugin_instance_name.empty()) {
|
|
mjCBase* plugin_obj = FindObject(mjOBJ_PLUGIN, plugin_instance_name);
|
|
if (plugin_obj) {
|
|
pname = static_cast<mjCPlugin*>(plugin_obj)->plugin_name;
|
|
} else {
|
|
throw mjCError(obj, "unrecognized name '%s' for plugin instance",
|
|
plugin_instance_name.c_str());
|
|
}
|
|
}
|
|
|
|
// if plugin_name is specified, check if it is in the list of active plugins
|
|
// (in XML, active plugins are those declared as <required>)
|
|
int plugin_slot = -1;
|
|
if (!pname.empty()) {
|
|
for (int i = 0; i < active_plugins_.size(); ++i) {
|
|
if (active_plugins_[i].first->name == pname) {
|
|
plugin_slot = active_plugins_[i].second;
|
|
break;
|
|
}
|
|
}
|
|
if (plugin_slot == -1) {
|
|
throw mjCError(obj, "unrecognized plugin '%s'", pname.c_str());
|
|
}
|
|
}
|
|
|
|
// implicit plugin instance
|
|
if (*plugin_instance && (*plugin_instance)->plugin_slot == -1) {
|
|
(*plugin_instance)->plugin_slot = plugin_slot;
|
|
(*plugin_instance)->parent = obj;
|
|
}
|
|
|
|
// explicit plugin instance, look up existing mjCPlugin by instance name
|
|
else if (!*plugin_instance) {
|
|
*plugin_instance =
|
|
static_cast<mjCPlugin*>(FindObject(mjOBJ_PLUGIN, plugin_instance_name));
|
|
(*plugin_instance)->plugin_slot = plugin_slot;
|
|
if (!*plugin_instance) {
|
|
throw mjCError(
|
|
obj, "unrecognized name '%s' for plugin instance", plugin_instance_name.c_str());
|
|
}
|
|
if (plugin_slot != -1 && plugin_slot != (*plugin_instance)->plugin_slot) {
|
|
throw mjCError(
|
|
obj, "'plugin' attribute does not match that of the instance");
|
|
}
|
|
plugin_slot = (*plugin_instance)->plugin_slot;
|
|
}
|
|
}
|