Initial open sourcing of MuJoCo.
PiperOrigin-RevId: 450374687 Change-Id: Ie3225a46ce095fc28ae8e63c326a640261f562bb
This commit is contained in:
committed by
Copybara-Service
parent
0e5d062302
commit
1913a02b40
@@ -0,0 +1,64 @@
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set(MUJOCO_TEST_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../src)
|
||||
set(MUJOCO_TEST_WORKING_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
include(GoogleTest)
|
||||
|
||||
macro(mujoco_test name)
|
||||
add_executable(${name} ${name}.cc)
|
||||
target_link_libraries(${name} gtest_main mujoco)
|
||||
target_include_directories(${name} PRIVATE ${MUJOCO_TEST_INCLUDE})
|
||||
set_target_properties(${name} PROPERTIES BUILD_RPATH ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
||||
# gtest_discover_tests is recommended over gtest_add_tests, but has some issues in Windows.
|
||||
gtest_add_tests(
|
||||
TARGET ${name}
|
||||
SOURCES ${name}.cc
|
||||
WORKING_DIRECTORY ${MUJOCO_TEST_WORKING_DIR}
|
||||
TEST_LIST testList
|
||||
)
|
||||
if(WIN32)
|
||||
set_tests_properties(
|
||||
${testList} PROPERTIES ENVIRONMENT "PATH=$<TARGET_FILE_DIR:mujoco>;$ENV{PATH}"
|
||||
)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
add_library(fixture STATIC fixture.h fixture.cc)
|
||||
target_include_directories(fixture PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
target_compile_definitions(fixture PUBLIC MJSTATIC)
|
||||
|
||||
target_link_libraries(
|
||||
fixture
|
||||
PUBLIC absl::core_headers
|
||||
absl::strings
|
||||
absl::synchronization
|
||||
gtest
|
||||
gmock
|
||||
mujoco::mujoco
|
||||
)
|
||||
target_include_directories(fixture PRIVATE ${mujoco_SOURCE_DIR}/include gmock)
|
||||
|
||||
mujoco_test(fixture_test)
|
||||
target_link_libraries(fixture_test fixture gmock)
|
||||
|
||||
mujoco_test(header_test)
|
||||
target_link_libraries(header_test fixture gmock)
|
||||
|
||||
add_subdirectory(benchmark)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(sample)
|
||||
add_subdirectory(user)
|
||||
add_subdirectory(xml)
|
||||
@@ -0,0 +1,57 @@
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Macro for benchmarks that don't use GL (same as mujoco_api_test, but uses
|
||||
# benchmark::benchmark_main).
|
||||
|
||||
macro(mujoco_benchmark_test name)
|
||||
add_executable(${name} ${name}.cc)
|
||||
target_link_libraries(
|
||||
${name}
|
||||
benchmark::benchmark_main
|
||||
mujoco
|
||||
absl::core_headers
|
||||
)
|
||||
target_include_directories(${name} PRIVATE ${MUJOCO_TEST_INCLUDE})
|
||||
# TODO(fraromano) Check RPATH settings
|
||||
set_target_properties(${name} PROPERTIES BUILD_RPATH ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
||||
# gtest_discover_tests is recommended over gtest_add_tests, but has some issues in Windows.
|
||||
gtest_add_tests(
|
||||
TARGET ${name}
|
||||
SOURCES ${name}.cc
|
||||
WORKING_DIRECTORY ${MUJOCO_TEST_WORKING_DIR}
|
||||
TEST_LIST testList
|
||||
)
|
||||
if(WIN32)
|
||||
set_tests_properties(
|
||||
${testList} PROPERTIES ENVIRONMENT "PATH=$<TARGET_FILE_DIR:mujoco>;$ENV{PATH}"
|
||||
)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
mujoco_benchmark_test(step_benchmark_test)
|
||||
target_link_libraries(
|
||||
step_benchmark_test
|
||||
fixture
|
||||
gmock
|
||||
benchmark::benchmark
|
||||
)
|
||||
|
||||
mujoco_benchmark_test(parse_benchmark_test)
|
||||
target_link_libraries(
|
||||
parse_benchmark_test
|
||||
fixture
|
||||
gmock
|
||||
benchmark::benchmark
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// A benchmark for parsing and compiling models from XML.
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <absl/base/attributes.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::NotNull;
|
||||
|
||||
static void run_parse_benchmark(const char *model_path, benchmark::State& state) {
|
||||
MujocoErrorTestGuard guard; // Fail test if there are any mujoco errors
|
||||
|
||||
const std::string xml_path = GetModelPath(model_path);
|
||||
std::array<char, 1024> error;
|
||||
for (auto s : state) {
|
||||
// TODO(nimrod): Load the models from VFS rather than from disk, to
|
||||
// limit the benchmark to the parsing and model compilation speed.
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.data(), nullptr, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
state.SetLabel(model_path);
|
||||
}
|
||||
|
||||
// Use ABSL_ATTRIBUTE_NO_TAIL_CALL to make sure the benchmark functions appear
|
||||
// separately in CPU profiles (and don't get replaced with raw calls to
|
||||
// run_parse_benchmark).
|
||||
|
||||
void ABSL_ATTRIBUTE_NO_TAIL_CALL BM_ParseCloth(benchmark::State& state) {
|
||||
run_parse_benchmark("composite/cloth.xml", state);
|
||||
}
|
||||
BENCHMARK(BM_ParseCloth);
|
||||
|
||||
void ABSL_ATTRIBUTE_NO_TAIL_CALL BM_ParseHumanoid(benchmark::State& state) {
|
||||
run_parse_benchmark("humanoid/humanoid.xml", state);
|
||||
}
|
||||
BENCHMARK(BM_ParseHumanoid);
|
||||
|
||||
void ABSL_ATTRIBUTE_NO_TAIL_CALL BM_ParseHumanoid100(benchmark::State& state) {
|
||||
run_parse_benchmark("humanoid100/humanoid100.xml", state);
|
||||
}
|
||||
BENCHMARK(BM_ParseHumanoid100);
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
Executable
+223
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""A script for running step_benchmark_test for a variety of build settings.
|
||||
|
||||
This is not meant to be run in google3, but in the git repository's root.
|
||||
|
||||
This will be used for release 2.1.1, and adapted for future use when more
|
||||
decisions about the build need to be made.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import NamedTuple, Tuple
|
||||
|
||||
|
||||
class _Setting(NamedTuple):
|
||||
name: str
|
||||
cmake_args: Tuple[str]
|
||||
|
||||
|
||||
def run_benchmark_variants(output_directory: str, build_directory: str,
|
||||
benchmark_repetitions: int, delete_build: bool):
|
||||
"""Builds different variants of the benchmark test, and runs them."""
|
||||
# For each of the settings, the first option is what we use as a baseline.
|
||||
# We chose to pick the settings that were likely best (so this is an ablation
|
||||
# study), so interactions between settings are tested.
|
||||
settings_ranges = {
|
||||
"avx": [
|
||||
_Setting("on", ("-DMUJOCO_ENABLE_AVX=ON",)),
|
||||
_Setting("off", ("-DMUJOCO_ENABLE_AVX=OFF",))
|
||||
],
|
||||
"avx_intrinsics": [
|
||||
_Setting("on", ("-DMUJOCO_ENABLE_AVX_INTRINSICS=ON",)),
|
||||
_Setting("off", ("-DMUJOCO_ENABLE_AVX_INTRINSICS=OFF",))
|
||||
],
|
||||
"lto": [
|
||||
_Setting("on", ("-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON",)),
|
||||
_Setting("off", ("-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF",)),
|
||||
],
|
||||
"compiler": compiler_cmake_flags_variants(),
|
||||
}
|
||||
|
||||
baseline_settings = {
|
||||
key: value[0] for key, value in settings_ranges.items()
|
||||
}
|
||||
run_benchmark(
|
||||
f"{sys.platform}_baseline",
|
||||
baseline_settings,
|
||||
output_directory=output_directory,
|
||||
build_directory=f"{build_directory}_baseline",
|
||||
benchmark_repetitions=benchmark_repetitions,
|
||||
delete_build=delete_build,
|
||||
)
|
||||
|
||||
for key in sorted(settings_ranges):
|
||||
settings = baseline_settings.copy()
|
||||
for setting in settings_ranges[key][1:]:
|
||||
settings[key] = setting
|
||||
run_benchmark(
|
||||
f"{sys.platform}_{key}_{setting.name}",
|
||||
settings,
|
||||
output_directory=output_directory,
|
||||
build_directory=f"{build_directory}_{key}_{setting.name}",
|
||||
benchmark_repetitions=benchmark_repetitions,
|
||||
delete_build=delete_build,
|
||||
)
|
||||
|
||||
|
||||
def run_benchmark(name, settings, output_directory: str, build_directory: str,
|
||||
benchmark_repetitions: int, delete_build: bool):
|
||||
"""Builds and runs a single benchmark."""
|
||||
current_dir = os.getcwd()
|
||||
try:
|
||||
if delete_build:
|
||||
shutil.rmtree(build_directory, ignore_errors=True)
|
||||
os.makedirs(build_directory, exist_ok=True)
|
||||
os.makedirs(output_directory, exist_ok=True)
|
||||
os.chdir(build_directory)
|
||||
cmake_args = [
|
||||
"cmake", "..", "-DMUJOCO_BUILD_TESTS=ON", "-DCMAKE_BUILD_TYPE=Release"
|
||||
]
|
||||
cmake_args.extend(os_specific_cmake_flags())
|
||||
for _, setting in sorted(settings.items()):
|
||||
cmake_args.extend(setting.cmake_args)
|
||||
|
||||
print(f"Running ({name}):", " ".join(cmake_args))
|
||||
subprocess.check_call(cmake_args)
|
||||
subprocess.check_call([
|
||||
"cmake", "--build", ".", "-j8", "--config=Release", "-t",
|
||||
"step_benchmark_test"
|
||||
])
|
||||
output_path = os.path.join(output_directory, f"{name}.json")
|
||||
|
||||
os.chdir("../mujoco/test")
|
||||
if sys.platform == "win32":
|
||||
cygwin_run_benchmark(build_directory, output_path, benchmark_repetitions)
|
||||
else:
|
||||
binary_path = os.path.join(build_directory, "bin", "step_benchmark_test")
|
||||
subprocess.check_call([
|
||||
binary_path,
|
||||
"--benchmark_filter=all",
|
||||
"--benchmark_enable_random_interleaving=true",
|
||||
"--benchmark_min_time=0.5",
|
||||
f"--benchmark_repetitions={benchmark_repetitions}",
|
||||
"--benchmark_format=json",
|
||||
f"--benchmark_out={output_path}",
|
||||
])
|
||||
finally:
|
||||
os.chdir(current_dir)
|
||||
|
||||
|
||||
def os_specific_cmake_flags():
|
||||
"""CMake args that should be passed to all benchmarks on current OS."""
|
||||
if sys.platform == "win32":
|
||||
return ("-A", "x64", "-Thost=x86",
|
||||
"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded",
|
||||
"-DCMAKE_SYSTEM_VERSION=10.0.19041.0")
|
||||
elif sys.platform.startswith("linux"):
|
||||
return ()
|
||||
elif sys.platform == "darwin":
|
||||
# macOS
|
||||
return ()
|
||||
else:
|
||||
raise ValueError(f"Unknown OS: {sys.platform}")
|
||||
|
||||
|
||||
def compiler_cmake_flags_variants():
|
||||
"""Returns a list of benchmark settings for different compilers."""
|
||||
if sys.platform == "win32":
|
||||
return [
|
||||
_Setting("VS2022", ("-G", "Visual Studio 17 2022")),
|
||||
# MSVC 2017 doesn't seem to support C17 standard.
|
||||
# _Setting("VS2017", ("-G", "Visual Studio 15 2017")),
|
||||
]
|
||||
elif sys.platform.startswith("linux"):
|
||||
return [
|
||||
_Setting("clang-11",
|
||||
("-DCMAKE_C_COMPILER=clang-11",
|
||||
"-DCMAKE_CXX_COMPILER=clang++-11", "-DCMAKE_LINKER=lld-11")),
|
||||
_Setting("clang-8",
|
||||
("-DCMAKE_C_COMPILER=clang-8",
|
||||
"-DCMAKE_CXX_COMPILER=clang++-8", "-DCMAKE_LINKER=lld-8")),
|
||||
]
|
||||
elif sys.platform == "darwin":
|
||||
# macOS
|
||||
return [_Setting("clang", ())]
|
||||
else:
|
||||
raise ValueError(f"Unknown OS: {sys.platform}")
|
||||
|
||||
|
||||
def cygwin_run_benchmark(build_directory, output_path, benchmark_repetitions):
|
||||
"""Runs the benchmark command under the cygwin environment."""
|
||||
# The subprocess module interacts badly with cygwin.
|
||||
# Rather than trying to run the binary directly through Popen, use bash on
|
||||
# cygwin.
|
||||
lib_path = cygwin_path(os.path.join(build_directory, "lib", "Release"))
|
||||
binary_path = cygwin_path(
|
||||
os.path.join(build_directory, "bin", "Release",
|
||||
"step_benchmark_test"))
|
||||
cygwin = subprocess.Popen(["bash"], stdin=subprocess.PIPE)
|
||||
command = (
|
||||
f'PATH="$PATH:{lib_path}" {binary_path} --benchmark_filter=all '
|
||||
"--benchmark_enable_random_interleaving=true ",
|
||||
"--benchmark_min_time=0.5 "
|
||||
f"--benchmark_repetitions={benchmark_repetitions} "
|
||||
"--benchmark_format=json "
|
||||
f"--benchmark_out='{output_path}'")
|
||||
cygwin.communicate(input=bytes(command, "utf-8"))
|
||||
if cygwin.returncode:
|
||||
raise ValueError(f"Benchmark returned error code: {cygwin.returncode}")
|
||||
|
||||
|
||||
def cygwin_path(windows_path):
|
||||
path = windows_path.replace("\\", "/")
|
||||
path = re.sub(r".*cygwin64", "", path)
|
||||
return path
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-o", "--output_directory", default="benchmark_results")
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"--build_directory",
|
||||
default="build",
|
||||
help="Base path for build directories. Benchmark names will be appended."
|
||||
)
|
||||
parser.add_argument("-n", "--repetitions", type=int, default=30)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--delete_build",
|
||||
nargs="?",
|
||||
type=bool,
|
||||
const=True,
|
||||
default=False,
|
||||
help="Delete the build directory before building benchmark.")
|
||||
args = parser.parse_args(argv[1:])
|
||||
run_benchmark_variants(
|
||||
output_directory=os.path.abspath(args.output_directory),
|
||||
build_directory=os.path.abspath(args.build_directory),
|
||||
delete_build=args.delete_build,
|
||||
benchmark_repetitions=args.repetitions)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// A benchmark which steps various models without rendering, and measures speed.
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <absl/base/attributes.h>
|
||||
#include <mujoco/mjdata.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::NotNull;
|
||||
|
||||
static void add_ctrl_noise(const mjModel* m, mjData* d, int step) {
|
||||
for (int i = 0; i < m->nu; i++) {
|
||||
mjtNum center = 0.0;
|
||||
mjtNum radius = 1.0;
|
||||
mjtNum* range = m->actuator_ctrlrange + 2 * i;
|
||||
if (m->actuator_ctrllimited[i]) {
|
||||
center = (range[1] + range[0]) / 2;
|
||||
radius = (range[1] - range[0]) / 2;
|
||||
}
|
||||
radius *= 0.01;
|
||||
d->ctrl[i] = center + radius * (2 * mju_Halton(step, i + 2) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void assert_model_not_null(mjModel* model,
|
||||
const std::array<char, 1024>& error) {
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
}
|
||||
|
||||
static mjModel* load_model(const char* model_path) {
|
||||
const std::string xml_path = GetModelPath(model_path);
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, error.data(), error.size());
|
||||
assert_model_not_null(model, error);
|
||||
return model;
|
||||
}
|
||||
|
||||
static void run_step_benchmark(const mjModel* model, benchmark::State& state) {
|
||||
mjData* data = mj_makeData(model);
|
||||
int i = 0;
|
||||
for (auto s : state) {
|
||||
add_ctrl_noise(model, data, i++);
|
||||
mj_step(model, data);
|
||||
}
|
||||
mj_deleteData(data);
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
}
|
||||
|
||||
// Use ABSL_ATTRIBUTE_NO_TAIL_CALL to make sure the benchmark functions appear
|
||||
// separately in CPU profiles (and don't get replaced with raw calls to
|
||||
// run_step_benchmark).
|
||||
|
||||
void ABSL_ATTRIBUTE_NO_TAIL_CALL BM_StepCloth(benchmark::State& state) {
|
||||
MujocoErrorTestGuard guard;
|
||||
static mjModel* model = load_model("composite/cloth.xml");
|
||||
run_step_benchmark(model, state);
|
||||
}
|
||||
BENCHMARK(BM_StepCloth);
|
||||
|
||||
void ABSL_ATTRIBUTE_NO_TAIL_CALL BM_StepHumanoid(benchmark::State& state) {
|
||||
MujocoErrorTestGuard guard;
|
||||
static mjModel* model = load_model("humanoid/humanoid.xml");
|
||||
run_step_benchmark(model, state);
|
||||
}
|
||||
BENCHMARK(BM_StepHumanoid);
|
||||
BENCHMARK(BM_StepHumanoid)->ThreadRange(2, 16);
|
||||
|
||||
void ABSL_ATTRIBUTE_NO_TAIL_CALL BM_StepHumanoid100(benchmark::State& state) {
|
||||
MujocoErrorTestGuard guard;
|
||||
static mjModel* model = load_model("humanoid100/humanoid100.xml");
|
||||
run_step_benchmark(model, state);
|
||||
}
|
||||
BENCHMARK(BM_StepHumanoid100);
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
mujoco_test(engine_collision_convex_test)
|
||||
target_link_libraries(engine_collision_convex_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_collision_driver_test)
|
||||
target_link_libraries(engine_collision_driver_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_core_smooth_test)
|
||||
target_link_libraries(engine_core_smooth_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_forward_test)
|
||||
target_link_libraries(engine_forward_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_io_test)
|
||||
target_link_libraries(
|
||||
engine_io_test
|
||||
fixture
|
||||
gmock
|
||||
absl::str_format
|
||||
)
|
||||
|
||||
mujoco_test(engine_ray_test)
|
||||
target_link_libraries(engine_ray_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_sensor_test)
|
||||
target_link_libraries(engine_sensor_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_support_test)
|
||||
target_link_libraries(engine_support_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_util_blas_test)
|
||||
target_link_libraries(engine_util_blas_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_util_errmem_test)
|
||||
target_link_libraries(engine_util_errmem_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_util_solve_test)
|
||||
target_link_libraries(engine_util_solve_test fixture gmock)
|
||||
|
||||
mujoco_test(engine_util_spatial_test)
|
||||
target_link_libraries(engine_util_spatial_test fixture gmock)
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_collision_convex.c.
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::NotNull;
|
||||
|
||||
static const char* const kFramelessContactPath =
|
||||
"engine/testdata/collision_convex/frameless_contact.xml";
|
||||
static const char* const kFramelessContactHfieldPath =
|
||||
"engine/testdata/collision_convex/frameless_contact_hfield.xml";
|
||||
static const char* const kCylinderBoxPath =
|
||||
"engine/testdata/collision_convex/cylinder_box.xml";
|
||||
|
||||
using MjcConvexTest = MujocoTest;
|
||||
|
||||
TEST_F(MjcConvexTest, FramelessContact) {
|
||||
const std::string xml_path = GetTestDataFilePath(kFramelessContactPath);
|
||||
char error[1024];
|
||||
const std::size_t error_sz = 1024;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, error, error_sz);
|
||||
// Loading used to fail with "engine error: xaxis of contact frame undefined".
|
||||
EXPECT_THAT(model, NotNull()) << "Failed to load model: " << error;
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjcConvexTest, FramelessContactHfield) {
|
||||
const std::string xml_path = GetTestDataFilePath(kFramelessContactHfieldPath);
|
||||
char error[1024];
|
||||
const std::size_t error_sz = 1024;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, error, error_sz);
|
||||
// Loading used to fail with "engine error: xaxis of contact frame undefined".
|
||||
EXPECT_THAT(model, NotNull()) << "Failed to load model: " << error;
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjcConvexTest, CylinderBox) {
|
||||
const std::string xml_path = GetTestDataFilePath(kCylinderBoxPath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, nullptr, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// with multiCCD enabled, should find 5 contacts
|
||||
mj_forward(model, data);
|
||||
ASSERT_EQ(data->ncon, 5);
|
||||
|
||||
// with multiCCD disabled, should find 1 contact
|
||||
model->opt.enableflags &= ~mjENBL_MULTICCD;
|
||||
mj_forward(model, data);
|
||||
ASSERT_EQ(data->ncon, 1);
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_collision_driver.c.
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using MjCollisionTest = MujocoTest;
|
||||
using GeomPair = std::pair<std::string, std::string>;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::ElementsAre;
|
||||
|
||||
// Returns a sorted list of pairs of colliding geom names, where each pair of
|
||||
// geom names is sorted.
|
||||
static std::vector<GeomPair> colliding_pairs(
|
||||
const mjModel* model, const mjData* data) {
|
||||
std::vector<GeomPair> result;
|
||||
for (int i = 0; i < data->ncon; i++) {
|
||||
std::string geom1 = mj_id2name(model, mjOBJ_GEOM, data->contact[i].geom1);
|
||||
std::string geom2 = mj_id2name(model, mjOBJ_GEOM, data->contact[i].geom2);
|
||||
result.push_back(GeomPair(std::min(geom1, geom2), std::max(geom1, geom2)));
|
||||
}
|
||||
std::sort(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_F(MjCollisionTest, PredefinedPairsOnly) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/collisions.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
|
||||
model->opt.collision = mjCOL_PAIR;
|
||||
mjData* data = mj_makeData(model);
|
||||
mj_fwdPosition(model, data);
|
||||
EXPECT_THAT(colliding_pairs(model, data), ElementsAre(
|
||||
GeomPair("box", "sphere_predefined")));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCollisionTest, AllCollisions) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/collisions.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// mjCOL_ALL is the default
|
||||
mj_fwdPosition(model, data);
|
||||
EXPECT_THAT(colliding_pairs(model, data), ElementsAre(
|
||||
GeomPair("box", "sphere_collides"),
|
||||
GeomPair("box", "sphere_predefined")
|
||||
));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCollisionTest, EmptyModel) {
|
||||
mjModel* model = LoadModelFromString("<mujoco/>");
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
mj_fwdPosition(model, data);
|
||||
EXPECT_THAT(colliding_pairs(model, data), IsEmpty());
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCollisionTest, ZeroedHessian) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/collisions.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
mj_fwdPosition(model, data);
|
||||
for (int i = 0; i < data->ncon; i++) {
|
||||
for (int j = 0; j < 36; j++) {
|
||||
EXPECT_FALSE(isnan(data->contact[i].H[j]))
|
||||
<< "NaN in contact[" << i << "].H[" << j << "]";
|
||||
}
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,334 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_core_smooth.c.
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
using CoreSmoothTest = MujocoTest;
|
||||
|
||||
static std::vector<mjtNum> GetVector(const mjtNum* array, int length) {
|
||||
return std::vector<mjtNum>(array, array + length);
|
||||
}
|
||||
|
||||
// --------------------------- connect constraint ------------------------------
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostConnectForceSlide) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/connect/force_slide.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(data->sensordata[i], model->sensor_user[i], 1e-6);
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostConnectForceSlideRotated) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/connect/force_slide_rotated.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(data->sensordata[i], model->sensor_user[i], 1e-6);
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostConnectForceFree) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/connect/force_free.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(data->sensordata[i], model->sensor_user[i], 1e-6);
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostConnectTorque) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/connect/torque_free.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(data->sensordata[i], model->sensor_user[i], 1e-6);
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostConnectMultipleConstraints) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/connect/multiple_constraints.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(data->sensordata[i], model->sensor_user[i], 1e-6);
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
// --------------------------- weld constraint ---------------------------------
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostWeldForceFree) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/weld/force_free.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int sensor_index=0; sensor_index < model->nsensor; sensor_index++) {
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(
|
||||
data->sensordata[model->sensor_adr[sensor_index] + i],
|
||||
model->sensor_user[model->nuser_sensor*sensor_index + i],
|
||||
1e-6);
|
||||
}
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostWeldForceFreeRotatoed) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/weld/force_free_rotated.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int sensor_index=0; sensor_index < model->nsensor; sensor_index++) {
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(
|
||||
data->sensordata[model->sensor_adr[sensor_index] + i],
|
||||
model->sensor_user[model->nuser_sensor*sensor_index + i],
|
||||
1e-6);
|
||||
}
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostWeldForceTorqueFree) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/weld/force_torque_free.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int sensor_index=0; sensor_index < model->nsensor; sensor_index++) {
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(
|
||||
data->sensordata[model->sensor_adr[sensor_index] + i],
|
||||
model->sensor_user[model->nuser_sensor*sensor_index + i],
|
||||
1e-6);
|
||||
}
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CoreSmoothTest, RnePostWeldForceTorqueFreeRotated) {
|
||||
static const char* const kModelFilePath =
|
||||
"engine/testdata/core_smooth/rne_post/weld/force_torque_free_rotated.xml";
|
||||
const std::string xml_path = GetTestDataFilePath(kModelFilePath);
|
||||
mjModel* model =
|
||||
mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
// settle physics:
|
||||
for (int i=0; i < 1000; i++) {
|
||||
mj_step(model, data);
|
||||
}
|
||||
for (int sensor_index=0; sensor_index < model->nsensor; sensor_index++) {
|
||||
for (int i=0; i < 3; i++) {
|
||||
EXPECT_NEAR(
|
||||
data->sensordata[model->sensor_adr[sensor_index] + i],
|
||||
model->sensor_user[model->nuser_sensor*sensor_index + i],
|
||||
1e-6);
|
||||
}
|
||||
}
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------- fluidshape --------------------------------
|
||||
|
||||
using EllipsoidFluidTest = MujocoTest;
|
||||
|
||||
TEST_F(EllipsoidFluidTest, GeomsEquivalentToBodies) {
|
||||
static constexpr char two_bodies_xml[] = R"(
|
||||
<mujoco>
|
||||
<option wind="5 5 0" density="10"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<freejoint/>
|
||||
<body>
|
||||
<geom type="box" size=".1 .01 0.01" pos="0.1 0 0" euler="40 0 0" fluidshape="ellipsoid"/>
|
||||
</body>
|
||||
<body>
|
||||
<geom type="box" size=".1 .01 0.01" pos="-.1 0 0" euler="0 20 0" fluidshape="ellipsoid"/>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
mjModel* m2 = LoadModelFromString(two_bodies_xml);
|
||||
mjData* d2 = mj_makeData(m2);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
d2->qvel[i] = (mjtNum) i+1;
|
||||
}
|
||||
d2->qpos[3] = 0.5;
|
||||
d2->qpos[4] = 0.5;
|
||||
d2->qpos[5] = 0.5;
|
||||
d2->qpos[6] = 0.5;
|
||||
|
||||
static constexpr char one_body_xml[] = R"(
|
||||
<mujoco>
|
||||
<option wind="5 5 0" density="10"/>
|
||||
<worldbody>
|
||||
<body pos="1 2 3">
|
||||
<freejoint/>
|
||||
<geom type="box" size=".1 .01 0.01" pos="0.1 0 0" euler="40 0 0" fluidshape="ellipsoid"/>
|
||||
<geom type="box" size=".1 .01 0.01" pos="-.1 0 0" euler="0 20 0" fluidshape="ellipsoid"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
mjModel* m1 = LoadModelFromString(one_body_xml);
|
||||
mjData* d1 = mj_makeData(m1);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
d1->qvel[i] = (mjtNum) i+1;
|
||||
}
|
||||
d1->qpos[3] = 0.5;
|
||||
d1->qpos[4] = 0.5;
|
||||
d1->qpos[5] = 0.5;
|
||||
d1->qpos[6] = 0.5;
|
||||
|
||||
const mjtNum tol = 1e-14; // tolerance for floating point numbers
|
||||
|
||||
EXPECT_EQ(m1->nv, m2->nv);
|
||||
for (int i = 0; i < m1->nv; i++) {
|
||||
EXPECT_NEAR(d2->qfrc_passive[i], d1->qfrc_passive[i], tol);
|
||||
}
|
||||
|
||||
mj_forward(m2, d2);
|
||||
mj_forward(m1, d1);
|
||||
for (int i = 0; i < m1->nv; i++) {
|
||||
EXPECT_NEAR(d2->qfrc_passive[i], d1->qfrc_passive[i], tol);
|
||||
}
|
||||
|
||||
mj_deleteData(d1);
|
||||
mj_deleteModel(m1);
|
||||
mj_deleteData(d2);
|
||||
mj_deleteModel(m2);
|
||||
}
|
||||
|
||||
TEST_F(EllipsoidFluidTest, DefaultsPropagate) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<option wind="5 5 0" density="10"/>
|
||||
<default>
|
||||
<geom fluidshape="ellipsoid" fluidcoef="2 3 4 5 6"/>
|
||||
<default class="test_class">
|
||||
<geom fluidshape="none" fluidcoef="5 4 3 2 1"/>
|
||||
</default>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<freejoint/>
|
||||
<geom type="box" size=".1 .01 0.01" pos="0.1 0 0" class="test_class"/>
|
||||
<geom type="box" size=".1 .01 0.01" pos="-0.1 0 0"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
EXPECT_THAT(GetVector(model->geom_fluid, 6),
|
||||
ElementsAre(0, 0, 0, 0, 0, 0));
|
||||
EXPECT_THAT(GetVector(model->geom_fluid + mjNFLUID, 6),
|
||||
ElementsAre(1, 2, 3, 4, 5, 6));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_forward.c.
|
||||
|
||||
#include "src/engine/engine_forward.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "src/engine/engine_io.h"
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ForwardTest = MujocoTest;
|
||||
|
||||
TEST_F(ForwardTest, ActLimited) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<option timestep="0.01"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="slide" type="slide" axis="1 0 0"/>
|
||||
<geom size=".1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="slide" gainprm="100" biasprm="0 -100" biastype="affine"
|
||||
dynprm="10" dyntype="integrator"
|
||||
actlimited="true" actrange="-1 1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
data->ctrl[0] = 1.0;
|
||||
// integrating up from 0, we will hit the clamp after 99 steps
|
||||
for (int i=0; i<200; i++) {
|
||||
mj_step(model, data);
|
||||
// always greater than lower bound
|
||||
ASSERT_GT(data->act[0], -1);
|
||||
// after 99 steps we hit the upper bound
|
||||
if (i < 99) ASSERT_LT(data->act[0], 1);
|
||||
if (i >= 99) ASSERT_EQ(data->act[0], 1);
|
||||
}
|
||||
|
||||
data->ctrl[0] = -1.0;
|
||||
// integrating down from 1, we will hit the clamp after 199 steps
|
||||
for (int i=0; i<300; i++) {
|
||||
mj_step(model, data);
|
||||
// always smaller than upper bound
|
||||
ASSERT_LT(data->act[0], model->actuator_actrange[1]);
|
||||
// after 199 steps we hit the lower bound
|
||||
if (i < 199) ASSERT_GT(data->act[0], model->actuator_actrange[0]);
|
||||
if (i >= 199) ASSERT_EQ(data->act[0], model->actuator_actrange[0]);
|
||||
}
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,588 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_io.c.
|
||||
|
||||
#include "src/engine/engine_io.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <absl/strings/str_format.h>
|
||||
#include <mujoco/mjxmacro.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
|
||||
using EngineIoTest = MujocoTest;
|
||||
|
||||
// Return an mjModel with just the ints set.
|
||||
mjModel PartialModel(const mjModel* m) {
|
||||
mjModel partial_model = {0};
|
||||
#define X(var) partial_model.var = m->var;
|
||||
MJMODEL_INTS;
|
||||
#undef X
|
||||
partial_model.nbuffer = 0;
|
||||
return partial_model;
|
||||
}
|
||||
|
||||
TEST_F(EngineIoTest, MakeDataFromPartialModel) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
mjData* data_from_model = mj_makeData(model);
|
||||
ASSERT_THAT(data_from_model, NotNull());
|
||||
|
||||
mjModel partial_model = PartialModel(model);
|
||||
mj_deleteModel(model);
|
||||
|
||||
mjData* data_from_partial = mj_makeData(&partial_model);
|
||||
ASSERT_THAT(data_from_partial, NotNull());
|
||||
|
||||
EXPECT_EQ(data_from_partial->nbuffer, data_from_model->nbuffer);
|
||||
int nbuffer = data_from_partial->nbuffer;
|
||||
// If there are no mocap bodies and qpos0 is all zero, mjData should be the
|
||||
// same whether it was made from the full model or the partial model.
|
||||
EXPECT_EQ(
|
||||
std::memcmp(data_from_partial->buffer, data_from_model->buffer, nbuffer),
|
||||
0) << "mjData content differs";
|
||||
|
||||
mj_deleteData(data_from_model);
|
||||
mj_deleteData(data_from_partial);
|
||||
}
|
||||
|
||||
TEST_F(EngineIoTest, MakeDataLoadsQpos0) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
model->qpos0[0] = 1;
|
||||
mjData* data = mj_makeData(model);
|
||||
ASSERT_THAT(data, NotNull());
|
||||
EXPECT_EQ(data->qpos[0], 1);
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(EngineIoTest, MakeDataLoadsMocapBodies) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body mocap="true" pos="42 0 42">
|
||||
<geom type="sphere" size="0.1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
mjData* data = mj_makeData(model);
|
||||
ASSERT_THAT(data, NotNull());
|
||||
EXPECT_EQ(data->mocap_pos[0], 42);
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(EngineIoTest, CopyDataWithPartialModel) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
<body mocap="true" pos="42 0 42">
|
||||
<geom type="sphere" size="0.1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
mjData* data = mj_makeData(model);
|
||||
ASSERT_THAT(data, NotNull());
|
||||
|
||||
mjModel partial_model = PartialModel(model);
|
||||
mj_deleteModel(model);
|
||||
mjData* copy = mj_makeData(&partial_model);
|
||||
ASSERT_THAT(copy, NotNull());
|
||||
|
||||
data->qpos[0] = 1;
|
||||
mj_copyData(copy, &partial_model, data);
|
||||
|
||||
EXPECT_EQ(copy->nbuffer, data->nbuffer);
|
||||
EXPECT_EQ(copy->qpos[0], 1);
|
||||
int nbuffer = copy->nbuffer;
|
||||
EXPECT_EQ(
|
||||
std::memcmp(copy->buffer, data->buffer, nbuffer),
|
||||
0) << "mjData content differs";
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteData(copy);
|
||||
}
|
||||
|
||||
using ValidateReferencesTest = MujocoTest;
|
||||
|
||||
TEST_F(ValidateReferencesTest, BodyReferences) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
model->jnt_bodyid[0] = 2;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("jnt_bodyid"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, AddressRange) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint/>
|
||||
<joint/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
model->body_jntnum[1] = 3;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("body_jntadr"));
|
||||
model->body_jntnum[1] = 2;
|
||||
// Could be more strict and test for -1, but at the moment the code is a bit
|
||||
// lenient.
|
||||
model->body_jntadr[1] = -2;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("body_jntadr"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, GeomCondim) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint/>
|
||||
<geom size="1" condim="6"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
model->geom_condim[0] = 7;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("geom_condim"));
|
||||
model->geom_condim[0] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("geom_condim"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, HField) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<hfield name="h" nrow="2" ncol="3" size="1 1 1 1" />
|
||||
</asset>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
model->hfield_adr[0] = -2;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("hfield_adr"));
|
||||
model->hfield_adr[0] = 0;
|
||||
model->hfield_ncol[0] = 4;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("hfield_adr"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, Texture) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<texture name="t" type="2d" width="2" height="3" builtin="flat" />
|
||||
</asset>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
model->tex_adr[0] = -2;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("tex_adr"));
|
||||
model->tex_adr[0] = 0;
|
||||
model->tex_height[0] = 4;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("tex_adr"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, GeomPairs) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom name="geom0" size="1"/>
|
||||
</body>
|
||||
<body>
|
||||
<geom name="geom1" size="1"/>
|
||||
<geom name="geom2" size="1"/>
|
||||
<geom name="geom3" size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<contact>
|
||||
<pair geom1="geom0" geom2="geom3"/>
|
||||
</contact>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
// Invalid geomid=4
|
||||
model->pair_signature[0] = (1 << 16) | 5;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("pair_body1"));
|
||||
|
||||
model->pair_signature[0] = (5 << 16) | 1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("pair_body2"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, SensorsAddress) {
|
||||
// The test will likely only catch sensor size errors for the last sensor
|
||||
// in the model, so iterate over possible last sensors, instead of adding
|
||||
// them all into the same model.
|
||||
static const char xml_template[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<joint name="slider" type="slide" axis="0 0 1"
|
||||
limited="true" range="-.2 .5"/>
|
||||
<geom name="geom1" size="1"/>
|
||||
<site name="site1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
%s
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
std::vector<std::string> sensor_strings{
|
||||
"<framepos objtype='site' objname='site1'/>",
|
||||
"<rangefinder site='site1'/>",
|
||||
"<gyro site='site1'/>",
|
||||
"<touch site='site1'/>",
|
||||
"<force site='site1'/>",
|
||||
"<torque site='site1'/>",
|
||||
"<jointlimitfrc joint='slider'/>",
|
||||
"<accelerometer site='site1'/>",
|
||||
"<subtreeangmom body='body1'/>",
|
||||
};
|
||||
|
||||
for (const std::string& sensor_string : sensor_strings) {
|
||||
std::string xml = absl::StrFormat(xml_template, sensor_string);
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model =
|
||||
LoadModelFromString(xml.c_str(), error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, SensorsObj) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<joint/>
|
||||
<geom size="1"/>
|
||||
<site name="site1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="site" objname="site1" reftype="body" refname="body1"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
model->sensor_objtype[0] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("sensor_objtype"));
|
||||
model->sensor_objtype[0] = mjOBJ_SITE;
|
||||
|
||||
model->sensor_objid[0] = model->nsite;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("sensor_objid"));
|
||||
model->sensor_objid[0] = 0;
|
||||
|
||||
model->sensor_reftype[0] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("sensor_reftype"));
|
||||
model->sensor_reftype[0] = mjOBJ_BODY;
|
||||
|
||||
model->sensor_refid[0] = model->nbody;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("sensor_refid"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, MoreBodiesThanGeoms) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom name="geom0" size="1"/>
|
||||
</body>
|
||||
<body>
|
||||
<geom name="geom1" size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<contact>
|
||||
<pair geom1="geom1" geom2="geom0"/>
|
||||
</contact>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, BodyExcludes) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="body1" />
|
||||
<body name="body2" />
|
||||
</worldbody>
|
||||
<contact>
|
||||
<exclude body1="body1" body2="body2"/>
|
||||
</contact>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
// Invalid bodyid=3
|
||||
model->exclude_signature[0] = (1 << 16) | 4;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("exclude_body1"));
|
||||
model->exclude_signature[0] = (4 << 16) | 2;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("exclude_body2"));
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, EqualityConstraints) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<joint name="joint1"/>
|
||||
<geom name="geom1" size="1"/>
|
||||
</body>
|
||||
<body name="body2">
|
||||
<joint/>
|
||||
<joint name="joint2"/>
|
||||
<geom size="1"/>
|
||||
<geom size="1"/>
|
||||
<geom name="geom2" size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<tendon>
|
||||
<fixed name="tendon1"><joint joint="joint1" coef="1"/></fixed>
|
||||
<fixed><joint joint="joint1" coef="1"/></fixed>
|
||||
<fixed><joint joint="joint1" coef="1"/></fixed>
|
||||
<fixed><joint joint="joint1" coef="1"/></fixed>
|
||||
<fixed name="tendon2"><joint joint="joint1" coef="1"/></fixed>
|
||||
</tendon>
|
||||
<equality>
|
||||
<connect anchor="0 0 0" body1="body1" />
|
||||
<weld body1="body1" body2="body2" />
|
||||
<distance geom1="geom1" geom2="geom2" />
|
||||
<joint joint1="joint1"/>
|
||||
<joint joint1="joint1" joint2="joint2"/>
|
||||
<tendon tendon1="tendon1"/>
|
||||
<tendon tendon1="tendon1" tendon2="tendon2"/>
|
||||
</equality>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
// connect constraint
|
||||
model->eq_obj1id[0] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj1id"));
|
||||
model->eq_obj1id[0] = model->nbody;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj1id"));
|
||||
model->eq_obj1id[0] = 1;
|
||||
|
||||
model->eq_obj2id[0] = -2;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj2id"));
|
||||
model->eq_obj2id[0] = model->nbody;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj2id"));
|
||||
model->eq_obj2id[0] = 0;
|
||||
|
||||
// weld constraint
|
||||
model->eq_obj1id[1] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj1id"));
|
||||
model->eq_obj1id[1] = model->nbody;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj1id"));
|
||||
model->eq_obj1id[1] = 1;
|
||||
|
||||
model->eq_obj2id[1] = -2;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj2id"));
|
||||
model->eq_obj2id[1] = model->nbody;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj2id"));
|
||||
model->eq_obj2id[1] = model->nbody - 1;
|
||||
|
||||
// distance constraint
|
||||
model->eq_obj1id[2] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj1id"));
|
||||
model->eq_obj1id[2] = model->ngeom;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj1id"));
|
||||
model->eq_obj1id[2] = 1;
|
||||
|
||||
model->eq_obj2id[2] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj2id"));
|
||||
model->eq_obj2id[2] = model->ngeom;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("eq_obj2id"));
|
||||
model->eq_obj2id[2] = model->ngeom - 1;
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(ValidateReferencesTest, Tuples) {
|
||||
static const char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<joint name="joint1"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
<body name="body2">
|
||||
<joint/>
|
||||
<joint name="joint2"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<custom>
|
||||
<tuple name="tuple">
|
||||
<element objtype="body" objname="body1"/>
|
||||
<element objtype="joint" objname="joint2"/>
|
||||
</tuple>
|
||||
</custom>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
EXPECT_THAT(mj_validateReferences(model), IsNull());
|
||||
|
||||
model->tuple_objtype[0] = -1;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("tuple_objtype"));
|
||||
model->tuple_objtype[0] = mjOBJ_BODY;
|
||||
|
||||
model->tuple_objid[0] = model->nbody;
|
||||
EXPECT_THAT(mj_validateReferences(model), HasSubstr("tuple_objid"));
|
||||
model->tuple_objid[0] = 1;
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2022 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for ray casting.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjdata.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
static constexpr char kRayCastingModel[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<geom name="static_group1" type="sphere" size=".1" pos="1 0 0"
|
||||
group="1"/>
|
||||
<body pos="0 0 0">
|
||||
<body pos="0 0 0">
|
||||
<geom name="group0" type="sphere" size=".1" pos="3 0 0"/>
|
||||
</body>
|
||||
<geom name="group2" type="sphere" size=".1" pos="5 0 0" group="2"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
using ::testing::NotNull;
|
||||
using RayTest = MujocoTest;
|
||||
|
||||
TEST_F(RayTest, NoExclusions) {
|
||||
mjModel* model = LoadModelFromString(kRayCastingModel);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
mjData* data = mj_makeData(model);
|
||||
ASSERT_THAT(data, NotNull());
|
||||
|
||||
mjtNum pnt[] = {0.0, 0.0, 0.0};
|
||||
mjtNum vec[] = {1.0, 0.0, 0.0};
|
||||
mjtByte* geomgroup = nullptr;
|
||||
mjtByte flg_static = 1; // Include static geoms
|
||||
int bodyexclude = -1;
|
||||
int geomid = -1;
|
||||
|
||||
mj_kinematics(model, data);
|
||||
mjtNum distance = mj_ray(model, data, pnt, vec, geomgroup, flg_static,
|
||||
bodyexclude, &geomid);
|
||||
EXPECT_STREQ(mj_id2name(model, mjOBJ_GEOM, geomid), "static_group1");
|
||||
EXPECT_FLOAT_EQ(distance, 0.9);
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(RayTest, Exclusions) {
|
||||
mjModel* model = LoadModelFromString(kRayCastingModel);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
mjData* data = mj_makeData(model);
|
||||
ASSERT_THAT(data, NotNull());
|
||||
|
||||
mjtNum pnt[] = {0.0, 0.0, 0.0};
|
||||
mjtNum vec[] = {1.0, 0.0, 0.0};
|
||||
mjtByte geomgroup[] = {1, 1, 1};
|
||||
mjtByte flg_static = 1;
|
||||
int bodyexclude = -1;
|
||||
int geomid = -1;
|
||||
|
||||
mj_kinematics(model, data);
|
||||
mjtNum distance = mj_ray(model, data, pnt, vec, geomgroup, flg_static,
|
||||
bodyexclude, &geomid);
|
||||
EXPECT_STREQ(mj_id2name(model, mjOBJ_GEOM, geomid), "static_group1");
|
||||
EXPECT_FLOAT_EQ(distance, 0.9);
|
||||
|
||||
// Exclude nearest geom
|
||||
geomgroup[1] = 0;
|
||||
distance = mj_ray(model, data, pnt, vec, geomgroup, flg_static, bodyexclude,
|
||||
&geomid);
|
||||
EXPECT_STREQ(mj_id2name(model, mjOBJ_GEOM, geomid), "group0");
|
||||
EXPECT_FLOAT_EQ(distance, 2.9);
|
||||
|
||||
geomgroup[0] = 0;
|
||||
distance = mj_ray(model, data, pnt, vec, geomgroup, flg_static, bodyexclude,
|
||||
&geomid);
|
||||
EXPECT_STREQ(mj_id2name(model, mjOBJ_GEOM, geomid), "group2");
|
||||
EXPECT_FLOAT_EQ(distance, 4.9);
|
||||
|
||||
geomgroup[2] = 0;
|
||||
distance = mj_ray(model, data, pnt, vec, geomgroup, flg_static, bodyexclude,
|
||||
&geomid);
|
||||
EXPECT_EQ(geomid, -1);
|
||||
EXPECT_FLOAT_EQ(distance, -1);
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(RayTest, ExcludeStatic) {
|
||||
mjModel* model = LoadModelFromString(kRayCastingModel);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
mjData* data = mj_makeData(model);
|
||||
ASSERT_THAT(data, NotNull());
|
||||
|
||||
mjtNum pnt[] = {0.0, 0.0, 0.0};
|
||||
mjtNum vec[] = {1.0, 0.0, 0.0};
|
||||
mjtByte geomgroup[] = {1, 1, 1};
|
||||
mjtByte flg_static = 0; // Exclude static geoms
|
||||
int bodyexclude = -1;
|
||||
int geomid = -1;
|
||||
|
||||
mj_kinematics(model, data);
|
||||
mjtNum distance = mj_ray(model, data, pnt, vec, geomgroup, flg_static,
|
||||
bodyexclude, &geomid);
|
||||
EXPECT_STREQ(mj_id2name(model, mjOBJ_GEOM, geomid), "group0");
|
||||
EXPECT_FLOAT_EQ(distance, 2.9);
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,361 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_sensor.c.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "src/engine/engine_util_blas.h"
|
||||
#include "src/engine/engine_util_spatial.h"
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
// returns as a vector the measured values from sensor with index `id`
|
||||
static std::vector<mjtNum> GetSensor(const mjModel* model, const mjData* data, int id) {
|
||||
return std::vector<mjtNum>(data->sensordata + model->sensor_adr[id],
|
||||
data->sensordata + model->sensor_adr[id] + model->sensor_dim[id]);
|
||||
}
|
||||
|
||||
|
||||
// --------------------- test relative frame sensors --------------------------
|
||||
|
||||
using ::testing::Pointwise;
|
||||
using ::testing::DoubleNear;
|
||||
using RelativeFrameSensorTest = MujocoTest;
|
||||
|
||||
const mjtNum tol = 1e-14; // nearness tolerance for floating point numbers
|
||||
|
||||
// hand-picked positions and orientations for simple expected values
|
||||
TEST_F(RelativeFrameSensorTest, ReferencePosMat) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="reference" pos="3 -4 0" xyaxes="4 3 0 -3 4 0"/>
|
||||
<site name="object" pos="4 3 0" xyaxes="3 -4 0 4 3 0"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="site" objname="object"
|
||||
reftype="xbody" refname="reference"/>
|
||||
<framexaxis objtype="site" objname="object"
|
||||
reftype="xbody" refname="reference"/>
|
||||
<frameyaxis objtype="site" objname="object"
|
||||
reftype="xbody" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
mj_forward(model, data);
|
||||
|
||||
// compare actual and expected values
|
||||
std::vector pos = GetSensor(model, data, 0);
|
||||
EXPECT_THAT(pos, Pointwise(DoubleNear(tol), {5, 5, 0}));
|
||||
|
||||
std::vector xaxis = GetSensor(model, data, 1);
|
||||
EXPECT_THAT(xaxis, Pointwise(DoubleNear(tol), {0, -1, 0}));
|
||||
|
||||
std::vector yaxis = GetSensor(model, data, 2);
|
||||
EXPECT_THAT(yaxis, Pointwise(DoubleNear(tol), {1, 0, 0}));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// orientations given by quaternion and by orientation matrix are identical
|
||||
TEST_F(RelativeFrameSensorTest, ReferenceQuatMat) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<site name="reference" euler="10 20 30"/>
|
||||
<site name="object" euler="20 40 60"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framexaxis objtype="site" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
<frameyaxis objtype="site" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
<framezaxis objtype="site" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
<framequat objtype="site" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// call mj_forward and convert orientation matrix to quaternion
|
||||
mj_forward(model, data);
|
||||
mjtNum mat[9], converted_quat[4];
|
||||
mju_transpose(mat, data->sensordata, 3, 3);
|
||||
mju_mat2Quat(converted_quat, mat);
|
||||
|
||||
// compare quaternion sensor and quat derived from orientation matrix
|
||||
std::vector quat = GetSensor(model, data, 3);
|
||||
EXPECT_THAT(quat, Pointwise(DoubleNear(tol), converted_quat));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// compare global frame and initially co-located relative frame on same body
|
||||
TEST_F(RelativeFrameSensorTest, ReferencePosMatQuat) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<freejoint/>
|
||||
<site name="reference"/>
|
||||
<geom name="object" euler="20 40 60" pos="1 2 3" size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="geom" objname="object"/>
|
||||
<framexaxis objtype="geom" objname="object"/>
|
||||
<frameyaxis objtype="geom" objname="object"/>
|
||||
<framezaxis objtype="geom" objname="object"/>
|
||||
<framequat objtype="geom" objname="object"/>
|
||||
<framepos objtype="geom" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
<framexaxis objtype="geom" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
<frameyaxis objtype="geom" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
<framezaxis objtype="geom" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
<framequat objtype="geom" objname="object"
|
||||
reftype="site" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
constexpr int nsensordata = 32;
|
||||
ASSERT_EQ(model->nsensordata, nsensordata);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// call mj_forward, save global sensors (colocated with reference frame)
|
||||
mj_forward(model, data);
|
||||
std::vector expected_values(data->sensordata, data->sensordata+nsensordata/2);
|
||||
|
||||
// set qpos to arbitrary values, call mj_forward
|
||||
for (int i=0; i<7; i++) {
|
||||
data->qpos[i] = i+1;
|
||||
}
|
||||
mj_forward(model, data);
|
||||
|
||||
// note that in the loop above the quat is unnormalized, but that's ok,
|
||||
// quaternions are automatically normalized in place:
|
||||
EXPECT_NEAR(mju_norm(data->qpos+3, 4), 1.0, tol);
|
||||
|
||||
// get values from relative sensors after moving the object
|
||||
std::vector actual_values(data->sensordata+nsensordata/2,
|
||||
data->sensordata+nsensordata);
|
||||
|
||||
// object and reference have moved together, we expect values to not unchange
|
||||
EXPECT_THAT(actual_values, Pointwise(DoubleNear(tol), expected_values));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// hand-picked velocities and orientations for simple expected values
|
||||
TEST_F(RelativeFrameSensorTest, FrameVelLinearFixed) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body xyaxes="1 -1 0 1 1 0">
|
||||
<joint type="slide" axis="1 0 0"/>
|
||||
<geom name="reference" size="1"/>
|
||||
</body>
|
||||
<body>
|
||||
<joint type="slide" axis="1 0 0"/>
|
||||
<geom name="object" size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framelinvel objtype="geom" objname="object"
|
||||
reftype="geom" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
data->qvel[0] = mju_sqrt(2);
|
||||
data->qvel[1] = 1;
|
||||
mj_forward(model, data);
|
||||
|
||||
// compare to expected values
|
||||
std::vector linvel = GetSensor(model, data, 0);
|
||||
const mjtNum expected_linvel[3] = {-mju_sqrt(0.5), mju_sqrt(0.5), 0};
|
||||
EXPECT_THAT(linvel, Pointwise(DoubleNear(tol), expected_linvel));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// object and reference in the same body, expect angular velocites to be zero
|
||||
TEST_F(RelativeFrameSensorTest, FrameVelAngFixed) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint type="hinge" axis="1 2 3"/>
|
||||
<geom name="reference" size="1" pos="1 2 3"/>
|
||||
<geom name="object" size="1" pos="-3 -2 -1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<frameangvel objtype="geom" objname="object"
|
||||
reftype="geom" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// set joint velocities and call forward dynamics
|
||||
data->qvel[0] = 1;
|
||||
mj_forward(model, data);
|
||||
|
||||
// obj and ref rotate together, relative angular velocites should be zero
|
||||
std::vector angvel = GetSensor(model, data, 0);
|
||||
EXPECT_THAT(angvel, Pointwise(DoubleNear(tol), {0, 0, 0}));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// object and reference rotate on the same global axis
|
||||
TEST_F(RelativeFrameSensorTest, FrameVelAngOpposing) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body xyaxes="0 -1 0 1 0 0">
|
||||
<joint type="hinge" axis="0 1 0"/>
|
||||
<geom name="reference" size="1"/>
|
||||
</body>
|
||||
<body>
|
||||
<joint type="hinge" axis="1 0 0"/>
|
||||
<geom name="object" size="1" pos="-3 -2 -1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<frameangvel objtype="geom" objname="object"
|
||||
reftype="geom" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
|
||||
// set joint velocities and call forward dynamics
|
||||
data->qvel[0] = -1;
|
||||
data->qvel[1] = 1;
|
||||
mj_forward(model, data);
|
||||
|
||||
// obj and ref rotate on same axis, we can just difference the velocities
|
||||
std::vector angvel = GetSensor(model, data, 0);
|
||||
const mjtNum expected_angvel[3] = {0, data->qvel[1]-data->qvel[0], 0};
|
||||
EXPECT_THAT(angvel, Pointwise(DoubleNear(tol), expected_angvel));
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// two arbitrary frames, compare velocity sensors and fin-diffed positions
|
||||
TEST_F(RelativeFrameSensorTest, FrameVelGeneral) {
|
||||
constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body pos="1 2 3" euler="10 20 30">
|
||||
<joint type="hinge" axis="2 3 4"/>
|
||||
<geom name="reference" size="1" pos="0 1 2"/>
|
||||
</body>
|
||||
<body pos="-3 -2 -1" euler="20 40 60">
|
||||
<joint type="hinge" axis="2 3 4"/>
|
||||
<geom name="object" size="1" pos="1 2 3"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="geom" objname="object"
|
||||
reftype="geom" refname="reference"/>
|
||||
<framequat objtype="geom" objname="object"
|
||||
reftype="geom" refname="reference"/>
|
||||
<framelinvel objtype="geom" objname="object"
|
||||
reftype="geom" refname="reference"/>
|
||||
<frameangvel objtype="geom" objname="object"
|
||||
reftype="geom" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
mjData* data = mj_makeData(model);
|
||||
mjtNum dt = 1e-6; // timestep used for finite differencing
|
||||
|
||||
// set (arbitrary) joint velocities and call forward dynamics
|
||||
data->qvel[0] = 1;
|
||||
data->qvel[1] = -1;
|
||||
mj_forward(model, data);
|
||||
|
||||
// save measured linear and angular velocities as vectors
|
||||
std::vector linvel = GetSensor(model, data, 2);
|
||||
std::vector angvel = GetSensor(model, data, 3);
|
||||
|
||||
// save current position, quaternion as arrays
|
||||
mjtNum pos0[3], quat0[4];
|
||||
mju_copy3(pos0, data->sensordata);
|
||||
mju_copy4(quat0, data->sensordata+3);
|
||||
|
||||
// explicit Euler integration with small dt
|
||||
mju_addToScl(data->qpos, data->qvel, dt, 2);
|
||||
|
||||
// call mj_forward again, save new position and quaternion
|
||||
mj_forward(model, data);
|
||||
mjtNum pos1[3], quat1[4];
|
||||
mju_copy3(pos1, data->sensordata);
|
||||
mju_copy4(quat1, data->sensordata+3);
|
||||
|
||||
// compute expected linear velocities using finite differencing
|
||||
mjtNum linvel_findiff[3];
|
||||
mju_sub3(linvel_findiff, pos1, pos0);
|
||||
mju_scl3(linvel_findiff, linvel_findiff, 1/dt);
|
||||
|
||||
// compute expected angular velocities using finite differencing
|
||||
mjtNum dquat[4], angvel_findiff[3];
|
||||
mju_negQuat(quat0, quat0);
|
||||
mju_mulQuat(dquat, quat1, quat0);
|
||||
mju_quat2Vel(angvel_findiff, dquat, dt);
|
||||
|
||||
// compare analytic and finite-differenced relative velocities
|
||||
EXPECT_THAT(linvel, Pointwise(DoubleNear(10*dt), linvel_findiff));
|
||||
EXPECT_THAT(angvel, Pointwise(DoubleNear(10*dt), angvel_findiff));
|
||||
|
||||
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_support.c.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using VersionTest = MujocoTest;
|
||||
|
||||
const char *const kExpectedVersionString = "2.2.0";
|
||||
|
||||
TEST_F(VersionTest, MjVersion) {
|
||||
EXPECT_EQ(mj_version(), mjVERSION_HEADER);
|
||||
}
|
||||
|
||||
TEST_F(VersionTest, MjVersionString) {
|
||||
EXPECT_EQ(std::string_view(mj_versionString()), kExpectedVersionString);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2022 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_util_blas.c
|
||||
|
||||
#include "src/engine/engine_util_blas.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using EngineUtilBlasTest = MujocoTest;
|
||||
|
||||
TEST_F(EngineUtilBlasTest, MjuDot) {
|
||||
mjtNum a[] = {1, 2, 3, 4, 5, 6, 7};
|
||||
mjtNum b[] = {7, 6, 5, 4, 3, 2, 1};
|
||||
|
||||
// test various vector lengths because mju_dot adds numbers in groups of four
|
||||
EXPECT_EQ(mju_dot(a, b, 0), 0);
|
||||
EXPECT_EQ(mju_dot(a, b, 1), 7);
|
||||
EXPECT_EQ(mju_dot(a, b, 2), 7 + 2*6);
|
||||
EXPECT_EQ(mju_dot(a, b, 3), 7 + 2*6 + 3*5);
|
||||
EXPECT_EQ(mju_dot(a, b, 4), 7 + 2*6 + 3*5 + 4*4);
|
||||
EXPECT_EQ(mju_dot(a, b, 5), 7 + 2*6 + 3*5 + 4*4 + 5*3);
|
||||
EXPECT_EQ(mju_dot(a, b, 6), 7 + 2*6 + 3*5 + 4*4 + 5*3 + 6*2);
|
||||
EXPECT_EQ(mju_dot(a, b, 7), 7 + 2*6 + 3*5 + 4*4 + 5*3 + 6*2 + 7);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_util_errmem.c.
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "src/engine/engine_util_errmem.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
constexpr int kBufferSize = 1000;
|
||||
|
||||
char* ErrorMessageBuffer() {
|
||||
static char error_message[kBufferSize] = "";
|
||||
return error_message;
|
||||
}
|
||||
|
||||
char* WarningMessageBuffer() {
|
||||
static char warning_message[kBufferSize] = "";
|
||||
return warning_message;
|
||||
}
|
||||
|
||||
void MjErrorHandler(const char* msg) {
|
||||
if (strnlen(msg, kBufferSize) == kBufferSize) {
|
||||
FAIL() << "mju_user_error message exceeds maximum length of "
|
||||
<< kBufferSize;
|
||||
}
|
||||
strncpy(ErrorMessageBuffer(), msg, kBufferSize);
|
||||
}
|
||||
|
||||
void MjWarningHandler(const char* msg) {
|
||||
if (strnlen(msg, kBufferSize) == kBufferSize) {
|
||||
FAIL() << "mju_user_warning message exceeds maximum length of "
|
||||
<< kBufferSize;
|
||||
}
|
||||
strncpy(WarningMessageBuffer(), msg, kBufferSize);
|
||||
}
|
||||
|
||||
void ClearErrorMessage() { ErrorMessageBuffer()[0] = '\0'; }
|
||||
|
||||
void ClearWarningMessage() { WarningMessageBuffer()[0] = '\0'; }
|
||||
|
||||
class MujocoErrorAndWarningTest : public ::testing::Test {
|
||||
public:
|
||||
MujocoErrorAndWarningTest() {
|
||||
mju_user_error = MjErrorHandler;
|
||||
mju_user_warning = MjWarningHandler;
|
||||
}
|
||||
|
||||
~MujocoErrorAndWarningTest() {
|
||||
mju_user_error = nullptr;
|
||||
mju_user_warning = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MujocoErrorAndWarningTest, MjuErrorI) {
|
||||
std::string format_string = "%010d";
|
||||
while (format_string.length() < 2 * kBufferSize) {
|
||||
format_string += 'x';
|
||||
}
|
||||
|
||||
std::string expected_message = "0123456789";
|
||||
while (expected_message.length() < kBufferSize - 1) {
|
||||
expected_message += 'x';
|
||||
}
|
||||
|
||||
ClearErrorMessage();
|
||||
mju_error_i(format_string.c_str(), 123456789);
|
||||
EXPECT_EQ(std::string(ErrorMessageBuffer()), expected_message);
|
||||
}
|
||||
|
||||
TEST_F(MujocoErrorAndWarningTest, MjuWarningI) {
|
||||
std::string format_string = "%010d";
|
||||
while (format_string.length() < 2 * kBufferSize) {
|
||||
format_string += 'x';
|
||||
}
|
||||
|
||||
std::string expected_message = "0123456789";
|
||||
while (expected_message.length() < kBufferSize - 1) {
|
||||
expected_message += 'x';
|
||||
}
|
||||
|
||||
ClearWarningMessage();
|
||||
mju_warning_i(format_string.c_str(), 123456789);
|
||||
EXPECT_EQ(std::string(WarningMessageBuffer()), expected_message);
|
||||
}
|
||||
|
||||
TEST_F(MujocoErrorAndWarningTest, MjuErrorS) {
|
||||
std::string format_string = "% 9s";
|
||||
while (format_string.length() < 2 * kBufferSize) {
|
||||
format_string += 'z';
|
||||
}
|
||||
|
||||
std::string expected_message = " foobar";
|
||||
while (expected_message.length() < kBufferSize - 1) {
|
||||
expected_message += 'z';
|
||||
}
|
||||
|
||||
ClearErrorMessage();
|
||||
mju_error_s(format_string.c_str(), "foobar");
|
||||
EXPECT_EQ(std::string(ErrorMessageBuffer()), expected_message);
|
||||
}
|
||||
|
||||
TEST_F(MujocoErrorAndWarningTest, MjuWarningS) {
|
||||
std::string format_string = "% 9s";
|
||||
while (format_string.length() < 2 * kBufferSize) {
|
||||
format_string += 'z';
|
||||
}
|
||||
|
||||
std::string expected_message = " foobar";
|
||||
while (expected_message.length() < kBufferSize - 1) {
|
||||
expected_message += 'z';
|
||||
}
|
||||
|
||||
ClearWarningMessage();
|
||||
mju_warning_s(format_string.c_str(), "foobar");
|
||||
EXPECT_EQ(std::string(WarningMessageBuffer()), expected_message);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_util_solve.c.
|
||||
|
||||
#include "src/engine/engine_util_solve.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using QCQP2Test = MujocoTest;
|
||||
|
||||
TEST_F(QCQP2Test, DegenerateAMatrix) {
|
||||
// A 2x2 matrix with determinant zero.
|
||||
const mjtNum Ain[9] { 6, -15, 2, -5 };
|
||||
|
||||
// Any values will do for these three inputs.
|
||||
const mjtNum bin[3] { -12, 49 };
|
||||
const mjtNum d[3] { 11, 31 };
|
||||
const mjtNum r = 0.01;
|
||||
|
||||
// Make output array explicitly nonzero to simulate uninitialized memory.
|
||||
mjtNum res[2] { 999, 999 };
|
||||
|
||||
EXPECT_EQ(mju_QCQP2(res, Ain, bin, d, r), 0);
|
||||
EXPECT_EQ(res[0], 0);
|
||||
EXPECT_EQ(res[1], 0);
|
||||
}
|
||||
|
||||
using QCQP3Test = MujocoTest;
|
||||
|
||||
TEST_F(QCQP3Test, DegenerateAMatrix) {
|
||||
// A 3x3 matrix with determinant zero.
|
||||
const mjtNum Ain[9] { 1, 4, -2, -3, -7, 5, 2, -9, 0 };
|
||||
|
||||
// Any values will do for these three inputs.
|
||||
const mjtNum bin[3] { -12, 49, 8 };
|
||||
const mjtNum d[3] { 11, 31, -23 };
|
||||
const mjtNum r = 0.1;
|
||||
|
||||
// Make output array explicitly nonzero to simulate uninitialized memory.
|
||||
mjtNum res[3] { 999, 999, 999 };
|
||||
|
||||
EXPECT_EQ(mju_QCQP3(res, Ain, bin, d, r), 0);
|
||||
EXPECT_EQ(res[0], 0);
|
||||
EXPECT_EQ(res[1], 0);
|
||||
EXPECT_EQ(res[2], 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2022 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for engine/engine_util_spatial.c
|
||||
|
||||
#include "src/engine/engine_util_spatial.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
|
||||
using Quat2MatTest = MujocoTest;
|
||||
|
||||
std::vector<mjtNum> AsVector(const mjtNum* array, int n) {
|
||||
return std::vector<mjtNum>(array, array + n);
|
||||
}
|
||||
|
||||
TEST_F(Quat2MatTest, NoRotation) {
|
||||
mjtNum result[9] = {0};
|
||||
mjtNum quat[] = {1, 0, 0, 0};
|
||||
mju_quat2Mat(result, quat);
|
||||
EXPECT_THAT(
|
||||
AsVector(result, 9),
|
||||
ElementsAre(1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(Quat2MatTest, TinyRotation) {
|
||||
mjtNum result[9] = {0};
|
||||
// An angle so small that cos(angle) == 1.0 to double accuracy
|
||||
mjtNum angle = 1e-8;
|
||||
mjtNum quat[] = {cos(angle/2), sin(angle/2), 0, 0};
|
||||
mju_quat2Mat(result, quat);
|
||||
EXPECT_THAT(
|
||||
AsVector(result, 9),
|
||||
ElementsAre(1, 0 , 0 ,
|
||||
0, cos(angle), -sin(angle),
|
||||
0, sin(angle), cos(angle))
|
||||
);
|
||||
}
|
||||
|
||||
using MulQuatTest = MujocoTest;
|
||||
|
||||
TEST_F(MulQuatTest, TinyRotation) {
|
||||
mjtNum null_quat[4] = {1, 0, 0, 0};
|
||||
mjtNum result[4];
|
||||
// An angle so small that cos(angle) == 1.0 to double accuracy
|
||||
mjtNum angle = 1e-8;
|
||||
mjtNum quat[] = {cos(angle/2), sin(angle/2), 0, 0};
|
||||
mju_mulQuat(result, null_quat, quat);
|
||||
EXPECT_THAT(
|
||||
AsVector(result, 4),
|
||||
ElementsAre(cos(angle/2), sin(angle/2), 0, 0)
|
||||
);
|
||||
}
|
||||
|
||||
using RotVecQuatTest = MujocoTest;
|
||||
|
||||
TEST_F(RotVecQuatTest, NoRotation) {
|
||||
mjtNum result[3];
|
||||
mjtNum vec[] = {1, 2, 3};
|
||||
mjtNum quat[] = {1, 0, 0, 0};
|
||||
mju_rotVecQuat(result, vec, quat);
|
||||
EXPECT_THAT(
|
||||
AsVector(result, 3),
|
||||
ElementsAre(1, 2, 3)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(RotVecQuatTest, TinyRotation) {
|
||||
mjtNum result[3];
|
||||
mjtNum vec[] = {0, 1, 0};
|
||||
// An angle so small that cos(angle) == 1.0 to double accuracy
|
||||
mjtNum angle = 1e-8;
|
||||
mjtNum quat[] = {cos(angle/2), sin(angle/2), 0, 0};
|
||||
mju_rotVecQuat(result, vec, quat);
|
||||
EXPECT_THAT(
|
||||
AsVector(result, 3),
|
||||
ElementsAre(0, cos(angle), sin(angle))
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,25 @@
|
||||
<mujoco>
|
||||
<option>
|
||||
<flag multiccd="enable"/>
|
||||
</option>
|
||||
|
||||
<visual>
|
||||
<quality shadowsize="8192"/>
|
||||
<scale forcewidth="0.01" contactwidth="0.05" contactheight="0.05"/>
|
||||
<map force="0.1"/>
|
||||
</visual>
|
||||
|
||||
<asset>
|
||||
<mesh name="box" vertex="-1 -1 -1 1 -1 -1 1 1 -1 1 1 1 1 -1 1 -1 1 -1 -1 1 1 -1 -1 1"
|
||||
scale="1 1 .3"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
<light pos="0 0 1"/>
|
||||
<geom type="mesh" mesh="box" pos="0 0 -.3"/>
|
||||
<body pos="0 0 .02" euler="90 40 0">
|
||||
<freejoint/>
|
||||
<geom type="capsule" size=".03 .1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,20 @@
|
||||
<mujoco>
|
||||
<option>
|
||||
<flag multiccd="enable"/>
|
||||
</option>
|
||||
|
||||
<visual>
|
||||
<quality shadowsize="8192"/>
|
||||
<scale forcewidth="0.01" contactwidth="0.05" contactheight="0.05"/>
|
||||
<map force="0.1"/>
|
||||
</visual>
|
||||
|
||||
<worldbody>
|
||||
<light pos="0 0 1"/>
|
||||
<geom type="box" size="1 1 .3" pos="0 0 -.3" rgba=".5 .5 .5 .5"/>
|
||||
<body pos="0 0 .02">
|
||||
<freejoint/>
|
||||
<geom type="cylinder" size=".4 .03"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,9 @@
|
||||
<mujoco model="frameless contact">
|
||||
<worldbody>
|
||||
<geom type="box" pos="0.0 0.0 -0.46" size="0.4 0.4 0.46"/>
|
||||
<body>
|
||||
<joint type="slide" axis="1 0 0"/>
|
||||
<geom pos="0.2 0 0.02" size="0.062 0.02" type="cylinder"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,14 @@
|
||||
<mujoco model="frameless contact">
|
||||
<asset>
|
||||
<hfield name="hf" nrow="10" ncol="10" size="1 1 0.1 0.01"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
<geom type="hfield" hfield="hf"/>
|
||||
<body>
|
||||
<joint type="slide" axis="1 0 0" damping="1"/>
|
||||
<joint type="slide" axis="0 1 0" damping="1"/>
|
||||
<geom pos="0 0 0.2" size="0.3 0.3 0.2" type="box"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,26 @@
|
||||
<mujoco>
|
||||
<option>
|
||||
<flag multiccd="enable"/>
|
||||
</option>
|
||||
|
||||
<visual>
|
||||
<quality shadowsize="8192"/>
|
||||
<scale forcewidth="0.01" contactwidth="0.05" contactheight="0.05"/>
|
||||
<map force="0.1"/>
|
||||
</visual>
|
||||
|
||||
<asset>
|
||||
<mesh name="long_box" vertex="-1 -1 -1 1 -1 -1 1 1 -1 1 1 1 1 -1 1 -1 1 -1 -1 1 1 -1 -1 1"
|
||||
scale=".6 .03 .03"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
<light pos="0 0 1"/>
|
||||
<geom type="box" size="1 1 .3" pos="0 0 -.3" rgba=".5 .5 .5 .5"/>
|
||||
<body pos="0 0 .02" euler="0 0 40">
|
||||
<freejoint/>
|
||||
<geom type="mesh" mesh="long_box"/>
|
||||
<!-- <geom type="box" size=".6 .03 .03"/> -->
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,49 @@
|
||||
<mujoco>
|
||||
<option noslip_iterations="2">
|
||||
<flag multiccd="enable"/>
|
||||
</option>
|
||||
|
||||
<visual>
|
||||
<quality shadowsize="8192"/>
|
||||
<scale forcewidth="0.01" contactwidth="0.05" contactheight="0.05"/>
|
||||
<map force="0.1"/>
|
||||
</visual>
|
||||
|
||||
<default>
|
||||
<geom solref=".006 1"/>
|
||||
</default>
|
||||
|
||||
<asset>
|
||||
<texture name="skybox" type="skybox" builtin="gradient" rgb1=".4 .6 .8" rgb2="0 0 0"
|
||||
width="256" height="256" mark="random" markrgb="1 1 1" random="0.003"/>
|
||||
<mesh name="box" vertex="-1 -1 -1 1 -1 -1 1 1 -1 1 1 1 1 -1 1 -1 1 -1 -1 1 1 -1 -1 1"
|
||||
scale="1 1 .1"/>
|
||||
<mesh name="boxoid" vertex="-1 -1 -1 1 -1 -1 1 1 -1 1 1 1 1 -1 1 -1 1 -1 -1 1 .5 -1 -1 2"
|
||||
scale=".3 .2 .1"/>
|
||||
<mesh name="pentaprism" vertex="1 0 0 0.309 0.951 0 -0.809 0.588 0 -0.809 -0.588 0 0.309 -0.951 0
|
||||
1 0 1 0.309 0.951 1 -0.809 0.588 1 -0.809 -0.588 1 0.309 -0.951 1"
|
||||
scale=".2 .2 .1"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<light pos="0 0 3"/>
|
||||
<light pos="2 2 2" dir="-1 -1 -1"/>
|
||||
<geom type="plane" pos="0 0 -.5" size="3 3 .01"/>
|
||||
<geom type="mesh" mesh="box" pos="0 0 -.15" euler="3 7 30"/>
|
||||
<body pos="-.3 -.3 .3">
|
||||
<freejoint/>
|
||||
<geom type="mesh" mesh="boxoid" rgba=".8 0 0 1" euler="3 5 -130"/>
|
||||
</body>
|
||||
<body pos=".3 .3 0.3">
|
||||
<freejoint/>
|
||||
<geom type="box" euler="3 5 -80" size=".3 .2 .1" rgba="0 .8 0 1"/>
|
||||
</body>
|
||||
<body pos=".3 .3 .6">
|
||||
<freejoint/>
|
||||
<geom type="mesh" mesh="pentaprism" rgba="0 0 .8 1"/>
|
||||
</body>
|
||||
<body pos=".6 -.3 .3">
|
||||
<freejoint/>
|
||||
<geom type="cylinder" size=".2 .05" rgba=".6 0 .6 1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,108 @@
|
||||
<mujoco>
|
||||
<option gravity="-4 4 -10">
|
||||
<flag multiccd="enable"/>
|
||||
</option>
|
||||
|
||||
<visual>
|
||||
<quality shadowsize="8192"/>
|
||||
<scale forcewidth="0.01" contactwidth="0.05" contactheight="0.05"/>
|
||||
<map force="0.1"/>
|
||||
</visual>
|
||||
|
||||
<size njmax="3000" nconmax="1000"/>
|
||||
|
||||
<asset>
|
||||
<mesh name="box" vertex="-1 -1 -1 1 -1 -1 1 1 -1 1 1 1 1 -1 1 -1 1 -1 -1 1 1 -1 -1 1"
|
||||
scale=".05 .05 .05"/>
|
||||
</asset>
|
||||
|
||||
<default>
|
||||
<geom friction="0.4"/>
|
||||
<default class="mesh_box">
|
||||
<geom type="mesh" mesh="box"/>
|
||||
</default>
|
||||
<default class="primitive_box">
|
||||
<geom type="box" size=".05 .05 .05" friction=".1"/>
|
||||
</default>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<light pos="1 -1 1" dir="-1 1 -1"/>
|
||||
<geom type="box" size=".5 .5 .1" pos="-.4 .4 -.1" rgba=".5 .5 .8 1"/>
|
||||
<geom type="box" size=".5 .1 .5" pos="-.4 .8 .4" rgba=".5 .5 .8 1"/>
|
||||
<geom type="box" size=".1 .5 .5" pos="-.8 .4 .4" rgba=".5 .5 .8 1"/>
|
||||
<body pos="-.4 .4 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.5 .4 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.6 .4 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.4 .5 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.5 .5 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.6 .5 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.4 .6 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.5 .6 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.6 .6 .1">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
|
||||
<body pos="-.4 .4 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.5 .4 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.6 .4 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.4 .5 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.5 .5 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.6 .5 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.4 .6 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.5 .6 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
<body pos="-.6 .6 .2">
|
||||
<freejoint/>
|
||||
<geom class="mesh_box"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
<mujoco model="collisions">
|
||||
<worldbody>
|
||||
<body name="box">
|
||||
<geom name="box" type="box" size="1 1 1"/>
|
||||
</body>
|
||||
<body pos="1.2 1.2 0.0">
|
||||
<!-- collides with box -->
|
||||
<joint/>
|
||||
<geom name="sphere_collides" type="sphere" size="1"/>
|
||||
</body>
|
||||
<body pos="-0.9 -0.9 0.0">
|
||||
<!-- collides with box, and is a predefined pair -->
|
||||
<joint/>
|
||||
<geom name="sphere_predefined" type="sphere" size="0.1"/>
|
||||
</body>
|
||||
<body pos="1.8 -1.8 0.0" >
|
||||
<!-- doesn't collide with box, but requires narrowphase checking -->
|
||||
<joint/>
|
||||
<geom name="sphere_narrowphase" type="sphere" size="1"/>
|
||||
</body>
|
||||
<body pos="-2.1 -2.1 0.0">
|
||||
<!-- doesn't collide with box, and can be eliminated in broadphase -->
|
||||
<joint/>
|
||||
<geom name="sphere_broadphase" type="sphere" size="1"/>
|
||||
</body>
|
||||
<body name="sphere_excluded">
|
||||
<!-- collides with box, but is excluded -->
|
||||
<joint/>
|
||||
<geom name="sphere_excluded" type="sphere" pos="0.0 0.0 0.0" size="0.2"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<contact>
|
||||
<exclude body1="box" body2="sphere_excluded"/>
|
||||
<pair geom1="box" geom2="sphere_predefined"/>
|
||||
</contact>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
body2 is held by body1 through the connect constraint, so the force sensor on body1
|
||||
should register -2*gravity (for the weight of both bodies).
|
||||
-->
|
||||
<option gravity="1 2 3"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom density="1000" size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<geom/>
|
||||
<site name="sensor"/>
|
||||
</body>
|
||||
<body name="body2" pos="1 2 3">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<connect body1="body1" body2="body2" anchor="0 0 0"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="-2 -4 -6"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
body2 is held by body1 through the connect constraint, so the force sensor on body1
|
||||
should register 20 (for the weight of both bodies).
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<geom/>
|
||||
<site name="sensor"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 0 -0.1">
|
||||
<joint type="slide" axis="0 0 1"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<connect body1="body1" body2="body2" anchor="0 0 0"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="0 0 2"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
body2 is held by body1 through the connect constraint, so the force sensor on body1
|
||||
should register 20 (for the weight of both bodies), rotated into the y-z plane.
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1" euler="0 45 0">
|
||||
<geom/>
|
||||
<site name="sensor" euler="0 0 90"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 0 -0.2">
|
||||
<joint type="slide" axis="0 0 1"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<connect body1="body1" body2="body2" anchor="0 0 0"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="0 1.41421356237 1.41421356237"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,59 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
Same as force_free.xml, but with a distractor constraint.
|
||||
-->
|
||||
<option gravity="1 2 3"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body0" pos="-3 0 ">
|
||||
<joint name="joint0" type="slide" axis="0 0 1"/>
|
||||
<geom/>
|
||||
</body>
|
||||
<body name="body1">
|
||||
<geom/>
|
||||
<site name="sensor"/>
|
||||
</body>
|
||||
<body name="body2" pos="1 2 3">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
<body name="body3" pos="3 0 0">
|
||||
<geom/>
|
||||
<body name="body3b" pos="3 0 -1">
|
||||
<joint type="slide" axis="0 0 1" frictionloss="9"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</body>
|
||||
<body name="body4" pos="4 0 0">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<joint joint1="joint0"/>
|
||||
<weld body1="body3" body2="body4"/>
|
||||
<connect body1="body1" body2="body2" anchor="0 0 0"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="-2 -4 -6"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
body2 is free, but because it's offset on y it wants to rotate about x due to gravity on z. The
|
||||
connect prevents that, so the torque sensor should record the weight*offset of the second body.
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<geom/>
|
||||
<site name="sensor"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 2 0">
|
||||
<freejoint/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<connect body1="body1" body2="body2" anchor="0 2 0"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<torque site="sensor" user="2 0 0"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
body2 is held by body1 through the weld constraint, so the force sensor on body1
|
||||
should register -2*gravity (for the weight of both bodies).
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<geom/>
|
||||
<site name="sensor"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 0 -.1">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<weld body1="body1" body2="body2"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="0 0 2"/>
|
||||
<torque site="sensor" user="0 0 0"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
body2 is held by body1 through the weld constraint, so the force sensor on body1
|
||||
should register -2*gravity (for the weight of both bodies).
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1" euler="0 45 0">
|
||||
<geom/>
|
||||
<site name="sensor" euler="0 0 90"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 0 -.2" euler="30 30 30">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<weld body1="body1" body2="body2"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="0 1.41421356237 1.41421356237"/>
|
||||
<torque site="sensor" user="0 0 0"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
the force sensor on body1 should register 2N (for the weight of both bodies).
|
||||
body2 is welded to body1 at an offset of 1m, so the torque sensor should register 1Nm.
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1">
|
||||
<geom/>
|
||||
<site name="sensor"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 1 0">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<weld body1="body1" body2="body2"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="0 0 2"/>
|
||||
<torque site="sensor" user="1 0 0"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
The force sensor on body1 should register 2 (for the weight of both bodies).
|
||||
body2 is welded to body1 at an offset of 1, so the torque sensor should register 1.
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1" euler="0 45 0">
|
||||
<geom/>
|
||||
<site name="sensor" euler="0 0 90"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 1 0" euler="30 30 30">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<weld body1="body1" body2="body2"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="0 1.41421356237 1.41421356237"/>
|
||||
<torque site="sensor" user="0 -0.70710678118 0.70710678118"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<!-- Copyright 2021 DeepMind Technologies Limited
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<mujoco>
|
||||
<!--
|
||||
the force sensor on body1 should register 2N (for the weight of both bodies).
|
||||
body2 is welded to body1 at an offset of 1m, so the torque sensor should register 1Nm.
|
||||
-->
|
||||
<option gravity="0 0 -1"/>
|
||||
|
||||
<default>
|
||||
<!-- A cube of size 0.1^3 with density of 1000 has mass of 1. -->
|
||||
<geom size="0.05 0.05 0.05" type="box"/>
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body name="body1" euler="0 45 0">
|
||||
<geom/>
|
||||
<site name="sensor" euler="0 0 90"/>
|
||||
</body>
|
||||
<body name="body2" pos="0 1 0" euler="30 30 30">
|
||||
<joint type="free"/>
|
||||
<geom/>
|
||||
</body>
|
||||
</worldbody>
|
||||
|
||||
<equality>
|
||||
<weld body1="body1" body2="body2"/>
|
||||
</equality>
|
||||
|
||||
<sensor>
|
||||
<force site="sensor" user="0 1.41421356237 1.41421356237"/>
|
||||
<torque site="sensor" user="0 -0.70710678118 0.70710678118"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "test/fixture.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <absl/base/const_init.h>
|
||||
#include <absl/strings/str_cat.h>
|
||||
#include <absl/synchronization/mutex.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
ABSL_CONST_INIT static absl::Mutex handlers_mutex(absl::kConstInit);
|
||||
static int guard_count ABSL_GUARDED_BY(handlers_mutex) = 0;
|
||||
|
||||
void default_mj_error_handler(const char* msg) {
|
||||
FAIL() << "mju_user_error: " << msg;
|
||||
}
|
||||
|
||||
void default_mj_warning_handler(const char* msg) {
|
||||
ADD_FAILURE() << "mju_user_warning: " << msg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MujocoErrorTestGuard::MujocoErrorTestGuard() {
|
||||
absl::MutexLock lock(&handlers_mutex);
|
||||
if (++guard_count == 1) {
|
||||
mju_user_error = default_mj_error_handler;
|
||||
mju_user_warning = default_mj_warning_handler;
|
||||
}
|
||||
}
|
||||
|
||||
MujocoErrorTestGuard::~MujocoErrorTestGuard() {
|
||||
absl::MutexLock lock(&handlers_mutex);
|
||||
if (--guard_count == 0) {
|
||||
mju_user_error = nullptr;
|
||||
mju_user_warning = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string GetTestDataFilePath(std::string_view path) {
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
const std::string GetModelPath(std::string_view path) {
|
||||
return absl::StrCat("../model/", path);
|
||||
}
|
||||
|
||||
mjModel* LoadModelFromString(std::string_view xml, char* error,
|
||||
int error_size) {
|
||||
static constexpr char file[] = "filename.xml";
|
||||
// mjVFS structs need to be allocated on the heap, because it's ~2MB
|
||||
auto vfs = std::make_unique<mjVFS>();
|
||||
mj_defaultVFS(vfs.get());
|
||||
mj_makeEmptyFileVFS(vfs.get(), file, xml.size());
|
||||
int file_idx = mj_findFileVFS(vfs.get(), file);
|
||||
memcpy(vfs->filedata[file_idx], xml.data(), xml.size());
|
||||
mjModel* m = mj_loadXML(file, vfs.get(), error, error_size);
|
||||
mj_deleteFileVFS(vfs.get(), file);
|
||||
return m;
|
||||
}
|
||||
|
||||
const std::string GetFileContents(const char* path) {
|
||||
std::ifstream ifs;
|
||||
ifs.open(path, std::ifstream::in);
|
||||
EXPECT_FALSE(ifs.fail());
|
||||
std::ostringstream sstream;
|
||||
sstream << ifs.rdbuf();
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
const std::string SaveAndReadXml(const mjModel* model) {
|
||||
EXPECT_THAT(model, testing::NotNull());
|
||||
|
||||
constexpr int kMaxPathLen = 1024;
|
||||
std::string path_template =
|
||||
std::filesystem::temp_directory_path().append("tmp.XXXXXX").string();
|
||||
EXPECT_LT(path_template.size(), kMaxPathLen);
|
||||
|
||||
char filepath[kMaxPathLen];
|
||||
mju_strncpy(filepath, path_template.c_str(), path_template.size() + 1);
|
||||
|
||||
#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
|
||||
int fd = mkstemp(filepath);
|
||||
EXPECT_NE(fd, -1) << std::strerror(errno);
|
||||
#elif defined(_WIN32)
|
||||
EXPECT_NE(_mktemp_s(filepath), EINVAL);
|
||||
#endif
|
||||
|
||||
mj_saveLastXML(filepath, model, nullptr, 0);
|
||||
std::string contents = GetFileContents(filepath);
|
||||
|
||||
#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
|
||||
close(fd);
|
||||
#endif
|
||||
std::remove(filepath);
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MUJOCO_TEST_FIXTURE_H_
|
||||
#define MUJOCO_TEST_FIXTURE_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <absl/strings/string_view.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
|
||||
namespace mujoco {
|
||||
|
||||
// Installs and uninstalls error callbacks on MuJoCo that fail the currently
|
||||
// running test if triggered. Prefer the use of MujocoTest, unless using a
|
||||
// test fixture is not possible.
|
||||
class MujocoErrorTestGuard {
|
||||
public:
|
||||
// Sets up error and warning callbacks on MuJoCo that will fail the test if
|
||||
// triggered.
|
||||
MujocoErrorTestGuard();
|
||||
|
||||
// Clears up the callbacks from the constructor.
|
||||
~MujocoErrorTestGuard();
|
||||
};
|
||||
|
||||
// A test fixture which simplifies writing tests for the MuJoCo C API.
|
||||
// By default, any MuJoCo operation which triggers a warning or error will
|
||||
// trigger a test failure.
|
||||
class MujocoTest : public ::testing::Test {
|
||||
private:
|
||||
MujocoErrorTestGuard error_guard;
|
||||
};
|
||||
|
||||
// Returns a path to a data file, under the mujoco/test directory.
|
||||
const std::string GetTestDataFilePath(absl::string_view path);
|
||||
|
||||
// Returns a path to a data file, under the mujoco/model directory.
|
||||
const std::string GetModelPath(absl::string_view path);
|
||||
|
||||
// Returns a newly-allocated mjModel, loaded from the contents of xml.
|
||||
// On failure returns nullptr and populates the error array if present.
|
||||
mjModel* LoadModelFromString(absl::string_view xml, char* error = nullptr,
|
||||
int error_size = 0);
|
||||
|
||||
// Returns a string loaded from first saving the model given an input.
|
||||
const std::string SaveAndReadXml(const mjModel* model);
|
||||
|
||||
} // namespace mujoco
|
||||
#endif // MUJOCO_TEST_FIXTURE_H_
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "test/fixture.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest-spi.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
// Tests for the MujocoTest test fixture itself.
|
||||
using MujocoTestTest = MujocoTest;
|
||||
class MujocoErrorTestGuardTest : public ::testing::Test {};
|
||||
using ::testing::IsNull;
|
||||
|
||||
|
||||
TEST_F(MujocoTestTest, MjUserWarningFailsTest) {
|
||||
EXPECT_NONFATAL_FAILURE(mju_warning("Warning."), "Warning.");
|
||||
}
|
||||
|
||||
TEST_F(MujocoTestTest, MjUserErrorFailsTest) {
|
||||
EXPECT_FATAL_FAILURE(mju_error("Error."), "Error.");
|
||||
}
|
||||
|
||||
TEST_F(MujocoErrorTestGuardTest, NestedErrorGuards) {
|
||||
{
|
||||
MujocoErrorTestGuard guard1;
|
||||
{
|
||||
MujocoErrorTestGuard guard2;
|
||||
}
|
||||
EXPECT_FATAL_FAILURE(mju_error("Error."), "Error.");
|
||||
EXPECT_NONFATAL_FAILURE(mju_warning("Warning."), "Warning.");
|
||||
}
|
||||
EXPECT_THAT(mju_user_error, IsNull());
|
||||
EXPECT_THAT(mju_user_warning, IsNull());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for structures in the public headers.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjdata.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjrender.h>
|
||||
#include <mujoco/mjui.h>
|
||||
#include <mujoco/mjvisualize.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using HeaderTest = MujocoTest;
|
||||
|
||||
TEST_F(HeaderTest, EnumsAreInts) {
|
||||
EXPECT_EQ(sizeof(mjtDisableBit), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtEnableBit), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtJoint), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtGeom), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtCamLight), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtTexture), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtIntegrator), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtCollision), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtCone), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtJacobian), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtSolver), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtEq), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtWrap), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtTrn), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtDyn), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtGain), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtBias), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtObj), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtConstraint), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtConstraintState), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtSensor), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtStage), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtDataType), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtLRMode), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtWarning), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtTimer), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtGridPos), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtFramebuffer), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtFontScale), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtFont), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtButton), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtEvent), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtItem), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtCatBit), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtMouse), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtPertBit), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtCamera), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtLabel), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtFrame), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtVisFlag), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtRndFlag), sizeof(int));
|
||||
EXPECT_EQ(sizeof(mjtStereo), sizeof(int));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(MUJOCO_BUILD_EXAMPLES)
|
||||
include(ShellTests)
|
||||
|
||||
add_mujoco_shell_test(compile_test compile)
|
||||
add_mujoco_shell_test(testspeed_test testspeed)
|
||||
endif()
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
MODEL="${CMAKE_SOURCE_DIR}/model/humanoid100/humanoid100.xml"
|
||||
OUTPUT_FILE="${TEST_TMPDIR}/compiled.mjb"
|
||||
|
||||
|
||||
die() { echo "$*" 1>&2 ; exit 1; }
|
||||
|
||||
if [ -z "$TARGET_BINARY" ]; then
|
||||
die "Expecting environment variable TARGET_BINARY."
|
||||
fi
|
||||
|
||||
if [ -z "$MUJOCO_DLL_DIR" ]; then
|
||||
# Extend PATH to include the directory containing the mujoco DLL.
|
||||
# This is needed on Windows.
|
||||
PATH=$PATH:$MUJOCO_DLL_DIR
|
||||
fi
|
||||
|
||||
"$TARGET_BINARY" "$MODEL" "$OUTPUT_FILE" || die "compile failed"
|
||||
|
||||
if [ ! -s "$OUTPUT_FILE" ]; then
|
||||
die "Output file empty or missing (${OUTPUT_FILE})."
|
||||
fi
|
||||
|
||||
echo "PASS"
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
MODEL="${CMAKE_SOURCE_DIR}/model/humanoid100/humanoid100.xml"
|
||||
|
||||
die() { echo "$*" 1>&2 ; exit 1; }
|
||||
|
||||
if [ -z "$TARGET_BINARY" ]; then
|
||||
die "Expecting environment variable TARGET_BINARY."
|
||||
fi
|
||||
|
||||
if [ -z "$MUJOCO_DLL_DIR" ]; then
|
||||
# Extend PATH to include the directory containing the mujoco DLL.
|
||||
# This is needed on Windows.
|
||||
PATH=$PATH:$MUJOCO_DLL_DIR
|
||||
fi
|
||||
|
||||
readonly EXPECTED_STR='Simulation time'
|
||||
("$TARGET_BINARY" "$MODEL" 10 || die "testspeed failed") | grep "$EXPECTED_STR"
|
||||
|
||||
if [ "$?" != 0 ]; then
|
||||
die "Expected string not found in output ($EXPECTED_STR)."
|
||||
fi
|
||||
|
||||
cd $CURRENT_DIR
|
||||
echo "PASS"
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
mujoco_test(user_model_test)
|
||||
target_link_libraries(user_model_test fixture gmock)
|
||||
|
||||
mujoco_test(user_objects_test)
|
||||
target_link_libraries(user_objects_test fixture gmock)
|
||||
|
||||
mujoco_test(user_mesh_test)
|
||||
target_link_libraries(user_mesh_test fixture gmock)
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<mujoco>
|
||||
<default>
|
||||
<geom size=".8 .9"/>
|
||||
<!-- These are arbitrary, the capsule-related `MjCGeomTest` instances pass for any values. -->
|
||||
</default>
|
||||
|
||||
<worldbody>
|
||||
<body pos="-2 0 0" name="sphere">
|
||||
<geom type="sphere"/>
|
||||
</body>
|
||||
|
||||
<body name="cylinder">
|
||||
<geom type="cylinder"/>
|
||||
</body>
|
||||
|
||||
<body pos="2 0 0" name="capsule">
|
||||
<geom type="capsule"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
v -0.500000 -0.500000 0.500000
|
||||
v 0.500000 -0.500000 0.500000
|
||||
v -0.500000 0.500000 0.500000
|
||||
v 0.500000 0.500000 0.500000
|
||||
v -0.500000 0.500000 -0.500000
|
||||
v 0.500000 0.500000 -0.500000
|
||||
v -0.500000 -0.500000 -0.500000
|
||||
v 0.500000 -0.500000 -0.500000
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh file="cube.obj"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="cube"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
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
|
||||
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
|
||||
f 1 2 3
|
||||
f 5 7 8
|
||||
f 9 12 10
|
||||
f 16 15 14
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh file="duplicate.obj"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="duplicate"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
BIN
Binary file not shown.
+8
@@ -0,0 +1,8 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh file="duplicate_vertices.stl"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="duplicate_vertices"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh file="torus.obj" scale="1 -1 1"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="torus"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<mujoco>
|
||||
<compiler meshdir="textured_torus" texturedir="textured_torus"/>
|
||||
<asset>
|
||||
<mesh name="torus" file="textured_torus.obj"/>
|
||||
<mesh name="flipped_torus" file="textured_torus.obj" scale="-1 1 1"/>
|
||||
<texture file="carpet.png" type="2d"/>
|
||||
<material name="carpet" texture="carpet"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="torus" material="carpet"/>
|
||||
<geom type="mesh" mesh="flipped_torus" material="carpet" pos="0 0 1"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,14 @@
|
||||
#
|
||||
# Wavefront material file
|
||||
# Converted by Meshlab Group
|
||||
#
|
||||
|
||||
newmtl material_0
|
||||
Ka 0.200000 0.200000 0.200000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 1.000000 1.000000 1.000000
|
||||
Tr 1.000000
|
||||
illum 2
|
||||
Ns 0.000000
|
||||
map_Kd carpet.png
|
||||
|
||||
+2961
File diff suppressed because it is too large
Load Diff
Vendored
+1168
File diff suppressed because it is too large
Load Diff
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh file="torus.obj"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="torus"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
Vendored
+2082
File diff suppressed because it is too large
Load Diff
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh file="torus.obj"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="mesh" mesh="torus"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for user/user_objects.cc.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using MjCMeshTest = MujocoTest;
|
||||
|
||||
// yuval: why do we want these anyway? why not string literals in the tests?
|
||||
// this does not improve readabillity IMO.
|
||||
static const char* const kDuplicateVerticesPath =
|
||||
"user/testdata/duplicate_vertices.xml";
|
||||
static const char* const kCubePath =
|
||||
"user/testdata/cube.xml";
|
||||
static const char* const kTorusPath =
|
||||
"user/testdata/torus.xml";
|
||||
static const char* const kTorusQuadsPath =
|
||||
"user/testdata/torus_quads.xml";
|
||||
static const char* const kTexturedTorusPath =
|
||||
"user/testdata/textured_torus.xml";
|
||||
static const char* const kDuplicateOBJPath =
|
||||
"user/testdata/duplicate.xml";
|
||||
|
||||
// ------------- test vertex de-duplication (STL) ------------------------------
|
||||
|
||||
TEST_F(MjCMeshTest, DeDuplicateSTLVertices) {
|
||||
const std::string xml_path = GetTestDataFilePath(kDuplicateVerticesPath);
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, error_sz);
|
||||
ASSERT_EQ(model->nmeshvert, 4);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// ------------- test OBJ loading ----------------------------------------------
|
||||
|
||||
using MjCMeshTest = MujocoTest;
|
||||
|
||||
TEST_F(MjCMeshTest, LoadCube) {
|
||||
const std::string xml_path = GetTestDataFilePath(kCubePath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), 0, nullptr, 0);
|
||||
ASSERT_GT(model->ngeom, 0);
|
||||
ASSERT_EQ(model->nmeshvert, 8);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, LoadTorus) {
|
||||
const std::string xml_path = GetTestDataFilePath(kTorusPath);
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
|
||||
ASSERT_GT(model->ngeom, 0);
|
||||
ASSERT_GT(model->nmeshvert, 0);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, LoadTorusQuads) {
|
||||
const std::string xml_path = GetTestDataFilePath(kTorusQuadsPath);
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
|
||||
ASSERT_GT(model->ngeom, 0);
|
||||
ASSERT_GT(model->nmeshvert, 0);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, LoadTexturedTorus) {
|
||||
const std::string xml_path = GetTestDataFilePath(kTexturedTorusPath);
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
|
||||
ASSERT_GT(model->ngeom, 0);
|
||||
ASSERT_GT(model->nmeshvert, 0);
|
||||
ASSERT_GT(model->ntex, 0);
|
||||
ASSERT_GT(model->ntexdata, 0);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, KeepDuplicateOBJVertices) {
|
||||
const std::string xml_path = GetTestDataFilePath(kDuplicateOBJPath);
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, error_sz);
|
||||
ASSERT_EQ(model->nmeshvert, 12);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCMeshTest, SaveMeshOnce) {
|
||||
const std::string xml_path = GetTestDataFilePath(kCubePath);
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, Not(testing::HasSubstr("vertex")));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for user/user_model.cc.
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
using UserDataTest = MujocoTest;
|
||||
|
||||
static std::vector<mjtNum> GetRow(const mjtNum* array, int ncolumn, int row) {
|
||||
return std::vector<mjtNum>(array + ncolumn * row,
|
||||
array + ncolumn * (row + 1));
|
||||
}
|
||||
|
||||
// ------------- test automatic inference of nuser_xxx -------------------------
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserBody) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body user="1 2 3"/>
|
||||
<body user="2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_body, 3);
|
||||
EXPECT_THAT(GetRow(m->body_user, m->nuser_body, 1), ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->body_user, m->nuser_body, 2), ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserJoint) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint user="1 2 3"/>
|
||||
<joint user="2 3"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_jnt, 3);
|
||||
EXPECT_THAT(GetRow(m->jnt_user, m->nuser_jnt, 0), ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->jnt_user, m->nuser_jnt, 1), ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserGeom) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<geom size="1" user="1 2 3"/>
|
||||
<geom size="1" user="2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_geom, 3);
|
||||
EXPECT_THAT(GetRow(m->geom_user, m->nuser_geom, 0), ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->geom_user, m->nuser_geom, 1), ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserSite) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<site user="1 2 3"/>
|
||||
<site user="2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_site, 3);
|
||||
EXPECT_THAT(GetRow(m->site_user, m->nuser_site, 0), ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->site_user, m->nuser_site, 1), ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserCamera) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<camera user="1 2 3"/>
|
||||
<camera user="2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_cam, 3);
|
||||
EXPECT_THAT(GetRow(m->cam_user, m->nuser_cam, 0), ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->cam_user, m->nuser_cam, 1), ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserTendon) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<site name="a"/>
|
||||
<site name="b"/>
|
||||
</worldbody>
|
||||
<tendon>
|
||||
<spatial user="1 2 3">
|
||||
<site site="a"/>
|
||||
<site site="b"/>
|
||||
</spatial>
|
||||
<spatial user="2 3">
|
||||
<site site="a"/>
|
||||
<site site="b"/>
|
||||
</spatial>
|
||||
</tendon>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_tendon, 3);
|
||||
EXPECT_THAT(GetRow(m->tendon_user, m->nuser_tendon, 0), ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->tendon_user, m->nuser_tendon, 1), ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserActuator) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="a"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<motor joint="a" user="1 2 3"/>
|
||||
<motor joint="a" user="2 3"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_actuator, 3);
|
||||
EXPECT_THAT(GetRow(m->actuator_user, m->nuser_actuator, 0),
|
||||
ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->actuator_user, m->nuser_actuator, 1),
|
||||
ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, AutoNUserSensor) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<site name="a"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<accelerometer site="a" user="1 2 3"/>
|
||||
<gyro site="a" user="2 3"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml);
|
||||
ASSERT_EQ(m->nuser_sensor, 3);
|
||||
EXPECT_THAT(GetRow(m->sensor_user, m->nuser_sensor, 0), ElementsAre(1, 2, 3));
|
||||
EXPECT_THAT(GetRow(m->sensor_user, m->nuser_sensor, 1), ElementsAre(2, 3, 0));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,519 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for user/user_objects.cc.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjtnum.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
static std::vector<mjtNum> GetRow(const mjtNum* array, int ncolumn, int row) {
|
||||
return std::vector<mjtNum>(array + ncolumn * row,
|
||||
array + ncolumn * (row + 1));
|
||||
}
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
|
||||
|
||||
// ------------- test relative frame sensor compilation-------------------------
|
||||
|
||||
using RelativeFrameSensorParsingTest = MujocoTest;
|
||||
|
||||
TEST_F(RelativeFrameSensorParsingTest, RefTypeNotRequired) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="sensorized"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="body" objname="sensorized"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(RelativeFrameSensorParsingTest, ReferenceBodyFound) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="reference"/>
|
||||
<body name="sensorized"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="xbody" objname="sensorized"
|
||||
reftype="xbody" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
ASSERT_EQ(model->sensor_reftype[0], mjOBJ_XBODY);
|
||||
ASSERT_EQ(model->sensor_refid[0], 1);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(RelativeFrameSensorParsingTest, MissingRefname) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="reference"/>
|
||||
<body name="sensorized"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="body" objname="sensorized"
|
||||
reftype="body" refname=""/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
LoadModelFromString(xml, error.data(), error.size());
|
||||
EXPECT_THAT(error.data(), HasSubstr("missing name of reference frame"));
|
||||
}
|
||||
|
||||
TEST_F(RelativeFrameSensorParsingTest, BadRefName) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="reference"/>
|
||||
<body name="sensorized"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="body" objname="sensorized"
|
||||
reftype="body" refname="wrong_name"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
LoadModelFromString(xml, error.data(), error.size());
|
||||
EXPECT_THAT(error.data(), HasSubstr("unrecognized name of reference frame"));
|
||||
}
|
||||
|
||||
TEST_F(RelativeFrameSensorParsingTest, BadRefType) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<light name="reference"/>
|
||||
<body name="sensorized"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objtype="body" objname="sensorized"
|
||||
reftype="light" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
LoadModelFromString(xml, error.data(), error.size());
|
||||
EXPECT_THAT(error.data(), HasSubstr("reference frame object must be"));
|
||||
}
|
||||
|
||||
// ------------- test capsule inertias -----------------------------------------
|
||||
|
||||
static const char* const kCapsuleInertiaPath =
|
||||
"user/testdata/capsule_inertia.xml";
|
||||
|
||||
using MjCGeomTest = MujocoTest;
|
||||
|
||||
static constexpr int kSphereBodyId = 1, kCylinderBodyId = 2,
|
||||
kCapsuleBodyId = 3, kCapsuleGeomId = 2;
|
||||
|
||||
TEST_F(MjCGeomTest, CapsuleMass) {
|
||||
const std::string xml_path = GetTestDataFilePath(kCapsuleInertiaPath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
// Mass of capsule should equal mass of cylinder + mass of sphere.
|
||||
mjtNum sphere_cylinder_mass =
|
||||
model->body_mass[kSphereBodyId] + model->body_mass[kCylinderBodyId];
|
||||
mjtNum capsule_mass = model->body_mass[kCapsuleBodyId];
|
||||
EXPECT_DOUBLE_EQ(sphere_cylinder_mass, capsule_mass);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCGeomTest, CapsuleInertiaZ) {
|
||||
const std::string xml_path = GetTestDataFilePath(kCapsuleInertiaPath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
// z-inertia of capsule should equal sphere + cylinder z-inertia.
|
||||
mjtNum sphere_cylinder_z_inertia =
|
||||
model->body_inertia[3*kSphereBodyId + 2] +
|
||||
model->body_inertia[3*kCylinderBodyId + 2];
|
||||
mjtNum capsule_z_inertia = model->body_inertia[3*kCapsuleBodyId + 2];
|
||||
EXPECT_DOUBLE_EQ(sphere_cylinder_z_inertia, capsule_z_inertia);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(MjCGeomTest, CapsuleInertiaX) {
|
||||
const std::string xml_path = GetTestDataFilePath(kCapsuleInertiaPath);
|
||||
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
|
||||
|
||||
// The CoM of a solid hemisphere is 3/8*radius away from from the disk.
|
||||
mjtNum hs_com = model->geom_size[3*kCapsuleGeomId] * 3 / 8;
|
||||
|
||||
// The mass of the two hemispherical end-caps is just the mass of the sphere.
|
||||
mjtNum sphere_mass = model->body_mass[1];
|
||||
|
||||
// x-inertia of capsule should equal sphere + cylinder x-inertias, with
|
||||
// corrections from shifting the hemispheres using parallel axis theorem.
|
||||
mjtNum sphere_cylinder_x_inertia = model->body_inertia[3*kSphereBodyId] +
|
||||
model->body_inertia[3*kCylinderBodyId];
|
||||
|
||||
// Parallel axis-theorem #1: translate the hemispheres in to the origin.
|
||||
mjtNum translate_in = hs_com;
|
||||
sphere_cylinder_x_inertia -= sphere_mass * translate_in*translate_in;
|
||||
|
||||
// Parallel axis-theorem #2: translate the hemispheres out to the end caps.
|
||||
mjtNum cylinder_half_length = model->geom_size[3*kCapsuleGeomId + 1];
|
||||
mjtNum translate_out = cylinder_half_length + hs_com;
|
||||
sphere_cylinder_x_inertia += sphere_mass * translate_out*translate_out;
|
||||
|
||||
// Compare native capsule inertia and computed inertia.
|
||||
mjtNum capsule_x_inertia = model->body_inertia[3*kCapsuleBodyId];
|
||||
EXPECT_DOUBLE_EQ(sphere_cylinder_x_inertia, capsule_x_inertia);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// ------------- test quaternion normalization----------------------------------
|
||||
|
||||
using QuatNorm = MujocoTest;
|
||||
|
||||
TEST_F(QuatNorm, QuatNotNormalized) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<site quat="1 2 2 4"/>
|
||||
<camera quat="1 2 2 4"/>
|
||||
<body quat="1 2 2 4">
|
||||
<geom quat="1 2 2 4" size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
|
||||
EXPECT_THAT(GetRow(m->body_quat, 4, 1), ElementsAre(1./5, 2./5, 2./5, 4./5));
|
||||
EXPECT_THAT(GetRow(m->geom_quat, 4, 0), ElementsAre(1./5, 2./5, 2./5, 4./5));
|
||||
EXPECT_THAT(GetRow(m->site_quat, 4, 0), ElementsAre(1./5, 2./5, 2./5, 4./5));
|
||||
EXPECT_THAT(GetRow(m->cam_quat, 4, 0), ElementsAre(1./5, 2./5, 2./5, 4./5));
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
// ------------- test actuator order -------------------------------------------
|
||||
|
||||
using ActuatorTest = MujocoTest;
|
||||
|
||||
TEST_F(ActuatorTest, BadOrder) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" dyntype="filter"/>
|
||||
<general joint="hinge"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
mjModel* model = LoadModelFromString(xml, error, error_sz);
|
||||
EXPECT_THAT(model, ::testing::IsNull());
|
||||
EXPECT_THAT(error, HasSubstr("stateless actuators must come before"));
|
||||
}
|
||||
|
||||
|
||||
// ------------- test actlimited and actrange fields ---------------------------
|
||||
|
||||
using ActRangeTest = MujocoTest;
|
||||
|
||||
TEST_F(ActRangeTest, ActRangeParsed) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general dyntype="integrator" joint="hinge" actlimited="true" actrange="-1 1.5"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* m = LoadModelFromString(xml, nullptr, 0);
|
||||
EXPECT_EQ(m->actuator_actlimited[0], 1);
|
||||
EXPECT_EQ(m->actuator_actrange[0], -1);
|
||||
EXPECT_EQ(m->actuator_actrange[1], 1.5);
|
||||
|
||||
mj_deleteModel(m);
|
||||
}
|
||||
|
||||
TEST_F(ActRangeTest, ActRangeBad) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general dyntype="integrator" joint="hinge" actlimited="true" actrange="1 -1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("invalid activation range"));
|
||||
}
|
||||
|
||||
TEST_F(ActRangeTest, ActRangeUndefined) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general dyntype="integrator" joint="hinge" actlimited="true"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("invalid activation range"));
|
||||
}
|
||||
|
||||
TEST_F(ActRangeTest, ActRangeNoDyntype) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" actlimited="true" actrange="-1 1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("actrange specified but dyntype is 'none'"));
|
||||
}
|
||||
|
||||
TEST_F(ActRangeTest, ActRangeDefaultsPropagate) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<option timestep="0.01"/>
|
||||
<default>
|
||||
<general dyntype="integrator" actlimited="true" actrange="-1 1"/>
|
||||
<default class="dclass">
|
||||
<general actlimited="false" actrange="2 3"/>
|
||||
</default>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="slide" type="slide" axis="1 0 0"/>
|
||||
<geom size=".1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="slide"/>
|
||||
<general joint="slide" class="dclass"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
|
||||
// first actuator
|
||||
EXPECT_THAT(model->actuator_actlimited[0], 1);
|
||||
EXPECT_THAT(model->actuator_actrange[0], -1);
|
||||
EXPECT_THAT(model->actuator_actrange[1], 1);
|
||||
|
||||
// // second actuator
|
||||
EXPECT_THAT(model->actuator_actlimited[1], 0);
|
||||
EXPECT_THAT(model->actuator_actrange[2], 2);
|
||||
EXPECT_THAT(model->actuator_actrange[3], 3);
|
||||
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
// ------------- test nuser_xxx fields -----------------------------------------
|
||||
|
||||
using UserDataTest = MujocoTest;
|
||||
|
||||
TEST_F(UserDataTest, NBodyTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_body="2"/>
|
||||
<worldbody>
|
||||
<body user="1 2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_body"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, NJointTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_jnt="2"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint user="1 2 3"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_jnt"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, NGeomTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_geom="2"/>
|
||||
<worldbody>
|
||||
<geom size="1" user="1 2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_geom"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, NSiteTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_site="2"/>
|
||||
<worldbody>
|
||||
<site user="1 2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_site"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, NCameraTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_cam="2"/>
|
||||
<worldbody>
|
||||
<camera user="1 2 3"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_cam"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, NTendonTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_tendon="2"/>
|
||||
<worldbody>
|
||||
<site name="a"/>
|
||||
<site name="b"/>
|
||||
</worldbody>
|
||||
<tendon>
|
||||
<spatial user="1 2 3">
|
||||
<site site="a"/>
|
||||
<site site="b"/>
|
||||
</spatial>
|
||||
</tendon>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_tendon"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, NActuatorTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_actuator="2"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="1"/>
|
||||
<joint name="a"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<motor joint="a" user="1 2 3"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_actuator"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, NSensorTooSmall) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_sensor="2"/>
|
||||
<worldbody>
|
||||
<site name="a"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<accelerometer site="a" user="1 2 3"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_sensor"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright 2021 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
mujoco_test(xml_api_test)
|
||||
target_link_libraries(xml_api_test fixture gmock)
|
||||
|
||||
mujoco_test(xml_native_reader_test)
|
||||
target_link_libraries(xml_native_reader_test fixture gmock)
|
||||
|
||||
mujoco_test(xml_native_writer_test)
|
||||
target_link_libraries(xml_native_writer_test fixture gmock)
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for xml/xml_api.cc.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjdata.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
|
||||
// ---------------------------- test mj_loadXML --------------------------------
|
||||
|
||||
using LoadXmlTest = MujocoTest;
|
||||
|
||||
TEST_F(LoadXmlTest, EmptyModel) {
|
||||
static constexpr char xml[] = "<mujoco/>";
|
||||
mjModel* model = LoadModelFromString(xml, 0, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
EXPECT_EQ(model->nq, 0);
|
||||
EXPECT_EQ(model->nv, 0);
|
||||
EXPECT_EQ(model->nu, 0);
|
||||
EXPECT_EQ(model->na, 0);
|
||||
EXPECT_EQ(model->nbody, 1); // worldbody exists even in empty model
|
||||
|
||||
mjData* data = mj_makeData(model);
|
||||
EXPECT_THAT(data, NotNull());
|
||||
mj_step(model, data);
|
||||
mj_deleteData(data);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(LoadXmlTest, InvalidXmlFailsToLoad) {
|
||||
static constexpr char invalid_xml[] = "<mujoc";
|
||||
char error[1024];
|
||||
size_t error_sz = 1024;
|
||||
mjModel* model = LoadModelFromString(invalid_xml, error, error_sz);
|
||||
EXPECT_THAT(model, IsNull()) << "Expected model loading to fail.";
|
||||
EXPECT_GT(std::strlen(error), 0);
|
||||
if (model) {
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
}
|
||||
// TODO(nimrod): Add more tests for mj_loadXML.
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for xml/xml_native_reader.cc.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::IsNull;
|
||||
|
||||
using UserDataTest = MujocoTest;
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserBody) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_body="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_body"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserJoint) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_jnt="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_jnt"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserGeom) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_geom="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_geom"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserSite) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_site="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_site"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserCamera) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_cam="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_cam"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserTendon) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_tendon="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_tendon"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserActuator) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_actuator="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_actuator"));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, InvalidNUserSensor) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<size nuser_sensor="-2"/>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("nuser_sensor"));
|
||||
}
|
||||
|
||||
// ------------- test relative frame sensor parsing ----------------------------
|
||||
|
||||
using RelativeFrameSensorParsingTest = MujocoTest;
|
||||
|
||||
TEST_F(RelativeFrameSensorParsingTest, RefNameButNoType) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<site name="reference"/>
|
||||
<body name="sensorized"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objname="sensorized" objtype="body" refname="reference"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
LoadModelFromString(xml, error.data(), error.size());
|
||||
EXPECT_THAT(error.data(), HasSubstr("but reftype is missing"));
|
||||
}
|
||||
|
||||
TEST_F(RelativeFrameSensorParsingTest, RefTypeButNoName) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<site name="reference"/>
|
||||
<body name="sensorized"/>
|
||||
</worldbody>
|
||||
<sensor>
|
||||
<framepos objname="sensorized" objtype="body" reftype="site"/>
|
||||
</sensor>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
LoadModelFromString(xml, error.data(), error.size());
|
||||
EXPECT_THAT(error.data(), HasSubstr("attribute missing: 'refname'"));
|
||||
}
|
||||
|
||||
// ------------- test actlimited parsing ---------------------------------------
|
||||
|
||||
using ActuatorTest = MujocoTest;
|
||||
|
||||
TEST_F(ActuatorTest, InvalidActlimited) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<motor joint="hinge" actlimited="invalid" actrange="-1 1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("unrecognized attribute"));
|
||||
}
|
||||
|
||||
TEST_F(ActuatorTest, IncompleteActlimited) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general joint="hinge" actlimited="true" actrange="-1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||
ASSERT_THAT(model, IsNull());
|
||||
EXPECT_THAT(error.data(), HasSubstr("actrange"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright 2021 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Tests for xml/xml_native_writer.cc.
|
||||
|
||||
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <clocale>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mjmodel.h>
|
||||
#include <mujoco/mjxmacro.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "src/cc/array_safety.h"
|
||||
#include "src/xml/xml_numeric_format.h"
|
||||
#include "test/fixture.h"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::Not;
|
||||
using ::testing::NotNull;
|
||||
|
||||
using XMLWriterTest = MujocoTest;
|
||||
|
||||
TEST_F(XMLWriterTest, KeepsEmptyClasses) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<default>
|
||||
<default class="empty_referenced"/>
|
||||
<default class="empty_unreferenced"/>
|
||||
<default class="regular">
|
||||
<geom size="0.3"/>
|
||||
</default>
|
||||
</default>
|
||||
<worldbody>
|
||||
<geom class="regular"/>
|
||||
<geom class="empty_referenced" size="0.2"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, HasSubstr("default class=\"regular\""));
|
||||
EXPECT_THAT(saved_xml, HasSubstr("default class=\"empty_referenced\""));
|
||||
EXPECT_THAT(saved_xml, HasSubstr("default class=\"empty_unreferenced\""));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, KeepsExplicitInertial) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="0.2"/>
|
||||
<inertial pos="0 1 2" mass="3"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, HasSubstr("<inertial pos=\"0 1 2\" mass=\"3\""));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, NotAddsInertial) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<geom size="0.2"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, DropsInertialIfFromGeom) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<compiler inertiafromgeom="true"/>
|
||||
<worldbody>
|
||||
<body>
|
||||
<inertial pos="0 1 2" mass="3"/>
|
||||
<geom size="0.2"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, Not(HasSubstr("inertial")));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, KeepsActlimited) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body>
|
||||
<joint name="hinge"/>
|
||||
<geom size="1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
<actuator>
|
||||
<general dyntype="filter" joint="hinge" actlimited="true" actrange="-1 1"/>
|
||||
</actuator>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, HasSubstr("actlimited=\"true\" actrange=\"-1 1\""));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, UsesTwoSpaces) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, HasSubstr(" "));
|
||||
EXPECT_THAT(saved_xml, Not(HasSubstr(" ")));
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, WritesSkin) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<body name="B0_0" pos="0 0 0">
|
||||
<composite type="cloth" count="2 2 1" spacing="0.05">
|
||||
<skin texcoord="true"/>
|
||||
<geom type="ellipsoid" size="1 1 1"/>
|
||||
</composite>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model));
|
||||
EXPECT_THAT(model->nskin, 1);
|
||||
EXPECT_THAT(mtemp->nskin, 1);
|
||||
mj_deleteModel(model);
|
||||
mj_deleteModel(mtemp);
|
||||
}
|
||||
|
||||
// check that no precision is lost when saving XMLs with FullFloatPrecision
|
||||
TEST_F(XMLWriterTest, SetPrecision) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<geom type="box" size="0.1 0.123456 0.1234567812345678"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
// save to XML and re-load, expect to lose precision
|
||||
mjModel* model_lo = LoadModelFromString(SaveAndReadXml(model));
|
||||
EXPECT_EQ(model->geom_size[1], model_lo->geom_size[1]);
|
||||
EXPECT_NE(model->geom_size[2], model_lo->geom_size[2]);
|
||||
{
|
||||
// save to XML and re-load with FullFloatPrecision, expect to maintain precision
|
||||
FullFloatPrecision increase_precision;
|
||||
mjModel* model_hi = LoadModelFromString(SaveAndReadXml(model));
|
||||
EXPECT_EQ(model->geom_size[2], model_hi->geom_size[2]);
|
||||
mj_deleteModel(model_hi);
|
||||
}
|
||||
mj_deleteModel(model_lo);
|
||||
mj_deleteModel(model);
|
||||
}
|
||||
|
||||
class XMLWriterLocaleTest : public MujocoTest {
|
||||
protected:
|
||||
char* old_locale;
|
||||
void SetUp() override {
|
||||
this->old_locale = std::setlocale(LC_ALL, nullptr);
|
||||
if (!std::setlocale(LC_ALL, "de_DE.UTF-8")) {
|
||||
GTEST_SKIP() << "This system doesn't support the de_DE.UTF-8 locale";
|
||||
}
|
||||
}
|
||||
void TearDown() override {
|
||||
std::setlocale(LC_ALL, old_locale);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(XMLWriterLocaleTest, IgnoresLocale) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<worldbody>
|
||||
<geom type="box" size="0.1 1.23 2.345"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
mjModel* model = LoadModelFromString(xml);
|
||||
std::string saved_xml = SaveAndReadXml(model);
|
||||
EXPECT_THAT(saved_xml, HasSubstr("0.1 1.23 2.345"));
|
||||
mj_deleteModel(model);
|
||||
|
||||
// Test that MuJoCo doesn't override locales for subsequent calls.
|
||||
char formatted[7];
|
||||
std::snprintf(formatted, sizeof(formatted), "%f", 3.9375);
|
||||
EXPECT_EQ(std::string(formatted), "3,9375");
|
||||
}
|
||||
|
||||
|
||||
// ------------------------ test loading and saving multiple files ---------------------------------
|
||||
namespace mju = ::mujoco::util;
|
||||
static constexpr int kFieldSize = 500;
|
||||
|
||||
// The maximum spacing between a normalised floating point number x and an
|
||||
// adjacent normalised number is 2 epsilon |x|; a factor 10 is added accounting
|
||||
// for losses during non-idempotent operations such as vector normalizations.
|
||||
mjtNum Compare(mjtNum val1, mjtNum val2) {
|
||||
mjtNum error;
|
||||
if (mju_abs(val1) <= 1 || mju_abs(val2) <= 1) {
|
||||
// Asbolute precision for small numbers
|
||||
error = mju_abs(val1-val2);
|
||||
} else {
|
||||
// Relative precision for larger numbers
|
||||
mjtNum magnitude = mju_max(mju_abs(val1), mju_abs(val2));
|
||||
error = mju_abs(val1/magnitude - val2/magnitude) / magnitude;
|
||||
}
|
||||
return error < 2*10*std::numeric_limits<double>::epsilon() ? 0 : error;
|
||||
}
|
||||
|
||||
mjtNum CompareModel(const mjModel* m1, const mjModel* m2, char (&field)[kFieldSize]) {
|
||||
mjtNum dif, maxdif = 0.0;
|
||||
|
||||
// define symbols corresponding to number of columns (needed in MJMODEL_POINTERS)
|
||||
MJMODEL_POINTERS_PREAMBLE(m1);
|
||||
|
||||
// compare ints
|
||||
#define X(name) \
|
||||
if (m1->name != m2->name) {maxdif = 1.0; mju::strcpy_arr(field, #name);}
|
||||
MJMODEL_INTS
|
||||
#undef X
|
||||
|
||||
// compare arrays
|
||||
#define X(type, name, nr, nc) \
|
||||
for (int r=0; r < m1->nr; r++) \
|
||||
for (int c=0; c < nc; c++) { \
|
||||
dif = Compare(m1->name[r*nc+c], m2->name[r*nc+c]); \
|
||||
if (dif > maxdif) {maxdif = dif; mju::strcpy_arr(field, #name);} }
|
||||
MJMODEL_POINTERS
|
||||
#undef X
|
||||
|
||||
// compare scalars in mjOption
|
||||
#define X(type, name) \
|
||||
dif = Compare(m1->opt.name, m2->opt.name); \
|
||||
if (dif > maxdif) {maxdif = dif; mju::strcpy_arr(field, #name);}
|
||||
MJOPTION_SCALARS
|
||||
#undef X
|
||||
|
||||
// compare arrays in mjOption
|
||||
#define X(name, n) \
|
||||
for (int c=0; c < n; c++) { \
|
||||
dif = Compare(m1->opt.name[c], m2->opt.name[c]); \
|
||||
if (dif > maxdif) {maxdif = dif; mju::strcpy_arr(field, #name);} }
|
||||
MJOPTION_VECTORS
|
||||
#undef X
|
||||
|
||||
// Return largest difference and field name
|
||||
return maxdif;
|
||||
}
|
||||
|
||||
TEST_F(XMLWriterTest, WriteReadCompare) {
|
||||
FullFloatPrecision increase_precision;
|
||||
// Loop over all xml files in data
|
||||
std::vector<std::string> paths = {GetModelPath("humanoid"), GetModelPath("humanoid100")};
|
||||
std::string ext(".xml");
|
||||
for (auto const& path : paths) {
|
||||
for (auto &p : std::filesystem::recursive_directory_iterator(path)) {
|
||||
if (p.path().extension() == ext) {
|
||||
std::string xml = p.path().string();
|
||||
|
||||
// load model
|
||||
std::array<char, 1000> error;
|
||||
mjModel* m = mj_loadXML(xml.c_str(), nullptr, error.data(), error.size());
|
||||
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
// make data
|
||||
mjData* d = mj_makeData(m);
|
||||
ASSERT_THAT(d, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
// save and load back
|
||||
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(m));
|
||||
ASSERT_THAT(mtemp, NotNull()) << "Failed to load model: " << error.data();
|
||||
|
||||
// compare
|
||||
char field[kFieldSize] = "";
|
||||
mjtNum result = CompareModel(m, mtemp, field);
|
||||
EXPECT_LE(result, 0) << "Loaded and saved models are different!" << std::endl
|
||||
<< "Affected file " << p.path().string() << std::endl
|
||||
<< "Different field: " << field << std::endl;
|
||||
|
||||
// delete everything
|
||||
mj_deleteData(d);
|
||||
mj_deleteModel(m);
|
||||
mj_deleteModel(mtemp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
Reference in New Issue
Block a user