Move OBJ handling to a mjpDecoder.
PiperOrigin-RevId: 875194599 Change-Id: I2cccc683d69a217f1817e70d16bc1815e366d00c
This commit is contained in:
committed by
Copybara-Service
parent
abdea379af
commit
b907ffedaa
@@ -38,4 +38,3 @@ set(MUJOCO_USER_SRCS
|
||||
)
|
||||
|
||||
target_sources(mujoco PRIVATE ${MUJOCO_USER_SRCS})
|
||||
target_compile_definitions(mujoco PRIVATE MUJOCO_TINYOBJLOADER_IMPL)
|
||||
|
||||
+46
-94
@@ -612,10 +612,7 @@ void mjCMesh::ProcessVertices(const std::vector<float>& vert, bool remove_repeat
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -627,9 +624,7 @@ bool mjCMesh::IsMSH(std::string_view filename, std::string_view 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";
|
||||
@@ -639,28 +634,62 @@ bool mjCMesh::IsMSH() const {
|
||||
return content_type_ == "model/vnd.mujoco.msh";
|
||||
}
|
||||
|
||||
// load mesh using decoder plugin
|
||||
void mjCMesh::LoadFromDecoder(mjResource* resource, bool remove_repeated) {
|
||||
const mjpDecoder* decoder = mjp_findDecoder(resource, content_type_.c_str());
|
||||
if (!decoder) {
|
||||
throw mjCError(this, "no decoder found for mesh file '%s'", resource->name);
|
||||
}
|
||||
mjSpec* mesh_spec = decoder->decode(resource, nullptr);
|
||||
if (!mesh_spec) {
|
||||
throw mjCError(this, "decoder failed for mesh file '%s'", resource->name);
|
||||
}
|
||||
mjsElement* elem = mjs_firstElement(mesh_spec, mjOBJ_MESH);
|
||||
if (elem) {
|
||||
mjsMesh* src_mesh = mjs_asMesh(elem);
|
||||
if (src_mesh) {
|
||||
normal_.assign(src_mesh->usernormal->begin(), src_mesh->usernormal->end());
|
||||
texcoord_.assign(src_mesh->usertexcoord->begin(), src_mesh->usertexcoord->end());
|
||||
face_.assign(src_mesh->userface->begin(), src_mesh->userface->end());
|
||||
facenormal_.assign(src_mesh->userfacenormal->begin(), src_mesh->userfacenormal->end());
|
||||
facetexcoord_.assign(src_mesh->userfacetexcoord->begin(), src_mesh->userfacetexcoord->end());
|
||||
|
||||
// correct winding order for left-handed coordinate systems
|
||||
bool righthand = scale[0] * scale[1] * scale[2] > 0;
|
||||
if (!righthand) {
|
||||
for (size_t i = 0; i < face_.size(); i += 3) {
|
||||
std::swap(face_[i + 1], face_[i + 2]);
|
||||
}
|
||||
for (size_t i = 0; i < facenormal_.size(); i += 3) {
|
||||
std::swap(facenormal_[i + 1], facenormal_[i + 2]);
|
||||
}
|
||||
for (size_t i = 0; i < facetexcoord_.size(); i += 3) {
|
||||
std::swap(facetexcoord_[i + 1], facetexcoord_[i + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> vert(src_mesh->uservert->begin(), src_mesh->uservert->end());
|
||||
mj_deleteSpec(mesh_spec);
|
||||
ProcessVertices(vert, remove_repeated);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mj_deleteSpec(mesh_spec);
|
||||
}
|
||||
|
||||
|
||||
// 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());
|
||||
LoadFromDecoder(resource, remove_repeated);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,83 +1038,6 @@ void mjCMesh::FitGeom(mjCGeom* geom, double center[3]) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// load OBJ mesh
|
||||
void mjCMesh::LoadOBJ(mjResource* resource, bool remove_repeated) {
|
||||
tinyobj::ObjReader objReader;
|
||||
const void* bytes = nullptr;
|
||||
|
||||
int buffer_sz = mju_readResource(resource, &bytes);
|
||||
if (buffer_sz < 0) {
|
||||
throw mjCError(this, "could not read OBJ file '%s'", resource->name);
|
||||
}
|
||||
|
||||
// TODO(etom): support .mtl files?
|
||||
const char* buffer = (const char*) bytes;
|
||||
objReader.ParseFromString(std::string(buffer, buffer_sz), std::string());
|
||||
|
||||
if (!objReader.Valid()) {
|
||||
throw mjCError(this, "could not parse OBJ file '%s'", resource->name);
|
||||
}
|
||||
|
||||
const auto& attrib = objReader.GetAttrib();
|
||||
normal_ = attrib.normals;
|
||||
texcoord_ = attrib.texcoords;
|
||||
facenormal_.clear();
|
||||
facetexcoord_.clear();
|
||||
|
||||
if (!objReader.GetShapes().empty()) {
|
||||
const auto& mesh = objReader.GetShapes()[0].mesh;
|
||||
bool righthand = scale[0] * scale[1] * scale[2] > 0;
|
||||
|
||||
// iterate over mesh faces
|
||||
std::vector<tinyobj::index_t> face_indices;
|
||||
for (int face = 0, idx = 0; idx < mesh.indices.size();) {
|
||||
int nfacevert = mesh.num_face_vertices[face];
|
||||
if (nfacevert < 3 || nfacevert > 4) {
|
||||
throw mjCError(
|
||||
this, "only tri or quad meshes are supported for OBJ (file '%s')",
|
||||
resource->name);
|
||||
}
|
||||
|
||||
face_indices.push_back(mesh.indices[idx]);
|
||||
face_indices.push_back(mesh.indices[idx + (righthand == 1 ? 1 : 2)]);
|
||||
face_indices.push_back(mesh.indices[idx + (righthand == 1 ? 2 : 1)]);
|
||||
|
||||
if (nfacevert == 4) {
|
||||
face_indices.push_back(mesh.indices[idx]);
|
||||
face_indices.push_back(mesh.indices[idx + (righthand == 1 ? 2 : 3)]);
|
||||
face_indices.push_back(mesh.indices[idx + (righthand == 1 ? 3 : 2)]);
|
||||
}
|
||||
idx += nfacevert;
|
||||
++face;
|
||||
}
|
||||
|
||||
// for each vertex, store index, normal, and texcoord
|
||||
for (const auto& mesh_index : face_indices) {
|
||||
face_.push_back(mesh_index.vertex_index);
|
||||
|
||||
if (!normal_.empty()) {
|
||||
facenormal_.push_back(mesh_index.normal_index);
|
||||
}
|
||||
|
||||
if (!texcoord_.empty()) {
|
||||
facetexcoord_.push_back(mesh_index.texcoord_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flip the second texcoord
|
||||
for (int i=0; i < texcoord_.size()/2; i++) {
|
||||
texcoord_[2*i+1] = 1-texcoord_[2*i+1];
|
||||
}
|
||||
|
||||
// copy vertex data
|
||||
ProcessVertices(attrib.vertices, remove_repeated);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// load mesh from cached asset, return true on success
|
||||
bool mjCMesh::LoadCachedMesh(mjCCache *cache, const mjResource* resource) {
|
||||
auto process_mesh = [&](const void* data) {
|
||||
@@ -1776,7 +1728,7 @@ void mjCMesh::CheckInitialMesh() const {
|
||||
|
||||
// check texcoord size if no face texcoord indices are given
|
||||
if (!texcoord_.empty() && texcoord_.size() != 2 * nvert() &&
|
||||
facetexcoord_.empty() && !IsObj()) {
|
||||
facetexcoord_.empty() && content_type_ != "model/obj") {
|
||||
throw mjCError(this,
|
||||
"texcoord must be 2*nv if face texcoord indices are not provided in an OBJ file");
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
@@ -34,10 +35,6 @@
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include "user/user_cache.h"
|
||||
#include "user/user_util.h"
|
||||
#include <tiny_obj_loader.h>
|
||||
|
||||
using face_vertices_type =
|
||||
decltype(tinyobj::mesh_t::num_face_vertices)::value_type;
|
||||
|
||||
// forward declarations of all mjC/X classes
|
||||
class mjCError;
|
||||
@@ -1241,11 +1238,11 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
|
||||
// load from OBJ, STL, or MSH file; throws mjCError on failure
|
||||
void LoadFromResource(mjResource* resource, bool remove_repeated = false);
|
||||
|
||||
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;
|
||||
|
||||
@@ -1262,7 +1259,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
|
||||
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 LoadFromDecoder(mjResource* resource, bool remove_repeated); // load mesh using decoder plugin
|
||||
void LoadSTL(mjResource* resource); // load mesh in STL BIN format
|
||||
void LoadMSH(mjResource* resource, bool remove_repeated); // load mesh in MSH BIN format
|
||||
|
||||
|
||||
Reference in New Issue
Block a user