Cache OBJ files in MuJoCo compiler.
PiperOrigin-RevId: 616211896 Change-Id: I3b765f4529957914e6154f0e62ef36fd9df93c96
This commit is contained in:
committed by
Copybara-Service
parent
bf66801bc9
commit
74d3d8cb4e
@@ -15,6 +15,8 @@
|
|||||||
set(MUJOCO_USER_SRCS
|
set(MUJOCO_USER_SRCS
|
||||||
user_api.cc
|
user_api.cc
|
||||||
user_api.h
|
user_api.h
|
||||||
|
user_cache.cc
|
||||||
|
user_cache.h
|
||||||
user_composite.cc
|
user_composite.cc
|
||||||
user_composite.h
|
user_composite.h
|
||||||
user_flexcomp.cc
|
user_flexcomp.cc
|
||||||
|
|||||||
+17
-2
@@ -13,8 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include "user/user_api.h"
|
#include "user/user_api.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <new>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -22,10 +25,9 @@
|
|||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
#include "user/user_objects.h"
|
#include "user/user_objects.h"
|
||||||
|
#include "user/user_cache.h"
|
||||||
#include "xml/xml_util.h"
|
#include "xml/xml_util.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// create model
|
// create model
|
||||||
mjSpec* mjm_createSpec() {
|
mjSpec* mjm_createSpec() {
|
||||||
mjCModel* modelC = new mjCModel;
|
mjCModel* modelC = new mjCModel;
|
||||||
@@ -600,4 +602,17 @@ const char* mjm_setFullInertia(mjmBody* bodyspec, double quat[4], double inertia
|
|||||||
return body->FullInertia(quat, inertia);
|
return body->FullInertia(quat, inertia);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------- GLOBAL ASSET CACHE -------------------------------
|
||||||
|
|
||||||
|
void mj_setCacheSize(mjCache cache, std::size_t size) {
|
||||||
|
mjCCache* ccache = reinterpret_cast<mjCCache*>(cache);
|
||||||
|
if (ccache) {
|
||||||
|
ccache->SetMaxSize(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
mjCache mj_globalCache() {
|
||||||
|
return NULL; // currently disabled
|
||||||
|
}
|
||||||
|
|||||||
@@ -983,6 +983,16 @@ MJAPI void mjm_defaultKey(mjmKey& key);
|
|||||||
// Default plugin attributes.
|
// Default plugin attributes.
|
||||||
MJAPI void mjm_defaultPlugin(mjmPlugin& plugin);
|
MJAPI void mjm_defaultPlugin(mjmPlugin& plugin);
|
||||||
|
|
||||||
|
//------------------------- Cache functions ------------------------------------
|
||||||
|
|
||||||
|
typedef struct _mjCache* mjCache;
|
||||||
|
|
||||||
|
// Set the size of the cache in bytes.
|
||||||
|
MJAPI void mj_setCacheSize(mjCache cache, size_t size);
|
||||||
|
|
||||||
|
// Get internal global cache context.
|
||||||
|
MJAPI mjCache mj_globalCache();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ template<typename T> std::size_t mjCAsset::Add(const std::string& name,
|
|||||||
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data);
|
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data);
|
||||||
mjCAssetData& block = it->second;
|
mjCAssetData& block = it->second;
|
||||||
|
|
||||||
block.bytes = std::make_shared<uint8_t[]>(nbytes);
|
block.bytes = std::shared_ptr<uint8_t>(new uint8_t[nbytes],
|
||||||
|
[](uint8_t *p) {delete [] p;});
|
||||||
std::copy(ptr, ptr + nbytes, block.bytes.get());
|
std::copy(ptr, ptr + nbytes, block.bytes.get());
|
||||||
block.nbytes = nbytes;
|
block.nbytes = nbytes;
|
||||||
nbytes_ += nbytes;
|
nbytes_ += nbytes;
|
||||||
|
|||||||
+18
-17
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
// data associated with an asset
|
// data associated with an asset
|
||||||
struct mjCAssetData {
|
struct mjCAssetData {
|
||||||
std::shared_ptr<uint8_t[]> bytes; // raw serialized bytes of cached data
|
std::shared_ptr<uint8_t> bytes; // raw serialized bytes of cached data
|
||||||
std::size_t nbytes; // number of bytes stored
|
std::size_t nbytes; // number of bytes stored
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,6 +80,11 @@ class mjCAsset {
|
|||||||
return blocks_.find(name) != blocks_.end();
|
return blocks_.find(name) != blocks_.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string& Timestamp() const { return timestamp_; }
|
||||||
|
const std::string& Id() const { return id_; }
|
||||||
|
std::size_t InsertNum() const { return insert_num_; }
|
||||||
|
std::size_t AccessCount() const { return access_count_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mjCAsset() = default;
|
mjCAsset() = default;
|
||||||
|
|
||||||
@@ -105,10 +110,6 @@ class mjCAsset {
|
|||||||
void SetTimestamp(std::string timestamp) { timestamp_ = timestamp; }
|
void SetTimestamp(std::string timestamp) { timestamp_ = timestamp; }
|
||||||
|
|
||||||
// accessors
|
// accessors
|
||||||
const std::string& Id() const { return id_; }
|
|
||||||
const std::string& Timestamp() const { return timestamp_; }
|
|
||||||
std::size_t InsertNum() const { return insert_num_; }
|
|
||||||
std::size_t AccessCount() const { return access_count_; }
|
|
||||||
std::size_t BytesCount() const { return nbytes_; }
|
std::size_t BytesCount() const { return nbytes_; }
|
||||||
const std::unordered_map<std::string, mjCAssetData>& Blocks() const {
|
const std::unordered_map<std::string, mjCAssetData>& Blocks() const {
|
||||||
return blocks_;
|
return blocks_;
|
||||||
@@ -128,14 +129,23 @@ class mjCAsset {
|
|||||||
std::set<std::string> references_;
|
std::set<std::string> references_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mjCAssetCompare {
|
||||||
|
bool operator()(const mjCAsset* e1, const mjCAsset* e2) const {
|
||||||
|
if (e1->AccessCount() != e2->AccessCount()) {
|
||||||
|
return e1->AccessCount() < e2->AccessCount();
|
||||||
|
}
|
||||||
|
return e1->InsertNum() < e2->InsertNum();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// the class container for a thread-safe asset cache
|
// the class container for a thread-safe asset cache
|
||||||
class mjCCache {
|
class mjCCache {
|
||||||
public:
|
public:
|
||||||
explicit mjCCache(std::size_t size) : max_size_(size) {}
|
explicit mjCCache(std::size_t size) : max_size_(size) {}
|
||||||
|
|
||||||
// move only
|
// move only
|
||||||
mjCCache(mjCCache&& other) = default;
|
mjCCache(mjCCache&& other) = delete;
|
||||||
mjCCache& operator=(mjCCache&& other) = default;
|
mjCCache& operator=(mjCCache&& other) = delete;
|
||||||
mjCCache(const mjCCache& other) = delete;
|
mjCCache(const mjCCache& other) = delete;
|
||||||
mjCCache& operator=(const mjCCache& other) = delete;
|
mjCCache& operator=(const mjCCache& other) = delete;
|
||||||
|
|
||||||
@@ -186,20 +196,11 @@ class mjCCache {
|
|||||||
std::size_t size_ = 0; // current size of the cache in bytes
|
std::size_t size_ = 0; // current size of the cache in bytes
|
||||||
std::size_t max_size_ = 0; // max size of the cache in bytes
|
std::size_t max_size_ = 0; // max size of the cache in bytes
|
||||||
|
|
||||||
// compare function for the priority queue
|
|
||||||
static constexpr auto compare_ = [](const mjCAsset* e1,
|
|
||||||
const mjCAsset* e2) {
|
|
||||||
if (e1->AccessCount() != e2->AccessCount()) {
|
|
||||||
return e1->AccessCount() < e2->AccessCount();
|
|
||||||
}
|
|
||||||
return e1->InsertNum() < e2->InsertNum();
|
|
||||||
};
|
|
||||||
|
|
||||||
// internal constant look up table for assets
|
// internal constant look up table for assets
|
||||||
std::unordered_map<std::string, mjCAsset> lookup_;
|
std::unordered_map<std::string, mjCAsset> lookup_;
|
||||||
|
|
||||||
// internal priority queue for the cache
|
// internal priority queue for the cache
|
||||||
std::set<mjCAsset*, decltype(compare_)> entries_;
|
std::set<mjCAsset*, mjCAssetCompare> entries_;
|
||||||
|
|
||||||
// models using the cache along with the assets they reference
|
// models using the cache along with the assets they reference
|
||||||
std::unordered_map<std::string, std::unordered_set<mjCAsset*>> models_;
|
std::unordered_map<std::string, std::unordered_set<mjCAsset*>> models_;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
#include "engine/engine_util_misc.h"
|
#include "engine/engine_util_misc.h"
|
||||||
#include "engine/engine_util_solve.h"
|
#include "engine/engine_util_solve.h"
|
||||||
#include "engine/engine_util_spatial.h"
|
#include "engine/engine_util_spatial.h"
|
||||||
|
#include "user/user_cache.h"
|
||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
#include "user/user_objects.h"
|
#include "user/user_objects.h"
|
||||||
#include "user/user_util.h"
|
#include "user/user_util.h"
|
||||||
@@ -967,6 +968,19 @@ void mjCMesh::RemoveRepeated() {
|
|||||||
void mjCMesh::LoadOBJ(mjResource* resource) {
|
void mjCMesh::LoadOBJ(mjResource* resource) {
|
||||||
tinyobj::ObjReader objReader;
|
tinyobj::ObjReader objReader;
|
||||||
const void* bytes = nullptr;
|
const void* bytes = nullptr;
|
||||||
|
|
||||||
|
// try loading from cache
|
||||||
|
mjCCache *cache = reinterpret_cast<mjCCache*>(mj_globalCache());
|
||||||
|
if (cache) {
|
||||||
|
auto asset = cache->Get(resource->name);
|
||||||
|
if (asset.has_value() &&
|
||||||
|
!mju_isModifiedResource(resource, asset->Timestamp().c_str())) {
|
||||||
|
if (LoadCachedOBJ(asset.value())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int buffer_sz = mju_readResource(resource, &bytes);
|
int buffer_sz = mju_readResource(resource, &bytes);
|
||||||
if (buffer_sz < 0) {
|
if (buffer_sz < 0) {
|
||||||
throw mjCError(this, "could not read OBJ file '%s'", resource->name);
|
throw mjCError(this, "could not read OBJ file '%s'", resource->name);
|
||||||
@@ -1030,9 +1044,117 @@ void mjCMesh::LoadOBJ(mjResource* resource) {
|
|||||||
for (int i=0; i<usertexcoord_.size()/2; i++) {
|
for (int i=0; i<usertexcoord_.size()/2; i++) {
|
||||||
usertexcoord_[2*i+1] = 1-usertexcoord_[2*i+1];
|
usertexcoord_[2*i+1] = 1-usertexcoord_[2*i+1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try caching asset
|
||||||
|
if (cache) {
|
||||||
|
mjCAsset asset("", resource->name, resource->timestamp);
|
||||||
|
|
||||||
|
asset.AddVector("uservert_", uservert_);
|
||||||
|
asset.AddVector("usernormal_", usernormal_);
|
||||||
|
asset.AddVector("usertexcoord_", usertexcoord_);
|
||||||
|
|
||||||
|
if (!objReader.GetShapes().empty()) {
|
||||||
|
const auto& mesh = objReader.GetShapes()[0].mesh;
|
||||||
|
std::vector<int> vertex_index;
|
||||||
|
vertex_index.reserve(mesh.indices.size());
|
||||||
|
|
||||||
|
std::vector<int> normal_index;
|
||||||
|
normal_index.reserve(mesh.indices.size());
|
||||||
|
|
||||||
|
std::vector<int> texcoord_index;
|
||||||
|
texcoord_index.reserve(mesh.indices.size());
|
||||||
|
|
||||||
|
for (tinyobj::index_t index : mesh.indices) {
|
||||||
|
vertex_index.push_back(index.vertex_index);
|
||||||
|
normal_index.push_back(index.normal_index);
|
||||||
|
texcoord_index.push_back(index.texcoord_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
asset.AddVector("num_face_vertices", mesh.num_face_vertices);
|
||||||
|
asset.AddVector("vertex_index", vertex_index);
|
||||||
|
asset.AddVector("normal_index", normal_index);
|
||||||
|
asset.AddVector("texcoord_index", texcoord_index);
|
||||||
|
} else {
|
||||||
|
asset.AddVector("num_face_vertices", std::vector<unsigned char>());
|
||||||
|
asset.AddVector("vertex_index", std::vector<int>());
|
||||||
|
asset.AddVector("normal_index", std::vector<int>());
|
||||||
|
asset.AddVector("texcoord_index", std::vector<int>());
|
||||||
|
}
|
||||||
|
cache->Insert(std::move(asset));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// load OBJ from cached asset, return true on success
|
||||||
|
bool mjCMesh::LoadCachedOBJ(const mjCAsset& asset) {
|
||||||
|
// check that asset has all data
|
||||||
|
if (!asset.HasData("uservert_") || !asset.HasData("usernormal_")
|
||||||
|
|| !asset.HasData("usertexcoord_") || !asset.HasData("num_face_vertices")
|
||||||
|
|| !asset.HasData("vertex_index") || !asset.HasData("normal_index")
|
||||||
|
|| !asset.HasData("texcoord_index")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uservert_ = asset.GetVector<float>("uservert_").value();
|
||||||
|
usernormal_ = asset.GetVector<float>("usernormal_").value();
|
||||||
|
usertexcoord_ = asset.GetVector<float>("usertexcoord_").value();
|
||||||
|
|
||||||
|
vector<int> vertex_index = asset.GetVector<int>("vertex_index").value();
|
||||||
|
vector<int> normal_index = asset.GetVector<int>("normal_index").value();
|
||||||
|
vector<int> texcoord_index = asset.GetVector<int>("texcoord_index").value();
|
||||||
|
vector<unsigned char> num_face_vertices =
|
||||||
|
asset.GetVector<unsigned char>("num_face_vertices").value();
|
||||||
|
|
||||||
|
bool righthand = (scale[0] * scale[1] * scale[2]) > 0;
|
||||||
|
|
||||||
|
|
||||||
|
for (int face = 0, i = 0; i < vertex_index.size();) {
|
||||||
|
int nfacevert = num_face_vertices[face];
|
||||||
|
if (nfacevert < 3 || nfacevert > 4) {
|
||||||
|
throw mjCError(
|
||||||
|
this, "only tri or quad meshes are supported for OBJ (file '%s')",
|
||||||
|
asset.Id().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
userface_.push_back(vertex_index[i]);
|
||||||
|
userface_.push_back(vertex_index[i + (righthand == 1 ? 1 : 2)]);
|
||||||
|
userface_.push_back(vertex_index[i + (righthand == 1 ? 2 : 1)]);
|
||||||
|
|
||||||
|
if (!usernormal_.empty()) {
|
||||||
|
userfacenormal_.push_back(normal_index[i]);
|
||||||
|
userfacenormal_.push_back(normal_index[i + (righthand == 1 ? 1 : 2)]);
|
||||||
|
userfacenormal_.push_back(normal_index[i + (righthand == 1 ? 2 : 1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usertexcoord_.empty()) {
|
||||||
|
userfacetexcoord_.push_back(texcoord_index[i]);
|
||||||
|
userfacetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 1 : 2)]);
|
||||||
|
userfacetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 2 : 1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nfacevert == 4) {
|
||||||
|
userface_.push_back(vertex_index[i]);
|
||||||
|
userface_.push_back(vertex_index[i + (righthand == 1 ? 2 : 3)]);
|
||||||
|
userface_.push_back(vertex_index[i + (righthand == 1 ? 3 : 2)]);
|
||||||
|
|
||||||
|
if (!usernormal_.empty()) {
|
||||||
|
userfacenormal_.push_back(normal_index[i]);
|
||||||
|
userfacenormal_.push_back(normal_index[i + (righthand == 1 ? 1 : 2)]);
|
||||||
|
userfacenormal_.push_back(normal_index[i + (righthand == 1 ? 2 : 1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usertexcoord_.empty()) {
|
||||||
|
userfacetexcoord_.push_back(texcoord_index[i]);
|
||||||
|
userfacetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 1 : 2)]);
|
||||||
|
userfacetexcoord_.push_back(texcoord_index[i + (righthand == 1 ? 2 : 1)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += nfacevert;
|
||||||
|
++face;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// load STL binary mesh
|
// load STL binary mesh
|
||||||
void mjCMesh::LoadSTL(mjResource* resource) {
|
void mjCMesh::LoadSTL(mjResource* resource) {
|
||||||
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <mujoco/mjmodel.h>
|
#include <mujoco/mjmodel.h>
|
||||||
#include <mujoco/mjplugin.h>
|
#include <mujoco/mjplugin.h>
|
||||||
#include "user/user_api.h"
|
#include "user/user_api.h"
|
||||||
|
#include "user/user_cache.h"
|
||||||
|
|
||||||
// forward declarations of all mjC/X classes
|
// forward declarations of all mjC/X classes
|
||||||
class mjCError;
|
class mjCError;
|
||||||
@@ -827,6 +828,7 @@ class mjCMesh: public mjCMesh_, private mjmMesh {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void LoadOBJ(mjResource* resource); // load mesh in wavefront OBJ format
|
void LoadOBJ(mjResource* resource); // load mesh in wavefront OBJ format
|
||||||
|
bool LoadCachedOBJ(const mjCAsset& asset); // load OBJ from cache asset, return true on success
|
||||||
void LoadSTL(mjResource* resource); // load mesh in STL BIN format
|
void LoadSTL(mjResource* resource); // load mesh in STL BIN format
|
||||||
void LoadMSH(mjResource* resource); // load mesh in MSH BIN format
|
void LoadMSH(mjResource* resource); // load mesh in MSH BIN format
|
||||||
void LoadSDF(); // generate mesh using marching cubes
|
void LoadSDF(); // generate mesh using marching cubes
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -116,5 +117,58 @@ TEST_F(PluginTest, RecompileCompare) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------- test cache with modified assets -------------------------
|
||||||
|
TEST_F(PluginTest, RecompileCompareCache) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<asset>
|
||||||
|
<mesh file="cube.obj"/>
|
||||||
|
</asset>
|
||||||
|
<worldbody>
|
||||||
|
<geom type="mesh" mesh="cube"/>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>)";
|
||||||
|
|
||||||
|
static constexpr char cube1[] = R"(
|
||||||
|
v -0.500000 -0.500000 0.500000
|
||||||
|
v 0.500000 -0.500000 0.500000
|
||||||
|
v -0.500000 0.500000 0.500000
|
||||||
|
v 0.500000 0.500000 0.500000
|
||||||
|
v -0.500000 0.500000 -0.500000
|
||||||
|
v 0.500000 0.500000 -0.500000
|
||||||
|
v -0.500000 -0.500000 -0.500000
|
||||||
|
v 0.500000 -0.500000 -0.500000)";
|
||||||
|
|
||||||
|
static constexpr char cube2[] = R"(
|
||||||
|
v -1 -1 1
|
||||||
|
v 1 -1 1
|
||||||
|
v -1 1 1
|
||||||
|
v 1 1 1
|
||||||
|
v -1 1 -1
|
||||||
|
v 1 1 -1
|
||||||
|
v -1 -1 -1
|
||||||
|
v 1 -1 -1)";
|
||||||
|
|
||||||
|
auto vfs = std::make_unique<mjVFS>();
|
||||||
|
mj_defaultVFS(vfs.get());
|
||||||
|
mj_addBufferVFS(vfs.get(), "cube.obj", cube1, sizeof(cube1));
|
||||||
|
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
|
||||||
|
// load model once
|
||||||
|
mjModel* m = LoadModelFromString(xml, error.data(), error.size(), vfs.get());
|
||||||
|
EXPECT_EQ(m->mesh_vert[0], -0.5);
|
||||||
|
mj_deleteModel(m);
|
||||||
|
|
||||||
|
// update cube.obj, load again
|
||||||
|
mj_deleteFileVFS(vfs.get(), "cube.obj");
|
||||||
|
mj_addBufferVFS(vfs.get(), "cube.obj", cube2, sizeof(cube2));
|
||||||
|
m = LoadModelFromString(xml, error.data(), error.size(), vfs.get());
|
||||||
|
EXPECT_EQ(m->mesh_vert[0], -1);
|
||||||
|
mj_deleteModel(m);
|
||||||
|
|
||||||
|
mj_deleteVFS(vfs.get());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace mujoco
|
} // namespace mujoco
|
||||||
|
|||||||
Reference in New Issue
Block a user