Merge branch 'google-deepmind:main' into unity/passive-resubmit
This commit is contained in:
@@ -222,7 +222,7 @@ package_wasm() {
|
||||
cp wasm/package.npm.json wasm/dist/package.json
|
||||
cp wasm/README.md wasm/dist/README.md
|
||||
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
VERSION="${VERSION:-${GITHUB_REF#refs/tags/}}"
|
||||
npm --prefix wasm/dist version "${VERSION}" --no-git-tag-version
|
||||
|
||||
echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ~/.npmrc
|
||||
|
||||
@@ -5,10 +5,25 @@ on:
|
||||
tags:
|
||||
- '[0-9]*.[0-9]*.[0-9]*'
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_version:
|
||||
description: 'Release tag version to deploy'
|
||||
required: true
|
||||
default: '0.0.0'
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish MuJoCo WASM package
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
env:
|
||||
VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_version || github.ref_name }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -32,5 +47,5 @@ jobs:
|
||||
- name: Package WASM bindings
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
VERSION: ${{ env.VERSION }}
|
||||
run: bash ./.github/workflows/build_steps.sh package_wasm
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
|
||||
|
||||
project(
|
||||
mujoco
|
||||
VERSION 3.6.0
|
||||
VERSION 3.7.0
|
||||
DESCRIPTION "MuJoCo Physics Simulator"
|
||||
HOMEPAGE_URL "https://mujoco.org"
|
||||
)
|
||||
|
||||
Vendored
+4
-4
@@ -1,6 +1,6 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 3,6,0,0
|
||||
PRODUCTVERSION 3,6,0,0
|
||||
FILEVERSION 3,7,0,0
|
||||
PRODUCTVERSION 3,7,0,0
|
||||
FILEOS 0x4
|
||||
FILETYPE 0x1
|
||||
{
|
||||
@@ -9,9 +9,9 @@ FILETYPE 0x1
|
||||
BLOCK "040904b0"
|
||||
{
|
||||
VALUE "ProductName", "MuJoCo"
|
||||
VALUE "ProductVersion", "3.6.0"
|
||||
VALUE "ProductVersion", "3.7.0"
|
||||
VALUE "FileDescription", "MuJoCo"
|
||||
VALUE "FileVersion", "3.6.0"
|
||||
VALUE "FileVersion", "3.7.0"
|
||||
VALUE "InternalName", "mujoco.dll"
|
||||
VALUE "OriginalFilename", "mujoco.dll"
|
||||
VALUE "CompanyName", "Google DeepMind"
|
||||
|
||||
Vendored
+4
-4
@@ -1,8 +1,8 @@
|
||||
MUJOCO ICON "mujoco.ico"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 3,6,0,0
|
||||
PRODUCTVERSION 3,6,0,0
|
||||
FILEVERSION 3,7,0,0
|
||||
PRODUCTVERSION 3,7,0,0
|
||||
FILEOS 0x4
|
||||
FILETYPE 0x1
|
||||
{
|
||||
@@ -11,9 +11,9 @@ FILETYPE 0x1
|
||||
BLOCK "040904b0"
|
||||
{
|
||||
VALUE "ProductName", "MuJoCo"
|
||||
VALUE "ProductVersion", "3.6.0"
|
||||
VALUE "ProductVersion", "3.7.0"
|
||||
VALUE "FileDescription", "MuJoCo"
|
||||
VALUE "FileVersion", "3.6.0"
|
||||
VALUE "FileVersion", "3.7.0"
|
||||
VALUE "InternalName", "simulate.exe"
|
||||
VALUE "OriginalFilename", "simulate.exe"
|
||||
VALUE "CompanyName", "Google DeepMind"
|
||||
|
||||
@@ -388,7 +388,7 @@ Defined in `mujoco.h <https://github.com/google-deepmind/mujoco/blob/main/includ
|
||||
- value
|
||||
- description
|
||||
* - ``mjVERSION_HEADER``
|
||||
- 3006000
|
||||
- 3007000
|
||||
- The version of the MuJoCo headers. This is an integer calculated from the version string "S.M.P"
|
||||
using the formula ``(S * 1e6) + (M * 1e3) + P``. For example, version 4.2.1 is represented as 4002001.
|
||||
The API function :ref:`mj_version` returns a number with the same meaning
|
||||
|
||||
@@ -3293,8 +3293,8 @@ void mj_objectVelocity(const mjModel* m, const mjData* d,
|
||||
int objtype, int objid, mjtNum res[6], int flg_local);
|
||||
void mj_objectAcceleration(const mjModel* m, const mjData* d,
|
||||
int objtype, int objid, mjtNum res[6], int flg_local);
|
||||
mjtNum mj_geomDistance(const mjModel* m, const mjData* d, int geom1, int geom2,
|
||||
mjtNum distmax, mjtNum fromto[6]);
|
||||
mjtNum mj_geomDistance(const mjModel* m, mjData* d, int geom1, int geom2, mjtNum distmax,
|
||||
mjtNum fromto[6]);
|
||||
void mj_contactForce(const mjModel* m, const mjData* d, int id, mjtNum result[6]);
|
||||
void mj_differentiatePos(const mjModel* m, mjtNum* qvel, mjtNum dt,
|
||||
const mjtNum* qpos1, const mjtNum* qpos2);
|
||||
|
||||
@@ -216,6 +216,8 @@ Benchmark an environment with _`testspeed`
|
||||
|
||||
mjwarp-testspeed benchmark/humanoid/humanoid.xml
|
||||
|
||||
.. _mjwViewer:
|
||||
|
||||
Interactive environment simulation with MJWarp
|
||||
|
||||
.. code-block:: shell
|
||||
@@ -489,7 +491,7 @@ Key features:
|
||||
`Warp's BVHs <https://nvidia.github.io/warp/api_reference/_generated/warp.Bvh.html#warp.Bvh>`__.
|
||||
|
||||
Basic Usage
|
||||
----------
|
||||
-----------
|
||||
|
||||
Rendering or raycasting requires a :class:`mjw.RenderContext <mujoco_warp.RenderContext>` which contains BVH structures,
|
||||
rendering specific fields, and output buffers.
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ Notice that we pass two extra arguments to ``mjx.make_data``:
|
||||
|
||||
* ``naconmax`` defines the maximum number of contacts for all worlds combined.
|
||||
* ``njmax`` defines the maximum number of constraints per world. If you are developing a new scene, these parameters
|
||||
should be tuned by loading them in the :ref:`viewer <MJW_Cli>` and increasing the values accordingly as overflows
|
||||
should be tuned by loading them in the :ref:`viewer <mjwViewer>` and increasing the values accordingly as overflows
|
||||
occur. Scale ``naconmax`` by the number of environments you'll eventually need in a ``jax.vmap``!
|
||||
|
||||
Contacts
|
||||
|
||||
+2
-2
@@ -37,14 +37,14 @@ _____
|
||||
|
||||
The MuJoCo app needs to be run at least once before the native library can be used, in order to register the library as
|
||||
a trusted binary. Then, copy the dynamic library file from
|
||||
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.6.0.dylib`` (it can be
|
||||
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.7.0.dylib`` (it can be
|
||||
found by browsing the contents of ``MuJoCo.app``) and rename it as ``mujoco.dylib``.
|
||||
|
||||
Linux
|
||||
_____
|
||||
|
||||
Expand the ``tar.gz`` archive to ``~/.mujoco``. Then copy the dynamic library from
|
||||
``~/.mujoco/mujoco-3.6.0/lib/libmujoco.so.3.6.0`` and rename it as ``libmujoco.so``.
|
||||
``~/.mujoco/mujoco-3.7.0/lib/libmujoco.so.3.7.0`` and rename it as ``libmujoco.so``.
|
||||
|
||||
Windows
|
||||
_______
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#define MUJOCO_MUJOCO_H_
|
||||
|
||||
// header version; should match the library version as returned by mj_version()
|
||||
#define mjVERSION_HEADER 3006000
|
||||
#define mjVERSION_HEADER 3007000
|
||||
|
||||
// needed to define size_t, fabs and log10
|
||||
#include <stdlib.h>
|
||||
@@ -609,8 +609,8 @@ MJAPI void mj_objectAcceleration(const mjModel* m, const mjData* d,
|
||||
|
||||
// Return smallest signed distance between two geoms and optionally segment from geom1 to geom2.
|
||||
// Nullable: fromto
|
||||
MJAPI mjtNum mj_geomDistance(const mjModel* m, const mjData* d, int geom1, int geom2,
|
||||
mjtNum distmax, mjtNum fromto[6]);
|
||||
MJAPI mjtNum mj_geomDistance(const mjModel* m, mjData* d, int geom1, int geom2, mjtNum distmax,
|
||||
mjtNum fromto[6]);
|
||||
|
||||
// Extract 6D force:torque given contact id, in the contact frame.
|
||||
MJAPI void mj_contactForce(const mjModel* m, const mjData* d, int id, mjtNum result[6]);
|
||||
|
||||
+4
-4
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name="mujoco-mjx"
|
||||
version = "3.6.0"
|
||||
version = "3.7.0"
|
||||
authors = [
|
||||
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
|
||||
]
|
||||
@@ -29,7 +29,7 @@ dependencies = [
|
||||
"etils[epath]",
|
||||
"jax",
|
||||
"jaxlib",
|
||||
"mujoco>=3.6.0.dev0",
|
||||
"mujoco>=3.7.0.dev0",
|
||||
"scipy",
|
||||
"trimesh",
|
||||
]
|
||||
@@ -45,9 +45,9 @@ mjx-viewer = "mujoco.mjx.viewer:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
|
||||
Documentation = "https://mujoco.readthedocs.io/en/3.6.0"
|
||||
Documentation = "https://mujoco.readthedocs.io/en/3.7.0"
|
||||
Repository = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
|
||||
Changelog = "https://mujoco.readthedocs.io/en/3.6.0/changelog.html"
|
||||
Changelog = "https://mujoco.readthedocs.io/en/3.7.0/changelog.html"
|
||||
|
||||
[tool.isort]
|
||||
force_single_line = true
|
||||
|
||||
@@ -86,7 +86,7 @@ if(NOT TARGET mujoco)
|
||||
if(MUJOCO_FRAMEWORK)
|
||||
message("MuJoCo framework is at ${MUJOCO_FRAMEWORK}/mujoco.framework")
|
||||
set(MUJOCO_LIBRARY
|
||||
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.6.0.dylib
|
||||
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.7.0.dylib
|
||||
)
|
||||
target_compile_options(mujoco INTERFACE -F${MUJOCO_FRAMEWORK})
|
||||
endif()
|
||||
@@ -94,7 +94,7 @@ if(NOT TARGET mujoco)
|
||||
|
||||
if(NOT MUJOCO_FRAMEWORK)
|
||||
find_library(
|
||||
MUJOCO_LIBRARY mujoco mujoco.3.6.0 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
|
||||
MUJOCO_LIBRARY mujoco mujoco.3.7.0 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
|
||||
)
|
||||
find_path(MUJOCO_INCLUDE mujoco/mujoco.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED)
|
||||
message("MuJoCo is at ${MUJOCO_LIBRARY}")
|
||||
|
||||
@@ -663,7 +663,7 @@ PYBIND11_MODULE(_functions, pymodule) {
|
||||
Def<traits::mj_contactForce>(pymodule);
|
||||
Def<traits::mj_geomDistance>(
|
||||
pymodule,
|
||||
[](const raw::MjModel* m, const raw::MjData* d,
|
||||
[](const raw::MjModel* m, raw::MjData* d,
|
||||
int geom1, int geom2, mjtNum distmax,
|
||||
std::optional<Eigen::Ref<EigenArrayXX>> fromto) {
|
||||
if (fromto.has_value() && fromto->size() != 6) {
|
||||
|
||||
@@ -3626,7 +3626,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
FunctionParameterDecl(
|
||||
name='d',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjData', is_const=True),
|
||||
inner_type=ValueType(name='mjData'),
|
||||
),
|
||||
),
|
||||
FunctionParameterDecl(
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.mujoco.mjpython</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3.6.0</string>
|
||||
<string>3.7.0</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>3.6.0</string>
|
||||
<string>3.7.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>3.6.0</string>
|
||||
<string>3.7.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.6.0</string>
|
||||
<string>3.7.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>mjpython</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "mujoco"
|
||||
version = "3.6.0"
|
||||
version = "3.7.0"
|
||||
authors = [
|
||||
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
|
||||
]
|
||||
@@ -34,9 +34,9 @@ dynamic = ["readme", "scripts"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/google-deepmind/mujoco"
|
||||
Documentation = "https://mujoco.readthedocs.io/en/3.6.0"
|
||||
Documentation = "https://mujoco.readthedocs.io/en/3.7.0"
|
||||
Repository = "https://github.com/google-deepmind/mujoco"
|
||||
Changelog = "https://mujoco.readthedocs.io/en/3.6.0/changelog.html"
|
||||
Changelog = "https://mujoco.readthedocs.io/en/3.7.0/changelog.html"
|
||||
|
||||
[tool.setuptools]
|
||||
include-package-data = false
|
||||
|
||||
@@ -24,7 +24,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
|
||||
|
||||
project(
|
||||
mujoco_samples
|
||||
VERSION 3.6.0
|
||||
VERSION 3.7.0
|
||||
DESCRIPTION "MuJoCo samples binaries"
|
||||
HOMEPAGE_URL "https://mujoco.org"
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ set(MUJOCO_DEP_VERSION_lodepng
|
||||
|
||||
project(
|
||||
mujoco_simulate
|
||||
VERSION 3.6.0
|
||||
VERSION 3.7.0
|
||||
DESCRIPTION "MuJoCo simulate binaries"
|
||||
HOMEPAGE_URL "https://mujoco.org"
|
||||
)
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
|
||||
//-------------------------- Constants -------------------------------------------------------------
|
||||
|
||||
#define mjVERSION 3006000
|
||||
#define mjVERSIONSTRING "3.6.0"
|
||||
#define mjVERSION 3007000
|
||||
#define mjVERSIONSTRING "3.7.0"
|
||||
|
||||
// names of disable flags
|
||||
const char* mjDISABLESTRING[mjNDISABLE] = {
|
||||
@@ -524,7 +524,7 @@ void mj_xfrcAccumulate(const mjModel* m, mjData* d, mjtNum* qfrc) {
|
||||
//-------------------------- miscellaneous ---------------------------------------------------------
|
||||
|
||||
// returns the smallest distance between two geoms (using nativeccd)
|
||||
static mjtNum mj_geomDistanceCCD(const mjModel* m, const mjData* d, int g1, int g2,
|
||||
static mjtNum mj_geomDistanceCCD(const mjModel* m, mjData* d, int g1, int g2,
|
||||
mjtNum distmax, mjtNum fromto[6]) {
|
||||
mjCCDConfig config;
|
||||
mjCCDStatus status;
|
||||
@@ -553,7 +553,7 @@ static mjtNum mj_geomDistanceCCD(const mjModel* m, const mjData* d, int g1, int
|
||||
|
||||
|
||||
// returns the smallest distance between two geoms
|
||||
mjtNum mj_geomDistance(const mjModel* m, const mjData* d, int geom1, int geom2, mjtNum distmax,
|
||||
mjtNum mj_geomDistance(const mjModel* m, mjData* d, int geom1, int geom2, mjtNum distmax,
|
||||
mjtNum fromto[6]) {
|
||||
mjContact con[mjMAXCONPAIR];
|
||||
mjtNum dist = distmax;
|
||||
|
||||
@@ -86,8 +86,8 @@ void mj_xfrcAccumulate(const mjModel* m, mjData* d, mjtNum* qfrc);
|
||||
//-------------------------- miscellaneous ---------------------------------------------------------
|
||||
|
||||
// returns the smallest distance between two geoms
|
||||
MJAPI mjtNum mj_geomDistance(const mjModel* m, const mjData* d, int geom1, int geom2,
|
||||
mjtNum distmax, mjtNum fromto[6]);
|
||||
MJAPI mjtNum mj_geomDistance(const mjModel* m, mjData* d, int geom1, int geom2, mjtNum distmax,
|
||||
mjtNum fromto[6]);
|
||||
|
||||
// compute velocity by finite-differencing two positions
|
||||
MJAPI void mj_differentiatePos(const mjModel* m, mjtNum* qvel, mjtNum dt,
|
||||
|
||||
@@ -140,6 +140,19 @@ void FilamentContext::Render(const mjrRect& viewport, const mjvScene* scene,
|
||||
mju_error("Unexpected context.");
|
||||
}
|
||||
|
||||
// If we're rendering to the window, and the window size has changed, we need
|
||||
// to reacquire the swap chain.
|
||||
if (scene_swap_chain_target_ == kWindowSwapChain &&
|
||||
(viewport.width != window_width_ || viewport.height != window_height_)) {
|
||||
if (window_width_ != 0 && window_height_ != 0) {
|
||||
engine_->flushAndWait();
|
||||
engine_->destroy(window_swap_chain_);
|
||||
window_swap_chain_ = engine_->createSwapChain(config_.native_window);
|
||||
}
|
||||
window_width_ = viewport.width;
|
||||
window_height_ = viewport.height;
|
||||
}
|
||||
|
||||
scene_view_->SetViewport(viewport);
|
||||
scene_view_->UpdateScene(con, scene);
|
||||
// Update the UX renderable entity after processing the scene in case there
|
||||
|
||||
@@ -89,6 +89,8 @@ class FilamentContext {
|
||||
std::unique_ptr<ObjectManager> object_manager_;
|
||||
std::unique_ptr<SceneView> scene_view_;
|
||||
std::unique_ptr<GuiView> gui_view_;
|
||||
int window_width_ = 0;
|
||||
int window_height_ = 0;
|
||||
};
|
||||
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -74,11 +74,11 @@ class ZipArchiveProvider : public mjpResourceProvider {
|
||||
|
||||
// Look for the root XML model in the archive. First look for an XML file
|
||||
// with the same name as the archive itself. Failing that, look for an XML
|
||||
// file with the same name including the parent directory.
|
||||
// file within a subdirectory with the same name as the archive.
|
||||
const std::filesystem::path path(name_);
|
||||
root_model_ = (path / path.stem()).string() + ".xml";
|
||||
if (!Contains(root_model_)) {
|
||||
root_model_ = (path / path.parent_path() / path.stem()).string() + ".xml";
|
||||
root_model_ = (path / path.stem() / path.stem()).string() + ".xml";
|
||||
if (!Contains(root_model_)) {
|
||||
SetError(error, error_sz, "Zip error: no root XML file found.");
|
||||
return;
|
||||
|
||||
+342
-166
@@ -26,6 +26,7 @@
|
||||
#include <implot.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "experimental/platform/helpers.h"
|
||||
#include "experimental/platform/interaction.h"
|
||||
#include "experimental/platform/imgui_widgets.h"
|
||||
|
||||
namespace mujoco::platform {
|
||||
@@ -41,70 +42,70 @@ void SetupTheme(GuiTheme theme) {
|
||||
ImVec4* c = s.Colors;
|
||||
if (theme == GuiTheme::kDark) {
|
||||
ImGui::StyleColorsDark(&s);
|
||||
c[ImGuiCol_Text] = ImVec4(1.00, 1.00, 1.00, 1.00);
|
||||
c[ImGuiCol_TextDisabled] = ImVec4(0.40, 0.40, 0.40, 1.00);
|
||||
c[ImGuiCol_ChildBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_WindowBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_PopupBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_Border] = ImVec4(0.12, 0.12, 0.12, 0.71);
|
||||
c[ImGuiCol_BorderShadow] = ImVec4(1.00, 1.00, 1.00, 0.06);
|
||||
c[ImGuiCol_FrameBg] = ImVec4(0.42, 0.42, 0.42, 0.54);
|
||||
c[ImGuiCol_FrameBgHovered] = ImVec4(0.42, 0.42, 0.42, 0.40);
|
||||
c[ImGuiCol_FrameBgActive] = ImVec4(0.56, 0.56, 0.56, 0.67);
|
||||
c[ImGuiCol_TitleBg] = ImVec4(0.19, 0.19, 0.19, 1.00);
|
||||
c[ImGuiCol_TitleBgActive] = ImVec4(0.22, 0.22, 0.22, 1.00);
|
||||
c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.17, 0.17, 0.17, 0.90);
|
||||
c[ImGuiCol_MenuBarBg] = ImVec4(0.34, 0.34, 0.34, 1.00);
|
||||
c[ImGuiCol_ScrollbarBg] = ImVec4(0.24, 0.24, 0.24, 0.53);
|
||||
c[ImGuiCol_ScrollbarGrab] = ImVec4(0.41, 0.41, 0.41, 1.00);
|
||||
c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.52, 0.52, 0.52, 1.00);
|
||||
c[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.76, 0.76, 0.76, 1.00);
|
||||
c[ImGuiCol_CheckMark] = ImVec4(0.65, 0.65, 0.65, 1.00);
|
||||
c[ImGuiCol_SliderGrab] = ImVec4(0.52, 0.52, 0.52, 1.00);
|
||||
c[ImGuiCol_SliderGrabActive] = ImVec4(0.64, 0.64, 0.64, 1.00);
|
||||
c[ImGuiCol_Button] = ImVec4(0.54, 0.54, 0.54, 0.35);
|
||||
c[ImGuiCol_ButtonHovered] = ImVec4(0.52, 0.52, 0.52, 0.59);
|
||||
c[ImGuiCol_ButtonActive] = ImVec4(0.76, 0.76, 0.76, 1.00);
|
||||
c[ImGuiCol_Header] = ImVec4(0.38, 0.38, 0.38, 1.00);
|
||||
c[ImGuiCol_HeaderHovered] = ImVec4(0.47, 0.47, 0.47, 1.00);
|
||||
c[ImGuiCol_HeaderActive] = ImVec4(0.76, 0.76, 0.76, 0.77);
|
||||
c[ImGuiCol_Separator] = ImVec4(0.00, 0.00, 0.00, 0.18);
|
||||
c[ImGuiCol_SeparatorHovered] = ImVec4(0.70, 0.67, 0.60, 0.29);
|
||||
c[ImGuiCol_SeparatorActive] = ImVec4(0.70, 0.67, 0.60, 0.67);
|
||||
c[ImGuiCol_ResizeGrip] = ImVec4(0.26, 0.59, 0.98, 0.25);
|
||||
c[ImGuiCol_ResizeGripHovered] = ImVec4(0.26, 0.59, 0.98, 0.67);
|
||||
c[ImGuiCol_ResizeGripActive] = ImVec4(0.26, 0.59, 0.98, 0.95);
|
||||
c[ImGuiCol_PlotLines] = ImVec4(0.61, 0.61, 0.61, 1.00);
|
||||
c[ImGuiCol_PlotLinesHovered] = ImVec4(1.00, 0.43, 0.35, 1.00);
|
||||
c[ImGuiCol_PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00);
|
||||
c[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00, 0.60, 0.00, 1.00);
|
||||
c[ImGuiCol_TextSelectedBg] = ImVec4(0.73, 0.73, 0.73, 0.35);
|
||||
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80, 0.80, 0.80, 0.35);
|
||||
c[ImGuiCol_DragDropTarget] = ImVec4(1.00, 1.00, 0.00, 0.90);
|
||||
c[ImGuiCol_NavHighlight] = ImVec4(0.26, 0.59, 0.98, 1.00);
|
||||
c[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00, 1.00, 1.00, 0.70);
|
||||
c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80, 0.80, 0.80, 0.20);
|
||||
c[ImGuiCol_DockingEmptyBg] = ImVec4(0.38, 0.38, 0.38, 1.00);
|
||||
c[ImGuiCol_Tab] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_TabHovered] = ImVec4(0.40, 0.40, 0.40, 1.00);
|
||||
c[ImGuiCol_TabActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_TabUnfocused] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_TabUnfocusedActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_DockingPreview] = ImVec4(0.85, 0.85, 0.85, 0.28);
|
||||
c[ImGuiCol_Text] = ImVec4(1.00, 1.00, 1.00, 1.00);
|
||||
c[ImGuiCol_TextDisabled] = ImVec4(0.40, 0.40, 0.40, 1.00);
|
||||
c[ImGuiCol_ChildBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_WindowBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_PopupBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_Border] = ImVec4(0.12, 0.12, 0.12, 0.71);
|
||||
c[ImGuiCol_BorderShadow] = ImVec4(1.00, 1.00, 1.00, 0.06);
|
||||
c[ImGuiCol_FrameBg] = ImVec4(0.42, 0.42, 0.42, 0.54);
|
||||
c[ImGuiCol_FrameBgHovered] = ImVec4(0.42, 0.42, 0.42, 0.40);
|
||||
c[ImGuiCol_FrameBgActive] = ImVec4(0.56, 0.56, 0.56, 0.67);
|
||||
c[ImGuiCol_TitleBg] = ImVec4(0.19, 0.19, 0.19, 1.00);
|
||||
c[ImGuiCol_TitleBgActive] = ImVec4(0.22, 0.22, 0.22, 1.00);
|
||||
c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.17, 0.17, 0.17, 0.90);
|
||||
c[ImGuiCol_MenuBarBg] = ImVec4(0.34, 0.34, 0.34, 1.00);
|
||||
c[ImGuiCol_ScrollbarBg] = ImVec4(0.24, 0.24, 0.24, 0.53);
|
||||
c[ImGuiCol_ScrollbarGrab] = ImVec4(0.41, 0.41, 0.41, 1.00);
|
||||
c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.52, 0.52, 0.52, 1.00);
|
||||
c[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.76, 0.76, 0.76, 1.00);
|
||||
c[ImGuiCol_CheckMark] = ImVec4(0.65, 0.65, 0.65, 1.00);
|
||||
c[ImGuiCol_SliderGrab] = ImVec4(0.52, 0.52, 0.52, 1.00);
|
||||
c[ImGuiCol_SliderGrabActive] = ImVec4(0.64, 0.64, 0.64, 1.00);
|
||||
c[ImGuiCol_Button] = ImVec4(0.54, 0.54, 0.54, 0.35);
|
||||
c[ImGuiCol_ButtonHovered] = ImVec4(0.52, 0.52, 0.52, 0.59);
|
||||
c[ImGuiCol_ButtonActive] = ImVec4(0.76, 0.76, 0.76, 1.00);
|
||||
c[ImGuiCol_Header] = ImVec4(0.38, 0.38, 0.38, 1.00);
|
||||
c[ImGuiCol_HeaderHovered] = ImVec4(0.47, 0.47, 0.47, 1.00);
|
||||
c[ImGuiCol_HeaderActive] = ImVec4(0.76, 0.76, 0.76, 0.77);
|
||||
c[ImGuiCol_Separator] = ImVec4(0.00, 0.00, 0.00, 0.18);
|
||||
c[ImGuiCol_SeparatorHovered] = ImVec4(0.70, 0.67, 0.60, 0.29);
|
||||
c[ImGuiCol_SeparatorActive] = ImVec4(0.70, 0.67, 0.60, 0.67);
|
||||
c[ImGuiCol_ResizeGrip] = ImVec4(0.26, 0.59, 0.98, 0.25);
|
||||
c[ImGuiCol_ResizeGripHovered] = ImVec4(0.26, 0.59, 0.98, 0.67);
|
||||
c[ImGuiCol_ResizeGripActive] = ImVec4(0.26, 0.59, 0.98, 0.95);
|
||||
c[ImGuiCol_PlotLines] = ImVec4(0.61, 0.61, 0.61, 1.00);
|
||||
c[ImGuiCol_PlotLinesHovered] = ImVec4(1.00, 0.43, 0.35, 1.00);
|
||||
c[ImGuiCol_PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00);
|
||||
c[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00, 0.60, 0.00, 1.00);
|
||||
c[ImGuiCol_TextSelectedBg] = ImVec4(0.73, 0.73, 0.73, 0.35);
|
||||
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80, 0.80, 0.80, 0.35);
|
||||
c[ImGuiCol_DragDropTarget] = ImVec4(1.00, 1.00, 0.00, 0.90);
|
||||
c[ImGuiCol_NavHighlight] = ImVec4(0.26, 0.59, 0.98, 1.00);
|
||||
c[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00, 1.00, 1.00, 0.70);
|
||||
c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80, 0.80, 0.80, 0.20);
|
||||
c[ImGuiCol_DockingEmptyBg] = ImVec4(0.38, 0.38, 0.38, 1.00);
|
||||
c[ImGuiCol_Tab] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_TabHovered] = ImVec4(0.40, 0.40, 0.40, 1.00);
|
||||
c[ImGuiCol_TabActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_TabUnfocused] = ImVec4(0.25, 0.25, 0.25, 1.00);
|
||||
c[ImGuiCol_TabUnfocusedActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
|
||||
c[ImGuiCol_DockingPreview] = ImVec4(0.85, 0.85, 0.85, 0.28);
|
||||
c[ImGuiCol_WindowBg].w = 1.0f;
|
||||
} else if (theme == GuiTheme::kLight) {
|
||||
ImGui::StyleColorsLight(&s);
|
||||
ImVec4 white = ImVec4(1.00, 1.00, 1.00, 1.00);
|
||||
ImVec4 white = ImVec4(1.00, 1.00, 1.00, 1.00);
|
||||
ImVec4 transparent = ImVec4(0.00, 0.00, 0.00, 0.00);
|
||||
ImVec4 dark = ImVec4(0.00, 0.00, 0.00, 0.20);
|
||||
ImVec4 darker = ImVec4(0.00, 0.00, 0.00, 0.50);
|
||||
ImVec4 background = ImVec4(0.95, 0.95, 0.95, 1.00);
|
||||
ImVec4 text = ImVec4(0.10, 0.10, 0.10, 1.00);
|
||||
ImVec4 border = ImVec4(0.60, 0.60, 0.60, 1.00);
|
||||
ImVec4 grab = ImVec4(0.69, 0.69, 0.69, 1.00);
|
||||
ImVec4 header = ImVec4(0.86, 0.86, 0.86, 1.00);
|
||||
ImVec4 active = ImVec4(0.00, 0.47, 0.84, 1.00);
|
||||
ImVec4 hover = ImVec4(0.00, 0.47, 0.84, 0.20);
|
||||
ImVec4 dark = ImVec4(0.00, 0.00, 0.00, 0.20);
|
||||
ImVec4 darker = ImVec4(0.00, 0.00, 0.00, 0.50);
|
||||
ImVec4 background = ImVec4(0.95, 0.95, 0.95, 1.00);
|
||||
ImVec4 text = ImVec4(0.10, 0.10, 0.10, 1.00);
|
||||
ImVec4 border = ImVec4(0.60, 0.60, 0.60, 1.00);
|
||||
ImVec4 grab = ImVec4(0.69, 0.69, 0.69, 1.00);
|
||||
ImVec4 header = ImVec4(0.86, 0.86, 0.86, 1.00);
|
||||
ImVec4 active = ImVec4(0.00, 0.47, 0.84, 1.00);
|
||||
ImVec4 hover = ImVec4(0.00, 0.47, 0.84, 0.20);
|
||||
|
||||
c[ImGuiCol_Text] = text;
|
||||
c[ImGuiCol_WindowBg] = background;
|
||||
@@ -129,46 +130,46 @@ void SetupTheme(GuiTheme theme) {
|
||||
c[ImGuiCol_ScrollbarGrab] = grab;
|
||||
c[ImGuiCol_ScrollbarGrabHovered] = dark;
|
||||
c[ImGuiCol_ScrollbarGrabActive] = darker;
|
||||
} else {
|
||||
} else {
|
||||
ImGui::StyleColorsDark(&s);
|
||||
ImVec4 black = ImVec4(0.00, 0.00, 0.00, 1.0);
|
||||
ImVec4 window = ImVec4(0.25, 0.25, 0.25, 1.0);
|
||||
ImVec4 font_active = ImVec4(1.00, 1.00, 1.00, 1.0);
|
||||
ImVec4 black = ImVec4(0.00, 0.00, 0.00, 1.0);
|
||||
ImVec4 window = ImVec4(0.25, 0.25, 0.25, 1.0);
|
||||
ImVec4 font_active = ImVec4(1.00, 1.00, 1.00, 1.0);
|
||||
ImVec4 font_inactive = ImVec4(0.50, 0.50, 0.50, 1.0);
|
||||
ImVec4 thumb = ImVec4(0.12, 0.12, 0.12, 1.0);
|
||||
ImVec4 section = ImVec4(0.40, 0.15, 0.15, 1.0);
|
||||
ImVec4 button = ImVec4(0.60, 0.40, 0.40, 1.0);
|
||||
ImVec4 check = ImVec4(0.40, 0.40, 0.70, 1.0);
|
||||
ImVec4 frame = ImVec4(0.40, 0.30, 0.40, 1.0);
|
||||
ImVec4 slider = ImVec4(0.60, 0.40, 0.60, 1.0);
|
||||
ImVec4 thumb = ImVec4(0.12, 0.12, 0.12, 1.0);
|
||||
ImVec4 section = ImVec4(0.40, 0.15, 0.15, 1.0);
|
||||
ImVec4 button = ImVec4(0.60, 0.40, 0.40, 1.0);
|
||||
ImVec4 check = ImVec4(0.40, 0.40, 0.70, 1.0);
|
||||
ImVec4 frame = ImVec4(0.40, 0.30, 0.40, 1.0);
|
||||
ImVec4 slider = ImVec4(0.60, 0.40, 0.60, 1.0);
|
||||
|
||||
c[ImGuiCol_WindowBg] = window;
|
||||
c[ImGuiCol_ChildBg] = black;
|
||||
c[ImGuiCol_PopupBg] = window;
|
||||
c[ImGuiCol_Text] = font_active;
|
||||
c[ImGuiCol_TextDisabled] = font_inactive;
|
||||
c[ImGuiCol_CheckMark] = font_active;
|
||||
c[ImGuiCol_Header] = section;
|
||||
c[ImGuiCol_HeaderHovered] = section;
|
||||
c[ImGuiCol_HeaderActive] = section;
|
||||
c[ImGuiCol_TitleBgActive] = window;
|
||||
c[ImGuiCol_ScrollbarBg] = window;
|
||||
c[ImGuiCol_ScrollbarGrab] = thumb;
|
||||
c[ImGuiCol_ScrollbarGrabHovered] = thumb;
|
||||
c[ImGuiCol_ScrollbarGrabActive] = thumb;
|
||||
c[ImGuiCol_FrameBg] = frame;
|
||||
c[ImGuiCol_FrameBgHovered] = frame;
|
||||
c[ImGuiCol_FrameBgActive] = frame;
|
||||
c[ImGuiCol_SliderGrab] = slider;
|
||||
c[ImGuiCol_SliderGrabActive] = slider;
|
||||
c[ImGuiCol_Button] = window;
|
||||
c[ImGuiCol_ButtonHovered] = button;
|
||||
c[ImGuiCol_ButtonActive] = button;
|
||||
c[ImGuiCol_Tab] = window;
|
||||
c[ImGuiCol_TabHovered] = check;
|
||||
c[ImGuiCol_TabSelected] = check;
|
||||
c[ImGuiCol_TabDimmed] = window;
|
||||
c[ImGuiCol_TabDimmedSelected] = check;
|
||||
c[ImGuiCol_WindowBg] = window;
|
||||
c[ImGuiCol_ChildBg] = black;
|
||||
c[ImGuiCol_PopupBg] = window;
|
||||
c[ImGuiCol_Text] = font_active;
|
||||
c[ImGuiCol_TextDisabled] = font_inactive;
|
||||
c[ImGuiCol_CheckMark] = font_active;
|
||||
c[ImGuiCol_Header] = section;
|
||||
c[ImGuiCol_HeaderHovered] = section;
|
||||
c[ImGuiCol_HeaderActive] = section;
|
||||
c[ImGuiCol_TitleBgActive] = window;
|
||||
c[ImGuiCol_ScrollbarBg] = window;
|
||||
c[ImGuiCol_ScrollbarGrab] = thumb;
|
||||
c[ImGuiCol_ScrollbarGrabHovered] = thumb;
|
||||
c[ImGuiCol_ScrollbarGrabActive] = thumb;
|
||||
c[ImGuiCol_FrameBg] = frame;
|
||||
c[ImGuiCol_FrameBgHovered] = frame;
|
||||
c[ImGuiCol_FrameBgActive] = frame;
|
||||
c[ImGuiCol_SliderGrab] = slider;
|
||||
c[ImGuiCol_SliderGrabActive] = slider;
|
||||
c[ImGuiCol_Button] = window;
|
||||
c[ImGuiCol_ButtonHovered] = button;
|
||||
c[ImGuiCol_ButtonActive] = button;
|
||||
c[ImGuiCol_Tab] = window;
|
||||
c[ImGuiCol_TabHovered] = check;
|
||||
c[ImGuiCol_TabSelected] = check;
|
||||
c[ImGuiCol_TabDimmed] = window;
|
||||
c[ImGuiCol_TabDimmedSelected] = check;
|
||||
}
|
||||
|
||||
int hspacing = 4;
|
||||
@@ -207,14 +208,11 @@ ImVec4 ConfigureDockingLayout() {
|
||||
const float kToolsBarHeight = 48.f * scale;
|
||||
const float kStatusBarHeight = 32.f * scale;
|
||||
|
||||
const ImVec2 dockspace_pos{
|
||||
viewport->WorkPos.x,
|
||||
viewport->WorkPos.y + kToolsBarHeight
|
||||
};
|
||||
const ImVec2 dockspace_pos{viewport->WorkPos.x,
|
||||
viewport->WorkPos.y + kToolsBarHeight};
|
||||
const ImVec2 dockspace_size{
|
||||
viewport->WorkSize.x,
|
||||
viewport->WorkSize.y - kToolsBarHeight - kStatusBarHeight
|
||||
};
|
||||
viewport->WorkSize.y - kToolsBarHeight - kStatusBarHeight};
|
||||
|
||||
ImGuiID root = ImGui::GetID("Root");
|
||||
const bool first_time = (ImGui::DockBuilderGetNode(root) == nullptr);
|
||||
@@ -228,16 +226,16 @@ ImVec4 ConfigureDockingLayout() {
|
||||
ImGuiID main = root;
|
||||
|
||||
ImGuiID options = 0;
|
||||
ImGui::DockBuilderSplitNode(main, ImGuiDir_Left, kOptionsRelWidth,
|
||||
&options, &main);
|
||||
ImGui::DockBuilderSplitNode(main, ImGuiDir_Left, kOptionsRelWidth, &options,
|
||||
&main);
|
||||
|
||||
ImGuiID inspector = 0;
|
||||
ImGui::DockBuilderSplitNode(main, ImGuiDir_Right, kInspectorRelWidth,
|
||||
&inspector, &main);
|
||||
|
||||
ImGuiID stats = 0;
|
||||
ImGui::DockBuilderSplitNode(options, ImGuiDir_Down, kStatsRelHeight,
|
||||
&stats, &options);
|
||||
ImGui::DockBuilderSplitNode(options, ImGuiDir_Down, kStatsRelHeight, &stats,
|
||||
&options);
|
||||
|
||||
ImGuiID properties = 0;
|
||||
ImGui::DockBuilderSplitNode(inspector, ImGuiDir_Down, kStatsRelHeight,
|
||||
@@ -265,11 +263,10 @@ ImVec4 ConfigureDockingLayout() {
|
||||
ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBackground;
|
||||
|
||||
const ImGuiWindowFlags kFixedFlags = ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoDocking;
|
||||
const ImGuiWindowFlags kFixedFlags =
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoDocking;
|
||||
|
||||
// Main workspace area in which we can dock other windows.
|
||||
{
|
||||
@@ -292,7 +289,8 @@ ImVec4 ConfigureDockingLayout() {
|
||||
style.Var(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
style.Var(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::SetNextWindowPos(viewport->WorkPos, ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kToolsBarHeight), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kToolsBarHeight),
|
||||
ImGuiCond_Always);
|
||||
ImGui::Begin("ToolBar", nullptr, kFixedFlags);
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -302,8 +300,10 @@ ImVec4 ConfigureDockingLayout() {
|
||||
platform::ScopedStyle style;
|
||||
style.Var(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
style.Var(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::SetNextWindowPos(ImVec2(0, viewport->Size.y - kStatusBarHeight), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kStatusBarHeight), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(ImVec2(0, viewport->Size.y - kStatusBarHeight),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kStatusBarHeight),
|
||||
ImGuiCond_Always);
|
||||
ImGui::Begin("StatusBar", nullptr, kFixedFlags);
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -317,6 +317,148 @@ ImVec4 ConfigureDockingLayout() {
|
||||
return ImVec4(workspace_x, workspace_y, workspace_w, workspace_h);
|
||||
}
|
||||
|
||||
bool ThemeSelectGui(GuiTheme* theme) {
|
||||
static constexpr const char* ICON_DARKMODE = ICON_FA_CIRCLE;
|
||||
static constexpr const char* ICON_LIGHTMODE = ICON_FA_CIRCLE_O;
|
||||
static constexpr const char* ICON_CLASSICMODE = ICON_FA_ADJUST;
|
||||
const char* theme_icons[] = {ICON_LIGHTMODE, ICON_DARKMODE, ICON_CLASSICMODE};
|
||||
const char* theme_tooltips[] = {"Light Mode", "Dark Mode", "Classic Mode"};
|
||||
const GuiTheme theme_values[] = {
|
||||
GuiTheme::kLight,
|
||||
GuiTheme::kDark,
|
||||
GuiTheme::kClassic,
|
||||
};
|
||||
|
||||
bool changed = false;
|
||||
|
||||
int theme_idx = static_cast<int>(*theme);
|
||||
ImGui::SetNextItemWidth(ImGui::CalcTextSize(theme_icons[0]).x +
|
||||
ImGui::GetStyle().FramePadding.x * 2);
|
||||
if (ImGui::BeginCombo("##Theme", theme_icons[theme_idx],
|
||||
ImGuiComboFlags_NoArrowButton)) {
|
||||
for (int n = 0; n < IM_ARRAYSIZE(theme_icons); n++) {
|
||||
if (ImGui::Selectable(theme_icons[n], (theme_idx == n))) {
|
||||
*theme = theme_values[n];
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", theme_tooltips[n]);
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Theme");
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool LabelSelectionGui(mjvOption* opts) {
|
||||
static constexpr const char* ICON_LABEL = ICON_FA_COMMENT;
|
||||
static constexpr const char* kLabelNames[] = {
|
||||
"None", "Body", "Joint", "Geom", "Site", "Camera",
|
||||
"Light", "Tendon", "Actuator", "Constraint", "Flex", "Skin",
|
||||
"Selection", "Sel Pnt", "Contact", "Force", "Island"};
|
||||
|
||||
bool changed = false;
|
||||
const std::string label_preview =
|
||||
std::string(ICON_LABEL) + " " + kLabelNames[opts->label];
|
||||
if (ImGui::BeginCombo("##Label", label_preview.c_str(),
|
||||
ImGuiComboFlags_NoArrowButton)) {
|
||||
for (int n = 0; n < IM_ARRAYSIZE(kLabelNames); n++) {
|
||||
if (ImGui::Selectable(kLabelNames[n], (opts->label == n))) {
|
||||
changed = true;
|
||||
opts->label = n;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Label");
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool FrameSelectionGui(mjvOption* opts) {
|
||||
static constexpr const char* ICON_FRAME = ICON_FA_ARROWS;
|
||||
static constexpr const char* kFrameNames[] = {
|
||||
"None", "Body", "Geom", "Site", "Camera", "Light", "Contact", "World"};
|
||||
|
||||
bool changed = false;
|
||||
const std::string frame_preview =
|
||||
std::string(ICON_FRAME) + " " + kFrameNames[opts->frame];
|
||||
if (ImGui::BeginCombo("##Frame", frame_preview.c_str(),
|
||||
ImGuiComboFlags_NoArrowButton)) {
|
||||
for (int n = 0; n < IM_ARRAYSIZE(kFrameNames); n++) {
|
||||
if (ImGui::Selectable(kFrameNames[n], (opts->frame == n))) {
|
||||
opts->frame = n;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Frame");
|
||||
return changed;
|
||||
}
|
||||
|
||||
static std::string GetCameraName(const mjModel* model, const mjvCamera& camera,
|
||||
int index) {
|
||||
static constexpr char kCameraTumbleName[] = "Free: tumble";
|
||||
static constexpr char kCameraWasdName[] = "Free: wasd";
|
||||
static constexpr char kCameraUnnamedName[] = "Unnamed";
|
||||
|
||||
if (index == kTumbleCameraIdx) {
|
||||
return kCameraTumbleName;
|
||||
} else if (index == kFreeCameraIdx) {
|
||||
return kCameraWasdName;
|
||||
} else if (index == kTrackingCameraIdx) {
|
||||
return "Tracking (" + std::to_string(camera.trackbodyid) + ")";
|
||||
} else if (model->names[model->name_camadr[index]]) {
|
||||
return std::string(model->names + model->name_camadr[index]);
|
||||
} else {
|
||||
return kCameraUnnamedName;
|
||||
}
|
||||
}
|
||||
|
||||
bool CameraSelectionGui(const mjModel* model, mjData* data, mjvCamera& camera,
|
||||
int& index) {
|
||||
static constexpr const char* ICON_CAMERA = ICON_FA_CAMERA;
|
||||
|
||||
auto select = [&](int type, int idx) {
|
||||
if (ImGui::Selectable(GetCameraName(model, camera, type).c_str(),
|
||||
(type == idx))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool changed = false;
|
||||
|
||||
const std::string preview =
|
||||
std::string(ICON_CAMERA) + " " + GetCameraName(model, camera, index);
|
||||
if (ImGui::BeginCombo("##Camera", preview.c_str(),
|
||||
ImGuiComboFlags_NoArrowButton)) {
|
||||
if (select(kTumbleCameraIdx, index)) {
|
||||
index = SetCamera(model, &camera, kTumbleCameraIdx);
|
||||
changed = true;
|
||||
}
|
||||
if (select(kFreeCameraIdx, index)) {
|
||||
index = SetCamera(model, &camera, kFreeCameraIdx);
|
||||
changed = true;
|
||||
}
|
||||
if (select(kTrackingCameraIdx, index)) {
|
||||
index = SetCamera(model, &camera, kTrackingCameraIdx);
|
||||
changed = true;
|
||||
}
|
||||
for (int cam = 0; cam < model->ncam; cam++) {
|
||||
if (select(cam, index)) {
|
||||
index = SetCamera(model, &camera, cam);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Camera");
|
||||
return changed;
|
||||
}
|
||||
|
||||
void SensorGui(const mjModel* model, const mjData* data) {
|
||||
if (model->nsensor == 0) {
|
||||
return;
|
||||
@@ -392,11 +534,12 @@ void StateGui(const mjModel* model, mjData* data, std::vector<mjtNum>& state,
|
||||
|
||||
ImGui::Unindent(0.5f * ImGui::GetTreeNodeToLabelSpacing());
|
||||
// State component names and tooltips.
|
||||
static constexpr const char* name_and_tooltip[][2] = {
|
||||
static constexpr const char* name_and_tooltip[mjNSTATE][2] = {
|
||||
{"TIME", "Time"},
|
||||
{"QPOS", "Position"},
|
||||
{"QVEL", "Velocity"},
|
||||
{"ACT", "Actuator activation"},
|
||||
{"HISTORY", "History buffers (control, sensor)"},
|
||||
{"WARMSTART", "Acceleration used for warmstart"},
|
||||
{"CTRL", "Control"},
|
||||
{"QFRC_APPLIED", "Applied generalized force"},
|
||||
@@ -935,36 +1078,49 @@ void ConvergenceGui(const mjModel* model, mjData* data) {
|
||||
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
|
||||
ImPlot::SetupFinish();
|
||||
|
||||
const int nisland = data->nefc ? mjMAX(1, mjMIN(data->nisland, mjNISLAND)) : 0;
|
||||
const int nisland =
|
||||
data->nefc ? mjMAX(1, mjMIN(data->nisland, mjNISLAND)) : 0;
|
||||
for (int k = 0; k < nisland; k++) {
|
||||
mjSolverStat* stats = data->solver + k * mjNSOLVER;
|
||||
const int npoints =
|
||||
mjMIN(mjMIN(data->solver_niter[k], mjNSOLVER), mjMAXLINEPNT);
|
||||
|
||||
ImPlot::PlotLineG("improvement", +[](int i, void* user_data) {
|
||||
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = mju_log10(mju_max(mjMINVAL, stats[i].improvement));
|
||||
return ImPlotPoint{x, y};
|
||||
}, stats, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"improvement",
|
||||
+[](int i, void* user_data) {
|
||||
const mjSolverStat* stats =
|
||||
static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = mju_log10(mju_max(mjMINVAL, stats[i].improvement));
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
stats, npoints);
|
||||
|
||||
if (model->opt.solver == mjSOL_PGS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImPlot::PlotLineG("gradient", +[](int i, void* user_data) {
|
||||
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = mju_log10(mju_max(mjMINVAL, stats[i].gradient));
|
||||
return ImPlotPoint{x, y};
|
||||
}, stats, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"gradient",
|
||||
+[](int i, void* user_data) {
|
||||
const mjSolverStat* stats =
|
||||
static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = mju_log10(mju_max(mjMINVAL, stats[i].gradient));
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
stats, npoints);
|
||||
|
||||
ImPlot::PlotLineG("lineslope", +[](int i, void* user_data) {
|
||||
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = mju_log10(mju_max(mjMINVAL, stats[i].lineslope));
|
||||
return ImPlotPoint{x, y};
|
||||
}, stats, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"lineslope",
|
||||
+[](int i, void* user_data) {
|
||||
const mjSolverStat* stats =
|
||||
static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = mju_log10(mju_max(mjMINVAL, stats[i].lineslope));
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
stats, npoints);
|
||||
}
|
||||
|
||||
ImPlot::PopStyleVar();
|
||||
@@ -982,56 +1138,76 @@ void CountsGui(const mjModel* model, mjData* data) {
|
||||
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
|
||||
ImPlot::SetupFinish();
|
||||
|
||||
const int nisland = data->nefc ? mjMAX(1, mjMIN(data->nisland, mjNISLAND)) : 0;
|
||||
const int nisland =
|
||||
data->nefc ? mjMAX(1, mjMIN(data->nisland, mjNISLAND)) : 0;
|
||||
for (int k = 0; k < nisland; k++) {
|
||||
const int npoints =
|
||||
mjMIN(mjMIN(data->solver_niter[k], mjNSOLVER), mjMAXLINEPNT);
|
||||
|
||||
mjSolverStat* stats = data->solver + k*mjNSOLVER;
|
||||
mjSolverStat* stats = data->solver + k * mjNSOLVER;
|
||||
|
||||
int nefc = nisland == 1 ? data->nefc : data->island_nefc[k];
|
||||
|
||||
ImPlot::PlotLineG("total", +[](int i, void* user_data) {
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = *(static_cast<int*>(user_data));
|
||||
return ImPlotPoint{x, y};
|
||||
}, &nefc, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"total",
|
||||
+[](int i, void* user_data) {
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = *(static_cast<int*>(user_data));
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
&nefc, npoints);
|
||||
|
||||
ImPlot::PlotLineG("active", +[](int i, void* user_data) {
|
||||
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].nactive;
|
||||
return ImPlotPoint{x, y};
|
||||
}, stats, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"active",
|
||||
+[](int i, void* user_data) {
|
||||
const mjSolverStat* stats =
|
||||
static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].nactive;
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
stats, npoints);
|
||||
|
||||
ImPlot::PlotLineG("changed", +[](int i, void* user_data) {
|
||||
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].nchange;
|
||||
return ImPlotPoint{x, y};
|
||||
}, stats, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"changed",
|
||||
+[](int i, void* user_data) {
|
||||
const mjSolverStat* stats =
|
||||
static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].nchange;
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
stats, npoints);
|
||||
|
||||
if (model->opt.solver == mjSOL_PGS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImPlot::PlotLineG("evals", +[](int i, void* user_data) {
|
||||
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].neval;
|
||||
return ImPlotPoint{x, y};
|
||||
}, stats, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"evals",
|
||||
+[](int i, void* user_data) {
|
||||
const mjSolverStat* stats =
|
||||
static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].neval;
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
stats, npoints);
|
||||
|
||||
if (model->opt.solver == mjSOL_CG) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImPlot::PlotLineG("updates", +[](int i, void* user_data) {
|
||||
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].nupdate;
|
||||
return ImPlotPoint{x, y};
|
||||
}, stats, npoints);
|
||||
ImPlot::PlotLineG(
|
||||
"updates",
|
||||
+[](int i, void* user_data) {
|
||||
const mjSolverStat* stats =
|
||||
static_cast<const mjSolverStat*>(user_data);
|
||||
const float x = static_cast<float>(i);
|
||||
const float y = stats[i].nupdate;
|
||||
return ImPlotPoint{x, y};
|
||||
},
|
||||
stats, npoints);
|
||||
}
|
||||
|
||||
ImPlot::PopStyleVar();
|
||||
|
||||
@@ -62,6 +62,19 @@ void SetupTheme(GuiTheme theme);
|
||||
// be used to place additional elements (e.g. floating charts).
|
||||
ImVec4 ConfigureDockingLayout();
|
||||
|
||||
// UX for selecting the GUI theme.
|
||||
bool ThemeSelectGui(GuiTheme* theme);
|
||||
|
||||
// UX for selecting the visualization label option.
|
||||
bool LabelSelectionGui(mjvOption* opts);
|
||||
|
||||
// UX for selecting the visualization frame option.
|
||||
bool FrameSelectionGui(mjvOption* opts);
|
||||
|
||||
// UX for selecting the camera.
|
||||
bool CameraSelectionGui(const mjModel* model, mjData* data, mjvCamera& camera,
|
||||
int& index);
|
||||
|
||||
// UX for controlling the physics simulation parameters (e.g. integrator,
|
||||
// solver, etc.) in mjModel.
|
||||
void PhysicsGui(mjModel* model, float min_width);
|
||||
|
||||
@@ -93,28 +93,29 @@ static void AddEditorButtons(mjsElement* element, mjsElement** selected_element,
|
||||
if (ImGui::SmallButton(ICON_FA_PLUS)) {
|
||||
ImGui::OpenPopupOnItemClick("BodyAddChild", 0);
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextItem("BodyAddChild")) {
|
||||
mjsBody* body = mjs_asBody(element);
|
||||
auto option = [&](const char* label, mjtObj type) {
|
||||
if (ImGui::Selectable(label)) {
|
||||
*selected_element = editor.AddBodyElement(body, type);
|
||||
}
|
||||
};
|
||||
option("Camera", mjOBJ_CAMERA);
|
||||
option("Frame", mjOBJ_FRAME);
|
||||
option("Geom", mjOBJ_GEOM);
|
||||
option("Joint", mjOBJ_JOINT);
|
||||
option("Light", mjOBJ_LIGHT);
|
||||
option("Site", mjOBJ_SITE);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (ImGui::SmallButton(ICON_FA_TRASH_CAN)) {
|
||||
editor.DeleteActiveElement();
|
||||
*selected_element = nullptr;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextItem("BodyAddChild")) {
|
||||
mjsBody* body = mjs_asBody(element);
|
||||
auto option = [&](const char* label, mjtObj type) {
|
||||
if (ImGui::Selectable(label)) {
|
||||
*selected_element = editor.AddBodyElement(body, type);
|
||||
}
|
||||
};
|
||||
option("Camera", mjOBJ_CAMERA);
|
||||
option("Frame", mjOBJ_FRAME);
|
||||
option("Geom", mjOBJ_GEOM);
|
||||
option("Joint", mjOBJ_JOINT);
|
||||
option("Light", mjOBJ_LIGHT);
|
||||
option("Site", mjOBJ_SITE);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
static void SelectableElement(mjsElement* element,
|
||||
|
||||
@@ -61,6 +61,10 @@ std::unique_ptr<ModelHolder> ModelHolder::FromBuffer(
|
||||
return mh;
|
||||
}
|
||||
|
||||
ModelHolder::ModelHolder() {
|
||||
mj_defaultVFS(&vfs_);
|
||||
}
|
||||
|
||||
ModelHolder::~ModelHolder() {
|
||||
if (data_) {
|
||||
mj_deleteData(data_);
|
||||
@@ -94,7 +98,6 @@ void ModelHolder::InitFromSpec(mjSpec* spec) {
|
||||
}
|
||||
|
||||
void ModelHolder::InitFromFile(std::string_view filepath) {
|
||||
mj_defaultVFS(&vfs_);
|
||||
if (filepath.ends_with(".mjb")) {
|
||||
model_ = mj_loadModel(filepath.data(), &vfs_);
|
||||
} else {
|
||||
@@ -108,8 +111,6 @@ void ModelHolder::InitFromFile(std::string_view filepath) {
|
||||
void ModelHolder::InitFromBuffer(std::span<const std::byte> buffer,
|
||||
std::string_view content_type,
|
||||
std::string_view filename) {
|
||||
mj_defaultVFS(&vfs_);
|
||||
|
||||
if (content_type == "text/xml") {
|
||||
const char* ptr = reinterpret_cast<const char*>(buffer.data());
|
||||
spec_ = mj_parseXMLString(ptr, nullptr, error_, sizeof(error_));
|
||||
|
||||
@@ -64,7 +64,7 @@ class ModelHolder {
|
||||
mjModel* ReleaseModel();
|
||||
|
||||
private:
|
||||
ModelHolder() = default;
|
||||
ModelHolder();
|
||||
void InitFromSpec(mjSpec* spec);
|
||||
void InitFromFile(std::string_view filepath);
|
||||
void InitFromBuffer(std::span<const std::byte> buffer,
|
||||
|
||||
+13
-116
@@ -78,13 +78,7 @@ static constexpr const char* ICON_VISCOUS_PAUSE = platform::ICON_FA_MAGIC;
|
||||
static constexpr const char* ICON_COPY_CAMERA = platform::ICON_FA_COPY;
|
||||
static constexpr const char* ICON_UNLOAD_MODEL = platform::ICON_FA_EJECT;
|
||||
static constexpr const char* ICON_RELOAD_MODEL = platform::ICON_FA_REFRESH;
|
||||
static constexpr const char* ICON_LABEL = platform::ICON_FA_COMMENT;
|
||||
static constexpr const char* ICON_RESET_MODEL = platform::ICON_FA_UNDO;
|
||||
static constexpr const char* ICON_FRAME = platform::ICON_FA_ARROWS;
|
||||
static constexpr const char* ICON_CAMERA = platform::ICON_FA_CAMERA;
|
||||
static constexpr const char* ICON_DARKMODE = platform::ICON_FA_CIRCLE;
|
||||
static constexpr const char* ICON_LIGHTMODE = platform::ICON_FA_CIRCLE_O;
|
||||
static constexpr const char* ICON_CLASSICMODE = platform::ICON_FA_ADJUST;
|
||||
static constexpr const char* ICON_PREV_FRAME = platform::ICON_FA_CARET_LEFT;
|
||||
static constexpr const char* ICON_NEXT_FRAME = platform::ICON_FA_CARET_RIGHT;
|
||||
static constexpr const char* ICON_CURR_FRAME = platform::ICON_FA_FAST_FORWARD;
|
||||
@@ -93,16 +87,6 @@ static constexpr const char* ICON_RELOAD_SPEC = platform::ICON_FA_REFRESH;
|
||||
static constexpr const char* ICON_UNDO_SPEC = platform::ICON_FA_UNDO;
|
||||
static constexpr const char* ICON_REDO_SPEC = platform::ICON_FA_REPEAT;
|
||||
|
||||
// UI labels for mjtLabel.
|
||||
static constexpr const char* kLabelNames[] = {
|
||||
"None", "Body", "Joint", "Geom", "Site", "Camera",
|
||||
"Light", "Tendon", "Actuator", "Constraint", "Flex", "Skin",
|
||||
"Selection", "Sel Pnt", "Contact", "Force", "Island"};
|
||||
|
||||
// UI labels for mjtFrame.
|
||||
static constexpr const char* kFrameNames[] = {
|
||||
"None", "Body", "Geom", "Site", "Camera", "Light", "Contact", "World"};
|
||||
|
||||
// logarithmically spaced real-time slow-down coefficients (percent)
|
||||
// clang-format off
|
||||
static constexpr std::array<const char*, 31> kPercentRealTime = {
|
||||
@@ -846,7 +830,10 @@ void App::BuildGui() {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupTheme(ui_.theme);
|
||||
if (!tmp_.style_editor) {
|
||||
platform::SetupTheme(ui_.theme);
|
||||
}
|
||||
|
||||
const ImVec4 workspace_rect = platform::ConfigureDockingLayout();
|
||||
|
||||
// Place charts in bottom right corner of the workspace.
|
||||
@@ -1024,14 +1011,6 @@ void App::BuildGui() {
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetupTheme(platform::GuiTheme theme) {
|
||||
if (!tmp_.style_editor) {
|
||||
platform::SetupTheme(theme);
|
||||
ui_.theme = theme;
|
||||
ImGui::GetIO().WantSaveIniSettings = true;
|
||||
}
|
||||
}
|
||||
|
||||
void App::ModelOptionsGui() {
|
||||
const float min_width = GetExpectedLabelWidth();
|
||||
const ImGuiChildFlags child_flags =
|
||||
@@ -1449,7 +1428,7 @@ void App::ToolBarGui() {
|
||||
const float label_width = GetExpectedLabelWidth();
|
||||
const float copy_btn_width = ImGui::CalcTextSize(ICON_COPY_CAMERA).x +
|
||||
ImGui::GetStyle().FramePadding.x * 2;
|
||||
const float theme_width = ImGui::CalcTextSize(ICON_LIGHTMODE).x +
|
||||
const float theme_width = ImGui::CalcTextSize(platform::ICON_FA_CIRCLE_O).x +
|
||||
ImGui::GetStyle().FramePadding.x * 2;
|
||||
const float sp = ImGui::GetStyle().ItemSpacing.x;
|
||||
const float right_width = label_width + sp + label_width + sp +
|
||||
@@ -1576,84 +1555,30 @@ void App::ToolBarGui() {
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() +
|
||||
(button_size.y - ImGui::GetFrameHeight()) * 0.5f);
|
||||
|
||||
// Camera selection.
|
||||
if (ImGui::Button(ICON_COPY_CAMERA)) {
|
||||
std::string camera_string = platform::CameraToString(data(), &camera_);
|
||||
platform::MaybeSaveToClipboard(camera_string);
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Copy Camera");
|
||||
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetNextItemWidth(GetExpectedLabelWidth());
|
||||
int camera_idx = ui_.camera_idx - platform::kTumbleCameraIdx;
|
||||
std::vector<const char*> cameras = GetCameraNames();
|
||||
std::string camera_preview =
|
||||
std::string(ICON_CAMERA) + " " + cameras[camera_idx];
|
||||
if (ImGui::BeginCombo("##Camera", camera_preview.c_str(), combo_flags)) {
|
||||
for (int n = 0; n < cameras.size(); n++) {
|
||||
if (ImGui::Selectable(cameras[n], (camera_idx == n))) {
|
||||
ui_.camera_idx = platform::SetCamera(model(), &camera_,
|
||||
n + platform::kTumbleCameraIdx);
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Camera");
|
||||
platform::CameraSelectionGui(model(), data(), camera_, ui_.camera_idx);
|
||||
|
||||
// Label selection.
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(GetExpectedLabelWidth());
|
||||
std::string label_preview =
|
||||
std::string(ICON_LABEL) + " " + kLabelNames[vis_options_.label];
|
||||
if (ImGui::BeginCombo("##Label", label_preview.c_str(), combo_flags)) {
|
||||
for (int n = 0; n < IM_ARRAYSIZE(kLabelNames); n++) {
|
||||
if (ImGui::Selectable(kLabelNames[n], (vis_options_.label == n))) {
|
||||
vis_options_.label = n;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Label");
|
||||
platform::LabelSelectionGui(&vis_options_);
|
||||
|
||||
// Frame selection.
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(GetExpectedLabelWidth());
|
||||
std::string frame_preview =
|
||||
std::string(ICON_FRAME) + " " + kFrameNames[vis_options_.frame];
|
||||
if (ImGui::BeginCombo("##Frame", frame_preview.c_str(), combo_flags)) {
|
||||
for (int n = 0; n < IM_ARRAYSIZE(kFrameNames); n++) {
|
||||
if (ImGui::Selectable(kFrameNames[n], (vis_options_.frame == n))) {
|
||||
vis_options_.frame = n;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Frame");
|
||||
platform::FrameSelectionGui(&vis_options_);
|
||||
|
||||
// Theme selection.
|
||||
ImGui::SameLine();
|
||||
const char* theme_icons[] = {ICON_LIGHTMODE, ICON_DARKMODE,
|
||||
ICON_CLASSICMODE};
|
||||
const char* theme_tooltips[] = {"Light Mode", "Dark Mode", "Classic Mode"};
|
||||
const platform::GuiTheme theme_values[] = {
|
||||
platform::GuiTheme::kLight,
|
||||
platform::GuiTheme::kDark,
|
||||
platform::GuiTheme::kClassic,
|
||||
};
|
||||
int theme_idx = static_cast<int>(ui_.theme);
|
||||
ImGui::SetNextItemWidth(ImGui::CalcTextSize(theme_icons[0]).x +
|
||||
ImGui::GetStyle().FramePadding.x * 2);
|
||||
if (ImGui::BeginCombo("##Theme", theme_icons[theme_idx], combo_flags)) {
|
||||
for (int n = 0; n < IM_ARRAYSIZE(theme_icons); n++) {
|
||||
if (ImGui::Selectable(theme_icons[n], (theme_idx == n))) {
|
||||
SetupTheme(theme_values[n]);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", theme_tooltips[n]);
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
ImGui::SetNextItemWidth(GetExpectedLabelWidth());
|
||||
if (platform::ThemeSelectGui(&ui_.theme)) {
|
||||
platform::SetupTheme(ui_.theme);
|
||||
ImGui::GetIO().WantSaveIniSettings = true;
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Theme");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
@@ -2011,34 +1936,6 @@ float App::GetExpectedLabelWidth() {
|
||||
return tmp_.expected_label_width;
|
||||
}
|
||||
|
||||
std::vector<const char*> App::GetCameraNames() {
|
||||
if (tmp_.camera_names.empty()) {
|
||||
tmp_.camera_names.reserve(model()->ncam + 3);
|
||||
|
||||
tmp_.camera_names.push_back("Free: tumble");
|
||||
tmp_.camera_names.push_back("Free: wasd");
|
||||
tmp_.camera_names.push_back("Tracking (-1)");
|
||||
for (int i = 0; i < model()->ncam; i++) {
|
||||
if (model()->names[model()->name_camadr[i]]) {
|
||||
tmp_.camera_names.push_back(model()->names + model()->name_camadr[i]);
|
||||
} else {
|
||||
tmp_.camera_names.push_back("Unnamed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update tracking camera name as this can change over time.
|
||||
tmp_.camera_names[2] =
|
||||
"Tracking (" + std::to_string(camera_.trackbodyid) + ")";
|
||||
|
||||
std::vector<const char*> names;
|
||||
names.reserve(tmp_.camera_names.size());
|
||||
for (const auto& name : tmp_.camera_names) {
|
||||
names.push_back(name.c_str());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
App::UiState::Dict App::UiState::ToDict() const {
|
||||
return {
|
||||
{"theme", std::to_string(static_cast<int>(theme))},
|
||||
|
||||
@@ -223,7 +223,6 @@ class App {
|
||||
void SpecEditorGui();
|
||||
|
||||
float GetExpectedLabelWidth();
|
||||
std::vector<const char*> GetCameraNames();
|
||||
|
||||
mjSpec* spec() { return model_holder_->spec(); }
|
||||
mjModel* model() { return model_holder_->model(); }
|
||||
|
||||
@@ -1185,7 +1185,7 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, mjsCompiler* compiler, char* error,
|
||||
}
|
||||
|
||||
// copy vertices
|
||||
point = mesh.Vert();
|
||||
point.assign(mesh.Vert().begin(), mesh.Vert().end());
|
||||
|
||||
if (mesh.HasTexcoord()) {
|
||||
texcoord = mesh.Texcoord();
|
||||
|
||||
+122
-115
@@ -492,7 +492,7 @@ void mjCMesh::CacheMesh(mjCCache* cache, const mjResource* resource) {
|
||||
|
||||
// calculate estimated size of mesh
|
||||
std::size_t size = sizeof(mjCMesh)
|
||||
+ (sizeof(double) * vert_.size())
|
||||
+ (sizeof(float) * vert_.size())
|
||||
+ (sizeof(float) * normal_.size())
|
||||
+ (sizeof(float) * texcoord_.size())
|
||||
+ (sizeof(int) * face_.size())
|
||||
@@ -538,7 +538,7 @@ struct VertexKey {
|
||||
|
||||
|
||||
|
||||
// convert vertices to double precision and remove repeated vertices if requested
|
||||
// process and remove repeated vertices if requested
|
||||
void mjCMesh::ProcessVertices(const std::vector<float>& vert, bool remove_repeated) {
|
||||
vert_.clear();
|
||||
int nvert = vert.size();
|
||||
@@ -647,19 +647,6 @@ void mjCMesh::LoadFromDecoder(mjResource* resource, bool remove_repeated) {
|
||||
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);
|
||||
@@ -788,24 +775,24 @@ void mjCMesh::TryCompile(const mjVFS* vfs) {
|
||||
// compute mesh properties
|
||||
if (!fromCache) {
|
||||
Process();
|
||||
}
|
||||
|
||||
// make octree
|
||||
if (!needsdf) {
|
||||
octree_.Clear(); // this occurs when a non-SDF mesh is loaded from a cached SDF mesh
|
||||
} else if (octree_.NumNodes() == 0) {
|
||||
octree_.SetFace(vert_, face_);
|
||||
octree_.CreateOctree(aamm_);
|
||||
|
||||
// compute sdf coefficients
|
||||
if (!plugin.active) {
|
||||
octree_.ComputeSdfCoeffs(vert_.data(), nvert(), face_.data(), nface(), tree_);
|
||||
if (!file_.empty()) {
|
||||
CacheMesh(cache, resource_);
|
||||
}
|
||||
} else {
|
||||
// When a mesh is loaded from the cache, has no octree but needs one,
|
||||
// we need to compute it here. If inversely it has an octree but we *do not*
|
||||
// need one, we clear it.
|
||||
if (!needsdf) {
|
||||
octree_.Clear();
|
||||
} else if (octree_.NumNodes() == 0) {
|
||||
std::vector<double> dvert(vert_.begin(), vert_.end());
|
||||
octree_.SetFace(dvert, face_);
|
||||
octree_.CreateOctree(aamm_);
|
||||
if (!plugin.active) {
|
||||
octree_.ComputeSdfCoeffs(dvert.data(), nvert(), face_.data(), nface(), tree_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cache mesh
|
||||
if (!fromCache && !file_.empty()) {
|
||||
CacheMesh(cache, resource_);
|
||||
}
|
||||
|
||||
// close resource
|
||||
@@ -818,18 +805,18 @@ void mjCMesh::TryCompile(const mjVFS* vfs) {
|
||||
|
||||
|
||||
// get bounding volume
|
||||
void mjCMesh::SetBoundingVolume(int faceid) {
|
||||
void mjCMesh::SetBoundingVolume(int faceid, const double* dvert) {
|
||||
constexpr double kMaxVal = std::numeric_limits<double>::max();
|
||||
double face_aamm[6] = {kMaxVal, kMaxVal, kMaxVal, -kMaxVal, -kMaxVal, -kMaxVal};
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
int vertid = face_[3*faceid + j];
|
||||
face_aamm[0] = std::min(face_aamm[0], vert_[3*vertid + 0]);
|
||||
face_aamm[1] = std::min(face_aamm[1], vert_[3*vertid + 1]);
|
||||
face_aamm[2] = std::min(face_aamm[2], vert_[3*vertid + 2]);
|
||||
face_aamm[3] = std::max(face_aamm[3], vert_[3*vertid + 0]);
|
||||
face_aamm[4] = std::max(face_aamm[4], vert_[3*vertid + 1]);
|
||||
face_aamm[5] = std::max(face_aamm[5], vert_[3*vertid + 2]);
|
||||
face_aamm[0] = std::min(face_aamm[0], dvert[3*vertid + 0]);
|
||||
face_aamm[1] = std::min(face_aamm[1], dvert[3*vertid + 1]);
|
||||
face_aamm[2] = std::min(face_aamm[2], dvert[3*vertid + 2]);
|
||||
face_aamm[3] = std::max(face_aamm[3], dvert[3*vertid + 0]);
|
||||
face_aamm[4] = std::max(face_aamm[4], dvert[3*vertid + 1]);
|
||||
face_aamm[5] = std::max(face_aamm[5], dvert[3*vertid + 2]);
|
||||
}
|
||||
|
||||
face_aabb_.push_back(.5 * (face_aamm[0] + face_aamm[3]));
|
||||
@@ -864,9 +851,7 @@ bool mjCMesh::HasTexcoord() const {
|
||||
|
||||
|
||||
void mjCMesh::CopyVert(float* arr) const {
|
||||
for (int i = 0; i < vert_.size(); ++i) {
|
||||
arr[i] = (float)vert_[i];
|
||||
}
|
||||
std::copy(vert_.begin(), vert_.end(), arr);
|
||||
}
|
||||
|
||||
|
||||
@@ -1200,16 +1185,15 @@ void mjCMesh::LoadMSH(mjResource* resource, bool remove_repeated) {
|
||||
|
||||
|
||||
// compute the volume and center-of-mass of the mesh given the face centroid
|
||||
double mjCMesh::ComputeVolume(double CoM[3], const double facecen[3]) const {
|
||||
double mjCMesh::ComputeVolume(double CoM[3], const double facecen[3], const double* dvert) const {
|
||||
double normal[3], center[3], total_volume = 0;
|
||||
CoM[0] = CoM[1] = CoM[2] = 0;
|
||||
int nf = (inertia == mjMESH_INERTIA_CONVEX) ? graph_[1] : nface();
|
||||
const int* f = (inertia == mjMESH_INERTIA_CONVEX) ? GraphFaces() : face_.data();
|
||||
|
||||
for (int i = 0; i < nf; i++) {
|
||||
// get area, normal and center
|
||||
double area = triangle(normal, center, &vert_[3*f[3*i]], &vert_[3*f[3*i + 1]],
|
||||
&vert_[3*f[3*i + 2]]);
|
||||
double area = triangle(normal, center, &dvert[3*f[3*i]], &dvert[3*f[3*i + 1]],
|
||||
&dvert[3*f[3*i + 2]]);
|
||||
|
||||
// compute and add volume
|
||||
double vec[3] = {center[0] - facecen[0], center[1] - facecen[1], center[2] - facecen[2]};
|
||||
@@ -1239,14 +1223,13 @@ double mjCMesh::ComputeVolume(double CoM[3], const double facecen[3]) const {
|
||||
|
||||
|
||||
// compute the surface area and center-of-mass of the mesh given the face centroid
|
||||
double mjCMesh::ComputeSurfaceArea(double CoM[3], const double facecen[3]) const {
|
||||
double mjCMesh::ComputeSurfaceArea(double CoM[3], const double facecen[3], const double* dvert) const {
|
||||
double surface = 0;
|
||||
CoM[0] = CoM[1] = CoM[2] = 0;
|
||||
for (int i = 0; i < nface(); i++) {
|
||||
// get area and center
|
||||
double area, center[3];
|
||||
area = triangle(nullptr, center, &vert_[3*face_[3*i]],
|
||||
&vert_[3*face_[3*i + 1]], &vert_[3*face_[3*i + 2]]);
|
||||
area = triangle(nullptr, center, &dvert[3*face_[3*i]],
|
||||
&dvert[3*face_[3*i + 1]], &dvert[3*face_[3*i + 2]]);
|
||||
|
||||
// add pyramid com
|
||||
surface += area;
|
||||
@@ -1267,28 +1250,24 @@ double mjCMesh::ComputeSurfaceArea(double CoM[3], const double facecen[3]) const
|
||||
|
||||
|
||||
// apply transformations
|
||||
void mjCMesh::ApplyTransformations() {
|
||||
// translate
|
||||
void mjCMesh::ApplyTransformations(double* dvert) {
|
||||
if (refpos[0] != 0 || refpos[1] != 0 || refpos[2] != 0) {
|
||||
int nv = nvert();
|
||||
for (int i = 0; i < nv; i++) {
|
||||
vert_[3*i + 0] -= refpos[0];
|
||||
vert_[3*i + 1] -= refpos[1];
|
||||
vert_[3*i + 2] -= refpos[2];
|
||||
dvert[3*i + 0] -= refpos[0];
|
||||
dvert[3*i + 1] -= refpos[1];
|
||||
dvert[3*i + 2] -= refpos[2];
|
||||
}
|
||||
}
|
||||
|
||||
// rotate
|
||||
if (refquat[0] != 1 || refquat[1] != 0 || refquat[2] != 0 || refquat[3] != 0) {
|
||||
// prepare rotation
|
||||
double quat[4] = {refquat[0], refquat[1], refquat[2], refquat[3]};
|
||||
double mat[9];
|
||||
mjuu_normvec(quat, 4);
|
||||
mjuu_quat2mat(mat, quat);
|
||||
|
||||
// process vertices
|
||||
for (int i = 0; i < nvert(); i++) {
|
||||
mjuu_mulvecmatT(&vert_[3*i], &vert_[3*i], mat);
|
||||
mjuu_mulvecmatT(&dvert[3*i], &dvert[3*i], mat);
|
||||
}
|
||||
|
||||
// process normals
|
||||
@@ -1304,9 +1283,9 @@ void mjCMesh::ApplyTransformations() {
|
||||
// scale
|
||||
if (scale[0] != 1 || scale[1] != 1 || scale[2] != 1) {
|
||||
for (int i = 0; i < nvert(); i++) {
|
||||
vert_[3*i + 0] *= scale[0];
|
||||
vert_[3*i + 1] *= scale[1];
|
||||
vert_[3*i + 2] *= scale[2];
|
||||
dvert[3*i + 0] *= scale[0];
|
||||
dvert[3*i + 1] *= scale[1];
|
||||
dvert[3*i + 2] *= scale[2];
|
||||
}
|
||||
|
||||
for (int i = 0; i < nnormal(); i++) {
|
||||
@@ -1338,14 +1317,13 @@ void mjCMesh::ApplyTransformations() {
|
||||
|
||||
|
||||
// find centroid of faces, return total area
|
||||
double mjCMesh::ComputeFaceCentroid(double facecen[3]) const {
|
||||
double mjCMesh::ComputeFaceCentroid(double facecen[3], const double* dvert) const {
|
||||
double total_area = 0;
|
||||
|
||||
for (int i = 0; i < nface(); i++) {
|
||||
// get area and center
|
||||
double area, center[3];
|
||||
area = triangle(nullptr, center, &vert_[3*face_[3*i]],
|
||||
&vert_[3*face_[3*i + 1]], &vert_[3*face_[3*i + 2]]);
|
||||
area = triangle(nullptr, center, &dvert[3*face_[3*i]],
|
||||
&dvert[3*face_[3*i + 1]], &dvert[3*face_[3*i + 2]]);
|
||||
|
||||
// accumulate
|
||||
facecen[0] += area * center[0];
|
||||
@@ -1366,13 +1344,15 @@ double mjCMesh::ComputeFaceCentroid(double facecen[3]) const {
|
||||
|
||||
|
||||
void mjCMesh::Process() {
|
||||
std::vector<double> dvert(vert_.begin(), vert_.end());
|
||||
|
||||
// create half-edge structure (if mesh was in XML)
|
||||
if (halfedge_.empty()) {
|
||||
for (int i = 0; i < nface(); i++) {
|
||||
int v0 = face_[3*i + 0];
|
||||
int v1 = face_[3*i + 1];
|
||||
int v2 = face_[3*i + 2];
|
||||
if (triangle(nullptr, nullptr, &vert_[3*v0], &vert_[3*v1], &vert_[3*v2]) > sqrt(mjMINVAL)) {
|
||||
if (triangle(nullptr, nullptr, &dvert[3*v0], &dvert[3*v1], &dvert[3*v2]) > sqrt(mjMINVAL)) {
|
||||
halfedge_.push_back({v0, v1});
|
||||
halfedge_.push_back({v1, v2});
|
||||
halfedge_.push_back({v2, v0});
|
||||
@@ -1396,7 +1376,7 @@ void mjCMesh::Process() {
|
||||
|
||||
// make graph describing convex hull
|
||||
if (needhull_ || face_.empty()) {
|
||||
MakeGraph();
|
||||
MakeGraph(dvert.data());
|
||||
}
|
||||
|
||||
// no faces: copy from convex hull
|
||||
@@ -1406,7 +1386,7 @@ void mjCMesh::Process() {
|
||||
|
||||
// no normals: make
|
||||
if (normal_.empty()) {
|
||||
MakeNormal();
|
||||
MakeNormal(dvert.data());
|
||||
}
|
||||
|
||||
// check facenormal size
|
||||
@@ -1429,17 +1409,31 @@ void mjCMesh::Process() {
|
||||
}
|
||||
|
||||
if (szgraph_) {
|
||||
MakePolygons();
|
||||
MakePolygons(dvert.data());
|
||||
} else {
|
||||
polygon_map_.resize(nvert());
|
||||
}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
|
||||
// user offset, rotation, scaling
|
||||
ApplyTransformations();
|
||||
ApplyTransformations(dvert.data());
|
||||
|
||||
// find centroid of faces
|
||||
double facecen[3] = {0, 0, 0};
|
||||
if (ComputeFaceCentroid(facecen) < mjMINVAL) {
|
||||
if (ComputeFaceCentroid(facecen, dvert.data()) < mjMINVAL) {
|
||||
throw mjCError(this, "mesh surface area is too small: %s", name.c_str());
|
||||
}
|
||||
|
||||
@@ -1450,12 +1444,12 @@ void mjCMesh::Process() {
|
||||
|
||||
// compute CoM and volume/area
|
||||
if (inertia == mjMESH_INERTIA_SHELL) {
|
||||
surface_ = ComputeSurfaceArea(CoM, facecen);
|
||||
surface_ = ComputeSurfaceArea(CoM, facecen, dvert.data());
|
||||
if (surface_ < mjMINVAL) {
|
||||
throw mjCError(this, "mesh surface area is too small: %s", name.c_str());
|
||||
}
|
||||
} else {
|
||||
if ((volume_ = ComputeVolume(CoM, facecen)) < mjMINVAL) {
|
||||
if ((volume_ = ComputeVolume(CoM, facecen, dvert.data())) < mjMINVAL) {
|
||||
if (volume_ < 0) {
|
||||
throw mjCError(this, "mesh volume is negative (misoriented triangles): %s", name.c_str());
|
||||
} else {
|
||||
@@ -1466,7 +1460,7 @@ void mjCMesh::Process() {
|
||||
}
|
||||
|
||||
// compute inertia
|
||||
double total_volume = ComputeInertia(inert, CoM);
|
||||
double total_volume = ComputeInertia(inert, CoM, dvert.data());
|
||||
if (inertia == mjMESH_INERTIA_SHELL) {
|
||||
surface_ = total_volume;
|
||||
} else {
|
||||
@@ -1510,11 +1504,11 @@ void mjCMesh::Process() {
|
||||
|
||||
// transform CoM to origin
|
||||
for (int i=0; i < nvert(); i++) {
|
||||
vert_[3*i + 0] -= CoM[0];
|
||||
vert_[3*i + 1] -= CoM[1];
|
||||
vert_[3*i + 2] -= CoM[2];
|
||||
dvert[3*i + 0] -= CoM[0];
|
||||
dvert[3*i + 1] -= CoM[1];
|
||||
dvert[3*i + 2] -= CoM[2];
|
||||
}
|
||||
Rotate(quattmp);
|
||||
Rotate(quattmp, dvert.data());
|
||||
|
||||
// save the pos and quat that was used to transform the mesh
|
||||
mjuu_copyvec(pos_, CoM, 3);
|
||||
@@ -1524,11 +1518,11 @@ void mjCMesh::Process() {
|
||||
|
||||
// no radii: make
|
||||
if (!center_) {
|
||||
MakeCenter();
|
||||
MakeCenter(dvert.data());
|
||||
}
|
||||
|
||||
// recompute polygon normals
|
||||
MakePolygonNormals();
|
||||
MakePolygonNormals(dvert.data());
|
||||
|
||||
// make bounding volume hierarchy
|
||||
if (tree_.Bvh().empty()) {
|
||||
@@ -1536,16 +1530,31 @@ void mjCMesh::Process() {
|
||||
face_aabb_.reserve(3*face_.size());
|
||||
tree_.AllocateBoundingVolumes(nface());
|
||||
for (int i = 0; i < nface(); i++) {
|
||||
SetBoundingVolume(i);
|
||||
SetBoundingVolume(i, dvert.data());
|
||||
}
|
||||
tree_.CreateBVH();
|
||||
}
|
||||
|
||||
// make octree
|
||||
if (needsdf) {
|
||||
octree_.SetFace(dvert, face_);
|
||||
octree_.CreateOctree(aamm_);
|
||||
|
||||
if (!plugin.active) {
|
||||
octree_.ComputeSdfCoeffs(dvert.data(), nvert(), face_.data(), nface(), tree_);
|
||||
}
|
||||
}
|
||||
|
||||
// narrow back to float
|
||||
for (int i = 0; i < (int)dvert.size(); i++) {
|
||||
vert_[i] = (float)dvert[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// compute abstract (unitless) inertia, recompute area / volume
|
||||
double mjCMesh::ComputeInertia(double inert[6], const double CoM[3]) const {
|
||||
double mjCMesh::ComputeInertia(double inert[6], const double CoM[3], const double* dvert) const {
|
||||
double total_volume = 0;
|
||||
|
||||
// copy vertices to avoid modifying the original mesh
|
||||
@@ -1554,9 +1563,9 @@ double mjCMesh::ComputeInertia(double inert[6], const double CoM[3]) const {
|
||||
|
||||
// translate vertices to origin in order to compute inertia
|
||||
for (int i = 0; i < nvert(); i++) {
|
||||
vert_centered.push_back(vert_[3*i + 0] - CoM[0]);
|
||||
vert_centered.push_back(vert_[3*i + 1] - CoM[1]);
|
||||
vert_centered.push_back(vert_[3*i + 2] - CoM[2]);
|
||||
vert_centered.push_back(dvert[3*i + 0] - CoM[0]);
|
||||
vert_centered.push_back(dvert[3*i + 1] - CoM[1]);
|
||||
vert_centered.push_back(dvert[3*i + 2] - CoM[2]);
|
||||
}
|
||||
|
||||
// accumulate products of inertia, recompute volume
|
||||
@@ -1611,22 +1620,20 @@ double mjCMesh::ComputeInertia(double inert[6], const double CoM[3]) const {
|
||||
|
||||
|
||||
|
||||
void mjCMesh::Rotate(double quat[4]) {
|
||||
// rotate vertices and normals of mesh by quaternion
|
||||
void mjCMesh::Rotate(double quat[4], double* dvert) {
|
||||
double neg[4] = {quat[0], -quat[1], -quat[2], -quat[3]};
|
||||
double mat[9];
|
||||
mjuu_quat2mat(mat, neg);
|
||||
int nv = nvert();
|
||||
for (int i = 0; i < nv; i++) {
|
||||
mjuu_mulvecmat(&vert_[3*i], &vert_[3*i], mat);
|
||||
mjuu_mulvecmat(&dvert[3*i], &dvert[3*i], mat);
|
||||
|
||||
// axis-aligned bounding box
|
||||
aamm_[0] = std::min(aamm_[0], vert_[3*i + 0]);
|
||||
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]);
|
||||
aamm_[0] = std::min(aamm_[0], dvert[3*i + 0]);
|
||||
aamm_[3] = std::max(aamm_[3], dvert[3*i + 0]);
|
||||
aamm_[1] = std::min(aamm_[1], dvert[3*i + 1]);
|
||||
aamm_[4] = std::max(aamm_[4], dvert[3*i + 1]);
|
||||
aamm_[2] = std::min(aamm_[2], dvert[3*i + 2]);
|
||||
aamm_[5] = std::max(aamm_[5], dvert[3*i + 2]);
|
||||
}
|
||||
|
||||
for (int i=0; i < nnormal(); i++) {
|
||||
@@ -1698,7 +1705,7 @@ double mjCMesh::GetVolumeRef() const {
|
||||
|
||||
|
||||
// make graph describing convex hull
|
||||
void mjCMesh::MakeGraph() {
|
||||
void mjCMesh::MakeGraph(const double* dvert) {
|
||||
int adr, ok, curlong, totlong, exitcode;
|
||||
facetT* facet, **facetp;
|
||||
vertexT* vertex, *vertex1, **vertex1p;
|
||||
@@ -1727,7 +1734,7 @@ void mjCMesh::MakeGraph() {
|
||||
if (!exitcode) {
|
||||
// actual init
|
||||
qh_initflags(qh, const_cast<char*>(qhopt.c_str()));
|
||||
qh_init_B(qh, vert_.data(), nvert(), 3, qh_False);
|
||||
qh_init_B(qh, const_cast<double*>(dvert), nvert(), 3, qh_False);
|
||||
|
||||
// construct convex hull
|
||||
qh_qhull(qh);
|
||||
@@ -2402,7 +2409,7 @@ void mjCMesh::MakeCone(int nedge, double radius) {
|
||||
|
||||
|
||||
// compute vertex normals
|
||||
void mjCMesh::MakeNormal() {
|
||||
void mjCMesh::MakeNormal(const double* dvert) {
|
||||
// only if normal data is missing
|
||||
if (!normal_.empty()) {
|
||||
return;
|
||||
@@ -2426,8 +2433,8 @@ void mjCMesh::MakeNormal() {
|
||||
// get triangle edges
|
||||
double vec01[3], vec02[3];
|
||||
for (int j=0; j < 3; j++) {
|
||||
vec01[j] = vert_[3*vertid[1]+j] - vert_[3*vertid[0]+j];
|
||||
vec02[j] = vert_[3*vertid[2]+j] - vert_[3*vertid[0]+j];
|
||||
vec01[j] = dvert[3*vertid[1]+j] - dvert[3*vertid[0]+j];
|
||||
vec02[j] = dvert[3*vertid[2]+j] - dvert[3*vertid[0]+j];
|
||||
}
|
||||
|
||||
// compute face normal
|
||||
@@ -2461,8 +2468,8 @@ void mjCMesh::MakeNormal() {
|
||||
// get triangle edges
|
||||
double vec01[3], vec02[3];
|
||||
for (int j=0; j < 3; j++) {
|
||||
vec01[j] = vert_[3*vertid[1]+j] - vert_[3*vertid[0]+j];
|
||||
vec02[j] = vert_[3*vertid[2]+j] - vert_[3*vertid[0]+j];
|
||||
vec01[j] = dvert[3*vertid[1]+j] - dvert[3*vertid[0]+j];
|
||||
vec02[j] = dvert[3*vertid[2]+j] - dvert[3*vertid[0]+j];
|
||||
}
|
||||
|
||||
// compute face normal
|
||||
@@ -2514,7 +2521,7 @@ void mjCMesh::MakeNormal() {
|
||||
|
||||
|
||||
// compute face circumradii
|
||||
void mjCMesh::MakeCenter() {
|
||||
void mjCMesh::MakeCenter(const double* dvert) {
|
||||
if (center_) {
|
||||
return;
|
||||
}
|
||||
@@ -2530,8 +2537,8 @@ void mjCMesh::MakeCenter() {
|
||||
// get triangle edges
|
||||
double a[3], b[3];
|
||||
for (int j=0; j < 3; j++) {
|
||||
a[j] = vert_[3*vertid[0]+j] - vert_[3*vertid[2]+j];
|
||||
b[j] = vert_[3*vertid[1]+j] - vert_[3*vertid[2]+j];
|
||||
a[j] = dvert[3*vertid[0]+j] - dvert[3*vertid[2]+j];
|
||||
b[j] = dvert[3*vertid[1]+j] - dvert[3*vertid[2]+j];
|
||||
}
|
||||
|
||||
// compute face normal
|
||||
@@ -2550,20 +2557,20 @@ void mjCMesh::MakeCenter() {
|
||||
norm_a_2 * b[2] - norm_b_2 * a[2]
|
||||
};
|
||||
mjuu_crossvec(res, vec, nrm);
|
||||
center_[3*i+0] = res[0]/(2*area*area) + vert_[3*vertid[2]+0];
|
||||
center_[3*i+1] = res[1]/(2*area*area) + vert_[3*vertid[2]+1];
|
||||
center_[3*i+2] = res[2]/(2*area*area) + vert_[3*vertid[2]+2];
|
||||
center_[3*i+0] = res[0]/(2*area*area) + dvert[3*vertid[2]+0];
|
||||
center_[3*i+1] = res[1]/(2*area*area) + dvert[3*vertid[2]+1];
|
||||
center_[3*i+2] = res[2]/(2*area*area) + dvert[3*vertid[2]+2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// compute the normals of the polygons
|
||||
void mjCMesh::MakePolygonNormals() {
|
||||
void mjCMesh::MakePolygonNormals(const double* dvert) {
|
||||
for (int i = 0; i < polygons_.size(); ++i) {
|
||||
double n[3];
|
||||
mjuu_makenormal(n, &vert_[3*polygons_[i][0]], &vert_[3*polygons_[i][1]],
|
||||
&vert_[3*polygons_[i][2]]);
|
||||
mjuu_makenormal(n, &dvert[3*polygons_[i][0]], &dvert[3*polygons_[i][1]],
|
||||
&dvert[3*polygons_[i][2]]);
|
||||
polygon_normals_[3*i + 0] = n[0];
|
||||
polygon_normals_[3*i + 1] = n[1];
|
||||
polygon_normals_[3*i + 2] = n[2];
|
||||
@@ -2802,7 +2809,7 @@ struct PairHash {
|
||||
|
||||
|
||||
// merge coplanar mesh triangular faces into polygonal sides to represent the geometry of the mesh
|
||||
void mjCMesh::MakePolygons() {
|
||||
void mjCMesh::MakePolygons(const double* dvert) {
|
||||
constexpr double kAngleTol = 0.01;
|
||||
std::unordered_map<std::pair<double, double>, MeshPolygon, PairHash> mesh_polygons;
|
||||
polygons_.clear();
|
||||
@@ -2819,9 +2826,9 @@ void mjCMesh::MakePolygons() {
|
||||
int vi1 = faces[3*i + 0];
|
||||
int vi2 = faces[3*i + 1];
|
||||
int vi3 = faces[3*i + 2];
|
||||
double* v1 = &vert_[3*vi1];
|
||||
double* v2 = &vert_[3*vi2];
|
||||
double* v3 = &vert_[3*vi3];
|
||||
const double* v1 = &dvert[3*vi1];
|
||||
const double* v2 = &dvert[3*vi2];
|
||||
const double* v3 = &dvert[3*vi3];
|
||||
|
||||
std::pair<double, double> key;
|
||||
if (!MeshPolygonKey(key, v1, v2, v3, kAngleTol)) {
|
||||
|
||||
@@ -587,6 +587,7 @@ void mjCOctree::CopyCoeff(mjtNum* coeff) const {
|
||||
|
||||
|
||||
void mjCOctree::SetFace(const std::vector<double>& vert, const std::vector<int>& face) {
|
||||
face_.reserve(face.size()/3);
|
||||
for (int i = 0; i < face.size(); i += 3) {
|
||||
std::array<double, 3> v0 = {vert[3*face[i+0]], vert[3*face[i+0]+1], vert[3*face[i+0]+2]};
|
||||
std::array<double, 3> v1 = {vert[3*face[i+1]], vert[3*face[i+1]+1], vert[3*face[i+1]+2]};
|
||||
@@ -613,6 +614,8 @@ void mjCOctree::Make(std::vector<Triangle>& elements) {
|
||||
|
||||
|
||||
void mjCOctree::CreateOctree(const double aamm[6]) {
|
||||
Clear();
|
||||
|
||||
double aabb[6] = {(aamm[0] + aamm[3]) / 2, (aamm[1] + aamm[4]) / 2, (aamm[2] + aamm[5]) / 2,
|
||||
(aamm[3] - aamm[0]) / 2, (aamm[4] - aamm[1]) / 2, (aamm[5] - aamm[2]) / 2};
|
||||
double box[6] = {aabb[0] - 1.1 * aabb[3], aabb[1] - 1.1 * aabb[4], aabb[2] - 1.1 * aabb[5],
|
||||
|
||||
+19
-17
@@ -299,8 +299,11 @@ class mjCOctree : public mjCOctree_ {
|
||||
sizeof(Point) * vert_.size();
|
||||
}
|
||||
void Clear() {
|
||||
nnode_ = 0;
|
||||
nvert_ = 0;
|
||||
node_.clear();
|
||||
face_.clear();
|
||||
vert_.clear();
|
||||
hang_.clear();
|
||||
}
|
||||
void AddCoeff(int n, int v, double coeff) { node_[n].coeff[v] = coeff; }
|
||||
double Coeff(int n, int v) const { return node_[n].coeff[v]; }
|
||||
@@ -1071,7 +1074,7 @@ class mjCMesh_ : public mjCBase {
|
||||
std::string content_type_ = ""; // content type of file
|
||||
std::string file_; // mesh file
|
||||
mjResource* resource_ = nullptr; // resource for mesh file
|
||||
std::vector<double> vert_; // vertex data
|
||||
std::vector<float> vert_; // vertex data
|
||||
std::vector<float> normal_; // normal data
|
||||
std::vector<float> texcoord_; // texcoord data
|
||||
std::vector<int> face_; // vertex indices
|
||||
@@ -1156,8 +1159,8 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
|
||||
const double* Refquat() const { return refquat; }
|
||||
const double* Scale() const { return scale; }
|
||||
bool SmoothNormal() const { return smoothnormal; }
|
||||
const std::vector<double>& Vert() const { return vert_; }
|
||||
double Vert(int i) const { return vert_[i]; }
|
||||
const std::vector<float>& Vert() const { return vert_; }
|
||||
float Vert(int i) const { return vert_[i]; }
|
||||
const std::vector<float>& UserVert() const { return spec_vert_; }
|
||||
const std::vector<float>& UserNormal() const { return spec_normal_; }
|
||||
const std::vector<float>& Texcoord() const { return texcoord_; }
|
||||
@@ -1233,7 +1236,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
|
||||
void CopyPolygonNormals(mjtNum* arr);
|
||||
|
||||
// sets properties of a bounding volume given a face id
|
||||
void SetBoundingVolume(int faceid);
|
||||
void SetBoundingVolume(int faceid, const double* dvert);
|
||||
|
||||
// load from OBJ, STL, or MSH file; throws mjCError on failure
|
||||
void LoadFromResource(mjResource* resource, bool remove_repeated = false);
|
||||
@@ -1264,22 +1267,22 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
|
||||
void LoadMSH(mjResource* resource, bool remove_repeated); // load mesh in MSH BIN format
|
||||
|
||||
void LoadSDF(); // generate mesh using marching cubes
|
||||
void MakeGraph(); // make graph of convex hull
|
||||
void MakeGraph(const double* dvert); // make graph of convex hull
|
||||
void CopyGraph(); // copy graph into face data
|
||||
void MakeNormal(); // compute vertex normals
|
||||
void MakeCenter(); // compute face circumcircle data
|
||||
void MakeNormal(const double* dvert); // compute vertex normals
|
||||
void MakeCenter(const double* dvert); // compute face circumcircle data
|
||||
void Process(); // compute inertial properties
|
||||
void ApplyTransformations(); // apply user transformations
|
||||
double ComputeFaceCentroid(double[3]) const; // compute centroid of all faces
|
||||
void ApplyTransformations(double* dvert); // apply user transformations
|
||||
double ComputeFaceCentroid(double[3], const double* dvert) const;
|
||||
void CheckInitialMesh() const; // check if initial mesh is valid
|
||||
void CopyPlugin();
|
||||
void Rotate(double quat[4]); // rotate mesh by quaternion
|
||||
void Rotate(double quat[4], double* dvert); // rotate mesh by quaternion
|
||||
void Transform(double pos[3], double quat[4]); // transform mesh by position and quaternion
|
||||
void MakePolygons(); // compute the polygon sides of the mesh
|
||||
void MakePolygonNormals(); // compute the normals of the polygons
|
||||
void MakePolygons(const double* dvert); // compute the polygon sides of the mesh
|
||||
void MakePolygonNormals(const double* dvert); // compute the normals of the polygons
|
||||
|
||||
// computes the inertia matrix of the mesh given the type of inertia
|
||||
double ComputeInertia(double inert[6], const double CoM[3]) const;
|
||||
double ComputeInertia(double inert[6], const double CoM[3], const double* dvert) const;
|
||||
|
||||
int* GraphFaces() const {
|
||||
return graph_ + 2 + 3*(graph_[0] + graph_[1]);
|
||||
@@ -1295,9 +1298,8 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
|
||||
std::vector<std::vector<int>> polygon_map_; // map from vertex to polygon
|
||||
|
||||
// compute the volume and center-of-mass of the mesh given the face centroid
|
||||
double ComputeVolume(double CoM[3], const double facecen[3]) const;
|
||||
// compute the surface area and center-of-mass of the mesh given the face centroid
|
||||
double ComputeSurfaceArea(double CoM[3], const double facecen[3]) const;
|
||||
double ComputeVolume(double CoM[3], const double facecen[3], const double* dvert) const;
|
||||
double ComputeSurfaceArea(double CoM[3], const double facecen[3], const double* dvert) const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ mjResource* mju_openResource(const char* dir, const char* name,
|
||||
if (non_const_vfs == nullptr) {
|
||||
mjVFS* local_vfs = (mjVFS*)mju_malloc(sizeof(mjVFS));
|
||||
mj_defaultVFS(local_vfs);
|
||||
mujoco::user::VFS::Upcast(local_vfs)->SetToSelfDestruct([](mjVFS* ptr) {
|
||||
mj_deleteVFS(ptr);
|
||||
mju_free(ptr);
|
||||
mujoco::user::VFS::Upcast(local_vfs)->SetToSelfDestruct([=]() {
|
||||
mj_deleteVFS(local_vfs);
|
||||
mju_free(local_vfs);
|
||||
});
|
||||
|
||||
non_const_vfs = local_vfs;
|
||||
|
||||
@@ -105,7 +105,8 @@ std::string StripPathAndLower(std::string path) {
|
||||
|
||||
namespace mujoco::user {
|
||||
|
||||
VFS::VFS(mjVFS* vfs) : self_(vfs) {
|
||||
VFS::VFS(mjVFS* vfs) {
|
||||
wrapped_vfs_.impl_ = this;
|
||||
mjp_defaultResourceProvider(&default_provider_);
|
||||
default_provider_.open = [](mjResource* res) {
|
||||
return OpenFile(res->name, res);
|
||||
@@ -121,7 +122,7 @@ VFS::VFS(mjVFS* vfs) : self_(vfs) {
|
||||
};
|
||||
|
||||
default_provider_.prefix = nullptr;
|
||||
default_mount_.vfs = self_;
|
||||
default_mount_.vfs = &wrapped_vfs_;
|
||||
default_mount_.provider = &default_provider_;
|
||||
default_mount_.data = nullptr;
|
||||
default_mount_.name = nullptr;
|
||||
@@ -245,7 +246,7 @@ int VFS::Read(mjResource* resource, const void** buffer) {
|
||||
VFS::ResourcePtr VFS::CreateResource(std::string_view name,
|
||||
const mjpResourceProvider* provider) {
|
||||
mjResource* res = new mjResource();
|
||||
res->vfs = self_;
|
||||
res->vfs = &wrapped_vfs_;
|
||||
res->provider = provider;
|
||||
res->data = nullptr;
|
||||
res->name = new char[name.size() + 1];
|
||||
@@ -312,11 +313,14 @@ mjResource* VFS::FindMount(const std::string& fullpath) {
|
||||
|
||||
void VFS::MaybeSelfDestruct() {
|
||||
if (destructor_) {
|
||||
destructor_(self_);
|
||||
// Copy the destructor to a local variable so that we can destroy `this`
|
||||
// object within the destructor.
|
||||
auto fn = std::move(destructor_);
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
void VFS::SetToSelfDestruct(std::function<void(mjVFS*)> destructor) {
|
||||
void VFS::SetToSelfDestruct(std::function<void()> destructor) {
|
||||
destructor_ = std::move(destructor);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -96,7 +96,7 @@ class VFS {
|
||||
// This is useful for when you want to create a temporary VFS instance with
|
||||
// a lifetime tied to a single mjResource to be opened. The `destructor`
|
||||
// should be set to `delete this` and any other cleanup that needs to happen.
|
||||
void SetToSelfDestruct(std::function<void(mjVFS*)> destructor);
|
||||
void SetToSelfDestruct(std::function<void()> destructor);
|
||||
|
||||
// Converts the public C-API pointer to the internal C++ class.
|
||||
static VFS* Upcast(mjVFS* vfs);
|
||||
@@ -117,13 +117,13 @@ class VFS {
|
||||
// that `this` will be invalidated after this call.
|
||||
void MaybeSelfDestruct();
|
||||
|
||||
mjVFS* self_;
|
||||
mjVFS wrapped_vfs_;
|
||||
std::mutex mutex_; // Protects open_resources_ and mounts_.
|
||||
std::unordered_map<mjResource*, ResourcePtr> open_resources_;
|
||||
std::unordered_map<std::string, ResourcePtr> mounts_;
|
||||
mjResource default_mount_;
|
||||
mjpResourceProvider default_provider_;
|
||||
std::function<void(mjVFS*)> destructor_;
|
||||
std::function<void()> destructor_;
|
||||
};
|
||||
|
||||
} // namespace mujoco::user
|
||||
|
||||
@@ -1086,6 +1086,71 @@ TEST_F(MjCMeshTest, MeshScale) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, NegativeScaleUserMeshCompiles) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="example_mesh" scale="-1 1 1" inertia="exact"
|
||||
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
|
||||
face="0 2 1 0 3 2 1 3 0 1 2 3" />
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="example_mesh"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
char error[1024];
|
||||
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
|
||||
ASSERT_THAT(model, NotNull()) << error;
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, NegativeScaleUserMeshMatchesPositiveScale) {
|
||||
static constexpr char pos_xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="pos_mesh"
|
||||
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
|
||||
face="0 2 1 0 3 2 1 3 0 1 2 3" />
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="pos_mesh"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
static constexpr char neg_xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="neg_mesh" scale="-1 1 1"
|
||||
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
|
||||
face="0 2 1 0 3 2 1 3 0 1 2 3" />
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="neg_mesh"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
char error[1024];
|
||||
mjModel* pos_model = LoadModelFromString(pos_xml, error, sizeof(error));
|
||||
ASSERT_THAT(pos_model, NotNull()) << error;
|
||||
|
||||
mjModel* neg_model = LoadModelFromString(neg_xml, error, sizeof(error));
|
||||
ASSERT_THAT(neg_model, NotNull()) << error;
|
||||
|
||||
ASSERT_EQ(pos_model->nmeshface, neg_model->nmeshface);
|
||||
|
||||
for (int i = 0; i < pos_model->nmeshface; i++) {
|
||||
EXPECT_EQ(pos_model->mesh_face[3*i + 0], neg_model->mesh_face[3*i + 0]);
|
||||
EXPECT_EQ(pos_model->mesh_face[3*i + 1], neg_model->mesh_face[3*i + 2]);
|
||||
EXPECT_EQ(pos_model->mesh_face[3*i + 2], neg_model->mesh_face[3*i + 1]);
|
||||
}
|
||||
|
||||
mj_deleteModel(pos_model);
|
||||
mj_deleteModel(neg_model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, ShellInertiaTest) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
|
||||
@@ -407,5 +407,29 @@ TEST_F(UserVfsTest, StackedMounts) {
|
||||
EXPECT_EQ(test2, expect2);
|
||||
EXPECT_EQ(test3, expect3);
|
||||
}
|
||||
|
||||
TEST_F(UserVfsTest, MoveVfs) {
|
||||
// Create and move a VFS to another address.
|
||||
mjVFS* original = new mjVFS();
|
||||
mj_defaultVFS(original);
|
||||
mjVFS vfs = *original;
|
||||
delete original;
|
||||
|
||||
std::string buffer = "<mujoco/>";
|
||||
mj_addBufferVFS(&vfs, "model", static_cast<const void*>(buffer.c_str()),
|
||||
buffer.size());
|
||||
|
||||
mjResource* resource = mju_openResource("", "model", &vfs, nullptr, 0);
|
||||
ASSERT_THAT(resource, NotNull());
|
||||
|
||||
const void* out = nullptr;
|
||||
const int size = mju_readResource(resource, &out);
|
||||
EXPECT_GT(size, 0);
|
||||
EXPECT_THAT(out, NotNull());
|
||||
|
||||
mju_closeResource(resource);
|
||||
mj_deleteVFS(&vfs);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -37,7 +37,7 @@ public class MujocoBinaryRetriever {
|
||||
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/mujoco.dylib") == null) {
|
||||
File.Copy(
|
||||
"/Applications/MuJoCo.app/Contents/Frameworks" +
|
||||
"/mujoco.framework/Versions/Current/libmujoco.3.6.0.dylib",
|
||||
"/mujoco.framework/Versions/Current/libmujoco.3.7.0.dylib",
|
||||
mujocoPath + "/mujoco.dylib");
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class MujocoBinaryRetriever {
|
||||
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/libmujoco.so") == null) {
|
||||
File.Copy(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) +
|
||||
"/.mujoco/mujoco-3.6.0/lib/libmujoco.so.3.6.0",
|
||||
"/.mujoco/mujoco-3.7.0/lib/libmujoco.so.3.7.0",
|
||||
mujocoPath + "/libmujoco.so");
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public const int mjMAXLINEPNT = 1001;
|
||||
public const int mjMAXPLANEGRID = 200;
|
||||
public const bool THIRD_PARTY_MUJOCO_MJXMACRO_H_ = true;
|
||||
public const bool THIRD_PARTY_MUJOCO_MUJOCO_H_ = true;
|
||||
public const int mjVERSION_HEADER = 3006000;
|
||||
public const int mjVERSION_HEADER = 3007000;
|
||||
|
||||
|
||||
// ------------------------------------Enums------------------------------------
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "org.mujoco",
|
||||
"displayName": "MuJoCo",
|
||||
"version": "3.6.0",
|
||||
"version": "3.7.0",
|
||||
"description": "MuJoCo importer and runtime plug-in",
|
||||
"dependencies": {},
|
||||
"author": {
|
||||
|
||||
+14
-6
@@ -307,7 +307,7 @@ The function `mjv_updateScene` populates an `mjvScene` object with information
|
||||
from `mjModel` and `mjData`.
|
||||
```typescript
|
||||
// Create instances of the necessary structs.
|
||||
const model = mujoco.MjModel.loadFromXML(xmlContent);
|
||||
const model = mujoco.MjModel.from_xml_string(xmlContent);
|
||||
const data = new mujoco.MjData(model);
|
||||
const scene = new mujoco.MjvScene(model, 1000);
|
||||
const option = new mujoco.MjvOption();
|
||||
@@ -348,23 +348,31 @@ mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value
|
||||
```
|
||||
|
||||
### Constants
|
||||
Scalar constants will be accessed the same way they are on python, simply:
|
||||
Scalar constants can be accessed as properties:
|
||||
|
||||
```javascript
|
||||
mujoco.mjNEQDATA
|
||||
```
|
||||
|
||||
Due to Embind limitations, more complex constants that are not scalar, but are
|
||||
represented in more dimensions are exposed as functions. E.g. to use
|
||||
`mujoco.mjFRAMESTRING` you will need to call a function:
|
||||
Non-scalar constants like `mjFRAMESTRING` are also accessed as properties, and
|
||||
return JavaScript arrays:
|
||||
|
||||
```javascript
|
||||
mujoco.get_mjFRAMESTRING()
|
||||
mujoco.mjFRAMESTRING
|
||||
```
|
||||
|
||||
This will return a javascript array representation of the values in MuJoCo
|
||||
`mjFRAMESTRING`.
|
||||
|
||||
> [!NOTE]
|
||||
> You will notice constants like `mjFRAMESTRING` are typed as `any`. This is
|
||||
> because they are bound using `emscripten::val::array()` in C++, and Embind
|
||||
> maps `emscripten::val` to `any` in TypeScript definition files. While
|
||||
> `EMSCRIPTEN_DECLARE_VAL_TYPE(StringArray)` could be used to define
|
||||
> `StringArray` as an alias for `emscripten::val` and hint to Embind how to
|
||||
> handle conversions in function signatures or when using `.as()` it does not
|
||||
> change how `emscripten::constant` infers types for properties.
|
||||
|
||||
## Development
|
||||
|
||||
In order to change the bindings you will need to change the [`bindings.cc`](codegen/generated/bindings.cc)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/em_asm.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -8361,6 +8362,39 @@ std::unique_ptr<MjModel> mj_loadModel_wrapper(std::string filename, const MjVFS&
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> from_xml_string_wrapper_1(const std::string& xml) {
|
||||
mjVFS vfs;
|
||||
mj_defaultVFS(&vfs);
|
||||
const char* filename = "model.xml";
|
||||
int add_result = mj_addBufferVFS(&vfs, filename, xml.c_str(), xml.length());
|
||||
if (add_result != 0) {
|
||||
mj_deleteVFS(&vfs);
|
||||
mju_error("Could not add XML string to VFS: %d", add_result);
|
||||
}
|
||||
char error[1000];
|
||||
mjModel* model = mj_loadXML(filename, &vfs, error, sizeof(error));
|
||||
mj_deleteVFS(&vfs);
|
||||
if (!model) {
|
||||
mju_error("Loading error: %s\n", error);
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> from_xml_string_wrapper_2(const std::string& xml, const MjVFS& vfs) {
|
||||
std::string filename = "model.xml";
|
||||
int add_result = mj_addBufferVFS(vfs.get(), filename.c_str(), xml.c_str(), xml.length());
|
||||
if (add_result != 0) {
|
||||
mju_error("Could not add XML string to VFS: %d", add_result);
|
||||
}
|
||||
char error[1000];
|
||||
mjModel* model = mj_loadXML(filename.c_str(), vfs.get(), error, sizeof(error));
|
||||
mj_deleteFileVFS(vfs.get(), filename.c_str());
|
||||
if (!model) {
|
||||
mju_error("Loading error: %s\n", error);
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjSpec> parseXMLString_wrapper(const std::string &xml) {
|
||||
char error[1000];
|
||||
mjSpec *ptr = mj_parseXMLString(xml.c_str(), nullptr, error, sizeof(error));
|
||||
@@ -8599,7 +8633,7 @@ void mj_fwdVelocity_wrapper(const MjModel& m, MjData& d) {
|
||||
mj_fwdVelocity(m.get(), d.get());
|
||||
}
|
||||
|
||||
mjtNum mj_geomDistance_wrapper(const MjModel& m, const MjData& d, int geom1, int geom2, mjtNum distmax, const val& fromto) {
|
||||
mjtNum mj_geomDistance_wrapper(const MjModel& m, MjData& d, int geom1, int geom2, mjtNum distmax, const val& fromto) {
|
||||
UNPACK_NULLABLE_VALUE(mjtNum, fromto);
|
||||
CHECK_SIZE(fromto, 6);
|
||||
return mj_geomDistance(m.get(), d.get(), geom1, geom2, distmax, fromto_.data());
|
||||
@@ -11596,9 +11630,16 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
.property("useexisting", &MjLROpt::useexisting, &MjLROpt::set_useexisting, reference())
|
||||
.property("uselimit", &MjLROpt::uselimit, &MjLROpt::set_uselimit, reference());
|
||||
emscripten::class_<MjModel>("MjModel")
|
||||
// mj_loadXML is deprecated and will be removed in a future release
|
||||
.class_function("mj_loadXML", emscripten::select_overload<std::unique_ptr<MjModel>(std::string)>(&mj_loadXML_wrapper_1))
|
||||
.class_function("mj_loadXML", emscripten::select_overload<std::unique_ptr<MjModel>(std::string, const MjVFS&)>(&mj_loadXML_wrapper_2))
|
||||
// mj_loadModel is deprecated and will be removed in a future release
|
||||
.class_function("mj_loadModel", &mj_loadModel_wrapper)
|
||||
.class_function("from_binary_path", &mj_loadModel_wrapper)
|
||||
.class_function("from_xml_string", emscripten::select_overload<std::unique_ptr<MjModel>(const std::string&)>(&from_xml_string_wrapper_1))
|
||||
.class_function("from_xml_string", emscripten::select_overload<std::unique_ptr<MjModel>(const std::string&, const MjVFS&)>(&from_xml_string_wrapper_2))
|
||||
.class_function("from_xml_path", emscripten::select_overload<std::unique_ptr<MjModel>(std::string)>(&mj_loadXML_wrapper_1))
|
||||
.class_function("from_xml_path", emscripten::select_overload<std::unique_ptr<MjModel>(std::string, const MjVFS&)>(&mj_loadXML_wrapper_2))
|
||||
.constructor<const MjModel &>()
|
||||
// Binds the functions on MjModel that return accessors.
|
||||
#define X_ACCESSOR(NAME, Name, OBJTYPE, field_name, nfield) \
|
||||
@@ -13379,6 +13420,8 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
// as using std::optional<MjVFS> caused memory errors due to missing copy/move constructors.
|
||||
function("mj_compile", emscripten::select_overload<std::unique_ptr<MjModel>(const MjSpec&)>(&mj_compile_wrapper_1));
|
||||
function("mj_compile", emscripten::select_overload<std::unique_ptr<MjModel>(const MjSpec&, const MjVFS&)>(&mj_compile_wrapper_2));
|
||||
function("from_xml_string", emscripten::select_overload<std::unique_ptr<MjModel>(const std::string&)>(&from_xml_string_wrapper_1));
|
||||
function("from_xml_string", emscripten::select_overload<std::unique_ptr<MjModel>(const std::string&, const MjVFS&)>(&from_xml_string_wrapper_2));
|
||||
|
||||
emscripten::class_<WasmBuffer<float>>("FloatBuffer")
|
||||
.constructor<int>()
|
||||
@@ -13409,6 +13452,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
.function("GetView", &WasmBuffer<uint8_t>::GetView);
|
||||
|
||||
emscripten::register_vector<std::string>("mjStringVec");
|
||||
emscripten::register_vector<std::vector<std::string>>("mjStringVecVec");
|
||||
emscripten::register_vector<int>("mjIntVec");
|
||||
emscripten::register_vector<mjIntVec>("mjIntVecVec");
|
||||
emscripten::register_vector<float>("mjFloatVec");
|
||||
@@ -13453,7 +13497,6 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
emscripten::constant("mjPI", mjPI);
|
||||
emscripten::constant("mjVERSION_HEADER", mjVERSION_HEADER);
|
||||
|
||||
// These complex constants are bound using function() rather than constant()
|
||||
emscripten::function("get_mjDISABLESTRING", &get_mjDISABLESTRING);
|
||||
emscripten::function("get_mjENABLESTRING", &get_mjENABLESTRING);
|
||||
emscripten::function("get_mjFRAMESTRING", &get_mjFRAMESTRING);
|
||||
@@ -13461,6 +13504,31 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
emscripten::function("get_mjRNDSTRING", &get_mjRNDSTRING);
|
||||
emscripten::function("get_mjTIMERSTRING", &get_mjTIMERSTRING);
|
||||
emscripten::function("get_mjVISSTRING", &get_mjVISSTRING);
|
||||
// Bind these complex constants as properties on the module object.
|
||||
// We use emscripten::constant with emscripten::val::array() to type them
|
||||
// as `any` in TypeScript. At runtime, the EM_ASM block below overrides
|
||||
// these properties with getters that return native JavaScript arrays
|
||||
// (string[] or string[][]) via the get_ functions above, which is more
|
||||
// performant and idiomatic than vector wrappers.
|
||||
emscripten::constant("mjDISABLESTRING", emscripten::val::array());
|
||||
emscripten::constant("mjENABLESTRING", emscripten::val::array());
|
||||
emscripten::constant("mjFRAMESTRING", emscripten::val::array());
|
||||
emscripten::constant("mjLABELSTRING", emscripten::val::array());
|
||||
emscripten::constant("mjRNDSTRING", emscripten::val::array());
|
||||
emscripten::constant("mjTIMERSTRING", emscripten::val::array());
|
||||
emscripten::constant("mjVISSTRING", emscripten::val::array());
|
||||
EM_ASM({
|
||||
if (typeof Module !== "undefined") {
|
||||
"mjDISABLESTRING mjENABLESTRING mjFRAMESTRING mjLABELSTRING mjRNDSTRING mjTIMERSTRING mjVISSTRING".split(" ").forEach(function(name) {
|
||||
Object.defineProperty(Module, name, {
|
||||
get: function() { return Module["get_" + name](); },
|
||||
set: function(v) { },
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mujoco::wasm
|
||||
|
||||
@@ -549,6 +549,9 @@ def _build_struct_bindings(
|
||||
MJDATA_ACCESSORS
|
||||
#undef X_ACCESSOR""".lstrip())
|
||||
elif w == "MjModel":
|
||||
builder.line(
|
||||
"// mj_loadXML is deprecated and will be removed in a future release"
|
||||
)
|
||||
builder.line(
|
||||
'.class_function("mj_loadXML",'
|
||||
" emscripten::select_overload<std::unique_ptr<MjModel>(std::string)>(&mj_loadXML_wrapper_1))"
|
||||
@@ -561,7 +564,31 @@ def _build_struct_bindings(
|
||||
f2 = common.wrapped_function_name(
|
||||
introspect_functions.FUNCTIONS["mj_loadModel"]
|
||||
)
|
||||
builder.line(
|
||||
"// mj_loadModel is deprecated and will be removed in a future"
|
||||
" release"
|
||||
)
|
||||
builder.line(f'.class_function("mj_loadModel", &{f2})')
|
||||
builder.line(f'.class_function("from_binary_path", &{f2})')
|
||||
builder.line(
|
||||
'.class_function("from_xml_string",'
|
||||
" emscripten::select_overload<std::unique_ptr<MjModel>(const"
|
||||
" std::string&)>(&from_xml_string_wrapper_1))"
|
||||
)
|
||||
builder.line(
|
||||
'.class_function("from_xml_string",'
|
||||
" emscripten::select_overload<std::unique_ptr<MjModel>(const"
|
||||
" std::string&, const MjVFS&)>(&from_xml_string_wrapper_2))"
|
||||
)
|
||||
builder.line(
|
||||
'.class_function("from_xml_path",'
|
||||
" emscripten::select_overload<std::unique_ptr<MjModel>(std::string)>(&mj_loadXML_wrapper_1))"
|
||||
)
|
||||
builder.line(
|
||||
'.class_function("from_xml_path",'
|
||||
" emscripten::select_overload<std::unique_ptr<MjModel>(std::string,"
|
||||
" const MjVFS&)>(&mj_loadXML_wrapper_2))"
|
||||
)
|
||||
builder.line(".constructor<const MjModel &>()")
|
||||
builder.line("""
|
||||
// Binds the functions on MjModel that return accessors.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/em_asm.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -746,6 +747,39 @@ std::unique_ptr<MjModel> mj_loadModel_wrapper(std::string filename, const MjVFS&
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> from_xml_string_wrapper_1(const std::string& xml) {
|
||||
mjVFS vfs;
|
||||
mj_defaultVFS(&vfs);
|
||||
const char* filename = "model.xml";
|
||||
int add_result = mj_addBufferVFS(&vfs, filename, xml.c_str(), xml.length());
|
||||
if (add_result != 0) {
|
||||
mj_deleteVFS(&vfs);
|
||||
mju_error("Could not add XML string to VFS: %d", add_result);
|
||||
}
|
||||
char error[1000];
|
||||
mjModel* model = mj_loadXML(filename, &vfs, error, sizeof(error));
|
||||
mj_deleteVFS(&vfs);
|
||||
if (!model) {
|
||||
mju_error("Loading error: %s\n", error);
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> from_xml_string_wrapper_2(const std::string& xml, const MjVFS& vfs) {
|
||||
std::string filename = "model.xml";
|
||||
int add_result = mj_addBufferVFS(vfs.get(), filename.c_str(), xml.c_str(), xml.length());
|
||||
if (add_result != 0) {
|
||||
mju_error("Could not add XML string to VFS: %d", add_result);
|
||||
}
|
||||
char error[1000];
|
||||
mjModel* model = mj_loadXML(filename.c_str(), vfs.get(), error, sizeof(error));
|
||||
mj_deleteFileVFS(vfs.get(), filename.c_str());
|
||||
if (!model) {
|
||||
mju_error("Loading error: %s\n", error);
|
||||
}
|
||||
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjSpec> parseXMLString_wrapper(const std::string &xml) {
|
||||
char error[1000];
|
||||
mjSpec *ptr = mj_parseXMLString(xml.c_str(), nullptr, error, sizeof(error));
|
||||
@@ -845,6 +879,8 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
// as using std::optional<MjVFS> caused memory errors due to missing copy/move constructors.
|
||||
function("mj_compile", emscripten::select_overload<std::unique_ptr<MjModel>(const MjSpec&)>(&mj_compile_wrapper_1));
|
||||
function("mj_compile", emscripten::select_overload<std::unique_ptr<MjModel>(const MjSpec&, const MjVFS&)>(&mj_compile_wrapper_2));
|
||||
function("from_xml_string", emscripten::select_overload<std::unique_ptr<MjModel>(const std::string&)>(&from_xml_string_wrapper_1));
|
||||
function("from_xml_string", emscripten::select_overload<std::unique_ptr<MjModel>(const std::string&, const MjVFS&)>(&from_xml_string_wrapper_2));
|
||||
|
||||
emscripten::class_<WasmBuffer<float>>("FloatBuffer")
|
||||
.constructor<int>()
|
||||
@@ -875,6 +911,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
.function("GetView", &WasmBuffer<uint8_t>::GetView);
|
||||
|
||||
emscripten::register_vector<std::string>("mjStringVec");
|
||||
emscripten::register_vector<std::vector<std::string>>("mjStringVecVec");
|
||||
emscripten::register_vector<int>("mjIntVec");
|
||||
emscripten::register_vector<mjIntVec>("mjIntVecVec");
|
||||
emscripten::register_vector<float>("mjFloatVec");
|
||||
@@ -919,7 +956,6 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
emscripten::constant("mjPI", mjPI);
|
||||
emscripten::constant("mjVERSION_HEADER", mjVERSION_HEADER);
|
||||
|
||||
// These complex constants are bound using function() rather than constant()
|
||||
emscripten::function("get_mjDISABLESTRING", &get_mjDISABLESTRING);
|
||||
emscripten::function("get_mjENABLESTRING", &get_mjENABLESTRING);
|
||||
emscripten::function("get_mjFRAMESTRING", &get_mjFRAMESTRING);
|
||||
@@ -927,6 +963,31 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
emscripten::function("get_mjRNDSTRING", &get_mjRNDSTRING);
|
||||
emscripten::function("get_mjTIMERSTRING", &get_mjTIMERSTRING);
|
||||
emscripten::function("get_mjVISSTRING", &get_mjVISSTRING);
|
||||
// Bind these complex constants as properties on the module object.
|
||||
// We use emscripten::constant with emscripten::val::array() to type them
|
||||
// as `any` in TypeScript. At runtime, the EM_ASM block below overrides
|
||||
// these properties with getters that return native JavaScript arrays
|
||||
// (string[] or string[][]) via the get_ functions above, which is more
|
||||
// performant and idiomatic than vector wrappers.
|
||||
emscripten::constant("mjDISABLESTRING", emscripten::val::array());
|
||||
emscripten::constant("mjENABLESTRING", emscripten::val::array());
|
||||
emscripten::constant("mjFRAMESTRING", emscripten::val::array());
|
||||
emscripten::constant("mjLABELSTRING", emscripten::val::array());
|
||||
emscripten::constant("mjRNDSTRING", emscripten::val::array());
|
||||
emscripten::constant("mjTIMERSTRING", emscripten::val::array());
|
||||
emscripten::constant("mjVISSTRING", emscripten::val::array());
|
||||
EM_ASM({
|
||||
if (typeof Module !== "undefined") {
|
||||
"mjDISABLESTRING mjENABLESTRING mjFRAMESTRING mjLABELSTRING mjRNDSTRING mjTIMERSTRING mjVISSTRING".split(" ").forEach(function(name) {
|
||||
Object.defineProperty(Module, name, {
|
||||
get: function() { return Module["get_" + name](); },
|
||||
set: function(v) { },
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mujoco::wasm
|
||||
|
||||
+1
-10
@@ -205,10 +205,7 @@ class App {
|
||||
}
|
||||
|
||||
loadModel(xmlContent: string) {
|
||||
// Write xml as a file so that mujoco can find it
|
||||
(mujoco as any).FS.writeFile('/working/model.xml', xmlContent);
|
||||
|
||||
this.mjModel = mujoco.MjModel.mj_loadXML('/working/model.xml');
|
||||
this.mjModel = mujoco.MjModel.from_xml_string(xmlContent);
|
||||
if (!app.mjModel) {
|
||||
throw new Error('Failed to load model');
|
||||
}
|
||||
@@ -437,8 +434,6 @@ function setupWindowEvents() {
|
||||
// Tip: put "window.dispatchEvent(new Event('unload'))" in the console to test
|
||||
window.addEventListener('unload', () => {
|
||||
app.dispose();
|
||||
|
||||
(mujoco as any).FS.unmount('/working');
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', (event) => {
|
||||
@@ -464,10 +459,6 @@ async function main() {
|
||||
try {
|
||||
mujoco = await loadMujoco();
|
||||
|
||||
// Set up emscripten virtual file system
|
||||
(mujoco as any).FS.mkdir('/working');
|
||||
(mujoco as any).FS.mount((mujoco as any).MEMFS, {root: '.'}, '/working');
|
||||
|
||||
app = new App();
|
||||
|
||||
setupWindowEvents();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "mujoco",
|
||||
"name": "@mujoco/mujoco",
|
||||
"version": "0.0.0",
|
||||
"description": "MuJoCo WASM bindings",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/google-deepmind/mujoco.git",
|
||||
"url": "git+https://github.com/google-deepmind/mujoco.git",
|
||||
"directory": "wasm"
|
||||
},
|
||||
"homepage": "https://github.com/google-deepmind/mujoco/tree/main/wasm",
|
||||
|
||||
@@ -683,24 +683,24 @@ describe('MuJoCo WASM Bindings', () => {
|
||||
|
||||
it('should check constants values', () => {
|
||||
expect(mujoco.mjNEQDATA).toBe(11);
|
||||
expect(mujoco.get_mjDISABLESTRING()).toEqual([
|
||||
expect(mujoco.mjDISABLESTRING).toEqual([
|
||||
'Constraint', 'Equality', 'Frictionloss', 'Limit', 'Contact', 'Spring',
|
||||
'Damper', 'Gravity', 'Clampctrl', 'Warmstart', 'Filterparent',
|
||||
'Actuation', 'Refsafe', 'Sensor', 'Midphase', 'Eulerdamp', 'AutoReset',
|
||||
'NativeCCD', 'Island'
|
||||
]);
|
||||
expect(mujoco.get_mjRNDSTRING()).toEqual([
|
||||
expect(mujoco.mjRNDSTRING).toEqual([
|
||||
['Shadow', '1', 'S'], ['Wireframe', '0', 'W'], ['Reflection', '1', 'R'],
|
||||
['Additive', '0', 'L'], ['Skybox', '1', 'K'], ['Fog', '0', 'G'],
|
||||
['Haze', '1', '/'], ['Depth', '0', ''], ['Segment', '0', ','],
|
||||
['Id Color', '0', ''], ['Cull Face', '1', '']
|
||||
]);
|
||||
expect(mujoco.get_mjFRAMESTRING().length)
|
||||
.toEqual(mujoco.mjtFrame.mjNFRAME.value);
|
||||
expect(mujoco.get_mjVISSTRING().length)
|
||||
expect(mujoco.mjFRAMESTRING.length).toEqual(mujoco.mjtFrame.mjNFRAME.value);
|
||||
expect(mujoco.mjVISSTRING.length)
|
||||
.toEqual(mujoco.mjtVisFlag.mjNVISFLAG.value);
|
||||
expect(mujoco.get_mjVISSTRING()[mujoco.mjtVisFlag.mjVIS_INERTIA.value])
|
||||
.toEqual(['Inertia', '0', 'I']);
|
||||
expect(mujoco.mjVISSTRING[mujoco.mjtVisFlag.mjVIS_INERTIA.value]).toEqual([
|
||||
'Inertia', '0', 'I'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create a spec from XML', () => {
|
||||
@@ -2603,4 +2603,56 @@ describe('MuJoCo WASM Bindings', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should load a model from an XML string', () => {
|
||||
let model: MjModel|null = null;
|
||||
try {
|
||||
model = mujoco.from_xml_string(TEST_XML);
|
||||
assertExists(model);
|
||||
expect(model.nbody).toBe(5);
|
||||
expect(model.ngeom).toBe(3);
|
||||
} finally {
|
||||
model?.delete();
|
||||
}
|
||||
});
|
||||
|
||||
it('should load a model from an XML string with VFS', () => {
|
||||
const xml = `
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh file="cube.obj"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="cube"/>
|
||||
</worldbody>
|
||||
</mujoco>`;
|
||||
|
||||
const cube1 = `
|
||||
v -1 -1 1
|
||||
v 1 -1 1
|
||||
v -1 1 1
|
||||
v 1 1 1
|
||||
v -1 1 -1
|
||||
v 1 1 -1
|
||||
v -1 -1 -1
|
||||
v 1 -1 -1`;
|
||||
|
||||
let model: MjModel|null = null;
|
||||
let vfs: MjVFS|null = null;
|
||||
try {
|
||||
vfs = new mujoco.MjVFS();
|
||||
vfs.addBuffer('cube.obj', new TextEncoder().encode(cube1));
|
||||
assertExists(vfs);
|
||||
|
||||
model = mujoco.from_xml_string(xml, vfs);
|
||||
assertExists(model);
|
||||
expect(model.nmesh).toBe(1);
|
||||
|
||||
const meshId =
|
||||
mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_MESH.value, 'cube');
|
||||
expect(meshId).toBeGreaterThanOrEqual(0);
|
||||
} finally {
|
||||
model?.delete();
|
||||
vfs?.delete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,9 +20,6 @@ declare function loadMujoco(): Promise<MainModule>;
|
||||
async function main() {
|
||||
const mujoco: MainModule = await loadMujoco();
|
||||
|
||||
(mujoco as any).FS.mkdir('/working');
|
||||
(mujoco as any).FS.mount((mujoco as any).MEMFS, {root: '.'}, '/working');
|
||||
|
||||
const xmlContent = `
|
||||
<mujoco model="Box falling">
|
||||
<option viscosity="1"/>
|
||||
@@ -37,13 +34,12 @@ async function main() {
|
||||
</worldbody>
|
||||
</mujoco>`;
|
||||
|
||||
(mujoco as any).FS.writeFile('/working/hello.xml', xmlContent);
|
||||
let model: MjModel|undefined;
|
||||
let data: MjData|undefined;
|
||||
|
||||
try {
|
||||
console.log('Hello world!: Loading model');
|
||||
model = mujoco.MjModel.mj_loadXML('/working/hello.xml');
|
||||
model = mujoco.MjModel.from_xml_string(xmlContent);
|
||||
if (!model) {
|
||||
throw new Error('Failed to load model');
|
||||
}
|
||||
@@ -57,7 +53,6 @@ async function main() {
|
||||
} finally {
|
||||
model?.delete();
|
||||
data?.delete();
|
||||
(mujoco as any).FS.unmount('/working');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user