Initial open sourcing of MuJoCo.

PiperOrigin-RevId: 450374687
Change-Id: Ie3225a46ce095fc28ae8e63c326a640261f562bb
This commit is contained in:
Saran Tunyasuvunakool
2022-05-23 01:08:10 -07:00
committed by Copybara-Service
parent 0e5d062302
commit 1913a02b40
275 changed files with 99607 additions and 935 deletions
+64
View File
@@ -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)
+57
View File
@@ -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
)
+68
View File
@@ -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
+223
View File
@@ -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)
+98
View File
@@ -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
+54
View File
@@ -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
+112
View File
@@ -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
+334
View File
@@ -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
+80
View File
@@ -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
+588
View File
@@ -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
+137
View File
@@ -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
+361
View File
@@ -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
+40
View File
@@ -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
+45
View File
@@ -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
+137
View File
@@ -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
+66
View File
@@ -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
+106
View File
@@ -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
+25
View File
@@ -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>
+20
View File
@@ -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>
+26
View File
@@ -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>
+108
View File
@@ -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>
+36
View File
@@ -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>
@@ -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
View File
@@ -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
+60
View File
@@ -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_
+53
View File
@@ -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
+80
View File
@@ -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
+20
View File
@@ -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()
+38
View File
@@ -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"
+38
View File
@@ -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"
+22
View File
@@ -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
View File
@@ -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>
+8
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
<mujoco>
<asset>
<mesh file="cube.obj"/>
</asset>
<worldbody>
<geom type="mesh" mesh="cube"/>
</worldbody>
</mujoco>
+20
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
<mujoco>
<asset>
<mesh file="duplicate.obj"/>
</asset>
<worldbody>
<geom type="mesh" mesh="duplicate"/>
</worldbody>
</mujoco>
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
<mujoco>
<asset>
<mesh file="duplicate_vertices.stl"/>
</asset>
<worldbody>
<geom type="mesh" mesh="duplicate_vertices"/>
</worldbody>
</mujoco>
+8
View File
@@ -0,0 +1,8 @@
<mujoco>
<asset>
<mesh file="torus.obj" scale="1 -1 1"/>
</asset>
<worldbody>
<geom type="mesh" mesh="torus"/>
</worldbody>
</mujoco>
+13
View File
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+14
View File
@@ -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
File diff suppressed because it is too large Load Diff
+1168
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
<mujoco>
<asset>
<mesh file="torus.obj"/>
</asset>
<worldbody>
<geom type="mesh" mesh="torus"/>
</worldbody>
</mujoco>
+2082
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
<mujoco>
<asset>
<mesh file="torus.obj"/>
</asset>
<worldbody>
<geom type="mesh" mesh="torus"/>
</worldbody>
</mujoco>
+120
View File
@@ -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
+192
View File
@@ -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
+519
View File
@@ -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
+22
View File
@@ -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)
+69
View File
@@ -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
+214
View File
@@ -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
+340
View File
@@ -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