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:
committed by
Copybara-Service
parent
e4ee00e695
commit
4d024f9421
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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]);
|
||||||
|
|||||||
Reference in New Issue
Block a user