diff --git a/doc/_static/example_saved.txt b/doc/_static/example_saved.txt index 578fad6b..1287d55c 100644 --- a/doc/_static/example_saved.txt +++ b/doc/_static/example_saved.txt @@ -14,7 +14,7 @@ nlight 1 nmesh 0 nmeshvert 0 nmeshface 0 -nmeshtexvert 0 +nmeshtexcoord 0 nmeshgraph 0 nskin 0 nskinvert 0 diff --git a/doc/changelog.rst b/doc/changelog.rst index 1957aa07..a628b448 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -10,6 +10,8 @@ General - The ``mjd_transitionFD`` function no longer triggers sensor calculation unless explicitly requested. - Corrected the spelling of the ``inteval`` attribute to ``interval`` in the ``mjLROpt`` struct. +- Mesh texture and normal mappings are now 3-per-triangle rather than 1-per-vertex. Mesh vertices are no longer + duplicated in order to circumvent this limitation as they previously were. Python bindings ^^^^^^^^^^^^^^^ diff --git a/doc/includes/references.h b/doc/includes/references.h index b7d18a62..a8008935 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -746,7 +746,8 @@ struct mjModel_ { int nlight; // number of lights int nmesh; // number of meshes int nmeshvert; // number of vertices in all meshes - int nmeshtexvert; // number of vertices with texcoords in all meshes + int nmeshnormal; // number of normals in all meshes + int nmeshtexcoord; // number of texcoords in all meshes int nmeshface; // number of triangular faces in all meshes int nmeshgraph; // number of ints in mesh auxiliary data int nskin; // number of skins @@ -942,14 +943,19 @@ struct mjModel_ { // meshes int* mesh_vertadr; // first vertex address (nmesh x 1) int* mesh_vertnum; // number of vertices (nmesh x 1) - int* mesh_texcoordadr; // texcoord data address; -1: no texcoord (nmesh x 1) int* mesh_faceadr; // first face address (nmesh x 1) int* mesh_facenum; // number of faces (nmesh x 1) + int* mesh_normaladr; // first normal address (nmesh x 1) + int* mesh_normalnum; // number of normals (nmesh x 1) + int* mesh_texcoordadr; // texcoord data address; -1: no texcoord (nmesh x 1) + int* mesh_texcoordnum; // number of texcoord (nmesh x 1) int* mesh_graphadr; // graph data address; -1: no graph (nmesh x 1) float* mesh_vert; // vertex positions for all meshes (nmeshvert x 3) - float* mesh_normal; // vertex normals for all meshes (nmeshvert x 3) - float* mesh_texcoord; // vertex texcoords for all meshes (nmeshtexvert x 2) - int* mesh_face; // triangle face data (nmeshface x 3) + float* mesh_normal; // normals for all meshes (nmeshnormal x 3) + float* mesh_texcoord; // vertex texcoords for all meshes (nmeshtexcoord x 2) + int* mesh_face; // vertex face data (nmeshface x 3) + int* mesh_facenormal; // normal face data (nmeshface x 3) + int* mesh_facetexcoord; // texture face data (nmeshface x 3) int* mesh_graph; // convex graph data (nmeshgraph x 1) // skins diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index ba093457..ed3e1055 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -542,7 +542,8 @@ struct mjModel_ { int nlight; // number of lights int nmesh; // number of meshes int nmeshvert; // number of vertices in all meshes - int nmeshtexvert; // number of vertices with texcoords in all meshes + int nmeshnormal; // number of normals in all meshes + int nmeshtexcoord; // number of texcoords in all meshes int nmeshface; // number of triangular faces in all meshes int nmeshgraph; // number of ints in mesh auxiliary data int nskin; // number of skins @@ -738,14 +739,19 @@ struct mjModel_ { // meshes int* mesh_vertadr; // first vertex address (nmesh x 1) int* mesh_vertnum; // number of vertices (nmesh x 1) - int* mesh_texcoordadr; // texcoord data address; -1: no texcoord (nmesh x 1) int* mesh_faceadr; // first face address (nmesh x 1) int* mesh_facenum; // number of faces (nmesh x 1) + int* mesh_normaladr; // first normal address (nmesh x 1) + int* mesh_normalnum; // number of normals (nmesh x 1) + int* mesh_texcoordadr; // texcoord data address; -1: no texcoord (nmesh x 1) + int* mesh_texcoordnum; // number of texcoord (nmesh x 1) int* mesh_graphadr; // graph data address; -1: no graph (nmesh x 1) float* mesh_vert; // vertex positions for all meshes (nmeshvert x 3) - float* mesh_normal; // vertex normals for all meshes (nmeshvert x 3) - float* mesh_texcoord; // vertex texcoords for all meshes (nmeshtexvert x 2) - int* mesh_face; // triangle face data (nmeshface x 3) + float* mesh_normal; // normals for all meshes (nmeshnormal x 3) + float* mesh_texcoord; // vertex texcoords for all meshes (nmeshtexcoord x 2) + int* mesh_face; // vertex face data (nmeshface x 3) + int* mesh_facenormal; // normal face data (nmeshface x 3) + int* mesh_facetexcoord; // texture face data (nmeshface x 3) int* mesh_graph; // convex graph data (nmeshgraph x 1) // skins diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 4812ff0b..c0ce2d2b 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -74,7 +74,8 @@ X( nlight ) \ X( nmesh ) \ X( nmeshvert ) \ - X( nmeshtexvert ) \ + X( nmeshnormal ) \ + X( nmeshtexcoord ) \ X( nmeshface ) \ X( nmeshgraph ) \ X( nskin ) \ @@ -263,14 +264,19 @@ X( float, light_specular, nlight, 3 ) \ X( int, mesh_vertadr, nmesh, 1 ) \ X( int, mesh_vertnum, nmesh, 1 ) \ + X( int, mesh_normaladr, nmesh, 1 ) \ + X( int, mesh_normalnum, nmesh, 1 ) \ X( int, mesh_texcoordadr, nmesh, 1 ) \ + X( int, mesh_texcoordnum, nmesh, 1 ) \ X( int, mesh_faceadr, nmesh, 1 ) \ X( int, mesh_facenum, nmesh, 1 ) \ X( int, mesh_graphadr, nmesh, 1 ) \ X( float, mesh_vert, nmeshvert, 3 ) \ - X( float, mesh_normal, nmeshvert, 3 ) \ - X( float, mesh_texcoord, nmeshtexvert, 2 ) \ + X( float, mesh_normal, nmeshnormal, 3 ) \ + X( float, mesh_texcoord, nmeshtexcoord, 2 ) \ X( int, mesh_face, nmeshface, 3 ) \ + X( int, mesh_facenormal, nmeshface, 3 ) \ + X( int, mesh_facetexcoord, nmeshface, 3 ) \ X( int, mesh_graph, nmeshgraph, 1 ) \ X( int, skin_matid, nskin, 1 ) \ X( int, skin_group, nskin, 1 ) \ diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index 2655cb94..ac2e571c 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -367,8 +367,8 @@ static int safeAddToBufferSize(intptr_t* offset, int* nbuffer, size_t type_size, // allocate and initialize mjModel structure mjModel* mj_makeModel(int nq, int nv, int nu, int na, int nbody, int njnt, int ngeom, int nsite, int ncam, int nlight, - int nmesh, int nmeshvert, int nmeshtexvert, int nmeshface, int nmeshgraph, - int nskin, int nskinvert, int nskintexvert, int nskinface, + int nmesh, int nmeshvert, int nmeshnormal, int nmeshtexcoord, int nmeshface, + int nmeshgraph, int nskin, int nskinvert, int nskintexvert, int nskinface, int nskinbone, int nskinbonevert, int nhfield, int nhfielddata, int ntex, int ntexdata, int nmat, int npair, int nexclude, int neq, int ntendon, int nwrap, int nsensor, @@ -399,7 +399,8 @@ mjModel* mj_makeModel(int nq, int nv, int nu, int na, int nbody, int njnt, m->nlight = nlight; m->nmesh = nmesh; m->nmeshvert = nmeshvert; - m->nmeshtexvert = nmeshtexvert; + m->nmeshnormal = nmeshnormal; + m->nmeshtexcoord = nmeshtexcoord; m->nmeshface = nmeshface; m->nmeshgraph = nmeshgraph; m->nskin = nskin; @@ -512,7 +513,7 @@ mjModel* mj_copyModel(mjModel* dest, const mjModel* src) { if (!dest) { dest = mj_makeModel(src->nq, src->nv, src->nu, src->na, src->nbody, src->njnt, src->ngeom, src->nsite, src->ncam, src->nlight, src->nmesh, src->nmeshvert, - src->nmeshtexvert, src->nmeshface, src->nmeshgraph, + src->nmeshnormal, src->nmeshtexcoord, src->nmeshface, src->nmeshgraph, src->nskin, src->nskinvert, src->nskintexvert, src->nskinface, src->nskinbone, src->nskinbonevert, src->nhfield, src->nhfielddata, src->ntex, src->ntexdata, src->nmat, src->npair, src->nexclude, @@ -691,7 +692,7 @@ mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) { info[28], info[29], info[30], info[31], info[32], info[33], info[34], info[35], info[36], info[37], info[38], info[39], info[40], info[41], info[42], info[43], info[44], info[45], info[46], info[47], info[48], - info[49], info[50]); + info[49], info[50], info[51]); if (!m || m->nbuffer!=info[getnint()-1]) { if (fp) { fclose(fp); @@ -1361,7 +1362,8 @@ const char* mj_validateReferences(const mjModel* m) { X(light_bodyid, nlight, nbody , 0 ) \ X(light_targetbodyid, nlight, nbody , 0 ) \ X(mesh_vertadr, nmesh, nmeshvert , m->mesh_vertnum ) \ - X(mesh_texcoordadr, nmesh, nmeshtexvert , 0 ) \ + X(mesh_normaladr, nmesh, nmeshnormal , m->mesh_normalnum ) \ + X(mesh_texcoordadr, nmesh, nmeshtexcoord, m->mesh_texcoordnum ) \ X(mesh_faceadr, nmesh, nmeshface , m->mesh_facenum ) \ X(mesh_graphadr, nmesh, nmeshgraph , 0 ) \ X(skin_matid, nskin, nmat , 0 ) \ diff --git a/src/engine/engine_io.h b/src/engine/engine_io.h index 5ee036bd..0d83e7ab 100644 --- a/src/engine/engine_io.h +++ b/src/engine/engine_io.h @@ -49,8 +49,8 @@ void mj_defaultStatistic(mjStatistic* stat); // allocate mjModel mjModel* mj_makeModel(int nq, int nv, int nu, int na, int nbody, int njnt, int ngeom, int nsite, int ncam, int nlight, - int nmesh, int nmeshvert, int nmeshtexvert, int nmeshface, int nmeshgraph, - int nskin, int nskinvert, int nskintexvert, int nskinface, + int nmesh, int nmeshvert, int nmeshnormal, int nmeshtexcoord, int nmeshface, + int nmeshgraph, int nskin, int nskinvert, int nskintexvert, int nskinface, int nskinbone, int nskinbonevert, int nhfield, int nhfielddata, int ntex, int ntexdata, int nmat, int npair, int nexclude, int neq, int ntendon, int nwrap, int nsensor, diff --git a/src/render/render_context.c b/src/render/render_context.c index 8b763b7f..ead72db6 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -207,7 +207,7 @@ static void makeMesh(const mjModel* m, mjrContext* con) { // (re) upload mesh to GPU void mjr_uploadMesh(const mjModel* m, const mjrContext* con, int meshid) { - int vertadr, numvert, numface, texcoordadr; + int vertadr, numvert, normaladr, numface, texcoordadr; float normal[3], *v1, *v2, *v3, *n1, *n2, *n3, *t1, *t2, *t3; // check index @@ -220,6 +220,7 @@ void mjr_uploadMesh(const mjModel* m, const mjrContext* con, int meshid) { // get vertex and texcoord address for this mesh vertadr = m->mesh_vertadr[meshid]; + normaladr = m->mesh_normaladr[meshid]; texcoordadr = m->mesh_texcoordadr[meshid]; // render original mesh @@ -234,15 +235,15 @@ void mjr_uploadMesh(const mjModel* m, const mjrContext* con, int meshid) { v3 = m->mesh_vert + 3*(m->mesh_face[3*face+2] + vertadr); // compute normal addresses - n1 = m->mesh_normal + 3*(m->mesh_face[3*face] + vertadr); - n2 = m->mesh_normal + 3*(m->mesh_face[3*face+1] + vertadr); - n3 = m->mesh_normal + 3*(m->mesh_face[3*face+2] + vertadr); + n1 = m->mesh_normal + 3*(m->mesh_facenormal[3*face] + normaladr); + n2 = m->mesh_normal + 3*(m->mesh_facenormal[3*face+1] + normaladr); + n3 = m->mesh_normal + 3*(m->mesh_facenormal[3*face+2] + normaladr); // compute texcoord addresses if (texcoordadr>=0) { - t1 = m->mesh_texcoord + 2*(m->mesh_face[3*face] + texcoordadr); - t2 = m->mesh_texcoord + 2*(m->mesh_face[3*face+1] + texcoordadr); - t3 = m->mesh_texcoord + 2*(m->mesh_face[3*face+2] + texcoordadr); + t1 = m->mesh_texcoord + 2*(m->mesh_facetexcoord[3*face] + texcoordadr); + t2 = m->mesh_texcoord + 2*(m->mesh_facetexcoord[3*face+1] + texcoordadr); + t3 = m->mesh_texcoord + 2*(m->mesh_facetexcoord[3*face+2] + texcoordadr); } else { t1 = t2 = t3 = NULL; } diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index ce9d2f11..0a3038c5 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -95,6 +95,8 @@ mjCMesh::mjCMesh(mjCModel* _model, mjCDef* _def) { usernormal.clear(); usertexcoord.clear(); userface.clear(); + userfacenormal.clear(); + userfacetexcoord.clear(); useredge.clear(); // clear internal variables @@ -106,12 +108,16 @@ mjCMesh::mjCMesh(mjCModel* _model, mjCDef* _def) { mjuu_setvec(boxsz_volume, 0, 0, 0); mjuu_setvec(aabb, 0, 0, 0); nvert = 0; + nnormal = 0; + ntexcoord = 0; nface = 0; szgraph = 0; vert = NULL; normal = NULL; texcoord = NULL; face = NULL; + facenormal = NULL; + facetexcoord = NULL; graph = NULL; needhull = false; invalidorientation.first = -1; @@ -141,17 +147,37 @@ mjCMesh::~mjCMesh() { usernormal.clear(); usertexcoord.clear(); userface.clear(); + userfacenormal.clear(); + userfacetexcoord.clear(); useredge.clear(); if (vert) mju_free(vert); if (normal) mju_free(normal); if (texcoord) mju_free(texcoord); if (face) mju_free(face); + if (facenormal) mju_free(facenormal); + if (facetexcoord) mju_free(facetexcoord); if (graph) mju_free(graph); } +template static T* VecToArray(std::vector& vector, bool clear = true){ + if (vector.empty()) + return nullptr; + else { + int n = (int)vector.size(); + T* cvec = (T*) mju_malloc(n*sizeof(T)); + memcpy(cvec, vector.data(), n*sizeof(T)); + if (clear) { + vector.clear(); + } + return cvec; + } +} + + + // compiler void mjCMesh::Compile(const mjVFS* vfs) { // load file @@ -191,8 +217,7 @@ void mjCMesh::Compile(const mjVFS* vfs) { // copy from user nvert = (int)uservert.size()/3; - vert = (float*) mju_malloc(3*nvert*sizeof(float)); - memcpy(vert, uservert.data(), 3*nvert*sizeof(float)); + vert = VecToArray(uservert, !file.empty()); } // copy user normal @@ -203,13 +228,13 @@ void mjCMesh::Compile(const mjVFS* vfs) { } // check size - if (usernormal.size()!=3*nvert) { - throw mjCError(this, "vertex and normal data incompatible size"); + if (usernormal.size()%3) { + throw mjCError(this, "normal data must be a multiple of 3"); } // copy from user - normal = (float*) mju_malloc(3*nvert*sizeof(float)); - memcpy(normal, usernormal.data(), 3*nvert*sizeof(float)); + nnormal = (int)usernormal.size()/3; + normal = VecToArray(usernormal, !file.empty()); } // copy user texcoord @@ -220,13 +245,13 @@ void mjCMesh::Compile(const mjVFS* vfs) { } // check size - if (usertexcoord.size()!=2*nvert) { - throw mjCError(this, "vertex and texcoord data incompatible size"); + if (usertexcoord.size()%2) { + throw mjCError(this, "texcoord must be a multiple of 2"); } // copy from user - texcoord = (float*) mju_malloc(2*nvert*sizeof(float)); - memcpy(texcoord, usertexcoord.data(), 2*nvert*sizeof(float)); + ntexcoord = (int)usertexcoord.size()/2; + texcoord = VecToArray(usertexcoord, !file.empty()); } // copy user face @@ -243,8 +268,7 @@ void mjCMesh::Compile(const mjVFS* vfs) { // copy from user nface = (int)userface.size()/3; - face = (int*) mju_malloc(3*nface*sizeof(int)); - memcpy(face, userface.data(), 3*nface*sizeof(int)); + face = VecToArray(userface, !file.empty()); // check vertices exist for (auto vertex_index : userface) { @@ -301,6 +325,36 @@ void mjCMesh::Compile(const mjVFS* vfs) { MakeNormal(); } + // copy user normal indices + if (!userfacenormal.empty()) { + // check repeated + if (facenormal) { + throw mjCError(this, "repeated facenormal specification"); + } + + if (userfacenormal.size()!=3*nface) { + throw mjCError(this, "face data must have the same size as face normal data"); + } + + facenormal = VecToArray(userfacenormal, !file.empty()); + } + + // copy user texcoord + if (!userfacetexcoord.empty()) { + // check repeated + if (facetexcoord) { + throw mjCError(this, "repeated facetexcoord specification"); + } + + facetexcoord = VecToArray(userfacetexcoord, !file.empty()); + } + + // facenormal might not exist if usernormal was specified + if (!facenormal) { + facenormal = (int*) mju_malloc(3*nface*sizeof(int)); + memcpy(facenormal, face, 3*nface*sizeof(int)); + } + // scale, center, orient, compute mass and inertia Process(); processed = true; @@ -543,19 +597,6 @@ void mjCMesh::RemoveRepeated() { } -template static T* VecToArray(std::vector& vector){ - if (vector.empty()) - return nullptr; - else { - int n = (int)vector.size(); - T* cvec = (T*) mju_malloc(n*sizeof(T)); - memcpy(cvec, vector.data(), n*sizeof(T)); - vector.clear(); - return cvec; - } -} - - // load OBJ mesh void mjCMesh::LoadOBJ(const mjVFS* vfs) { @@ -587,105 +628,73 @@ void mjCMesh::LoadOBJ(const mjVFS* vfs) { throw mjCError(this, "%s", msg.str().c_str()); } - auto attrib = objReader.GetAttrib(); + const auto& attrib = objReader.GetAttrib(); + uservert = attrib.vertices; // copy from one std::vector to another + usernormal = attrib.normals; + usertexcoord = attrib.texcoords; - if (objReader.GetShapes().empty()) { - uservert = attrib.vertices; // copy from one std::vector to another - usernormal = attrib.normals; - usertexcoord = attrib.texcoords; - } else { - auto mesh = objReader.GetShapes()[0].mesh; - bool has_normals = !attrib.normals.empty(); - bool has_texcoords = !attrib.texcoords.empty(); + if (!objReader.GetShapes().empty()) { + const auto& mesh = objReader.GetShapes()[0].mesh; bool righthand = (scale[0]*scale[1]*scale[2] > 0); // iterate over mesh faces - int index_in_mesh_indices = 0; - for (int face = 0; face < mesh.num_face_vertices.size(); face++) { - if (mesh.num_face_vertices[face] > 4) { + std::vector face_indices; + for (int face = 0, idx = 0; idx < mesh.indices.size();) { + int nfacevert = mesh.num_face_vertices[face]; + if (nfacevert < 3 || nfacevert > 4) { throw mjCError( this, "only tri or quad meshes are supported for OBJ (file '%s')", filename.c_str()); } - // add face - std::vector> faces; - tinyobj::index_t v0 = mesh.indices[index_in_mesh_indices]; - tinyobj::index_t v1 = mesh.indices[index_in_mesh_indices+1]; - tinyobj::index_t v2 = mesh.indices[index_in_mesh_indices+2]; - std::array face1 = {v0, v1, v2}; - faces.push_back(face1); + face_indices.push_back(mesh.indices[idx]); + face_indices.push_back(mesh.indices[idx + (righthand==1 ? 1 : 2)]); + face_indices.push_back(mesh.indices[idx + (righthand==1 ? 2 : 1)]); - // handle quad: add second triangle with 4th vertex - if (mesh.num_face_vertices[face] == 4) { - tinyobj::index_t v3 = mesh.indices[index_in_mesh_indices+3]; - std::array face2 = {v0, v2, v3}; - faces.push_back(face2); + if (nfacevert == 4) { + face_indices.push_back(mesh.indices[idx]); + face_indices.push_back(mesh.indices[idx + (righthand==1 ? 2 : 3)]); + face_indices.push_back(mesh.indices[idx + (righthand==1 ? 3 : 2)]); } - for (const auto& face_indices : faces) { - int index_of_first_vertex = uservert.size()/3; - for (auto tinyobj_index : face_indices) { - // add vertices to uservert - uservert.insert( - uservert.end(), - attrib.vertices.begin() + 3*tinyobj_index.vertex_index, - attrib.vertices.begin() + 3*tinyobj_index.vertex_index + 3); + idx += nfacevert; + ++face; + } - // for each vertex, add its normal - if (has_normals) { - usernormal.insert( - usernormal.end(), - attrib.normals.begin() + 3*tinyobj_index.normal_index, - attrib.normals.begin() + 3*tinyobj_index.normal_index + 3); - } + // for each vertex, store index, normal, and texcoord + for (const auto& mesh_index : face_indices) { + userface.push_back(mesh_index.vertex_index); - // for each vertex, add two entries to usertexcoord - if (has_texcoords) { - usertexcoord.push_back( - attrib.texcoords[2*tinyobj_index.texcoord_index]); - usertexcoord.push_back( // flip the v coordinate - 1-attrib.texcoords[2*tinyobj_index.texcoord_index + 1]); - } - } - int i0 = index_of_first_vertex; - int i1 = index_of_first_vertex+1; - int i2 = 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 { - // TODO(b/255525326) - } - - // 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); - } + if (!usernormal.empty()) { + userfacenormal.push_back(mesh_index.normal_index); + } + + if (!usertexcoord.empty()) { + userfacetexcoord.push_back(mesh_index.texcoord_index); + } + } + + for (int i = 0; i < face_indices.size(); i += 3) { + // add edges + const float *v0 = uservert.data() + 3*face_indices[i+0].vertex_index; + const float *v1 = uservert.data() + 3*face_indices[i+1].vertex_index; + const float *v2 = uservert.data() + 3*face_indices[i+2].vertex_index; + + // only consider edges if the face contribution is significant + mjtNum normal[3]; + if (_triangle(normal, nullptr, v0, v1, v2)>sqrt(mjMINVAL)) { + useredge.push_back(std::pair(face_indices[i+0].vertex_index, face_indices[i+1].vertex_index)); + useredge.push_back(std::pair(face_indices[i+1].vertex_index, face_indices[i+2].vertex_index)); + useredge.push_back(std::pair(face_indices[i+2].vertex_index, face_indices[i+0].vertex_index)); + } else { + // TODO(b/255525326) } - index_in_mesh_indices += mesh.num_face_vertices[face]; } } - nvert = (int)uservert.size()/3; - nface = (int)userface.size()/3; - vert = VecToArray(uservert); - face = VecToArray(userface); - normal = VecToArray(usernormal); - texcoord = VecToArray(usertexcoord); + // flip the second texcoord + for (int i=1; invert; + nmeshnormal += meshes[i]->nnormal; nmeshface += meshes[i]->nface; - nmeshtexvert += (meshes[i]->texcoord ? meshes[i]->nvert : 0); + nmeshtexcoord += (meshes[i]->texcoord ? meshes[i]->ntexcoord : 0); nmeshgraph += meshes[i]->szgraph; } @@ -1645,7 +1647,7 @@ void mjCModel::CopyTree(mjModel* m) { // copy objects outside kinematic tree void mjCModel::CopyObjects(mjModel* m) { - int i, j, adr, bone_adr, vert_adr, face_adr, texcoord_adr; + int i, j, adr, bone_adr, vert_adr, normal_adr, face_adr, texcoord_adr; int bonevert_adr, graph_adr, data_adr; // sizes outside call to mj_makeModel @@ -1657,6 +1659,7 @@ void mjCModel::CopyObjects(mjModel* m) { // meshes vert_adr = 0; + normal_adr = 0; texcoord_adr = 0; face_adr = 0; graph_adr = 0; @@ -1667,17 +1670,24 @@ void mjCModel::CopyObjects(mjModel* m) { // set fields m->mesh_vertadr[i] = vert_adr; m->mesh_vertnum[i] = pme->nvert; + m->mesh_normaladr[i] = normal_adr; + m->mesh_normalnum[i] = pme->nnormal; m->mesh_texcoordadr[i] = (pme->texcoord ? texcoord_adr : -1); + m->mesh_texcoordnum[i] = pme->ntexcoord; m->mesh_faceadr[i] = face_adr; m->mesh_facenum[i] = pme->nface; m->mesh_graphadr[i] = (pme->szgraph ? graph_adr : -1); // copy vertices, normals, faces, texcoords, aux data memcpy(m->mesh_vert + 3*vert_adr, pme->vert, 3*pme->nvert*sizeof(float)); - memcpy(m->mesh_normal + 3*vert_adr, pme->normal, 3*pme->nvert*sizeof(float)); + memcpy(m->mesh_normal + 3*normal_adr, pme->normal, 3*pme->nnormal*sizeof(float)); memcpy(m->mesh_face + 3*face_adr, pme->face, 3*pme->nface*sizeof(float)); + memcpy(m->mesh_facenormal + 3*face_adr, pme->facenormal, 3*pme->nface*sizeof(int)); if (pme->texcoord) { - memcpy(m->mesh_texcoord + 2*texcoord_adr, pme->texcoord, 2*pme->nvert*sizeof(float)); + memcpy(m->mesh_texcoord + 2*texcoord_adr, pme->texcoord, 2*pme->ntexcoord*sizeof(float)); + memcpy(m->mesh_facetexcoord + 3*face_adr, pme->facetexcoord, 3*pme->nface*sizeof(int)); + } else { + memset(m->mesh_facetexcoord + 3*face_adr, 0, 3*pme->nface*sizeof(int)); } if (pme->szgraph) { memcpy(m->mesh_graph + graph_adr, pme->graph, pme->szgraph*sizeof(int)); @@ -1685,7 +1695,8 @@ void mjCModel::CopyObjects(mjModel* m) { // advance counters vert_adr += pme->nvert; - texcoord_adr += (pme->texcoord ? pme->nvert : 0); + normal_adr += pme->nnormal; + texcoord_adr += (pme->texcoord ? pme->ntexcoord : 0); face_adr += pme->nface; graph_adr += pme->szgraph; } @@ -2619,7 +2630,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { // create low-level model m = mj_makeModel(nq, nv, nu, na, nbody, njnt, ngeom, nsite, ncam, nlight, - nmesh, nmeshvert, nmeshtexvert, nmeshface, nmeshgraph, + nmesh, nmeshvert, nmeshnormal, nmeshtexcoord, nmeshface, nmeshgraph, nskin, nskinvert, nskintexvert, nskinface, nskinbone, nskinbonevert, nhfield, nhfielddata, ntex, ntexdata, nmat, npair, nexclude, neq, ntendon, nwrap, nsensor, diff --git a/src/user/user_model.h b/src/user/user_model.h index 6eb6c2a5..7d2b44fc 100644 --- a/src/user/user_model.h +++ b/src/user/user_model.h @@ -216,7 +216,8 @@ class mjCModel { int nu; // number of actuators/controls int na; // number of activation variables int nmeshvert; // number of vertices in all meshes - int nmeshtexvert; // number of texture coordinates in all meshes + int nmeshnormal; // number of normals in all meshes + int nmeshtexcoord; // number of texture coordinates in all meshes int nmeshface; // number of triangular faces in all meshes int nmeshgraph; // number of shorts in mesh auxiliary data int nskinvert; // number of vertices in all skins diff --git a/src/user/user_objects.h b/src/user/user_objects.h index f04e9922..9e2fd391 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -476,7 +476,9 @@ class mjCMesh: public mjCBase { std::vector uservert; // user vertex data std::vector usernormal; // user normal data std::vector usertexcoord; // user texcoord data - std::vector userface; // user face data + std::vector userface; // user vertex indices + std::vector userfacenormal; // user normal indices + std::vector userfacetexcoord; // user texcoord indices std::vector< std::pair > useredge; // user half-edge data private: @@ -514,12 +516,16 @@ class mjCMesh: public mjCBase { // mesh data to be copied into mjModel int nvert; // number of vertices + int nnormal; // number of normals + int ntexcoord; // number of texcoords int nface; // number of faces int szgraph; // size of graph data in ints float* vert; // vertex data (3*nvert), relative to (pos, quat) - float* normal; // vertex normal data (3*nvert) - float* texcoord; // vertex texcoord data (2*nvert, or NULL) + float* normal; // vertex normal data (3*nnormal) + float* texcoord; // vertex texcoord data (2*ntexcoord or NULL) int* face; // face vertex indices (3*nface) + int* facenormal; // face normal indices (3*nface) + int* facetexcoord; // face texcoord indices (3*nface) int* graph; // convex graph data bool needhull; // needs convex hull for collisions diff --git a/test/user/testdata/cube.msh b/test/user/testdata/cube.msh new file mode 100644 index 00000000..0a2f278f Binary files /dev/null and b/test/user/testdata/cube.msh differ diff --git a/test/user/testdata/mesh.xml b/test/user/testdata/mesh.xml new file mode 100644 index 00000000..03a31d9d --- /dev/null +++ b/test/user/testdata/mesh.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/user/user_mesh_test.cc b/test/user/user_mesh_test.cc index b91be472..902fb7fa 100644 --- a/test/user/user_mesh_test.cc +++ b/test/user/user_mesh_test.cc @@ -32,6 +32,8 @@ namespace { using MjCMeshTest = MujocoTest; +static const char* const kMeshPath = + "user/testdata/mesh.xml"; static const char* const kDuplicateVerticesPath = "user/testdata/duplicate_vertices.xml"; static const char* const kCubePath = @@ -100,6 +102,17 @@ TEST_F(MjCMeshTest, DeDuplicateSTLVertices) { mj_deleteModel(model); } +// ------------- test Mesh loading (MSH) ------------------------------ +TEST_F(MjCMeshTest, LoadMSH) { + const std::string xml_path = GetTestDataFilePath(kMeshPath); + char error[1024]; + size_t error_sz = 1024; + mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, error_sz); + ASSERT_THAT(model, testing::NotNull()) << error; + ASSERT_EQ(model->nmeshvert, 36); + mj_deleteModel(model); +} + // ------------- test OBJ loading ---------------------------------------------- using MjCMeshTest = MujocoTest; @@ -109,6 +122,7 @@ TEST_F(MjCMeshTest, LoadCube) { mjModel* model = mj_loadXML(xml_path.c_str(), 0, nullptr, 0); ASSERT_GT(model->ngeom, 0); ASSERT_EQ(model->nmeshvert, 8); + ASSERT_EQ(model->nmeshface, 12); mj_deleteModel(model); } @@ -146,7 +160,7 @@ TEST_F(MjCMeshTest, KeepDuplicateOBJVertices) { char error[1024]; size_t error_sz = 1024; mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, error_sz); - ASSERT_EQ(model->nmeshvert, 12); + ASSERT_EQ(model->nmeshvert, 16); mj_deleteModel(model); } @@ -175,6 +189,26 @@ TEST_F(MjCMeshTest, TinyMeshLoads) { mj_deleteModel(model); } +// ------------- test inline loading ------------------------------------------ +TEST_F(MjCMeshTest, FaceNormalAutogenerated) { + static constexpr char xml[] = R"( + + + + + + + + + )"; + mjModel* model = LoadModelFromString(xml, 0, 0); + ASSERT_THAT(model, testing::NotNull()); + mj_deleteModel(model); +} + // ------------- test inertia ------------------------------------------------- TEST_F(MjCMeshTest, SmallInertiaLoads) { diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 18c6f0c9..220129a5 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -1874,7 +1874,8 @@ public unsafe struct mjModel_ { public int nlight; public int nmesh; public int nmeshvert; - public int nmeshtexvert; + public int nmeshnormal; + public int nmeshtexcoord; public int nmeshface; public int nmeshgraph; public int nskin; @@ -2042,14 +2043,19 @@ public unsafe struct mjModel_ { public float* light_specular; public int* mesh_vertadr; public int* mesh_vertnum; - public int* mesh_texcoordadr; public int* mesh_faceadr; public int* mesh_facenum; + public int* mesh_normaladr; + public int* mesh_normalnum; + public int* mesh_texcoordadr; + public int* mesh_texcoordnum; public int* mesh_graphadr; public float* mesh_vert; public float* mesh_normal; public float* mesh_texcoord; public int* mesh_face; + public int* mesh_facenormal; + public int* mesh_facetexcoord; public int* mesh_graph; public int* skin_matid; public int* skin_group;