From 215433f5f7b8ffa0e11ddfde0a3f0f74836f4a71 Mon Sep 17 00:00:00 2001
From: Saran Tunyasuvunakool
Date: Wed, 30 Aug 2023 04:28:17 -0700
Subject: [PATCH 01/38] Implement mj_stackAlloc and mj_arenaAlloc alignment
using bitwise-and.
This implementation explicitly avoids calling division instructions (which are generally expensive) even when link-time optimization is not active or if the alloc functions aren't inlined for some reason.
Also make mj_stackAlloc cheaper for external callers who may be linking against MuJoCo dynamically (and therefore cannot benefit from inlining).
PiperOrigin-RevId: 561299565
Change-Id: Iea4be5bf6d351ddf0c9d1fd8bd89a0c207b39309
---
doc/APIreference/functions.rst | 3 ++-
include/mujoco/mujoco.h | 3 ++-
introspect/functions.py | 2 +-
src/engine/engine_io.c | 9 +++++++--
src/engine/engine_io.h | 4 ++--
5 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst
index 963bc409..c80c8358 100644
--- a/doc/APIreference/functions.rst
+++ b/doc/APIreference/functions.rst
@@ -1239,7 +1239,8 @@ mj_stackAlloc
.. mujoco-include:: mj_stackAlloc
-Allocate a specific number of bytes on :ref:`mjData` stack. Call mju_error on stack overflow.
+Allocate a number of bytes on :ref:`mjData` stack at a specific alignment which must be a power of 2.
+Call mju_error on stack overflow.
.. _mj_stackAllocNum:
diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h
index 706def25..19c37f67 100644
--- a/include/mujoco/mujoco.h
+++ b/include/mujoco/mujoco.h
@@ -188,7 +188,8 @@ MJAPI void mj_resetDataDebug(const mjModel* m, mjData* d, unsigned char debug_va
// Reset data, set fields from specified keyframe.
MJAPI void mj_resetDataKeyframe(const mjModel* m, mjData* d, int key);
-// Allocate a specific number of bytes on mjData stack. Call mju_error on stack overflow.
+// Allocate a number of bytes on mjData stack at a specific alignment which must be a power of 2.
+// Call mju_error on stack overflow.
MJAPI void* mj_stackAlloc(mjData* d, size_t bytes, size_t alignment);
// Allocate array of mjtNums on mjData stack. Call mju_error on stack overflow.
diff --git a/introspect/functions.py b/introspect/functions.py
index 87b1bde2..a5bbf038 100644
--- a/introspect/functions.py
+++ b/introspect/functions.py
@@ -699,7 +699,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
type=ValueType(name='size_t'),
),
),
- doc='Allocate a specific number of bytes on mjData stack. Call mju_error on stack overflow.', # pylint: disable=line-too-long
+ doc='Allocate a number of bytes on mjData stack at a specific alignment which must be a power of 2. Call mju_error on stack overflow.', # pylint: disable=line-too-long
)),
('mj_stackAllocNum',
FunctionDecl(
diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c
index d62044e7..5b13811f 100644
--- a/src/engine/engine_io.c
+++ b/src/engine/engine_io.c
@@ -45,6 +45,11 @@
static const int MAX_ARRAY_SIZE = INT_MAX / 4;
+// compute a % b assuming that the second argument is a power of 2
+static inline size_t modpow2(size_t a, size_t b) {
+ return a & (b - 1);
+}
+
//------------------------------ mjLROpt -----------------------------------------------------------
// set default options for length range computation
@@ -1200,7 +1205,7 @@ mjData* mj_copyData(mjData* dest, const mjModel* m, const mjData* src) {
// allocate memory from the mjData arena
void* mj_arenaAlloc(mjData* d, size_t bytes, size_t alignment) {
- size_t misalignment = d->parena % alignment;
+ size_t misalignment = modpow2(d->parena, alignment);
size_t padding = misalignment ? alignment - misalignment : 0;
// check size
@@ -1255,7 +1260,7 @@ static inline void* stackalloc(mjData* d, size_t size, size_t alignment) {
uintptr_t start_ptr = end_ptr - (size + mjREDZONE);
// align the pointer
- start_ptr -= start_ptr % alignment;
+ start_ptr -= modpow2(start_ptr, alignment);
// new top of the stack
uintptr_t new_pstack_ptr = start_ptr - mjREDZONE;
diff --git a/src/engine/engine_io.h b/src/engine/engine_io.h
index 0916d769..137a00f4 100644
--- a/src/engine/engine_io.h
+++ b/src/engine/engine_io.h
@@ -102,10 +102,10 @@ MJAPI void mj_resetDataDebug(const mjModel* m, mjData* d, unsigned char debug_va
// reset data, set fields from specified keyframe
MJAPI void mj_resetDataKeyframe(const mjModel* m, mjData* d, int key);
-// mjData arena allocate
+// mjData arena allocate (alignment must be a power of 2)
MJAPI void* mj_arenaAlloc(mjData* d, size_t bytes, size_t alignment);
-// mjData stack allocate
+// mjData stack allocate (alignment must be a power of 2)
MJAPI void* mj_stackAlloc(mjData* d, size_t bytes, size_t alignment);
// mjData stack allocate for array of mjtNums
From f86e8b449fdef29d276cd78ddd9d36e90681f742 Mon Sep 17 00:00:00 2001
From: Kyle Bayes
Date: Wed, 30 Aug 2023 04:46:32 -0700
Subject: [PATCH 02/38] Refactor VFS logic out of ResourceProvider plugin code.
PiperOrigin-RevId: 561302536
Change-Id: I952f9e76e85a7e3a111e0338f307378a6ff58197
---
doc/includes/references.h | 47 ++++------
include/mujoco/mjmodel.h | 31 ++-----
include/mujoco/mjplugin.h | 23 +++--
introspect/structs.py | 2 +-
src/engine/engine_io.c | 30 ++----
src/engine/engine_plugin.cc | 118 ++++--------------------
src/engine/engine_plugin.h | 3 -
src/engine/engine_resource.c | 121 ++++++++-----------------
src/engine/engine_resource.h | 13 ++-
src/engine/engine_vfs.c | 65 +++++++++----
src/engine/engine_vfs.h | 5 +-
src/user/user_mesh.cc | 9 +-
src/user/user_model.cc | 14 +--
src/user/user_model.h | 6 +-
src/user/user_objects.cc | 42 ++++-----
src/user/user_objects.h | 19 ++--
src/xml/xml.cc | 24 ++---
src/xml/xml.h | 2 +-
src/xml/xml_api.cc | 38 ++------
test/engine/engine_resource_test.cc | 131 +++++++++++++++------------
test/fixture.cc | 10 +-
unity/Runtime/Bindings/MjBindings.cs | 2 +-
22 files changed, 310 insertions(+), 445 deletions(-)
diff --git a/doc/includes/references.h b/doc/includes/references.h
index 3a4f910f..35aaa77c 100644
--- a/doc/includes/references.h
+++ b/doc/includes/references.h
@@ -655,28 +655,13 @@ struct mjLROpt_ { // options for mj_setLengthRange()
mjtNum tolrange; // convergence tolerance (relative to range)
};
typedef struct mjLROpt_ mjLROpt;
-struct mjVFS_ { // virtual file system for loading from memory
- int nfile; // number of files present
- char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
- int filesize[mjMAXVFS]; // file size in bytes
- void* filedata[mjMAXVFS]; // buffer with file data
+struct mjVFS_ { // virtual file system for loading from memory
+ int nfile; // number of files present
+ char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
+ size_t filesize[mjMAXVFS]; // file size in bytes
+ void* filedata[mjMAXVFS]; // buffer with file data
};
typedef struct mjVFS_ mjVFS;
-struct mjResource_ {
- char* name; // name of resource (filename, etc)
- void* data; // opaque data pointer
- const void* provider_data; // opaque resource provider data
-
- // reading callback from resource provider
- int (*read)(struct mjResource_* resource, const void** buffer);
-
- // closing callback from resource provider
- void (*close)(struct mjResource_* resource);
-
- // getdir callback from resource provider
- void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir);
-};
-typedef struct mjResource_ mjResource;
struct mjOption_ { // physics options
// timing parameters
mjtNum timestep; // timestep
@@ -1262,15 +1247,21 @@ struct mjModel_ {
int* names_map; // internal hash map of names (nnames_map x 1)
};
typedef struct mjModel_ mjModel;
-struct mjpResourceProvider_ {
- const char* prefix; // prefix for match against a resource name
- mjfOpenResource open; // opening callback
- mjfReadResource read; // reading callback
- mjfCloseResource close; // closing callback
- mjfGetResourceDir getdir; // getdir callback (optional)
- void* data; // opaque data pointer (resource invariant)
+struct mjResource_ {
+ char* name; // name of resource (filename, etc)
+ void* data; // opaque data pointer
+ const struct mjpResourceProvider* provider; // pointer to the provider
};
-typedef struct mjpResourceProvider_ mjpResourceProvider;
+typedef struct mjResource_ mjResource;
+struct mjpResourceProvider {
+ const char* prefix; // prefix for match against a resource name
+ mjfOpenResource open; // opening callback
+ mjfReadResource read; // reading callback
+ mjfCloseResource close; // closing callback
+ mjfGetResourceDir getdir; // get directory callback (optional)
+ void* data; // opaque data pointer (resource invariant)
+};
+typedef struct mjpResourceProvider mjpResourceProvider;
typedef enum mjtPluginCapabilityBit_ {
mjPLUGIN_ACTUATOR = 1<<0, // actuator forces
mjPLUGIN_SENSOR = 1<<1, // sensor measurements
diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h
index 61f70100..38f28f44 100644
--- a/include/mujoco/mjmodel.h
+++ b/include/mujoco/mjmodel.h
@@ -17,6 +17,7 @@
#include
+
#include
// global constants
@@ -378,34 +379,14 @@ typedef struct mjLROpt_ mjLROpt;
//---------------------------------- mjVFS ---------------------------------------------------------
-struct mjVFS_ { // virtual file system for loading from memory
- int nfile; // number of files present
- char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
- int filesize[mjMAXVFS]; // file size in bytes
- void* filedata[mjMAXVFS]; // buffer with file data
+struct mjVFS_ { // virtual file system for loading from memory
+ int nfile; // number of files present
+ char filename[mjMAXVFS][mjMAXVFSNAME]; // file name without path
+ size_t filesize[mjMAXVFS]; // file size in bytes
+ void* filedata[mjMAXVFS]; // buffer with file data
};
typedef struct mjVFS_ mjVFS;
-
-//---------------------------------- mjResource ----------------------------------------------------
-
-struct mjResource_ {
- char* name; // name of resource (filename, etc)
- void* data; // opaque data pointer
- const void* provider_data; // opaque resource provider data
-
- // reading callback from resource provider
- int (*read)(struct mjResource_* resource, const void** buffer);
-
- // closing callback from resource provider
- void (*close)(struct mjResource_* resource);
-
- // getdir callback from resource provider
- void (*getdir)(struct mjResource_* resource, const char** dir, int* ndir);
-};
-typedef struct mjResource_ mjResource;
-
-
//---------------------------------- mjOption ------------------------------------------------------
struct mjOption_ { // physics options
diff --git a/include/mujoco/mjplugin.h b/include/mujoco/mjplugin.h
index a6936e20..aa23bc44 100644
--- a/include/mujoco/mjplugin.h
+++ b/include/mujoco/mjplugin.h
@@ -22,7 +22,12 @@
//---------------------------------- Resource Provider ---------------------------------------------
-#define mjVFS_PREFIX "vfs" // prefix for VFS providers
+struct mjResource_ {
+ char* name; // name of resource (filename, etc)
+ void* data; // opaque data pointer
+ const struct mjpResourceProvider* provider; // pointer to the provider
+};
+typedef struct mjResource_ mjResource;
// callback for opeing a resource, returns zero on failure
typedef int (*mjfOpenResource)(mjResource* resource);
@@ -39,15 +44,15 @@ typedef void (*mjfCloseResource)(mjResource* resource);
typedef void (*mjfGetResourceDir)(mjResource* resource, const char** dir, int* ndir);
// struct describing a single resource provider
-struct mjpResourceProvider_ {
- const char* prefix; // prefix for match against a resource name
- mjfOpenResource open; // opening callback
- mjfReadResource read; // reading callback
- mjfCloseResource close; // closing callback
- mjfGetResourceDir getdir; // getdir callback (optional)
- void* data; // opaque data pointer (resource invariant)
+struct mjpResourceProvider {
+ const char* prefix; // prefix for match against a resource name
+ mjfOpenResource open; // opening callback
+ mjfReadResource read; // reading callback
+ mjfCloseResource close; // closing callback
+ mjfGetResourceDir getdir; // get directory callback (optional)
+ void* data; // opaque data pointer (resource invariant)
};
-typedef struct mjpResourceProvider_ mjpResourceProvider;
+typedef struct mjpResourceProvider mjpResourceProvider;
//---------------------------------- Plugins -------------------------------------------------------
diff --git a/introspect/structs.py b/introspect/structs.py
index f0cc95d9..5ac4330a 100644
--- a/introspect/structs.py
+++ b/introspect/structs.py
@@ -106,7 +106,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
StructFieldDecl(
name='filesize',
type=ArrayType(
- inner_type=ValueType(name='int'),
+ inner_type=ValueType(name='size_t'),
extents=(2000,),
),
doc='file size in bytes',
diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c
index 5b13811f..159ab555 100644
--- a/src/engine/engine_io.c
+++ b/src/engine/engine_io.c
@@ -661,7 +661,7 @@ void mj_saveModel(const mjModel* m, const char* filename, void* buffer, int buff
// load model from binary MJB resource
-static mjModel* _mj_loadModel(const char* filename, int vfs_provider) {
+mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
int header[NHEADER] = {0};
int expected_header[NHEADER] = {ID, sizeof(mjtNum), getnint(), getnsize(), getnptr()};
int ints[256];
@@ -670,8 +670,11 @@ static mjModel* _mj_loadModel(const char* filename, int vfs_provider) {
mjModel *m = 0;
mjResource* r = NULL;
- if((r = mju_openResource(filename, vfs_provider)) == NULL) {
- return NULL;
+ // first try vfs, otherwise try a provider or OS filesystem
+ if ((r = mju_openVfsResource(filename, vfs)) == NULL) {
+ if ((r = mju_openResource(filename)) == NULL) {
+ return NULL;
+ }
}
const void* buffer = NULL;
@@ -785,27 +788,6 @@ static mjModel* _mj_loadModel(const char* filename, int vfs_provider) {
-// load model from binary MJB file
-// if vfs is not NULL, look up file in vfs before reading from disk
-mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
- if (vfs == NULL) {
- return _mj_loadModel(filename, 0);
- }
-
- int index = mj_registerVfsProvider(vfs);
- if (index < 1) {
- mjERROR("could not allocate memory");
- return NULL;
- }
-
- mjModel* model = _mj_loadModel(filename, index);
-
- mjp_unregisterResourceProvider(index);
- return model;
-}
-
-
-
// de-allocate mjModel
void mj_deleteModel(mjModel* m) {
if (m) {
diff --git a/src/engine/engine_plugin.cc b/src/engine/engine_plugin.cc
index 1b314b5d..52e86455 100644
--- a/src/engine/engine_plugin.cc
+++ b/src/engine/engine_plugin.cc
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -57,9 +58,6 @@ constexpr int kMaxAttributes = 255;
constexpr int kCacheLine = 256;
-// vfs prefix
-constexpr const char* kVfsPrefix = mjVFS_PREFIX;
-
// A table of registered plugins, implemented as a linked list of array "blocks".
// This is a compromise that maintains a good degree of memory locality while not invalidating
// existing pointers when growing the table. It is expected that for most users, the number of
@@ -270,6 +268,7 @@ bool ResourceProvidersAreIdentical(const mjpResourceProvider* p1, const mjpResou
p1->open == p2->open &&
p1->read == p2->read &&
p1->close == p2->close &&
+ p1->getdir == p2->getdir &&
p1->data == p2->data);
}
@@ -532,18 +531,7 @@ void mjp_defaultResourceProvider(mjpResourceProvider* provider) {
// globally register a resource provider (thread-safe), return new slot id
int mjp_registerResourceProvider(const mjpResourceProvider* provider) {
- // check against reserved prefixes
- if (PrefixesAreIdentical(kVfsPrefix, provider->prefix)) {
- mju_warning("provider->prefix is '%s' which is reserved", provider->prefix);
- return -1;
- }
-
- return mjp_registerResourceProviderInternal(provider);
-}
-
-// internal version of mjp_registerResourceProvider without prechecks on reserved prefixes
-int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
- // check if prefix is valid URI scheme format
+ // check if prefix is valid URI scheme format
if (!IsValidURISchemeFormat(provider->prefix)) {
mju_warning("provider->prefix is '%s' which is not a valid URI scheme format",
provider->prefix);
@@ -562,35 +550,25 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
// Do not handle objects with nontrivial destructors outside of this lambda.
// Do not call mju_error inside this lambda.
int slot = [&]() -> int {
- bool vfs_provider = false;
std::unique_ptr prefix;
- // check if this is a VFS provider
- if (PrefixesAreIdentical(mjVFS_PREFIX, provider->prefix)) {
- vfs_provider = true;
- }
-
// copy prefix
- if (!vfs_provider) {
- prefix = CopyName(provider->prefix);
- if (!prefix) {
- if (strklen(provider->prefix) == -1) {
- std::snprintf(err, sizeof(err),
- "provider->prefix length exceeds the maximum limit of %d", kMaxNameLength);
- } else {
- std::snprintf(err, sizeof(err), "failed to allocate memory for resource provider prefix");
- }
- return -1;
+ prefix = CopyName(provider->prefix);
+ if (!prefix) {
+ if (strklen(provider->prefix) == -1) {
+ std::snprintf(err, sizeof(err),
+ "provider->prefix length exceeds the maximum limit of %d", kMaxNameLength);
+ } else {
+ std::snprintf(err, sizeof(err), "failed to allocate memory for resource provider prefix");
}
+ return -1;
}
Global& global = GetGlobal();
auto lock = global.lock_mutex_exclusively();
int count = global.count().load(std::memory_order_acquire);
int local_idx = 0;
- int free_idx = -1, free_local_idx = -1;
PluginTable* table = &global.table();
- PluginTable* free_table = nullptr;
// check if a non-identical provider with the same name has already been registered
for (int i = 0; i < count; ++i, ++local_idx) {
@@ -600,17 +578,7 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
}
mjpResourceProvider& existing = table->plugins[local_idx];
- // VFS providers can safely go in open slots
- if (vfs_provider && existing.prefix == nullptr && free_idx == -1) {
- free_table = table;
- free_idx = i;
- free_local_idx = local_idx;
-
- // can skip the rest
- break;
- }
-
- if (!vfs_provider && existing.prefix != nullptr) {
+ if (existing.prefix != nullptr) {
// if identical then return slot number
if (PrefixesAreIdentical(provider->prefix, existing.prefix)) {
if (ResourceProvidersAreIdentical(provider, &existing)) {
@@ -626,7 +594,7 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
}
// allocate a new block of PluginTable if the last allocated block is full
- if (free_local_idx == -1 && local_idx == PluginTable::kBlockSize) {
+ if (local_idx == PluginTable::kBlockSize) {
local_idx = 0;
table = AddNewTableBlock(table);
if (!table) {
@@ -636,19 +604,12 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
// all checks passed, register the plugin into the global table
mjpResourceProvider& registered_provider = table->plugins[local_idx];
- if (free_local_idx != -1) {
- registered_provider = free_table->plugins[free_local_idx];
- }
-
registered_provider = *provider;
- registered_provider.prefix = (!vfs_provider) ? prefix.release() : kVfsPrefix;
+ registered_provider.prefix = prefix.release();
// increment the global plugin count with a release memory barrier
- if (free_idx == -1) {
- free_idx = count;
- global.count().store(count + 1, std::memory_order_release);
- }
- return free_idx;
+ global.count().store(count + 1, std::memory_order_release);
+ return count;
}();
// ========= ATTENTION! ==========================================================================
@@ -663,46 +624,6 @@ int mjp_registerResourceProviderInternal(const mjpResourceProvider* provider) {
return slot+1;
}
-// globally unregister resource provider (thread-safe)
-// only used for VFS resource providers
-void mjp_unregisterResourceProvider(int slot) {
- // shift slot to zero-index
- slot--;
-
- if (slot < 0) {
- return;
- }
-
- // get global table, acquire lock
- Global& global = GetGlobal();
- auto lock = global.lock_mutex_exclusively();
- int count = global.count().load(std::memory_order_acquire);
-
- if (slot >= count) {
- return;
- }
-
- PluginTable* table = &global.table();
-
- // iterate over blocks in the global table until the local index is less than the block size
- int local_idx = slot;
- while (local_idx >= PluginTable::kBlockSize) {
- local_idx -= PluginTable::kBlockSize;
- table = table->next;
- if (!table) {
- return;
- }
- }
-
- // local_idx is now a valid index into the current block
- mjpResourceProvider& provider = table->plugins[local_idx];
-
- // no-op for anything other than VFS resource providers
- if (provider.prefix == kVfsPrefix) {
- provider.prefix = nullptr;
- }
-}
-
// return the number of globally registered resource providers
int mjp_resourceProviderCount() {
return GetGlobal().count().load(std::memory_order_acquire);
@@ -728,12 +649,6 @@ const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) {
return nullptr;
}
- // since multiple VFS resource providers can be registered with the same
- // prefix, it doesn't make sense to try to match against them
- if (PrefixesAreIdentical(kVfsPrefix, file_prefix.c_str())) {
- return nullptr;
- }
-
Global& global = GetGlobal();
auto lock = global.lock_mutex_exclusively();
PluginTable* table = &global.table();
@@ -743,7 +658,6 @@ const mjpResourceProvider* mjp_getResourceProvider(const char* resource_name) {
for (int i = 0;
i < PluginTable::kBlockSize && found_slot < count;
++i, ++found_slot) {
-
const mjpResourceProvider& provider = table->plugins[i];
const char *prefix = provider.prefix;
diff --git a/src/engine/engine_plugin.h b/src/engine/engine_plugin.h
index 83fbef83..13068a4a 100644
--- a/src/engine/engine_plugin.h
+++ b/src/engine/engine_plugin.h
@@ -31,9 +31,6 @@ MJAPI int mjp_registerPlugin(const mjpPlugin* plugin);
// globally register a resource provider (thread-safe), return new slot id
MJAPI int mjp_registerResourceProvider(const mjpResourceProvider* provider);
-// globally unregister a resource provider (thread-safe)
-MJAPI void mjp_unregisterResourceProvider(int slot);
-
// return the number of globally registered plugins
MJAPI int mjp_pluginCount();
diff --git a/src/engine/engine_resource.c b/src/engine/engine_resource.c
index eba0ad62..bd7280fa 100644
--- a/src/engine/engine_resource.c
+++ b/src/engine/engine_resource.c
@@ -16,40 +16,23 @@
#include
#include
+#include
#include
#include
-#include
#include
#include "engine/engine_plugin.h"
#include "engine/engine_util_errmem.h"
// file buffer used internally for the OS filesystem
typedef struct {
- void* buffer;
- int nbuffer;
+ uint8_t* buffer; // raw bytes from file
+ size_t nbuffer; // size of buffer in bytes
} file_buffer;
-// helper function to fill data from resource provider into provider
-static void fillResource(const mjpResourceProvider* provider, mjResource* resource) {
- if (provider == NULL) {
- resource->read = NULL;
- resource->close = NULL;
- resource->getdir = NULL;
- resource->provider_data = NULL;
- } else {
- resource->read = provider->read;
- resource->close = provider->close;
- resource->getdir = provider->getdir;
- resource->provider_data = provider->data;
- }
-}
-
-
// open the given resource; if the name doesn't have a prefix matching with a
-// resource provider, then the default_provider is used
-// if default_provider non-positive, then the OS filesystem is used
-mjResource* mju_openResource(const char* name, int default_provider) {
+// resource provider, then the OS filesystem is used
+mjResource* mju_openResource(const char* name) {
mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource));
const mjpResourceProvider* provider = NULL;
if (resource == NULL) {
@@ -57,19 +40,22 @@ mjResource* mju_openResource(const char* name, int default_provider) {
return NULL;
}
+ // clear out resource
+ memset(resource, 0, sizeof(mjResource));
+
// copy name
resource->name = mju_malloc(sizeof(char) * (strlen(name) + 1));
if (resource->name == NULL) {
- mju_free(resource);
+ mju_closeResource(resource);
mjERROR("could not allocate memory");
return NULL;
}
- strcpy(resource->name, name);
+ memcpy(resource->name, name, sizeof(char) * (strlen(name) + 1));
// find provider based off prefix of name
provider = mjp_getResourceProvider(name);
if (provider != NULL) {
- fillResource(provider, resource);
+ resource->provider = provider;
if (provider->open(resource)) {
return resource;
}
@@ -77,48 +63,19 @@ mjResource* mju_openResource(const char* name, int default_provider) {
mju_warning("mju_openResource: could not open resource '%s' "
"using a resource provider matching prefix '%s'",
name, provider->prefix);
- mju_free(resource->name);
- mju_free(resource);
- return NULL;
- }
-
- // fallback to default provider
- if (default_provider > 0) {
- provider = mjp_getResourceProviderAtSlot(default_provider);
- if (provider == NULL) {
- mju_warning("mju_openResource: unknown resource provider at slot %d",
- default_provider);
- mju_free(resource->name);
- mju_free(resource);
- return NULL;
- }
-
- fillResource(provider, resource);
- if (provider->open(resource)) {
- return resource;
- }
-
- mju_warning("mju_openResource: could not open resource '%s' "
- "with default provider at slot %d",
- name, default_provider);
- mju_free(resource->name);
- mju_free(resource);
+ mju_closeResource(resource);
return NULL;
}
// lastly fallback to OS filesystem
- else {
- fillResource(NULL, resource);
- resource->data = mju_malloc(sizeof(file_buffer));
- file_buffer* fb = (file_buffer*) resource->data;
- fb->buffer = mju_fileToMemory(name, &(fb->nbuffer));
- if (fb->buffer == NULL) {
- mju_warning("mju_openResource: unknown file '%s'", name);
- mju_free(fb);
- mju_free(resource->name);
- mju_free(resource);
- return NULL;
- }
+ resource->provider = NULL;
+ resource->data = mju_malloc(sizeof(file_buffer));
+ file_buffer* fb = (file_buffer*) resource->data;
+ fb->buffer = mju_fileToMemory(name, &(fb->nbuffer));
+ if (fb->buffer == NULL) {
+ mju_warning("mju_openResource: unknown file '%s'", name);
+ mju_closeResource(resource);
+ return NULL;
}
return resource;
}
@@ -131,20 +88,20 @@ void mju_closeResource(mjResource* resource) {
return;
}
- // use the resource provider to close resource
- if (resource->close) {
- resource->close(resource);
- }
-
- // if provider is NULL, then OS filesystem is used
- else {
+ // use the resource provider close callback
+ if (resource->provider && resource->provider->close) {
+ resource->provider->close(resource);
+ } else {
+ // clear OS filesystem if present
file_buffer* fb = (file_buffer*) resource->data;
- mju_free(fb->buffer);
- mju_free(fb);
+ if (fb) {
+ if (fb->buffer) mju_free(fb->buffer);
+ mju_free(fb);
+ }
}
- // free name and resource
- mju_free(resource->name);
+ // free resource
+ if (resource->name) mju_free(resource->name);
mju_free(resource);
}
@@ -157,12 +114,12 @@ int mju_readResource(mjResource* resource, const void** buffer) {
return 0;
}
- if (resource->read) {
- return resource->read(resource, buffer);
+ if (resource->provider) {
+ return resource->provider->read(resource, buffer);
}
- // if provider read callback is NULL, then OS filesystem is used
+ // if provider is NULL, then OS filesystem is used
const file_buffer* fb = (file_buffer*) resource->data;
*buffer = fb->buffer;
return fb->nbuffer;
@@ -175,14 +132,14 @@ void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir) {
*dir = NULL;
*ndir = 0;
- if (!resource) {
+ if (resource == NULL) {
return;
}
// provider is not OS filesystem
- if (resource->read) {
- if (resource->getdir) {
- resource->getdir(resource, dir, ndir);
+ if (resource->provider) {
+ if (resource->provider->getdir) {
+ resource->provider->getdir(resource, dir, ndir);
}
} else {
*dir = resource->name;
@@ -211,7 +168,7 @@ int mju_dirnamelen(const char* path) {
// read file into memory buffer (allocated here with mju_malloc)
-void* mju_fileToMemory(const char* filename, int* filesize) {
+void* mju_fileToMemory(const char* filename, size_t* filesize) {
// open file
*filesize = 0;
FILE* fp = fopen(filename, "rb");
diff --git a/src/engine/engine_resource.h b/src/engine/engine_resource.h
index 7c659d0b..b45b3a82 100644
--- a/src/engine/engine_resource.h
+++ b/src/engine/engine_resource.h
@@ -15,6 +15,8 @@
#ifndef MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
#define MUJOCO_SRC_ENGINE_ENGINE_RESOURCE_H_
+#include
+
#include
#include "engine/engine_plugin.h"
@@ -23,9 +25,8 @@ extern "C" {
#endif
// open the given resource; if the name doesn't have a prefix matching with a
-// resource provider, then the default_provider is used
-// if default_provider non-positive, then the OS filesystem is used
-MJAPI mjResource* mju_openResource(const char* name, int default_provider);
+// resource provider, then the OS filesystem is used
+MJAPI mjResource* mju_openResource(const char* name);
// close the given resource; no-op if resource is NULL
MJAPI void mju_closeResource(mjResource* resource);
@@ -37,11 +38,15 @@ MJAPI int mju_readResource(mjResource* resource, const void** buffer);
// sets for a resource with a name partitioned as {dir}{filename}, the dir and ndir pointers
MJAPI void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir);
+// Returns > 0 if resource has been modified since last read, 0 if not, and < 0
+// if inconclusive
+MJAPI int mju_isModifiedResource(const mjResource* resource);
+
// get the length of the dirname portion of a given path
int mju_dirnamelen(const char* path);
// read file into memory buffer (allocated here with mju_malloc)
-void* mju_fileToMemory(const char* filename, int* filesize);
+void* mju_fileToMemory(const char* filename, size_t* filesize);
#ifdef __cplusplus
}
diff --git a/src/engine/engine_vfs.c b/src/engine/engine_vfs.c
index e4246bb7..0535111e 100644
--- a/src/engine/engine_vfs.c
+++ b/src/engine/engine_vfs.c
@@ -14,10 +14,10 @@
#include "engine/engine_vfs.h"
+#include
#include
#include "engine/engine_array_safety.h"
-#include "engine/engine_plugin.h"
#include "engine/engine_resource.h"
#include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h"
@@ -90,7 +90,7 @@ int mj_addFileVFS(mjVFS* vfs, const char* directory, const char* filename) {
mjSTRNCPY(vfs->filename[vfs->nfile], newname);
// allocate and read
- int filesize = 0;
+ size_t filesize = 0;
vfs->filedata[vfs->nfile] = mju_fileToMemory(fullname, &filesize);
if (!vfs->filedata[vfs->nfile]) {
return -1;
@@ -211,11 +211,11 @@ void mj_deleteVFS(mjVFS* vfs) {
// open callback for the VFS resource provider
static int vfs_open_callback(mjResource* resource) {
- if (!resource || !resource->provider_data || !resource->name) {
+ if (!resource || !resource->name || !resource->data) {
return 0;
}
- const mjVFS* vfs = (const mjVFS*) resource->provider_data;
+ const mjVFS* vfs = (const mjVFS*) resource->data;
return mj_findFileVFS(vfs, resource->name) >= 0;
}
@@ -223,12 +223,12 @@ static int vfs_open_callback(mjResource* resource) {
// read callback for the VFS resource provider
static int vfs_read_callback(mjResource* resource, const void** buffer) {
- if (!resource || !resource->provider_data) {
+ if (!resource || !resource->name || !resource->data) {
*buffer = NULL;
return -1;
}
- const mjVFS* vfs = (const mjVFS*) resource->provider_data;
+ const mjVFS* vfs = (const mjVFS*) resource->data;
int i = mj_findFileVFS(vfs, resource->name);
if (i < 0) {
*buffer = NULL;
@@ -260,16 +260,49 @@ static void vfs_getdir_callback(mjResource* resource, const char** dir, int* ndi
-// registers a VFS resource provider; returns the index of the provider
-int mj_registerVfsProvider(const mjVFS* vfs) {
- mjpResourceProvider provider = {
- .prefix = mjVFS_PREFIX,
- .open = &vfs_open_callback,
- .read = &vfs_read_callback,
- .close = &vfs_close_callback,
- .getdir = &vfs_getdir_callback,
- .data = (void*) vfs
+// open VFS resource
+mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs) {
+ if (vfs == NULL) {
+ return NULL;
+ }
+
+ // VFS provider
+ static struct mjpResourceProvider provider = {
+ .prefix = NULL,
+ .data = NULL,
+ .open = &vfs_open_callback,
+ .read = &vfs_read_callback,
+ .close = &vfs_close_callback,
+ .getdir = &vfs_getdir_callback,
};
- return mjp_registerResourceProviderInternal(&provider);
+ // create resource
+ mjResource* resource = (mjResource*) mju_malloc(sizeof(mjResource));
+ if (resource == NULL) {
+ mjERROR("could not allocate memory");
+ return NULL;
+ }
+
+ // clear out resource
+ memset(resource, 0, sizeof(mjResource));
+
+ // copy name
+ resource->name = mju_malloc(sizeof(char) * (strlen(name) + 1));
+ if (resource->name == NULL) {
+ mju_closeResource(resource);
+ mjERROR("could not allocate memory");
+ return NULL;
+ }
+ memcpy(resource->name, name, sizeof(char) * (strlen(name) + 1));
+ resource->data = (void*) vfs;
+
+ // open resource
+ resource->provider = &provider;
+ if (provider.open(resource)) {
+ return resource;
+ }
+
+ // not found in VFS
+ mju_closeResource(resource);
+ return NULL;
}
diff --git a/src/engine/engine_vfs.h b/src/engine/engine_vfs.h
index 7fa0cd40..b9b578a1 100644
--- a/src/engine/engine_vfs.h
+++ b/src/engine/engine_vfs.h
@@ -17,6 +17,7 @@
#include
#include
+#include
#ifdef __cplusplus
extern "C" {
@@ -40,8 +41,8 @@ MJAPI int mj_deleteFileVFS(mjVFS* vfs, const char* filename);
// delete all files from VFS
MJAPI void mj_deleteVFS(mjVFS* vfs);
-// registers a VFS resource provider; returns the index of the provider
-MJAPI int mj_registerVfsProvider(const mjVFS* vfs);
+// open VFS resource
+MJAPI mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs);
#ifdef __cplusplus
}
diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc
index 6a04f4b3..548819ab 100644
--- a/src/user/user_mesh.cc
+++ b/src/user/user_mesh.cc
@@ -29,6 +29,7 @@
#include
#include
+#include
#include "cc/array_safety.h"
#include "engine/engine_crossplatform.h"
#include "engine/engine_resource.h"
@@ -329,7 +330,7 @@ void mjCMesh::LoadSDF() {
// compiler
-void mjCMesh::Compile(int vfs_provider) {
+void mjCMesh::Compile(const mjVFS* vfs) {
// load file
if (!file_.empty()) {
// remove path from file if necessary
@@ -348,7 +349,7 @@ void mjCMesh::Compile(int vfs_provider) {
}
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file_);
- mjResource* resource = LoadResource(filename, vfs_provider);
+ mjResource* resource = LoadResource(filename, vfs);
try {
if (asset_type == "model/stl") {
@@ -1836,7 +1837,7 @@ mjCSkin::~mjCSkin() {
// compiler
-void mjCSkin::Compile(int vfs_provider) {
+void mjCSkin::Compile(const mjVFS* vfs) {
// load file
if (!file.empty()) {
@@ -1865,7 +1866,7 @@ void mjCSkin::Compile(int vfs_provider) {
}
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
- mjResource* resource = LoadResource(filename, vfs_provider);
+ mjResource* resource = LoadResource(filename, vfs);
try {
LoadSKN(resource);
diff --git a/src/user/user_model.cc b/src/user/user_model.cc
index 3bdde0ed..8b782f08 100644
--- a/src/user/user_model.cc
+++ b/src/user/user_model.cc
@@ -2423,7 +2423,7 @@ static void warninghandler(const char* msg) {
// compiler
-mjModel* mjCModel::Compile(int vfs_provider) {
+mjModel* mjCModel::Compile(const mjVFS* vfs) {
// 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
@@ -2454,7 +2454,7 @@ mjModel* mjCModel::Compile(int vfs_provider) {
// TryCompile resulted in an mju_error which was converted to a longjmp.
throw mjCError(0, "engine error: %s", errortext);
}
- TryCompile(*const_cast(&m), *const_cast(&data), vfs_provider);
+ TryCompile(*const_cast(&m), *const_cast(&data), vfs);
} catch (mjCError err) {
// deallocate everything allocated in Compile
mj_deleteModel(m);
@@ -2480,7 +2480,7 @@ mjModel* mjCModel::Compile(int vfs_provider) {
}
-void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) {
+void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
// check if nan test works
double test = mjNAN;
if (mjuu_defined(test)) {
@@ -2557,7 +2557,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) {
// compile meshes (needed for geom compilation)
for (int i=0; iCompile(vfs_provider);
+ meshes[i]->Compile(vfs);
}
// automatically set nuser fields
@@ -2616,9 +2616,9 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) {
}
// compile all other objects except for keyframes
- for (int i=0; iCompile(vfs_provider);
- for (int i=0; iCompile(vfs_provider);
- for (int i=0; iCompile(vfs_provider);
+ for (int i=0; iCompile(vfs);
+ for (int i=0; iCompile(vfs);
+ for (int i=0; iCompile(vfs);
for (int i=0; iCompile();
for (int i=0; iCompile();
for (int i=0; iCompile();
diff --git a/src/user/user_model.h b/src/user/user_model.h
index 4ac38f3a..f4a3ed68 100644
--- a/src/user/user_model.h
+++ b/src/user/user_model.h
@@ -65,7 +65,7 @@ class mjCModel {
mjCModel(); // constructor
~mjCModel(); // destructor
- mjModel* Compile(int vfs_provider = 0); // COMPILER: construct mjModel
+ mjModel* Compile(const mjVFS* vfs = 0); // COMPILER: construct mjModel
bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back
void FuseStatic(void); // fuse static bodies with parent
void FuseReindex(mjCBody* body); // reindex elements during fuse
@@ -163,8 +163,8 @@ class mjCModel {
int nuser_sensor; // number of mjtNums in sensor_user
private:
- void TryCompile(mjModel*& m, mjData*& d, int vfs_provider);
- mjModel* _Compile(int vfs_provider);
+ void TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs);
+ mjModel* _Compile(const mjVFS* vfs);
void Clear(void); // clear objects allocated by Compile
diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc
index 853d5a70..75eca814 100644
--- a/src/user/user_objects.cc
+++ b/src/user/user_objects.cc
@@ -39,6 +39,7 @@
#include "engine/engine_util_misc.h"
#include "engine/engine_util_solve.h"
#include "engine/engine_util_spatial.h"
+#include "engine/engine_vfs.h"
#include "user/user_model.h"
#include "user/user_util.h"
@@ -495,17 +496,14 @@ mjCBase::mjCBase() {
// load resource if found (fallback to OS filesystem)
-mjResource* mjCBase::LoadResource(string filename, int provider) {
+mjResource* mjCBase::LoadResource(string filename, const mjVFS* vfs) {
mjResource* r = nullptr;
const char* cname = filename.c_str();
- // try reading from given provider
- if ((r = mju_openResource(cname, provider)) == nullptr) {
- if (!provider) {
- throw mjCError(0, "file not found: '%s'", cname);
- }
- // if provider wasn't the OS filesystem try to fallback to OS filesystem
- if ((r = mju_openResource(filename.c_str(), 0)) == nullptr) {
+ // try reading from provided VFS
+ if ((r = mju_openVfsResource(cname, vfs)) == nullptr) {
+ // not in vfs try a provider or fallback to OS filesystem
+ if ((r = mju_openResource(filename.c_str())) == nullptr) {
throw mjCError(this, "resource not found via provider or OS filesystem: '%s'", cname);
}
}
@@ -2194,7 +2192,7 @@ void mjCHField::LoadPNG(mjResource* resource) {
// compiler
-void mjCHField::Compile(int vfs_provider) {
+void mjCHField::Compile(const mjVFS* vfs) {
// check size parameters
for (int i=0; i<4; i++)
if (size[i]<=0)
@@ -2226,7 +2224,7 @@ void mjCHField::Compile(int vfs_provider) {
}
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
- mjResource* resource = LoadResource(filename, vfs_provider);
+ mjResource* resource = LoadResource(filename, vfs);
try {
if (asset_type == "image/png") {
@@ -2617,7 +2615,7 @@ void mjCTexture::LoadCustom(mjResource* resource,
// load from PNG or custom file, flip if specified
-void mjCTexture::LoadFlip(string filename, int vfs_provider,
+void mjCTexture::LoadFlip(string filename, const mjVFS* vfs,
std::vector& image,
unsigned int& w, unsigned int& h) {
std::string asset_type = GetAssetContentType(filename, content_type);
@@ -2631,7 +2629,7 @@ void mjCTexture::LoadFlip(string filename, int vfs_provider,
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
- mjResource* resource = LoadResource(filename, vfs_provider);
+ mjResource* resource = LoadResource(filename, vfs);
try {
if (asset_type == "image/png") {
@@ -2693,11 +2691,11 @@ void mjCTexture::LoadFlip(string filename, int vfs_provider,
// load 2D
-void mjCTexture::Load2D(string filename, int vfs_provider) {
+void mjCTexture::Load2D(string filename, const mjVFS* vfs) {
// load PNG or custom
unsigned int w, h;
std::vector image;
- LoadFlip(filename, vfs_provider, image, w, h);
+ LoadFlip(filename, vfs, image, w, h);
// assign size
width = w;
@@ -2716,7 +2714,7 @@ void mjCTexture::Load2D(string filename, int vfs_provider) {
// load cube or skybox from single file (repeated or grid)
-void mjCTexture::LoadCubeSingle(string filename, int vfs_provider) {
+void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
// check gridsize
if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) {
throw mjCError(this,
@@ -2727,7 +2725,7 @@ void mjCTexture::LoadCubeSingle(string filename, int vfs_provider) {
// load PNG or custom
unsigned int w, h;
std::vector image;
- LoadFlip(filename, vfs_provider, image, w, h);
+ LoadFlip(filename, vfs, image, w, h);
// check gridsize for compatibility
if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) {
@@ -2816,7 +2814,7 @@ void mjCTexture::LoadCubeSingle(string filename, int vfs_provider) {
// load cube or skybox from separate file
-void mjCTexture::LoadCubeSeparate(int vfs_provider) {
+void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
// keep track of which faces were defined
int loaded[6] = {0, 0, 0, 0, 0, 0};
@@ -2834,7 +2832,7 @@ void mjCTexture::LoadCubeSeparate(int vfs_provider) {
// load PNG or custom
unsigned int w, h;
std::vector image;
- LoadFlip(filename, vfs_provider, image, w, h);
+ LoadFlip(filename, vfs, image, w, h);
// PNG must be square
if (w!=h) {
@@ -2888,7 +2886,7 @@ void mjCTexture::LoadCubeSeparate(int vfs_provider) {
// compiler
-void mjCTexture::Compile(int vfs_provider) {
+void mjCTexture::Compile(const mjVFS* vfs) {
// builtin
if (builtin!=mjBUILTIN_NONE) {
// check size
@@ -2931,9 +2929,9 @@ void mjCTexture::Compile(int vfs_provider) {
// dispatch
if (type==mjTEXTURE_2D) {
- Load2D(filename, vfs_provider);
+ Load2D(filename, vfs);
} else {
- LoadCubeSingle(filename, vfs_provider);
+ LoadCubeSingle(filename, vfs);
}
}
@@ -2961,7 +2959,7 @@ void mjCTexture::Compile(int vfs_provider) {
}
// only cube and skybox
- LoadCubeSeparate(vfs_provider);
+ LoadCubeSeparate(vfs);
}
// make sure someone allocated data; SHOULD NOT OCCUR
diff --git a/src/user/user_objects.h b/src/user/user_objects.h
index e5bbd58a..148b3997 100644
--- a/src/user/user_objects.h
+++ b/src/user/user_objects.h
@@ -23,6 +23,7 @@
#include "lodepng.h"
#include
+#include
// forward declarations of all mjC/X classes
class mjCError;
@@ -173,7 +174,7 @@ class mjCBase {
public:
// load resource if found (fallback to OS filesystem)
- mjResource* LoadResource(std::string filename, int provider);
+ mjResource* LoadResource(std::string filename, const mjVFS* vfs);
// Get and sanitize content type from raw_text if not empty, otherwise parse
// content type from resource_name; throw on failure
@@ -572,7 +573,7 @@ class mjCMesh: public mjCBase {
void set_usertexcoord(std::optional>&& usertexcoord);
void set_userface(std::optional>&& userface);
- void Compile(int vfs_provider); // compiler
+ void Compile(const mjVFS* vfs); // compiler
double* GetPosPtr(mjtMeshType type); // get position
double* GetQuatPtr(mjtMeshType type); // get orientation
double* GetInertiaBoxPtr(mjtMeshType type); // get inertia box
@@ -696,7 +697,7 @@ class mjCSkin: public mjCBase {
private:
mjCSkin(mjCModel* = 0); // constructor
~mjCSkin(); // destructor
- void Compile(int vfs_provider); // compiler
+ void Compile(const mjVFS* vfs); // compiler
void LoadSKN(mjResource* resource); // load skin in SKN BIN format
int matid; // material id
@@ -723,7 +724,7 @@ class mjCHField : public mjCBase {
private:
mjCHField(mjCModel* model); // constructor
~mjCHField(); // destructor
- void Compile(int vfs_provider); // compiler
+ void Compile(const mjVFS* vfs); // compiler
void LoadCustom(mjResource* resource); // load from custom format
void LoadPNG(mjResource* resource); // load from PNG format
@@ -768,15 +769,15 @@ class mjCTexture : public mjCBase {
private:
mjCTexture(mjCModel*); // constructor
~mjCTexture(); // destructior
- void Compile(int vfs_provider); // compiler
+ void Compile(const mjVFS* vfs); // compiler
void Builtin2D(void); // make builtin 2D
void BuiltinCube(void); // make builtin cube
- void Load2D(std::string filename, int vfs_provider); // load 2D from file
- void LoadCubeSingle(std::string filename, int vfs_provider); // load cube from single file
- void LoadCubeSeparate(int vfs_provider); // load cube from separate files
+ void Load2D(std::string filename, const mjVFS* vfs); // load 2D from file
+ void LoadCubeSingle(std::string filename, const mjVFS* vfs); // load cube from single file
+ void LoadCubeSeparate(const mjVFS* vfs); // load cube from separate files
- void LoadFlip(std::string filename, int vfs_provider, // load and flip
+ void LoadFlip(std::string filename, const mjVFS* vfs, // load and flip
std::vector& image,
unsigned int& w, unsigned int& h);
diff --git a/src/xml/xml.cc b/src/xml/xml.cc
index 7b8ec3cd..fda3cde8 100644
--- a/src/xml/xml.cc
+++ b/src/xml/xml.cc
@@ -22,9 +22,11 @@
#include
+#include
#include "cc/array_safety.h"
#include "engine/engine_crossplatform.h"
#include "engine/engine_resource.h"
+#include "engine/engine_vfs.h"
#include "user/user_model.h"
#include "user/user_util.h"
#include "xml/xml_native_reader.h"
@@ -110,7 +112,7 @@ string mjWriteXML(mjCModel* model, char* error, int error_sz) {
// find include elements recursively, replace them with subtree from xml file
static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
- int vfs_provider, vector& included) {
+ const mjVFS* vfs, vector& included) {
// include element: process
if (!strcasecmp(elem->Value(), "include")) {
// make sure include has no children
@@ -133,9 +135,9 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
// get data source
mjResource *resource = nullptr;
const char* xmlstring = nullptr;
- if ((resource = mju_openResource(filename.c_str(), vfs_provider)) == nullptr) {
- // load from OS filesystem
- if (!vfs_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
+ if ((resource = mju_openVfsResource(filename.c_str(), vfs)) == nullptr) {
+ // load from provider or OS filesystem
+ if ((resource = mju_openResource(filename.c_str())) == nullptr) {
throw mjXError(elem, "Could not open file '%s'", filename.c_str());
}
}
@@ -196,14 +198,14 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
}
// run XMLInclude on first new child
- return mjIncludeXML(first->ToElement(), dir, vfs_provider, included);
+ return mjIncludeXML(first->ToElement(), dir, vfs, included);
}
// otherwise check all child elements, return self
else {
XMLElement* child = elem->FirstChildElement();
while (child) {
- child = mjIncludeXML(child, dir, vfs_provider, included);
+ child = mjIncludeXML(child, dir, vfs, included);
if (child) {
child = child->NextSiblingElement();
}
@@ -215,7 +217,7 @@ static XMLElement* mjIncludeXML(XMLElement* elem, string dir,
// Main parser function
-mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int error_sz) {
+mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz) {
LocaleOverride locale_override;
// check arguments
@@ -235,9 +237,9 @@ mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int er
// get data source
mjResource* resource = nullptr;
const char* xmlstring = nullptr;
- if ((resource = mju_openResource(filename, vfs_provider)) == nullptr) {
- // load from OS filesystem
- if (!vfs_provider || (resource = mju_openResource(filename, 0)) == nullptr) {
+ if ((resource = mju_openVfsResource(filename, vfs)) == nullptr) {
+ // load from provider or fallback to OS filesystem
+ if ((resource = mju_openResource(filename)) == nullptr) {
if (error) {
snprintf(error, error_sz, "mjParseXML: could not open file '%s'", filename);
}
@@ -303,7 +305,7 @@ mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int er
// find include elements, replace them with subtree from xml file
vector included;
included.push_back(filename);
- mjIncludeXML(root, model->modelfiledir, vfs_provider, included);
+ mjIncludeXML(root, model->modelfiledir, vfs, included);
// parse MuJoCo model
mjXReader parser;
diff --git a/src/xml/xml.h b/src/xml/xml.h
index acbe81ce..84ef9ae3 100644
--- a/src/xml/xml.h
+++ b/src/xml/xml.h
@@ -26,7 +26,7 @@
std::string mjWriteXML(mjCModel* model, char* error, int error_sz);
// Main parser function
-mjCModel* mjParseXML(const char* filename, int vfs_provider, char* error, int error_sz);
+mjCModel* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz);
#endif // MUJOCO_SRC_XML_XML_H_
diff --git a/src/xml/xml_api.cc b/src/xml/xml_api.cc
index bad07392..4aa468eb 100644
--- a/src/xml/xml_api.cc
+++ b/src/xml/xml_api.cc
@@ -70,20 +70,23 @@ static std::mutex themutex;
//---------------------------------- Functions -----------------------------------------------------
-// mj_loadXML helper function
-mjModel* _loadXML(const char* filename, int vfs_provider,
- char* error, int error_sz) {
+// parse XML file in MJCF or URDF format, compile it, return low-level model
+// if vfs is not NULL, look up files in vfs before reading from disk
+// error can be NULL; otherwise assumed to have size error_sz
+mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
+ char* error, int error_sz) {
+
// serialize access to themodel
std::lock_guard lock(themutex);
// parse new model
- mjCModel* newmodel = mjParseXML(filename, vfs_provider, error, error_sz);
+ mjCModel* newmodel = mjParseXML(filename, vfs, error, error_sz);
if (!newmodel) {
return nullptr;
}
// compile new model
- mjModel* m = newmodel->Compile(vfs_provider);
+ mjModel* m = newmodel->Compile(vfs);
if (!m) {
mjCopyError(error, newmodel->GetError().message, error_sz);
delete newmodel;
@@ -106,31 +109,6 @@ mjModel* _loadXML(const char* filename, int vfs_provider,
-// parse XML file in MJCF or URDF format, compile it, return low-level model
-// if vfs is not NULL, look up files in vfs before reading from disk
-// error can be NULL; otherwise assumed to have size error_sz
-mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
- char* error, int error_sz) {
-
- if (vfs == nullptr) {
- return _loadXML(filename, 0, error, error_sz);
- }
-
- int index = mj_registerVfsProvider(vfs);
- if (index < 1) {
- if (error) {
- snprintf(error, error_sz, "mj_loadXML: could not register VFS");
- }
- return nullptr;
- }
-
- mjModel* model = _loadXML(filename, index, error, error_sz);
- mjp_unregisterResourceProvider(index);
- return model;
-}
-
-
-
// update XML data structures with info from low-level model, save as MJCF
// returns 1 if successful, 0 otherwise
// error can be NULL; otherwise assumed to have size error_sz
diff --git a/test/engine/engine_resource_test.cc b/test/engine/engine_resource_test.cc
index 686c297d..c3287375 100644
--- a/test/engine/engine_resource_test.cc
+++ b/test/engine/engine_resource_test.cc
@@ -19,12 +19,10 @@
#include
#include
#include "src/cc/array_safety.h"
-#include
#include
#include
#include "src/engine/engine_plugin.h"
#include "src/engine/engine_resource.h"
-#include "src/engine/engine_vfs.h"
#include "test/fixture.h"
namespace mujoco {
@@ -68,20 +66,48 @@ void close_str(mjResource* resource) {
}
TEST_F(ResourceTest, RegisterProviderSuccess) {
- mjpResourceProvider provider = {"my-prefix.123+45", open_nop, read_nop, close_nop,
- nullptr};
+ mjpResourceProvider provider = {
+ "my-prefix.123+45", open_nop, read_nop, close_nop
+ };
int count1 = mjp_resourceProviderCount();
int i = mjp_registerResourceProvider(&provider);
int count2 = mjp_resourceProviderCount();
EXPECT_GT(i, 0);
- EXPECT_EQ(count1+1, count2);
+ EXPECT_EQ(count2 - count1, 1);
+}
+
+TEST_F(ResourceTest, RegisterProviderMultipleSuccess) {
+ mjpResourceProvider provider = {
+ "my-prefix.123+44", open_nop, read_nop, close_nop
+ };
+
+ mjpResourceProvider provider2 = {
+ "my-prefix.123+46", open_nop, read_nop, close_nop
+ };
+
+
+ mjpResourceProvider provider3 = {
+ "my-prefix.123+41", open_nop, read_nop, close_nop
+ };
+
+ int count1 = mjp_resourceProviderCount();
+ int i = mjp_registerResourceProvider(&provider);
+ int i2 = mjp_registerResourceProvider(&provider2);
+ int i3 = mjp_registerResourceProvider(&provider3);
+ int count2 = mjp_resourceProviderCount();
+
+ EXPECT_GT(i, 0);
+ EXPECT_GT(i2, 0);
+ EXPECT_GT(i3, 0);
+ EXPECT_EQ(count2 - count1, 3);
}
TEST_F(ResourceTest, RegisterProviderMissingCallbacks) {
- mjpResourceProvider provider = {"myprefix", nullptr, nullptr, nullptr,
- nullptr};
+ mjpResourceProvider provider = {
+ "myprefix"
+ };
// install warning handler
static char warning[1024];
@@ -98,7 +124,9 @@ TEST_F(ResourceTest, RegisterProviderMissingCallbacks) {
}
TEST_F(ResourceTest, RegisterProviderMissingPrefix) {
- mjpResourceProvider provider = {"", open_nop, read_nop, close_nop, nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "", open_nop, read_nop, close_nop
+ };
// install warning handler
static char warning[1024];
@@ -115,7 +143,9 @@ TEST_F(ResourceTest, RegisterProviderMissingPrefix) {
}
TEST_F(ResourceTest, RegisterProviderInvalidPrefix1) {
- mjpResourceProvider provider = {"1invalid", open_nop, read_nop, close_nop, nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "1invalid", open_nop, read_nop, close_nop
+ };
// install warning handler
static char warning[1024];
@@ -132,7 +162,9 @@ TEST_F(ResourceTest, RegisterProviderInvalidPrefix1) {
}
TEST_F(ResourceTest, RegisterProviderInvalidPrefix2) {
- mjpResourceProvider provider = {"invalid:", open_nop, read_nop, close_nop, nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "invalid:", open_nop, read_nop, close_nop
+ };
// install warning handler
static char warning[1024];
@@ -149,11 +181,13 @@ TEST_F(ResourceTest, RegisterProviderInvalidPrefix2) {
}
TEST_F(ResourceTest, RegisterProviderSame) {
- mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop,
- nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "prefix", open_nop, read_nop, close_nop
+ };
- mjpResourceProvider provider2 = {"prefix", open_nop, read_nop, close_nop,
- nullptr, nullptr};
+ mjpResourceProvider provider2 = {
+ "prefix", open_nop, read_nop, close_nop
+ };
int i1 = mjp_registerResourceProvider(&provider);
int count1 = mjp_resourceProviderCount();
@@ -166,11 +200,13 @@ TEST_F(ResourceTest, RegisterProviderSame) {
}
TEST_F(ResourceTest, RegisterProviderSameCase) {
- mjpResourceProvider provider = {"prefix", open_nop, read_nop, close_nop,
- nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "prefix", open_nop, read_nop, close_nop
+ };
- mjpResourceProvider provider2 = {"PREFIX", open_nop, read_nop, close_nop,
- nullptr, nullptr};
+ mjpResourceProvider provider2 = {
+ "PREFIX", open_nop, read_nop, close_nop
+ };
int i1 = mjp_registerResourceProvider(&provider);
int count1 = mjp_resourceProviderCount();
@@ -183,15 +219,16 @@ TEST_F(ResourceTest, RegisterProviderSameCase) {
}
TEST_F(ResourceTest, GeneralTest) {
- mjpResourceProvider provider = {"str", open_str, read_str, close_str,
- nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "str", open_str, read_str, close_str
+ };
// register resource provider
int i = mjp_registerResourceProvider(&provider);
EXPECT_GT(i, 0);
// open resource
- mjResource* resource = mju_openResource("str:file", 0);
+ mjResource* resource = mju_openResource("str:file");
ASSERT_THAT(resource, NotNull());
const char* buffer = NULL;
@@ -203,8 +240,9 @@ TEST_F(ResourceTest, GeneralTest) {
}
TEST_F(ResourceTest, GeneralTestFailure) {
- mjpResourceProvider provider = {"str", open_str, read_str, close_str,
- nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "str", open_str, read_str, close_str
+ };
// register resource provider
int i = mjp_registerResourceProvider(&provider);
@@ -219,15 +257,16 @@ TEST_F(ResourceTest, GeneralTestFailure) {
};
// open resource
- mjResource* resource = mju_openResource("str:notfound", 0);
+ mjResource* resource = mju_openResource("str:notfound");
ASSERT_THAT(resource, IsNull());
EXPECT_THAT(warning, HasSubstr("could not open"));
}
TEST_F(ResourceTest, NameWithValidPrefix) {
- mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop,
- nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "nop", open_nop, read_nop, close_nop
+ };
// register resource provider
int i = mjp_registerResourceProvider(&provider);
@@ -242,14 +281,15 @@ TEST_F(ResourceTest, NameWithValidPrefix) {
};
// open resource
- mjResource* resource = mju_openResource("nop:found", 0);
+ mjResource* resource = mju_openResource("nop:found");
ASSERT_THAT(resource, NotNull());
mju_closeResource(resource);
}
TEST_F(ResourceTest, NameWithUpperCasePrefix) {
- mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop,
- nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "nop", open_nop, read_nop, close_nop,
+ };
// register resource provider
int i = mjp_registerResourceProvider(&provider);
@@ -264,14 +304,15 @@ TEST_F(ResourceTest, NameWithUpperCasePrefix) {
};
// open resource
- mjResource* resource = mju_openResource("NOP:found", 0);
+ mjResource* resource = mju_openResource("NOP:found");
ASSERT_THAT(resource, NotNull());
mju_closeResource(resource);
}
TEST_F(ResourceTest, NameWithInvalidPrefix) {
- mjpResourceProvider provider = {"nop", open_nop, read_nop, close_nop,
- nullptr, nullptr};
+ mjpResourceProvider provider = {
+ "nop", open_nop, read_nop, close_nop
+ };
// register resource provider
int i = mjp_registerResourceProvider(&provider);
@@ -286,33 +327,9 @@ TEST_F(ResourceTest, NameWithInvalidPrefix) {
};
// open resource
- mjResource* resource = mju_openResource("nopfound", 0);
+ mjResource* resource = mju_openResource("nopfound");
ASSERT_THAT(resource, IsNull());
}
-TEST_F(ResourceTest, VFSProvider) {
- // load VFS on the heap
- auto vfs = std::make_unique();
- mj_defaultVFS(vfs.get());
- mj_makeEmptyFileVFS(vfs.get(), "file", 1);
-
- // register resource provider
- int i = mj_registerVfsProvider(vfs.get());
- EXPECT_GT(i, 0);
-
-
- // install warning handler
- static char warning[1024];
- warning[0] = '\0';
- mju_user_warning = [](const char* msg) {
- util::strcpy_arr(warning, msg);
- };
-
- // open resource
- mjResource* resource = mju_openResource("vfs:file", 0);
- ASSERT_THAT(resource, IsNull());
- mj_deleteFileVFS(vfs.get(), "file");
-}
-
} // namespace
} // namespace mujoco
diff --git a/test/fixture.cc b/test/fixture.cc
index 10432236..4c451b65 100644
--- a/test/fixture.cc
+++ b/test/fixture.cc
@@ -14,6 +14,7 @@
#include "test/fixture.h"
+#include
#include
#include
#include
@@ -79,10 +80,11 @@ mjModel* LoadModelFromString(std::string_view xml, char* error,
resource->data = &(resource->name[strlen("LoadModelFromString:")]);
return 1;
};
- resourceProvider.read = +[](mjResource* resource, const void** buffer) {
- *buffer = resource->data;
- return (int) strlen((const char*) resource->data);
- };
+ resourceProvider.read =
+ +[](mjResource* resource, const void** buffer) {
+ *buffer = resource->data;
+ return (int) strlen((const char*) resource->data);
+ };
resourceProvider.close = +[](mjResource* resource) {};
mjp_registerResourceProvider(&resourceProvider);
}
diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs
index b4f9f6a1..7b318b97 100644
--- a/unity/Runtime/Bindings/MjBindings.cs
+++ b/unity/Runtime/Bindings/MjBindings.cs
@@ -1756,7 +1756,7 @@ public unsafe struct _mjVFS
{
public int nfile;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000 * 1000)] public char[] filename;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public int[] filesize;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public UIntPtr[] filesize;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2000)] public IntPtr[] filedata;
}
From 273c23038cae4695bcbe16fe6e4f29bfd9bb7897 Mon Sep 17 00:00:00 2001
From: Alessio Quaglino
Date: Wed, 30 Aug 2023 05:39:42 -0700
Subject: [PATCH 03/38] MakeParticle and elasticity plugin refactoring.
- Removed the explicit dependency on a plugin name.
- Creation of particles from user-defined vertices (if a plugin is present).
- Add skin support for an irregular mesh
- Fixes #642 and #674.
PiperOrigin-RevId: 561313771
Change-Id: I450f91f0d3b578cc18d97ed7180a0a8944b7dd41
---
plugin/elasticity/CMakeLists.txt | 2 +
plugin/elasticity/elasticity.cc | 51 ++++-
plugin/elasticity/elasticity.h | 50 +++++
plugin/elasticity/register.cc | 26 +++
plugin/elasticity/solid.cc | 102 ++-------
plugin/elasticity/solid.h | 6 +-
src/user/user_composite.cc | 253 +++++++++++++++++-----
src/user/user_composite.h | 4 +
test/plugin/elasticity/elasticity_test.cc | 3 -
9 files changed, 355 insertions(+), 142 deletions(-)
create mode 100644 plugin/elasticity/elasticity.h
create mode 100644 plugin/elasticity/register.cc
diff --git a/plugin/elasticity/CMakeLists.txt b/plugin/elasticity/CMakeLists.txt
index f35cf2d3..81946e9a 100644
--- a/plugin/elasticity/CMakeLists.txt
+++ b/plugin/elasticity/CMakeLists.txt
@@ -20,6 +20,8 @@ set(MUJOCO_ELASTICITY_SRCS
cable.cc
cable.h
elasticity.cc
+ elasticity.h
+ register.cc
solid.cc
solid.h
)
diff --git a/plugin/elasticity/elasticity.cc b/plugin/elasticity/elasticity.cc
index e349379f..35c0e0fc 100644
--- a/plugin/elasticity/elasticity.cc
+++ b/plugin/elasticity/elasticity.cc
@@ -12,15 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include
-#include "cable.h"
-#include "solid.h"
+#include "elasticity.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
namespace mujoco::plugin::elasticity {
-mjPLUGIN_LIB_INIT {
- Cable::RegisterPlugin();
- Solid::RegisterPlugin();
+void String2Vector(const std::string& txt, std::vector& vec) {
+ std::stringstream strm(txt);
+ vec.clear();
+
+ while (!strm.eof()) {
+ int num;
+ strm >> num;
+ if (strm.fail()) {
+ break;
+ } else {
+ vec.push_back(num);
+ }
+ }
+}
+
+bool CheckAttr(const char* name, const mjModel* m, int instance) {
+ char* end;
+ std::string value = mj_getPluginConfig(m, instance, name);
+ value.erase(std::remove_if(value.begin(), value.end(), isspace), value.end());
+ strtod(value.c_str(), &end);
+ return end == value.data() + value.size();
+}
+
+mjtNum SquaredDist3(const mjtNum pos1[3], const mjtNum pos2[3]) {
+ mjtNum dif[3] = {pos1[0]-pos2[0], pos1[1]-pos2[1], pos1[2]-pos2[2]};
+ return dif[0]*dif[0] + dif[1]*dif[1] + dif[2]*dif[2];
+}
+
+void UpdateSquaredLengths(std::vector& len,
+ const std::vector >& edges,
+ const mjtNum* x) {
+ for (int e = 0; e < len.size(); e++) {
+ const mjtNum* p0 = x + 3*edges[e].first;
+ const mjtNum* p1 = x + 3*edges[e].second;
+ len[e] = SquaredDist3(p0, p1);
+ }
}
} // namespace mujoco::plugin::elasticity
diff --git a/plugin/elasticity/elasticity.h b/plugin/elasticity/elasticity.h
new file mode 100644
index 00000000..54b0fb14
--- /dev/null
+++ b/plugin/elasticity/elasticity.h
@@ -0,0 +1,50 @@
+// Copyright 2022 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.
+
+#ifndef MUJOCO_PLUGIN_ELASTICITY_ELASTICITY_H_
+#define MUJOCO_PLUGIN_ELASTICITY_ELASTICITY_H_
+
+#include
+#include
+#include
+
+#include
+
+namespace mujoco::plugin::elasticity {
+
+struct PairHash
+{
+ template
+ std::size_t operator() (const std::pair& pair) const {
+ return std::hash()(pair.first) ^ std::hash()(pair.second);
+ }
+};
+
+// copied from mjXUtil
+void String2Vector(const std::string& txt, std::vector& vec);
+
+// reads numeric attributes
+bool CheckAttr(const char* name, const mjModel* m, int instance);
+
+// Cartesian distance between 3D vectors
+mjtNum SquaredDist3(const mjtNum pos1[3], const mjtNum pos2[3]);
+
+// updates square lengths of edges
+void UpdateSquaredLengths(std::vector& len,
+ const std::vector >& edges,
+ const mjtNum* x);
+
+} // namespace mujoco::plugin::elasticity
+
+#endif // MUJOCO_PLUGIN_ELASTICITY_ELASTICITY_H_
diff --git a/plugin/elasticity/register.cc b/plugin/elasticity/register.cc
new file mode 100644
index 00000000..e349379f
--- /dev/null
+++ b/plugin/elasticity/register.cc
@@ -0,0 +1,26 @@
+// Copyright 2022 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
+#include "cable.h"
+#include "solid.h"
+
+namespace mujoco::plugin::elasticity {
+
+mjPLUGIN_LIB_INIT {
+ Cable::RegisterPlugin();
+ Solid::RegisterPlugin();
+}
+
+} // namespace mujoco::plugin::elasticity
diff --git a/plugin/elasticity/solid.cc b/plugin/elasticity/solid.cc
index 83777613..84130bff 100644
--- a/plugin/elasticity/solid.cc
+++ b/plugin/elasticity/solid.cc
@@ -13,15 +13,17 @@
// limitations under the License.
#include
-#include
-#include
-#include
+#include
+#include
#include
#include
+#include
+#include
#include
#include
#include
+#include "elasticity.h"
#include "solid.h"
@@ -36,15 +38,6 @@ constexpr int edge[kNumEdges][2] = {{0, 1}, {1, 2}, {2, 0},
constexpr int face[kNumVerts][3] = {{2, 1, 0}, {0, 1, 3}, {1, 2, 3}, {2, 0, 3}};
constexpr int e2f[kNumEdges][2] = {{2, 3}, {1, 3}, {2, 1},
{1, 0}, {0, 2}, {0, 3}};
-constexpr int cube2tets[kNumEdges][kNumVerts] = {{0, 3, 1, 7}, {0, 1, 4, 7},
- {1, 3, 2, 7}, {1, 2, 6, 7},
- {1, 5, 4, 7}, {1, 6, 5, 7}};
-
-// Cartesian distance between 3D vectors
-mjtNum SquaredDist3(const mjtNum pos1[3], const mjtNum pos2[3]) {
- mjtNum dif[3] = {pos1[0]-pos2[0], pos1[1]-pos2[1], pos1[2]-pos2[2]};
- return dif[0]*dif[0] + dif[1]*dif[1] + dif[2]*dif[2];
-}
// volume of a tetrahedron
mjtNum ComputeVolume(const mjtNum* x, const int v[kNumVerts]) {
@@ -89,17 +82,6 @@ void ComputeBasis(mjtNum basis[9], const mjtNum* x, const int v[kNumVerts],
}
}
-// update edge lengths
-void UpdateSquaredLengths(std::vector& len,
- const std::vector >& edges,
- const mjtNum* x) {
- for (int e = 0; e < len.size(); e++) {
- const mjtNum* p0 = x + 3*edges[e].first;
- const mjtNum* p1 = x + 3*edges[e].second;
- len[e] = SquaredDist3(p0, p1);
- }
-}
-
// gradients of edge lengths with respect to vertex positions
void GradSquaredLengths(mjtNum gradient[kNumEdges][2][3],
const mjtNum* x,
@@ -113,40 +95,20 @@ void GradSquaredLengths(mjtNum gradient[kNumEdges][2][3],
}
}
-// reads numeric attributes
-bool CheckAttr(const char* name, const mjModel* m, int instance) {
- char* end;
- std::string value = mj_getPluginConfig(m, instance, name);
- value.erase(std::remove_if(value.begin(), value.end(), isspace), value.end());
- strtod(value.c_str(), &end);
- return end == value.data() + value.size();
-}
-
-struct PairHash
-{
- template
- std::size_t operator() (const std::pair& pair) const {
- return std::hash()(pair.first) ^ std::hash()(pair.second);
- }
-};
-
} // namespace
// factory function
std::optional Solid::Create(const mjModel* m, mjData* d, int instance) {
- if (CheckAttr("nx", m, instance) &&
- CheckAttr("ny", m, instance) &&
- CheckAttr("nz", m, instance) &&
+ if (CheckAttr("face", m, instance) &&
CheckAttr("poisson", m, instance) &&
CheckAttr("young", m, instance)) {
- int nx = strtod(mj_getPluginConfig(m, instance, "nx"), nullptr);
- int ny = strtod(mj_getPluginConfig(m, instance, "ny"), nullptr);
- int nz = strtod(mj_getPluginConfig(m, instance, "nz"), nullptr);
mjtNum nu = strtod(mj_getPluginConfig(m, instance, "poisson"), nullptr);
mjtNum E = strtod(mj_getPluginConfig(m, instance, "young"), nullptr);
mjtNum damp =
strtod(mj_getPluginConfig(m, instance, "damping"), nullptr);
- return Solid(m, d, instance, nx, ny, nz, nu, E, damp);
+ std::vector face;
+ String2Vector(mj_getPluginConfig(m, instance, "face"), face);
+ return Solid(m, d, instance, nu, E, damp, face);
} else {
mju_warning("Invalid parameter specification in solid plugin");
return std::nullopt;
@@ -154,30 +116,13 @@ std::optional Solid::Create(const mjModel* m, mjData* d, int instance) {
}
// create map from tetrahedra to vertices and edges and from edges to vertices
-void Solid::CreateStencils(int nx, int ny, int nz) {
+void Solid::CreateStencils(const std::vector& simplex) {
+ // populate stencil
+ nt = simplex.size() / kNumVerts;
elements.resize(nt);
-
- // create a tetrahedral mesh by splitting a grid of hexahedral cells
- for (int ix = 0; ix < nx-1; ix++) {
- for (int iy = 0; iy < ny-1; iy++) {
- for (int iz = 0; iz < nz-1; iz++) {
- int t = 6*(nz-1)*(ny-1)*ix + 6*(nz-1)*iy + 6*iz;
- int vert[8] = {
- nz*ny*(ix+0) + nz*(iy+0) + iz+0,
- nz*ny*(ix+1) + nz*(iy+0) + iz+0,
- nz*ny*(ix+1) + nz*(iy+1) + iz+0,
- nz*ny*(ix+0) + nz*(iy+1) + iz+0,
- nz*ny*(ix+0) + nz*(iy+0) + iz+1,
- nz*ny*(ix+1) + nz*(iy+0) + iz+1,
- nz*ny*(ix+1) + nz*(iy+1) + iz+1,
- nz*ny*(ix+0) + nz*(iy+1) + iz+1,
- };
- for (int s = 0; s < 6; s++) {
- for (int v = 0; v < kNumVerts; v++) {
- elements[t+s].vertices[v] = vert[cube2tets[s][v]];
- }
- }
- }
+ for (int t = 0; t < nt; t++) {
+ for (int v = 0; v < kNumVerts; v++) {
+ elements[t].vertices[v] = simplex[kNumVerts*t+v]-1;
}
}
@@ -209,8 +154,9 @@ void Solid::CreateStencils(int nx, int ny, int nz) {
}
// plugin constructor
-Solid::Solid(const mjModel* m, mjData* d, int instance, int nx, int ny, int nz,
- mjtNum nu, mjtNum E, mjtNum damp): damping(damp) {
+Solid::Solid(const mjModel* m, mjData* d, int instance, mjtNum nu, mjtNum E,
+ mjtNum damp, const std::vector& simplex)
+ : damping(damp) {
// count plugin bodies
nv = ne = 0;
for (int i = 1; i < m->nbody; i++) {
@@ -221,13 +167,11 @@ Solid::Solid(const mjModel* m, mjData* d, int instance, int nx, int ny, int nz,
}
}
- // allocate arrays
- nc = (nx-1)*(ny-1)*(nz-1); // number of cubes
- nt = 6*nc; // number of tets
- metric.assign(kNumEdges*kNumEdges*nt, 0); // metric induced by the geometry
-
// generate tetrahedra from the vertices
- CreateStencils(nx, ny, nz);
+ CreateStencils(simplex);
+
+ // allocate arrays
+ metric.assign(kNumEdges*kNumEdges*nt, 0);
// loop over all tetrahedra
for (int t = 0; t < nt; t++) {
@@ -357,7 +301,7 @@ void Solid::RegisterPlugin() {
plugin.name = "mujoco.elasticity.solid";
plugin.capabilityflags |= mjPLUGIN_PASSIVE;
- const char* attributes[] = {"nx", "ny", "nz", "young", "poisson", "damping"};
+ const char* attributes[] = {"face", "young", "poisson", "damping"};
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
plugin.attributes = attributes;
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
diff --git a/plugin/elasticity/solid.h b/plugin/elasticity/solid.h
index 96527a1d..7f4ee95d 100644
--- a/plugin/elasticity/solid.h
+++ b/plugin/elasticity/solid.h
@@ -63,10 +63,10 @@ class Solid {
mjtNum damping;
private:
- Solid(const mjModel* m, mjData* d, int instance, int nx, int ny, int nz,
- mjtNum nu, mjtNum E, mjtNum damp);
+ Solid(const mjModel* m, mjData* d, int instance, mjtNum nu, mjtNum E,
+ mjtNum damp, const std::vector& simplex);
- void CreateStencils(int nx, int ny, int nz);
+ void CreateStencils(const std::vector& simplex);
};
} // namespace mujoco::plugin::elasticity
diff --git a/src/user/user_composite.cc b/src/user/user_composite.cc
index 256f9a95..b44b1600 100644
--- a/src/user/user_composite.cc
+++ b/src/user/user_composite.cc
@@ -34,6 +34,7 @@
#include "user/user_model.h"
#include "user/user_objects.h"
#include "user/user_util.h"
+#include "xml/xml_util.h"
namespace {
namespace mju = ::mujoco::util;
@@ -314,66 +315,178 @@ bool mjCComposite::Make(mjCModel* model, mjCBody* body, char* error, int error_s
-// make particles
bool mjCComposite::MakeParticle(mjCModel* model, mjCBody* body, char* error, int error_sz) {
- // create bodies and geoms
- for (int ix=0; ixAddBody(NULL);
- mju::sprintf_arr(txt, "%sB%d_%d_%d", prefix.c_str(), ix, iy, iz);
- b->name = txt;
+ char txt[100];
+ std::vector face;
- // set body position
- b->pos[0] = offset[0] + spacing*(ix - 0.5*count[0]);
- b->pos[1] = offset[1] + spacing*(iy - 0.5*count[1]);
- b->pos[2] = offset[2] + spacing*(iz - 0.5*count[2]);
+ // populate vertices and names
+ if (uservert.empty()) {
+ if (spacing < mju_max(def[0].geom.size[0],
+ mju_max(def[0].geom.size[1], def[0].geom.size[2])))
+ return comperr(error, "Spacing must be larger than geometry size", error_sz);
- // add slider joints if none defined
- if (!add[mjCOMPKIND_PARTICLE]) {
- for (int i=0; i<3; i++) {
- mjCJoint* jnt = b->AddJoint(&defjoint[mjCOMPKIND_JOINT][0], false);
- jnt->def = body->def;
- jnt->type = mjJNT_SLIDE;
- mjuu_setvec(jnt->pos, 0, 0, 0);
- mjuu_setvec(jnt->axis, 0, 0, 0);
- jnt->axis[i] = 1;
- }
- }
+ for (int ix=0; ixAddJoint(&defjnt, false);
- jnt->def = body->def;
- }
- }
-
- // add geom
- mjCGeom* g = b->AddGeom(def);
- g->def = body->def;
-
- // add plugin
- if (plugin_instance) {
- b->is_plugin = true;
- b->plugin_name = plugin_name;
- b->plugin_instance = plugin_instance;
- b->plugin_instance_name = plugin_instance_name;
-
- // propagate attributes
- if (plugin_name == "mujoco.elasticity.solid") {
- b->plugin_instance->config_attribs["nx"] = std::to_string(count[0]);
- b->plugin_instance->config_attribs["ny"] = std::to_string(count[1]);
- b->plugin_instance->config_attribs["nz"] = std::to_string(count[2]);
- }
+ mju::sprintf_arr(txt, "%sB%d_%d_%d", prefix.c_str(), ix, iy, iz);
+ username.push_back(std::string(txt));
}
}
}
}
- // skin
+ // create faces
+ if (userface.empty()) {
+ if (dim == 3) {
+ int cube2tets[6][4] = {{0, 3, 1, 7}, {0, 1, 4, 7},
+ {1, 3, 2, 7}, {1, 2, 6, 7},
+ {1, 5, 4, 7}, {1, 6, 5, 7}};
+ for (int ix = 0; ix < count[0]-1; ix++) {
+ for (int iy = 0; iy < count[1]-1; iy++) {
+ for (int iz = 0; iz < count[2]-1; iz++) {
+ int vert[8] = {
+ count[2]*count[1]*(ix+0) + count[2]*(iy+0) + iz+0,
+ count[2]*count[1]*(ix+1) + count[2]*(iy+0) + iz+0,
+ count[2]*count[1]*(ix+1) + count[2]*(iy+1) + iz+0,
+ count[2]*count[1]*(ix+0) + count[2]*(iy+1) + iz+0,
+ count[2]*count[1]*(ix+0) + count[2]*(iy+0) + iz+1,
+ count[2]*count[1]*(ix+1) + count[2]*(iy+0) + iz+1,
+ count[2]*count[1]*(ix+1) + count[2]*(iy+1) + iz+1,
+ count[2]*count[1]*(ix+0) + count[2]*(iy+1) + iz+1,
+ };
+ for (int s = 0; s < 6; s++) {
+ for (int v = 0; v < 4; v++) {
+ face.push_back(vert[cube2tets[s][v]]+1);
+ }
+ }
+ }
+ }
+ }
+ } else if (dim == 2) {
+ int quad2tri[2][3] = {{0, 1, 2}, {0, 2, 3}};
+ for (int ix = 0; ix < count[0]-1; ix++) {
+ for (int iy = 0; iy < count[1]-1; iy++) {
+ int vert[4] = {
+ count[2]*count[1]*(ix+0) + count[2]*(iy+0),
+ count[2]*count[1]*(ix+1) + count[2]*(iy+0),
+ count[2]*count[1]*(ix+1) + count[2]*(iy+1),
+ count[2]*count[1]*(ix+0) + count[2]*(iy+1),
+ };
+ for (int s = 0; s < 2; s++) {
+ for (int v = 0; v < 3; v++) {
+ face.push_back(vert[quad2tri[s][v]]+1);
+ }
+ }
+ }
+ }
+ }
+ mjXUtil::Vector2String(userface, face);
+ } else {
+ dim = 2; // can only load a surface for now
+ mjXUtil::String2Vector(userface, face);
+ }
+
+ // compute volume
+ std::vector volume(uservert.size()/3);
+ mjtNum t = 1;
+ if (dim == 2 && plugin_instance) {
+ // do nothing for now (until new passive forces are supported)
+ }
+ if (!userface.empty()) {
+ mjXUtil::String2Vector(userface, face);
+ for (int j=0; jAddBody(NULL);
+
+ if (!username.empty()) {
+ b->name = username[i];
+ } else {
+ mju::sprintf_arr(txt, "%sB%d", prefix.c_str(), i);
+ b->name = txt;
+ }
+
+ // set body position
+ b->pos[0] = offset[0] + uservert[3*i];
+ b->pos[1] = offset[1] + uservert[3*i+1];
+ b->pos[2] = offset[2] + uservert[3*i+2];
+
+ // add slider joints if none defined
+ if (!add[mjCOMPKIND_PARTICLE]) {
+ for (int i=0; i<3; i++) {
+ mjCJoint* jnt = b->AddJoint(&defjoint[mjCOMPKIND_JOINT][0], false);
+ jnt->def = body->def;
+ jnt->type = mjJNT_SLIDE;
+ mjuu_setvec(jnt->pos, 0, 0, 0);
+ mjuu_setvec(jnt->axis, 0, 0, 0);
+ jnt->axis[i] = 1;
+ }
+ }
+
+ // add user-specified joints
+ else {
+ for (auto defjnt : defjoint[mjCOMPKIND_PARTICLE]) {
+ mjCJoint* jnt = b->AddJoint(&defjnt, false);
+ jnt->def = body->def;
+ }
+ }
+
+ // add geom
+ mjCGeom* g = b->AddGeom(def);
+ g->def = body->def;
+
+ // add site
+ mjCSite* s = b->AddSite(def);
+ s->def = body->def;
+ s->type = mjGEOM_SPHERE;
+ mju::sprintf_arr(txt, "%sS%d", prefix.c_str(), i);
+ s->name = txt;
+
+ // add plugin
+ if (plugin_instance) {
+ b->is_plugin = true;
+ b->plugin_name = plugin_name;
+ b->plugin_instance = plugin_instance;
+ b->plugin_instance_name = plugin_instance_name;
+
+ if (i==0 && !plugin_instance->config_attribs["face"].empty()) {
+ return comperr(error, "Face attribute already exists in plugin", error_sz);
+ }
+
+ b->plugin_instance->config_attribs["face"] = userface;
+
+ // update density
+ if (dim == 2) {
+ g->density *= volume[i] / (4./3. * mjPI * pow(g->size[0], 3));
+ }
+ }
+ }
+
if (skin) {
MakeSkin3(model);
}
@@ -2040,6 +2153,44 @@ void mjCComposite::MakeSkin3(mjCModel* model) {
skin->inflate = skininflate;
skin->group = skingroup;
+ // copy skin from existing mesh
+ if (type==mjCOMPTYPE_PARTICLE && username.empty()) {
+ std::vector face;
+ mjXUtil::String2Vector(userface, face);
+ int nvert = uservert.size()/3;
+
+ for (int j=0; j<2; j++) {
+ for (int i=0; ivert.push_back(0);
+ skin->vert.push_back(0);
+ skin->vert.push_back(0);
+
+ mju::sprintf_arr(txt, "%sB%d", prefix.c_str(), i);
+ skin->bodyname.push_back(txt);
+ skin->bindpos.push_back(0);
+ skin->bindpos.push_back(0);
+ skin->bindpos.push_back(0);
+ skin->bindquat.push_back(1);
+ skin->bindquat.push_back(0);
+ skin->bindquat.push_back(0);
+ skin->bindquat.push_back(0);
+
+ vector vertid;
+ vector vertweight;
+ vertid.push_back(j*nvert+i);
+ vertweight.push_back(1);
+ skin->vertid.push_back(vertid);
+ skin->vertweight.push_back(vertweight);
+ }
+
+ for (int i=0; iface.push_back(j*nvert+face[3*i]-1);
+ skin->face.push_back(j*nvert+face[3*i+(j==0 ? 1 : 2)]-1);
+ skin->face.push_back(j*nvert+face[3*i+(j==0 ? 2 : 1)]-1);
+ }
+ }
+ }
+
// box
if (type==mjCOMPTYPE_BOX || type==mjCOMPTYPE_PARTICLE) {
// z-faces
diff --git a/src/user/user_composite.h b/src/user/user_composite.h
index af6e6807..0d60b97e 100644
--- a/src/user/user_composite.h
+++ b/src/user/user_composite.h
@@ -106,9 +106,13 @@ class mjCComposite {
// currently used only for cable
std::string initial; // root boundary type
std::vector uservert; // user-specified vertex positions
+ std::string userface; // connectivity
mjtNum size[3]; // rope size (meaning depends on the shape)
mjtCompShape curve[3]; // geometric shape
+ // body names used in the skin
+ std::vector username;
+
// plugin support
bool is_plugin;
std::string plugin_name;
diff --git a/test/plugin/elasticity/elasticity_test.cc b/test/plugin/elasticity/elasticity_test.cc
index d6a03d2e..d2e36025 100644
--- a/test/plugin/elasticity/elasticity_test.cc
+++ b/test/plugin/elasticity/elasticity_test.cc
@@ -41,9 +41,6 @@ TEST_F(PluginTest, ElasticEnergy) {
-
-
-
From 16ee0dfde545a51e809d2793a8de6bce390700b8 Mon Sep 17 00:00:00 2001
From: Kyle Bayes
Date: Wed, 30 Aug 2023 06:52:36 -0700
Subject: [PATCH 04/38] Add modified callback to resource providers (for
caching purposes).
PiperOrigin-RevId: 561327623
Change-Id: Id6683f6d6d32ee91019aba46599c28cbffe29df6
---
doc/includes/references.h | 1 +
doc/programming/extension.rst | 9 ++++--
include/mujoco/mjplugin.h | 6 ++++
src/engine/engine_plugin.cc | 1 +
src/engine/engine_resource.c | 53 +++++++++++++++++++++++++++++++++++
src/engine/engine_vfs.c | 1 +
6 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/doc/includes/references.h b/doc/includes/references.h
index 35aaa77c..d6223101 100644
--- a/doc/includes/references.h
+++ b/doc/includes/references.h
@@ -1259,6 +1259,7 @@ struct mjpResourceProvider {
mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback
mjfGetResourceDir getdir; // get directory callback (optional)
+ mjfResourceModified modified; // resource modified callback (optional)
void* data; // opaque data pointer (resource invariant)
};
typedef struct mjpResourceProvider mjpResourceProvider;
diff --git a/doc/programming/extension.rst b/doc/programming/extension.rst
index e5e22846..7ab8f7e2 100644
--- a/doc/programming/extension.rst
+++ b/doc/programming/extension.rst
@@ -330,8 +330,9 @@ Resource prefix
Callbacks
There are three callbacks that a resource provider is required to implement: :ref:`open`,
- :ref:`read`, and :ref:`close`. A fourth callback :ref:`getdir`
- is optional. More details on these callbacks are given below.
+ :ref:`read`, and :ref:`close`. The other two callback
+ :ref:`getdir` and :ref:`modified` are optional. More details on these callbacks
+ are given below.
Data Pointer
Lastly, there's an opaque data pointer for the provider to pass data into the callbacks. This data pointer is constant
@@ -351,6 +352,8 @@ Resource providers work via callbacks:
- :ref:`mjfGetResourceDir`: This callback is optional and is used to extract the directory from a
resource name. For example, the resource name ``http://www.example.com/myasset.obj`` would have
``http://www.example.com/`` as its directory.
+- :ref:`mjfModifiedResource`: This callback is optional and is used to check if an existing
+ opened resource has been modifed from its orginal source.
.. _exProviderUsage:
@@ -421,6 +424,6 @@ Now we can write assets as strings in our MJCF files:
-
+
...
diff --git a/include/mujoco/mjplugin.h b/include/mujoco/mjplugin.h
index aa23bc44..2c071195 100644
--- a/include/mujoco/mjplugin.h
+++ b/include/mujoco/mjplugin.h
@@ -43,6 +43,11 @@ typedef void (*mjfCloseResource)(mjResource* resource);
// sets dir to directory string with ndir being size of directory string
typedef void (*mjfGetResourceDir)(mjResource* resource, const char** dir, int* ndir);
+// callback for checking if a resource was modified since last read
+// returns > 0 if resource was modified since last open, 0 if resource was not
+// modified, and < 0 if inconclusive
+typedef int (*mjfResourceModified)(const mjResource* resource);
+
// struct describing a single resource provider
struct mjpResourceProvider {
const char* prefix; // prefix for match against a resource name
@@ -50,6 +55,7 @@ struct mjpResourceProvider {
mjfReadResource read; // reading callback
mjfCloseResource close; // closing callback
mjfGetResourceDir getdir; // get directory callback (optional)
+ mjfResourceModified modified; // resource modified callback (optional)
void* data; // opaque data pointer (resource invariant)
};
typedef struct mjpResourceProvider mjpResourceProvider;
diff --git a/src/engine/engine_plugin.cc b/src/engine/engine_plugin.cc
index 52e86455..50b93d61 100644
--- a/src/engine/engine_plugin.cc
+++ b/src/engine/engine_plugin.cc
@@ -269,6 +269,7 @@ bool ResourceProvidersAreIdentical(const mjpResourceProvider* p1, const mjpResou
p1->read == p2->read &&
p1->close == p2->close &&
p1->getdir == p2->getdir &&
+ p1->modified == p2->modified &&
p1->data == p2->data);
}
diff --git a/src/engine/engine_resource.c b/src/engine/engine_resource.c
index bd7280fa..4718df50 100644
--- a/src/engine/engine_resource.c
+++ b/src/engine/engine_resource.c
@@ -19,6 +19,17 @@
#include
#include
#include
+#include
+#include
+#include
+
+#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
+ #include
+#endif
+
+#ifdef _WIN32
+ #define stat _stat
+#endif
#include
#include "engine/engine_plugin.h"
@@ -28,6 +39,7 @@
typedef struct {
uint8_t* buffer; // raw bytes from file
size_t nbuffer; // size of buffer in bytes
+ time_t mtime; // last modified time
} file_buffer;
// open the given resource; if the name doesn't have a prefix matching with a
@@ -77,6 +89,13 @@ mjResource* mju_openResource(const char* name) {
mju_closeResource(resource);
return NULL;
}
+ struct stat file_stat;
+ if (stat(name, &file_stat) == 0) {
+ memcpy(&fb->mtime, &file_stat.st_mtime, sizeof(time_t));
+ } else {
+ memset(&fb->mtime, 0, sizeof(time_t));
+ }
+
return resource;
}
@@ -149,6 +168,40 @@ void mju_getResourceDir(mjResource* resource, const char** dir, int* ndir) {
+// modified callback for OS filesystem
+static int mju_isModifiedFile(const char* name, const file_buffer* fb) {
+ if (fb != NULL) {
+ struct stat file_stat;
+ if (stat(name, &file_stat) == 0) {
+ return difftime(fb->mtime, file_stat.st_mtime) < 0;
+ }
+ return -1;
+ }
+ return -2;
+}
+
+
+
+// Returns > 0 if resource has been modified since last read, 0 if not, and < 0
+// if inconclusive
+int mju_isModifiedResource(const mjResource* resource) {
+ if (resource == NULL) {
+ return -2;
+ }
+
+ // provider is not OS filesystem
+ if (resource->provider) {
+ if (resource->provider->modified) {
+ return resource->provider->modified(resource);
+ }
+ return 1; // default (modified)
+ }
+
+ return mju_isModifiedFile(resource->name, (file_buffer*) resource->data);
+}
+
+
+
// get the length of the dirname portion of a given path
int mju_dirnamelen(const char* path) {
if (!path) {
diff --git a/src/engine/engine_vfs.c b/src/engine/engine_vfs.c
index 0535111e..26db392e 100644
--- a/src/engine/engine_vfs.c
+++ b/src/engine/engine_vfs.c
@@ -274,6 +274,7 @@ mjResource* mju_openVfsResource(const char* name, const mjVFS* vfs) {
.read = &vfs_read_callback,
.close = &vfs_close_callback,
.getdir = &vfs_getdir_callback,
+ .modified = NULL
};
// create resource
From f371ca7a4e64e112966444de33d4afc71626e9a5 Mon Sep 17 00:00:00 2001
From: Alessio Quaglino
Date: Wed, 30 Aug 2023 07:07:21 -0700
Subject: [PATCH 05/38] Add missing `face` attribute to the XML schema.
PiperOrigin-RevId: 561330536
Change-Id: I693fc5228591e4a26ea0749435a6ce9869afd457
---
doc/XMLreference.rst | 5 +++++
doc/XMLschema.rst | 4 +++-
src/user/user_composite.cc | 4 ++--
src/xml/xml_native_reader.cc | 7 +++++--
4 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst
index 62c0c191..6ce1ce42 100644
--- a/doc/XMLreference.rst
+++ b/doc/XMLreference.rst
@@ -3319,6 +3319,11 @@ coordinates results in compiler error. See :ref:`CComposite` in the modeling gui
:at:`vertex`: :at-val:`real(3*nvert), optional`
Vertex 3D positions in global coordinates (cable only).
+.. _body-composite-face:
+
+:at:`face`: :at-val:`real(3*nvert), optional`
+ Face connectivity of the vertices (shell only).
+
.. _body-composite-initial:
:at:`initial`: :at-val:`[free, ball, none], "0"`
diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst
index ee7ad48c..2210168f 100644
--- a/doc/XMLschema.rst
+++ b/doc/XMLschema.rst
@@ -390,7 +390,9 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`offset` | :ref:`flatinertia` | :ref:`solrefsmooth` | :ref:`solimpsmooth` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
-| | | | :ref:`vertex` | :ref:`initial` | :ref:`curve` | :ref:`size` | |
+| | | | :ref:`vertex` | :ref:`face` | :ref:`initial` | :ref:`curve` | |
+| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+| | | | :ref:`size` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| composite |br| |_2| |L| | | .. table:: |
diff --git a/src/user/user_composite.cc b/src/user/user_composite.cc
index b44b1600..7a8558c6 100644
--- a/src/user/user_composite.cc
+++ b/src/user/user_composite.cc
@@ -236,7 +236,7 @@ bool mjCComposite::Make(mjCModel* model, mjCBody* body, char* error, int error_s
}
// check spacing
- if (type==mjCOMPTYPE_GRID || type==mjCOMPTYPE_PARTICLE) {
+ if (type==mjCOMPTYPE_GRID || (type==mjCOMPTYPE_PARTICLE && uservert.empty())) {
if (spacing < mju_max(def[0].geom.size[0],
mju_max(def[0].geom.size[1], def[0].geom.size[2]))) {
return comperr(error, "Spacing must be larger than geometry size",
@@ -2192,7 +2192,7 @@ void mjCComposite::MakeSkin3(mjCModel* model) {
}
// box
- if (type==mjCOMPTYPE_BOX || type==mjCOMPTYPE_PARTICLE) {
+ else if (type==mjCOMPTYPE_BOX || type==mjCOMPTYPE_PARTICLE) {
// z-faces
MakeSkin3Box(skin, count[0], count[1], 1, vcnt, "%sB%d_%d_0");
fmt = "%sB%d_%d_" + string(cnt2);
diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc
index 3f04b6e5..3026dfb5 100644
--- a/src/xml/xml_native_reader.cc
+++ b/src/xml/xml_native_reader.cc
@@ -270,8 +270,8 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = {
{"light", "*", "15", "name", "class", "directional", "castshadow", "active",
"pos", "dir", "attenuation", "cutoff", "exponent", "ambient", "diffuse", "specular",
"mode", "target"},
- {"composite", "*", "12", "prefix", "type", "count", "spacing", "offset",
- "flatinertia", "solrefsmooth", "solimpsmooth", "vertex",
+ {"composite", "*", "13", "prefix", "type", "count", "spacing", "offset",
+ "flatinertia", "solrefsmooth", "solimpsmooth", "vertex", "face",
"initial", "curve", "size"},
{"<"},
{"plugin", "*", "2", "plugin", "instance"},
@@ -1943,6 +1943,9 @@ void mjXReader::OneComposite(XMLElement* elem, mjCBody* pbody, mjCDef* def) {
String2Vector(text, comp.uservert);
}
+ // shell
+ ReadAttrTxt(elem, "face", comp.userface);
+
// process curve string
std::istringstream iss(curves);
int i = 0;
From ac150f751d28ce22a5f99c8d3555d9a997d9530b Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Wed, 30 Aug 2023 11:46:10 -0700
Subject: [PATCH 06/38] Clean up mj_mulM.
PiperOrigin-RevId: 561404770
Change-Id: Ie802f5d7f1df6bf32d4549d3db6809d0f507d1e1
---
src/engine/engine_support.c | 51 ++++++++++++++++++-------------------
1 file changed, 25 insertions(+), 26 deletions(-)
diff --git a/src/engine/engine_support.c b/src/engine/engine_support.c
index e959ac00..7e57a21a 100644
--- a/src/engine/engine_support.c
+++ b/src/engine/engine_support.c
@@ -827,25 +827,27 @@ void mj_fullM(const mjModel* m, mjtNum* dst, const mjtNum* M) {
// multiply vector by inertia matrix
void mj_mulM(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec) {
- int adr, nv = m->nv;
+ int nv = m->nv;
const mjtNum* M = d->qM;
- const int* dofMadr = m->dof_Madr;
+ const int* Madr = m->dof_Madr;
+ const int* parentid = m->dof_parentid;
+ const int* simplenum = m->dof_simplenum;
mju_zero(res, nv);
for (int i=0; i < nv; i++) {
#ifdef mjUSEAVX
- // simple: diagonal division, AVX
- if (m->dof_simplenum[i] >= 4) {
+ // simple: diagonal multiplication, AVX
+ if (simplenum[i] >= 4) {
// init
__m256d result, val1, val2;
// parallel computation
val1 = _mm256_loadu_pd(vec+i);
- val2 = _mm256_set_pd(M[dofMadr[i+3]],
- M[dofMadr[i+2]],
- M[dofMadr[i+1]],
- M[dofMadr[i+0]]);
+ val2 = _mm256_set_pd(M[Madr[i+3]],
+ M[Madr[i+2]],
+ M[Madr[i+1]],
+ M[Madr[i+0]]);
result = _mm256_mul_pd(val1, val2);
// store result
@@ -856,29 +858,26 @@ void mj_mulM(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec)
continue;
}
#endif
+ // address in M
+ int adr = Madr[i];
- // simple: diagonal multiplication
- if (m->dof_simplenum[i]) {
- res[i] = M[dofMadr[i]]*vec[i];
+ // compute diagonal
+ res[i] = M[adr]*vec[i];
+
+ // simple dof: continue
+ if (simplenum[i]) {
+ continue;
}
- // regular: full multiplication
- else {
- // diagonal
- adr = dofMadr[i];
- res[i] += M[adr]*vec[i];
-
- // off-diagonal
- int j = m->dof_parentid[i];
+ // compute off-diagonals
+ int j = parentid[i];
+ while (j >= 0) {
adr++;
- while (j >= 0) {
- res[i] += M[adr]*vec[j];
- res[j] += M[adr]*vec[i];
+ res[i] += M[adr]*vec[j];
+ res[j] += M[adr]*vec[i];
- // advance to next element
- j = m->dof_parentid[j];
- adr++;
- }
+ // advance to parent
+ j = parentid[j];
}
}
}
From 43a0bd36461319a81d4308999ca2efece7450c42 Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Thu, 31 Aug 2023 01:42:55 -0700
Subject: [PATCH 07/38] Formatting improvements to docs Overview chapter.
PiperOrigin-RevId: 561586361
Change-Id: Ib529cbd49cebf26d802d023d12d973491e41d3ad
---
doc/changelog.rst | 8 +-
doc/overview.rst | 401 ++++++++++++++++++++++++++--------------------
2 files changed, 231 insertions(+), 178 deletions(-)
diff --git a/doc/changelog.rst b/doc/changelog.rst
index a77cf944..cebd8ce5 100644
--- a/doc/changelog.rst
+++ b/doc/changelog.rst
@@ -38,11 +38,11 @@ General
Euler integrator. See the :ref:`Numerical Integration` section for more details.
#. Added the flag :ref:`invdiscrete`, which enables discrete-time inverse dynamics for all
:ref:`integrators` other than ``RK4``. See the flag documentation for more details.
-#. Changed the function ``mj_stackAlloc`` to allocate an arbitrary number of bytes, rather than in multiples of
+#. Changed the function :ref:`mj_stackAlloc` to allocate an arbitrary number of bytes, rather than in multiples of
``sizeof(mjtNum)``, and add an additional argument for specifying the alignment of the returned pointer. The existing
- functionality of allocating ``mjtNum`` arrays is still available through the new function ``mj_stackAllocNum``.
-#. Renamed the ``nstack`` field in ``mjModel`` and ``mjData`` to ``narena``. Changed ``narena``, ``pstack``, and
- ``maxuse_stack`` to count number of bytes rather than number of ``mjtNum``s.
+ functionality of allocating ``mjtNum`` arrays is still available through the new function :ref:`mj_stackAllocNum`.
+#. Renamed the ``nstack`` field in :ref:`mjModel` and :ref:`mjData` to ``narena``. Changed ``narena``, ``pstack``, and
+ ``maxuse_stack`` to count number of bytes rather than number of :ref:`mjtNum` |-| s.
Python bindings
^^^^^^^^^^^^^^^
diff --git a/doc/overview.rst b/doc/overview.rst
index c011f47e..583556ce 100644
--- a/doc/overview.rst
+++ b/doc/overview.rst
@@ -239,8 +239,7 @@ The function :ref:`mj_step` is the top-level function which advances the simulat
of course is just a passive dynamical system. Things get more interesting when the user specifies controls or applies
forces and starts interacting with the system.
-Next we provide a more elaborate example illustrating several features of MJCF.
-
+Next we provide a more elaborate example illustrating several features of MJCF. Consider the following
`example.xml <_static/example.xml>`__:
.. code:: xml
@@ -332,18 +331,24 @@ XML file, default values are used. The options are designed such that the user c
simulation time step. Within a time step however none of the options should be changed.
``mjOption``
- This structure contains all options that affect the physics simulation. It is used to select algorithms and set their
- parameters, enable and disable different portions of the simulation pipeline, and adjust system-level physical
- properties such as gravity.
+^^^^^^^^^^^^
+
+This structure contains all options that affect the physics simulation. It is used to select algorithms and set their
+parameters, enable and disable different portions of the simulation pipeline, and adjust system-level physical
+properties such as gravity.
``mjVisual``
- This structure contains all visualization options. There are additional OpenGL rendering options, but these are
- session-dependent and are not part of the model.
+^^^^^^^^^^^^
+
+This structure contains all visualization options. There are additional OpenGL rendering options, but these are
+session-dependent and are not part of the model.
``mjStatistic``
- This structure contains statistics about the model which are computed by the compiler: average body mass, spatial
- extent of the model etc. It is included for information purposes, and also because the visualizer uses it for
- automatic scaling.
+^^^^^^^^^^^^^^^
+
+This structure contains statistics about the model which are computed by the compiler: average body mass, spatial
+extent of the model etc. It is included for information purposes, and also because the visualizer uses it for
+automatic scaling.
.. _Assets:
@@ -357,51 +362,61 @@ purpose of including an asset is to reference it, and referencing can only be do
undefined.
Mesh
- MuJoCo can load triangulated meshes from OBJ files and binary STL. Software such as `MeshLab
- `__ can be used to convert from other formats. While any collection of triangles can be
- loaded and visualized as a mesh, the collision detector works with the convex hull. There are compile-time options
- for scaling the mesh, as well as fitting a primitive geometric shape to it. The mesh can also be used to
- automatically infer inertial properties -- by treating it as a union of triangular pyramids and combining their
- masses and inertias. Note that meshes have no color, instead the mesh is colored using the material properties of the
- referencing geom. In contrast, all spatial properties are determined by the mesh data. MuJoCo supports both OBJ and a
- custom binary file format for normals and texture coordinates. Meshes can also be embedded directly in the XML.
+^^^^
+
+MuJoCo can load triangulated meshes from OBJ files and binary STL. Software such as `MeshLab
+`__ can be used to convert from other formats. While any collection of triangles can be
+loaded and visualized as a mesh, the collision detector works with the convex hull. There are compile-time options
+for scaling the mesh, as well as fitting a primitive geometric shape to it. The mesh can also be used to
+automatically infer inertial properties -- by treating it as a union of triangular pyramids and combining their
+masses and inertias. Note that meshes have no color, instead the mesh is colored using the material properties of the
+referencing geom. In contrast, all spatial properties are determined by the mesh data. MuJoCo supports both OBJ and a
+custom binary file format for normals and texture coordinates. Meshes can also be embedded directly in the XML.
Skin
- Skinned meshes (or skins) are meshes whose shape can deform at runtime. Their vertices are attached to rigid bodies
- (called bones in this context) and each vertex can belong to multiple bones, resulting in smooth deformations of the
- skin. Skins are purely visualization objects and do not affect the physics, but nevertheless they can enhance visual
- realism significantly. Skins can be loaded from custom binary files, or embedded directly in the XML, similar to
- meshes. When generating composite flexible objects automatically, the model compiler also generates skins for these
- objects.
+^^^^
+
+Skinned meshes (or skins) are meshes whose shape can deform at runtime. Their vertices are attached to rigid bodies
+(called bones in this context) and each vertex can belong to multiple bones, resulting in smooth deformations of the
+skin. Skins are purely visualization objects and do not affect the physics, but nevertheless they can enhance visual
+realism significantly. Skins can be loaded from custom binary files, or embedded directly in the XML, similar to
+meshes. When generating composite flexible objects automatically, the model compiler also generates skins for these
+objects.
Height field
- Height fields can be loaded from PNG files (converted to gray-scale internally) or from files in a custom binary
- format described later. A height field is a rectangular grid of elevation data. The compiler normalizes the data to
- the range [0-1]. The actual spatial extent of the height field is then determined by the size parameters of the
- referencing geom. Height fields can only be referenced from geoms that are attached to the world body. For rendering
- and collision detection purposes, the grid rectangles are automatically triangulated, thus the height field is
- treated as a union of triangular prisms. Collision detection with such a composite object can in principle generate a
- large number of contact points for a single geom pair. If that happens, only the first 64 contact points are kept.
- The rationale is that height fields should be used to model terrain maps whose spatial features are large compared to
- the other objects in the simulation, so the number of contacts will be small for well-designed models.
+^^^^^^^^^^^^
+
+Height fields can be loaded from PNG files (converted to gray-scale internally) or from files in a custom binary
+format described later. A height field is a rectangular grid of elevation data. The compiler normalizes the data to
+the range [0-1]. The actual spatial extent of the height field is then determined by the size parameters of the
+referencing geom. Height fields can only be referenced from geoms that are attached to the world body. For rendering
+and collision detection purposes, the grid rectangles are automatically triangulated, thus the height field is
+treated as a union of triangular prisms. Collision detection with such a composite object can in principle generate a
+large number of contact points for a single geom pair. If that happens, only the first 64 contact points are kept.
+The rationale is that height fields should be used to model terrain maps whose spatial features are large compared to
+the other objects in the simulation, so the number of contacts will be small for well-designed models.
Texture
- Textures can be loaded from PNG files or synthesized by the compiler based on user-defined procedural parameters.
- There is also the option to leave the texture empty at model creation time and change it later at runtime -- so as to
- render video in a MuJoCo simulation, or create other dynamic effects. The visualizer supports two types of texture
- mapping: 2D and cube. 2D mapping is useful for planes and height fields. Cube mapping is useful for "shrink-wrapping"
- textures around 3D objects without having to specify texture coordinates. It is also used to create a skybox. The six
- sides of a cube maps can be loaded from separate image files, or from one composite image file, or generated by
- repeating the same image. Unlike all other assets which are referenced directly from model elements, textures can
- only be referenced from another asset (namely material) which is then referenced from model elements.
+^^^^^^^
+
+Textures can be loaded from PNG files or synthesized by the compiler based on user-defined procedural parameters.
+There is also the option to leave the texture empty at model creation time and change it later at runtime -- so as to
+render video in a MuJoCo simulation, or create other dynamic effects. The visualizer supports two types of texture
+mapping: 2D and cube. 2D mapping is useful for planes and height fields. Cube mapping is useful for "shrink-wrapping"
+textures around 3D objects without having to specify texture coordinates. It is also used to create a skybox. The six
+sides of a cube maps can be loaded from separate image files, or from one composite image file, or generated by
+repeating the same image. Unlike all other assets which are referenced directly from model elements, textures can
+only be referenced from another asset (namely material) which is then referenced from model elements.
Material
- Materials are used to control the appearance of geoms, sites and tendons. This is done by referencing the material
- from the corresponding model element. Appearance includes texture mapping as well as other properties that interact
- with OpenGL lights below: RGBA, specularity, shininess, emission. Materials can also be used to make objects
- reflective. Currently reflections are rendered only on planes and on the Z+ faces of boxes. Note that model elements
- can also have their local RGBA parameter for setting color. If both material and local RGBA are specified, the local
- definition has precedence.
+^^^^^^^^
+
+Materials are used to control the appearance of geoms, sites and tendons. This is done by referencing the material
+from the corresponding model element. Appearance includes texture mapping as well as other properties that interact
+with OpenGL lights below: RGBA, specularity, shininess, emission. Materials can also be used to make objects
+reflective. Currently reflections are rendered only on planes and on the Z+ faces of boxes. Note that model elements
+can also have their local RGBA parameter for setting color. If both material and local RGBA are specified, the local
+definition has precedence.
.. _Kinematic:
@@ -417,171 +432,209 @@ within a body and belong to that body. This is in contrast with the stand-alone
associated with a single body.
Body
- Bodies have mass and inertial properties but do not have any geometric properties. Instead geometric shapes (or
- geoms) are attached to the bodies. Each body has two coordinate frames: the frame used to define it as well as to
- position other elements relative to it, and an inertial frame centered at the body's center of mass and aligned with
- its principal axes of inertia. The body inertia matrix is therefore diagonal in this frame. At each time step MuJoCo
- computes the forward kinematics recursively, yielding all body positions and orientations in global Cartesian
- coordinates. This provides the basis for all subsequent computations.
+^^^^
+
+Bodies have mass and inertial properties but do not have any geometric properties. Instead geometric shapes (or
+geoms) are attached to the bodies. Each body has two coordinate frames: the frame used to define it as well as to
+position other elements relative to it, and an inertial frame centered at the body's center of mass and aligned with
+its principal axes of inertia. The body inertia matrix is therefore diagonal in this frame. At each time step MuJoCo
+computes the forward kinematics recursively, yielding all body positions and orientations in global Cartesian
+coordinates. This provides the basis for all subsequent computations.
Joint
- Joints are defined within bodies. They create motion degrees of freedom (DOFs) between the body and its parent. In
- the absence of joints the body is welded to its parent. This is the opposite of gaming engines which use
- over-complete Cartesian coordinates, where joints remove DOFs instead of adding them. There are four types of joints:
- ball, slide, hinge, and a "free joint" which creates floating bodies. A single body can have multiple joints. In this
- way composite joints are created automatically, without having to define dummy bodies. The orientation components of
- ball and free joints are represented as unit quaternions, and all computations in MuJoCo respect the properties of
- quaternions.
+^^^^^
+
+Joints are defined within bodies. They create motion degrees of freedom (DOFs) between the body and its parent. In
+the absence of joints the body is welded to its parent. This is the opposite of gaming engines which use
+over-complete Cartesian coordinates, where joints remove DOFs instead of adding them. There are four types of joints:
+ball, slide, hinge, and a "free joint" which creates floating bodies. A single body can have multiple joints. In this
+way composite joints are created automatically, without having to define dummy bodies. The orientation components of
+ball and free joints are represented as unit quaternions, and all computations in MuJoCo respect the properties of
+quaternions.
+
+Joint reference
+'''''''''''''''
+
+The reference pose is a vector of joint positions stored in ``mjModel.qpos0``. It corresponds to the numeric values
+of the joints when the model is in its initial configuration. In our earlier example the elbow was created in a bent
+configuration at 90° angle. But MuJoCo does not know what an elbow is, and so by default it treats this joint
+configuration as having numeric value of 0. We can override the default behavior and specify that the initial
+configuration corresponds to 90°, using the ref attribute of :ref:`joint `. The reference values of all
+joints are assembled into the vector ``mjModel.qpos0``. Whenever the simulation is reset, the joint configuration
+``mjData.qpos`` is set to ``mjModel.qpos0``. At runtime the joint position vector is interpreted relative to the
+reference pose. In particular, the amount of spatial transformation applied by the joints is ``mjData.qpos -
+mjModel.qpos0``. This transformation is in addition to the parent-child translation and rotation offsets stored in
+the body elements of ``mjModel``. The ref attribute only applies to scalar joints (slide and hinge). For ball joints,
+the quaternion saved in ``mjModel.qpos0`` is always (1,0,0,0) which corresponds to the null rotation. For free
+joints, the global 3D position and quaternion of the floating body are saved in ``mjModel.qpos0``.
+
+Spring reference
+''''''''''''''''
+
+This is the pose in which all joint and tendon springs achieve their resting length. Spring forces are generated
+when the joint configuration deviates from the spring reference pose, and are linear in the amount of deviation. The
+spring reference pose is saved in ``mjModel.qpos_spring``. For slide and hinge joints, the spring reference is
+specified with the attribute springref. For ball and free joints, the spring reference corresponds to the initial
+model configuration.
DOF
- Degrees of freedom are closely related to joints, but are not in one-to-one correspondence because ball and free
- joints have multiple DOFs. Think of joints as specifying positional information, and of DOFs as specifying velocity
- and force information. More formally, the joint positions are coordinates over the configuration manifold of the
- system, while the joint velocities are coordinates over the tangent space to this manifold at the current position.
- DOFs have velocity-related properties such as friction loss, damping, armature inertia. All generalized forces acting
- on the system are expressed in the space of DOFs. In contrast, joints have position-related properties such as limits
- and spring stiffness. DOFs are not specified directly by the user. Instead they are created by the compiler given the
- joints.
+^^^
+
+Degrees of freedom are closely related to joints, but are not in one-to-one correspondence because ball and free
+joints have multiple DOFs. Think of joints as specifying positional information, and of DOFs as specifying velocity
+and force information. More formally, the joint positions are coordinates over the configuration manifold of the
+system, while the joint velocities are coordinates over the tangent space to this manifold at the current position.
+DOFs have velocity-related properties such as friction loss, damping, armature inertia. All generalized forces acting
+on the system are expressed in the space of DOFs. In contrast, joints have position-related properties such as limits
+and spring stiffness. DOFs are not specified directly by the user. Instead they are created by the compiler given the
+joints.
Geom
- Geoms are 3D shapes rigidly attached to the bodies. Multiple geoms can be attached to the same body. This is
- particularly useful in light of the fact that MuJoCo only supports convex geom-geom collisions, and the only way to
- create non-convex objects is to represent them as a union of convex geoms. Apart from collision detection and
- subsequent computation of contact forces, geoms are used for rendering, as well as automatic inference of body masses
- and inertias when the latter are omitted. MuJoCo supports several primitive geometric shapes: plane, sphere, capsule,
- ellipsoid, cylinder, box. A geom can also be a mesh or a height field; this is done by referencing the corresponding
- asset. Geoms have a number of material properties that affect the simulation and visualization.
+^^^^
+
+Geoms are 3D shapes rigidly attached to the bodies. Multiple geoms can be attached to the same body. This is
+particularly useful in light of the fact that MuJoCo only supports convex geom-geom collisions, and the only way to
+create non-convex objects is to represent them as a union of convex geoms. Apart from collision detection and
+subsequent computation of contact forces, geoms are used for rendering, as well as automatic inference of body masses
+and inertias when the latter are omitted. MuJoCo supports several primitive geometric shapes: plane, sphere, capsule,
+ellipsoid, cylinder, box. A geom can also be a mesh or a height field; this is done by referencing the corresponding
+asset. Geoms have a number of material properties that affect the simulation and visualization.
Site
- Sites are essentially light geoms. They represent locations of interest within the body frame. Sites do not
- participate in collision detection or automated computation of inertial properties, however they can be used to
- specify the spatial properties of other objects like sensors, tendon routing, and slider-crank endpoints.
+^^^^
+
+Sites are essentially light geoms. They represent locations of interest within the body frame. Sites do not
+participate in collision detection or automated computation of inertial properties, however they can be used to
+specify the spatial properties of other objects like sensors, tendon routing, and slider-crank endpoints.
Camera
- Multiple cameras can be defined in a model. There is always a default camera which the user can freely move with the
- mouse in the interactive visualizer. However it is often convenient to define additional cameras that are either
- fixed to the world, or are attached to one of the bodies and move with it. In addition to the camera position and
- orientation, the user can adjust the field of view and the inter-pupilary distance for stereoscopic rendering, as
- well as create oblique projections needed for stereoscopic virtual environments.
+^^^^^^
+
+Multiple cameras can be defined in a model. There is always a default camera which the user can freely move with the
+mouse in the interactive visualizer. However it is often convenient to define additional cameras that are either
+fixed to the world, or are attached to one of the bodies and move with it. In addition to the camera position and
+orientation, the user can adjust the field of view and the inter-pupilary distance for stereoscopic rendering, as
+well as create oblique projections needed for stereoscopic virtual environments.
Light
- Lights can be fixed to the world body or attached to moving bodies. The visualizer provides access to the full
- lighting model in OpenGL (fixed function) including ambient, diffuse and specular components, attenuation and cutoff,
- positional and directional lighting, fog. Lights, or rather the objects illuminated by them, can also cast shadows.
- However, similar to material reflections, each shadow-casting light adds one rendering pass so this feature should be
- used with caution. Documenting the lighting model in detail is beyond the scope of this chapter; see `OpenGL
- documentation `__ instead. Note that in addition to lights defined
- by the user in the kinematic tree, there is a default headlight that moves with the camera. Its properties are
- adjusted through the mjVisual options.
+^^^^^
+
+Lights can be fixed to the world body or attached to moving bodies. The visualizer provides access to the full
+lighting model in OpenGL (fixed function) including ambient, diffuse and specular components, attenuation and cutoff,
+positional and directional lighting, fog. Lights, or rather the objects illuminated by them, can also cast shadows.
+However, similar to material reflections, each shadow-casting light adds one rendering pass so this feature should be
+used with caution. Documenting the lighting model in detail is beyond the scope of this chapter; see `OpenGL
+documentation `__ instead. Note that in addition to lights defined
+by the user in the kinematic tree, there is a default headlight that moves with the camera. Its properties are
+adjusted through the mjVisual options.
.. _Standalone:
-Stand-alone elements
-~~~~~~~~~~~~~~~~~~~~
+Stand-alone
+~~~~~~~~~~~
Here we describe the model elements which do not belong to an individual body, and therefore are described outside the
kinematic tree.
-Reference pose
- The reference pose is a vector of joint positions stored in ``mjModel.qpos0``. It corresponds to the numeric values
- of the joints when the model is in its initial configuration. In our earlier example the elbow was created in a bent
- configuration at 90° angle. But MuJoCo does not know what an elbow is, and so by default it treats this joint
- configuration as having numeric value of 0. We can override the default behavior and specify that the initial
- configuration corresponds to 90°, using the ref attribute of :ref:`joint `. The reference values of all
- joints are assembled into the vector ``mjModel.qpos0``. Whenever the simulation is reset, the joint configuration
- ``mjData.qpos`` is set to ``mjModel.qpos0``. At runtime the joint position vector is interpreted relative to the
- reference pose. In particular, the amount of spatial transformation applied by the joints is ``mjData.qpos -
- mjModel.qpos0``. This transformation is in addition to the parent-child translation and rotation offsets stored in
- the body elements of ``mjModel``. The ref attribute only applies to scalar joints (slide and hinge). For ball joints,
- the quaternion saved in ``mjModel.qpos0`` is always (1,0,0,0) which corresponds to the null rotation. For free
- joints, the global 3D position and quaternion of the floating body are saved in ``mjModel.qpos0``.
-
-Spring reference pose
- This is the pose in which all joint and tendon springs achieve their resting length. Spring forces are generated
- when the joint configuration deviates from the spring reference pose, and are linear in the amount of deviation. The
- spring reference pose is saved in ``mjModel.qpos_spring``. For slide and hinge joints, the spring reference is
- specified with the attribute springref. For ball and free joints, the spring reference corresponds to the initial
- model configuration.
-
Tendon
- Tendons are scalar length elements that can be used for actuation, imposing limits and equality constraints, or
- creating spring-dampers and friction loss. There are two types of tendons: fixed and spatial. Fixed tendons are
- linear combinations of (scalar) joint positions. They are useful for modeling mechanical coupling. Spatial tendons
- are defined as the shortest path that passes through a sequence of specified sites (or via-points) or wraps around
- specified geoms. Only spheres and cylinders are supported as wrapping geoms, and cylinders are treated as having
- infinite length for wrapping purposes. To avoid abrupt jumps of the tendon from one side of the wrapping geom to the
- other, the user can also specify the preferred side. If there are multiple wrapping geoms in the tendon path they
- must be separated by sites, so as to avoid the need for an iterative solver. Spatial tendons can also be split into
- multiple branches using pulleys.
+^^^^^^
+
+Tendons are scalar length elements that can be used for actuation, imposing limits and equality constraints, or
+creating spring-dampers and friction loss. There are two types of tendons: fixed and spatial. Fixed tendons are
+linear combinations of (scalar) joint positions. They are useful for modeling mechanical coupling. Spatial tendons
+are defined as the shortest path that passes through a sequence of specified sites (or via-points) or wraps around
+specified geoms. Only spheres and cylinders are supported as wrapping geoms, and cylinders are treated as having
+infinite length for wrapping purposes. To avoid abrupt jumps of the tendon from one side of the wrapping geom to the
+other, the user can also specify the preferred side. If there are multiple wrapping geoms in the tendon path they
+must be separated by sites, so as to avoid the need for an iterative solver. Spatial tendons can also be split into
+multiple branches using pulleys.
Actuator
- MuJoCo provides a flexible actuator model, with three components that can be specified independently. Together they
- determine how the actuator works. Common actuator types are obtained by specifying these components in a coordinated
- way. The three components are transmission, activation dynamics, and force generation. The transmission specifies how
- the actuator is attached to the rest of the system; available types are joint, tendon and slider-crank. The
- activation dynamics can be used to model internal activation states of pneumatic or hydraulic cylinders as well as
- biological muscles; using such actuators makes the overall system dynamics 3rd-order. The force generation mechanism
- determines how the scalar control signal provided as input to the actuator is mapped into a scalar force, which is in
- turn mapped into a generalized force by the moment arms inferred from the transmission.
+^^^^^^^^
+
+MuJoCo provides a flexible actuator model, with three components that can be specified independently. Together they
+determine how the actuator works. Common actuator types are obtained by specifying these components in a coordinated
+way. The three components are transmission, activation dynamics, and force generation. The transmission specifies how
+the actuator is attached to the rest of the system; available types are joint, tendon and slider-crank. The
+activation dynamics can be used to model internal activation states of pneumatic or hydraulic cylinders as well as
+biological muscles; using such actuators makes the overall system dynamics 3rd-order. The force generation mechanism
+determines how the scalar control signal provided as input to the actuator is mapped into a scalar force, which is in
+turn mapped into a generalized force by the moment arms inferred from the transmission.
Sensor
- MuJoCo can generate simulated sensor data which is saved in the global array ``mjData.sensordata``. The result is not
- used in any internal computations; instead it is provided because the user presumably needs it for custom computation
- or data analysis. Available sensor types include touch sensors, inertial measurement units (IMUs), force-torque
- sensors, joint and tendon position and velocity sensors, actuator position, velocity and force sensors, motion
- capture marker positions and quaternions, and magnetometers. Some of these require extra computation, while others
- are copied from the corresponding fields of ``mjData``. There is also a user sensor, allowing user code to insert any
- other quantity of interest in the sensor data array. MuJoCo also has off-screen rendering capabilities, making it
- straightforward to simulate both color and depth camera sensors. This is not included in the standard sensor model
- and instead has to be done programmatically, as illustrated in the code sample :ref:`simulate.cc `.
+^^^^^^
+
+MuJoCo can generate simulated sensor data which is saved in the global array ``mjData.sensordata``. The result is not
+used in any internal computations; instead it is provided because the user presumably needs it for custom computation
+or data analysis. Available sensor types include touch sensors, inertial measurement units (IMUs), force-torque
+sensors, joint and tendon position and velocity sensors, actuator position, velocity and force sensors, motion
+capture marker positions and quaternions, and magnetometers. Some of these require extra computation, while others
+are copied from the corresponding fields of ``mjData``. There is also a user sensor, allowing user code to insert any
+other quantity of interest in the sensor data array. MuJoCo also has off-screen rendering capabilities, making it
+straightforward to simulate both color and depth camera sensors. This is not included in the standard sensor model
+and instead has to be done programmatically, as illustrated in the code sample :ref:`simulate.cc `.
Equality
- Equality constraints can impose additional constraints beyond those already imposed by the kinematic tree structure
- and the joints/DOFs defined in it. They can be used to create loop joints, or in general model mechanical coupling.
- The internal forces that enforce these constraints are computed together with all other constraint forces. The
- available equality constraint types are: connect two bodies at a point (creating a ball joint outside the kinematic
- tree); weld two bodies together; make two surfaces slide on each other; fix the position of a joint or tendon; couple
- the positions of two joints or two tendons via a cubic polynomial.
+^^^^^^^^
+
+Equality constraints can impose additional constraints beyond those already imposed by the kinematic tree structure
+and the joints/DOFs defined in it. They can be used to create loop joints, or in general model mechanical coupling.
+The internal forces that enforce these constraints are computed together with all other constraint forces. The
+available equality constraint types are: connect two bodies at a point (creating a ball joint outside the kinematic
+tree); weld two bodies together; make two surfaces slide on each other; fix the position of a joint or tendon; couple
+the positions of two joints or two tendons via a cubic polynomial.
Contact pair
- Contact generation in MuJoCo is an elaborate process. Geom pairs that are checked for contact can come from two
- sources: automated proximity tests and other filters collectively called "dynamic", as well as an explicit list of
- geom pairs provided in the model. The latter is a separate type of model element. Because a contact involves a
- combination of two geoms, the explicit specification allows the user to define contact parameters in ways that cannot
- be done with the dynamic mechanism. It is also useful for fine-tuning the contact model, in particular adding contact
- pairs that were removed by an aggressive filtering scheme.
+^^^^^^^^^^^^
+
+Contact generation in MuJoCo is an elaborate process. Geom pairs that are checked for contact can come from two
+sources: automated proximity tests and other filters collectively called "dynamic", as well as an explicit list of
+geom pairs provided in the model. The latter is a separate type of model element. Because a contact involves a
+combination of two geoms, the explicit specification allows the user to define contact parameters in ways that cannot
+be done with the dynamic mechanism. It is also useful for fine-tuning the contact model, in particular adding contact
+pairs that were removed by an aggressive filtering scheme.
Contact exclude
- This is the opposite of contact pairs: it specifies pairs of bodies (rather than geoms) which should be excluded from
- the generation of candidate contact pairs. It is useful for disabling contacts between bodies whose geometry causes
- an undesirable permanent contact. Note that MuJoCo has other mechanisms for dealing with this situation (in
- particular geoms cannot collide if they belong to the same body or to a parent and a child body), but sometimes these
- automated mechanisms are not sufficient and explicit exclusion becomes necessary.
+^^^^^^^^^^^^^^^
+
+This is the opposite of contact pairs: it specifies pairs of bodies (rather than geoms) which should be excluded from
+the generation of candidate contact pairs. It is useful for disabling contacts between bodies whose geometry causes
+an undesirable permanent contact. Note that MuJoCo has other mechanisms for dealing with this situation (in
+particular geoms cannot collide if they belong to the same body or to a parent and a child body), but sometimes these
+automated mechanisms are not sufficient and explicit exclusion becomes necessary.
Custom numeric
- There are three ways to enter custom numbers in a MuJoCo simulation. First, global numeric fields can be defined in
- the XML. They have a name and an array of real values. Second, the definition of certain model elements can be
- extended with element-specific custom arrays. This is done by setting the attributes ``nuser_XXX`` in the XML element
- ``size``. Third, there is the array ``mjData.userdata`` which is not used by any MuJoCo computations. The user can
- store results from custom computations there; recall that everything that changes over time should be stored in
- ``mjData`` and not in ``mjModel``.
+^^^^^^^^^^^^^^
+
+There are three ways to enter custom numbers in a MuJoCo simulation. First, global numeric fields can be defined in
+the XML. They have a name and an array of real values. Second, the definition of certain model elements can be
+extended with element-specific custom arrays. This is done by setting the attributes ``nuser_XXX`` in the XML element
+``size``. Third, there is the array ``mjData.userdata`` which is not used by any MuJoCo computations. The user can
+store results from custom computations there; recall that everything that changes over time should be stored in
+``mjData`` and not in ``mjModel``.
Custom text
- Custom text fields can be saved in the model. They can be used in custom computations - either to specify keyword
- commands, or to provide some other textual information. Do not use them for comments though; there is no benefit to
- saving comments in a compiled model. XML has its own commenting mechanism (ignored by MuJoCo's parser and compiler)
- which is more suitable.
+^^^^^^^^^^^
+
+Custom text fields can be saved in the model. They can be used in custom computations - either to specify keyword
+commands, or to provide some other textual information. Do not use them for comments though; there is no benefit to
+saving comments in a compiled model. XML has its own commenting mechanism (ignored by MuJoCo's parser and compiler)
+which is more suitable.
Custom tuple
- Custom tuples are lists of MuJoCo model elements, possibly including other tuples. They are not used by the
- simulator, but are available for specifying groups of elements that are needed for user code. For example, one can
- use tuples to define pairs of bodies for custom contact processing.
+^^^^^^^^^^^^
+
+Custom tuples are lists of MuJoCo model elements, possibly including other tuples. They are not used by the
+simulator, but are available for specifying groups of elements that are needed for user code. For example, one can
+use tuples to define pairs of bodies for custom contact processing.
Keyframe
- A keyframe is a snapshot of the simulation state variables. It contains the vectors of joint positions, joint
- velocities, actuator activations when present, and the simulation time. The model can contain a library of keyframes.
- They are useful for resetting the state of the system to a point of interest. Note that keyframes are not intended
- for storing trajectory data in the model; external files should be used for this purpose.
+^^^^^^^^
+
+A keyframe is a snapshot of the simulation state variables. It contains the vectors of joint positions, joint
+velocities, actuator activations when present, and the simulation time. The model can contain a library of keyframes.
+They are useful for resetting the state of the system to a point of interest. Note that keyframes are not intended
+for storing trajectory data in the model; external files should be used for this purpose.
.. _Clarifications:
From ba5cf4d54d63b815f7e70ee21bc318c3a47a866d Mon Sep 17 00:00:00 2001
From: Google DeepMind
Date: Thu, 31 Aug 2023 03:24:01 -0700
Subject: [PATCH 08/38] Address -Wint-conversion issues.
PiperOrigin-RevId: 561604684
Change-Id: I31af8621f8506d2e029c4ca630982f82ea4cfca9
---
src/engine/engine_io.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c
index 159ab555..9ba2674d 100644
--- a/src/engine/engine_io.c
+++ b/src/engine/engine_io.c
@@ -1272,12 +1272,12 @@ static inline void* stackalloc(mjData* d, size_t size, size_t alignment) {
}
// store new stack usage in the red zone
- ASAN_UNPOISON_MEMORY_REGION(new_pstack_ptr, sizeof(size_t));
+ ASAN_UNPOISON_MEMORY_REGION((void*)new_pstack_ptr, sizeof(size_t));
*(size_t*)new_pstack_ptr = usage;
- ASAN_POISON_MEMORY_REGION(new_pstack_ptr, sizeof(size_t));
+ ASAN_POISON_MEMORY_REGION((void*)new_pstack_ptr, sizeof(size_t));
// unpoison the actual usable allocation
- ASAN_UNPOISON_MEMORY_REGION(start_ptr, size);
+ ASAN_UNPOISON_MEMORY_REGION((void*)start_ptr, size);
#endif
#undef mjREDZONE
From 129abc6bca232d204ec8ae835befcdd2c64aac18 Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Thu, 31 Aug 2023 05:10:14 -0700
Subject: [PATCH 09/38] Add private function `mj_solveM_island` for mass matrix
solve with sub indices corresponding to one island.
PiperOrigin-RevId: 561622962
Change-Id: Ib43f03b3dd0c7456c57d4ba45a68e0708bf1c91c
---
src/engine/engine_core_smooth.c | 62 +++++++++++++++++++++++++-
src/engine/engine_core_smooth.h | 3 ++
test/engine/engine_core_smooth_test.cc | 59 ++++++++++++++++++++++++
3 files changed, 123 insertions(+), 1 deletion(-)
diff --git a/src/engine/engine_core_smooth.c b/src/engine/engine_core_smooth.c
index 9b4affe7..b8a667d6 100644
--- a/src/engine/engine_core_smooth.c
+++ b/src/engine/engine_core_smooth.c
@@ -1086,7 +1086,7 @@ void mj_factorM(const mjModel* m, mjData* d) {
-// sparse backsubstitution: x = inv(L'*D*L)*y
+// in-place sparse backsubstitution: x = inv(L'*D*L)*x
// L is in lower triangle of qLD; D is on diagonal of qLD
// handle n vectors at once
void mj_solveLD(const mjModel* m, mjtNum* restrict x, int n,
@@ -1209,6 +1209,66 @@ void mj_solveM(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n) {
}
+// in-place sparse backsubstitution for one island: x = inv(L'*D*L)*x
+// L is in lower triangle of qLD; D is on diagonal of qLD
+void mj_solveM_island(const mjModel* m, const mjData* d, mjtNum* restrict x, int island) {
+ // local constants: general
+ const int* Madr = m->dof_Madr;
+ const int* parentid = m->dof_parentid;
+ const mjtNum* qLD = d->qLD;
+ const mjtNum* qLDiagInv = d->qLDiagInv;
+ const int* simplenum = m->dof_simplenum;
+
+ // local constants: island specific
+ int ndof = d->island_dofnum[island];
+ const int* dofind = d->island_dofind + d->island_dofadr[island];
+ const int* islandind = d->dof_islandind;
+
+ // x <- inv(L') * x; skip simple, exploit sparsity of input vector
+ for (int k=ndof-1; k >= 0; k--) {
+ int i = dofind[k];
+ if (!simplenum[i] && x[k]) {
+ // init
+ int Madr_ij = Madr[i]+1;
+ int j = parentid[i];
+
+ // traverse ancestors backwards
+ // read directly from x[l] since j cannot be a parent of itself
+ while (j >= 0) {
+ x[islandind[j]] -= qLD[Madr_ij++]*x[k]; // x(j) -= L(i,j) * x(i)
+
+ // advance to parent
+ j = parentid[j];
+ }
+ }
+ }
+
+ // x <- inv(D) * x
+ for (int k=ndof-1; k >= 0; k--) {
+ x[k] *= qLDiagInv[dofind[k]]; // x(i) /= L(i,i)
+ }
+
+ // x <- inv(L) * x; skip simple
+ for (int k=0; k < ndof; k++) {
+ int i = dofind[k];
+ if (!simplenum[i]) {
+ // init
+ int Madr_ij = Madr[i]+1;
+ int j = parentid[i];
+
+ // traverse ancestors backwards
+ // write directly in x[i] since i cannot be a parent of itself
+ while (j >= 0) {
+ x[k] -= qLD[Madr_ij++]*x[islandind[j]]; // x(i) -= L(i,j) * x(j)
+
+ // advance to parent
+ j = parentid[j];
+ }
+ }
+ }
+}
+
+
// half of sparse backsubstitution: x = sqrt(inv(D))*inv(L')*y
void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n) {
diff --git a/src/engine/engine_core_smooth.h b/src/engine/engine_core_smooth.h
index edaa4133..48776db5 100644
--- a/src/engine/engine_core_smooth.h
+++ b/src/engine/engine_core_smooth.h
@@ -59,6 +59,9 @@ MJAPI void mj_solveLD(const mjModel* m, mjtNum* x, int n,
// sparse backsubstitution: x = inv(L'*D*L)*y, use factorization in d
MJAPI void mj_solveM(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n);
+// sparse backsubstitution for one island: x = inv(L'*D*L)*x, use factorization in d
+MJAPI void mj_solveM_island(const mjModel* m, const mjData* d, mjtNum* x, int island);
+
// half of sparse backsubstitution: x = sqrt(inv(D))*inv(L')*y
MJAPI void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n);
diff --git a/test/engine/engine_core_smooth_test.cc b/test/engine/engine_core_smooth_test.cc
index 7f672263..6d123d1a 100644
--- a/test/engine/engine_core_smooth_test.cc
+++ b/test/engine/engine_core_smooth_test.cc
@@ -14,8 +14,11 @@
// Tests for engine/engine_core_smooth.c.
+#include "src/engine/engine_core_smooth.h"
+
#include
#include
+#include
#include
#include
@@ -291,5 +294,61 @@ TEST_F(CoreSmoothTest, RefsiteBringsToPose) {
mj_deleteModel(model);
}
+static const char* const kIlslandEfcPath =
+ "engine/testdata/island/island_efc.xml";
+
+TEST_F(CoreSmoothTest, SolveMIsland) {
+ const std::string xml_path = GetTestDataFilePath(kIlslandEfcPath);
+ mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
+ mjData* data = mj_makeData(model);
+ int nv = model->nv;
+
+ // allocate vec, fill with arbitrary values, copy to sol
+ mjtNum* vec = (mjtNum*) mju_malloc(sizeof(mjtNum) * nv);
+ mjtNum* res = (mjtNum*) mju_malloc(sizeof(mjtNum) * nv);
+ for (int i=0; i < nv; i++) {
+ vec[i] = 0.2 + 0.3*i;
+ }
+ mju_copy(res, vec, nv);
+
+ // simulate for 0.3 seconds
+ mj_resetData(model, data);
+ while (data->time < 0.3) {
+ mj_step(model, data);
+ }
+ mj_forward(model, data);
+
+ // divide by mass matrix: sol = M^-1 * vec
+ mj_solveM(model, data, res, res, 1);
+
+ // iterate over islands
+ for (int i=0; i < data->nisland; i++) {
+ // allocate dof vectors for island
+ int dofnum = data->island_dofnum[i];
+ mjtNum* res_i = (mjtNum*)mju_malloc(sizeof(mjtNum) * dofnum);
+
+ // copy values into sol_i
+ int* dofind = data->island_dofind + data->island_dofadr[i];
+ for (int j=0; j < dofnum; j++) {
+ res_i[j] = vec[dofind[j]];
+ }
+
+ // divide by mass matrix, for this island
+ mj_solveM_island(model, data, res_i, i);
+
+ // expect corresponding values to match
+ for (int j=0; j < dofnum; j++) {
+ EXPECT_THAT(res_i[j], DoubleNear(res[dofind[j]], 1e-12));
+ }
+
+ mju_free(res_i);
+ }
+
+ mju_free(res);
+ mju_free(vec);
+ mj_deleteData(data);
+ mj_deleteModel(model);
+}
+
} // namespace
} // namespace mujoco
From 9fd186ac2bce133eb042fb9a6c116d26a7ca8276 Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Thu, 31 Aug 2023 06:41:55 -0700
Subject: [PATCH 10/38] Add private function `mj_mulM_island` for mass matrix
multiplication with sub indices corresponding to one island.
PiperOrigin-RevId: 561639405
Change-Id: I3777cea51ef22f6847b3cf3e17bdac5cf621c019
---
src/engine/engine_support.c | 52 +++++++++++++++++++++++++++
src/engine/engine_support.h | 4 +++
test/engine/engine_support_test.cc | 58 ++++++++++++++++++++++++++++++
3 files changed, 114 insertions(+)
diff --git a/src/engine/engine_support.c b/src/engine/engine_support.c
index 7e57a21a..c58e5e06 100644
--- a/src/engine/engine_support.c
+++ b/src/engine/engine_support.c
@@ -884,6 +884,58 @@ void mj_mulM(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec)
+// multiply vector by inertia matrix for one dof island
+void mj_mulM_island(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec, int island) {
+ // if no island, call regular function
+ if (island < 0) {
+ mj_mulM(m, d, res, vec);
+ return;
+ }
+
+ // local constants: general
+ const mjtNum* M = d->qM;
+ const int* Madr = m->dof_Madr;
+ const int* parentid = m->dof_parentid;
+ const int* simplenum = m->dof_simplenum;
+
+ // local constants: island specific
+ int ndof = d->island_dofnum[island];
+ const int* dofind = d->island_dofind + d->island_dofadr[island];
+ const int* islandind = d->dof_islandind;
+
+ mju_zero(res, ndof);
+
+ for (int k=0; k < ndof; k++) {
+ // address in full dof vector
+ int i = dofind[k];
+
+ // address in M
+ int adr = Madr[i];
+
+ // diagonal
+ res[k] = M[adr]*vec[k];
+
+ // simple dof: continue
+ if (simplenum[i]) {
+ continue;
+ }
+
+ // off-diagonal
+ int j = parentid[i];
+ while (j >= 0) {
+ adr++;
+ int l = islandind[j];
+ res[k] += M[adr]*vec[l];
+ res[l] += M[adr]*vec[k];
+
+ // advance to parent
+ j = parentid[j];
+ }
+ }
+}
+
+
+
// multiply vector by M^(1/2)
void mj_mulM2(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec) {
int adr, nv = m->nv;
diff --git a/src/engine/engine_support.h b/src/engine/engine_support.h
index ce601a06..3b10fbe7 100644
--- a/src/engine/engine_support.h
+++ b/src/engine/engine_support.h
@@ -108,6 +108,10 @@ MJAPI void mj_fullM(const mjModel* m, mjtNum* dst, const mjtNum* M);
// multiply vector by inertia matrix
MJAPI void mj_mulM(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
+// multiply vector by inertia matrix for one dof island
+MJAPI void mj_mulM_island(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec,
+ int island);
+
// multiply vector by (inertia matrix)^(1/2)
MJAPI void mj_mulM2(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
diff --git a/test/engine/engine_support_test.cc b/test/engine/engine_support_test.cc
index b2a1a59a..a33b3f28 100644
--- a/test/engine/engine_support_test.cc
+++ b/test/engine/engine_support_test.cc
@@ -14,6 +14,8 @@
// Tests for engine/engine_support.c.
+#include "src/engine/engine_support.h"
+
#include
#include
#include
@@ -453,5 +455,61 @@ TEST_F(AddMTest, DenseSameAsSparse) {
mj_deleteModel(m);
}
+static const char* const kIlslandEfcPath =
+ "engine/testdata/island/island_efc.xml";
+
+TEST_F(SupportTest, MulMIsland) {
+ const std::string xml_path = GetTestDataFilePath(kIlslandEfcPath);
+ mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
+ mjData* data = mj_makeData(model);
+
+ // allocate vec, fill with arbitrary values
+ mjtNum* vec = (mjtNum*) mju_malloc(sizeof(mjtNum)*model->nv);
+ for (int i=0; i < model->nv; i++) {
+ vec[i] = 0.2 + 0.3*i;
+ }
+
+ // simulate for 0.3 seconds
+ mj_resetData(model, data);
+ while (data->time < 0.3) {
+ mj_step(model, data);
+ }
+ mj_forward(model, data);
+
+ // multiply by Mass matrix: Mvec = M * vec
+ mjtNum* Mvec = (mjtNum*) mju_malloc(sizeof(mjtNum)*data->nefc);
+ mj_mulM(model, data, Mvec, vec);
+
+ // iterate over islands
+ for (int i=0; i < data->nisland; i++) {
+ // allocate dof vectors for island
+ int dofnum = data->island_dofnum[i];
+ mjtNum* vec_i = (mjtNum*)mju_malloc(sizeof(mjtNum) * dofnum);
+ mjtNum* Mvec_i = (mjtNum*)mju_malloc(sizeof(mjtNum) * dofnum);
+
+ // copy values into vec_i
+ int* dofind = data->island_dofind + data->island_dofadr[i];
+ for (int j=0; j < dofnum; j++) {
+ vec_i[j] = vec[dofind[j]];
+ }
+
+ // multiply by Jacobian, for this island
+ mj_mulM_island(model, data, Mvec_i, vec_i, i);
+
+ // expect corresponding values to match
+ for (int j=0; j < dofnum; j++) {
+ EXPECT_THAT(Mvec_i[j], DoubleNear(Mvec[dofind[j]], 1e-12));
+ }
+
+ mju_free(vec_i);
+ mju_free(Mvec_i);
+ }
+
+ mju_free(Mvec);
+ mju_free(vec);
+ mj_deleteData(data);
+ mj_deleteModel(model);
+}
+
} // namespace
} // namespace mujoco
From 85fd922b376081345daa5e2eb1a11a7866542f0e Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Thu, 31 Aug 2023 07:27:34 -0700
Subject: [PATCH 11/38] Add private function `mj_mulJacVec_island` for Jacobian
multiplication with sub indices corresponding to one island.
PiperOrigin-RevId: 561648540
Change-Id: I8080a620807e824b40b5d6ba484beb06d081d444
---
src/engine/engine_core_constraint.c | 39 +++++++++++++
src/engine/engine_core_constraint.h | 4 ++
test/engine/engine_core_constraint_test.cc | 65 +++++++++++++++++++++-
3 files changed, 107 insertions(+), 1 deletion(-)
diff --git a/src/engine/engine_core_constraint.c b/src/engine/engine_core_constraint.c
index 0de536ad..34b2892e 100644
--- a/src/engine/engine_core_constraint.c
+++ b/src/engine/engine_core_constraint.c
@@ -395,6 +395,45 @@ void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec) {
+// multiply Jacobian by vector, for one island
+void mj_mulJacVec_island(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec, int island) {
+ // no island, call regular function
+ if (island < 0) {
+ mj_mulJacVec(m, d, res, vec);
+ return;
+ }
+
+ // sizes
+ int vecnnz = d->island_dofnum[island];
+ int resnnz = d->island_efcnum[island];
+
+ // indices
+ int* vecind = d->island_dofind + d->island_dofadr[island];
+ int* resind = d->island_efcind + d->island_efcadr[island];
+
+ // sparse Jacobian
+ if (mj_isSparse(m)) {
+ for (int i=0; i < resnnz; i++) {
+ int row = resind[i];
+ int Jnnz = d->efc_J_rownnz[row];
+ int Jrowadr = d->efc_J_rowadr[row];
+ int* Jind = d->efc_J_colind + Jrowadr;
+ mjtNum* J = d->efc_J + Jrowadr;
+ res[i] = mju_dotSparse2(vec, J, vecnnz, vecind, Jnnz, Jind);
+ }
+ }
+
+ // dense Jacobian
+ else {
+ int nv = m->nv;
+ for (int i=0; i < resnnz; i++) {
+ res[i] = mju_dotSparse(vec, d->efc_J + nv*resind[i], vecnnz, vecind);
+ }
+ }
+}
+
+
+
// multiply JacobianT by vector
void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec) {
// exit if no constraints
diff --git a/src/engine/engine_core_constraint.h b/src/engine/engine_core_constraint.h
index f90d1322..afa3447c 100644
--- a/src/engine/engine_core_constraint.h
+++ b/src/engine/engine_core_constraint.h
@@ -38,6 +38,10 @@ MJAPI int mj_isDual(const mjModel* m);
// multiply Jacobian by vector
MJAPI void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
+// multiply Jacobian by vector, for one island
+MJAPI void mj_mulJacVec_island(const mjModel* m, mjData* d,
+ mjtNum* res, const mjtNum* vec, int island);
+
// multiply JacobianT by vector
MJAPI void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
diff --git a/test/engine/engine_core_constraint_test.cc b/test/engine/engine_core_constraint_test.cc
index 8022f163..af2df04c 100644
--- a/test/engine/engine_core_constraint_test.cc
+++ b/test/engine/engine_core_constraint_test.cc
@@ -14,7 +14,6 @@
// Tests for engine/engine_core_constraint.c.
-#include
#include
#include
#include
@@ -198,5 +197,69 @@ TEST_F(CoreConstraintTest, JacobianPreAllocate) {
}
}
+static const char* const kIlslandEfcPath =
+ "engine/testdata/island/island_efc.xml";
+
+TEST_F(CoreConstraintTest, MulJacVecIsland) {
+ const std::string xml_path = GetTestDataFilePath(kIlslandEfcPath);
+ mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
+ mjData* data = mj_makeData(model);
+
+ // allocate vec_nv, fill with arbitrary values
+ mjtNum* vec_nv = (mjtNum*) mju_malloc(sizeof(mjtNum)*model->nv);
+ for (int i=0; i < model->nv; i++) {
+ vec_nv[i] = 0.2 + 0.3*i;
+ }
+
+ // iterate through dense and sparse
+ for (mjtJacobian sparsity : {mjJAC_DENSE, mjJAC_SPARSE}) {
+ model->opt.jacobian = sparsity;
+
+ // simulate for 0.3 seconds
+ mj_resetData(model, data);
+ while (data->time < 0.3) {
+ mj_step(model, data);
+ }
+ mj_forward(model, data);
+
+ // multiply by Jacobian: vec_nefc = J * vec_nv
+ mjtNum* vec_nefc = (mjtNum*) mju_malloc(sizeof(mjtNum)*data->nefc);
+ mj_mulJacVec(model, data, vec_nefc, vec_nv);
+
+ // iterate over islands
+ for (int i=0; i < data->nisland; i++) {
+ // allocate dof and efc vectors for island
+ int dofnum = data->island_dofnum[i];
+ mjtNum* vec_nvi = (mjtNum*)mju_malloc(sizeof(mjtNum) * dofnum);
+ int efcnum = data->island_efcnum[i];
+ mjtNum* vec_nefci = (mjtNum*)mju_malloc(sizeof(mjtNum) * efcnum);
+
+ // copy values into vec_nvi
+ int* dofind = data->island_dofind + data->island_dofadr[i];
+ for (int j=0; j < dofnum; j++) {
+ vec_nvi[j] = vec_nv[dofind[j]];
+ }
+
+ // multiply by Jacobian, for this island
+ mj_mulJacVec_island(model, data, vec_nefci, vec_nvi, i);
+
+ // expect corresponding values to match
+ int* efcind = data->island_efcind + data->island_efcadr[i];
+ for (int j=0; j < efcnum; j++) {
+ EXPECT_THAT(vec_nefci[j], DoubleNear(vec_nefc[efcind[j]], 1e-12));
+ }
+
+ mju_free(vec_nvi);
+ mju_free(vec_nefci);
+ }
+
+ mju_free(vec_nefc);
+ }
+
+ mju_free(vec_nv);
+ mj_deleteData(data);
+ mj_deleteModel(model);
+}
+
} // namespace
} // namespace mujoco
From eaa2572a8b87e96f74b7bd9a5523eff0524ad274 Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Thu, 31 Aug 2023 09:11:47 -0700
Subject: [PATCH 12/38] Add private function `mj_mulJacTVec_island` for
Jacobian transpose multiplication with sub indices corresponding to one
island.
PiperOrigin-RevId: 561672392
Change-Id: Ic72fd7de2a542603cc42e1c15ecb4edd27a9a986
---
src/engine/engine_core_constraint.c | 43 +++++++++++++++
src/engine/engine_core_constraint.h | 4 ++
test/engine/engine_core_constraint_test.cc | 62 ++++++++++++++++++++++
3 files changed, 109 insertions(+)
diff --git a/src/engine/engine_core_constraint.c b/src/engine/engine_core_constraint.c
index 34b2892e..d0d14caa 100644
--- a/src/engine/engine_core_constraint.c
+++ b/src/engine/engine_core_constraint.c
@@ -455,6 +455,45 @@ void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec)
+// multiply Jacobian transpose by vector, for one island
+void mj_mulJacTVec_island(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec, int island) {
+ // no island, call regular function
+ if (island < 0) {
+ mj_mulJacTVec(m, d, res, vec);
+ return;
+ }
+
+ // sizes
+ int vecnnz = d->island_efcnum[island];
+ int resnnz = d->island_dofnum[island];
+
+ // indices
+ int* vecind = d->island_efcind + d->island_efcadr[island];
+ int* resind = d->island_dofind + d->island_dofadr[island];
+
+ // sparse Jacobian
+ if (mj_isSparse(m)) {
+ for (int i=0; i < resnnz; i++) {
+ int row = resind[i];
+ int JTnnz = d->efc_JT_rownnz[row];
+ int JTrowadr = d->efc_JT_rowadr[row];
+ int* JTind = d->efc_JT_colind + JTrowadr;
+ mjtNum* JT = d->efc_JT + JTrowadr;
+ res[i] = mju_dotSparse2(vec, JT, vecnnz, vecind, JTnnz, JTind);
+ }
+ }
+
+ // dense Jacobian
+ else {
+ int nefc = d->nefc;
+ for (int i=0; i < resnnz; i++) {
+ res[i] = mju_dotSparse(vec, d->efc_JT + nefc*resind[i], vecnnz, vecind);
+ }
+ }
+}
+
+
+
//--------------------- instantiate constraints by type --------------------------------------------
// equality constraints
@@ -1772,6 +1811,10 @@ void mj_makeConstraint(const mjModel* m, mjData* d) {
// supernodes of JT
mju_superSparse(m->nv, d->efc_JT_rowsuper,
d->efc_JT_rownnz, d->efc_JT_rowadr, d->efc_JT_colind);
+ } else {
+ if (mjENABLED(mjENBL_ISLAND)) {
+ mju_transpose(d->efc_JT, d->efc_J, d->nefc, m->nv);
+ }
}
// compute diagApprox
diff --git a/src/engine/engine_core_constraint.h b/src/engine/engine_core_constraint.h
index afa3447c..3104a339 100644
--- a/src/engine/engine_core_constraint.h
+++ b/src/engine/engine_core_constraint.h
@@ -42,6 +42,10 @@ MJAPI void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum*
MJAPI void mj_mulJacVec_island(const mjModel* m, mjData* d,
mjtNum* res, const mjtNum* vec, int island);
+// multiply Jacobian transposed by vector, for one island
+MJAPI void mj_mulJacTVec_island(const mjModel* m, mjData* d,
+ mjtNum* res, const mjtNum* vec, int island);
+
// multiply JacobianT by vector
MJAPI void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
diff --git a/test/engine/engine_core_constraint_test.cc b/test/engine/engine_core_constraint_test.cc
index af2df04c..a0c2a477 100644
--- a/test/engine/engine_core_constraint_test.cc
+++ b/test/engine/engine_core_constraint_test.cc
@@ -261,5 +261,67 @@ TEST_F(CoreConstraintTest, MulJacVecIsland) {
mj_deleteModel(model);
}
+TEST_F(CoreConstraintTest, MulJacTVecIsland) {
+ const std::string xml_path = GetTestDataFilePath(kIlslandEfcPath);
+ mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
+ mjData* data = mj_makeData(model);
+
+ // allocate vec_nv
+ mjtNum* vec_nv = (mjtNum*) mju_malloc(sizeof(mjtNum)*model->nv);
+
+ // iterate through dense and sparse
+ for (mjtJacobian sparsity : {mjJAC_DENSE, mjJAC_SPARSE}) {
+ model->opt.jacobian = sparsity;
+
+ // simulate for 0.3 seconds
+ mj_resetData(model, data);
+ while (data->time < 0.3) {
+ mj_step(model, data);
+ }
+ mj_forward(model, data);
+
+ // allocate vec_nefc, fill with arbitrary values
+ mjtNum* vec_nefc = (mjtNum*) mju_malloc(sizeof(mjtNum)*data->nefc);
+ for (int i=0; i < data->nefc; i++) {
+ vec_nefc[i] = 0.2 + 0.3*i;
+ }
+
+ // multiply by Jacobian: vec_nv = J^T * vec_nefc
+ mj_mulJacTVec(model, data, vec_nv, vec_nefc);
+
+ // iterate over islands
+ for (int i=0; i < data->nisland; i++) {
+ // allocate dof and efc vectors for island
+ int dofnum = data->island_dofnum[i];
+ mjtNum* vec_nvi = (mjtNum*)mju_malloc(sizeof(mjtNum) * dofnum);
+ int efcnum = data->island_efcnum[i];
+ mjtNum* vec_nefci = (mjtNum*)mju_malloc(sizeof(mjtNum) * efcnum);
+
+ // copy values into vec_nefci
+ int* efcind = data->island_efcind + data->island_efcadr[i];
+ for (int j=0; j < efcnum; j++) {
+ vec_nefci[j] = vec_nefc[efcind[j]];
+ }
+
+ // multiply by Jacobian, for this island
+ mj_mulJacTVec_island(model, data, vec_nvi, vec_nefci, i);
+
+ // expect corresponding values to match
+ int* dofind = data->island_dofind + data->island_dofadr[i];
+ for (int j=0; j < dofnum; j++) {
+ EXPECT_THAT(vec_nvi[j], DoubleNear(vec_nv[dofind[j]], 1e-12));
+ }
+
+ mju_free(vec_nvi);
+ mju_free(vec_nefci);
+ }
+ mju_free(vec_nefc);
+ }
+
+ mju_free(vec_nv);
+ mj_deleteData(data);
+ mj_deleteModel(model);
+}
+
} // namespace
} // namespace mujoco
From f9c57f6213246f7c6fbf0952306e2d19e12b1e74 Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Thu, 31 Aug 2023 09:58:41 -0700
Subject: [PATCH 13/38] Refactor a loop in mj_constraintUpdate.
PiperOrigin-RevId: 561684763
Change-Id: Ifbca1793760ecb49f4b48a1a236bd7c1c6e73d0e
---
src/engine/engine_core_constraint.c | 81 +++++++++++++++--------------
1 file changed, 42 insertions(+), 39 deletions(-)
diff --git a/src/engine/engine_core_constraint.c b/src/engine/engine_core_constraint.c
index d0d14caa..52ebec3b 100644
--- a/src/engine/engine_core_constraint.c
+++ b/src/engine/engine_core_constraint.c
@@ -2021,51 +2021,54 @@ void mj_constraintUpdate(const mjModel* m, mjData* d, const mjtNum* jar,
force[i] = -D[i]*jar[i];
}
- // equality
- for (int i=0; i < ne; i++) {
- if (cost) {
- s += 0.5*D[i]*jar[i]*jar[i];
- }
-
- d->efc_state[i] = mjCNSTRSTATE_QUADRATIC;
- }
-
- // friction
- for (int i=ne; i < ne+nf; i++) {
- // linear negative
- if (jar[i] <= -R[i]*floss[i]) {
- if (cost) {
- s += -0.5*R[i]*floss[i]*floss[i] - floss[i]*jar[i];
- }
-
- force[i] = floss[i];
-
- d->efc_state[i] = mjCNSTRSTATE_LINEARNEG;
- }
-
- // linear positive
- else if (jar[i] >= R[i]*floss[i]) {
- if (cost) {
- s += -0.5*R[i]*floss[i]*floss[i] + floss[i]*jar[i];
- }
-
- force[i] = -floss[i];
-
- d->efc_state[i] = mjCNSTRSTATE_LINEARPOS;
- }
-
- // quadratic
- else {
+ // update constraints
+ for (int i=0; i < nefc; i++) {
+ // ==== equality
+ if (i < ne) {
if (cost) {
s += 0.5*D[i]*jar[i]*jar[i];
}
-
d->efc_state[i] = mjCNSTRSTATE_QUADRATIC;
+ continue;
}
- }
- // contact
- for (int i=ne+nf; i < nefc; i++) {
+ // ==== friction
+ if (i < ne + nf) {
+ // linear negative
+ if (jar[i] <= -R[i]*floss[i]) {
+ if (cost) {
+ s += -0.5*R[i]*floss[i]*floss[i] - floss[i]*jar[i];
+ }
+
+ force[i] = floss[i];
+
+ d->efc_state[i] = mjCNSTRSTATE_LINEARNEG;
+ }
+
+ // linear positive
+ else if (jar[i] >= R[i]*floss[i]) {
+ if (cost) {
+ s += -0.5*R[i]*floss[i]*floss[i] + floss[i]*jar[i];
+ }
+
+ force[i] = -floss[i];
+
+ d->efc_state[i] = mjCNSTRSTATE_LINEARPOS;
+ }
+
+ // quadratic
+ else {
+ if (cost) {
+ s += 0.5*D[i]*jar[i]*jar[i];
+ }
+
+ d->efc_state[i] = mjCNSTRSTATE_QUADRATIC;
+ }
+ continue;
+ }
+
+ // ==== contact
+
// non-negative constraint
if (d->efc_type[i] != mjCNSTR_CONTACT_ELLIPTIC) {
// constraint is satisfied: no cost
From 02e819e76a505857cf79cca9ec4183489ceb8341 Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Thu, 31 Aug 2023 11:26:30 -0700
Subject: [PATCH 14/38] Tighten const qualifiers of `mjData` in Jacobian
multiplication functions.
PiperOrigin-RevId: 561711229
Change-Id: I66dc1d2fbcfbf8e935b244fe496ada314d53b135
---
doc/includes/references.h | 4 ++--
include/mujoco/mujoco.h | 4 ++--
introspect/functions.py | 4 ++--
src/engine/engine_core_constraint.c | 10 ++++++----
src/engine/engine_core_constraint.h | 13 ++++++-------
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/doc/includes/references.h b/doc/includes/references.h
index d6223101..e6f81619 100644
--- a/doc/includes/references.h
+++ b/doc/includes/references.h
@@ -2268,8 +2268,8 @@ int mj_addContact(const mjModel* m, mjData* d, const mjContact* con);
int mj_isPyramidal(const mjModel* m);
int mj_isSparse(const mjModel* m);
int mj_isDual(const mjModel* m);
-void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
-void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
+void mj_mulJacVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
+void mj_mulJacTVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
void mj_jac(const mjModel* m, const mjData* d, mjtNum* jacp, mjtNum* jacr,
const mjtNum point[3], int body);
void mj_jacBody(const mjModel* m, const mjData* d, mjtNum* jacp, mjtNum* jacr, int body);
diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h
index 19c37f67..c289b78a 100644
--- a/include/mujoco/mujoco.h
+++ b/include/mujoco/mujoco.h
@@ -386,10 +386,10 @@ MJAPI int mj_isSparse(const mjModel* m);
MJAPI int mj_isDual(const mjModel* m);
// Multiply dense or sparse constraint Jacobian by vector.
-MJAPI void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
+MJAPI void mj_mulJacVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
// Multiply dense or sparse constraint Jacobian transpose by vector.
-MJAPI void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
+MJAPI void mj_mulJacTVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
// Compute 3/6-by-nv end-effector Jacobian of global point attached to given body.
MJAPI void mj_jac(const mjModel* m, const mjData* d, mjtNum* jacp, mjtNum* jacr,
diff --git a/introspect/functions.py b/introspect/functions.py
index a5bbf038..a8db3f87 100644
--- a/introspect/functions.py
+++ b/introspect/functions.py
@@ -1987,7 +1987,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
FunctionParameterDecl(
name='d',
type=PointerType(
- inner_type=ValueType(name='mjData'),
+ inner_type=ValueType(name='mjData', is_const=True),
),
),
FunctionParameterDecl(
@@ -2019,7 +2019,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
FunctionParameterDecl(
name='d',
type=PointerType(
- inner_type=ValueType(name='mjData'),
+ inner_type=ValueType(name='mjData', is_const=True),
),
),
FunctionParameterDecl(
diff --git a/src/engine/engine_core_constraint.c b/src/engine/engine_core_constraint.c
index 52ebec3b..42b4ebf8 100644
--- a/src/engine/engine_core_constraint.c
+++ b/src/engine/engine_core_constraint.c
@@ -375,7 +375,7 @@ int mj_mergeChainSimple(const mjModel* m, int* chain, int b1, int b2) {
// multiply Jacobian by vector
-void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec) {
+void mj_mulJacVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec) {
// exit if no constraints
if (!d->nefc) {
return;
@@ -396,7 +396,8 @@ void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec) {
// multiply Jacobian by vector, for one island
-void mj_mulJacVec_island(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec, int island) {
+void mj_mulJacVec_island(const mjModel* m, const mjData* d,
+ mjtNum* res, const mjtNum* vec, int island) {
// no island, call regular function
if (island < 0) {
mj_mulJacVec(m, d, res, vec);
@@ -435,7 +436,7 @@ void mj_mulJacVec_island(const mjModel* m, mjData* d, mjtNum* res, const mjtNum*
// multiply JacobianT by vector
-void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec) {
+void mj_mulJacTVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec) {
// exit if no constraints
if (!d->nefc) {
return;
@@ -456,7 +457,8 @@ void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec)
// multiply Jacobian transpose by vector, for one island
-void mj_mulJacTVec_island(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec, int island) {
+void mj_mulJacTVec_island(const mjModel* m, const mjData* d,
+ mjtNum* res, const mjtNum* vec, int island) {
// no island, call regular function
if (island < 0) {
mj_mulJacTVec(m, d, res, vec);
diff --git a/src/engine/engine_core_constraint.h b/src/engine/engine_core_constraint.h
index 3104a339..2dcc42fb 100644
--- a/src/engine/engine_core_constraint.h
+++ b/src/engine/engine_core_constraint.h
@@ -36,19 +36,18 @@ MJAPI int mj_isSparse(const mjModel* m);
MJAPI int mj_isDual(const mjModel* m);
// multiply Jacobian by vector
-MJAPI void mj_mulJacVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
+MJAPI void mj_mulJacVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
// multiply Jacobian by vector, for one island
-MJAPI void mj_mulJacVec_island(const mjModel* m, mjData* d,
+MJAPI void mj_mulJacVec_island(const mjModel* m, const mjData* d,
mjtNum* res, const mjtNum* vec, int island);
-// multiply Jacobian transposed by vector, for one island
-MJAPI void mj_mulJacTVec_island(const mjModel* m, mjData* d,
- mjtNum* res, const mjtNum* vec, int island);
-
// multiply JacobianT by vector
-MJAPI void mj_mulJacTVec(const mjModel* m, mjData* d, mjtNum* res, const mjtNum* vec);
+MJAPI void mj_mulJacTVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec);
+// multiply JacobianT by vector, for one island
+MJAPI void mj_mulJacTVec_island(const mjModel* m, const mjData* d,
+ mjtNum* res, const mjtNum* vec, int island);
//-------------------------- utility functions -----------------------------------------------------
From 5279ce6f6f3168a2444a68ff5d8f3d5c3bdb3782 Mon Sep 17 00:00:00 2001
From: Nimrod Gileadi
Date: Fri, 1 Sep 2023 04:55:27 -0700
Subject: [PATCH 15/38] Fix comment typo in simulate.cc
PiperOrigin-RevId: 561921946
Change-Id: I423378df566be7a6ad88fb3526769d298ac32a3b
---
simulate/simulate.cc | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/simulate/simulate.cc b/simulate/simulate.cc
index 77edbf1a..404004dd 100644
--- a/simulate/simulate.cc
+++ b/simulate/simulate.cc
@@ -195,7 +195,7 @@ void InitializeProfiler(mj::Simulate* sim) {
mju::strcpy_arr(sim->figsize.xlabel, "Video frame");
mju::strcpy_arr(sim->figtimer.xlabel, "Video frame");
- // y-tick nubmer formats
+ // y-tick number formats
mju::strcpy_arr(sim->figconstraint.yformat, "%.0f");
mju::strcpy_arr(sim->figcost.yformat, "%.1f");
mju::strcpy_arr(sim->figsize.yformat, "%.0f");
@@ -261,11 +261,12 @@ void InitializeProfiler(mj::Simulate* sim) {
sim->figtimer.range[1][1] = 0.4f;
// init x axis on history figures (do not show yet)
- for (int n=0; n<6; n++)
+ for (int n=0; n<6; n++) {
for (int i=0; ifigtimer.linedata[n][2*i] = -i;
sim->figsize.linedata[n][2*i] = -i;
}
+ }
}
// update profiler figures
@@ -2237,7 +2238,6 @@ void Simulate::Render() {
topleftlabel.c_str(), nullptr, &this->platform_ui->mjr_context());
}
-
// show ui 0
if (this->ui0_enable) {
mjui_render(&this->ui0, &this->uistate, &this->platform_ui->mjr_context());
From 70c5fa50f21ada3f9a1bdc57df084cde966359ba Mon Sep 17 00:00:00 2001
From: Yuval Tassa
Date: Fri, 1 Sep 2023 05:51:26 -0700
Subject: [PATCH 16/38] Improve island-constraint testing model.
PiperOrigin-RevId: 561931744
Change-Id: I2826d4c5b987cc6edd761026f6734fe40fb31278
---
test/engine/engine_core_constraint_test.cc | 4 +--
test/engine/engine_core_smooth_test.cc | 4 +--
test/engine/engine_island_test.cc | 28 +++++++++++----------
test/engine/engine_support_test.cc | 4 +--
test/engine/testdata/island/island_efc.xml | 29 ++++++++++++++++++----
5 files changed, 45 insertions(+), 24 deletions(-)
diff --git a/test/engine/engine_core_constraint_test.cc b/test/engine/engine_core_constraint_test.cc
index a0c2a477..fee8ba7d 100644
--- a/test/engine/engine_core_constraint_test.cc
+++ b/test/engine/engine_core_constraint_test.cc
@@ -215,9 +215,9 @@ TEST_F(CoreConstraintTest, MulJacVecIsland) {
for (mjtJacobian sparsity : {mjJAC_DENSE, mjJAC_SPARSE}) {
model->opt.jacobian = sparsity;
- // simulate for 0.3 seconds
+ // simulate for 0.2 seconds
mj_resetData(model, data);
- while (data->time < 0.3) {
+ while (data->time < 0.2) {
mj_step(model, data);
}
mj_forward(model, data);
diff --git a/test/engine/engine_core_smooth_test.cc b/test/engine/engine_core_smooth_test.cc
index 6d123d1a..52f95e50 100644
--- a/test/engine/engine_core_smooth_test.cc
+++ b/test/engine/engine_core_smooth_test.cc
@@ -311,9 +311,9 @@ TEST_F(CoreSmoothTest, SolveMIsland) {
}
mju_copy(res, vec, nv);
- // simulate for 0.3 seconds
+ // simulate for 0.2 seconds
mj_resetData(model, data);
- while (data->time < 0.3) {
+ while (data->time < 0.2) {
mj_step(model, data);
}
mj_forward(model, data);
diff --git a/test/engine/engine_island_test.cc b/test/engine/engine_island_test.cc
index 1c631254..2e74bfe3 100644
--- a/test/engine/engine_island_test.cc
+++ b/test/engine/engine_island_test.cc
@@ -346,7 +346,7 @@ TEST_F(IslandTest, IslandEfc) {
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
mjData* data = mj_makeData(model);
- while (data->time < 0.3) {
+ while (data->time < 0.2) {
mj_step(model, data);
}
@@ -356,25 +356,27 @@ TEST_F(IslandTest, IslandEfc) {
int nisland = data->nisland;
// expect island structure to correspond to comment at top of xml
- EXPECT_EQ(nisland, 2);
+ EXPECT_EQ(nisland, 3);
EXPECT_EQ(data->ne, 1);
- EXPECT_EQ(data->nf, 1);
+ EXPECT_EQ(data->nf, 2);
EXPECT_EQ(data->nl, 1);
- EXPECT_EQ(nefc, 7);
+ EXPECT_EQ(nefc, 24);
EXPECT_THAT(AsVector(data->dof_island, nv),
- ElementsAre(0, -1, 0, 0, 0, 1, 0));
- EXPECT_THAT(AsVector(data->island_dofnum, nisland), ElementsAre(5, 1));
- EXPECT_THAT(AsVector(data->island_dofadr, nisland), ElementsAre(0, 5));
+ ElementsAre(0, -1, 0, 0, 0, 1, 2, 0, 1, 1, 1, 1, 1, 1));
+ EXPECT_THAT(AsVector(data->island_dofnum, nisland), ElementsAre(5, 7, 1));
+ EXPECT_THAT(AsVector(data->island_dofadr, nisland), ElementsAre(0, 5, 12));
EXPECT_THAT(AsVector(data->island_dofind, nv),
- ElementsAre(0, 2, 3, 4, 6, 5, -1));
+ ElementsAre(0, 2, 3, 4, 7, 5, 8, 9, 10, 11, 12, 13, 6, -1));
EXPECT_THAT(AsVector(data->dof_islandind, nv),
- ElementsAre(0, -1, 1, 2, 3, 0, 4));
+ ElementsAre(0, -1, 1, 2, 3, 0, 0, 4, 1, 2, 3, 4, 5, 6));
EXPECT_THAT(AsVector(data->efc_island, nefc),
- ElementsAre(0, 1, 0, 0, 0, 0, 0));
- EXPECT_THAT(AsVector(data->island_efcnum, nisland), ElementsAre(6, 1));
- EXPECT_THAT(AsVector(data->island_efcadr, nisland), ElementsAre(0, 6));
+ ElementsAre(0, 1, 2, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1));
+ EXPECT_THAT(AsVector(data->island_efcnum, nisland), ElementsAre(6, 17, 1));
+ EXPECT_THAT(AsVector(data->island_efcadr, nisland), ElementsAre(0, 6, 23));
EXPECT_THAT(AsVector(data->island_efcind, nefc),
- ElementsAre(0, 2, 3, 4, 5, 6, 1));
+ ElementsAre(0, 3, 4, 5, 6, 7, 1, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 2));
mj_deleteData(data);
mj_deleteModel(model);
diff --git a/test/engine/engine_support_test.cc b/test/engine/engine_support_test.cc
index a33b3f28..1d7b8511 100644
--- a/test/engine/engine_support_test.cc
+++ b/test/engine/engine_support_test.cc
@@ -469,9 +469,9 @@ TEST_F(SupportTest, MulMIsland) {
vec[i] = 0.2 + 0.3*i;
}
- // simulate for 0.3 seconds
+ // simulate for 0.2 seconds
mj_resetData(model, data);
- while (data->time < 0.3) {
+ while (data->time < 0.2) {
mj_step(model, data);
}
mj_forward(model, data);
diff --git a/test/engine/testdata/island/island_efc.xml b/test/engine/testdata/island/island_efc.xml
index 0be5e69e..3f30d9ec 100644
--- a/test/engine/testdata/island/island_efc.xml
+++ b/test/engine/testdata/island/island_efc.xml
@@ -1,10 +1,10 @@
@@ -16,15 +16,22 @@
+
+
+
+
+
+
+
@@ -37,14 +44,26 @@
+
+
+
+
+
+