Do not raise an error for flipped triangles if their area contribution is negligible.
PiperOrigin-RevId: 474552367 Change-Id: I9727a74e50a9bab9b04bb0de0b64e11587ed36f1
This commit is contained in:
committed by
Copybara-Service
parent
c8517e63a4
commit
0e05e7ce52
+81
-55
@@ -48,6 +48,38 @@ extern "C" {
|
|||||||
using std::string;
|
using std::string;
|
||||||
using std::vector;
|
using std::vector;
|
||||||
|
|
||||||
|
// compute triangle area, surface normal, center
|
||||||
|
static mjtNum _triangle(mjtNum* normal, mjtNum* center,
|
||||||
|
const float* v1, const float* v2, const float* v3) {
|
||||||
|
// center
|
||||||
|
if (center) {
|
||||||
|
for (int i=0; i<3; i++) {
|
||||||
|
center[i] = (v1[i] + v2[i] + v3[i])/3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// normal = (v2-v1) cross (v3-v1)
|
||||||
|
double b[3] = { v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2] };
|
||||||
|
double c[3] = { v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2] };
|
||||||
|
mju_cross(normal, b, c);
|
||||||
|
|
||||||
|
// get length
|
||||||
|
double len = mju_norm3(normal);
|
||||||
|
|
||||||
|
// ignore small faces
|
||||||
|
if (len<mjMINVAL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize
|
||||||
|
normal[0] /= len;
|
||||||
|
normal[1] /= len;
|
||||||
|
normal[2] /= len;
|
||||||
|
|
||||||
|
// return area
|
||||||
|
return len/2;
|
||||||
|
}
|
||||||
|
|
||||||
//------------------ class mjCMesh implementation --------------------------------------------------
|
//------------------ class mjCMesh implementation --------------------------------------------------
|
||||||
|
|
||||||
// constructor
|
// constructor
|
||||||
@@ -212,15 +244,27 @@ void mjCMesh::Compile(const mjVFS* vfs) {
|
|||||||
face = (int*) mju_malloc(3*nface*sizeof(int));
|
face = (int*) mju_malloc(3*nface*sizeof(int));
|
||||||
memcpy(face, userface.data(), 3*nface*sizeof(int));
|
memcpy(face, userface.data(), 3*nface*sizeof(int));
|
||||||
|
|
||||||
|
// check vertices exist
|
||||||
|
for (auto vertex_index : userface) {
|
||||||
|
if (vertex_index>=nvert || vertex_index < 0) {
|
||||||
|
throw mjCError(this, "found index in userface that exceeds uservert size.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// create half-edge structure (if mesh was in XML)
|
// create half-edge structure (if mesh was in XML)
|
||||||
if (useredge.empty()) {
|
if (useredge.empty()) {
|
||||||
for (int i=0; i<nface; i++) {
|
for (int i=0; i<nface; i++) {
|
||||||
int v0 = userface[3*i+0];
|
int v0 = userface[3*i+0];
|
||||||
int v1 = userface[3*i+1];
|
int v1 = userface[3*i+1];
|
||||||
int v2 = userface[3*i+2];
|
int v2 = userface[3*i+2];
|
||||||
useredge.push_back(std::pair(v0, v1));
|
mjtNum normal[3];
|
||||||
useredge.push_back(std::pair(v1, v2));
|
if (_triangle(normal, nullptr, vert+3*v0, vert+3*v1, vert+3*v2)>sqrt(mjMINVAL)) {
|
||||||
useredge.push_back(std::pair(v2, v0));
|
useredge.push_back(std::pair(v0, v1));
|
||||||
|
useredge.push_back(std::pair(v1, v2));
|
||||||
|
useredge.push_back(std::pair(v2, v0));
|
||||||
|
} else {
|
||||||
|
mju_warning("Degenerate triangle found, its orientation will not be checked.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -554,6 +598,7 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
|||||||
bool has_normals = !attrib.normals.empty();
|
bool has_normals = !attrib.normals.empty();
|
||||||
bool has_texcoords = !attrib.texcoords.empty();
|
bool has_texcoords = !attrib.texcoords.empty();
|
||||||
bool righthand = (scale[0]*scale[1]*scale[2] > 0);
|
bool righthand = (scale[0]*scale[1]*scale[2] > 0);
|
||||||
|
|
||||||
// iterate over mesh faces
|
// iterate over mesh faces
|
||||||
int index_in_mesh_indices = 0;
|
int index_in_mesh_indices = 0;
|
||||||
for (int face = 0; face < mesh.num_face_vertices.size(); face++) {
|
for (int face = 0; face < mesh.num_face_vertices.size(); face++) {
|
||||||
@@ -562,6 +607,7 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
|||||||
this, "only tri or quad meshes are supported for OBJ (file '%s')",
|
this, "only tri or quad meshes are supported for OBJ (file '%s')",
|
||||||
filename.c_str());
|
filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// add face
|
// add face
|
||||||
std::vector<std::array<tinyobj::index_t, 3>> faces;
|
std::vector<std::array<tinyobj::index_t, 3>> faces;
|
||||||
tinyobj::index_t v0 = mesh.indices[index_in_mesh_indices];
|
tinyobj::index_t v0 = mesh.indices[index_in_mesh_indices];
|
||||||
@@ -569,18 +615,12 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
|||||||
tinyobj::index_t v2 = mesh.indices[index_in_mesh_indices+2];
|
tinyobj::index_t v2 = mesh.indices[index_in_mesh_indices+2];
|
||||||
std::array<tinyobj::index_t, 3> face1 = {v0, v1, v2};
|
std::array<tinyobj::index_t, 3> face1 = {v0, v1, v2};
|
||||||
faces.push_back(face1);
|
faces.push_back(face1);
|
||||||
// add edges
|
|
||||||
useredge.push_back(std::pair(v0.vertex_index, v1.vertex_index));
|
|
||||||
useredge.push_back(std::pair(v1.vertex_index, v2.vertex_index));
|
|
||||||
useredge.push_back(std::pair(v2.vertex_index, v0.vertex_index));
|
|
||||||
// handle quad: add second triangle with 4th vertex
|
// handle quad: add second triangle with 4th vertex
|
||||||
if (mesh.num_face_vertices[face] == 4) {
|
if (mesh.num_face_vertices[face] == 4) {
|
||||||
tinyobj::index_t v3 = mesh.indices[index_in_mesh_indices+3];
|
tinyobj::index_t v3 = mesh.indices[index_in_mesh_indices+3];
|
||||||
std::array<tinyobj::index_t, 3> face2 = {v0, v2, v3};
|
std::array<tinyobj::index_t, 3> face2 = {v0, v2, v3};
|
||||||
faces.push_back(face2);
|
faces.push_back(face2);
|
||||||
useredge.push_back(std::pair(v0.vertex_index, v2.vertex_index));
|
|
||||||
useredge.push_back(std::pair(v2.vertex_index, v3.vertex_index));
|
|
||||||
useredge.push_back(std::pair(v3.vertex_index, v0.vertex_index));
|
|
||||||
}
|
}
|
||||||
for (const auto& face_indices : faces) {
|
for (const auto& face_indices : faces) {
|
||||||
int index_of_first_vertex = uservert.size()/3;
|
int index_of_first_vertex = uservert.size()/3;
|
||||||
@@ -590,6 +630,7 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
|||||||
uservert.end(),
|
uservert.end(),
|
||||||
attrib.vertices.begin() + 3*tinyobj_index.vertex_index,
|
attrib.vertices.begin() + 3*tinyobj_index.vertex_index,
|
||||||
attrib.vertices.begin() + 3*tinyobj_index.vertex_index + 3);
|
attrib.vertices.begin() + 3*tinyobj_index.vertex_index + 3);
|
||||||
|
|
||||||
// for each vertex, add its normal
|
// for each vertex, add its normal
|
||||||
if (has_normals) {
|
if (has_normals) {
|
||||||
usernormal.insert(
|
usernormal.insert(
|
||||||
@@ -597,6 +638,7 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
|||||||
attrib.normals.begin() + 3*tinyobj_index.normal_index,
|
attrib.normals.begin() + 3*tinyobj_index.normal_index,
|
||||||
attrib.normals.begin() + 3*tinyobj_index.normal_index + 3);
|
attrib.normals.begin() + 3*tinyobj_index.normal_index + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for each vertex, add two entries to usertexcoord
|
// for each vertex, add two entries to usertexcoord
|
||||||
if (has_texcoords) {
|
if (has_texcoords) {
|
||||||
usertexcoord.push_back(
|
usertexcoord.push_back(
|
||||||
@@ -605,14 +647,33 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) {
|
|||||||
1-attrib.texcoords[2*tinyobj_index.texcoord_index + 1]);
|
1-attrib.texcoords[2*tinyobj_index.texcoord_index + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add vertex indices (in uservert) to userface
|
int i0 = index_of_first_vertex;
|
||||||
userface.push_back(index_of_first_vertex);
|
int i1 = index_of_first_vertex+1;
|
||||||
if (righthand) {
|
int i2 = index_of_first_vertex+2;
|
||||||
userface.push_back(index_of_first_vertex+1);
|
|
||||||
userface.push_back(index_of_first_vertex+2);
|
// add edges
|
||||||
|
const float *v0 = uservert.data() + 3*i0;
|
||||||
|
const float *v1 = uservert.data() + 3*i1;
|
||||||
|
const float *v2 = uservert.data() + 3*i2;
|
||||||
|
mjtNum normal[3];
|
||||||
|
|
||||||
|
// only consider edges if the face contribution is significant
|
||||||
|
if (_triangle(normal, nullptr, v0, v1, v2)>sqrt(mjMINVAL)) {
|
||||||
|
useredge.push_back(std::pair(face_indices[0].vertex_index, face_indices[1].vertex_index));
|
||||||
|
useredge.push_back(std::pair(face_indices[1].vertex_index, face_indices[2].vertex_index));
|
||||||
|
useredge.push_back(std::pair(face_indices[2].vertex_index, face_indices[0].vertex_index));
|
||||||
} else {
|
} else {
|
||||||
userface.push_back(index_of_first_vertex+2);
|
mju_warning("Degenerate triangle found, its orientation will not be checked.");
|
||||||
userface.push_back(index_of_first_vertex+1);
|
}
|
||||||
|
|
||||||
|
// add vertex indices (in uservert) to userface
|
||||||
|
userface.push_back(i0);
|
||||||
|
if (righthand) {
|
||||||
|
userface.push_back(i1);
|
||||||
|
userface.push_back(i2);
|
||||||
|
} else {
|
||||||
|
userface.push_back(i2);
|
||||||
|
userface.push_back(i1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index_in_mesh_indices += mesh.num_face_vertices[face];
|
index_in_mesh_indices += mesh.num_face_vertices[face];
|
||||||
@@ -859,41 +920,6 @@ void mjCMesh::LoadMSH(const mjVFS* vfs) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// compute triangle area, surface normal, center
|
|
||||||
static double _areaNrmCen(double* normal, double* center,
|
|
||||||
const float* v1, const float* v2, const float* v3) {
|
|
||||||
// center
|
|
||||||
if (center) {
|
|
||||||
for (int i=0; i<3; i++) {
|
|
||||||
center[i] = (v1[i] + v2[i] + v3[i])/3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// normal = (v2-v1) cross (v3-v1)
|
|
||||||
double b[3] = { v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2] };
|
|
||||||
double c[3] = { v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2] };
|
|
||||||
normal[0] = b[1]*c[2] - b[2]*c[1];
|
|
||||||
normal[1] = b[2]*c[0] - b[0]*c[2];
|
|
||||||
normal[2] = b[0]*c[1] - b[1]*c[0];
|
|
||||||
|
|
||||||
// get length
|
|
||||||
double len = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);
|
|
||||||
|
|
||||||
// ignore small faces
|
|
||||||
if (len<mjMINVAL) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize
|
|
||||||
normal[0] /= len;
|
|
||||||
normal[1] /= len;
|
|
||||||
normal[2] /= len;
|
|
||||||
|
|
||||||
// return area
|
|
||||||
return len/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// apply transformations
|
// apply transformations
|
||||||
void mjCMesh::Process() {
|
void mjCMesh::Process() {
|
||||||
for ( const auto type : { mjtMeshType::mjVOLUME_MESH, mjtMeshType::mjSHELL_MESH } ) {
|
for ( const auto type : { mjtMeshType::mjVOLUME_MESH, mjtMeshType::mjSHELL_MESH } ) {
|
||||||
@@ -992,7 +1018,7 @@ void mjCMesh::Process() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get area and center
|
// get area and center
|
||||||
double a = _areaNrmCen(nrm, cen, vert+3*face[3*i], vert+3*face[3*i+1], vert+3*face[3*i+2]);
|
double a = _triangle(nrm, cen, vert+3*face[3*i], vert+3*face[3*i+1], vert+3*face[3*i+2]);
|
||||||
|
|
||||||
// accumulate
|
// accumulate
|
||||||
for (j=0; j<3; j++) {
|
for (j=0; j<3; j++) {
|
||||||
@@ -1017,7 +1043,7 @@ void mjCMesh::Process() {
|
|||||||
GetVolumeRef(type) = 0;
|
GetVolumeRef(type) = 0;
|
||||||
for (i=0; i<nface; i++) {
|
for (i=0; i<nface; i++) {
|
||||||
// get area, normal and center
|
// get area, normal and center
|
||||||
double a = _areaNrmCen(nrm, cen, vert+3*face[3*i], vert+3*face[3*i+1], vert+3*face[3*i+2]);
|
double a = _triangle(nrm, cen, vert+3*face[3*i], vert+3*face[3*i+1], vert+3*face[3*i+2]);
|
||||||
|
|
||||||
// compute and add volume
|
// compute and add volume
|
||||||
const double vec[3] = {cen[0]-facecen[0], cen[1]-facecen[1], cen[2]-facecen[2]};
|
const double vec[3] = {cen[0]-facecen[0], cen[1]-facecen[1], cen[2]-facecen[2]};
|
||||||
@@ -1066,7 +1092,7 @@ void mjCMesh::Process() {
|
|||||||
float* F = vert+3*face[3*i+2];
|
float* F = vert+3*face[3*i+2];
|
||||||
|
|
||||||
// get area, normal and center; update volume
|
// get area, normal and center; update volume
|
||||||
double a = _areaNrmCen(nrm, cen, D, E, F);
|
double a = _triangle(nrm, cen, D, E, F);
|
||||||
double vol = type==mjSHELL_MESH ? a : mjuu_dot3(cen, nrm) * a / 3;
|
double vol = type==mjSHELL_MESH ? a : mjuu_dot3(cen, nrm) * a / 3;
|
||||||
|
|
||||||
// if legacy computation requested, then always positive
|
// if legacy computation requested, then always positive
|
||||||
|
|||||||
@@ -288,6 +288,28 @@ TEST_F(MujocoTest, FlippedFaceAllowedInertial) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, FlippedFaceAllowedNegligibleArea) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<asset>
|
||||||
|
<mesh name="example_mesh"
|
||||||
|
vertex="0 0 0 1 0 0 0 1 0 0 0 1 0 0 1"
|
||||||
|
face="2 0 3 0 1 3 1 2 3 0 2 1 0 3 4" />
|
||||||
|
</asset>
|
||||||
|
<worldbody>
|
||||||
|
<body>
|
||||||
|
<geom type="mesh" mesh="example_mesh"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
EXPECT_THAT(model, testing::NotNull());
|
||||||
|
CheckTetrahedronWasRescaled(model);
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(MujocoTest, AreaTooSmall) {
|
TEST_F(MujocoTest, AreaTooSmall) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
|
|||||||
Reference in New Issue
Block a user