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
+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