Add a new plugin / extension mechanism called a resource provider along with retrofitting VFS on top of it.
A resource provider provides a mechanism for MuJoCo to read from filesystems other than the OS filesystem or the Virtual File System (VFS). PiperOrigin-RevId: 525394983 Change-Id: I077ff5a7e2e76806b48b6defb531280aadc8b169
This commit is contained in:
committed by
Copybara-Service
parent
b25728cc2e
commit
fe3dccfd1d
+81
-147
@@ -30,13 +30,12 @@
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include "cc/array_safety.h"
|
||||
#include "engine/engine_crossplatform.h"
|
||||
#include "engine/engine_file.h"
|
||||
#include "engine/engine_resource.h"
|
||||
#include "engine/engine_macro.h"
|
||||
#include "engine/engine_util_blas.h"
|
||||
#include "engine/engine_util_errmem.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_objects.h"
|
||||
#include "user/user_util.h"
|
||||
@@ -180,7 +179,7 @@ template <typename T> static T* VecToArray(std::vector<T>& vector, bool clear =
|
||||
|
||||
|
||||
// compiler
|
||||
void mjCMesh::Compile(const mjVFS* vfs) {
|
||||
void mjCMesh::Compile(int default_provider) {
|
||||
// load file
|
||||
if (!file.empty()) {
|
||||
// remove path from file if necessary
|
||||
@@ -191,11 +190,11 @@ void mjCMesh::Compile(const mjVFS* vfs) {
|
||||
// load STL, OBJ or MSH
|
||||
string ext = mjuu_getext(file);
|
||||
if (!strcasecmp(ext.c_str(), ".stl")) {
|
||||
LoadSTL(vfs);
|
||||
LoadSTL(default_provider);
|
||||
} else if (!strcasecmp(ext.c_str(), ".obj")) {
|
||||
LoadOBJ(vfs);
|
||||
LoadOBJ(default_provider);
|
||||
} else if (!strcasecmp(ext.c_str(), ".msh")) {
|
||||
LoadMSH(vfs);
|
||||
LoadMSH(default_provider);
|
||||
} else {
|
||||
throw mjCError(this, "Unknown mesh file type: %s", file.c_str());
|
||||
}
|
||||
@@ -584,34 +583,36 @@ void mjCMesh::RemoveRepeated() {
|
||||
|
||||
|
||||
// load OBJ mesh
|
||||
void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
||||
void mjCMesh::LoadOBJ(int default_provider) {
|
||||
|
||||
// make filename
|
||||
string filename = mjuu_makefullname(
|
||||
model->modelfiledir, model->meshdir, file);
|
||||
mjResource* r = nullptr;
|
||||
|
||||
tinyobj::ObjReader objReader;
|
||||
char* buffer = nullptr;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id >= 0) {
|
||||
buffer = static_cast<char*>(vfs->filedata[id]);
|
||||
int buffer_sz = vfs->filesize[id];
|
||||
// TODO(etom): support .mtl files in the VFS case?
|
||||
objReader.ParseFromString(std::string(buffer, buffer_sz), std::string());
|
||||
// try reading from default provider
|
||||
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
// try reading from filesystem
|
||||
if (default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||
throw mjCError(this, "could not parse OBJ file '%s'", filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// if not found in vfs, read from file
|
||||
if (!buffer) {
|
||||
objReader.ParseFromFile(filename);
|
||||
tinyobj::ObjReader objReader;
|
||||
const void* bytes = nullptr;
|
||||
int buffer_sz = mju_readResource(r, &bytes);
|
||||
if (buffer_sz < 0) {
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "could not parse OBJ file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// TODO(etom): support .mtl files?
|
||||
const char* buffer = (const char*) bytes;
|
||||
objReader.ParseFromString(std::string(buffer, buffer_sz), std::string());
|
||||
mju_closeResource(r);
|
||||
|
||||
if (!objReader.Valid()) {
|
||||
std::stringstream msg;
|
||||
msg << "could not parse OBJ file '" << filename << "': \n"
|
||||
<< objReader.Error();
|
||||
throw mjCError(this, "%s", msg.str().c_str());
|
||||
throw mjCError(this, "could not parse OBJ file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
const auto& attrib = objReader.GetAttrib();
|
||||
@@ -685,56 +686,42 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
||||
|
||||
|
||||
// load STL binary mesh
|
||||
void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
||||
void mjCMesh::LoadSTL(int default_provider) {
|
||||
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
||||
|
||||
// make filename
|
||||
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
||||
mjResource* r = nullptr;
|
||||
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
if(!default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get file data in buffer
|
||||
char* buffer = 0;
|
||||
int buffer_sz = 0;
|
||||
bool own_buffer = false;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id>=0) {
|
||||
buffer = (char*)vfs->filedata[id];
|
||||
buffer_sz = vfs->filesize[id];
|
||||
}
|
||||
}
|
||||
|
||||
// if not found in vfs, read from file
|
||||
if (!buffer) {
|
||||
buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz);
|
||||
own_buffer = true;
|
||||
}
|
||||
int buffer_sz = mju_readResource(r, (const void**) &buffer);
|
||||
|
||||
// still not found
|
||||
if (!buffer) {
|
||||
if (buffer_sz < 0) {
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||
} else if (!buffer_sz) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "STL file '%s' is empty", filename.c_str());
|
||||
}
|
||||
|
||||
// make sure there is enough data for header
|
||||
if (buffer_sz<84) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "invalid header in STL file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// get number of triangles, check bounds
|
||||
nface = *(unsigned int*)(buffer+80);
|
||||
if (nface<1 || nface>200000) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this,
|
||||
"number of faces should be between 1 and 200000 in STL file '%s';"
|
||||
" perhaps this is an ASCII file?", filename.c_str());
|
||||
@@ -742,10 +729,7 @@ void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
||||
|
||||
// check remaining buffer size
|
||||
if (nface*50 != buffer_sz-84) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this,
|
||||
"STL file '%s' has wrong size; perhaps this is an ASCII file?",
|
||||
filename.c_str());
|
||||
@@ -765,19 +749,13 @@ void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
||||
float* v = (float*)(stl+50*i+12*(j+1));
|
||||
for (int k=0; k < 3; k++) {
|
||||
if (std::isnan(v[k]) || std::isinf(v[k])) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "STL file '%s' contains invalid vertices.",
|
||||
filename.c_str());
|
||||
}
|
||||
// check if vertex coordinates can be cast to an int safely
|
||||
if (fabs(v[k])>pow(2, 30)) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this,
|
||||
"vertex coordinates in STL file '%s' exceed maximum bounds",
|
||||
filename.c_str());
|
||||
@@ -797,56 +775,43 @@ void mjCMesh::LoadSTL(const mjVFS* vfs) {
|
||||
}
|
||||
}
|
||||
|
||||
// free buffer if allocated here
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(r);
|
||||
RemoveRepeated();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// load MSH binary mesh
|
||||
void mjCMesh::LoadMSH(const mjVFS* vfs) {
|
||||
void mjCMesh::LoadMSH(int default_provider) {
|
||||
bool righthand = (scale[0]*scale[1]*scale[2]>0);
|
||||
|
||||
// make filename
|
||||
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
||||
|
||||
mjResource* r = nullptr;
|
||||
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
// fall back to OS filesystem
|
||||
if(!default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// get file data in buffer
|
||||
char* buffer = 0;
|
||||
int buffer_sz = 0;
|
||||
bool own_buffer = false;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id>=0) {
|
||||
buffer = (char*)vfs->filedata[id];
|
||||
buffer_sz = vfs->filesize[id];
|
||||
}
|
||||
}
|
||||
|
||||
// if not found in vfs, read from file
|
||||
if (!buffer) {
|
||||
buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz);
|
||||
own_buffer = true;
|
||||
}
|
||||
int buffer_sz = mju_readResource(r, (const void**) &buffer);
|
||||
|
||||
// still not found
|
||||
if (!buffer) {
|
||||
throw mjCError(this, "could not open MSH file '%s'", filename.c_str());
|
||||
if (buffer_sz < 0) {
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "could not open STL file '%s'", filename.c_str());
|
||||
} else if (!buffer_sz) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
throw mjCError(this, "MSH file '%s' is empty", filename.c_str());
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "STL file '%s' is empty", filename.c_str());
|
||||
}
|
||||
|
||||
// make sure header is present
|
||||
if (buffer_sz<4*sizeof(int)) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "missing header in MSH file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
@@ -860,18 +825,14 @@ void mjCMesh::LoadMSH(const mjVFS* vfs) {
|
||||
if (nvert<4 || nface<0 || nnormal<0 || ntexcoord<0 ||
|
||||
(nnormal>0 && nnormal!=nvert) ||
|
||||
(ntexcoord>0 && ntexcoord!=nvert)) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "invalid sizes in MSH file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// check file size
|
||||
if (buffer_sz != 4*sizeof(int) + 3*nvert*sizeof(float) + 3*nnormal*sizeof(float) +
|
||||
2*ntexcoord*sizeof(float) + 3*nface*sizeof(int)) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "unexpected file size in MSH file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
@@ -912,10 +873,7 @@ void mjCMesh::LoadMSH(const mjVFS* vfs) {
|
||||
}
|
||||
}
|
||||
|
||||
// free buffer if allocated here
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
}
|
||||
|
||||
|
||||
@@ -1602,7 +1560,7 @@ mjCSkin::~mjCSkin() {
|
||||
|
||||
|
||||
// compiler
|
||||
void mjCSkin::Compile(const mjVFS* vfs) {
|
||||
void mjCSkin::Compile(int default_provider) {
|
||||
|
||||
// load file
|
||||
if (!file.empty()) {
|
||||
@@ -1627,7 +1585,7 @@ void mjCSkin::Compile(const mjVFS* vfs) {
|
||||
// load SKN
|
||||
string ext = mjuu_getext(file);
|
||||
if (!strcasecmp(ext.c_str(), ".skn")) {
|
||||
LoadSKN(vfs);
|
||||
LoadSKN(default_provider);
|
||||
} else {
|
||||
throw mjCError(this, "Unknown skin file type: %s", file.c_str());
|
||||
}
|
||||
@@ -1749,43 +1707,32 @@ void mjCSkin::Compile(const mjVFS* vfs) {
|
||||
|
||||
|
||||
// load skin in SKN BIN format
|
||||
void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
||||
void mjCSkin::LoadSKN(int default_provider) {
|
||||
// make filename
|
||||
string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file);
|
||||
|
||||
// get file data in buffer
|
||||
char* buffer = NULL;
|
||||
int buffer_sz = 0;
|
||||
bool own_buffer = false;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id>=0) {
|
||||
buffer = (char*)vfs->filedata[id];
|
||||
buffer_sz = vfs->filesize[id];
|
||||
}
|
||||
mjResource* r = nullptr;
|
||||
if((r = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
if(!default_provider || (r = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||
throw mjCError(this, "could not open SKN file '%s'", filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// if not found in vfs, read from file
|
||||
if (!buffer) {
|
||||
buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz);
|
||||
own_buffer = true;
|
||||
}
|
||||
char* buffer = 0;
|
||||
int buffer_sz = mju_readResource(r, (const void**) &buffer);
|
||||
|
||||
// still not found
|
||||
if (!buffer) {
|
||||
if (buffer_sz < 0) {
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "could not open SKN file '%s'", filename.c_str());
|
||||
} else if (!buffer_sz) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "SKN file '%s' is empty", filename.c_str());
|
||||
}
|
||||
|
||||
// make sure header is present
|
||||
if (buffer_sz<16) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "missing header in SKN file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
@@ -1797,17 +1744,13 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
||||
|
||||
// negative sizes not allowed
|
||||
if (nvert<0 || ntexcoord<0 || nface<0 || nbone<0) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "negative size in header of SKN file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// make sure we have data for vert, texcoord, face
|
||||
if (buffer_sz < 16 + 12*nvert + 8*ntexcoord + 12*nface) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "insufficient data in SKN file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
@@ -1847,9 +1790,7 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
||||
for (int i=0; i<nbone; i++) {
|
||||
// check size
|
||||
if (buffer_sz/4-4-cnt < 18) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "insufficient data in SKN file '%s', bone %d", filename.c_str(), i);
|
||||
}
|
||||
|
||||
@@ -1874,18 +1815,14 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
||||
|
||||
// check for negative
|
||||
if (vcount<1) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "vertex count must be positive in SKN file '%s', bone %d",
|
||||
filename.c_str(), i);
|
||||
}
|
||||
|
||||
// check size
|
||||
if (buffer_sz/4-4-cnt < 2*vcount) {
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
throw mjCError(this, "insufficient vertex data in SKN file '%s', bone %d",
|
||||
filename.c_str(), i);
|
||||
}
|
||||
@@ -1901,10 +1838,7 @@ void mjCSkin::LoadSKN(const mjVFS* vfs) {
|
||||
cnt += vcount;
|
||||
}
|
||||
|
||||
// free buffer if allocated here
|
||||
if (own_buffer) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(r);
|
||||
|
||||
// check final size
|
||||
if (buffer_sz != 16+4*cnt) {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "engine/engine_macro.h"
|
||||
#include "engine/engine_plugin.h"
|
||||
#include "engine/engine_setconst.h"
|
||||
#include "engine/engine_resource.h"
|
||||
#include "engine/engine_support.h"
|
||||
#include "engine/engine_util_blas.h"
|
||||
#include "engine/engine_util_errmem.h"
|
||||
@@ -2392,7 +2393,7 @@ static void warninghandler(const char* msg) {
|
||||
|
||||
|
||||
// compiler
|
||||
mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
||||
mjModel* mjCModel::Compile(int default_provider) {
|
||||
// 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
|
||||
@@ -2423,7 +2424,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
||||
// TryCompile resulted in an mju_error which was converted to a longjmp.
|
||||
throw mjCError(0, "engine error: %s", errortext);
|
||||
}
|
||||
TryCompile(*const_cast<mjModel**>(&m), *const_cast<mjData**>(&data), vfs);
|
||||
TryCompile(*const_cast<mjModel**>(&m), *const_cast<mjData**>(&data), default_provider);
|
||||
} catch (mjCError err) {
|
||||
// deallocate everything allocated in Compile
|
||||
mj_deleteModel(m);
|
||||
@@ -2449,7 +2450,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
|
||||
}
|
||||
|
||||
|
||||
void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
void mjCModel::TryCompile(mjModel*& m, mjData*& d, int default_provider) {
|
||||
// check if nan test works
|
||||
double test = mjNAN;
|
||||
if (mjuu_defined(test)) {
|
||||
@@ -2526,7 +2527,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
|
||||
// compile meshes (needed for geom compilation)
|
||||
for (int i=0; i<meshes.size(); i++) {
|
||||
meshes[i]->Compile(vfs);
|
||||
meshes[i]->Compile(default_provider);
|
||||
}
|
||||
|
||||
// automatically set nuser fields
|
||||
@@ -2585,9 +2586,9 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
}
|
||||
|
||||
// compile all other objects except for keyframes
|
||||
for (int i=0; i<skins.size(); i++) skins[i]->Compile(vfs);
|
||||
for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(vfs);
|
||||
for (int i=0; i<textures.size(); i++) textures[i]->Compile(vfs);
|
||||
for (int i=0; i<skins.size(); i++) skins[i]->Compile(default_provider);
|
||||
for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(default_provider);
|
||||
for (int i=0; i<textures.size(); i++) textures[i]->Compile(default_provider);
|
||||
for (int i=0; i<materials.size(); i++) materials[i]->Compile();
|
||||
for (int i=0; i<pairs.size(); i++) pairs[i]->Compile();
|
||||
for (int i=0; i<excludes.size(); i++) excludes[i]->Compile();
|
||||
|
||||
+24
-23
@@ -62,32 +62,32 @@ class mjCModel {
|
||||
friend class mjXWriter;
|
||||
|
||||
public:
|
||||
mjCModel(); // constructor
|
||||
~mjCModel(); // destructor
|
||||
mjCModel(); // constructor
|
||||
~mjCModel(); // destructor
|
||||
|
||||
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
|
||||
mjModel* Compile(int default_provider = 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
|
||||
|
||||
|
||||
//------------------------ API for adding model elements
|
||||
mjCMesh* AddMesh(mjCDef* def = 0); // mesh
|
||||
mjCSkin* AddSkin(void); // skin
|
||||
mjCHField* AddHField(void); // heightfield
|
||||
mjCTexture* AddTexture(void); // texture
|
||||
mjCMaterial*AddMaterial(mjCDef* def = 0); // material
|
||||
mjCPair* AddPair(mjCDef* def = 0); // geom pair for inclusion
|
||||
mjCBodyPair*AddExclude(void); // body pair for exclusion
|
||||
mjCEquality*AddEquality(mjCDef* def = 0); // equality constraint
|
||||
mjCTendon* AddTendon(mjCDef* def = 0); // tendon
|
||||
mjCActuator*AddActuator(mjCDef* def = 0); // actuator
|
||||
mjCSensor* AddSensor(void); // sensor
|
||||
mjCNumeric* AddNumeric(void); // custom numeric
|
||||
mjCText* AddText(void); // custom text
|
||||
mjCTuple* AddTuple(void); // custom tuple
|
||||
mjCKey* AddKey(void); // keyframe
|
||||
mjCPlugin* AddPlugin(void); // plugin instance
|
||||
mjCMesh* AddMesh(mjCDef* def = 0); // mesh
|
||||
mjCSkin* AddSkin(void); // skin
|
||||
mjCHField* AddHField(void); // heightfield
|
||||
mjCTexture* AddTexture(void); // texture
|
||||
mjCMaterial* AddMaterial(mjCDef* def = 0); // material
|
||||
mjCPair* AddPair(mjCDef* def = 0); // geom pair for inclusion
|
||||
mjCBodyPair* AddExclude(void); // body pair for exclusion
|
||||
mjCEquality* AddEquality(mjCDef* def = 0); // equality constraint
|
||||
mjCTendon* AddTendon(mjCDef* def = 0); // tendon
|
||||
mjCActuator* AddActuator(mjCDef* def = 0); // actuator
|
||||
mjCSensor* AddSensor(void); // sensor
|
||||
mjCNumeric* AddNumeric(void); // custom numeric
|
||||
mjCText* AddText(void); // custom text
|
||||
mjCTuple* AddTuple(void); // custom tuple
|
||||
mjCKey* AddKey(void); // keyframe
|
||||
mjCPlugin* AddPlugin(void); // plugin instance
|
||||
|
||||
//------------------------ API for access to model elements (outside tree)
|
||||
int NumObjects(mjtObj type); // number of objects in specified list
|
||||
@@ -163,7 +163,8 @@ class mjCModel {
|
||||
int nuser_sensor; // number of mjtNums in sensor_user
|
||||
|
||||
private:
|
||||
void TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs);
|
||||
void TryCompile(mjModel*& m, mjData*& d, int default_provider);
|
||||
mjModel* _Compile(int default_provider);
|
||||
|
||||
void Clear(void); // clear objects allocated by Compile
|
||||
|
||||
|
||||
+82
-108
@@ -29,7 +29,7 @@
|
||||
#include "cc/array_safety.h"
|
||||
#include "engine/engine_core_smooth.h"
|
||||
#include "engine/engine_crossplatform.h"
|
||||
#include "engine/engine_file.h"
|
||||
#include "engine/engine_resource.h"
|
||||
#include "engine/engine_io.h"
|
||||
#include "engine/engine_macro.h"
|
||||
#include "engine/engine_passive.h"
|
||||
@@ -38,7 +38,6 @@
|
||||
#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"
|
||||
|
||||
@@ -1979,33 +1978,27 @@ mjCHField::~mjCHField() {
|
||||
|
||||
|
||||
// load elevation data from custom format
|
||||
void mjCHField::LoadCustom(string filename, const mjVFS* vfs) {
|
||||
void mjCHField::LoadCustom(string filename, int default_provider) {
|
||||
// get file data in buffer
|
||||
void* buffer = 0;
|
||||
int buffer_sz = 0, flag_existing = 0;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id>=0) {
|
||||
buffer = vfs->filedata[id];
|
||||
buffer_sz = vfs->filesize[id];
|
||||
flag_existing = 1;
|
||||
const void* buffer = 0;
|
||||
mjResource* resource = nullptr;
|
||||
|
||||
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
// default to OS filesystem
|
||||
if (!default_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||
throw mjCError(this, "could not open hfield file '%s'", filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// if not found in vfs, read from file
|
||||
if (!buffer) {
|
||||
buffer = mju_fileToMemory(filename.c_str(), &buffer_sz);
|
||||
}
|
||||
int buffer_sz = mju_readResource(resource, &buffer);
|
||||
|
||||
// still not found
|
||||
if (!buffer || !buffer_sz) {
|
||||
if (!buffer || buffer_sz < 1) {
|
||||
throw mjCError(this, "could not open hfield file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
if (buffer_sz < 2*sizeof(int)) {
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "hfield missing header '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
@@ -2016,76 +2009,69 @@ void mjCHField::LoadCustom(string filename, const mjVFS* vfs) {
|
||||
|
||||
// check dimensions
|
||||
if (nrow<1 || ncol<1) {
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "non-positive hfield dimensions in file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// check buffer size
|
||||
if (buffer_sz != nrow*ncol*sizeof(float)+8) {
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "unexpected file size in file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// allocate
|
||||
data = (float*) mju_malloc(nrow*ncol*sizeof(float));
|
||||
if (!data) {
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "could not allocate buffers in hfield");
|
||||
}
|
||||
|
||||
// copy data
|
||||
memcpy(data, (void*)(pint+2), nrow*ncol*sizeof(float));
|
||||
|
||||
// free buffer if allocated here
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
// close file
|
||||
mju_closeResource(resource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// load elevation data from PNG format
|
||||
void mjCHField::LoadPNG(string filename, const mjVFS* vfs) {
|
||||
void mjCHField::LoadPNG(string filename, int default_provider) {
|
||||
// determine data source
|
||||
const unsigned char* inbuffer = 0;
|
||||
size_t inbuffer_sz = 0;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id>=0) {
|
||||
inbuffer = (const unsigned char*)vfs->filedata[id];
|
||||
inbuffer_sz = (size_t)vfs->filesize[id];
|
||||
}
|
||||
const void* inbuffer = 0;
|
||||
mjResource* resource = nullptr;
|
||||
|
||||
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
int inbuffer_sz = mju_readResource(resource, &inbuffer);
|
||||
|
||||
// still not found
|
||||
if (!inbuffer || inbuffer_sz < 1) {
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// load PNG from file or memory
|
||||
unsigned int w, h, err;
|
||||
std::vector<unsigned char> image;
|
||||
if (inbuffer_sz) {
|
||||
err = lodepng::decode(image, w, h, inbuffer, inbuffer_sz, LCT_GREY, 8);
|
||||
} else {
|
||||
err = lodepng::decode(image, w, h, filename, LCT_GREY, 8);
|
||||
}
|
||||
err = lodepng::decode(image, w, h, (const unsigned char*) inbuffer, inbuffer_sz, LCT_GREY, 8);
|
||||
|
||||
// check
|
||||
if (err) {
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "PNG load error '%s' in hfield id = %d", lodepng_error_text(err), id);
|
||||
}
|
||||
if (!w || !h) {
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "Zero dimension in PNG hfield '%s' (id = %d)", name.c_str(), id);
|
||||
}
|
||||
|
||||
// allocate
|
||||
data = (float*) mju_malloc(w*h*sizeof(float));
|
||||
if (!data) {
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "could not allocate buffers in hfield");
|
||||
}
|
||||
|
||||
@@ -2097,12 +2083,13 @@ void mjCHField::LoadPNG(string filename, const mjVFS* vfs) {
|
||||
data[c+(nrow-1-r)*ncol] = (float)image[c+r*ncol];
|
||||
}
|
||||
image.clear();
|
||||
mju_closeResource(resource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// compiler
|
||||
void mjCHField::Compile(const mjVFS* vfs) {
|
||||
void mjCHField::Compile(int default_provider) {
|
||||
// check size parameters
|
||||
for (int i=0; i<4; i++)
|
||||
if (size[i]<=0)
|
||||
@@ -2128,9 +2115,9 @@ void mjCHField::Compile(const mjVFS* vfs) {
|
||||
// load depending on format
|
||||
string ext = mjuu_getext(filename);
|
||||
if (!strcasecmp(ext.c_str(), ".png")) {
|
||||
LoadPNG(filename, vfs);
|
||||
LoadPNG(filename, default_provider);
|
||||
} else {
|
||||
LoadCustom(filename, vfs);
|
||||
LoadCustom(filename, default_provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2441,27 +2428,27 @@ void mjCTexture::BuiltinCube(void) {
|
||||
|
||||
|
||||
// load PNG file
|
||||
void mjCTexture::LoadPNG(string filename, const mjVFS* vfs,
|
||||
void mjCTexture::LoadPNG(string filename, int default_provider,
|
||||
std::vector<unsigned char>& image,
|
||||
unsigned int& w, unsigned int& h) {
|
||||
// determine data source
|
||||
const unsigned char* inbuffer = 0;
|
||||
size_t inbuffer_sz = 0;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id>=0) {
|
||||
inbuffer = (const unsigned char*)vfs->filedata[id];
|
||||
inbuffer_sz = (size_t)vfs->filesize[id];
|
||||
}
|
||||
const void* inbuffer = 0;
|
||||
mjResource* resource = nullptr;
|
||||
|
||||
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
int inbuffer_sz = mju_readResource(resource, &inbuffer);
|
||||
|
||||
// still not found
|
||||
if (!inbuffer || inbuffer_sz < 1) {
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "could not open PNG file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// load PNG from file or memory
|
||||
unsigned int err;
|
||||
if (inbuffer_sz) {
|
||||
err = lodepng::decode(image, w, h, inbuffer, inbuffer_sz, LCT_RGB, 8);
|
||||
} else {
|
||||
err = lodepng::decode(image, w, h, filename, LCT_RGB, 8);
|
||||
}
|
||||
unsigned int err = lodepng::decode(image, w, h, (const unsigned char*) inbuffer, inbuffer_sz, LCT_RGB, 8);
|
||||
mju_closeResource(resource);
|
||||
|
||||
// check
|
||||
if (err) {
|
||||
@@ -2476,28 +2463,24 @@ void mjCTexture::LoadPNG(string filename, const mjVFS* vfs,
|
||||
|
||||
|
||||
// load custom file
|
||||
void mjCTexture::LoadCustom(string filename, const mjVFS* vfs,
|
||||
void mjCTexture::LoadCustom(string filename, int default_provider,
|
||||
std::vector<unsigned char>& image,
|
||||
unsigned int& w, unsigned int& h) {
|
||||
// get file data in buffer
|
||||
void* buffer = 0;
|
||||
int buffer_sz = 0, flag_existing = 0;
|
||||
if (vfs) {
|
||||
int id = mj_findFileVFS(vfs, filename.c_str());
|
||||
if (id>=0) {
|
||||
buffer = vfs->filedata[id];
|
||||
buffer_sz = vfs->filesize[id];
|
||||
flag_existing = 1;
|
||||
const void* buffer = 0;
|
||||
mjResource* resource = nullptr;
|
||||
|
||||
if((resource = mju_openResource(filename.c_str(), default_provider)) == nullptr) {
|
||||
// default to OS filesystem
|
||||
if (!default_provider || (resource = mju_openResource(filename.c_str(), 0)) == nullptr) {
|
||||
throw mjCError(this, "could not open texture file '%s'", filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// if not found in vfs, read from file
|
||||
if (!buffer) {
|
||||
buffer = mju_fileToMemory(filename.c_str(), &buffer_sz);
|
||||
}
|
||||
int buffer_sz = mju_readResource(resource, &buffer);
|
||||
|
||||
// still not found
|
||||
if (!buffer || !buffer_sz) {
|
||||
if (!buffer || buffer_sz < 0) {
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "could not open texture file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
@@ -2508,20 +2491,14 @@ void mjCTexture::LoadCustom(string filename, const mjVFS* vfs,
|
||||
|
||||
// check dimensions
|
||||
if (w<1 || h<1) {
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "Non-PNG texture, assuming custom binary file format,\n"
|
||||
"non-positive texture dimensions in file '%s'", filename.c_str());
|
||||
}
|
||||
|
||||
// check buffer size
|
||||
if (buffer_sz != 2*sizeof(int) + w*h*3*sizeof(char)) {
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
|
||||
mju_closeResource(resource);
|
||||
throw mjCError(this, "Non-PNG texture, assuming custom binary file format,\n"
|
||||
"unexpected file size in file '%s'", filename.c_str());
|
||||
}
|
||||
@@ -2530,24 +2507,21 @@ void mjCTexture::LoadCustom(string filename, const mjVFS* vfs,
|
||||
image.resize(w*h*3);
|
||||
memcpy(image.data(), (void*)(pint+2), w*h*3*sizeof(char));
|
||||
|
||||
// free buffer if allocated here
|
||||
if (!flag_existing) {
|
||||
mju_free(buffer);
|
||||
}
|
||||
mju_closeResource(resource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// load from PNG or custom file, flip if specified
|
||||
void mjCTexture::LoadFlip(string filename, const mjVFS* vfs,
|
||||
void mjCTexture::LoadFlip(string filename, int default_provider,
|
||||
std::vector<unsigned char>& image,
|
||||
unsigned int& w, unsigned int& h) {
|
||||
// dispatch to PNG or Custom loaded
|
||||
string ext = mjuu_getext(filename);
|
||||
if (!strcasecmp(ext.c_str(), ".png")) {
|
||||
LoadPNG(filename, vfs, image, w, h);
|
||||
LoadPNG(filename, default_provider, image, w, h);
|
||||
} else {
|
||||
LoadCustom(filename, vfs, image, w, h);
|
||||
LoadCustom(filename, default_provider, image, w, h);
|
||||
}
|
||||
|
||||
// horizontal flip
|
||||
@@ -2598,11 +2572,11 @@ void mjCTexture::LoadFlip(string filename, const mjVFS* vfs,
|
||||
|
||||
|
||||
// load 2D
|
||||
void mjCTexture::Load2D(string filename, const mjVFS* vfs) {
|
||||
void mjCTexture::Load2D(string filename, int default_provider) {
|
||||
// load PNG or custom
|
||||
unsigned int w, h;
|
||||
std::vector<unsigned char> image;
|
||||
LoadFlip(filename, vfs, image, w, h);
|
||||
LoadFlip(filename, default_provider, image, w, h);
|
||||
|
||||
// assign size
|
||||
width = w;
|
||||
@@ -2621,7 +2595,7 @@ void mjCTexture::Load2D(string filename, const mjVFS* vfs) {
|
||||
|
||||
|
||||
// load cube or skybox from single file (repeated or grid)
|
||||
void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
|
||||
void mjCTexture::LoadCubeSingle(string filename, int default_provider) {
|
||||
// check gridsize
|
||||
if (gridsize[0]<1 || gridsize[1]<1 || gridsize[0]*gridsize[1]>12) {
|
||||
throw mjCError(this,
|
||||
@@ -2632,7 +2606,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
|
||||
// load PNG or custom
|
||||
unsigned int w, h;
|
||||
std::vector<unsigned char> image;
|
||||
LoadFlip(filename, vfs, image, w, h);
|
||||
LoadFlip(filename, default_provider, image, w, h);
|
||||
|
||||
// check gridsize for compatibility
|
||||
if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) {
|
||||
@@ -2721,7 +2695,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) {
|
||||
|
||||
|
||||
// load cube or skybox from separate file
|
||||
void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
|
||||
void mjCTexture::LoadCubeSeparate(int default_provider) {
|
||||
// keep track of which faces were defined
|
||||
int loaded[6] = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
@@ -2739,7 +2713,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
|
||||
// load PNG or custom
|
||||
unsigned int w, h;
|
||||
std::vector<unsigned char> image;
|
||||
LoadFlip(filename, vfs, image, w, h);
|
||||
LoadFlip(filename, default_provider, image, w, h);
|
||||
|
||||
// PNG must be square
|
||||
if (w!=h) {
|
||||
@@ -2793,7 +2767,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
|
||||
|
||||
|
||||
// compiler
|
||||
void mjCTexture::Compile(const mjVFS* vfs) {
|
||||
void mjCTexture::Compile(int default_provider) {
|
||||
// builtin
|
||||
if (builtin!=mjBUILTIN_NONE) {
|
||||
// check size
|
||||
@@ -2836,9 +2810,9 @@ void mjCTexture::Compile(const mjVFS* vfs) {
|
||||
|
||||
// dispatch
|
||||
if (type==mjTEXTURE_2D) {
|
||||
Load2D(filename, vfs);
|
||||
Load2D(filename, default_provider);
|
||||
} else {
|
||||
LoadCubeSingle(filename, vfs);
|
||||
LoadCubeSingle(filename, default_provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2866,7 +2840,7 @@ void mjCTexture::Compile(const mjVFS* vfs) {
|
||||
}
|
||||
|
||||
// only cube and skybox
|
||||
LoadCubeSeparate(vfs);
|
||||
LoadCubeSeparate(default_provider);
|
||||
}
|
||||
|
||||
// make sure someone allocated data; SHOULD NOT OCCUR
|
||||
|
||||
+16
-16
@@ -512,10 +512,10 @@ class mjCMesh: public mjCBase {
|
||||
private:
|
||||
mjCMesh(mjCModel* = 0, mjCDef* = 0); // constructor
|
||||
~mjCMesh(); // destructor
|
||||
void Compile(const mjVFS* vfs); // compiler
|
||||
void LoadOBJ(const mjVFS* vfs); // load mesh in wavefront OBJ format
|
||||
void LoadSTL(const mjVFS* vfs); // load mesh in STL BIN format
|
||||
void LoadMSH(const mjVFS* vfs); // load mesh in MSH BIN format
|
||||
void Compile(int default_provider); // compiler
|
||||
void LoadOBJ(int default_provider); // load mesh in wavefront OBJ format
|
||||
void LoadSTL(int default_provider); // load mesh in STL BIN format
|
||||
void LoadMSH(int default_provider); // load mesh in MSH BIN format
|
||||
void MakeGraph(void); // make graph of convex hull
|
||||
void CopyGraph(void); // copy graph into face data
|
||||
void MakeNormal(void); // compute vertex normals
|
||||
@@ -590,8 +590,8 @@ class mjCSkin: public mjCBase {
|
||||
private:
|
||||
mjCSkin(mjCModel* = 0); // constructor
|
||||
~mjCSkin(); // destructor
|
||||
void Compile(const mjVFS* vfs); // compiler
|
||||
void LoadSKN(const mjVFS* vfs); // load skin in SKN BIN format
|
||||
void Compile(int default_provider); // compiler
|
||||
void LoadSKN(int default_provider); // load skin in SKN BIN format
|
||||
|
||||
int matid; // material id
|
||||
std::vector<int> bodyid; // body ids
|
||||
@@ -616,10 +616,10 @@ class mjCHField : public mjCBase {
|
||||
private:
|
||||
mjCHField(mjCModel* model); // constructor
|
||||
~mjCHField(); // destructor
|
||||
void Compile(const mjVFS* vfs); // compiler
|
||||
void Compile(int default_provider); // compiler
|
||||
|
||||
void LoadCustom(std::string filename, const mjVFS* vfs); // load from custom format
|
||||
void LoadPNG(std::string filename, const mjVFS* vfs); // load from PNG format
|
||||
void LoadCustom(std::string filename, int default_provider); // load from custom format
|
||||
void LoadPNG(std::string filename, int default_provider); // load from PNG format
|
||||
};
|
||||
|
||||
|
||||
@@ -660,22 +660,22 @@ class mjCTexture : public mjCBase {
|
||||
private:
|
||||
mjCTexture(mjCModel*); // constructor
|
||||
~mjCTexture(); // destructior
|
||||
void Compile(const mjVFS* vfs); // compiler
|
||||
void Compile(int default_provider); // compiler
|
||||
|
||||
void Builtin2D(void); // make builtin 2D
|
||||
void BuiltinCube(void); // make builtin cube
|
||||
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 Load2D(std::string filename, int default_provider); // load 2D from file
|
||||
void LoadCubeSingle(std::string filename, int default_provider); // load cube from single file
|
||||
void LoadCubeSeparate(int default_provider); // load cube from separate files
|
||||
|
||||
void LoadFlip(std::string filename, const mjVFS* vfs, // load and flip
|
||||
void LoadFlip(std::string filename, int default_provider, // load and flip
|
||||
std::vector<unsigned char>& image,
|
||||
unsigned int& w, unsigned int& h);
|
||||
|
||||
void LoadPNG(std::string filename, const mjVFS* vfs,
|
||||
void LoadPNG(std::string filename, int default_provider,
|
||||
std::vector<unsigned char>& image,
|
||||
unsigned int& w, unsigned int& h);
|
||||
void LoadCustom(std::string filename, const mjVFS* vfs,
|
||||
void LoadCustom(std::string filename, int default_provider,
|
||||
std::vector<unsigned char>& image,
|
||||
unsigned int& w, unsigned int& h);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user