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
@@ -101,6 +101,7 @@ copy_plugins_posix() {
|
|||||||
mkdir -p ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
mkdir -p ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
cp lib/libactuator.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
cp lib/libactuator.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
cp lib/libelasticity.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
cp lib/libelasticity.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
|
cp lib/libobj_decoder.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
cp lib/libsensor.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
cp lib/libsensor.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
cp lib/libsdf_plugin.* ${TMPDIR}/mujoco_install/mujoco_plugin
|
cp lib/libsdf_plugin.* ${TMPDIR}/mujoco_install/mujoco_plugin
|
||||||
}
|
}
|
||||||
@@ -111,6 +112,7 @@ copy_plugins_window() {
|
|||||||
mkdir -p ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
mkdir -p ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
cp bin/Release/actuator.dll ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
cp bin/Release/actuator.dll ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
cp bin/Release/elasticity.dll ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
cp bin/Release/elasticity.dll ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
|
cp bin/Release/obj_decoder.dll ${TMPDIR}/mujoco_install/mujoco_plugin &&
|
||||||
cp bin/Release/sensor.dll ${TMPDIR}/mujoco_install/mujoco_plugin
|
cp bin/Release/sensor.dll ${TMPDIR}/mujoco_install/mujoco_plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ add_subdirectory(src/engine)
|
|||||||
add_subdirectory(src/user)
|
add_subdirectory(src/user)
|
||||||
add_subdirectory(src/xml)
|
add_subdirectory(src/xml)
|
||||||
add_subdirectory(src/thread)
|
add_subdirectory(src/thread)
|
||||||
|
add_subdirectory(plugin/obj_decoder)
|
||||||
|
|
||||||
if(MUJOCO_USE_FILAMENT AND NOT EMSCRIPTEN)
|
if(MUJOCO_USE_FILAMENT AND NOT EMSCRIPTEN)
|
||||||
# Note that, by default, the "src/render" and "src/ui" code is added directly
|
# Note that, by default, the "src/render" and "src/ui" code is added directly
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ function(add_mujoco_shell_test TEST_NAME TARGET_BINARY)
|
|||||||
"CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}"
|
"CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}"
|
||||||
"TARGET_BINARY=$<TARGET_FILE:${TARGET_BINARY}>"
|
"TARGET_BINARY=$<TARGET_FILE:${TARGET_BINARY}>"
|
||||||
"TEST_TMPDIR=${TEST_TMPDIR}"
|
"TEST_TMPDIR=${TEST_TMPDIR}"
|
||||||
|
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:obj_decoder>"
|
||||||
)
|
)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# Define the directory containing the mujoco DLL library so that it can be added to the PATH.
|
# Define the directory containing the mujoco DLL library so that it can be added to the PATH.
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Copyright 2026 DeepMind Technologies Limited
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||||
|
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}")
|
||||||
|
|
||||||
|
add_library(obj_decoder SHARED obj_decoder.cc)
|
||||||
|
|
||||||
|
target_include_directories(obj_decoder PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../..
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(obj_decoder PRIVATE
|
||||||
|
mujoco
|
||||||
|
tinyobjloader
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(obj_decoder PRIVATE TINYOBJLOADER_IMPLEMENTATION)
|
||||||
|
|
||||||
|
target_compile_options(obj_decoder PRIVATE
|
||||||
|
${AVX_COMPILE_OPTIONS}
|
||||||
|
${MUJOCO_MACOS_COMPILE_OPTIONS}
|
||||||
|
${EXTRA_COMPILE_OPTIONS}
|
||||||
|
${MUJOCO_CXX_FLAGS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_options(obj_decoder PRIVATE
|
||||||
|
${MUJOCO_MACOS_LINK_OPTIONS}
|
||||||
|
${EXTRA_LINK_OPTIONS}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
TARGETS obj_decoder
|
||||||
|
LIBRARY DESTINATION "${CMAKE_INSTALL_BINDIR}/mujoco_plugin"
|
||||||
|
)
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2026 DeepMind Technologies Limited
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mujoco/mjplugin.h>
|
||||||
|
#include <mujoco/mjspec.h>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
|
#include <tiny_obj_loader.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
mjSpec* Decode(mjResource* resource, const mjVFS* vfs) {
|
||||||
|
const void* bytes = nullptr;
|
||||||
|
int buffer_sz = mju_readResource(resource, &bytes);
|
||||||
|
if (buffer_sz < 0) {
|
||||||
|
mju_warning("obj_decoder: could not read OBJ file '%s'", resource->name);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
tinyobj::ObjReader obj_reader;
|
||||||
|
const char* buffer = static_cast<const char*>(bytes);
|
||||||
|
obj_reader.ParseFromString(std::string(buffer, buffer_sz), std::string());
|
||||||
|
|
||||||
|
if (!obj_reader.Valid()) {
|
||||||
|
mju_warning("obj_decoder: could not parse OBJ file '%s'", resource->name);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjSpec* spec = mj_makeSpec();
|
||||||
|
mjsMesh* mesh = mjs_addMesh(spec, nullptr);
|
||||||
|
|
||||||
|
const auto& attrib = obj_reader.GetAttrib();
|
||||||
|
|
||||||
|
std::vector<float> usernormal = attrib.normals;
|
||||||
|
std::vector<float> usertexcoord = attrib.texcoords;
|
||||||
|
std::vector<int> userface;
|
||||||
|
std::vector<int> userfacenormal;
|
||||||
|
std::vector<int> userfacetexcoord;
|
||||||
|
|
||||||
|
if (!obj_reader.GetShapes().empty()) {
|
||||||
|
const auto& obj_mesh = obj_reader.GetShapes()[0].mesh;
|
||||||
|
|
||||||
|
std::vector<tinyobj::index_t> face_indices;
|
||||||
|
for (size_t face = 0, idx = 0; idx < obj_mesh.indices.size();) {
|
||||||
|
int nfacevert = obj_mesh.num_face_vertices[face];
|
||||||
|
if (nfacevert < 3 || nfacevert > 4) {
|
||||||
|
mju_warning(
|
||||||
|
"obj_decoder: only tri or quad meshes are supported (file '%s')",
|
||||||
|
resource->name);
|
||||||
|
mj_deleteSpec(spec);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
face_indices.push_back(obj_mesh.indices[idx]);
|
||||||
|
face_indices.push_back(obj_mesh.indices[idx + 1]);
|
||||||
|
face_indices.push_back(obj_mesh.indices[idx + 2]);
|
||||||
|
|
||||||
|
if (nfacevert == 4) {
|
||||||
|
face_indices.push_back(obj_mesh.indices[idx]);
|
||||||
|
face_indices.push_back(obj_mesh.indices[idx + 2]);
|
||||||
|
face_indices.push_back(obj_mesh.indices[idx + 3]);
|
||||||
|
}
|
||||||
|
idx += nfacevert;
|
||||||
|
++face;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& mesh_index : face_indices) {
|
||||||
|
userface.push_back(mesh_index.vertex_index);
|
||||||
|
|
||||||
|
if (!usernormal.empty()) {
|
||||||
|
userfacenormal.push_back(mesh_index.normal_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usertexcoord.empty()) {
|
||||||
|
userfacetexcoord.push_back(mesh_index.texcoord_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < usertexcoord.size() / 2; i++) {
|
||||||
|
usertexcoord[2 * i + 1] = 1 - usertexcoord[2 * i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
mjs_setString(mesh->file, resource->name);
|
||||||
|
mjs_setFloat(mesh->uservert, attrib.vertices.data(), attrib.vertices.size());
|
||||||
|
mjs_setFloat(mesh->usernormal, usernormal.data(), usernormal.size());
|
||||||
|
mjs_setFloat(mesh->usertexcoord, usertexcoord.data(), usertexcoord.size());
|
||||||
|
mjs_setInt(mesh->userface, userface.data(), userface.size());
|
||||||
|
mjs_setInt(mesh->userfacenormal, userfacenormal.data(), userfacenormal.size());
|
||||||
|
mjs_setInt(mesh->userfacetexcoord, userfacetexcoord.data(), userfacetexcoord.size());
|
||||||
|
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CanDecode(const mjResource* resource) {
|
||||||
|
std::string_view name(resource->name);
|
||||||
|
return name.ends_with(".obj") || name.ends_with(".OBJ");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
mjPLUGIN_LIB_INIT {
|
||||||
|
mjpDecoder decoder;
|
||||||
|
mjp_defaultDecoder(&decoder);
|
||||||
|
decoder.content_type = "model/obj";
|
||||||
|
decoder.extension = ".obj";
|
||||||
|
decoder.decode = Decode;
|
||||||
|
decoder.can_decode = CanDecode;
|
||||||
|
mjp_registerDecoder(&decoder);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
"""Tests for msh2obj.py."""
|
"""Tests for msh2obj.py."""
|
||||||
|
|
||||||
|
|
||||||
from absl.testing import absltest
|
from absl.testing import absltest
|
||||||
from etils import epath
|
from etils import epath
|
||||||
import mujoco
|
import mujoco
|
||||||
|
|||||||
@@ -38,4 +38,3 @@ set(MUJOCO_USER_SRCS
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_sources(mujoco PRIVATE ${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) {
|
bool mjCMesh::IsSTL(std::string_view filename, std::string_view ct) {
|
||||||
std::string asset_type = GetAssetContentType(filename, 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";
|
return asset_type == "model/vnd.mujoco.msh";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mjCMesh::IsObj() const {
|
|
||||||
return content_type_ == "model/obj";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mjCMesh::IsSTL() const {
|
bool mjCMesh::IsSTL() const {
|
||||||
return content_type_ == "model/stl";
|
return content_type_ == "model/stl";
|
||||||
@@ -639,28 +634,62 @@ bool mjCMesh::IsMSH() const {
|
|||||||
return content_type_ == "model/vnd.mujoco.msh";
|
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
|
// load mesh from resource; throw error on failure
|
||||||
void mjCMesh::LoadFromResource(mjResource* resource, bool remove_repeated) {
|
void mjCMesh::LoadFromResource(mjResource* resource, bool remove_repeated) {
|
||||||
// set content type from resource name
|
// set content type from resource name
|
||||||
std::string asset_type = GetAssetContentType(resource->name, content_type_);
|
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;
|
content_type_ = asset_type;
|
||||||
|
|
||||||
if (IsSTL()) {
|
if (IsSTL()) {
|
||||||
LoadSTL(resource);
|
LoadSTL(resource);
|
||||||
} else if (IsObj()) {
|
|
||||||
LoadOBJ(resource, remove_repeated);
|
|
||||||
} else if (IsMSH()) {
|
} else if (IsMSH()) {
|
||||||
LoadMSH(resource, remove_repeated);
|
LoadMSH(resource, remove_repeated);
|
||||||
} else {
|
} 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
|
// load mesh from cached asset, return true on success
|
||||||
bool mjCMesh::LoadCachedMesh(mjCCache *cache, const mjResource* resource) {
|
bool mjCMesh::LoadCachedMesh(mjCCache *cache, const mjResource* resource) {
|
||||||
auto process_mesh = [&](const void* data) {
|
auto process_mesh = [&](const void* data) {
|
||||||
@@ -1776,7 +1728,7 @@ void mjCMesh::CheckInitialMesh() const {
|
|||||||
|
|
||||||
// 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() && !IsObj()) {
|
facetexcoord_.empty() && content_type_ != "model/obj") {
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -34,10 +35,6 @@
|
|||||||
#include <mujoco/mjtnum.h>
|
#include <mujoco/mjtnum.h>
|
||||||
#include "user/user_cache.h"
|
#include "user/user_cache.h"
|
||||||
#include "user/user_util.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
|
// forward declarations of all mjC/X classes
|
||||||
class mjCError;
|
class mjCError;
|
||||||
@@ -1241,11 +1238,11 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
|
|||||||
// load from OBJ, STL, or MSH file; throws mjCError on failure
|
// load from OBJ, STL, or MSH file; throws mjCError on failure
|
||||||
void LoadFromResource(mjResource* resource, bool remove_repeated = false);
|
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 IsSTL(std::string_view filename, std::string_view ct = "");
|
||||||
static bool IsMSH(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 IsSTL() const;
|
||||||
bool IsMSH() 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 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 LoadSTL(mjResource* resource); // load mesh in STL BIN format
|
||||||
void LoadMSH(mjResource* resource, bool remove_repeated); // load mesh in MSH BIN format
|
void LoadMSH(mjResource* resource, bool remove_repeated); // load mesh in MSH BIN format
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,13 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
mujoco_test(user_model_test ADDITIONAL_LINK_LIBRARIES absl::str_format)
|
mujoco_test(
|
||||||
|
user_model_test
|
||||||
|
PROPERTIES
|
||||||
|
ENVIRONMENT
|
||||||
|
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:obj_decoder>"
|
||||||
|
ADDITIONAL_LINK_LIBRARIES absl::str_format
|
||||||
|
)
|
||||||
|
|
||||||
mujoco_test(user_objects_test)
|
mujoco_test(user_objects_test)
|
||||||
|
|
||||||
@@ -23,9 +29,20 @@ mujoco_test(
|
|||||||
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:elasticity>"
|
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:elasticity>"
|
||||||
)
|
)
|
||||||
|
|
||||||
mujoco_test(user_flex_test)
|
mujoco_test(
|
||||||
|
user_flex_test
|
||||||
|
PROPERTIES
|
||||||
|
ENVIRONMENT
|
||||||
|
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:obj_decoder>"
|
||||||
|
)
|
||||||
|
|
||||||
mujoco_test(user_mesh_test ADDITIONAL_LINK_LIBRARIES absl::str_format)
|
mujoco_test(
|
||||||
|
user_mesh_test
|
||||||
|
PROPERTIES
|
||||||
|
ENVIRONMENT
|
||||||
|
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:obj_decoder>"
|
||||||
|
ADDITIONAL_LINK_LIBRARIES absl::str_format
|
||||||
|
)
|
||||||
|
|
||||||
mujoco_test(user_composite_test)
|
mujoco_test(user_composite_test)
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ static const char* const kMalformedFaceOBJPath =
|
|||||||
static const char* const kCubeSkinPath =
|
static const char* const kCubeSkinPath =
|
||||||
"user/testdata/cube_skin.xml";
|
"user/testdata/cube_skin.xml";
|
||||||
|
|
||||||
|
static const char* const kNoDecoderForMeshErrorMsh =
|
||||||
|
"no decoder found for mesh";
|
||||||
|
|
||||||
using ::testing::ElementsAre;
|
using ::testing::ElementsAre;
|
||||||
using ::testing::HasSubstr;
|
using ::testing::HasSubstr;
|
||||||
using ::testing::IsNull;
|
using ::testing::IsNull;
|
||||||
@@ -110,7 +113,7 @@ TEST_F(MjCMeshTest, UnknownMeshFormat) {
|
|||||||
LoadModelFromString(xml.c_str(), error.data(), error.size(), &vfs);
|
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 or unsupported mesh file: "));
|
EXPECT_THAT(error.data(), HasSubstr(kNoDecoderForMeshErrorMsh));
|
||||||
EXPECT_THAT(error.data(), HasSubstr(name));
|
EXPECT_THAT(error.data(), HasSubstr(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +303,7 @@ TEST_F(MjCMeshTest, LoadMSHWithContentTypeError) {
|
|||||||
// should error with unknown file type
|
// should error with unknown file type
|
||||||
mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
|
mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
|
||||||
EXPECT_THAT(model, IsNull());
|
EXPECT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error, HasSubstr("unsupported mesh type: 'model/unknown'"));
|
EXPECT_THAT(error, HasSubstr(kNoDecoderForMeshErrorMsh));
|
||||||
mj_deleteVFS(&vfs);
|
mj_deleteVFS(&vfs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +330,7 @@ TEST_F(MjCMeshTest, LoadMSHWithInvalidContentType) {
|
|||||||
// should error with unknown file type
|
// should error with unknown file type
|
||||||
mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
|
mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
|
||||||
EXPECT_THAT(model, IsNull());
|
EXPECT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error, HasSubstr("invalid content type: 'model'"));
|
EXPECT_THAT(error, HasSubstr(kNoDecoderForMeshErrorMsh));
|
||||||
mj_deleteVFS(&vfs);
|
mj_deleteVFS(&vfs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,12 @@
|
|||||||
|
|
||||||
mujoco_test(xml_api_test)
|
mujoco_test(xml_api_test)
|
||||||
|
|
||||||
mujoco_test(xml_native_reader_test)
|
mujoco_test(
|
||||||
|
xml_native_reader_test
|
||||||
|
PROPERTIES
|
||||||
|
ENVIRONMENT
|
||||||
|
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:obj_decoder>"
|
||||||
|
)
|
||||||
|
|
||||||
mujoco_test(xml_utils_test)
|
mujoco_test(xml_utils_test)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user