6419534bad
PiperOrigin-RevId: 866478839 Change-Id: Id21a6da0f98454c8fa39ea5af8a5e213d6eae497
5717 lines
172 KiB
C++
5717 lines
172 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 <array>
|
|
#include <atomic>
|
|
#include <cmath>
|
|
#include <csetjmp>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <filesystem> // NOLINT(build/c++17)
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <mujoco/mjdata.h>
|
|
#include <mujoco/mjmacro.h>
|
|
#include <mujoco/mjmodel.h>
|
|
#include <mujoco/mjplugin.h>
|
|
#include <mujoco/mjspec.h>
|
|
#include <mujoco/mjtnum.h>
|
|
#include <mujoco/mujoco.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_threadpool.h"
|
|
#include "user/user_util.h"
|
|
|
|
namespace {
|
|
namespace mju = ::mujoco::util;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
//---------------------------------- 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;
|
|
}
|
|
|
|
unsigned int NumCompilerThreads(int upper_bound = -1) {
|
|
// Use at most half the available threads to avoid
|
|
// overloading hyperthreaded CPUs.
|
|
// Compilation is largely compute-bound so we want to give each
|
|
// physical core a chance without too much L1/L2 cache thrashing.
|
|
unsigned int nthreads = std::thread::hardware_concurrency() / 2;
|
|
if (upper_bound > 0) {
|
|
nthreads = std::min(nthreads, static_cast<unsigned int>(upper_bound));
|
|
}
|
|
return std::max(static_cast<unsigned int>(1), nthreads);
|
|
}
|
|
|
|
// 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();
|
|
meshdir_.clear();
|
|
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);
|
|
PointToLocal();
|
|
|
|
// copy attached specs first so that we can resolve references to them
|
|
for (auto* s : other.specs_) {
|
|
specs_.push_back(s);
|
|
static_cast<mjCModel*>(s->element)->AddRef();
|
|
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.compiler.meshdir = &meshdir_;
|
|
spec.compiler.texturedir = &texturedir_;
|
|
comment = nullptr;
|
|
modelfiledir = nullptr;
|
|
modelname = nullptr;
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::CopyFromSpec() {
|
|
*static_cast<mjSpec*>(this) = spec;
|
|
comment_ = spec_comment_;
|
|
modelfiledir_ = spec_modelfiledir_;
|
|
modelname_ = spec_modelname_;
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
nJfe = 0;
|
|
nJfv = 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);
|
|
if (flexes_[i]->interpolated || flexes_[i]->rigid) {
|
|
continue;
|
|
}
|
|
|
|
// count number of non-zero elements in the edge Jacobian matrix
|
|
for (const auto& edge : flexes_[i]->edge) {
|
|
mjCBody* b1 = bodies_[flexes_[i]->vertbodyid[edge.first]];
|
|
mjCBody* b2 = bodies_[flexes_[i]->vertbodyid[edge.second]];
|
|
std::unordered_set<mjCBody*> bodies_in_jac;
|
|
while (b1 || b2) {
|
|
if (b1) {
|
|
bodies_in_jac.insert(b1);
|
|
b1 = b1->parent;
|
|
}
|
|
if (b2) {
|
|
bodies_in_jac.insert(b2);
|
|
b2 = b2->parent;
|
|
}
|
|
}
|
|
for (mjCBody* b : bodies_in_jac) {
|
|
nJfe += b->dofnum;
|
|
}
|
|
}
|
|
|
|
// compute nJfv
|
|
std::vector<std::vector<int>> adj(flexes_[i]->nvert);
|
|
for (const auto& edge : flexes_[i]->edge) {
|
|
adj[edge.first].push_back(edge.second);
|
|
adj[edge.second].push_back(edge.first);
|
|
}
|
|
for (int j=0; j < flexes_[i]->nvert; j++) {
|
|
std::unordered_set<int> vert_bodies;
|
|
vert_bodies.insert(flexes_[i]->vertbodyid[j]);
|
|
for (int neighbor : adj[j]) {
|
|
vert_bodies.insert(flexes_[i]->vertbodyid[neighbor]);
|
|
}
|
|
std::unordered_set<mjCBody*> bodies_in_jac;
|
|
for (int body_id : vert_bodies) {
|
|
mjCBody* b = bodies_[body_id];
|
|
while (b) {
|
|
bodies_in_jac.insert(b);
|
|
b = b->parent;
|
|
}
|
|
}
|
|
for (mjCBody* b : bodies_in_jac) {
|
|
nJfv += b->dofnum;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 += static_cast<mjtSize>(hfields_[i]->nrow) * hfields_[i]->ncol;
|
|
}
|
|
|
|
// ntexdata
|
|
for (int i=0; i < ntex; i++) {
|
|
const mjCTexture* tex = textures_[i];
|
|
ntexdata += static_cast<mjtSize>(tex->nchannel) * tex->width * tex->height;
|
|
}
|
|
|
|
// nwrap
|
|
for (int i=0; i < ntendon; i++) {
|
|
nwrap += static_cast<mjtSize>(tendons_[i]->path.size());
|
|
}
|
|
|
|
// nsensordata
|
|
for (int i=0; i < nsensor; i++) {
|
|
nsensordata += sensors_[i]->dim;
|
|
}
|
|
|
|
// nhistory: layout is [user, cursor, times(n), values(n*dim)] = 2+2n per actuator (dim=1)
|
|
nhistory = 0;
|
|
for (int i=0; i < actuators_.size(); i++) {
|
|
if (actuators_[i]->nsample > 0) {
|
|
nhistory += 2 + 2 * actuators_[i]->nsample;
|
|
}
|
|
}
|
|
// sensor delay: layout is [user, cursor, times(n), values(n*dim)] = 2 + n + n*dim
|
|
for (int i=0; i < sensors_.size(); i++) {
|
|
if (sensors_[i]->nsample > 0) {
|
|
nhistory += 2 + sensors_[i]->nsample + sensors_[i]->nsample * 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;
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
|
|
const auto nthread = NumCompilerThreads(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
|
|
// using vectors to allow arbitrary number of threads without stack overflow
|
|
std::vector<std::vector<char>> err(nthread, std::vector<char>(200));
|
|
std::vector<mjData*> pdata(nthread);
|
|
pdata[0] = 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
|
|
std::vector<LRThreadArg> arg(nthread);
|
|
for (int i=0; i < nthread; i++) {
|
|
LRThreadArg temp = {
|
|
m, pdata[i], i * num, num, &compiler.LRopt, err[i].data(), 200};
|
|
arg[i] = temp;
|
|
err[i][0] = 0;
|
|
}
|
|
|
|
// launch threads
|
|
std::vector<std::thread> th;
|
|
th.reserve(nthread);
|
|
for (int i=0; i < nthread; i++) {
|
|
th.emplace_back(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].data());
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_projection[cid] = pc->proj;
|
|
m->cam_fovy[cid] = (mjtNum)pc->fovy;
|
|
m->cam_ipd[cid] = (mjtNum)pc->ipd;
|
|
mjuu_copyvec(m->cam_resolution+2*cid, pc->resolution, 2);
|
|
m->cam_output[cid] = pc->output;
|
|
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;
|
|
}
|
|
}
|
|
|
|
// initialize AUTO sleep policy for all trees
|
|
for (int i=0; i < m->ntree; i++) {
|
|
m->tree_sleep_policy[i] = mjSLEEP_AUTO;
|
|
}
|
|
|
|
// loop over bodies, check and set non-default sleep policy
|
|
for (int i=1; i < nbody; i++) {
|
|
mjCBody* pb = bodies_[i];
|
|
|
|
// validate and set non-default sleep policy
|
|
if (pb->sleep != mjSLEEP_AUTO) {
|
|
int treeid = m->body_treeid[i];
|
|
// non-default sleep policy only allowed for first body in a tree
|
|
if (treeid == -1 || treeid == m->body_treeid[i-1]) {
|
|
throw mjCError(pb, "sleep policy only allowed for movable root bodies");
|
|
}
|
|
m->tree_sleep_policy[treeid] = pb->sleep;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
mjtSize adr, bone_adr, vert_adr, node_adr, normal_adr, face_adr, texcoord_adr, oct_adr;
|
|
mjtSize edge_adr, elem_adr, elemdata_adr, elemedge_adr, shelldata_adr, evpair_adr;
|
|
mjtSize bonevert_adr, graph_adr, data_adr, bvh_adr;
|
|
mjtSize 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->nhistory = nhistory;
|
|
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_size + 3 * i, pfl->size, 3);
|
|
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]->name1_ == pfl->name) {
|
|
if (equalities_[k]->type == mjEQ_FLEX) {
|
|
m->flex_edgeequality[i] = 1;
|
|
break;
|
|
}
|
|
if (equalities_[k]->type == mjEQ_FLEXVERT) {
|
|
m->flex_edgeequality[i] = 2;
|
|
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->order_;
|
|
|
|
// 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(),
|
|
static_cast<mjtSize>(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
|
|
mjtSize nbytes = static_cast<mjtSize>(ptex->nchannel) * ptex->width * ptex->height;
|
|
memcpy(m->tex_data + data_adr, ptex->data_.data(), nbytes);
|
|
|
|
// advance counter
|
|
data_adr += nbytes;
|
|
}
|
|
|
|
// 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;
|
|
int delay_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;
|
|
|
|
// historyadr
|
|
m->actuator_delay[i] = (mjtNum)pac->delay;
|
|
m->actuator_history[2*i] = pac->nsample;
|
|
m->actuator_history[2*i+1] = pac->interp;
|
|
if (pac->nsample > 0) {
|
|
m->actuator_historyadr[i] = delay_adr;
|
|
delay_adr += 2 + 2 * pac->nsample; // [user, cursor, times, values]
|
|
} else {
|
|
m->actuator_historyadr[i] = -1;
|
|
}
|
|
|
|
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;
|
|
|
|
// history buffer
|
|
m->sensor_delay[i] = (mjtNum)psen->delay;
|
|
m->sensor_history[2*i] = psen->nsample;
|
|
m->sensor_history[2*i+1] = psen->interp;
|
|
m->sensor_interval[2*i] = (mjtNum)psen->interval[0];
|
|
m->sensor_interval[2*i+1] = (mjtNum)psen->interval[1];
|
|
if (psen->nsample > 0) {
|
|
m->sensor_historyadr[i] = delay_adr;
|
|
int dim = psen->dim;
|
|
delay_adr += 2 + psen->nsample + psen->nsample * dim; // [user, cursor, times(n), values(n*dim)]
|
|
} else {
|
|
m->sensor_historyadr[i] = -1;
|
|
}
|
|
|
|
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);
|
|
model = nullptr;
|
|
mj_deleteData(data);
|
|
data = nullptr;
|
|
Clear();
|
|
|
|
// save error info
|
|
errInfo = err;
|
|
if (warningtext[0]) {
|
|
mju::strcat_arr(errInfo.message, "\n");
|
|
mju::strcat_arr(errInfo.message, warningtext);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
// Helper function for mesh compilation used by both serial and parallel paths
|
|
static void CompileMesh(mjCMesh* mesh, const mjVFS* vfs,
|
|
std::exception_ptr& exception, std::mutex& exception_mutex,
|
|
std::string* warningtext) {
|
|
local_warningtext_ptr = warningtext;
|
|
auto previous_handler = _mjPRIVATE__get_tls_warning_fn();
|
|
_mjPRIVATE__set_tls_warning_fn(warninghandler);
|
|
|
|
try {
|
|
mesh->Compile(vfs);
|
|
} catch (...) {
|
|
std::lock_guard<std::mutex> lock(exception_mutex);
|
|
if (!exception) {
|
|
exception = std::current_exception();
|
|
}
|
|
}
|
|
|
|
_mjPRIVATE__set_tls_warning_fn(previous_handler);
|
|
local_warningtext_ptr = nullptr;
|
|
}
|
|
|
|
// Helper function for texture compilation used by both serial and parallel paths
|
|
static void CompileTexture(mjCTexture* texture, const mjVFS* vfs,
|
|
std::exception_ptr& exception,
|
|
std::mutex& exception_mutex, std::string* warningtext) {
|
|
local_warningtext_ptr = warningtext;
|
|
auto previous_handler = _mjPRIVATE__get_tls_warning_fn();
|
|
_mjPRIVATE__set_tls_warning_fn(warninghandler);
|
|
|
|
try {
|
|
texture->Compile(vfs);
|
|
} catch (...) {
|
|
std::lock_guard<std::mutex> lock(exception_mutex);
|
|
if (!exception) {
|
|
exception = std::current_exception();
|
|
}
|
|
}
|
|
|
|
_mjPRIVATE__set_tls_warning_fn(previous_handler);
|
|
local_warningtext_ptr = nullptr;
|
|
}
|
|
|
|
// multi-threaded mesh and texture compilation with shared threadpool
|
|
void mjCModel::CompileMeshesAndTextures(const mjVFS* vfs) {
|
|
int nmesh = meshes_.size();
|
|
int ntexture = textures_.size();
|
|
int total_tasks = nmesh + ntexture;
|
|
|
|
// holds exceptions thrown by worker threads
|
|
std::exception_ptr mesh_exception;
|
|
std::mutex mesh_except_mutex;
|
|
std::exception_ptr texture_exception;
|
|
std::mutex texture_except_mutex;
|
|
|
|
std::vector<std::string> mesh_warningtext(nmesh);
|
|
std::vector<std::string> texture_warningtext(ntexture);
|
|
|
|
// If no pool provided or too few total tasks, run serially
|
|
if (!compiler.usethread || total_tasks < 2) {
|
|
// Compile meshes serially
|
|
for (int i = 0; i < nmesh; i++) {
|
|
CompileMesh(meshes_[i], vfs, mesh_exception, mesh_except_mutex,
|
|
&mesh_warningtext[i]);
|
|
}
|
|
// Compile textures serially
|
|
for (int i = 0; i < ntexture; i++) {
|
|
CompileTexture(textures_[i], vfs, texture_exception,
|
|
texture_except_mutex, &texture_warningtext[i]);
|
|
}
|
|
} else {
|
|
mujoco::user::ThreadPool pool(NumCompilerThreads(total_tasks));
|
|
|
|
// Enqueue mesh tasks
|
|
for (int i = 0; i < nmesh; ++i) {
|
|
pool.Schedule([mesh = meshes_[i], vfs, &mesh_exception,
|
|
&mesh_except_mutex, warningtext = &mesh_warningtext[i]]() {
|
|
CompileMesh(mesh, vfs, mesh_exception, mesh_except_mutex, warningtext);
|
|
});
|
|
}
|
|
|
|
// Enqueue texture tasks
|
|
for (int i = 0; i < ntexture; ++i) {
|
|
pool.Schedule([texture = textures_[i], vfs, &texture_exception,
|
|
&texture_except_mutex,
|
|
warningtext = &texture_warningtext[i]]() {
|
|
CompileTexture(texture, vfs, texture_exception, texture_except_mutex,
|
|
warningtext);
|
|
});
|
|
}
|
|
|
|
// Wait for all tasks to complete
|
|
pool.WaitCount(total_tasks);
|
|
}
|
|
|
|
// concatenate all mesh warnings, 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());
|
|
|
|
// aggregate texture warnings
|
|
for (int i = 0; i < ntexture; ++i) {
|
|
if (!texture_warningtext[i].empty()) {
|
|
if (has_warning) mju::strcat_arr(warningtext, "\n");
|
|
mju::strcat_arr(warningtext, texture_warningtext[i].c_str());
|
|
has_warning = true;
|
|
}
|
|
}
|
|
|
|
// if exceptions were caught, rethrow the first one
|
|
if (mesh_exception) {
|
|
std::rethrow_exception(mesh_exception);
|
|
}
|
|
if (texture_exception) {
|
|
std::rethrow_exception(texture_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 and textures (needed for geom compilation)
|
|
CompileMeshesAndTextures(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 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, nJfe, nJfv,
|
|
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;
|
|
int enableflags = m->opt.enableflags;
|
|
m->opt.enableflags &= ~mjENBL_SLEEP;
|
|
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;
|
|
|
|
// if sleep was enabled, check for trees initialized as sleeping
|
|
bool asleep_init = false;
|
|
if (enableflags & mjENBL_SLEEP) {
|
|
for (int i=0; i < m->ntree; i++) {
|
|
if (m->tree_sleep_policy[i] == mjSLEEP_INIT) {
|
|
asleep_init = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if any trees initialized as sleeping, restore flags before mj_makeData
|
|
if (asleep_init) {
|
|
m->opt.disableflags = disableflags;
|
|
m->opt.enableflags = enableflags;
|
|
}
|
|
|
|
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 unless asleep_init is true (potentially expensive)
|
|
if (!asleep_init) {
|
|
mj_step(m, d);
|
|
}
|
|
|
|
// delete data, restore flags
|
|
mj_deleteData(d);
|
|
m->opt.disableflags = disableflags;
|
|
m->opt.enableflags = enableflags;
|
|
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
|
|
}
|
|
}
|
|
|
|
static void PrintIndent(std::stringstream& ss, int depth) {
|
|
// A static string of spaces, created only once during the program's lifetime.
|
|
static const std::string spaces(1024, ' ');
|
|
|
|
if (depth > 0) {
|
|
// Write 'depth * 2' spaces directly to the stringstream
|
|
// without creating any new std::string objects.
|
|
ss.write(spaces.c_str(), std::min((size_t)depth * 2, spaces.length()));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void mjCModel::PrintTree(std::stringstream& tree, const mjCBody* body, int depth) {
|
|
if (depth == 1024) {
|
|
throw mjCError(body, "depth limit exceeded in signature computation");
|
|
}
|
|
PrintIndent(tree, depth);
|
|
tree << "<body>\n";
|
|
for (const auto& joint : body->joints) {
|
|
PrintIndent(tree, depth + 1);
|
|
tree << "<joint>" << std::to_string(joint->nq()) << "</joint>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->geoms.size(); ++i) {
|
|
PrintIndent(tree, depth + 1);
|
|
tree << "<geom/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->sites.size(); ++i) {
|
|
PrintIndent(tree, depth + 1);
|
|
tree << "<site/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->cameras.size(); ++i) {
|
|
PrintIndent(tree, depth + 1);
|
|
tree << "<camera/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->lights.size(); ++i) {
|
|
PrintIndent(tree, depth + 1);
|
|
tree << "<light/>\n";
|
|
}
|
|
for (uint64_t i = 0; i < body->bodies.size(); ++i) {
|
|
PrintTree(tree, body->bodies[i], depth + 1);
|
|
}
|
|
PrintIndent(tree, depth);
|
|
tree << "</body>\n";
|
|
}
|
|
|
|
|
|
|
|
uint64_t mjCModel::Signature() {
|
|
std::stringstream tree;
|
|
tree << "\n";
|
|
PrintTree(tree, 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.str().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];
|
|
memcpy(userdata, modeldata, nrow*ncol*sizeof(float));
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|