Move STL handling to a mjpDecoder.

PiperOrigin-RevId: 880820344
Change-Id: Ib3a714dd9698335433e518ed6af17d7b8407a8fb
This commit is contained in:
Sam Haves
2026-03-09 06:14:23 -07:00
committed by Copybara-Service
parent 330c36c99b
commit ed0d7ba29f
7 changed files with 203 additions and 83 deletions
+2
View File
@@ -102,6 +102,7 @@ copy_plugins_posix() {
cp lib/libactuator.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
cp lib/libelasticity.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
cp lib/libobj_decoder.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
cp lib/libstl_decoder.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
cp lib/libsensor.* ${TMPDIR}/mujoco_install/mujoco_plugin &&
cp lib/libsdf_plugin.* ${TMPDIR}/mujoco_install/mujoco_plugin
}
@@ -113,6 +114,7 @@ copy_plugins_window() {
cp bin/Release/actuator.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/stl_decoder.dll ${TMPDIR}/mujoco_install/mujoco_plugin &&
cp bin/Release/sensor.dll ${TMPDIR}/mujoco_install/mujoco_plugin
}
+1
View File
@@ -111,6 +111,7 @@ if(NOT EMSCRIPTEN)
add_subdirectory(plugin/sdf)
endif()
add_subdirectory(plugin/obj_decoder)
add_subdirectory(plugin/stl_decoder)
add_subdirectory(src/engine)
add_subdirectory(src/user)
add_subdirectory(src/xml)
+54
View File
@@ -0,0 +1,54 @@
# 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.
if(EMSCRIPTEN)
add_library(stl_decoder OBJECT stl_decoder.cc)
else()
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}")
add_library(stl_decoder SHARED stl_decoder.cc)
endif()
target_include_directories(stl_decoder PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../..
${CMAKE_CURRENT_SOURCE_DIR}/../../include
)
if(EMSCRIPTEN)
target_link_libraries(stl_decoder PRIVATE)
else()
target_link_libraries(stl_decoder PRIVATE
mujoco
)
endif()
target_compile_options(stl_decoder PRIVATE
${AVX_COMPILE_OPTIONS}
${MUJOCO_MACOS_COMPILE_OPTIONS}
${EXTRA_COMPILE_OPTIONS}
${MUJOCO_CXX_FLAGS}
)
if(NOT EMSCRIPTEN)
target_link_options(stl_decoder PRIVATE
${MUJOCO_MACOS_LINK_OPTIONS}
${EXTRA_LINK_OPTIONS}
)
install(
TARGETS stl_decoder
LIBRARY DESTINATION "${CMAKE_INSTALL_BINDIR}/mujoco_plugin"
)
endif()
+143
View File
@@ -0,0 +1,143 @@
// 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 <cmath>
#include <cstring>
#include <map>
#include <string_view>
#include <vector>
#include <mujoco/mjplugin.h>
#include <mujoco/mjspec.h>
#include <mujoco/mujoco.h>
namespace {
template <typename T>
void ReadFromBuffer(T* dst, const char* src) {
std::memcpy(dst, src, sizeof(T));
}
struct Vec3Key {
int x, y, z;
bool operator<(const Vec3Key& o) const {
if (x != o.x) return x < o.x;
if (y != o.y) return y < o.y;
return z < o.z;
}
};
Vec3Key FloatToKey(const float v[3]) {
int x, y, z;
std::memcpy(&x, &v[0], sizeof(int));
std::memcpy(&y, &v[1], sizeof(int));
std::memcpy(&z, &v[2], sizeof(int));
return {x, y, z};
}
mjSpec* Decode(mjResource* resource, const mjVFS* vfs) {
const void* bytes = nullptr;
int buffer_sz = mju_readResource(resource, &bytes);
if (buffer_sz < 0) {
mju_warning("stl_decoder: could not read STL file '%s'", resource->name);
return nullptr;
}
if (!buffer_sz) {
mju_warning("stl_decoder: STL file '%s' is empty", resource->name);
return nullptr;
}
const char* buffer = static_cast<const char*>(bytes);
if (buffer_sz < 84) {
mju_warning("stl_decoder: invalid header in STL file '%s'", resource->name);
return nullptr;
}
int nfaces = 0;
ReadFromBuffer(&nfaces, buffer + 80);
if (nfaces < 1 || nfaces > 200000) {
mju_warning(
"stl_decoder: number of faces should be between 1 and 200000 in STL "
"file '%s'; perhaps this is an ASCII file?",
resource->name);
return nullptr;
}
if (nfaces * 50 != buffer_sz - 84) {
mju_warning(
"stl_decoder: STL file '%s' has wrong size; perhaps this is an ASCII "
"file?",
resource->name);
return nullptr;
}
const char* stl = buffer + 84;
std::vector<float> uservert;
std::vector<int> userface(3 * nfaces, 0);
std::map<Vec3Key, int> vertmap;
for (int i = 0; i < nfaces; i++) {
for (int j = 0; j < 3; j++) {
float v[3];
ReadFromBuffer(&v, stl + 50 * i + 12 * (j + 1));
if (std::fabs(v[0]) > std::pow(2, 30) ||
std::fabs(v[1]) > std::pow(2, 30) ||
std::fabs(v[2]) > std::pow(2, 30)) {
mju_warning(
"stl_decoder: vertex in STL file '%s' "
"exceeds maximum bounds",
resource->name);
return nullptr;
}
Vec3Key key = FloatToKey(v);
auto [it, inserted] = vertmap.emplace(key, uservert.size() / 3);
if (inserted) {
uservert.push_back(v[0]);
uservert.push_back(v[1]);
uservert.push_back(v[2]);
}
userface[3 * i + j] = it->second;
}
}
mjSpec* spec = mj_makeSpec();
mjsMesh* mesh = mjs_addMesh(spec, nullptr);
mjs_setString(mesh->file, resource->name);
mjs_setFloat(mesh->uservert, uservert.data(), uservert.size());
mjs_setInt(mesh->userface, userface.data(), userface.size());
return spec;
}
int CanDecode(const mjResource* resource) {
std::string_view name(resource->name);
return name.ends_with(".stl") || name.ends_with(".STL");
}
} // namespace
mjPLUGIN_LIB_INIT {
mjpDecoder decoder;
mjp_defaultDecoder(&decoder);
decoder.content_type = "model/stl";
decoder.extension = ".stl";
decoder.decode = Decode;
decoder.can_decode = CanDecode;
mjp_registerDecoder(&decoder);
}
+1 -81
View File
@@ -614,10 +614,6 @@ void mjCMesh::ProcessVertices(const std::vector<float>& vert, bool remove_repeat
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);
@@ -626,9 +622,6 @@ bool mjCMesh::IsMSH(std::string_view filename, std::string_view ct) {
bool mjCMesh::IsSTL() const {
return content_type_ == "model/stl";
}
bool mjCMesh::IsMSH() const {
return content_type_ == "model/vnd.mujoco.msh";
@@ -684,9 +677,7 @@ void mjCMesh::LoadFromResource(mjResource* resource, bool remove_repeated) {
std::string asset_type = GetAssetContentType(resource->name, content_type_);
content_type_ = asset_type;
if (IsSTL()) {
LoadSTL(resource);
} else if (IsMSH()) {
if (IsMSH()) {
LoadMSH(resource, remove_repeated);
} else {
LoadFromDecoder(resource, remove_repeated);
@@ -1115,77 +1106,6 @@ bool mjCMesh::LoadCachedMesh(mjCCache *cache, const mjResource* resource) {
// load STL binary mesh
void mjCMesh::LoadSTL(mjResource* resource) {
bool righthand = scale[0] * scale[1] * scale[2] > 0;
// get file data in buffer
char* buffer = 0;
int buffer_sz = mju_readResource(resource, (const void**)&buffer);
// still not found
if (buffer_sz < 0) {
throw mjCError(this, "could not read STL file '%s'", resource->name);
} else if (!buffer_sz) {
throw mjCError(this, "STL file '%s' is empty", resource->name);
}
// make sure there is enough data for header
if (buffer_sz < 84) {
throw mjCError(this, "invalid header in STL file '%s'", resource->name);
}
// get number of triangles, check bounds
int nfaces = 0;
ReadFromBuffer(&nfaces, buffer + 80);
if (nfaces < 1 || nfaces > 200000) {
throw mjCError(this,
"number of faces should be between 1 and 200000 in STL file '%s';"
" perhaps this is an ASCII file?", resource->name);
}
// check remaining buffer size
if (nfaces*50 != buffer_sz-84) {
throw mjCError(this,
"STL file '%s' has wrong size; perhaps this is an ASCII file?",
resource->name);
}
// assign stl data pointer
const char* stl = buffer + 84;
// allocate face and vertex data
face_.assign(3*nfaces, 0);
std::vector<float> vert;
// add vertices and faces, including repeated for now
for (int i=0; i < nfaces; i++) {
for (int j=0; j < 3; j++) {
// read vertex coordinates
float v[3];
ReadFromBuffer(&v, stl + 50*i + 12*(j + 1));
// check if vertex can be cast to an int safely
if (fabs(v[0]) > pow(2, 30) || fabs(v[1]) > pow(2, 30) || fabs(v[2]) > pow(2, 30)) {
throw mjCError(this, "vertex in STL file '%s' exceed maximum bounds", resource->name);
}
// add vertex address in face; change order if scale makes it lefthanded
if (righthand || j == 0) {
face_[3*i + j] = vert.size() / 3;
} else {
face_[3*i + 3 - j] = vert.size() / 3;
}
// add vertex data
vert.push_back(v[0]);
vert.push_back(v[1]);
vert.push_back(v[2]);
}
}
ProcessVertices(vert, true);
}
// load MSH binary mesh
+1 -1
View File
@@ -60,6 +60,6 @@ set_target_properties(mujoco_wasm PROPERTIES
OUTPUT_NAME "mujoco"
)
target_link_libraries(mujoco_wasm ccd lodepng mujoco tinyxml2 qhullstatic_r obj_decoder)
target_link_libraries(mujoco_wasm ccd lodepng mujoco tinyxml2 qhullstatic_r obj_decoder stl_decoder)
install(TARGETS mujoco_wasm DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})
+1 -1
View File
@@ -53,6 +53,6 @@ add_executable(mujoco_wasm_benchmark ${MUJOCO_WASM_FILES})
set_target_properties(mujoco_wasm_benchmark PROPERTIES LINK_FLAGS "${EMCC_LINKER_FLAGS_STR}")
target_link_libraries(mujoco_wasm_benchmark ccd lodepng mujoco tinyxml2 qhullstatic_r obj_decoder)
target_link_libraries(mujoco_wasm_benchmark ccd lodepng mujoco tinyxml2 qhullstatic_r obj_decoder stl_decoder)
install(TARGETS mujoco_wasm_benchmark DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})