Change LoadModelFromString to return a smart pointer, add MakeData, and update tests to have C++ RAII clean up model and data.

PiperOrigin-RevId: 935980153
Change-Id: I41d25bfab4935494dc984168820cb7cad123cadf
This commit is contained in:
Kyle Bayes
2026-06-22 04:18:29 -07:00
committed by Copybara-Service
parent 34d142ee50
commit 1490336955
39 changed files with 4616 additions and 5908 deletions
+179 -194
View File
@@ -24,12 +24,12 @@
#include <string>
#include <vector>
#include <absl/strings/str_format.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mujoco/mujoco.h>
#include <mujoco/mjspec.h>
#include <absl/strings/str_format.h>
#include <mujoco/mjplugin.h>
#include <mujoco/mjspec.h>
#include <mujoco/mujoco.h>
#include "src/xml/xml_api.h"
#include "test/compare_model.h"
#include "test/fixture.h"
@@ -228,7 +228,6 @@ TEST_F(MujocoTest, OriginSpecInvariantToAttachment) {
mjs_findElement(parent_spec, mjOBJ_BODY, "parent_body");
EXPECT_EQ(mjs_getOriginSpec(parent_spec_body), parent_spec);
mj_deleteSpec(child_spec);
mj_deleteSpec(parent_spec);
}
@@ -404,10 +403,9 @@ TEST_F(MujocoTest, SetToDCMotorNullable) {
double motorconst[2] = {0.05, 0.05};
double resistance = 2.0;
const char* err = mjs_setToDCMotor(actuator, motorconst, resistance,
nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr,
nullptr, 0);
const char* err =
mjs_setToDCMotor(actuator, motorconst, resistance, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, 0);
EXPECT_STREQ(err, "");
EXPECT_EQ(actuator->gainprm[0], 2.0);
EXPECT_EQ(actuator->gainprm[1], 0.05);
@@ -427,10 +425,9 @@ TEST_F(MujocoTest, SetToDCMotorDeriveKe) {
double resistance = 2.0;
double nominal[3] = {12.0, 0, 100.0}; // vn=12, omega0=100
const char* err = mjs_setToDCMotor(actuator, nullptr, resistance,
nominal, nullptr, nullptr,
nullptr, nullptr, nullptr,
nullptr, 0);
const char* err =
mjs_setToDCMotor(actuator, nullptr, resistance, nominal, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, 0);
EXPECT_STREQ(err, "");
EXPECT_EQ(actuator->gainprm[0], 2.0);
EXPECT_NEAR(actuator->gainprm[1], 0.12, 1e-5);
@@ -447,10 +444,9 @@ TEST_F(MujocoTest, SetToDCMotorFull) {
double saturation[3] = {1.0, 2.0, 3.0};
double controller[6] = {10.0, 20.0, 30.0, 40.0, 50.0, 60.0};
const char* err = mjs_setToDCMotor(actuator, motorconst, resistance,
nullptr, saturation, nullptr,
nullptr, controller, nullptr,
nullptr, 0);
const char* err =
mjs_setToDCMotor(actuator, motorconst, resistance, nullptr, saturation,
nullptr, nullptr, controller, nullptr, nullptr, 0);
EXPECT_STREQ(err, "");
EXPECT_EQ(actuator->gainprm[0], 2.0); // resistance
EXPECT_EQ(actuator->gainprm[1], 0.05); // K
@@ -473,10 +469,9 @@ TEST_F(MujocoTest, SetToDCMotorLuGre) {
double resistance = 2.0;
double lugre[5] = {100.0, 1.0, 0.5, 0.7, 10.0};
const char* err = mjs_setToDCMotor(actuator, motorconst, resistance,
nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr,
lugre, 0);
const char* err =
mjs_setToDCMotor(actuator, motorconst, resistance, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, lugre, 0);
EXPECT_STREQ(err, "");
EXPECT_EQ(actuator->dynprm[5], 100.0); // stiffness
EXPECT_EQ(actuator->dynprm[6], 1.0); // damping
@@ -764,7 +759,6 @@ TEST_F(MujocoTest, ModifyShellInertiaFails) {
mj_deleteModel(model);
}
TEST_F(MujocoTest, RecompileEdit) {
static constexpr char xml[] = R"(
<mujoco>
@@ -778,18 +772,18 @@ TEST_F(MujocoTest, RecompileEdit) {
)";
std::array<char, 1000> er;
mjSpec *spec = mj_parseXMLString(xml, 0, er.data(), er.size());
mjSpec* spec = mj_parseXMLString(xml, 0, er.data(), er.size());
EXPECT_THAT(spec, NotNull()) << er.data();
mjModel *m1 = mj_compile(spec, nullptr);
mjModel* m1 = mj_compile(spec, nullptr);
EXPECT_THAT(m1, NotNull());
// add a geom
mjsBody *world = mjs_findBody(spec, "world");
mjsGeom *geom = mjs_addGeom(world, nullptr);
mjsBody* world = mjs_findBody(spec, "world");
mjsGeom* geom = mjs_addGeom(world, nullptr);
geom->size[0] = 1;
// compile again
mjModel *m2 = mj_compile(spec, nullptr);
mjModel* m2 = mj_compile(spec, nullptr);
EXPECT_THAT(m2, NotNull());
mj_deleteModel(m1);
@@ -837,43 +831,40 @@ TEST_F(MujocoTest, RecompileCompareObjCache) {
std::array<char, 1024> error;
// load model once
mjModel* m = LoadModelFromString(xml, error.data(), error.size(), vfs.get());
MjModelPtr m =
LoadModelFromString(xml, error.data(), error.size(), vfs.get());
EXPECT_EQ(m->mesh_vert[0], -0.5);
mj_deleteModel(m);
// update cube.obj, load again
mj_deleteFileVFS(vfs.get(), "cube.obj");
mj_addBufferVFS(vfs.get(), "cube.obj", cube2, sizeof(cube2));
m = LoadModelFromString(xml, error.data(), error.size(), vfs.get());
EXPECT_EQ(m->mesh_vert[0], -1);
mj_deleteModel(m);
mj_deleteVFS(vfs.get());
}
// tiny RGB 2 x 3 PNG file
static constexpr uint8_t tex1[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02,
0x08, 0x02, 0x00, 0x00, 0x00, 0x12, 0x16, 0xf1, 0x4d, 0x00, 0x00, 0x00,
0x1c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0x78, 0xc1, 0xc0, 0xc0,
0xc0, 0xf0, 0xbf, 0xb8, 0xb8, 0x98, 0x81, 0xe1, 0x3f, 0xc3, 0xff, 0xff,
0xff, 0xc5, 0xc4, 0xc4, 0x00, 0x46, 0xd7, 0x07, 0x7f, 0xd2, 0x52, 0xa1,
0x41, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
0x82
};
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00,
0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x02, 0x08, 0x02, 0x00, 0x00, 0x00, 0x12, 0x16, 0xf1, 0x4d,
0x00, 0x00, 0x00, 0x1c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63,
0x78, 0xc1, 0xc0, 0xc0, 0xc0, 0xf0, 0xbf, 0xb8, 0xb8, 0x98, 0x81,
0xe1, 0x3f, 0xc3, 0xff, 0xff, 0xff, 0xc5, 0xc4, 0xc4, 0x00, 0x46,
0xd7, 0x07, 0x7f, 0xd2, 0x52, 0xa1, 0x41, 0x00, 0x00, 0x00, 0x00,
0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82};
// previous PNG file, but rotated by 180 degrees
static constexpr uint8_t tex2[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02,
0x08, 0x02, 0x00, 0x00, 0x00, 0x12, 0x16, 0xf1, 0x4d, 0x00, 0x00, 0x00,
0x1c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0x10, 0x13, 0x13, 0xfb,
0xff, 0xff, 0x3f, 0xc3, 0x7f, 0x06, 0x96, 0xd8, 0xd8, 0x58, 0x46, 0x46,
0x86, 0x17, 0x0c, 0x0c, 0x00, 0x49, 0x22, 0x06, 0x44, 0xe4, 0x91, 0xb8,
0x83, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
0x82
};
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00,
0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x02, 0x08, 0x02, 0x00, 0x00, 0x00, 0x12, 0x16, 0xf1, 0x4d,
0x00, 0x00, 0x00, 0x1c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63,
0x10, 0x13, 0x13, 0xfb, 0xff, 0xff, 0x3f, 0xc3, 0x7f, 0x06, 0x96,
0xd8, 0xd8, 0x58, 0x46, 0x46, 0x86, 0x17, 0x0c, 0x0c, 0x00, 0x49,
0x22, 0x06, 0x44, 0xe4, 0x91, 0xb8, 0x83, 0x00, 0x00, 0x00, 0x00,
0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82};
TEST_F(MujocoTest, RecompileComparePngCache) {
static constexpr char xml[] = R"(
@@ -896,10 +887,10 @@ TEST_F(MujocoTest, RecompileComparePngCache) {
std::array<char, 1024> error;
// load model once
mjModel* m = LoadModelFromString(xml, error.data(), error.size(), vfs.get());
MjModelPtr m =
LoadModelFromString(xml, error.data(), error.size(), vfs.get());
EXPECT_EQ(m->ntexdata, 18); // w x h x rgb = 3 x 2 x 3
mjtByte byte = m->tex_data[0];
mj_deleteModel(m);
// update tex.png, load again
mj_deleteFileVFS(vfs.get(), "tex.png");
@@ -907,7 +898,6 @@ TEST_F(MujocoTest, RecompileComparePngCache) {
m = LoadModelFromString(xml, error.data(), error.size(), vfs.get());
EXPECT_NE(m->tex_data[0], byte);
EXPECT_EQ(m->tex_data[15], byte); // first pixel is now last pixel
mj_deleteModel(m);
mj_deleteVFS(vfs.get());
}
@@ -937,14 +927,13 @@ TEST_F(MujocoTest, DisableCache) {
std::array<char, 1024> error;
// load model once
mjModel* m = LoadModelFromString(xml, error.data(), error.size(), vfs.get());
MjModelPtr m =
LoadModelFromString(xml, error.data(), error.size(), vfs.get());
EXPECT_EQ(mj_getCacheSize(cache), 0);
EXPECT_EQ(m->ntexdata, 18); // w x h x rgb = 3 x 2 x 3
mj_setCacheCapacity(cache, capacity);
mj_deleteModel(m);
mj_deleteVFS(vfs.get());
}
@@ -1226,16 +1215,16 @@ TEST_F(MujocoTest, AttachSame) {
EXPECT_THAT(m_attached->body_parentid[4], 0);
// compare with expected XML
mjModel* m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';;
MjModelPtr m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
;
// destroy everything
mj_deleteSpec(parent);
mj_deleteModel(m_attached);
mj_deleteModel(m_expected);
}
TEST_F(MujocoTest, AttachSpatialTendonWithoutSidesite) {
@@ -1283,13 +1272,15 @@ TEST_F(MujocoTest, AttachSpatialTendonWithoutSidesite) {
mjsSite* attach_site = mjs_addSite(parent_body, 0);
mjs_setName(attach_site->element, "attach_site");
mjs_attach(attach_site->element,
mjs_findBody(child, "child_body")->element, "", "_child");
mjs_attach(attach_site->element, mjs_findBody(child, "child_body")->element,
"", "_child");
EXPECT_THAT(mjs_findElement(parent, mjOBJ_TENDON,
"tendon_with_sidesite_child"), NotNull());
EXPECT_THAT(mjs_findElement(parent, mjOBJ_TENDON,
"tendon_without_sidesite_child"), NotNull());
EXPECT_THAT(
mjs_findElement(parent, mjOBJ_TENDON, "tendon_with_sidesite_child"),
NotNull());
EXPECT_THAT(
mjs_findElement(parent, mjOBJ_TENDON, "tendon_without_sidesite_child"),
NotNull());
mjModel* model = mj_compile(parent, nullptr);
ASSERT_THAT(model, NotNull()) << mjs_getError(parent);
@@ -1335,11 +1326,9 @@ TEST_F(MujocoTest, AttachSpatialTendonGitHubIssue3119) {
</mujoco>)";
std::array<char, 1000> er;
mjSpec* parent_spec =
mj_parseXMLString(parent_xml, 0, er.data(), er.size());
mjSpec* parent_spec = mj_parseXMLString(parent_xml, 0, er.data(), er.size());
ASSERT_THAT(parent_spec, NotNull()) << er.data();
mjSpec* child_spec =
mj_parseXMLString(child_xml, 0, er.data(), er.size());
mjSpec* child_spec = mj_parseXMLString(child_xml, 0, er.data(), er.size());
ASSERT_THAT(child_spec, NotNull()) << er.data();
mjsBody* parent_body = mjs_findBody(parent_spec, "parent_body");
@@ -1348,13 +1337,14 @@ TEST_F(MujocoTest, AttachSpatialTendonGitHubIssue3119) {
mjs_setName(attach_site->element, "attach_site");
mjs_attach(attach_site->element,
mjs_findBody(child_spec, "child_body")->element,
"", "_child");
mjs_findBody(child_spec, "child_body")->element, "", "_child");
EXPECT_THAT(
mjs_findElement(parent_spec, mjOBJ_TENDON, "tendon_with_sidesite_child"),
NotNull());
EXPECT_THAT(mjs_findElement(parent_spec, mjOBJ_TENDON,
"tendon_with_sidesite_child"), NotNull());
EXPECT_THAT(mjs_findElement(parent_spec, mjOBJ_TENDON,
"tendon_without_sidesite_child"), NotNull());
"tendon_without_sidesite_child"),
NotNull());
mjModel* model = mj_compile(parent_spec, nullptr);
ASSERT_THAT(model, NotNull()) << mjs_getError(parent_spec);
@@ -1498,17 +1488,17 @@ TEST_F(MujocoTest, AttachDifferent) {
EXPECT_THAT(mjs_findDefault(parent, "attached-cylinder-1"), NotNull());
// compare with expected XML
mjModel* m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';;
MjModelPtr m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
;
// destroy everything
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(m_attached);
mj_deleteModel(m_expected);
}
TEST_F(MujocoTest, AttachFrame) {
@@ -1633,17 +1623,17 @@ TEST_F(MujocoTest, AttachFrame) {
EXPECT_THAT(m_attached->body_parentid[2], 1);
// compare with expected XML
mjModel* m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';;
MjModelPtr m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
;
// destroy everything
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(m_attached);
mj_deleteModel(m_expected);
}
TEST_F(MujocoTest, AttachCompiled) {
@@ -1691,8 +1681,8 @@ TEST_F(MujocoTest, AttachCompiled) {
// check that attached model can be compiled
mjModel* m_attached = mj_compile(parent, 0);
EXPECT_THAT(m_attached, NotNull()) <<
"Failed to compile attached model" << mjs_getError(parent);
EXPECT_THAT(m_attached, NotNull())
<< "Failed to compile attached model" << mjs_getError(parent);
// destroy everything
mj_deleteSpec(parent);
@@ -1758,24 +1748,23 @@ void TestDetachBody(bool compile) {
std::array<char, 1024> s;
EXPECT_EQ(mj_saveXMLString(child, s.data(), 1024, e.data(), 1024), -1);
EXPECT_THAT(e.data(), compile
? HasSubstr("Model has pending keyframes")
: HasSubstr("Only compiled model can be written"));
? HasSubstr("Model has pending keyframes")
: HasSubstr("Only compiled model can be written"));
// compile new model
mjModel* m_detached = mj_compile(child, 0);
EXPECT_THAT(m_detached, NotNull());
// compare with expected XML
mjModel* m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(m_detached, m_expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr m_expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(m_expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(m_detached, m_expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
// destroy everything
mj_deleteSpec(child);
mj_deleteModel(m_detached);
mj_deleteModel(m_expected);
if (m_child) mj_deleteModel(m_child);
}
@@ -1836,16 +1825,15 @@ TEST_F(MujocoTest, AttachToSite) {
mjModel* model = mj_compile(parent, 0);
EXPECT_THAT(model, NotNull());
mjModel* expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(model);
mj_deleteModel(expected);
}
TEST_F(MujocoTest, AttachFrameToSite) {
@@ -1902,16 +1890,15 @@ TEST_F(MujocoTest, AttachFrameToSite) {
mjModel* model = mj_compile(parent, 0);
EXPECT_THAT(model, NotNull());
mjModel* expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(model);
mj_deleteModel(expected);
}
TEST_F(MujocoTest, BodyToFrame) {
@@ -1991,18 +1978,17 @@ TEST_F(MujocoTest, BodyToFrame) {
// compile and compare
mjModel* model2 = mj_compile(parent, 0);
EXPECT_THAT(model2, NotNull());
mjModel* expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(model2, expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(model2, expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
mj_deleteSpec(parent);
mj_deleteSpec(child1);
mj_deleteSpec(child2);
mj_deleteModel(model1);
mj_deleteModel(model2);
mj_deleteModel(expected);
}
TEST_F(MujocoTest, BodyToFrameWithInertial) {
@@ -2100,11 +2086,11 @@ TEST_F(MujocoTest, AttachSpecToSite) {
// compile and compare
mjModel* model = mj_compile(parent, 0);
EXPECT_THAT(model, NotNull());
mjModel* expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
// check that the child world still exists
mjsBody* child_world = mjs_findBody(child, "world");
@@ -2113,7 +2099,6 @@ TEST_F(MujocoTest, AttachSpecToSite) {
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(model);
mj_deleteModel(expected);
}
TEST_F(MujocoTest, AttachSpecToBody) {
@@ -2181,11 +2166,11 @@ TEST_F(MujocoTest, AttachSpecToBody) {
// compile and compare
mjModel* model = mj_compile(parent, 0);
EXPECT_THAT(model, NotNull());
mjModel* expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr expected = LoadModelFromString(xml_result, er.data(), er.size());
EXPECT_THAT(expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
// check that the child world still exists
mjsBody* child_world = mjs_findBody(child, "world");
@@ -2194,7 +2179,6 @@ TEST_F(MujocoTest, AttachSpecToBody) {
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(model);
mj_deleteModel(expected);
}
TEST_F(MujocoTest, PreserveState) {
@@ -2246,31 +2230,32 @@ TEST_F(MujocoTest, PreserveState) {
// compile models
mjModel* model = mj_compile(spec, 0);
EXPECT_THAT(model, NotNull());
mjModel* m_expected = LoadModelFromString(xml_expected, er.data(), er.size());
EXPECT_THAT(m_expected, NotNull());
MjModelPtr m_expected =
LoadModelFromString(xml_expected, er.data(), er.size());
EXPECT_THAT(m_expected.get(), NotNull());
// create data
mjData* data = mj_makeData(model);
EXPECT_THAT(data, NotNull());
mjData* d_expected = mj_makeData(m_expected);
EXPECT_THAT(d_expected, NotNull());
MjDataPtr d_expected = MakeData(m_expected);
EXPECT_THAT(d_expected.get(), NotNull());
// set ctrl
data->ctrl[0] = 1;
data->ctrl[1] = 2;
d_expected->ctrl[0] = 2;
d_expected.get()->ctrl[0] = 2;
// set mocap
data->mocap_pos[3] = 1;
data->mocap_quat[4] = 0;
data->mocap_quat[5] = 1;
d_expected->mocap_pos[0] = 1;
d_expected->mocap_quat[0] = 0;
d_expected->mocap_quat[1] = 1;
d_expected.get()->mocap_pos[0] = 1;
d_expected.get()->mocap_quat[0] = 0;
d_expected.get()->mocap_quat[1] = 1;
// step models
mj_step(model, data);
mj_step(m_expected, d_expected);
mj_step(m_expected.get(), d_expected.get());
EXPECT_THAT(data->time, model->opt.timestep);
// detach subtree
@@ -2298,7 +2283,7 @@ TEST_F(MujocoTest, PreserveState) {
joint->axis[0] = 0;
joint->axis[1] = 0;
joint->axis[2] = 1;
joint->ref = d_expected->qpos[m_expected->nq-1];
joint->ref = d_expected.get()->qpos[m_expected->nq - 1];
// compile new model
mj_recompile(spec, 0, model, data);
@@ -2308,32 +2293,36 @@ TEST_F(MujocoTest, PreserveState) {
// compare qpos
EXPECT_EQ(model->nq, m_expected->nq);
for (int i = 0; i < model->nq; ++i) {
EXPECT_EQ(data->qpos[i], d_expected->qpos[i]) << i;
EXPECT_EQ(data->qpos[i], d_expected.get()->qpos[i]) << i;
}
// compare qvel
EXPECT_EQ(model->nv, m_expected->nv);
for (int i = 0; i < model->nv-1; ++i) {
EXPECT_EQ(data->qvel[i], d_expected->qvel[i]) << i;
for (int i = 0; i < model->nv - 1; ++i) {
EXPECT_EQ(data->qvel[i], d_expected.get()->qvel[i]) << i;
}
// second body was added after stepping so qvel should be zero
EXPECT_EQ(data->qvel[model->nv-1], 0);
EXPECT_EQ(data->qvel[model->nv - 1], 0);
// compare act
EXPECT_EQ(model->na, m_expected->na);
for (int i = 0; i < model->na; ++i) {
EXPECT_EQ(data->act[i], d_expected->act[i]) << i;
EXPECT_EQ(data->act[i], d_expected.get()->act[i]) << i;
}
// compare mocap
EXPECT_EQ(model->nmocap, m_expected->nmocap);
for (int i = 0; i < model->nmocap; ++i) {
for (int j = 0; j < 3; ++j) {
EXPECT_EQ(data->mocap_pos[3*i+j], d_expected->mocap_pos[3*i+j]) << i;
EXPECT_EQ(data->mocap_pos[3 * i + j],
d_expected.get()->mocap_pos[3 * i + j])
<< i;
}
for (int j = 0; j < 4; ++j) {
EXPECT_EQ(data->mocap_quat[4*i+j], d_expected->mocap_quat[4*i+j]) << i;
EXPECT_EQ(data->mocap_quat[4 * i + j],
d_expected.get()->mocap_quat[4 * i + j])
<< i;
}
}
@@ -2342,10 +2331,8 @@ TEST_F(MujocoTest, PreserveState) {
mj_recompile(spec, 0, model, nullptr);
// destroy everything
mj_deleteData(d_expected);
mj_deleteSpec(spec);
mj_deleteModel(model);
mj_deleteModel(m_expected);
}
TEST_F(MujocoTest, RecompileAttach) {
@@ -2445,15 +2432,15 @@ TEST_F(MujocoTest, AttachMocap) {
mjModel* model = mj_compile(spec, 0);
EXPECT_THAT(model, NotNull());
mjModel* m_expected = LoadModelFromString(xml_expected, er.data(), er.size());
EXPECT_THAT(m_expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(model, m_expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr m_expected =
LoadModelFromString(xml_expected, er.data(), er.size());
EXPECT_THAT(m_expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(model, m_expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
mj_deleteSpec(spec);
mj_deleteModel(model);
mj_deleteModel(m_expected);
}
TEST_F(MujocoTest, ReplicateKeyframe) {
@@ -2475,8 +2462,8 @@ TEST_F(MujocoTest, ReplicateKeyframe) {
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, testing::NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), testing::NotNull()) << error.data();
EXPECT_THAT(m->ngeom, 1);
EXPECT_THAT(m->nbody, 2);
@@ -2485,10 +2472,8 @@ TEST_F(MujocoTest, ReplicateKeyframe) {
EXPECT_THAT(m->nq, 1);
EXPECT_THAT(m->key_qpos[0], 1);
EXPECT_THAT(m->key_qpos[1], 0);
EXPECT_STREQ(mj_id2name(m, mjOBJ_KEY, 0), "keyframe0");
EXPECT_STREQ(mj_id2name(m, mjOBJ_KEY, 1), "keyframe");
mj_deleteModel(m);
EXPECT_STREQ(mj_id2name(m.get(), mjOBJ_KEY, 0), "keyframe0");
EXPECT_STREQ(mj_id2name(m.get(), mjOBJ_KEY, 1), "keyframe");
}
TEST_F(MujocoTest, AttachUnnamedAssets) {
@@ -2690,17 +2675,17 @@ void AttachNestedKeyframe(bool compile) {
std::string field = "";
mjModel* m_attached = mj_compile(parent, 0);
EXPECT_THAT(m_attached, NotNull());
mjModel* m_expected = LoadModelFromString(
MjModelPtr m_expected = LoadModelFromString(
compile ? expected_xml : expected_xml_uncompiled, er.data(), er.size());
EXPECT_THAT(m_expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';;
EXPECT_THAT(m_expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(m_attached, m_expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
;
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteSpec(gchild);
mj_deleteModel(m_expected);
mj_deleteModel(m_attached);
mj_deleteModel(m_child);
}
@@ -2736,30 +2721,30 @@ TEST_F(MujocoTest, RepeatedAttachKeyframe) {
</keyframe>
</mujoco>)";
std::array<char, 1000> er;
mjSpec* parent = mj_parseXMLString(xml_1, 0, er.data(), er.size());
EXPECT_THAT(parent, NotNull()) << er.data();
mjSpec* child = mj_parseXMLString(xml_2, 0, er.data(), er.size());
EXPECT_THAT(child, NotNull()) << er.data();
std::array<char, 1000> er;
mjSpec* parent = mj_parseXMLString(xml_1, 0, er.data(), er.size());
EXPECT_THAT(parent, NotNull()) << er.data();
mjSpec* child = mj_parseXMLString(xml_2, 0, er.data(), er.size());
EXPECT_THAT(child, NotNull()) << er.data();
mjsBody* body_1 = mjs_findBody(parent, "body");
mjsElement* attachment_frame = mjs_addFrame(body_1, 0)->element;
mjs_attach(attachment_frame, mjs_findBody(child, "b1")->element, "b1-", "");
mjModel* model_1 = mj_compile(parent, 0);
EXPECT_THAT(model_1, NotNull());
mjs_attach(attachment_frame, mjs_findBody(child, "b2")->element, "b2-", "");
mjModel* model_2 = mj_compile(parent, 0);
EXPECT_THAT(model_2, NotNull());
mjsBody* body_1 = mjs_findBody(parent, "body");
mjsElement* attachment_frame = mjs_addFrame(body_1, 0)->element;
mjs_attach(attachment_frame, mjs_findBody(child, "b1")->element, "b1-", "");
mjModel* model_1 = mj_compile(parent, 0);
EXPECT_THAT(model_1, NotNull());
mjs_attach(attachment_frame, mjs_findBody(child, "b2")->element, "b2-", "");
mjModel* model_2 = mj_compile(parent, 0);
EXPECT_THAT(model_2, NotNull());
EXPECT_EQ(model_1->nkey, 1);
EXPECT_EQ(model_2->nkey, 2);
EXPECT_STREQ(mj_id2name(model_2, mjOBJ_KEY, 0), "b1-home");
EXPECT_STREQ(mj_id2name(model_2, mjOBJ_KEY, 1), "b2-home");
EXPECT_EQ(model_1->nkey, 1);
EXPECT_EQ(model_2->nkey, 2);
EXPECT_STREQ(mj_id2name(model_2, mjOBJ_KEY, 0), "b1-home");
EXPECT_STREQ(mj_id2name(model_2, mjOBJ_KEY, 1), "b2-home");
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(model_1);
mj_deleteModel(model_2);
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(model_1);
mj_deleteModel(model_2);
}
TEST_F(MujocoTest, ResizeParentKeyframe) {
@@ -2820,16 +2805,15 @@ TEST_F(MujocoTest, ResizeParentKeyframe) {
mjtNum tol = 0;
std::string field = "";
mjModel* expected = LoadModelFromString(xml_expected, er.data(), er.size());
EXPECT_THAT(expected, NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected, field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
MjModelPtr expected = LoadModelFromString(xml_expected, er.data(), er.size());
EXPECT_THAT(expected.get(), NotNull()) << er.data();
EXPECT_LE(CompareModel(model, expected.get(), field), tol)
<< "Expected and attached models are different!\n"
<< "Different field: " << field << '\n';
mj_deleteSpec(parent);
mj_deleteSpec(child);
mj_deleteModel(model);
mj_deleteModel(expected);
}
TEST_F(MujocoTest, KeyframeSizeError) {
@@ -2854,8 +2838,10 @@ TEST_F(MujocoTest, KeyframeSizeError) {
std::array<char, 1000> er;
mjSpec* spec = mj_parseXMLString(xml, 0, er.data(), er.size());
EXPECT_THAT(spec, IsNull());
EXPECT_THAT(er.data(), HasSubstr(
"Keyframe 'invalid_qpos' has invalid qpos size, got 2, should be 1"));
EXPECT_THAT(
er.data(),
HasSubstr(
"Keyframe 'invalid_qpos' has invalid qpos size, got 2, should be 1"));
}
TEST_F(MujocoTest, DifferentUnitsAllowed) {
@@ -2899,8 +2885,7 @@ TEST_F(MujocoTest, DifferentUnitsAllowed) {
)";
std::array<char, 1024> error;
mjSpec* gchild =
mj_parseXMLString(gchild_xml, 0, error.data(), error.size());
mjSpec* gchild = mj_parseXMLString(gchild_xml, 0, error.data(), error.size());
mjSpec* child = mj_parseXMLString(child_xml, 0, error.data(), error.size());
mjSpec* spec = mj_parseXMLString(parent_xml, 0, error.data(), error.size());
ASSERT_THAT(spec, NotNull()) << error.data();
+4 -5
View File
@@ -48,9 +48,8 @@ TEST_F(UserCompositeTest, ShapeCanBeOmitted) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, NotNull()) << error.data();
mj_deleteModel(m);
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), NotNull()) << error.data();
}
TEST_F(UserCompositeTest, InvalidShape) {
@@ -64,8 +63,8 @@ TEST_F(UserCompositeTest, InvalidShape) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("The curve array contains an invalid shape"));
}
+200 -246
View File
@@ -24,19 +24,15 @@
#include <mujoco/mujoco.h>
#include "test/fixture.h"
namespace mujoco {
namespace {
using ::testing::HasSubstr;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::HasSubstr;
using ::testing::Pointwise;
using UserFlexTest = MujocoTest;
TEST_F(UserFlexTest, ParentMustHaveName) {
static constexpr char xml[] = R"(
<mujoco>
@@ -46,8 +42,8 @@ TEST_F(UserFlexTest, ParentMustHaveName) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull());
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("required attribute missing: 'name'"));
}
@@ -60,8 +56,8 @@ TEST_F(UserFlexTest, InvalidDim) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull());
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("Invalid dim, must be between 1 and 3"));
}
@@ -74,8 +70,8 @@ TEST_F(UserFlexTest, CountTooSmall) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull());
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("Count too small"));
}
@@ -95,8 +91,8 @@ TEST_F(UserFlexTest, CellnumZeroInterpolated) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull());
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("cellcount cannot be 0"));
}
@@ -109,8 +105,8 @@ TEST_F(UserFlexTest, SpacingGreaterThanGeometry) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("Spacing must be larger than geometry size"));
}
@@ -124,8 +120,8 @@ TEST_F(UserFlexTest, ScaleMinValue) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(), HasSubstr("Scale must be larger than mjMINVAL"));
}
@@ -138,8 +134,8 @@ TEST_F(UserFlexTest, MassMinValue) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("Mass and inertiabox must be larger than mjMINVAL"));
}
@@ -153,8 +149,8 @@ TEST_F(UserFlexTest, PointSizeNotMultipleOf3) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(), HasSubstr("Point size must be a multiple of 3"));
}
@@ -167,8 +163,8 @@ TEST_F(UserFlexTest, ElementSize) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("Element size must be a multiple of dim+1"));
}
@@ -182,8 +178,8 @@ TEST_F(UserFlexTest, PointAndElementNotInDirect) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(), HasSubstr("Point and element required"));
}
@@ -196,8 +192,8 @@ TEST_F(UserFlexTest, UnknownFlexCompType) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(), HasSubstr("invalid keyword: 'unknown'"));
}
@@ -212,8 +208,8 @@ TEST_F(UserFlexTest, InvalidPinid) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("element 1 has point id 1, number of points is 1"));
}
@@ -227,8 +223,8 @@ TEST_F(UserFlexTest, MeshFileMissing) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(), HasSubstr("File is required"));
}
@@ -249,8 +245,8 @@ TEST_F(UserFlexTest, CreateBVHSuccess) {
ASSERT_THAT(m, NotNull()) << error.data();
mjData* d = mj_makeData(m);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, RigidFlex) {
@@ -261,8 +257,8 @@ TEST_F(UserFlexTest, RigidFlex) {
ASSERT_THAT(m, NotNull()) << error.data();
mjData* d = mj_makeData(m);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, FlexNotCollide) {
@@ -277,17 +273,16 @@ TEST_F(UserFlexTest, FlexNotCollide) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
mjData* d = mj_makeData(m);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
MjDataPtr d = MakeData(m);
mj_step(m.get(), d.get());
}
TEST_F(UserFlexTest, BoundingBoxCoordinates) {
#ifdef mjUSESINGLE
GTEST_SKIP() << "Float32 rounding in bounding box centering gives ~3e-8 error";
GTEST_SKIP()
<< "Float32 rounding in bounding box centering gives ~3e-8 error";
#endif
static constexpr char xml[] = R"(
<mujoco>
@@ -298,34 +293,31 @@ TEST_F(UserFlexTest, BoundingBoxCoordinates) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
mjData* d = mj_makeData(m);
mj_kinematics(m, d);
mj_flex(m, d);
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
MjDataPtr d = MakeData(m);
mj_kinematics(m.get(), d.get());
mj_flex(m.get(), d.get());
EXPECT_EQ(m->nflexvert, 5*5*5);
EXPECT_EQ(m->nflexelem, 4*4*4*6);
EXPECT_EQ(m->nflexvert, 5 * 5 * 5);
EXPECT_EQ(m->nflexelem, 4 * 4 * 4 * 6);
EXPECT_EQ(m->flex_dim[0], 3);
// Cartesian coordinates
EXPECT_EQ(d->flexvert_xpos[0], -1);
EXPECT_EQ(d->flexvert_xpos[1], -2);
EXPECT_EQ(d->flexvert_xpos[2], -3);
EXPECT_EQ(d->flexvert_xpos[3*m->nflexvert-3], 3);
EXPECT_EQ(d->flexvert_xpos[3*m->nflexvert-2], 2);
EXPECT_EQ(d->flexvert_xpos[3*m->nflexvert-1], 1);
EXPECT_EQ(d->flexvert_xpos[3 * m->nflexvert - 3], 3);
EXPECT_EQ(d->flexvert_xpos[3 * m->nflexvert - 2], 2);
EXPECT_EQ(d->flexvert_xpos[3 * m->nflexvert - 1], 1);
// bounding box coordinates
EXPECT_EQ(m->flex_vert0[0], 0);
EXPECT_EQ(m->flex_vert0[1], 0);
EXPECT_EQ(m->flex_vert0[2], 0);
EXPECT_EQ(m->flex_vert0[3*m->nflexvert-3], 1);
EXPECT_EQ(m->flex_vert0[3*m->nflexvert-2], 1);
EXPECT_EQ(m->flex_vert0[3*m->nflexvert-1], 1);
mj_deleteModel(m);
mj_deleteData(d);
EXPECT_EQ(m->flex_vert0[3 * m->nflexvert - 3], 1);
EXPECT_EQ(m->flex_vert0[3 * m->nflexvert - 2], 1);
EXPECT_EQ(m->flex_vert0[3 * m->nflexvert - 1], 1);
}
TEST_F(UserFlexTest, TrilinearCannotDoSelfCollision) {
@@ -339,8 +331,8 @@ TEST_F(UserFlexTest, TrilinearCannotDoSelfCollision) {
</worldbody>
</mujoco>
)";
mjModel* m1 = LoadModelFromString(xml_selfcoll, error.data(), error.size());
EXPECT_THAT(m1, IsNull()) << error.data();
MjModelPtr m1 = LoadModelFromString(xml_selfcoll, error.data(), error.size());
EXPECT_THAT(m1.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("trilinear interpolation cannot do self-collision"));
static constexpr char xml_internal[] = R"(
@@ -352,8 +344,8 @@ TEST_F(UserFlexTest, TrilinearCannotDoSelfCollision) {
</worldbody>
</mujoco>
)";
mjModel* m2 = LoadModelFromString(xml_internal, error.data(), error.size());
EXPECT_THAT(m2, IsNull()) << error.data();
MjModelPtr m2 = LoadModelFromString(xml_internal, error.data(), error.size());
EXPECT_THAT(m2.get(), IsNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("trilinear interpolation cannot do internal"));
}
@@ -370,10 +362,11 @@ TEST_F(UserFlexTest, TrilinearInterpolation) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m1 = LoadModelFromString(xml_trilinear, error.data(), error.size());
ASSERT_THAT(m1, NotNull()) << error.data();
mjData* d1 = mj_makeData(m1);
mj_step(m1, d1);
MjModelPtr m1 =
LoadModelFromString(xml_trilinear, error.data(), error.size());
ASSERT_THAT(m1.get(), NotNull()) << error.data();
MjDataPtr d1 = MakeData(m1);
mj_step(m1.get(), d1.get());
static constexpr char xml_linear[] = R"(
<mujoco>
@@ -385,13 +378,13 @@ TEST_F(UserFlexTest, TrilinearInterpolation) {
</worldbody>
</mujoco>
)";
mjModel* m2 = LoadModelFromString(xml_linear, error.data(), error.size());
ASSERT_THAT(m2, NotNull()) << error.data();
mjData* d2 = mj_makeData(m2);
mj_step(m2, d2);
MjModelPtr m2 = LoadModelFromString(xml_linear, error.data(), error.size());
ASSERT_THAT(m2.get(), NotNull()) << error.data();
MjDataPtr d2 = MakeData(m2);
mj_step(m2.get(), d2.get());
EXPECT_EQ(m1->nflexvert, m2->nflexvert);
for (int i = 0; i < 3*m1->nflexvert; ++i) {
for (int i = 0; i < 3 * m1->nflexvert; ++i) {
EXPECT_NEAR(m1->flex_vert[i], d2->flexvert_xpos[i], 1e-7);
EXPECT_NEAR(m1->flex_vert0[i], m2->flex_vert0[i], 1e-7);
EXPECT_NEAR(d1->flexvert_xpos[i], d2->flexvert_xpos[i], 1e-7);
@@ -409,11 +402,11 @@ TEST_F(UserFlexTest, TrilinearInterpolation) {
}
EXPECT_EQ(m1->body_mass[i], m2->body_mass[i]);
for (int j = 0; j < 2; ++j) {
EXPECT_EQ(m1->body_invweight0[i*2+j], m2->body_invweight0[i*2+j]);
EXPECT_EQ(m1->body_invweight0[i * 2 + j], m2->body_invweight0[i * 2 + j]);
}
for (int j = 0; j < 10; ++j) {
EXPECT_NEAR(d1->cinert[10*i+j], d2->cinert[i*10+j], 1e-5) << i;
EXPECT_NEAR(d1->crb[10*i+j], d2->crb[i*10+j], 1e-5) << i;
EXPECT_NEAR(d1->cinert[10 * i + j], d2->cinert[i * 10 + j], 1e-5) << i;
EXPECT_NEAR(d1->crb[10 * i + j], d2->crb[i * 10 + j], 1e-5) << i;
}
}
@@ -436,18 +429,13 @@ TEST_F(UserFlexTest, TrilinearInterpolation) {
}
}
EXPECT_EQ(d1->nefc, 4*(d1->contact[0].dim-1)*2);
EXPECT_EQ(d2->nefc, 4*(d2->contact[0].dim-1)*2);
EXPECT_EQ(d1->nefc, 4 * (d1->contact[0].dim - 1) * 2);
EXPECT_EQ(d2->nefc, 4 * (d2->contact[0].dim - 1) * 2);
EXPECT_EQ(d1->nJ, d2->nJ);
for (int i = 0; i < d1->nefc; ++i) {
EXPECT_EQ(d1->efc_diagA[i], d2->efc_diagA[i]);
EXPECT_EQ(d1->efc_D[i], d2->efc_D[i]);
}
mj_deleteModel(m1);
mj_deleteModel(m2);
mj_deleteData(d1);
mj_deleteData(d2);
}
TEST_F(UserFlexTest, StiffnessMatrix) {
@@ -466,22 +454,20 @@ TEST_F(UserFlexTest, StiffnessMatrix) {
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_NE(m->flex_stiffness[m->flex_stiffnessadr[0]], 0);
EXPECT_EQ(m->nflexnode, 8);
// constants are in the kernel
mjtNum ones[24], zeros[24], res[24];
for (int i = 0; i < 3*m->nflexnode; ++i) {
for (int i = 0; i < 3 * m->nflexnode; ++i) {
zeros[i] = 0;
ones[i] = 1;
}
mju_mulMatVec(res, m->flex_stiffness + m->flex_stiffnessadr[0], ones,
3 * m->nflexnode, 3 * m->nflexnode);
EXPECT_THAT(res, Pointwise(MjNear(1e-8, 1e-4), zeros));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, StiffnessCacheDiffersByGeometry) {
@@ -510,19 +496,18 @@ TEST_F(UserFlexTest, StiffnessCacheDiffersByGeometry) {
</mujoco>
)";
mjModel* m_small = LoadModelFromString(xml_small, error.data(), error.size());
ASSERT_THAT(m_small, NotNull()) << error.data();
MjModelPtr m_small =
LoadModelFromString(xml_small, error.data(), error.size());
ASSERT_THAT(m_small.get(), NotNull()) << error.data();
mjModel* m_large = LoadModelFromString(xml_large, error.data(), error.size());
ASSERT_THAT(m_large, NotNull()) << error.data();
MjModelPtr m_large =
LoadModelFromString(xml_large, error.data(), error.size());
ASSERT_THAT(m_large.get(), NotNull()) << error.data();
// Same number of nodes but different stiffness due to different geometry
EXPECT_EQ(m_small->nflexnode, m_large->nflexnode);
EXPECT_NE(m_small->flex_stiffness[m_small->flex_stiffnessadr[0]],
m_large->flex_stiffness[m_large->flex_stiffnessadr[0]]);
mj_deleteModel(m_small);
mj_deleteModel(m_large);
}
TEST_F(UserFlexTest, LoadTexture) {
@@ -552,8 +537,8 @@ TEST_F(UserFlexTest, LoadMSHBinary_41_Success) {
EXPECT_EQ(m->nflexelem, 24);
EXPECT_EQ(m->flex_dim[0], 3);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHBinary_22_Success) {
@@ -567,8 +552,8 @@ TEST_F(UserFlexTest, LoadMSHBinary_22_Success) {
EXPECT_EQ(m->nflexelem, 24);
EXPECT_EQ(m->flex_dim[0], 3);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHSurfaceBinary_41_Success) {
@@ -585,18 +570,18 @@ TEST_F(UserFlexTest, LoadMSHSurfaceBinary_41_Success) {
EXPECT_EQ(m->flex_dim[0], 2);
// first node x y z
EXPECT_EQ(d->flexvert_xpos[0], -0.5 );
EXPECT_EQ(d->flexvert_xpos[1], -0.5 );
EXPECT_EQ(d->flexvert_xpos[2], 0 );
EXPECT_EQ(d->flexvert_xpos[0], -0.5);
EXPECT_EQ(d->flexvert_xpos[1], -0.5);
EXPECT_EQ(d->flexvert_xpos[2], 0);
// first element
EXPECT_EQ(m->flex_elem[0], 9-1 );
EXPECT_EQ(m->flex_elem[1], 4-1 );
EXPECT_EQ(m->flex_elem[2], 3-1 );
EXPECT_EQ(m->flex_elem[0], 9 - 1);
EXPECT_EQ(m->flex_elem[1], 4 - 1);
EXPECT_EQ(m->flex_elem[2], 3 - 1);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHSurfaceBinary_22_Success) {
@@ -613,18 +598,18 @@ TEST_F(UserFlexTest, LoadMSHSurfaceBinary_22_Success) {
EXPECT_EQ(m->flex_dim[0], 2);
// first node x y z
EXPECT_EQ(d->flexvert_xpos[0], -0.5 );
EXPECT_EQ(d->flexvert_xpos[1], -0.5 );
EXPECT_EQ(d->flexvert_xpos[2], 0 );
EXPECT_EQ(d->flexvert_xpos[0], -0.5);
EXPECT_EQ(d->flexvert_xpos[1], -0.5);
EXPECT_EQ(d->flexvert_xpos[2], 0);
// first element
EXPECT_EQ(m->flex_elem[0], 9-1 );
EXPECT_EQ(m->flex_elem[1], 4-1 );
EXPECT_EQ(m->flex_elem[2], 3-1 );
EXPECT_EQ(m->flex_elem[0], 9 - 1);
EXPECT_EQ(m->flex_elem[1], 4 - 1);
EXPECT_EQ(m->flex_elem[2], 3 - 1);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHBinaryFTETWILD_22_Success) {
@@ -638,8 +623,8 @@ TEST_F(UserFlexTest, LoadMSHBinaryFTETWILD_22_Success) {
EXPECT_EQ(m->nflexelem, 1073);
EXPECT_EQ(m->flex_dim[0], 3);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_41_Success) {
@@ -653,8 +638,8 @@ TEST_F(UserFlexTest, LoadMSHASCII_41_Success) {
EXPECT_EQ(m->nflexelem, 24);
EXPECT_EQ(m->flex_dim[0], 3);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_22_Success) {
@@ -668,8 +653,8 @@ TEST_F(UserFlexTest, LoadMSHASCII_22_Success) {
EXPECT_EQ(m->nflexelem, 24);
EXPECT_EQ(m->flex_dim[0], 3);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHSurfaceASCII_41_Success) {
@@ -686,18 +671,18 @@ TEST_F(UserFlexTest, LoadMSHSurfaceASCII_41_Success) {
EXPECT_EQ(m->flex_dim[0], 2);
// first node x y z
EXPECT_EQ(d->flexvert_xpos[0], -0.5 );
EXPECT_EQ(d->flexvert_xpos[1], -0.5 );
EXPECT_EQ(d->flexvert_xpos[2], 0 );
EXPECT_EQ(d->flexvert_xpos[0], -0.5);
EXPECT_EQ(d->flexvert_xpos[1], -0.5);
EXPECT_EQ(d->flexvert_xpos[2], 0);
// first element
EXPECT_EQ(m->flex_elem[0], 9-1 );
EXPECT_EQ(m->flex_elem[1], 4-1 );
EXPECT_EQ(m->flex_elem[2], 3-1 );
EXPECT_EQ(m->flex_elem[0], 9 - 1);
EXPECT_EQ(m->flex_elem[1], 4 - 1);
EXPECT_EQ(m->flex_elem[2], 3 - 1);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHSurfaceASCII_22_Success) {
@@ -714,18 +699,18 @@ TEST_F(UserFlexTest, LoadMSHSurfaceASCII_22_Success) {
EXPECT_EQ(m->flex_dim[0], 2);
// first node x y z
EXPECT_EQ(d->flexvert_xpos[0], -0.5 );
EXPECT_EQ(d->flexvert_xpos[1], -0.5 );
EXPECT_EQ(d->flexvert_xpos[2], 0 );
EXPECT_EQ(d->flexvert_xpos[0], -0.5);
EXPECT_EQ(d->flexvert_xpos[1], -0.5);
EXPECT_EQ(d->flexvert_xpos[2], 0);
// first element
EXPECT_EQ(m->flex_elem[0], 9-1 );
EXPECT_EQ(m->flex_elem[1], 4-1 );
EXPECT_EQ(m->flex_elem[2], 3-1 );
EXPECT_EQ(m->flex_elem[0], 9 - 1);
EXPECT_EQ(m->flex_elem[1], 4 - 1);
EXPECT_EQ(m->flex_elem[2], 3 - 1);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCIIFTETWILD_22_Success) {
@@ -739,70 +724,69 @@ TEST_F(UserFlexTest, LoadMSHASCIIFTETWILD_22_Success) {
EXPECT_EQ(m->nflexelem, 1070);
EXPECT_EQ(m->flex_dim[0], 3);
mj_step(m, d);
mj_deleteModel(m);
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_41_MissingNodeHeader_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_node_header.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_node_header.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(error.data(), HasSubstr(
"XML Error: Error: All nodes must be in single block"));
EXPECT_THAT(error.data(),
HasSubstr("XML Error: Error: All nodes must be in single block"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_41_MissingNodeIndex_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_node_index.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_node_index.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(error.data(), HasSubstr(
"XML Error: Error: Node tags must be sequential"));
EXPECT_THAT(error.data(),
HasSubstr("XML Error: Error: Node tags must be sequential"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_41_MissingElementHeader_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_element_header.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_element_header.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(error.data(), HasSubstr(
"XML Error: Error: All elements must be in single block"));
EXPECT_THAT(
error.data(),
HasSubstr("XML Error: Error: All elements must be in single block"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_41_MissingElement_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_element.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_missing_element.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(error.data(), HasSubstr(
"XML Error: Error: Error reading Elements"));
EXPECT_THAT(error.data(),
HasSubstr("XML Error: Error: Error reading Elements"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest,
LoadMSHASCII_41_MismatchBetweenMaxNodesAndNodesInBlock_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_41_ascii_mismatch_between_max_nodes_and_nodes_in_block.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/"
"malformed_cube_41_ascii_mismatch_between_max_nodes_and_nodes_in_block."
"xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(error.data(), HasSubstr(
"XML Error: Error: Maximum number of nodes must be equal to number of nodes in a block\nElement 'flexcomp', line 22\n"));
EXPECT_THAT(
error.data(),
HasSubstr("XML Error: Error: Maximum number of nodes must be equal to "
"number of nodes in a block\nElement 'flexcomp', line 22\n"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_22_MissingNumNodes_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_22_ascii_missing_num_nodes.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_cube_22_ascii_missing_num_nodes.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
// TODO(mohammadhamid): Replace with an assertion about the error message. For
@@ -812,20 +796,18 @@ TEST_F(UserFlexTest, LoadMSHASCII_22_MissingNumNodes_Fail) {
}
TEST_F(UserFlexTest, LoadMSHASCII_22_MissingNode_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_22_ascii_missing_node.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_cube_22_ascii_missing_node.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(error.data(), HasSubstr(
"XML Error: Error: Error reading node tags"));
EXPECT_THAT(error.data(),
HasSubstr("XML Error: Error: Error reading node tags"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_22_MissingNumElements_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_shark_22_ascii_missing_num_elements.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_shark_22_ascii_missing_num_elements.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
// TODO(mohammadhamid): Replace with an assertion about the error message. For
@@ -835,20 +817,18 @@ TEST_F(UserFlexTest, LoadMSHASCII_22_MissingNumElements_Fail) {
}
TEST_F(UserFlexTest, LoadMSHASCII_22_MissingElement_Fail) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/malformed_cube_22_ascii_missing_element.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/malformed_cube_22_ascii_missing_element.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(error.data(), HasSubstr(
"XML Error: Error: Error reading Elements"));
EXPECT_THAT(error.data(),
HasSubstr("XML Error: Error: Error reading Elements"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, LoadMSHASCII_dim_missing_in_xml) {
const std::string xml_path =
GetTestDataFilePath(
"user/testdata/cube_22_ascii_vol_gmshApp_missing_dim.xml");
const std::string xml_path = GetTestDataFilePath(
"user/testdata/cube_22_ascii_vol_gmshApp_missing_dim.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_EQ(m->flex_dim[0], 3);
@@ -871,9 +851,8 @@ TEST_F(UserFlexTest, TrilinearUnusedVertices_Crash) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, testing::NotNull()) << error.data();
mj_deleteModel(m);
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), testing::NotNull()) << error.data();
}
TEST_F(UserFlexTest, MeshNodePinning) {
@@ -891,8 +870,8 @@ TEST_F(UserFlexTest, MeshNodePinning) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, testing::NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), testing::NotNull()) << error.data();
// Verify that node 0 (corner) is pinned
// Trilinear 3D has 8 nodes.
@@ -901,14 +880,11 @@ TEST_F(UserFlexTest, MeshNodePinning) {
// m->nbody should reflect this. Flex bodies are added to the model.
// Model has 1 world body + flex bodies.
EXPECT_EQ(m->nbody, 1 + 7); // 1 world + 7 flex nodes (1 pinned)
mj_deleteModel(m);
}
TEST_F(UserFlexTest, FlexcompMeshLoadsFromVFS) {
// read cube.stl from testdata into a buffer
const std::string stl_path =
GetTestDataFilePath("user/testdata/cube.stl");
const std::string stl_path = GetTestDataFilePath("user/testdata/cube.stl");
FILE* f = fopen(stl_path.c_str(), "rb");
ASSERT_THAT(f, NotNull()) << "Could not open " << stl_path;
fseek(f, 0, SEEK_END);
@@ -938,12 +914,10 @@ TEST_F(UserFlexTest, FlexcompMeshLoadsFromVFS) {
// cleanup
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size(), &vfs);
ASSERT_THAT(m, NotNull()) << error.data();
mjData* d = mj_makeData(m);
mj_step(m, d);
mj_deleteData(d);
mj_deleteModel(m);
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size(), &vfs);
ASSERT_THAT(m.get(), NotNull()) << error.data();
MjDataPtr d = MakeData(m);
mj_step(m.get(), d.get());
mj_deleteVFS(&vfs);
}
@@ -986,22 +960,20 @@ TEST_F(UserFlexTest, FlexAttachConstraintPreserved) {
// First verify the standalone flex model has constraints
std::array<char, 1024> error;
mjModel* m_standalone =
MjModelPtr m_standalone =
LoadModelFromString(flex_xml, error.data(), error.size(), vfs.get());
ASSERT_THAT(m_standalone, NotNull()) << error.data();
mjData* d_standalone = mj_makeData(m_standalone);
mj_forward(m_standalone, d_standalone);
ASSERT_THAT(m_standalone.get(), NotNull()) << error.data();
MjDataPtr d_standalone = MakeData(m_standalone);
mj_forward(m_standalone.get(), d_standalone.get());
int standalone_neq = m_standalone->neq;
EXPECT_GT(standalone_neq, 0) << "Standalone flex should have constraints";
mj_deleteData(d_standalone);
mj_deleteModel(m_standalone);
// Now load the parent model which attaches the flex
mjModel* m_attached =
MjModelPtr m_attached =
LoadModelFromString(parent_xml, error.data(), error.size(), vfs.get());
ASSERT_THAT(m_attached, NotNull()) << error.data();
mjData* d_attached = mj_makeData(m_attached);
mj_forward(m_attached, d_attached);
ASSERT_THAT(m_attached.get(), NotNull()) << error.data();
MjDataPtr d_attached = MakeData(m_attached);
mj_forward(m_attached.get(), d_attached.get());
// THE BUG: flex constraints disappear when attached
EXPECT_GT(m_attached->neq, 0)
@@ -1009,8 +981,6 @@ TEST_F(UserFlexTest, FlexAttachConstraintPreserved) {
EXPECT_EQ(m_attached->neq, standalone_neq)
<< "Attached flex should have same number of constraints as standalone";
mj_deleteData(d_attached);
mj_deleteModel(m_attached);
mj_deleteVFS(vfs.get());
}
@@ -1024,11 +994,10 @@ TEST_F(UserFlexTest, FlexNoConstraintsWarning) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_THAT(error.data(),
HasSubstr("no equality constraints or passive forces"));
mj_deleteModel(m);
}
TEST_F(UserFlexTest, EmptyCellNodePinning) {
@@ -1046,8 +1015,8 @@ TEST_F(UserFlexTest, EmptyCellNodePinning) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
// A 2x2x2 grid with trilinear order has (2+1)^3 = 27 node positions.
int nadr = m->flex_nodeadr[0];
@@ -1065,20 +1034,16 @@ TEST_F(UserFlexTest, EmptyCellNodePinning) {
EXPECT_EQ(pinned, 0);
// Verify simulation works
mjData* d = mj_makeData(m);
MjDataPtr d = MakeData(m);
for (int i = 0; i < 10; i++) {
mj_step(m, d);
mj_step(m.get(), d.get());
}
mj_deleteData(d);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, EmptyCellNodePinningMesh) {
// Load bunny_multicell.xml which has a 3x3x3 grid.
// The bunny mesh only occupies some cells, so many nodes should be pinned.
const std::string xml_path =
GetModelPath("flex/bunny_multicell.xml");
const std::string xml_path = GetModelPath("flex/bunny_multicell.xml");
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
@@ -1152,8 +1117,8 @@ TEST_F(UserFlexTest, EmptyCellNodePinningQuadratic) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
// 3x1x1 quadratic grid: (3*2+1) * (1*2+1) * (1*2+1) = 7*3*3 = 63 nodes
int nadr = m->flex_nodeadr[0];
@@ -1161,7 +1126,7 @@ TEST_F(UserFlexTest, EmptyCellNodePinningQuadratic) {
EXPECT_EQ(nnode, 63);
// Count pinned nodes: pinned nodes are assigned to the parent body.
int parent_bid = mj_name2id(m, mjOBJ_BODY, "parent");
int parent_bid = mj_name2id(m.get(), mjOBJ_BODY, "parent");
ASSERT_GT(parent_bid, 0);
int pinned = 0;
for (int n = nadr; n < nadr + nnode; n++) {
@@ -1175,8 +1140,7 @@ TEST_F(UserFlexTest, EmptyCellNodePinningQuadratic) {
// That's 4 * 3 * 3 = 36 nodes.
EXPECT_EQ(pinned, 36);
mj_deleteData(mj_makeData(m));
mj_deleteModel(m);
mj_deleteData(mj_makeData(m.get()));
}
TEST_F(UserFlexTest, EmptyCellDetectsElements) {
@@ -1208,8 +1172,8 @@ TEST_F(UserFlexTest, EmptyCellDetectsElements) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
// 6x6x6 trilinear grid: (6+1)^3 = 343 nodes
int nadr = m->flex_nodeadr[0];
@@ -1217,7 +1181,7 @@ TEST_F(UserFlexTest, EmptyCellDetectsElements) {
ASSERT_EQ(nnode, 343);
// Count pinned nodes: those assigned to the parent body.
int parent_bid = mj_name2id(m, mjOBJ_BODY, "parent");
int parent_bid = mj_name2id(m.get(), mjOBJ_BODY, "parent");
ASSERT_GT(parent_bid, 0);
int pinned = 0;
for (int n = nadr; n < nadr + nnode; n++) {
@@ -1233,8 +1197,7 @@ TEST_F(UserFlexTest, EmptyCellDetectsElements) {
// No cells are empty → 0 nodes pinned.
EXPECT_EQ(pinned, 0);
mj_deleteData(mj_makeData(m));
mj_deleteModel(m);
mj_deleteData(mj_makeData(m.get()));
}
TEST_F(UserFlexTest, TotalMassTrilinear) {
@@ -1249,8 +1212,8 @@ TEST_F(UserFlexTest, TotalMassTrilinear) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
double total_mass = 0;
for (int i = 1; i < m->nbody; ++i) {
@@ -1258,7 +1221,6 @@ TEST_F(UserFlexTest, TotalMassTrilinear) {
}
EXPECT_NEAR(total_mass, 1.5, 1e-5);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, TotalMassQuadratic) {
@@ -1273,8 +1235,8 @@ TEST_F(UserFlexTest, TotalMassQuadratic) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
double total_mass = 0;
for (int i = 1; i < m->nbody; ++i) {
@@ -1282,7 +1244,6 @@ TEST_F(UserFlexTest, TotalMassQuadratic) {
}
EXPECT_NEAR(total_mass, 2.0, 1e-5);
mj_deleteModel(m);
}
TEST_F(UserFlexTest, Dof2d) {
@@ -1298,7 +1259,8 @@ TEST_F(UserFlexTest, Dof2d) {
</mujoco>
)";
// same model with dof="full" for comparison: 9 vertices, 3 DOFs each -> nv = 27
// same model with dof="full" for comparison: 9 vertices, 3 DOFs each -> nv =
// 27
static constexpr char xml_full[] = R"(
<mujoco>
<worldbody>
@@ -1313,14 +1275,14 @@ TEST_F(UserFlexTest, Dof2d) {
std::array<char, 1024> error;
// load 2d model
mjModel* m_2d = LoadModelFromString(xml_2d, error.data(), error.size());
ASSERT_THAT(m_2d, NotNull()) << error.data();
mjData* d_2d = mj_makeData(m_2d);
MjModelPtr m_2d = LoadModelFromString(xml_2d, error.data(), error.size());
ASSERT_THAT(m_2d.get(), NotNull()) << error.data();
MjDataPtr d_2d = MakeData(m_2d);
// load full model
mjModel* m_full = LoadModelFromString(xml_full, error.data(), error.size());
ASSERT_THAT(m_full, NotNull()) << error.data();
mjData* d_full = mj_makeData(m_full);
MjModelPtr m_full = LoadModelFromString(xml_full, error.data(), error.size());
ASSERT_THAT(m_full.get(), NotNull()) << error.data();
MjDataPtr d_full = MakeData(m_full);
// verify DOF counts
EXPECT_EQ(m_2d->nv, 18); // 9 vertices * 2 DOFs
@@ -1340,14 +1302,9 @@ TEST_F(UserFlexTest, Dof2d) {
// simulate a few steps to make sure nothing crashes
for (int i = 0; i < 10; i++) {
mj_step(m_2d, d_2d);
mj_step(m_full, d_full);
mj_step(m_2d.get(), d_2d.get());
mj_step(m_full.get(), d_full.get());
}
mj_deleteModel(m_2d);
mj_deleteModel(m_full);
mj_deleteData(d_2d);
mj_deleteData(d_full);
}
TEST_F(UserFlexTest, Vert0RotationInvariant) {
@@ -1380,11 +1337,12 @@ TEST_F(UserFlexTest, Vert0RotationInvariant) {
)";
std::array<char, 1024> error;
mjModel* m1 = LoadModelFromString(xml_unrotated, error.data(), error.size());
ASSERT_THAT(m1, NotNull()) << error.data();
MjModelPtr m1 =
LoadModelFromString(xml_unrotated, error.data(), error.size());
ASSERT_THAT(m1.get(), NotNull()) << error.data();
mjModel* m2 = LoadModelFromString(xml_rotated, error.data(), error.size());
ASSERT_THAT(m2, NotNull()) << error.data();
MjModelPtr m2 = LoadModelFromString(xml_rotated, error.data(), error.size());
ASSERT_THAT(m2.get(), NotNull()) << error.data();
// same number of vertices
ASSERT_EQ(m1->nflexvert, m2->nflexvert);
@@ -1394,9 +1352,6 @@ TEST_F(UserFlexTest, Vert0RotationInvariant) {
EXPECT_NEAR(m1->flex_vert0[i], m2->flex_vert0[i], 1e-10)
<< "vert0 mismatch at index " << i;
}
mj_deleteModel(m1);
mj_deleteModel(m2);
}
TEST_F(UserFlexTest, Load1DFlexFromOBJ) {
@@ -1413,4 +1368,3 @@ TEST_F(UserFlexTest, Load1DFlexFromOBJ) {
} // namespace
} // namespace mujoco
+188 -237
View File
@@ -37,16 +37,12 @@ namespace {
using MjCMeshTest = MujocoTest;
static const char* const kMeshPath =
"user/testdata/mesh.xml";
static const char* const kMeshPath = "user/testdata/mesh.xml";
static const char* const kDuplicateVerticesPath =
"user/testdata/duplicate_vertices.xml";
static const char* const kCubePath =
"user/testdata/cube.xml";
static const char* const kCubeCompletePath =
"user/testdata/cube_complete.obj";
static const char* const kTorusPath =
"user/testdata/torus.xml";
static const char* const kCubePath = "user/testdata/cube.xml";
static const char* const kCubeCompletePath = "user/testdata/cube_complete.obj";
static const char* const kTorusPath = "user/testdata/torus.xml";
static const char* const kTorusMaxhullVertPath =
"user/testdata/torus_maxhullvert.xml";
static const char* const kTorusDefaultMaxhullVertPath =
@@ -57,18 +53,14 @@ static const char* const kConvexInertiaPath =
"user/testdata/inertia_convex.xml";
static const char* const kConcaveInertiaPath =
"user/testdata/inertia_concave.xml";
static const char* const kShellInertiaPath =
"user/testdata/inertia_shell.xml";
static const char* const kTorusQuadsPath =
"user/testdata/torus_quads.xml";
static const char* const kShellInertiaPath = "user/testdata/inertia_shell.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";
static const char* const kDuplicateOBJPath = "user/testdata/duplicate.xml";
static const char* const kMalformedFaceOBJPath =
"user/testdata/malformed_face.xml";
static const char* const kCubeSkinPath =
"user/testdata/cube_skin.xml";
static const char* const kCubeSkinPath = "user/testdata/cube_skin.xml";
static const char* const kNoDecoderForMeshErrorMsh =
"no decoder found for mesh";
@@ -94,13 +86,8 @@ TEST_F(MjCMeshTest, UnknownMeshFormat) {
</mujoco>
)";
std::vector<std::string> invalid_names = {
"noextension",
"anobj",
"f",
"mesh.exe",
"file%s"
};
std::vector<std::string> invalid_names = {"noextension", "anobj", "f",
"mesh.exe", "file%s"};
mjVFS vfs;
mj_defaultVFS(&vfs);
@@ -108,9 +95,9 @@ TEST_F(MjCMeshTest, UnknownMeshFormat) {
mj_addBufferVFS(&vfs, name.c_str(), nullptr, 0);
std::string xml = absl::StrFormat(xml_format, name);
std::array<char, 1024> error;
mjModel* model =
MjModelPtr model =
LoadModelFromString(xml.c_str(), error.data(), error.size(), &vfs);
ASSERT_THAT(model, testing::IsNull())
ASSERT_THAT(model.get(), testing::IsNull())
<< "Should fail to load a mesh named: " << name;
EXPECT_THAT(error.data(), HasSubstr(kNoDecoderForMeshErrorMsh));
EXPECT_THAT(error.data(), HasSubstr(name));
@@ -141,8 +128,8 @@ TEST_F(MjCMeshTest, LoadMSHWithVFS) {
mj_defaultVFS(vfs.get());
// should fallback to OS filesystem
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("Error opening file"));
mj_deleteVFS(vfs.get());
}
@@ -167,8 +154,8 @@ TEST_F(MjCMeshTest, LoadOBJWithVFS) {
mj_defaultVFS(vfs.get());
// should fallback to OS filesystem
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("Error opening file"));
mj_deleteVFS(vfs.get());
}
@@ -193,8 +180,8 @@ TEST_F(MjCMeshTest, LoadSTLWithVFS) {
mj_defaultVFS(vfs.get());
// should fallback to OS filesystem
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("Error opening file"));
mj_deleteVFS(vfs.get());
}
@@ -221,8 +208,8 @@ TEST_F(MjCMeshTest, LoadMSHWithContentType) {
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("Error opening file"));
mj_deleteVFS(vfs.get());
}
@@ -247,8 +234,8 @@ TEST_F(MjCMeshTest, LoadOBJWithContentType) {
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("Error opening file"));
mj_deleteVFS(vfs.get());
}
@@ -273,8 +260,8 @@ TEST_F(MjCMeshTest, LoadSTLWithContentType) {
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("Error opening file"));
mj_deleteVFS(vfs.get());
}
@@ -300,8 +287,8 @@ TEST_F(MjCMeshTest, LoadMSHWithContentTypeError) {
mj_addBufferVFS(&vfs, "some_file", nullptr, 0);
// should error with unknown file type
mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, &vfs);
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr(kNoDecoderForMeshErrorMsh));
mj_deleteVFS(&vfs);
}
@@ -327,8 +314,8 @@ TEST_F(MjCMeshTest, LoadMSHWithInvalidContentType) {
mj_addBufferVFS(&vfs, "some_file", nullptr, 0);
// should error with unknown file type
mjModel* model = LoadModelFromString(xml, error, error_sz, &vfs);
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, &vfs);
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr(kNoDecoderForMeshErrorMsh));
mj_deleteVFS(&vfs);
}
@@ -353,8 +340,8 @@ TEST_F(MjCMeshTest, LoadMSHWithContentTypeParam) {
mj_defaultVFS(vfs.get());
// should try opening the file (not found obviously)
mjModel* model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, error_sz, vfs.get());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("Error opening file"));
mj_deleteVFS(vfs.get());
}
@@ -438,8 +425,7 @@ TEST_F(MjCMeshTest, SaveMeshOnce) {
std::array<char, 1024> error;
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
// Confirm the mesh file is loaded and stored in paths
EXPECT_EQ(
std::string(&model->paths[model->mesh_pathadr[0]]), "cube.obj");
EXPECT_EQ(std::string(&model->paths[model->mesh_pathadr[0]]), "cube.obj");
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, Not(testing::HasSubstr("vertex")));
mj_deleteModel(model);
@@ -457,9 +443,8 @@ TEST_F(MjCMeshTest, TinyMeshLoads) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
// ------------- test max hull vert -------------------------------------------
@@ -498,9 +483,8 @@ TEST_F(MjCMeshTest, FaceNormalAutogenerated) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
// ------------- test inertia -------------------------------------------------
@@ -521,9 +505,8 @@ TEST_F(MjCMeshTest, SmallInertiaLoads) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
TEST_F(MjCMeshTest, TinyInertiaFails) {
@@ -586,11 +569,13 @@ TEST_F(MjCMeshTest, FlippedFaceFailsExactInertia) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::IsNull());
EXPECT_THAT(error.data(), HasSubstr(
"Error: faces of mesh 'example_mesh' have inconsistent orientation. "
"Please check the faces containing the vertices 1 and 2."));
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::IsNull());
EXPECT_THAT(
error.data(),
HasSubstr(
"Error: faces of mesh 'example_mesh' have inconsistent orientation. "
"Please check the faces containing the vertices 1 and 2."));
}
void CheckTetrahedronWasRescaled(mjModel* model) {
@@ -598,12 +583,12 @@ void CheckTetrahedronWasRescaled(mjModel* model) {
// with vertices (0, 0, 0), (1, 0, 0), (0, 2, 0), (0, 0, 3)
// after mesh preprocessing is performed
std::vector<mjtNum> vert = {
-0.51610732078552246, -0.57402724027633667, -0.5283237099647522,
0.42337465286254883, -0.90627568960189819, -0.61189728975296021,
0.065528042614459991, 1.2306677103042603, -1.1645441055297852,
0.027204651385545731, 0.24963514506816864, 2.3047652244567871};
-0.51610732078552246, -0.57402724027633667, -0.5283237099647522,
0.42337465286254883, -0.90627568960189819, -0.61189728975296021,
0.065528042614459991, 1.2306677103042603, -1.1645441055297852,
0.027204651385545731, 0.24963514506816864, 2.3047652244567871};
mjtNum tolerance = std::numeric_limits<float>::epsilon();
for (int i=0; i < 12; ++i) {
for (int i = 0; i < 12; ++i) {
EXPECT_NEAR(model->mesh_vert[i], vert[i], tolerance);
}
}
@@ -622,10 +607,9 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedWorld) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
CheckTetrahedronWasRescaled(model);
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
CheckTetrahedronWasRescaled(model.get());
}
TEST_F(MjCMeshTest, FlippedFaceAllowedNoMass) {
@@ -644,10 +628,9 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedNoMass) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
CheckTetrahedronWasRescaled(model);
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
CheckTetrahedronWasRescaled(model.get());
}
TEST_F(MjCMeshTest, FlippedFaceAllowedInertial) {
@@ -667,10 +650,9 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedInertial) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
CheckTetrahedronWasRescaled(model);
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
CheckTetrahedronWasRescaled(model.get());
}
TEST_F(MjCMeshTest, FlippedFaceAllowedNegligibleArea) {
@@ -689,10 +671,9 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedNegligibleArea) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
CheckTetrahedronWasRescaled(model);
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
CheckTetrahedronWasRescaled(model.get());
}
TEST_F(MjCMeshTest, AreaTooSmall) {
@@ -711,8 +692,8 @@ TEST_F(MjCMeshTest, AreaTooSmall) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("mesh surface area is too small"));
}
@@ -732,10 +713,9 @@ TEST_F(MjCMeshTest, VolumeTooSmall) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::IsNull());
EXPECT_THAT(error.data(), HasSubstr("mesh volume is too small"));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, VisualVolumeTooSmall) {
@@ -759,10 +739,9 @@ TEST_F(MjCMeshTest, VisualVolumeTooSmall) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::IsNull());
EXPECT_THAT(error.data(), HasSubstr("mesh volume is too small"));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, VisualVolumeSmallAllowedShell) {
@@ -786,12 +765,11 @@ TEST_F(MjCMeshTest, VisualVolumeSmallAllowedShell) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_LE(mju_abs(model->geom_size[0]), 1);
EXPECT_LE(mju_abs(model->geom_size[1]), 1);
EXPECT_LE(mju_abs(model->geom_size[2]), 1);
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, VolumeSmallAllowedShell) {
@@ -810,12 +788,11 @@ TEST_F(MjCMeshTest, VolumeSmallAllowedShell) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_LE(mju_abs(model->geom_size[0]), 1);
EXPECT_LE(mju_abs(model->geom_size[1]), 1);
EXPECT_LE(mju_abs(model->geom_size[2]), 1);
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, Flex2DElasticityRequiresPositiveThickness) {
@@ -829,8 +806,8 @@ TEST_F(MjCMeshTest, Flex2DElasticityRequiresPositiveThickness) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::IsNull());
EXPECT_THAT(error.data(),
HasSubstr("2d elasticity requires positive thickness"));
}
@@ -847,9 +824,8 @@ TEST_F(MjCMeshTest, InterpolatedFlexSupportsBendElasticityWithWarning) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::NotNull()) << error.data();
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::NotNull()) << error.data();
}
TEST_F(MjCMeshTest, InterpolatedFlexSupportsBothElasticityWithWarning) {
@@ -864,9 +840,8 @@ TEST_F(MjCMeshTest, InterpolatedFlexSupportsBothElasticityWithWarning) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::NotNull()) << error.data();
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::NotNull()) << error.data();
}
TEST_F(MjCMeshTest, Flex2DElasticityRequires2DFlex) {
@@ -880,8 +855,8 @@ TEST_F(MjCMeshTest, Flex2DElasticityRequires2DFlex) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::IsNull());
EXPECT_THAT(error.data(), HasSubstr("2d elasticity requires 2d flex"));
}
@@ -908,23 +883,24 @@ TEST_F(MjCMeshTest, VolumeNegativeThrowsError) {
for (int nmesh : {3, 16, 17, 50}) {
std::string mesh_definitions = "";
for (int i = 1; i < nmesh+1; i++) {
for (int i = 1; i < nmesh + 1; i++) {
mesh_definitions += absl::StrFormat(bad_mesh, i);
}
std::string geom_definitions = "";
for (int i = 1; i < nmesh+1; i++) {
for (int i = 1; i < nmesh + 1; i++) {
geom_definitions += absl::StrFormat(geom, i);
}
std::string xml_str = xml;
absl::StrReplaceAll({{"MESH_DEFINITIONS", mesh_definitions},
{"GEOM_DEFINITIONS", geom_definitions}}, &xml_str);
{"GEOM_DEFINITIONS", geom_definitions}},
&xml_str);
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml_str.c_str(),
error.data(), error.size());
EXPECT_THAT(model, IsNull());
MjModelPtr model =
LoadModelFromString(xml_str.c_str(), error.data(), error.size());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("mesh volume is negative"));
}
}
@@ -964,22 +940,22 @@ TEST_F(MjCMeshTest, ExactConcaveInertia) {
mjtNum m_hole = .9 * .8 * .8 * density;
mjtNum m_cube = 1. * density;
mjtNum m_concave_cube = m_cube - m_hole;
mjtNum I_cube = m_cube/6.;
mjtNum I_cube = m_cube / 6.;
// due to the asymmetric hole, the com position has changed
// so we need to use https://en.wikipedia.org/wiki/Parallel_axis_theorem
mjtNum d_cube = .5 - model->body_ipos[5];
mjtNum d_hole = .55 - model->body_ipos[5];
mjtNum I1 = I_cube - m_hole*(.8*.8 + .8*.8)/12;
mjtNum I2 = I_cube - m_hole*(.8*.8 + .9*.9)/12
+ m_cube*d_cube*d_cube - m_hole*d_hole*d_hole;
mjtNum I1 = I_cube - m_hole * (.8 * .8 + .8 * .8) / 12;
mjtNum I2 = I_cube - m_hole * (.8 * .8 + .9 * .9) / 12 +
m_cube * d_cube * d_cube - m_hole * d_hole * d_hole;
EXPECT_NEAR(model->body_mass[1], m_concave_cube, kMaxAbsErr);
EXPECT_NEAR(model->body_mass[2], m_concave_cube, kMaxAbsErr);
EXPECT_NEAR(model->body_mass[3], m_concave_cube, kMaxAbsErr);
EXPECT_NEAR(model->body_mass[4], m_concave_cube, kMaxAbsErr);
for (int i = 3; i < 15; i += 3) {
EXPECT_NEAR(model->body_inertia[i], I1, kMaxAbsErr);
EXPECT_NEAR(model->body_inertia[i+1], I2, kMaxAbsErr);
EXPECT_NEAR(model->body_inertia[i+2], I2, kMaxAbsErr);
EXPECT_NEAR(model->body_inertia[i + 1], I2, kMaxAbsErr);
EXPECT_NEAR(model->body_inertia[i + 2], I2, kMaxAbsErr);
}
mj_deleteModel(model);
}
@@ -990,7 +966,7 @@ TEST_F(MjCMeshTest, ExactConvexInertia) {
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
// https://en.wikipedia.org/wiki/List_of_moments_of_inertia
mjtNum m_solid_cube = 1.;
mjtNum I_solid_cube = 1./6. * m_solid_cube;
mjtNum I_solid_cube = 1. / 6. * m_solid_cube;
EXPECT_LE(mju_abs(model->body_mass[1] - m_solid_cube), kMaxAbsErr);
EXPECT_LE(mju_abs(model->body_mass[2] - m_solid_cube), kMaxAbsErr);
for (int i = 3; i < 9; i++) {
@@ -1005,7 +981,7 @@ TEST_F(MjCMeshTest, ExactShellInertia) {
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
// see https://en.wikipedia.org/wiki/List_of_moments_of_inertia
mjtNum m_hollow_cube = 6.;
mjtNum I_hollow_cube = 5./18. * m_hollow_cube;
mjtNum I_hollow_cube = 5. / 18. * m_hollow_cube;
EXPECT_LE(mju_abs(model->body_mass[1] - m_hollow_cube), kMaxAbsErr);
EXPECT_LE(mju_abs(model->body_inertia[3] - I_hollow_cube), kMaxAbsErr);
EXPECT_LE(mju_abs(model->body_inertia[4] - I_hollow_cube), kMaxAbsErr);
@@ -1026,8 +1002,8 @@ TEST_F(MjCMeshTest, MeshPosQuat) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
// loading the mesh results in an offset of the geom's pos and quat due to the
// fact that the geom's center is not the volumetric center of the mesh. To
// recover the geom's originally specified pose, the offset used is stored in
@@ -1035,16 +1011,15 @@ TEST_F(MjCMeshTest, MeshPosQuat) {
// and orientation, first invert the specified mesh_pos and mesh_quat
mjtNum inverse_mesh_pos[3];
mjtNum inverse_mesh_quat[4];
mju_negPose(inverse_mesh_pos, inverse_mesh_quat,
&model->mesh_pos[0], &model->mesh_quat[0]);
mju_negPose(inverse_mesh_pos, inverse_mesh_quat, &model->mesh_pos[0],
&model->mesh_quat[0]);
// apply the inverted mesh_pos and inverted mesh_quat to the geom's pos and
// quat. It should match the originally specified values
mjtNum recovered_pos[3];
mjtNum recovered_quat[4];
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[0], &model->geom_quat[0],
inverse_mesh_pos, inverse_mesh_quat);
mju_mulPose(recovered_pos, recovered_quat, &model->geom_pos[0],
&model->geom_quat[0], inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 0, MjTol(1e-12, 1e-6));
@@ -1055,11 +1030,10 @@ TEST_F(MjCMeshTest, MeshPosQuat) {
EXPECT_NEAR(recovered_quat[3], 0, MjTol(1e-12, 1e-6));
// same test on the other geom
mju_negPose(inverse_mesh_pos, inverse_mesh_quat,
&model->mesh_pos[0], &model->mesh_quat[0]);
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[3], &model->geom_quat[4],
inverse_mesh_pos, inverse_mesh_quat);
mju_negPose(inverse_mesh_pos, inverse_mesh_quat, &model->mesh_pos[0],
&model->mesh_quat[0]);
mju_mulPose(recovered_pos, recovered_quat, &model->geom_pos[3],
&model->geom_quat[4], inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 1, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 2, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 3, MjTol(1e-12, 1e-6));
@@ -1068,8 +1042,6 @@ TEST_F(MjCMeshTest, MeshPosQuat) {
EXPECT_NEAR(recovered_quat[1], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[2], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[3], 0.5, MjTol(1e-12, 1e-6));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, MeshPosQuatShellInertia) {
@@ -1085,8 +1057,8 @@ TEST_F(MjCMeshTest, MeshPosQuatShellInertia) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
// loading the mesh results in an offset of the geom's pos and quat due to the
// fact that the geom's center is not the volumetric center of the mesh. To
// recover the geom's originally specified pose, the offset used is stored in
@@ -1094,16 +1066,15 @@ TEST_F(MjCMeshTest, MeshPosQuatShellInertia) {
// and orientation, first invert the specified mesh_pos and mesh_quat
mjtNum inverse_mesh_pos[3];
mjtNum inverse_mesh_quat[4];
mju_negPose(inverse_mesh_pos, inverse_mesh_quat,
&model->mesh_pos[0], &model->mesh_quat[0]);
mju_negPose(inverse_mesh_pos, inverse_mesh_quat, &model->mesh_pos[0],
&model->mesh_quat[0]);
// apply the inverted mesh_pos and inverted mesh_quat to the geom's pos and
// quat. It should match the originally specified values
mjtNum recovered_pos[3];
mjtNum recovered_quat[4];
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[0], &model->geom_quat[0],
inverse_mesh_pos, inverse_mesh_quat);
mju_mulPose(recovered_pos, recovered_quat, &model->geom_pos[0],
&model->geom_quat[0], inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 0, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 0, MjTol(1e-12, 1e-6));
@@ -1114,11 +1085,10 @@ TEST_F(MjCMeshTest, MeshPosQuatShellInertia) {
EXPECT_NEAR(recovered_quat[3], 0, MjTol(1e-12, 1e-6));
// same test on the other geom
mju_negPose(inverse_mesh_pos, inverse_mesh_quat,
&model->mesh_pos[0], &model->mesh_quat[0]);
mju_mulPose(recovered_pos, recovered_quat,
&model->geom_pos[3], &model->geom_quat[4],
inverse_mesh_pos, inverse_mesh_quat);
mju_negPose(inverse_mesh_pos, inverse_mesh_quat, &model->mesh_pos[0],
&model->mesh_quat[0]);
mju_mulPose(recovered_pos, recovered_quat, &model->geom_pos[3],
&model->geom_quat[4], inverse_mesh_pos, inverse_mesh_quat);
EXPECT_NEAR(recovered_pos[0], 1, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[1], 2, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_pos[2], 3, MjTol(1e-12, 1e-6));
@@ -1127,7 +1097,6 @@ TEST_F(MjCMeshTest, MeshPosQuatShellInertia) {
EXPECT_NEAR(recovered_quat[1], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[2], 0.5, MjTol(1e-12, 1e-6));
EXPECT_NEAR(recovered_quat[3], 0.5, MjTol(1e-12, 1e-6));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, MeshScale) {
@@ -1144,12 +1113,11 @@ TEST_F(MjCMeshTest, MeshScale) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_THAT(AsVector(model->mesh_scale + 0, 3), ElementsAre(1, 1, 1));
EXPECT_THAT(AsVector(model->mesh_scale + 3, 3), ElementsAre(0.9, 1, -1));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, NegativeScaleUserMeshCompiles) {
@@ -1166,9 +1134,8 @@ TEST_F(MjCMeshTest, NegativeScaleUserMeshCompiles) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
TEST_F(MjCMeshTest, NegativeScaleUserMeshMatchesPositiveScale) {
@@ -1199,22 +1166,19 @@ TEST_F(MjCMeshTest, NegativeScaleUserMeshMatchesPositiveScale) {
)";
char error[1024];
mjModel* pos_model = LoadModelFromString(pos_xml, error, sizeof(error));
ASSERT_THAT(pos_model, NotNull()) << error;
MjModelPtr pos_model = LoadModelFromString(pos_xml, error, sizeof(error));
ASSERT_THAT(pos_model.get(), NotNull()) << error;
mjModel* neg_model = LoadModelFromString(neg_xml, error, sizeof(error));
ASSERT_THAT(neg_model, NotNull()) << error;
MjModelPtr neg_model = LoadModelFromString(neg_xml, error, sizeof(error));
ASSERT_THAT(neg_model.get(), NotNull()) << error;
ASSERT_EQ(pos_model->nmeshface, neg_model->nmeshface);
for (int i = 0; i < pos_model->nmeshface; i++) {
EXPECT_EQ(pos_model->mesh_face[3*i + 0], neg_model->mesh_face[3*i + 0]);
EXPECT_EQ(pos_model->mesh_face[3*i + 1], neg_model->mesh_face[3*i + 2]);
EXPECT_EQ(pos_model->mesh_face[3*i + 2], neg_model->mesh_face[3*i + 1]);
EXPECT_EQ(pos_model->mesh_face[3 * i + 0], neg_model->mesh_face[3 * i + 0]);
EXPECT_EQ(pos_model->mesh_face[3 * i + 1], neg_model->mesh_face[3 * i + 2]);
EXPECT_EQ(pos_model->mesh_face[3 * i + 2], neg_model->mesh_face[3 * i + 1]);
}
mj_deleteModel(pos_model);
mj_deleteModel(neg_model);
}
TEST_F(MjCMeshTest, ShellInertiaTest) {
@@ -1231,12 +1195,11 @@ TEST_F(MjCMeshTest, ShellInertiaTest) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
EXPECT_THAT(AsVector(model->mesh_scale + 0, 3), ElementsAre(1, 1, 1));
EXPECT_THAT(AsVector(model->mesh_scale + 3, 3), ElementsAre(0.9, 1, -1));
mj_deleteModel(model);
}
// ----------------------------- texcoord -------------------------------------
@@ -1251,9 +1214,8 @@ TEST_F(MjCMeshTest, CreateFaceTexCoord) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
TEST_F(MjCMeshTest, UseFaceTexCoord) {
@@ -1267,21 +1229,20 @@ TEST_F(MjCMeshTest, UseFaceTexCoord) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, NotNull()) << error.data();
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 0]], .0);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 1]], .2);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 2]], .1);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 3]], .0);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 4]], .1);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 5]], .3);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 6]], .2);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 7]], .0);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 8]], .3);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 9]], .1);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[10]], .2);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[11]], .3);
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), NotNull()) << error.data();
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[0]], .0);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[1]], .2);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[2]], .1);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[3]], .0);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[4]], .1);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[5]], .3);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[6]], .2);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[7]], .0);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[8]], .3);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[9]], .1);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[10]], .2);
EXPECT_FLOAT_EQ(model->mesh_texcoord[2 * model->mesh_facetexcoord[11]], .3);
}
TEST_F(MjCMeshTest, MissingTexCoord) {
@@ -1294,8 +1255,8 @@ TEST_F(MjCMeshTest, MissingTexCoord) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::IsNull());
EXPECT_THAT(error.data(), HasSubstr("texcoord must be 2*nv"));
}
@@ -1312,10 +1273,9 @@ TEST_F(MjCMeshTest, NaNConvexHullDisallowed) {
MockWarningHandler warning_handler;
warning_handler.ExpectWarnings();
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, testing::IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), testing::IsNull());
EXPECT_THAT(error.data(), HasSubstr("vertex coordinate 0 is not finite"));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, InvalidIndexInFace) {
@@ -1333,10 +1293,9 @@ TEST_F(MjCMeshTest, InvalidIndexInFace) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), IsNull());
EXPECT_THAT(error, HasSubstr("in face 0, vertex index 6 does not exist"));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, QhullCache) {
@@ -1351,7 +1310,7 @@ TEST_F(MjCMeshTest, QhullCache) {
</worldbody>
</mujoco>)";
static constexpr char xml2[] = R"(
static constexpr char xml2[] = R"(
<mujoco>
<asset>
<mesh name="box" file="cube_complete.obj"/>
@@ -1367,17 +1326,16 @@ TEST_F(MjCMeshTest, QhullCache) {
mj_addFileVFS(&vfs, "", GetTestDataFilePath(kCubeCompletePath).c_str());
std::array<char, 1000> error;
mjModel* model = LoadModelFromString(xml1, error.data(), error.size(), &vfs);
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
MjModelPtr model =
LoadModelFromString(xml1, error.data(), error.size(), &vfs);
ASSERT_THAT(model.get(), NotNull())
<< "Failed to load model: " << error.data();
EXPECT_THAT(model->mesh_graphadr[0], -1);
mj_deleteModel(model);
model = LoadModelFromString(xml2, error.data(), error.size(), &vfs);
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
ASSERT_THAT(model.get(), NotNull())
<< "Failed to load model: " << error.data();
EXPECT_GT(model->mesh_graphadr[0], -1);
mj_deleteModel(model);
mj_deleteVFS(&vfs);
}
@@ -1395,8 +1353,8 @@ TEST_F(MjCMeshTest, ColocatedMeshError) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("colocated"));
}
@@ -1414,8 +1372,8 @@ TEST_F(MjCMeshTest, CollinearMeshError) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("collinear"));
}
@@ -1433,8 +1391,8 @@ TEST_F(MjCMeshTest, CoplanarMeshError) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("coplanar"));
}
@@ -1478,8 +1436,7 @@ bool AreAabbsAdjacent(const mjtNum* aabb1, const mjtNum* aabb2) {
const mjtNum half_size1 = aabb1[dim + 3];
const mjtNum center2 = aabb2[dim];
const mjtNum half_size2 = aabb2[dim + 3];
const mjtNum gap =
std::abs(center1 - center2) - (half_size1 + half_size2);
const mjtNum gap = std::abs(center1 - center2) - (half_size1 + half_size2);
if (std::abs(gap) < kEps) {
touching_dims++;
} else if (gap < -kEps) {
@@ -1533,15 +1490,15 @@ TEST_F(MjCMeshTest, OctreeIsBalanced) {
const int level2 = model->oct_depth[octree_adr + node2_idx];
if (std::abs(level1 - level2) > 1) {
if (unbalanced_pairs < 10) {
ADD_FAILURE()
<< "Nodes " << node1_idx << " (level " << level1 << ") and "
<< node2_idx << " (level " << level2 << ") are not balanced."
<< "\nAABB1: center=(" << aabb1[0] << ", " << aabb1[1] << ", "
<< aabb1[2] << "), half_size=(" << aabb1[3] << ", "
<< aabb1[4] << ", " << aabb1[5] << ")"
<< "\nAABB2: center=(" << aabb2[0] << ", " << aabb2[1] << ", "
<< aabb2[2] << "), half_size=(" << aabb2[3] << ", "
<< aabb2[4] << ", " << aabb2[5] << ")";
ADD_FAILURE() << "Nodes " << node1_idx << " (level " << level1
<< ") and " << node2_idx << " (level " << level2
<< ") are not balanced."
<< "\nAABB1: center=(" << aabb1[0] << ", " << aabb1[1]
<< ", " << aabb1[2] << "), half_size=(" << aabb1[3]
<< ", " << aabb1[4] << ", " << aabb1[5] << ")"
<< "\nAABB2: center=(" << aabb2[0] << ", " << aabb2[1]
<< ", " << aabb2[2] << "), half_size=(" << aabb2[3]
<< ", " << aabb2[4] << ", " << aabb2[5] << ")";
}
unbalanced_pairs++;
}
@@ -1610,8 +1567,7 @@ TEST_F(MjCMeshTest, OctreeHangingNodeInterpolation) {
// decide which node is finer and which is coarser
const int finer_node_idx = (level1 > level2) ? node1_idx : node2_idx;
const int coarser_node_idx =
(level1 > level2) ? node2_idx : node1_idx;
const int coarser_node_idx = (level1 > level2) ? node2_idx : node1_idx;
const mjtNum* coarser_aabb =
&model->oct_aabb[(octree_adr + coarser_node_idx) * 6];
const mjtNum* finer_aabb =
@@ -1729,12 +1685,11 @@ TEST_F(MjCMeshTest, OctreeNotComputedForNonSDF) {
}
mjtNum CubeSDF(mjtNum p[3], mjtNum b[3]) {
mjtNum q[3] = {mju_abs(p[0]) - b[0],
mju_abs(p[1]) - b[1],
mjtNum q[3] = {mju_abs(p[0]) - b[0], mju_abs(p[1]) - b[1],
mju_abs(p[2]) - b[2]};
return mju_sqrt(std::pow(std::max(q[0], (mjtNum)0), 2) +
std::pow(std::max(q[1], (mjtNum)0), 2) +
std::pow(std::max(q[2], (mjtNum)0), 2)) +
std::pow(std::max(q[1], (mjtNum)0), 2) +
std::pow(std::max(q[2], (mjtNum)0), 2)) +
std::min(std::max(q[0], std::max(q[1], q[2])), (mjtNum)0);
}
@@ -1751,12 +1706,12 @@ TEST_F(MjCMeshTest, OctreeCube) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->noct, 63497);
mjData* d = mj_makeData(m);
MjDataPtr d = MakeData(m);
ASSERT_THAT(d, NotNull());
mj_forward(m, d);
mj_forward(m.get(), d.get());
mjSDF sdf;
int instance = 0;
mjtGeom geomtype = mjGEOM_SDF;
@@ -1768,11 +1723,10 @@ TEST_F(MjCMeshTest, OctreeCube) {
mjtNum pnt[3][3] = {{1, 1, 1}, {.5, .5, .5}, {.5, 0, 0}};
mjtNum size[3] = {1, 1, 1};
for (int i = 0; i < 3; ++i) {
EXPECT_NEAR(mjc_distance(m, d, &sdf, pnt[i]), CubeSDF(pnt[i], size), 1e-1)
EXPECT_NEAR(mjc_distance(m.get(), d.get(), &sdf, pnt[i]),
CubeSDF(pnt[i], size), 1e-1)
<< "i = " << i;
}
mj_deleteModel(m);
mj_deleteData(d);
}
TEST_F(MjCMeshTest, HemisphereSizes) {
@@ -1786,15 +1740,14 @@ TEST_F(MjCMeshTest, HemisphereSizes) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
EXPECT_EQ(model->mesh_vertnum[0], 2 * (0 + 1) * (0 + 2) + 2);
EXPECT_EQ(model->mesh_vertnum[1], 2 * (1 + 1) * (1 + 2) + 2);
EXPECT_EQ(model->mesh_vertnum[2], 2 * (2 + 1) * (2 + 2) + 2);
EXPECT_EQ(model->mesh_facenum[0], 4 * (0 + 1) * (0 + 2));
EXPECT_EQ(model->mesh_facenum[1], 4 * (1 + 1) * (1 + 2));
EXPECT_EQ(model->mesh_facenum[2], 4 * (2 + 1) * (2 + 2));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, SphereSizes) {
@@ -1808,15 +1761,14 @@ TEST_F(MjCMeshTest, SphereSizes) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
EXPECT_EQ(model->mesh_vertnum[0], 2 + 10 * std::pow(4, 0));
EXPECT_EQ(model->mesh_vertnum[1], 2 + 10 * std::pow(4, 1));
EXPECT_EQ(model->mesh_vertnum[2], 2 + 10 * std::pow(4, 2));
EXPECT_EQ(model->mesh_facenum[0], 20 * std::pow(4, 0));
EXPECT_EQ(model->mesh_facenum[1], 20 * std::pow(4, 1));
EXPECT_EQ(model->mesh_facenum[2], 20 * std::pow(4, 2));
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, MeshMaterial) {
@@ -1835,11 +1787,10 @@ TEST_F(MjCMeshTest, MeshMaterial) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
EXPECT_EQ(model->geom_matid[0], 1);
EXPECT_EQ(model->geom_matid[1], 0);
mj_deleteModel(model);
}
} // namespace
+77 -116
View File
@@ -95,15 +95,13 @@ TEST_F(UserModelTest, WeldRootID) {
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, NotNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), NotNull());
EXPECT_THAT(AsVector(model->body_rootid, model->nbody),
ElementsAre(0, 1, 1, 3, 3, 5, 5, 7, 7, 9, 9));
EXPECT_THAT(AsVector(model->body_weldid, model->nbody),
ElementsAre(0, 1, 2, 3, 3, 0, 0, 0, 0, 0, 10));
mj_deleteModel(model);
}
TEST_F(UserModelTest, RepeatedNames) {
@@ -119,8 +117,8 @@ TEST_F(UserModelTest, RepeatedNames) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("repeated name 'geom1' in geom"));
}
@@ -143,8 +141,8 @@ TEST_F(UserModelTest, SameFrame) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
EXPECT_EQ(model->geom_sameframe[0], mjSAMEFRAME_NONE);
EXPECT_EQ(model->geom_sameframe[1], mjSAMEFRAME_BODY);
EXPECT_EQ(model->geom_sameframe[2], mjSAMEFRAME_INERTIA);
@@ -152,27 +150,24 @@ TEST_F(UserModelTest, SameFrame) {
EXPECT_EQ(model->geom_sameframe[4], mjSAMEFRAME_INERTIAROT);
// make data, get geom_xpos
mjData* data = mj_makeData(model);
mj_kinematics(model, data);
auto geom_xpos = AsVector(data->geom_xpos, model->ngeom*3);
auto geom_xmat = AsVector(data->geom_xmat, model->ngeom*9);
MjDataPtr data = MakeData(model);
mj_kinematics(model.get(), data.get());
auto geom_xpos = AsVector(data->geom_xpos, model->ngeom * 3);
auto geom_xmat = AsVector(data->geom_xmat, model->ngeom * 9);
// set all geom_sameframe to 0, call kinematics again
for (int i = 0; i < model->ngeom; i++) {
model->geom_sameframe[i] = mjSAMEFRAME_NONE;
}
mj_resetData(model, data);
mj_kinematics(model, data);
auto geom_xpos2 = AsVector(data->geom_xpos, model->ngeom*3);
auto geom_xmat2 = AsVector(data->geom_xmat, model->ngeom*9);
mj_resetData(model.get(), data.get());
mj_kinematics(model.get(), data.get());
auto geom_xpos2 = AsVector(data->geom_xpos, model->ngeom * 3);
auto geom_xmat2 = AsVector(data->geom_xmat, model->ngeom * 9);
// expect them to be equal
constexpr double eps = 1e-6;
EXPECT_THAT(geom_xpos, Pointwise(MjNear(eps, eps), geom_xpos2));
EXPECT_THAT(geom_xmat, Pointwise(MjNear(eps, eps), geom_xmat2));
mj_deleteData(data);
mj_deleteModel(model);
}
TEST_F(UserModelTest, ActuatorSparsity) {
@@ -194,9 +189,8 @@ TEST_F(UserModelTest, ActuatorSparsity) {
</actuator>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nJmom, 2);
mj_deleteModel(m);
}
TEST_F(UserModelTest, FixedTendonSparsity) {
@@ -226,8 +220,8 @@ TEST_F(UserModelTest, FixedTendonSparsity) {
</tendon>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
ASSERT_THAT(m, NotNull());
MjModelPtr m = LoadModelFromString(xml);
ASSERT_THAT(m.get(), NotNull());
EXPECT_EQ(m->nJten, 3);
EXPECT_EQ(m->ten_J_rownnz[0], 3);
@@ -237,8 +231,6 @@ TEST_F(UserModelTest, FixedTendonSparsity) {
int rowadr = m->ten_J_rowadr[0];
int* colind = m->ten_J_colind + rowadr;
EXPECT_THAT(std::vector<int>(colind, colind + 3), ElementsAre(0, 1, 2));
mj_deleteModel(m);
}
TEST_F(UserModelTest, NestedZeroMassBodiesOK) {
@@ -259,9 +251,8 @@ TEST_F(UserModelTest, NestedZeroMassBodiesOK) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
TEST_F(UserModelTest, NestedZeroMassBodiesWithJointOK) {
@@ -286,9 +277,8 @@ TEST_F(UserModelTest, NestedZeroMassBodiesWithJointOK) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
TEST_F(UserModelTest, NestedZeroMassBodiesFail) {
@@ -309,14 +299,13 @@ TEST_F(UserModelTest, NestedZeroMassBodiesFail) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), IsNull());
EXPECT_THAT(
error,
HasSubstr(
"mass and inertia of moving bodies must be larger than mjMINVAL"));
EXPECT_THAT(error, HasSubstr("Element name 'bad'"));
mj_deleteModel(model);
}
TEST_F(UserModelTest, ConvexHullForCollisionMeshes) {
@@ -338,14 +327,14 @@ TEST_F(UserModelTest, ConvexHullForCollisionMeshes) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
int no_hull_id = mj_name2id(model, mjOBJ_MESH, "mesh_no_hull");
int no_hull_id = mj_name2id(model.get(), mjOBJ_MESH, "mesh_no_hull");
int with_hull_contype_id =
mj_name2id(model, mjOBJ_MESH, "mesh_with_hull_contype");
mj_name2id(model.get(), mjOBJ_MESH, "mesh_with_hull_contype");
int with_hull_conaffinity_id =
mj_name2id(model, mjOBJ_MESH, "mesh_with_hull_conaffinity");
mj_name2id(model.get(), mjOBJ_MESH, "mesh_with_hull_conaffinity");
EXPECT_NE(no_hull_id, -1);
EXPECT_NE(with_hull_contype_id, -1);
@@ -359,8 +348,6 @@ TEST_F(UserModelTest, ConvexHullForCollisionMeshes) {
// mesh_with_hull_conaffinity should have a convex hull.
EXPECT_NE(model->mesh_graphadr[with_hull_conaffinity_id], -1);
mj_deleteModel(model);
}
TEST_F(UserModelTest, ConvexHullForPairCollisionMeshes) {
@@ -380,17 +367,15 @@ TEST_F(UserModelTest, ConvexHullForPairCollisionMeshes) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
int mesh_in_pair_id = mj_name2id(model, mjOBJ_MESH, "mesh");
int mesh_in_pair_id = mj_name2id(model.get(), mjOBJ_MESH, "mesh");
EXPECT_NE(mesh_in_pair_id, -1);
// mesh_in_pair should have a convex hull because it is in a collision pair.
EXPECT_NE(model->mesh_graphadr[mesh_in_pair_id], -1);
mj_deleteModel(model);
}
// ------------- test automatic inference of nuser_xxx -------------------------
@@ -406,11 +391,10 @@ TEST_F(UserDataTest, AutoNUserBody) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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) {
@@ -425,11 +409,10 @@ TEST_F(UserDataTest, AutoNUserJoint) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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) {
@@ -441,11 +424,10 @@ TEST_F(UserDataTest, AutoNUserGeom) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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) {
@@ -457,11 +439,10 @@ TEST_F(UserDataTest, AutoNUserSite) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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) {
@@ -473,11 +454,10 @@ TEST_F(UserDataTest, AutoNUserCamera) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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) {
@@ -499,11 +479,10 @@ TEST_F(UserDataTest, AutoNUserTendon) {
</tendon>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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) {
@@ -521,13 +500,12 @@ TEST_F(UserDataTest, AutoNUserActuator) {
</actuator>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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) {
@@ -542,11 +520,10 @@ TEST_F(UserDataTest, AutoNUserSensor) {
</sensor>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr 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);
}
// ------------- test duplicate names ------------------------------------------
@@ -584,10 +561,10 @@ TEST_F(FuseStaticTest, FuseStaticEquivalent) {
std::string fuse = absl::StrFormat(xml_template, "true");
std::string no_fuse = absl::StrFormat(xml_template, "false");
mjModel* m_fuse = LoadModelFromString(fuse.c_str());
mjModel* m_no_fuse = LoadModelFromString(no_fuse.c_str());
ASSERT_THAT(m_fuse, NotNull());
ASSERT_THAT(m_no_fuse, NotNull());
MjModelPtr m_fuse = LoadModelFromString(fuse.c_str());
MjModelPtr m_no_fuse = LoadModelFromString(no_fuse.c_str());
ASSERT_THAT(m_fuse.get(), NotNull());
ASSERT_THAT(m_no_fuse.get(), NotNull());
EXPECT_EQ(m_fuse->nbody, 2) << "Expecting a world body and one other body";
EXPECT_EQ(m_no_fuse->nbody, 3) << "Expecting a world body and two others";
@@ -600,20 +577,16 @@ TEST_F(FuseStaticTest, FuseStaticEquivalent) {
EXPECT_EQ(m_no_fuse->body_bvhnum[2], 3);
EXPECT_EQ(m_fuse->body_bvhnum[1], 3);
mjData* d_fuse = mj_makeData(m_fuse);
mjData* d_no_fuse = mj_makeData(m_no_fuse);
MjDataPtr d_fuse = MakeData(m_fuse);
MjDataPtr d_no_fuse = MakeData(m_no_fuse);
mj_step(m_fuse, d_fuse);
mj_step(m_no_fuse, d_no_fuse);
mj_step(m_fuse.get(), d_fuse.get());
mj_step(m_no_fuse.get(), d_no_fuse.get());
EXPECT_NEAR(d_fuse->qvel[0], d_no_fuse->qvel[0], MjTol(2e-17, 1e-8))
EXPECT_NEAR(d_fuse.get()->qvel[0], d_no_fuse.get()->qvel[0],
MjTol(2e-17, 1e-8))
<< "Velocity should be the same after 1 step";
EXPECT_NE(d_fuse->qvel[0], 0);
mj_deleteData(d_fuse);
mj_deleteData(d_no_fuse);
mj_deleteModel(m_fuse);
mj_deleteModel(m_no_fuse);
EXPECT_NE(d_fuse.get()->qvel[0], 0);
}
TEST_F(FuseStaticTest, FuseStaticActuatorReferencedBody) {
@@ -642,10 +615,9 @@ TEST_F(FuseStaticTest, FuseStaticActuatorReferencedBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 3) << "Expecting a world body and two others";
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticLightReferencedBody) {
@@ -671,10 +643,9 @@ TEST_F(FuseStaticTest, FuseStaticLightReferencedBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 3) << "Expecting a world body and two others";
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticForceSensorReferencedBody) {
@@ -704,10 +675,9 @@ TEST_F(FuseStaticTest, FuseStaticForceSensorReferencedBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 3) << "Expecting a world body and two others";
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticCameraInBody) {
@@ -727,11 +697,10 @@ TEST_F(FuseStaticTest, FuseStaticCameraInBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 2) << "Static body should be fused";
EXPECT_EQ(m->ncam, 1);
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticLightInBody) {
@@ -750,11 +719,10 @@ TEST_F(FuseStaticTest, FuseStaticLightInBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 2) << "Static body should be fused";
EXPECT_EQ(m->nlight, 1);
mj_deleteModel(m);
}
// ------------- test discardvisual --------------------------------------------
@@ -789,13 +757,12 @@ TEST_F(DiscardVisualTest, DiscardVisualKeepsInertia) {
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), NotNull()) << error.data();
EXPECT_THAT(model->nmesh, 1);
EXPECT_THAT(model->body_inertia[3], model->body_inertia[6]);
EXPECT_THAT(model->body_inertia[4], model->body_inertia[7]);
EXPECT_THAT(model->body_inertia[5], model->body_inertia[8]);
mj_deleteModel(model);
}
TEST_F(DiscardVisualTest, DiscardVisualEquivalent) {
@@ -818,8 +785,8 @@ TEST_F(DiscardVisualTest, DiscardVisualEquivalent) {
EXPECT_THAT(model1->nq, model2->nq);
EXPECT_THAT(model1->nmat, 0);
EXPECT_THAT(model1->ntex, 0);
EXPECT_THAT(model2->ngeom-model1->ngeom, 3);
EXPECT_THAT(model2->nmesh-model1->nmesh, 2);
EXPECT_THAT(model2->ngeom - model1->ngeom, 3);
EXPECT_THAT(model2->nmesh - model1->nmesh, 2);
EXPECT_THAT(model1->npair, model2->npair);
EXPECT_THAT(model1->nsensor, model2->nsensor);
EXPECT_THAT(model1->nwrap, model2->nwrap);
@@ -851,8 +818,8 @@ TEST_F(DiscardVisualTest, DiscardVisualEquivalent) {
EXPECT_STREQ(model1->names + adr1, model2->names + adr2);
}
mjData *d1 = mj_makeData(model1);
mjData *d2 = mj_makeData(model2);
mjData* d1 = mj_makeData(model1);
mjData* d2 = mj_makeData(model2);
for (int i = 0; i < 100; i++) {
mj_step(model1, d1);
mj_step(model2, d2);
@@ -877,8 +844,7 @@ TEST_F(LengthRangeTest, LengthRangeThreading) {
size_t error_sz = 1024;
std::string field = "";
static const char* const kLengthrangePath =
"user/testdata/lengthrange.xml";
static const char* const kLengthrangePath = "user/testdata/lengthrange.xml";
const std::string xml_path1 = GetTestDataFilePath(kLengthrangePath);
mjSpec* spec = mj_parseXML(xml_path1.c_str(), 0, error, error_sz);
@@ -1022,20 +988,17 @@ TEST_F(MujocoTest, NestedMeshDir) {
sizeof(cube));
std::array<char, 1024> error;
mjModel* child_model = LoadModelFromString(child_xml, error.data(),
error.size(), vfs.get());
EXPECT_THAT(child_model, NotNull()) << error.data();
mj_deleteModel(child_model);
MjModelPtr child_model =
LoadModelFromString(child_xml, error.data(), error.size(), vfs.get());
EXPECT_THAT(child_model.get(), NotNull()) << error.data();
mjModel* parent_model = LoadModelFromString(parent_xml, error.data(),
error.size(), vfs.get());
EXPECT_THAT(parent_model, NotNull()) << error.data();
mj_deleteModel(parent_model);
MjModelPtr parent_model =
LoadModelFromString(parent_xml, error.data(), error.size(), vfs.get());
EXPECT_THAT(parent_model.get(), NotNull()) << error.data();
mjModel* grandparent_model = LoadModelFromString(
MjModelPtr grandparent_model = LoadModelFromString(
grandparent_xml, error.data(), error.size(), vfs.get());
EXPECT_THAT(grandparent_model, NotNull()) << error.data();
mj_deleteModel(grandparent_model);
EXPECT_THAT(grandparent_model.get(), NotNull()) << error.data();
mj_deleteVFS(vfs.get());
}
@@ -1087,8 +1050,8 @@ TEST_F(DelayBufferTest, ActuatorDelayBufferSizes) {
</actuator>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
ASSERT_THAT(m, NotNull());
MjModelPtr m = LoadModelFromString(xml);
ASSERT_THAT(m.get(), NotNull());
ASSERT_EQ(m->nu, 3);
// nhistory = (2+2*3) + (2+2*10) = 8 + 22 = 30
@@ -1101,8 +1064,6 @@ TEST_F(DelayBufferTest, ActuatorDelayBufferSizes) {
EXPECT_EQ(m->actuator_historyadr[0], -1);
EXPECT_EQ(m->actuator_historyadr[1], 0);
EXPECT_EQ(m->actuator_historyadr[2], 8);
mj_deleteModel(m);
}
} // namespace
File diff suppressed because it is too large Load Diff