Convert internal representation of vertices to double. Also replace remove duplicate vertices with fast hash implementation.

PiperOrigin-RevId: 734516595
Change-Id: Ic78556688d5f2b75d69cd286b8d060e2cdeb7c48
This commit is contained in:
Kyle Bayes
2025-03-07 05:16:54 -08:00
committed by Copybara-Service
parent e4ee00e695
commit 4d024f9421
9 changed files with 435 additions and 363 deletions
+8 -7
View File
@@ -3397,10 +3397,11 @@ saving the XML:
compatible with :at:`dim=1`. compatible with :at:`dim=1`.
**mesh** loads the flexcomp points and elements (i.e. triangles) from a mesh file, in the same file formats as mesh **mesh** loads the flexcomp points and elements (i.e. triangles) from a mesh file, in the same file formats as mesh
assets. A mesh asset is not actually added to the model. Instead the vertex and face data from the mesh file are used assets, excluding the legacy .msh format. A mesh asset is not actually added to the model. Instead the vertex and
to populate the point and element data of the flexcomp. :at:`dim` is automatically set to 2. Recall that a mesh asset face data from the mesh file are used to populate the point and element data of the flexcomp. :at:`dim` is
in MuJoCo can be used as a rigid geom attached to a single body. In contrast, the flex generated here corresponds to automatically set to 2. Recall that a mesh asset in MuJoCo can be used as a rigid geom attached to a single body. In
a soft mesh with the same initial shape, where each vertex is a separate moving body (unless pinned). contrast, the flex generated here corresponds to a soft mesh with the same initial shape, where each vertex is a
separate moving body (unless pinned).
.. _gmsh-file-docs: .. _gmsh-file-docs:
@@ -3471,9 +3472,9 @@ saving the XML:
:at:`file`: :at-val:`string, optional` :at:`file`: :at-val:`string, optional`
The name of the file from which a **surface** (triangular) or **volumetric** (tetrahedral) mesh is loaded. For The name of the file from which a **surface** (triangular) or **volumetric** (tetrahedral) mesh is loaded. For
surface meshes, the file extension is used to determine the file format. Supported formats are the same as in surface meshes, the file extension is used to determine the file format. Supported formats are GMSH and the formats
:ref:`mesh assets<asset-mesh>` and also including GMSH. Volumetric meshes are supported only in GMSH format. specified in :ref:`mesh assets<asset-mesh>`, excluding the legacy .msh format. Volumetric meshes are supported only
See :ref:`here<gmsh-file-docs>` for more information on GMSH files. in GMSH format. See :ref:`here<gmsh-file-docs>` for more information on GMSH files.
.. _body-flexcomp-rigid: .. _body-flexcomp-rigid:
+9 -34
View File
@@ -989,14 +989,6 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, char* error, int error_sz) {
return comperr(error, "File is required", error_sz); return comperr(error, "File is required", error_sz);
} }
// get extension and check; must be STL, OBJ or MSH
std::string ext = mjuu_getext(file);
if (strcasecmp(ext.c_str(), ".stl") &&
strcasecmp(ext.c_str(), ".obj") &&
strcasecmp(ext.c_str(), ".msh")) {
return comperr(error, "Mesh file extension must be stl, obj or msh", error_sz);
}
// check dim // check dim
if (def.spec.flex->dim < 2) { if (def.spec.flex->dim < 2) {
return comperr(error, "Flex dim must be at least 2 for mesh", error_sz); return comperr(error, "Flex dim must be at least 2 for mesh", error_sz);
@@ -1006,6 +998,11 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, char* error, int error_sz) {
std::string filename = mjuu_combinePaths(mjs_getString(model->spec.meshdir), file); std::string filename = mjuu_combinePaths(mjs_getString(model->spec.meshdir), file);
mjResource* resource = nullptr; mjResource* resource = nullptr;
if (mjCMesh::IsMSH(filename)) {
return comperr(error, "legacy MSH files are not supported in flexcomp", error_sz);
}
try { try {
resource = mjCBase::LoadResource(mjs_getString(model->spec.modelfiledir), resource = mjCBase::LoadResource(mjs_getString(model->spec.modelfiledir),
filename, 0); filename, 0);
@@ -1013,46 +1010,24 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, char* error, int error_sz) {
return comperr(error, err.message, error_sz); return comperr(error, err.message, error_sz);
} }
// load mesh // load mesh
mjCMesh mesh; mjCMesh mesh;
bool isobj = false;
try { try {
if (!strcasecmp(ext.c_str(), ".stl")) { mesh.LoadFromResource(resource, true);
mesh.LoadSTL(resource);
} else if (!strcasecmp(ext.c_str(), ".obj")) {
isobj = true;
mesh.LoadOBJ(resource);
} else {
mesh.LoadMSH(resource);
}
mju_closeResource(resource); mju_closeResource(resource);
} catch (mjCError err) { } catch (mjCError err) {
mju_closeResource(resource); mju_closeResource(resource);
return comperr(error, err.message, error_sz); return comperr(error, err.message, error_sz);
} }
// LoadOBJ uses userXXX, extra processing needed
if (isobj) {
// check sizes // check sizes
if (mesh.Vert().empty() || mesh.Face().empty()) { if (mesh.Vert().empty() || mesh.Face().empty()) {
return comperr(error, "Vertex and face data required", error_sz); return comperr(error, "Vertex and face data required", error_sz);
} }
if (mesh.Vert().size()%3) {
return comperr(error, "Vertex data must be multiple of 3", error_sz);
}
if (mesh.Face().size()%3) {
return comperr(error, "Face data must be multiple of 3", error_sz);
}
// remove repeated vertices (not called in LoadOBJ) // copy vertices
mesh.RemoveRepeated(); point = mesh.Vert();
}
// copy vertices, convert from float to double
point = vector<double> (mesh.nvert()*3);
for (int i=0; i < mesh.nvert()*3; i++) {
point[i] = (double) mesh.Vert(i);
}
if (mesh.HasTexcoord()) { if (mesh.HasTexcoord()) {
texcoord = mesh.Texcoord(); texcoord = mesh.Texcoord();
+290 -253
View File
@@ -76,8 +76,8 @@ namespace {
} // namespace } // namespace
// compute triangle area, surface normal, center // compute triangle area, surface normal, center
static double _triangle(double* normal, double* center, static double triangle(double* normal, double* center,
const float* v1, const float* v2, const float* v3) { const double* v1, const double* v2, const double* v3) {
// center // center
if (center) { if (center) {
for (int i=0; i < 3; i++) { for (int i=0; i < 3; i++) {
@@ -104,7 +104,7 @@ static double _triangle(double* normal, double* center,
normal[2] /= len; normal[2] /= len;
// return area // return area
return len/2; return 0.5 * len;
} }
// Read data of type T from a potentially unaligned buffer pointer. // Read data of type T from a potentially unaligned buffer pointer.
@@ -238,9 +238,9 @@ void mjCMesh::CopyFromSpec() {
*static_cast<mjsMesh*>(this) = spec; *static_cast<mjsMesh*>(this) = spec;
file_ = spec_file_; file_ = spec_file_;
content_type_ = spec_content_type_; content_type_ = spec_content_type_;
vert_ = spec_vert_;
normal_ = spec_normal_; normal_ = spec_normal_;
face_ = spec_face_; face_ = spec_face_;
ProcessVertices(spec_vert_);
texcoord_ = spec_texcoord_; texcoord_ = spec_texcoord_;
facetexcoord_ = spec_facetexcoord_; facetexcoord_ = spec_facetexcoord_;
maxhullvert_ = spec.maxhullvert; maxhullvert_ = spec.maxhullvert;
@@ -339,32 +339,35 @@ void mjCMesh::LoadSDF() {
std::vector<float> usernormal; std::vector<float> usernormal;
std::vector<int> userface; std::vector<int> userface;
for (size_t i = 0; i < mesh.vertices.size(); i++) { uservert.reserve(mesh.vertices.size() * 3);
uservert.push_back(2*aabb[3]*mesh.vertices.at(i).x/(nx-1) + aabb[0]-aabb[3]); usernormal.reserve(mesh.normals.size() * 3);
uservert.push_back(2*aabb[4]*mesh.vertices.at(i).y/(ny-1) + aabb[1]-aabb[4]); userface.reserve(mesh.indices.size());
uservert.push_back(2*aabb[5]*mesh.vertices.at(i).z/(nz-1) + aabb[2]-aabb[5]);
for (const auto& vertex : mesh.vertices) {
uservert.push_back(2*aabb[3]*vertex.x/(nx-1) + aabb[0]-aabb[3]);
uservert.push_back(2*aabb[4]*vertex.y/(ny-1) + aabb[1]-aabb[4]);
uservert.push_back(2*aabb[5]*vertex.z/(nz-1) + aabb[2]-aabb[5]);
} }
for (size_t i = 0; i < mesh.normals.size(); i++) { for (const auto& normal : mesh.normals) {
usernormal.push_back(mesh.normals.at(i).x); usernormal.push_back(normal.x);
usernormal.push_back(mesh.normals.at(i).y); usernormal.push_back(normal.y);
usernormal.push_back(mesh.normals.at(i).z); usernormal.push_back(normal.z);
} }
for (size_t i = 0; i < mesh.indices.size(); i++) { for (unsigned int index : mesh.indices) {
userface.push_back(mesh.indices.at(i)); userface.push_back(index);
} }
vert_ = std::move(uservert);
normal_ = std::move(usernormal); normal_ = std::move(usernormal);
face_ = std::move(userface); face_ = std::move(userface);
ProcessVertices(uservert);
delete[] field; delete[] field;
} }
void mjCMesh::CacheMesh(mjCCache* cache, const mjResource* resource, void mjCMesh::CacheMesh(mjCCache* cache, const mjResource* resource) {
std::string_view asset_type) {
if (cache == nullptr) return; if (cache == nullptr) return;
if (asset_type != "model/obj") return; // only OBJ files are cached if (!IsObj()) return; // only OBJ files are cached
// cache mesh data into new mesh object // cache mesh data into new mesh object
mjCMesh *mesh = new mjCMesh(); mjCMesh *mesh = new mjCMesh();
@@ -381,7 +384,7 @@ void mjCMesh::CacheMesh(mjCCache* cache, const mjResource* resource,
// calculate estimated size of mesh // calculate estimated size of mesh
std::size_t size = sizeof(mjCMesh) std::size_t size = sizeof(mjCMesh)
+ (sizeof(float) * vert_.size()) + (sizeof(double) * vert_.size())
+ (sizeof(float) * normal_.size()) + (sizeof(float) * normal_.size())
+ (sizeof(float) * texcoord_.size()) + (sizeof(float) * texcoord_.size())
+ (sizeof(int) * face_.size()) + (sizeof(int) * face_.size())
@@ -399,12 +402,168 @@ void mjCMesh::CacheMesh(mjCCache* cache, const mjResource* resource,
cache->Insert("", resource, cached_data, size); cache->Insert("", resource, cached_data, size);
} }
// compiler namespace {
// vertex key for hash map
struct VertexKey {
float v[3];
bool operator==(const VertexKey &other) const {
return (v[0] == other.v[0] && v[1] == other.v[1] && v[2] == other.v[2]);
}
std::size_t operator()(const VertexKey& vertex) const {
// combine all three hash values into a single hash value
return ((std::hash<float>()(vertex.v[0])
^ (std::hash<float>()(vertex.v[1]) << 1)) >> 1)
^ (std::hash<float>()(vertex.v[2]) << 1);
}
};
} // namespace
// convert vertices to double precision and remove repeated vertices if requested
void mjCMesh::ProcessVertices(const std::vector<float>& vert, bool remove_repeated) {
vert_.clear();
int nvert = vert.size();
if (nvert % 3) {
throw mjCError(this, "vertex data must be a multiple of 3");
}
if (face_.size() % 3) {
throw mjCError(this, "face data must be a multiple of 3");
}
// convert vertices to double precision, may contain repeated vertices
if (!remove_repeated) {
vert_.reserve(nvert);
for (int i = 0; i < nvert / 3; ++i) {
const float* v = &vert[3 * i];
if (!std::isfinite(v[0]) || !std::isfinite(v[1]) || !std::isfinite(v[2])) {
throw mjCError(this, "vertex coordinate %d is not finite", nullptr, i);
}
vert_.push_back(v[0]);
vert_.push_back(v[1]);
vert_.push_back(v[2]);
}
return;
}
int index = 0;
std::unordered_map<VertexKey, int, VertexKey> vertex_map;
// populate vertex map with new vertex indices
for (int i = 0; i < nvert; i += 3) {
const float* v = &vert[i];
if (!std::isfinite(v[0]) || !std::isfinite(v[1]) || !std::isfinite(v[2])) {
throw mjCError(this, "vertex coordinate %d is not finite", nullptr, i);
}
VertexKey key = {v[0], v[1], v[2]};
if (vertex_map.find(key) == vertex_map.end()) {
vertex_map.insert({key, index});
++index;
}
}
// no repeated vertices (just copy vertex data)
if (3*index == nvert) {
vert_.reserve(nvert);
for (float v : vert) {
vert_.push_back(v);
}
return;
}
// update face vertex indices
for (int i = 0; i < face_.size(); ++i) {
VertexKey key = {vert[3*face_[i]], vert[3*face_[i] + 1],
vert[3*face_[i] + 2]};
face_[i] = vertex_map[key];
}
// repopulate vertex data
vert_.resize(3 * index);
for (const auto& pair : vertex_map) {
const VertexKey& key = pair.first;
int index = pair.second;
// double precision
vert_[3*index + 0] = key.v[0];
vert_[3*index + 1] = key.v[1];
vert_[3*index + 2] = key.v[2];
}
}
bool mjCMesh::IsObj(std::string_view filename, std::string_view ct) {
std::string asset_type = GetAssetContentType(filename, ct);
return asset_type == "model/obj";
}
bool mjCMesh::IsSTL(std::string_view filename, std::string_view ct) {
std::string asset_type = GetAssetContentType(filename, ct);
return asset_type == "model/stl";
}
bool mjCMesh::IsMSH(std::string_view filename, std::string_view ct) {
std::string asset_type = GetAssetContentType(filename, ct);
return asset_type == "model/vnd.mujoco.msh";
}
bool mjCMesh::IsObj() const{
return content_type_ == "model/obj";
}
bool mjCMesh::IsSTL() const {
return content_type_ == "model/stl";
}
bool mjCMesh::IsMSH() const {
return content_type_ == "model/vnd.mujoco.msh";
}
// load mesh from resource; throw error on failure
void mjCMesh::LoadFromResource(mjResource* resource, bool remove_repeated) {
// set content type from resource name
std::string asset_type = GetAssetContentType(resource->name, content_type_);
if (asset_type.empty()) {
if (!content_type_.empty()) {
throw mjCError(this, "invalid content type: '%s'", content_type_.c_str());
}
throw mjCError(this, "unknown or unsupported mesh file: '%s'", resource->name);
}
content_type_ = asset_type;
if (IsSTL()) {
LoadSTL(resource);
} else if (IsObj()) {
LoadOBJ(resource, remove_repeated);
} else if (IsMSH()) {
LoadMSH(resource, remove_repeated);
} else {
throw mjCError(this, "unsupported mesh type: '%s'", asset_type.c_str());
}
}
// compiler wrapper
void mjCMesh::Compile(const mjVFS* vfs) { void mjCMesh::Compile(const mjVFS* vfs) {
try {
TryCompile(vfs);
} catch (mjCError err) {
if (resource_ != nullptr) {
mju_closeResource(resource_);
resource_ = nullptr;
}
throw err;
}
}
// compiler
void mjCMesh::TryCompile(const mjVFS* vfs) {
bool fromCache = false;
CopyFromSpec(); CopyFromSpec();
visual_ = true; visual_ = true;
std::string asset_type = GetAssetContentType(file_, content_type_);
mjResource* resource = nullptr;
mjCCache *cache = reinterpret_cast<mjCCache*>(mj_globalCache()); mjCCache *cache = reinterpret_cast<mjCCache*>(mj_globalCache());
// load file // load file
@@ -415,6 +574,10 @@ void mjCMesh::Compile(const mjVFS* vfs) {
texcoord_.clear(); texcoord_.clear();
facenormal_.clear(); facenormal_.clear();
facetexcoord_.clear(); facetexcoord_.clear();
if (resource_ != nullptr) {
mju_closeResource(resource_);
resource_ = nullptr;
}
// copy paths from model if not already defined // copy paths from model if not already defined
if (modelfiledir_.empty()) { if (modelfiledir_.empty()) {
@@ -429,48 +592,22 @@ void mjCMesh::Compile(const mjVFS* vfs) {
file_ = mjuu_strippath(file_); file_ = mjuu_strippath(file_);
} }
if (asset_type.empty()) {
throw mjCError(this, "unknown mesh content type for file: '%s'", file_.c_str());
}
if (asset_type != "model/stl" && asset_type != "model/obj"
&& asset_type != "model/vnd.mujoco.msh") {
throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str());
}
FilePath filename = meshdir_ + FilePath(file_); FilePath filename = meshdir_ + FilePath(file_);
resource = LoadResource(modelfiledir_.Str(), filename.Str(), vfs); resource_ = LoadResource(modelfiledir_.Str(), filename.Str(), vfs);
// try loading from cache // try loading from cache
if (cache != nullptr && LoadCachedMesh(cache, resource)) { if (cache != nullptr && LoadCachedMesh(cache, resource_)) {
mju_closeResource(resource); mju_closeResource(resource_);
resource = nullptr; resource_ = nullptr;
fromCache = true;
} }
if (resource != nullptr) { if (!fromCache) {
try { LoadFromResource(resource_);
if (asset_type == "model/stl") { CacheMesh(cache, resource_);
LoadSTL(resource);
} else if (asset_type == "model/obj"){
LoadOBJ(resource);
} else {
LoadMSH(resource);
}
} catch (mjCError err) {
mju_closeResource(resource);
throw err;
}
CacheMesh(cache, resource, asset_type);
mju_closeResource(resource);
} }
// check repeated mesh data // check repeated mesh data
if (!vert_.empty() && !spec_vert_.empty()) {
throw mjCError(this, "repeated vertex specification");
} else if (vert_.empty()) {
vert_ = spec_vert_;
}
if (!normal_.empty() && !spec_normal_.empty()) { if (!normal_.empty() && !spec_normal_.empty()) {
throw mjCError(this, "repeated normal specification"); throw mjCError(this, "repeated normal specification");
} else if (normal_.empty()) { } else if (normal_.empty()) {
@@ -486,6 +623,11 @@ void mjCMesh::Compile(const mjVFS* vfs) {
} else if (face_.empty()) { } else if (face_.empty()) {
face_ = spec_face_; face_ = spec_face_;
} }
if (!vert_.empty() && !spec_vert_.empty()) {
throw mjCError(this, "repeated vertex specification");
} else if (vert_.empty()) {
ProcessVertices(spec_vert_);
}
if (!facenormal_.empty() && !spec_normal_.empty()) { if (!facenormal_.empty() && !spec_normal_.empty()) {
throw mjCError(this, "repeated facenormal specification"); throw mjCError(this, "repeated facenormal specification");
} else if (facenormal_.empty()) { } else if (facenormal_.empty()) {
@@ -509,11 +651,16 @@ void mjCMesh::Compile(const mjVFS* vfs) {
// check texcoord size if no face texcoord indices are given // check texcoord size if no face texcoord indices are given
if (!texcoord_.empty() && texcoord_.size() != 2 * nvert() && if (!texcoord_.empty() && texcoord_.size() != 2 * nvert() &&
facetexcoord_.empty() && asset_type != "model/obj") { facetexcoord_.empty() && !IsObj()) {
throw mjCError(this, throw mjCError(this,
"texcoord must be 2*nv if face texcoord indices are not provided in an OBJ file"); "texcoord must be 2*nv if face texcoord indices are not provided in an OBJ file");
} }
// require vertices
if (vert_.empty()) {
throw mjCError(this, "no vertices");
}
// check vertices exist // check vertices exist
for (int i=0; i < face_.size(); i++) { for (int i=0; i < face_.size(); i++) {
if (face_[i] >= nvert() || face_[i] < 0) { if (face_[i] >= nvert() || face_[i] < 0) {
@@ -529,8 +676,8 @@ void mjCMesh::Compile(const mjVFS* vfs) {
int v1 = face_[3*i+1]; int v1 = face_[3*i+1];
int v2 = face_[3*i+2]; int v2 = face_[3*i+2];
double normal[3]; double normal[3];
float* vtx = vert_.data(); double* vtx = vert_.data();
if (_triangle(normal, nullptr, vtx+3*v0, vtx+3*v1, vtx+3*v2)>sqrt(mjMINVAL)) { if (triangle(normal, nullptr, vtx+3*v0, vtx+3*v1, vtx+3*v2)>sqrt(mjMINVAL)) {
halfedge_.push_back(std::pair(v0, v1)); halfedge_.push_back(std::pair(v0, v1));
halfedge_.push_back(std::pair(v1, v2)); halfedge_.push_back(std::pair(v1, v2));
halfedge_.push_back(std::pair(v2, v0)); halfedge_.push_back(std::pair(v2, v0));
@@ -540,8 +687,8 @@ void mjCMesh::Compile(const mjVFS* vfs) {
} }
} }
// check vertices exist // check that vertex indices are valid
for (auto vertex_index : face_) { for (int vertex_index : face_) {
if (vertex_index >= nvert() || vertex_index < 0) { if (vertex_index >= nvert() || vertex_index < 0) {
throw mjCError(this, "found index in userface that exceeds uservert size."); throw mjCError(this, "found index in userface that exceeds uservert size.");
} }
@@ -557,11 +704,6 @@ void mjCMesh::Compile(const mjVFS* vfs) {
} }
} }
// require vertices
if (vert_.empty()) {
throw mjCError(this, "no vertices");
}
// make graph describing convex hull // make graph describing convex hull
if (needhull_ || face_.empty()) { if (needhull_ || face_.empty()) {
MakeGraph(); MakeGraph();
@@ -619,6 +761,9 @@ void mjCMesh::Compile(const mjVFS* vfs) {
// check that processed mesh is valid // check that processed mesh is valid
CheckMesh(); CheckMesh();
mju_closeResource(resource_);
resource_ = nullptr;
} }
@@ -671,7 +816,9 @@ bool mjCMesh::HasTexcoord() const {
void mjCMesh::CopyVert(float* arr) const { void mjCMesh::CopyVert(float* arr) const {
std::copy(vert_.begin(), vert_.end(), arr); for (int i = 0; i < vert_.size(); ++i) {
arr[i] = (float)vert_[i];
}
} }
@@ -867,89 +1014,8 @@ void mjCMesh::FitGeom(mjCGeom* geom, double* meshpos) {
// comparison function for vertex sorting
bool vertcompare(int index1, int index2, const std::vector<float>& vert) {
for (int i = 0; i < 3; i++) {
if (vert[3*index1 + i] < vert[3*index2 + i]) {
return true;
}
if (vert[3*index1 + i] > vert[3*index2 + i]) {
return false;
}
}
return false;
}
// remove repeated vertices
void mjCMesh::RemoveRepeated() {
int repeated = 0;
std::vector<int> index(nvert());
std::vector<int> redirect(nvert());
for (int i=0; i < nvert(); i++) {
index[i] = redirect[i] = i;
}
std::stable_sort(index.begin(), index.end(), [&vert = vert_](int a, int b) {
return vertcompare(a, b, vert);
});
// find repeated vertices, set redirect
for (int i=1; i < nvert(); i++) {
if (vert_[3*index[i]] == vert_[3*index[i-1]] &&
vert_[3*index[i]+1] == vert_[3*index[i-1]+1] &&
vert_[3*index[i]+2] == vert_[3*index[i-1]+2]) {
redirect[index[i]] = index[i-1];
repeated++;
}
}
// compress vertices, change face data
if (repeated) {
// track redirections until non-redirected vertex, set
for (int i=0; i < nvert(); i++) {
int j = i;
while (redirect[j]!=j) {
j = redirect[j];
}
redirect[i] = j;
}
// find good vertices, compress, reuse index to save compressed position
int j = 0;
for (int i=0; i < nvert(); i++) {
if (redirect[i]==i) {
index[i] = j;
memcpy(vert_.data()+3*j, vert_.data()+3*i, 3*sizeof(float));
j++;
} else {
index[i] = -1;
}
}
// recompute face data to reflect compressed vertices
for (int i=0; i < 3*nface(); i++) {
face_[i] = index[redirect[face_[i]]];
// sanity check, SHOULD NOT OCCUR
if (face_[i]<0 || face_[i]>=nvert()-repeated) {
throw mjCError(
this, "error removing vertices from mesh '%s'", name.c_str());
}
}
}
// resize vert if any vertices were removed
if (repeated) {
std::vector<float> old = vert_;
vert_.assign(3*(nvert()-repeated), 0);
memcpy(vert_.data(), old.data(), 3*nvert()*sizeof(float));
}
}
// load OBJ mesh // load OBJ mesh
void mjCMesh::LoadOBJ(mjResource* resource) { void mjCMesh::LoadOBJ(mjResource* resource, bool remove_repeated) {
tinyobj::ObjReader objReader; tinyobj::ObjReader objReader;
const void* bytes = nullptr; const void* bytes = nullptr;
@@ -967,7 +1033,6 @@ void mjCMesh::LoadOBJ(mjResource* resource) {
} }
const auto& attrib = objReader.GetAttrib(); const auto& attrib = objReader.GetAttrib();
vert_ = attrib.vertices; // copy from one std::vector to another
normal_ = attrib.normals; normal_ = attrib.normals;
texcoord_ = attrib.texcoords; texcoord_ = attrib.texcoords;
facenormal_.clear(); facenormal_.clear();
@@ -975,7 +1040,7 @@ void mjCMesh::LoadOBJ(mjResource* resource) {
if (!objReader.GetShapes().empty()) { if (!objReader.GetShapes().empty()) {
const auto& mesh = objReader.GetShapes()[0].mesh; const auto& mesh = objReader.GetShapes()[0].mesh;
bool righthand = (scale[0]*scale[1]*scale[2] > 0); bool righthand = scale[0] * scale[1] * scale[2] > 0;
// iterate over mesh faces // iterate over mesh faces
std::vector<tinyobj::index_t> face_indices; std::vector<tinyobj::index_t> face_indices;
@@ -1034,6 +1099,9 @@ void mjCMesh::LoadOBJ(mjResource* resource) {
texcoord_index_.push_back(index.texcoord_index); texcoord_index_.push_back(index.texcoord_index);
} }
} }
// copy vertex data
ProcessVertices(attrib.vertices, remove_repeated);
} }
@@ -1056,7 +1124,6 @@ bool mjCMesh::LoadCachedMesh(mjCCache *cache, const mjResource* resource) {
bool righthand = (scale[0] * scale[1] * scale[2]) > 0; bool righthand = (scale[0] * scale[1] * scale[2]) > 0;
for (int face = 0, i = 0; i < vertex_index_.size();) { for (int face = 0, i = 0; i < vertex_index_.size();) {
int nfacevert = num_face_vertices_[face]; int nfacevert = num_face_vertices_[face];
if (nfacevert < 3 || nfacevert > 4) { if (nfacevert < 3 || nfacevert > 4) {
@@ -1106,7 +1173,7 @@ bool mjCMesh::LoadCachedMesh(mjCCache *cache, const mjResource* resource) {
// 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;
// get file data in buffer // get file data in buffer
char* buffer = 0; char* buffer = 0;
@@ -1145,7 +1212,7 @@ void mjCMesh::LoadSTL(mjResource* resource) {
// allocate face and vertex data // allocate face and vertex data
face_.assign(3*nfaces, 0); face_.assign(3*nfaces, 0);
vert_.clear(); std::vector<float> vert;
// add vertices and faces, including repeated for now // add vertices and faces, including repeated for now
for (int i=0; i < nfaces; i++) { for (int i=0; i < nfaces; i++) {
@@ -1154,41 +1221,32 @@ void mjCMesh::LoadSTL(mjResource* resource) {
float v[3]; float v[3];
ReadFromBuffer(&v, stl + 50*i + 12*(j + 1)); ReadFromBuffer(&v, stl + 50*i + 12*(j + 1));
for (int k=0; k < 3; k++) { // check if vertex can be cast to an int safely
if (std::isnan(v[k]) || std::isinf(v[k])) { if (fabs(v[0]) > pow(2, 30) || fabs(v[1]) > pow(2, 30) || fabs(v[2]) > pow(2, 30)) {
throw mjCError(this, "STL file '%s' contains invalid vertices.", throw mjCError(this, "vertex in STL file '%s' exceed maximum bounds", resource->name);
resource->name);
}
// check if vertex coordinates can be cast to an int safely
if (fabs(v[k]) > pow(2, 30)) {
throw mjCError(this,
"vertex coordinates in STL file '%s' exceed maximum bounds",
resource->name);
}
} }
// add vertex address in face; change order if scale makes it lefthanded // add vertex address in face; change order if scale makes it lefthanded
if (righthand || j == 0) { if (righthand || j == 0) {
face_[3*i+j] = nvert(); face_[3*i + j] = vert.size() / 3;
} else { } else {
face_[3*i+3-j] = nvert(); face_[3*i + 3 - j] = vert.size() / 3;
} }
// add vertex data // add vertex data
vert_.push_back(v[0]); vert.push_back(v[0]);
vert_.push_back(v[1]); vert.push_back(v[1]);
vert_.push_back(v[2]); vert.push_back(v[2]);
} }
} }
ProcessVertices(vert, true);
RemoveRepeated();
} }
// load MSH binary mesh // load MSH binary mesh
void mjCMesh::LoadMSH(mjResource* resource) { void mjCMesh::LoadMSH(mjResource* resource, bool remove_repeated) {
bool righthand = (scale[0]*scale[1]*scale[2]>0); bool righthand = scale[0] * scale[1] * scale[2] > 0;
// get file data in buffer // get file data in buffer
char* buffer = 0; char* buffer = 0;
@@ -1235,20 +1293,23 @@ void mjCMesh::LoadMSH(mjResource* resource) {
// allocate and copy // allocate and copy
using UnalignedFloat = char[sizeof(float)]; using UnalignedFloat = char[sizeof(float)];
auto fdata = reinterpret_cast<UnalignedFloat*>(buffer + 4*sizeof(int)); auto fdata = reinterpret_cast<UnalignedFloat*>(buffer + 4*sizeof(int));
std::vector<float> vert;
int nvert = 0;
if (nvbuf) { if (nvbuf) {
vert_.assign(3*nvbuf, 0); vert.assign(3*nvbuf, 0);
memcpy(vert_.data(), fdata, 3*nvert()*sizeof(float)); nvert = 3*nvbuf;
fdata += 3*nvert(); memcpy(vert.data(), fdata, nvert*sizeof(float));
fdata += nvert;
} }
if (nnbuf) { if (nnbuf) {
normal_.assign(3*nvert(), 0); normal_.assign(nvert, 0);
memcpy(normal_.data(), fdata, 3*nvert()*sizeof(float)); memcpy(normal_.data(), fdata, nvert*sizeof(float));
fdata += 3*nvert(); fdata += nvert;
} }
if (ntbuf) { if (ntbuf) {
texcoord_.assign(2*nvert(), 0); texcoord_.assign(2*(nvert / 3), 0);
memcpy(texcoord_.data(), fdata, 2*nvert()*sizeof(float)); memcpy(texcoord_.data(), fdata, 2*(nvert/3)*sizeof(float));
fdata += 2*nvert(); fdata += 2*(nvert / 3);
} }
if (nfbuf) { if (nfbuf) {
face_.assign(3*nfbuf, 0); face_.assign(3*nfbuf, 0);
@@ -1269,6 +1330,7 @@ void mjCMesh::LoadMSH(mjResource* resource) {
face_[3*i+2] = tmp; face_[3*i+2] = tmp;
} }
} }
ProcessVertices(vert, remove_repeated);
} }
@@ -1279,10 +1341,10 @@ void mjCMesh::ComputeVolume(double CoM[3], const double facecen[3]) {
mjuu_zerovec(CoM, 3); mjuu_zerovec(CoM, 3);
int nf = (inertia == mjMESH_INERTIA_CONVEX) ? graph_[1] : nface(); int nf = (inertia == mjMESH_INERTIA_CONVEX) ? graph_[1] : nface();
int* f = (inertia == mjMESH_INERTIA_CONVEX) ? graph_ + 2 + 3*(graph_[0]+graph_[1]) : face_.data(); int* f = (inertia == mjMESH_INERTIA_CONVEX) ? graph_ + 2 + 3*(graph_[0]+graph_[1]) : face_.data();
float* vv = vert_.data(); double* vv = vert_.data();
for (int i=0; i < nf; i++) { for (int i=0; i < nf; i++) {
// get area, normal and center // get area, normal and center
double a = _triangle(nrm, cen, vv+3*f[3*i], vv+3*f[3*i+1], vv+3*f[3*i+2]); double a = triangle(nrm, cen, vv+3*f[3*i], vv+3*f[3*i+1], vv+3*f[3*i+2]);
// compute and add volume // compute and add volume
const double vec[3] = {cen[0]-facecen[0], cen[1]-facecen[1], cen[2]-facecen[2]}; const double vec[3] = {cen[0]-facecen[0], cen[1]-facecen[1], cen[2]-facecen[2]};
@@ -1316,10 +1378,10 @@ void mjCMesh::ComputeSurfaceArea(double CoM[3], const double facecen[3]) {
double cen[3]; double cen[3];
GetVolumeRef() = 0; GetVolumeRef() = 0;
mjuu_zerovec(CoM, 3); mjuu_zerovec(CoM, 3);
float* vv = vert_.data(); double* vv = vert_.data();
for (int i=0; i < nface(); i++) { for (int i=0; i < nface(); i++) {
// get area, normal and center // get area, normal and center
double a = _triangle(nrm, cen, vv+3*face_.data()[3*i], double a = triangle(nrm, cen, vv+3*face_.data()[3*i],
vv+3*face_.data()[3*i+1], vv+3*face_.data()[3*i+2]); vv+3*face_.data()[3*i+1], vv+3*face_.data()[3*i+2]);
// add pyramid com // add pyramid com
@@ -1344,14 +1406,10 @@ void mjCMesh::ComputeSurfaceArea(double CoM[3], const double facecen[3]) {
void mjCMesh::ApplyTransformations() { void mjCMesh::ApplyTransformations() {
// translate // translate
if (refpos[0] != 0 || refpos[1] != 0 || refpos[2] != 0) { if (refpos[0] != 0 || refpos[1] != 0 || refpos[2] != 0) {
// prepare translation
float rp[3] = {(float)refpos[0], (float)refpos[1], (float)refpos[2]};
// process vertices
for (int i = 0; i < nvert(); i++) { for (int i = 0; i < nvert(); i++) {
vert_[3*i] -= rp[0]; vert_[3*i + 0] -= refpos[0];
vert_[3*i+1] -= rp[1]; vert_[3*i + 1] -= refpos[1];
vert_[3*i+2] -= rp[2]; vert_[3*i + 2] -= refpos[2];
} }
} }
@@ -1365,11 +1423,7 @@ void mjCMesh::ApplyTransformations() {
// process vertices // process vertices
for (int i = 0; i < nvert(); i++) { for (int i = 0; i < nvert(); i++) {
double p1[3], p0[3] = {vert_[3*i], vert_[3*i+1], vert_[3*i+2]}; mjuu_mulvecmatT(&vert_[3*i], &vert_[3*i], mat);
mjuu_mulvecmatT(p1, p0, mat);
vert_[3*i] = (float) p1[0];
vert_[3*i+1] = (float) p1[1];
vert_[3*i+2] = (float) p1[2];
} }
// process normals // process normals
@@ -1385,13 +1439,13 @@ void mjCMesh::ApplyTransformations() {
// scale // scale
if (scale[0] != 1 || scale[1] != 1 || scale[2] != 1) { if (scale[0] != 1 || scale[1] != 1 || scale[2] != 1) {
for (int i = 0; i < nvert(); i++) { for (int i = 0; i < nvert(); i++) {
vert_[3*i] *= scale[0]; vert_[3*i + 0] *= scale[0];
vert_[3*i + 1] *= scale[1]; vert_[3*i + 1] *= scale[1];
vert_[3*i + 2] *= scale[2]; vert_[3*i + 2] *= scale[2];
} }
for (int i = 0; i < nnormal(); i++) { for (int i = 0; i < nnormal(); i++) {
normal_[3*i] *= scale[0]; normal_[3*i + 0] *= scale[0];
normal_[3*i + 1] *= scale[1]; normal_[3*i + 1] *= scale[1];
normal_[3*i + 2] *= scale[2]; normal_[3*i + 2] *= scale[2];
} }
@@ -1405,11 +1459,11 @@ void mjCMesh::ApplyTransformations() {
// rescale // rescale
if (len > mjMINVAL) { if (len > mjMINVAL) {
float scl = 1/sqrtf(len); float scl = 1/sqrtf(len);
normal_[3*i] *= scl; normal_[3*i + 0] *= scl;
normal_[3*i + 1] *= scl; normal_[3*i + 1] *= scl;
normal_[3*i + 2] *= scl; normal_[3*i + 2] *= scl;
} else { } else {
normal_[3*i] = 0; normal_[3*i + 0] = 0;
normal_[3*i + 1] = 0; normal_[3*i + 1] = 0;
normal_[3*i + 2] = 1; normal_[3*i + 2] = 1;
} }
@@ -1432,8 +1486,8 @@ void mjCMesh::ComputeFaceCentroid(double facecen[3]) {
} }
// get area and center // get area and center
float* vv = vert_.data(); double* vv = vert_.data();
double a = _triangle(nrm, cen, vv+3*face_[3*i], vv+3*face_[3*i+1], vv+3*face_[3*i+2]); double a = triangle(nrm, cen, vv+3*face_[3*i], vv+3*face_[3*i+1], vv+3*face_[3*i+2]);
// accumulate // accumulate
for (int j=0; j<3; j++) { for (int j=0; j<3; j++) {
@@ -1527,13 +1581,13 @@ void mjCMesh::ComputeInertia(double inert[6], double CoM[3]) {
double density = model->def_map[classname]->Geom().density; double density = model->def_map[classname]->Geom().density;
// copy vertices to avoid modifying the original mesh // copy vertices to avoid modifying the original mesh
std::vector<float> vert_centered(vert_); std::vector<double> vert_centered(vert_);
// translate vertices to origin in order to compute inertia // translate vertices to origin in order to compute inertia
for (int i=0; i < nvert(); i++) { for (int i=0; i < nvert(); i++) {
for (int j=0; j<3; j++) { vert_centered[3*i + 0] -= CoM[0];
vert_centered[3*i+j] -= CoM[j]; vert_centered[3*i + 1] -= CoM[1];
} vert_centered[3*i + 2] -= CoM[2];
} }
// accumulate products of inertia, recompute volume // accumulate products of inertia, recompute volume
@@ -1543,13 +1597,13 @@ void mjCMesh::ComputeInertia(double inert[6], double CoM[3]) {
int nf = (inertia == mjMESH_INERTIA_CONVEX) ? graph_[1] : nface(); int nf = (inertia == mjMESH_INERTIA_CONVEX) ? graph_[1] : nface();
int* f = (inertia == mjMESH_INERTIA_CONVEX) ? graph_ + 2 + 3*(graph_[0]+graph_[1]) : face_.data(); int* f = (inertia == mjMESH_INERTIA_CONVEX) ? graph_ + 2 + 3*(graph_[0]+graph_[1]) : face_.data();
for (int i=0; i < nf; i++) { for (int i=0; i < nf; i++) {
float* D = vert_centered.data()+3*f[3*i]; double* D = &vert_centered[3*f[3*i + 0]];
float* E = vert_centered.data()+3*f[3*i+1]; double* E = &vert_centered[3*f[3*i + 1]];
float* F = vert_centered.data()+3*f[3*i+2]; double* F = &vert_centered[3*f[3*i + 2]];
// get area, normal and center; update volume // get area, normal and center; update volume
double a = _triangle(nrm, cen, D, E, F); double a = triangle(nrm, cen, D, E, F);
double vol = inertia==mjMESH_INERTIA_SHELL ? a : mjuu_dot3(cen, nrm) * a / 3; double vol = (inertia == mjMESH_INERTIA_SHELL) ? a : mjuu_dot3(cen, nrm) * a / 3;
// if legacy computation requested, then always positive // if legacy computation requested, then always positive
if (inertia == mjMESH_INERTIA_LEGACY) { if (inertia == mjMESH_INERTIA_LEGACY) {
@@ -1586,18 +1640,17 @@ void mjCMesh::Rotate(double quat[4]) {
double mat[9]; double mat[9];
mjuu_quat2mat(mat, neg); mjuu_quat2mat(mat, neg);
for (int i = 0; i < nvert(); i++) { for (int i = 0; i < nvert(); i++) {
// vertices mjuu_mulvecmat(&vert_[3*i], &vert_[3*i], mat);
const double vec[3] = {vert_[3*i], vert_[3*i+1], vert_[3*i+2]};
double res[3];
mjuu_mulvecmat(res, vec, mat);
for (int j=0; j<3; j++) {
vert_[3*i+j] = (float) res[j];
// axis-aligned bounding box // axis-aligned bounding box
aamm_[j+0] = min(aamm_[j+0], res[j]); aamm_[0] = std::min(aamm_[0], vert_[3*i + 0]);
aamm_[j+3] = max(aamm_[j+3], res[j]); aamm_[3] = std::max(aamm_[3], vert_[3*i + 0]);
} aamm_[1] = std::min(aamm_[1], vert_[3*i + 1]);
aamm_[4] = std::max(aamm_[4], vert_[3*i + 1]);
aamm_[2] = std::min(aamm_[2], vert_[3*i + 2]);
aamm_[5] = std::max(aamm_[5], vert_[3*i + 2]);
} }
for (int i=0; i < nnormal(); i++) { for (int i=0; i < nnormal(); i++) {
// normals // normals
const double nrm[3] = {normal_[3*i], normal_[3*i+1], normal_[3*i+2]}; const double nrm[3] = {normal_[3*i], normal_[3*i+1], normal_[3*i+2]};
@@ -1613,9 +1666,9 @@ void mjCMesh::Rotate(double quat[4]) {
void mjCMesh::Transform(double pos[3], double quat[4]) { void mjCMesh::Transform(double pos[3], double quat[4]) {
// subtract CoM position from vertices // subtract CoM position from vertices
for (int i=0; i < nvert(); i++) { for (int i=0; i < nvert(); i++) {
for (int j=0; j<3; j++) { vert_[3*i + 0] -= pos[0];
vert_[3*i+j] -= pos[j]; vert_[3*i + 1] -= pos[1];
} vert_[3*i + 2] -= pos[2];
} }
Rotate(quat); Rotate(quat);
@@ -1655,14 +1708,13 @@ double* mjCMesh::GetInertiaBoxPtr() {
double& mjCMesh::GetVolumeRef() { double& mjCMesh::GetVolumeRef() {
CheckMesh(); CheckMesh();
return inertia==mjMESH_INERTIA_SHELL ? surface_ : volume_; return (inertia == mjMESH_INERTIA_SHELL) ? surface_ : volume_;
} }
// make graph describing convex hull // make graph describing convex hull
void mjCMesh::MakeGraph() { void mjCMesh::MakeGraph() {
int adr, ok, curlong, totlong, exitcode; int adr, ok, curlong, totlong, exitcode;
double* data;
facetT* facet, **facetp; facetT* facet, **facetp;
vertexT* vertex, *vertex1, **vertex1p; vertexT* vertex, *vertex1, **vertex1p;
@@ -1677,19 +1729,6 @@ void mjCMesh::MakeGraph() {
return; return;
} }
// convert mesh data to double
data = (double*) mju_malloc(3*nvert()*sizeof(double));
if (!data) {
throw mjCError(this, "could not allocate data for qhull");
}
for (int i=0; i < 3*nvert(); i++) {
if (!std::isfinite(vert_[i])) {
mju_free(data);
throw mjCError(this, "vertex coordinate %d is not finite", NULL, i);
}
data[i] = (double)vert_[i];
}
qhT qh_qh; qhT qh_qh;
qhT* qh = &qh_qh; qhT* qh = &qh_qh;
qh_zero(qh, stderr); qh_zero(qh, stderr);
@@ -1703,7 +1742,7 @@ void mjCMesh::MakeGraph() {
if (!exitcode) { if (!exitcode) {
// actual init // actual init
qh_initflags(qh, const_cast<char*>(qhopt.c_str())); qh_initflags(qh, const_cast<char*>(qhopt.c_str()));
qh_init_B(qh, data, nvert(), 3, False); qh_init_B(qh, vert_.data(), nvert(), 3, False);
// construct convex hull // construct convex hull
qh_qhull(qh); qh_qhull(qh);
@@ -1814,7 +1853,6 @@ void mjCMesh::MakeGraph() {
// free all // free all
qh_freeqhull(qh, !qh_ALL); qh_freeqhull(qh, !qh_ALL);
qh_memfreeshort(qh, &curlong, &totlong); qh_memfreeshort(qh, &curlong, &totlong);
mju_free(data);
// bad graph: delete // bad graph: delete
if (!ok) { if (!ok) {
@@ -1849,7 +1887,6 @@ void mjCMesh::MakeGraph() {
// free all // free all
qh_freeqhull(qh, !qh_ALL); qh_freeqhull(qh, !qh_ALL);
qh_memfreeshort(qh, &curlong, &totlong); qh_memfreeshort(qh, &curlong, &totlong);
mju_free(data);
if (graph_) { if (graph_) {
mju_free(graph_); mju_free(graph_);
szgraph_ = 0; szgraph_ = 0;
@@ -1860,7 +1897,7 @@ void mjCMesh::MakeGraph() {
} }
// copy graph into face data // copy graph into face data
void mjCMesh::CopyGraph(void) { void mjCMesh::CopyGraph() {
// only if face data is missing // only if face data is missing
if (!face_.empty()) { if (!face_.empty()) {
return; return;
@@ -1876,7 +1913,7 @@ void mjCMesh::CopyGraph(void) {
int j = 2 + 3*numvert + 3*nface() + 3*i; int j = 2 + 3*numvert + 3*nface() + 3*i;
// copy // copy
face_[3*i] = graph_[j]; face_[3*i + 0] = graph_[j + 0];
face_[3*i + 1] = graph_[j + 1]; face_[3*i + 1] = graph_[j + 1];
face_[3*i + 2] = graph_[j + 2]; face_[3*i + 2] = graph_[j + 2];
} }
@@ -1885,7 +1922,7 @@ void mjCMesh::CopyGraph(void) {
// compute vertex normals // compute vertex normals
void mjCMesh::MakeNormal(void) { void mjCMesh::MakeNormal() {
// only if normal data is missing // only if normal data is missing
if (!normal_.empty()) { if (!normal_.empty()) {
return; return;
@@ -1996,7 +2033,7 @@ void mjCMesh::MakeNormal(void) {
// compute face circumradii // compute face circumradii
void mjCMesh::MakeCenter(void) { void mjCMesh::MakeCenter() {
if (center_) { if (center_) {
return; return;
} }
@@ -2058,7 +2095,7 @@ void mjCMesh::MakePolygonNormals() {
class MeshPolygon { class MeshPolygon {
public: public:
// constructors (need starting face) // constructors (need starting face)
MeshPolygon(const float v1[3], const float v2[3], const float v3[3], MeshPolygon(const double v1[3], const double v2[3], const double v3[3],
int v1i, int v2i, int v3i); int v1i, int v2i, int v3i);
MeshPolygon() = delete; MeshPolygon() = delete;
MeshPolygon(const MeshPolygon&) = delete; MeshPolygon(const MeshPolygon&) = delete;
@@ -2084,7 +2121,7 @@ class MeshPolygon {
MeshPolygon::MeshPolygon(const float v1[3], const float v2[3], const float v3[3], MeshPolygon::MeshPolygon(const double v1[3], const double v2[3], const double v3[3],
int v1i, int v2i, int v3i) { int v1i, int v2i, int v3i) {
mjuu_makenormal(normal_, v1, v2, v3); mjuu_makenormal(normal_, v1, v2, v3);
edges_ = {{v1i, v2i}, {v2i, v3i}, {v3i, v1i}}; edges_ = {{v1i, v2i}, {v2i, v3i}, {v3i, v1i}};
@@ -2293,9 +2330,9 @@ void mjCMesh::MakePolygons() {
// process each face // process each face
for (int i = 0; i < nfaces; i++) { for (int i = 0; i < nfaces; i++) {
float* v1 = &vert_[3*faces[3*i + 0]]; double* v1 = &vert_[3*faces[3*i + 0]];
float* v2 = &vert_[3*faces[3*i + 1]]; double* v2 = &vert_[3*faces[3*i + 1]];
float* v3 = &vert_[3*faces[3*i + 2]]; double* v3 = &vert_[3*faces[3*i + 2]];
MeshPolygon face(v1, v2, v3, faces[3*i + 0], faces[3*i + 1], faces[3*i + 2]); MeshPolygon face(v1, v2, v3, faces[3*i + 0], faces[3*i + 1], faces[3*i + 2]);
auto it = polygons.find(face); auto it = polygons.find(face);
+1 -1
View File
@@ -734,7 +734,7 @@ std::string mjCBase::GetAssetContentType(std::string_view resource_name,
auto type = mjuu_parseContentTypeAttrType(raw_text); auto type = mjuu_parseContentTypeAttrType(raw_text);
auto subtype = mjuu_parseContentTypeAttrSubtype(raw_text); auto subtype = mjuu_parseContentTypeAttrSubtype(raw_text);
if (!type.has_value() || !subtype.has_value()) { if (!type.has_value() || !subtype.has_value()) {
throw mjCError(this, "invalid format for content_type"); return "";
} }
return std::string(*type) + "/" + std::string(*subtype); return std::string(*type) + "/" + std::string(*subtype);
} else { } else {
+29 -12
View File
@@ -15,6 +15,7 @@
#ifndef MUJOCO_SRC_USER_USER_OBJECTS_H_ #ifndef MUJOCO_SRC_USER_USER_OBJECTS_H_
#define MUJOCO_SRC_USER_USER_OBJECTS_H_ #define MUJOCO_SRC_USER_USER_OBJECTS_H_
#include <stdbool.h>
#include <cstddef> #include <cstddef>
#include <array> #include <array>
#include <cstdlib> #include <cstdlib>
@@ -195,7 +196,7 @@ class mjCBase : public mjCBase_ {
// Get and sanitize content type from raw_text if not empty, otherwise parse // Get and sanitize content type from raw_text if not empty, otherwise parse
// content type from resource_name; throw on failure // content type from resource_name; throw on failure
std::string GetAssetContentType(std::string_view resource_name, std::string_view raw_text); static std::string GetAssetContentType(std::string_view resource_name, std::string_view raw_text);
// Add frame transformation // Add frame transformation
void SetFrame(mjCFrame* _frame); void SetFrame(mjCFrame* _frame);
@@ -842,9 +843,10 @@ class mjCMesh_ : public mjCBase {
std::string plugin_name; std::string plugin_name;
std::string plugin_instance_name; std::string plugin_instance_name;
std::string content_type_; // content type of file std::string content_type_ = ""; // content type of file
std::string file_; // mesh file std::string file_; // mesh file
std::vector<float> vert_; // vertex data mjResource* resource_ = nullptr; // resource for mesh file
std::vector<double> vert_; // vertex data
std::vector<float> normal_; // normal data std::vector<float> normal_; // normal data
std::vector<float> texcoord_; // texcoord data std::vector<float> texcoord_; // texcoord data
std::vector<int> face_; // vertex indices std::vector<int> face_; // vertex indices
@@ -886,7 +888,7 @@ class mjCMesh_ : public mjCBase {
double surface_; // surface of the mesh double surface_; // surface of the mesh
// size of mesh data to be copied into mjModel // size of mesh data to be copied into mjModel
int szgraph_; // size of graph data in ints int szgraph_ = 0; // size of graph data in ints
bool needhull_; // needs convex hull for collisions bool needhull_; // needs convex hull for collisions
int maxhullvert_; // max vertex count of convex hull int maxhullvert_; // max vertex count of convex hull
@@ -923,8 +925,8 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
const double* Refquat() const { return refquat; } const double* Refquat() const { return refquat; }
const double* Scale() const { return scale; } const double* Scale() const { return scale; }
bool SmoothNormal() const { return smoothnormal; } bool SmoothNormal() const { return smoothnormal; }
const std::vector<float>& Vert() const { return vert_; } const std::vector<double>& Vert() const { return vert_; }
float Vert(int i) const { return vert_[i]; } double Vert(int i) const { return vert_[i]; }
const std::vector<float>& UserVert() const { return spec_vert_; } const std::vector<float>& UserVert() const { return spec_vert_; }
const std::vector<float>& UserNormal() const { return spec_normal_; } const std::vector<float>& UserNormal() const { return spec_normal_; }
const std::vector<float>& Texcoord() const { return texcoord_; } const std::vector<float>& Texcoord() const { return texcoord_; }
@@ -997,18 +999,33 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
// sets properties of a bounding volume given a face id // sets properties of a bounding volume given a face id
void SetBoundingVolume(int faceid); void SetBoundingVolume(int faceid);
void LoadOBJ(mjResource* resource); // load mesh in wavefront OBJ format // load from OBJ, STL, or MSH file; throws mjCError on failure
void LoadSTL(mjResource* resource); // load mesh in STL BIN format void LoadFromResource(mjResource* resource, bool remove_repeated = false);
void LoadMSH(mjResource* resource); // load mesh in MSH BIN format
void RemoveRepeated(); // remove repeated vertices static bool IsObj(std::string_view filename, std::string_view ct = "");
static bool IsSTL(std::string_view filename, std::string_view ct = "");
static bool IsMSH(std::string_view filename, std::string_view ct = "");
bool IsObj() const;
bool IsSTL() const;
bool IsMSH() const;
private: private:
void TryCompile(const mjVFS* vfs);
// load mesh from cache asset, return true on success (OBJ files are only supported) // load mesh from cache asset, return true on success (OBJ files are only supported)
bool LoadCachedMesh(mjCCache *cache, const mjResource* resource); bool LoadCachedMesh(mjCCache *cache, const mjResource* resource);
// store mesh into asset cache (OBJ files are only supported) // store mesh into asset cache (OBJ files are only supported)
void CacheMesh(mjCCache *cache, const mjResource* resource, void CacheMesh(mjCCache *cache, const mjResource* resource);
std::string_view asset_type);
// convert vertices to double precision and remove repeated vertices if requested
void ProcessVertices(const std::vector<float>& vert, bool remove_repeated = false);
void LoadOBJ(mjResource* resource, bool remove_repeated); // load mesh in wavefront OBJ format
void LoadSTL(mjResource* resource); // load mesh in STL BIN format
void LoadMSH(mjResource* resource, bool remove_repeated); // load mesh in MSH BIN format
void LoadSDF(); // generate mesh using marching cubes void LoadSDF(); // generate mesh using marching cubes
void MakeGraph(); // make graph of convex hull void MakeGraph(); // make graph of convex hull
+6 -1
View File
@@ -359,7 +359,8 @@ void mjuu_crossvec(double* a, const double* b, const double* c) {
// compute normal vector to given triangle, return length // compute normal vector to given triangle, return length
double mjuu_makenormal(double* normal, const float* a, const float* b, const float* c) { template<typename T> double mjuu_makenormal(double* normal, const T a[3],
const T b[3], const T c[3]) {
double v1[3] = {a[0], a[1], a[2]}; double v1[3] = {a[0], a[1], a[2]};
double v2[3] = {b[0], b[1], b[2]}; double v2[3] = {b[0], b[1], b[2]};
double v3[3] = {c[0], c[1], c[2]}; double v3[3] = {c[0], c[1], c[2]};
@@ -379,6 +380,10 @@ double mjuu_makenormal(double* normal, const float* a, const float* b, const flo
return nrm; return nrm;
} }
template double mjuu_makenormal(double* normal, const double a[3],
const double b[3], const double c[3]);
template double mjuu_makenormal(double* normal, const float a[3],
const float b[3], const float c[3]);
// compute quaternion as minimal rotation from [0;0;1] to vec // compute quaternion as minimal rotation from [0;0;1] to vec
void mjuu_z2quat(double* quat, const double* vec) { void mjuu_z2quat(double* quat, const double* vec) {
+3 -2
View File
@@ -107,8 +107,9 @@ void mjuu_localquat(double* local, const double* child, const double* parent);
// compute vector cross-product a = b x c // compute vector cross-product a = b x c
void mjuu_crossvec(double* a, const double* b, const double* c); void mjuu_crossvec(double* a, const double* b, const double* c);
// compute normal vector to given triangle (uses float for OpenGL) // compute normal vector to given triangle
double mjuu_makenormal(double* normal, const float* a, const float* b, const float* c); template<typename T> double mjuu_makenormal(double* normal, const T a[3],
const T b[3], const T c[3]);
// compute quaternion corresponding to minimal rotation from [0;0;1] to vec // compute quaternion corresponding to minimal rotation from [0;0;1] to vec
void mjuu_z2quat(double* quat, const double* vec); void mjuu_z2quat(double* quat, const double* vec);
+1 -1
View File
@@ -991,7 +991,7 @@ TEST_F(MjGjkTest, SmallBoxMesh) {
EXPECT_NEAR(dir[2], 1, kTolerance); EXPECT_NEAR(dir[2], 1, kTolerance);
// position // position
EXPECT_NEAR(mju_abs(pos[0]), 0.08333333, kTolerance); // -pos[0] on ARM EXPECT_NEAR(pos[0], 0, kTolerance);
EXPECT_NEAR(pos[1], 0, kTolerance); EXPECT_NEAR(pos[1], 0, kTolerance);
EXPECT_NEAR(pos[2], 0, kTolerance); EXPECT_NEAR(pos[2], 0, kTolerance);
+46 -10
View File
@@ -85,6 +85,7 @@ TEST_F(MjCMeshTest, UnknownMeshFormat) {
</worldbody> </worldbody>
</mujoco> </mujoco>
)"; )";
std::vector<std::string> invalid_names = { std::vector<std::string> invalid_names = {
"noextension", "noextension",
"anobj", "anobj",
@@ -92,16 +93,22 @@ TEST_F(MjCMeshTest, UnknownMeshFormat) {
"mesh.exe", "mesh.exe",
"file%s" "file%s"
}; };
mjVFS vfs;
mj_defaultVFS(&vfs);
for (const auto& name : invalid_names) { for (const auto& name : invalid_names) {
mj_addBufferVFS(&vfs, name.c_str(), nullptr, 0);
std::string xml = absl::StrFormat(xml_format, name); std::string xml = absl::StrFormat(xml_format, name);
std::array<char, 1024> error; std::array<char, 1024> error;
mjModel* model = mjModel* model =
LoadModelFromString(xml.c_str(), error.data(), error.size()); LoadModelFromString(xml.c_str(), error.data(), error.size(), &vfs);
ASSERT_THAT(model, testing::IsNull()) ASSERT_THAT(model, testing::IsNull())
<< "Should fail to load a mesh named: " << name; << "Should fail to load a mesh named: " << name;
EXPECT_THAT(error.data(), HasSubstr("unknown mesh content type for file")); EXPECT_THAT(error.data(), HasSubstr("unknown or unsupported mesh file: "));
EXPECT_THAT(error.data(), HasSubstr(name)); EXPECT_THAT(error.data(), HasSubstr(name));
} }
mj_deleteVFS(&vfs);
} }
// -------------------- test OS filesystem fallback ---------------------------- // -------------------- test OS filesystem fallback ----------------------------
@@ -280,14 +287,42 @@ TEST_F(MjCMeshTest, LoadMSHWithContentTypeError) {
size_t error_sz = 1024; size_t error_sz = 1024;
// load VFS on the heap // load VFS on the heap
auto vfs = std::make_unique<mjVFS>(); mjVFS vfs;
mj_defaultVFS(vfs.get()); mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "some_file", nullptr, 0);
// should error with unknown file type // should error with unknown file type
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get()); mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
EXPECT_THAT(model, IsNull()); EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("unsupported content type: 'model/unknown'")); EXPECT_THAT(error, HasSubstr("unsupported mesh type: 'model/unknown'"));
mj_deleteVFS(vfs.get()); mj_deleteVFS(&vfs);
}
TEST_F(MjCMeshTest, LoadMSHWithInvalidContentType) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="mesh1" content_type="model" file="some_file"/>
</asset>
<worldbody>
<geom type="mesh" mesh="mesh1"/>
</worldbody>
</mujoco>
)";
char error[1024];
size_t error_sz = 1024;
// load VFS on the heap
mjVFS vfs;
mj_defaultVFS(&vfs);
mj_addBufferVFS(&vfs, "some_file", nullptr, 0);
// should error with unknown file type
mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error, HasSubstr("invalid content type: 'model'"));
mj_deleteVFS(&vfs);
} }
TEST_F(MjCMeshTest, LoadMSHWithContentTypeParam) { TEST_F(MjCMeshTest, LoadMSHWithContentTypeParam) {
@@ -323,6 +358,7 @@ TEST_F(MjCMeshTest, DeduplicateSTLVertices) {
char error[1024]; char error[1024];
size_t error_sz = 1024; size_t error_sz = 1024;
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, error_sz); mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, error_sz);
ASSERT_THAT(model, NotNull()) << error;
ASSERT_EQ(model->nmeshvert, 4); ASSERT_EQ(model->nmeshvert, 4);
mj_deleteModel(model); mj_deleteModel(model);
} }
@@ -519,9 +555,9 @@ TEST_F(MjCMeshTest, MissingFaceAllowedConvexInertia) {
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, sizeof(error)); mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error; ASSERT_THAT(model, NotNull()) << error;
EXPECT_THAT(model->nmeshface, 10); EXPECT_THAT(model->nmeshface, 10);
EXPECT_THAT(model->body_inertia[3], model->body_inertia[9]); EXPECT_NE(model->body_inertia[3], model->body_inertia[9]);
EXPECT_THAT(model->body_inertia[4], model->body_inertia[10]); EXPECT_NE(model->body_inertia[4], model->body_inertia[10]);
EXPECT_THAT(model->body_inertia[5], model->body_inertia[11]); EXPECT_NE(model->body_inertia[5], model->body_inertia[11]);
EXPECT_NE(model->body_inertia[3], model->body_inertia[6]); EXPECT_NE(model->body_inertia[3], model->body_inertia[6]);
EXPECT_NE(model->body_inertia[4], model->body_inertia[7]); EXPECT_NE(model->body_inertia[4], model->body_inertia[7]);
EXPECT_NE(model->body_inertia[5], model->body_inertia[8]); EXPECT_NE(model->body_inertia[5], model->body_inertia[8]);