// Copyright 2021 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #define TINYOBJLOADER_IMPLEMENTATION #include #include "cc/array_safety.h" #include "engine/engine_crossplatform.h" #include "engine/engine_file.h" #include "engine/engine_macro.h" #include "engine/engine_util_blas.h" #include "engine/engine_util_errmem.h" #include "engine/engine_util_solve.h" #include "engine/engine_util_spatial.h" #include "engine/engine_vfs.h" #include "user/user_model.h" #include "user/user_objects.h" #include "user/user_util.h" #include extern "C" { #include "qhull_ra.h" } using std::string; 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 (lenmesh; } // set model, def model = _model; def = (_def ? _def : (_model ? _model->defaults[0] : 0)); } // destructor mjCMesh::~mjCMesh() { file.clear(); uservert.clear(); 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 if (!file.empty()) { // remove path from file if necessary if (model->strippath) { file = mjuu_strippath(file); } // load STL, OBJ or MSH string ext = mjuu_getext(file); if (!strcasecmp(ext.c_str(), ".stl")) { LoadSTL(vfs); } else if (!strcasecmp(ext.c_str(), ".obj")) { LoadOBJ(vfs); } else if (!strcasecmp(ext.c_str(), ".msh")) { LoadMSH(vfs); } else { throw mjCError(this, "Unknown mesh file type: %s", file.c_str()); } } // copy user vertex if (!uservert.empty()) { // check repeated if (vert) { throw mjCError(this, "repeated vertex specification"); } // check size if (uservert.size()<12) { throw mjCError(this, "at least 4 verices required"); } if (uservert.size()%3) { throw mjCError(this, "vertex data must be a multiple of 3"); } // copy from user nvert = (int)uservert.size()/3; vert = VecToArray(uservert, !file.empty()); } // copy user normal if (!usernormal.empty()) { // check repeated if (normal) { throw mjCError(this, "repeated normal specification"); } // check size if (usernormal.size()%3) { throw mjCError(this, "normal data must be a multiple of 3"); } // copy from user nnormal = (int)usernormal.size()/3; normal = VecToArray(usernormal, !file.empty()); } // copy user texcoord if (!usertexcoord.empty()) { // check repeated if (texcoord) { throw mjCError(this, "repeated texcoord specification"); } // check size if (usertexcoord.size()%2) { throw mjCError(this, "texcoord must be a multiple of 2"); } // copy from user ntexcoord = (int)usertexcoord.size()/2; texcoord = VecToArray(usertexcoord, !file.empty()); } // copy user face if (!userface.empty()) { // check repeated if (face) { throw mjCError(this, "repeated face specification"); } // check size if (userface.size()%3) { throw mjCError(this, "face data must be a multiple of 3"); } // copy from user nface = (int)userface.size()/3; face = VecToArray(userface, !file.empty()); // 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) if (useredge.empty()) { for (int i=0; isqrt(mjMINVAL)) { useredge.push_back(std::pair(v0, v1)); useredge.push_back(std::pair(v1, v2)); useredge.push_back(std::pair(v2, v0)); } else { // TODO(b/255525326) } } } } // check for inconsistent face orientations if (!useredge.empty()) { std::sort(useredge.begin(), useredge.end()); auto iterator = std::adjacent_find(useredge.begin(), useredge.end()); if (iterator != useredge.end()) { invalidorientation.first = iterator->first+1; invalidorientation.second = iterator->second+1; } } // require vertices if (!vert) { throw mjCError(this, "no vertices"); } // make graph describing convex hull if ((model->convexhull && needhull) || !face) { MakeGraph(); } // no faces: copy from convex hull if (!face) { CopyGraph(); } // no normals: make if (!normal) { 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; } // get position double* mjCMesh::GetPosPtr(mjtMeshType type) { if (type==mjSHELL_MESH) { return pos_surface; } else { return pos_volume; } } // get orientation double* mjCMesh::GetQuatPtr(mjtMeshType type) { if (type==mjSHELL_MESH) { return quat_surface; } else { return quat_volume; } } // set geom size to match mesh void mjCMesh::FitGeom(mjCGeom* geom, double* meshpos) { // copy mesh pos into meshpos mjuu_copyvec(meshpos, GetPosPtr(geom->typeinertia), 3); // use inertial box if (!model->fitaabb) { // get inertia box type (shell or volume) double* boxsz = GetInertiaBoxPtr(geom->typeinertia); switch (geom->type) { case mjGEOM_SPHERE: geom->size[0] = (boxsz[0] + boxsz[1] + boxsz[2])/3; break; case mjGEOM_CAPSULE: geom->size[0] = (boxsz[0] + boxsz[1])/2; geom->size[1] = mjMAX(0, boxsz[2] - geom->size[0]/2); break; case mjGEOM_CYLINDER: geom->size[0] = (boxsz[0] + boxsz[1])/2; geom->size[1] = boxsz[2]; break; case mjGEOM_ELLIPSOID: case mjGEOM_BOX: geom->size[0] = boxsz[0]; geom->size[1] = boxsz[1]; geom->size[2] = boxsz[2]; break; default: throw mjCError(this, "invalid geom type in fitting mesh %s", name.c_str()); } } // use aabb else { // find aabb box center double cen[3] = {(aabb[0]+aabb[3])/2, (aabb[1]+aabb[4])/2, (aabb[2]+aabb[5])/2}; // add box center into meshpos meshpos[0] += cen[0]; meshpos[1] += cen[1]; meshpos[2] += cen[2]; // compute depending on type switch (geom->type) { case mjGEOM_SPHERE: // find maximum distance geom->size[0] = 0; for (int i=0; isize[0] = mjMAX(geom->size[0], dst); } break; case mjGEOM_CAPSULE: case mjGEOM_CYLINDER: // find maximum distance in XY, separately in Z geom->size[0] = 0; geom->size[1] = 0; for (int i=0; isize[0] = mjMAX(geom->size[0], dst); // proceed with z: valid for cylinder double dst2 = fabs(v[2]-cen[2]); geom->size[1] = mjMAX(geom->size[1], dst2); } // special handling of capsule: consider curved cap if (geom->type==mjGEOM_CAPSULE) { geom->size[1] = 0; for (int i=0; isize[0] * sin(acos(dst/geom->size[0])); geom->size[1] = mjMAX(geom->size[1], dst2-h); } } break; case mjGEOM_ELLIPSOID: case mjGEOM_BOX: geom->size[0] = aabb[3] - cen[0]; geom->size[1] = aabb[4] - cen[1]; geom->size[2] = aabb[5] - cen[2]; break; default: throw mjCError(this, "invalid fittype in mesh %s", name.c_str()); } } // rescale size geom->size[0] *= geom->fitscale; geom->size[1] *= geom->fitscale; geom->size[2] *= geom->fitscale; } // comparison function for vertex sorting quicksortfunc(vertcompare, context, el1, el2) { float* vert = (float*) context; float x1 = vert[3*(*(int*)el1)] + 1e-2*vert[1+3*(*(int*)el1)] + 1e-4*vert[2+3*(*(int*)el1)]; float x2 = vert[3*(*(int*)el2)] + 1e-2*vert[1+3*(*(int*)el2)] + 1e-4*vert[2+3*(*(int*)el2)]; if (x1 < x2) { return -1; } else if (x1 == x2) { return 0; } else { return 1; } } // remove repeated vertices void mjCMesh::RemoveRepeated() { int repeated = 0; // allocate sort and redirection indices, set to identity auto index = std::unique_ptr(new int[nvert]); auto redirect = std::unique_ptr(new int[nvert]); for (int i=0; i < nvert; i++) { index[i] = redirect[i] = i; } // sort vertices mjQUICKSORT(index.get(), nvert, sizeof(int), vertcompare, vert); // find repeated vertices, set redirect for (int i=1; i < nvert; i++) { if (vert[3*index[i]] == vert[3*index[i-1]] && vert[3*index[i]+1] == vert[3*index[i-1]+1] && vert[3*index[i]+2] == vert[3*index[i-1]+2]) { redirect[index[i]] = index[i-1]; repeated++; } } // compress vertices, change face data if (repeated) { // track redirections until non-redirected vertex, set for (int i=0; i=nvert-repeated) { throw mjCError( this, "error removing vertices from mesh '%s'", name.c_str()); } } } // correct vertex count nvert -= repeated; // resize vert if any vertices were removed if (repeated) { float* old = vert; vert = (float*) mju_malloc(3*nvert*sizeof(float)); memcpy(vert, old, 3*nvert*sizeof(float)); mju_free(old); } } // load OBJ mesh void mjCMesh::LoadOBJ(const mjVFS* vfs) { // make filename string filename = mjuu_makefullname( model->modelfiledir, model->meshdir, file); tinyobj::ObjReader objReader; char* buffer = nullptr; if (vfs) { int id = mj_findFileVFS(vfs, filename.c_str()); if (id >= 0) { buffer = static_cast(vfs->filedata[id]); int buffer_sz = vfs->filesize[id]; // TODO(etom): support .mtl files in the VFS case? objReader.ParseFromString(std::string(buffer, buffer_sz), std::string()); } } // if not found in vfs, read from file if (!buffer) { objReader.ParseFromFile(filename); } if (!objReader.Valid()) { std::stringstream msg; msg << "could not parse OBJ file '" << filename << "': \n" << objReader.Error(); throw mjCError(this, "%s", msg.str().c_str()); } 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()) { const auto& mesh = objReader.GetShapes()[0].mesh; bool righthand = (scale[0]*scale[1]*scale[2] > 0); // iterate over mesh faces 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()); } 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)]); 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)]); } idx += nfacevert; ++face; } // for each vertex, store index, normal, and texcoord for (const auto& mesh_index : face_indices) { userface.push_back(mesh_index.vertex_index); 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) } } } // flip the second texcoord for (int i=1; i0); // make filename string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file); // get file data in buffer char* buffer = 0; int buffer_sz = 0; bool own_buffer = false; if (vfs) { int id = mj_findFileVFS(vfs, filename.c_str()); if (id>=0) { buffer = (char*)vfs->filedata[id]; buffer_sz = vfs->filesize[id]; } } // if not found in vfs, read from file if (!buffer) { buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz); own_buffer = true; } // still not found if (!buffer) { throw mjCError(this, "could not open STL file '%s'", filename.c_str()); } else if (!buffer_sz) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "STL file '%s' is empty", filename.c_str()); } // make sure there is enough data for header if (buffer_sz<84) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "invalid header in STL file '%s'", filename.c_str()); } // get number of triangles, check bounds nface = *(unsigned int*)(buffer+80); if (nface<1 || nface>200000) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "number of faces should be between 1 and 200000 in STL file '%s';" " perhaps this is an ASCII file?", filename.c_str()); } // check remaining buffer size if (nface*50 != buffer_sz-84) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "STL file '%s' has wrong size; perhaps this is an ASCII file?", filename.c_str()); } // assign stl data pointer const char* stl = buffer + 84; // allocate face and vertex data face = (int*) mju_malloc(3*nface*sizeof(int)); vert = (float*) mju_malloc(9*nface*sizeof(float)); // add vertices and faces, including repeated for now for (int i=0; ipow(2, 30)) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "vertex coordinates in STL file '%s' exceed maximum bounds", filename.c_str()); } } // add vertex address in face; change order if scale makes it lefthanded if (righthand || j==0) { face[3*i+j] = nvert; } else { face[3*i+3-j] = nvert; } // add vertex data memcpy(vert+3*nvert, v, 3*sizeof(float)); nvert++; } } // free buffer if allocated here if (own_buffer) { mju_free(buffer); } RemoveRepeated(); } // load MSH binary mesh void mjCMesh::LoadMSH(const mjVFS* vfs) { bool righthand = (scale[0]*scale[1]*scale[2]>0); // make filename string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file); // get file data in buffer char* buffer = 0; int buffer_sz = 0; bool own_buffer = false; if (vfs) { int id = mj_findFileVFS(vfs, filename.c_str()); if (id>=0) { buffer = (char*)vfs->filedata[id]; buffer_sz = vfs->filesize[id]; } } // if not found in vfs, read from file if (!buffer) { buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz); own_buffer = true; } // still not found if (!buffer) { throw mjCError(this, "could not open MSH file '%s'", filename.c_str()); } else if (!buffer_sz) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "MSH file '%s' is empty", filename.c_str()); } // make sure header is present if (buffer_sz<4*sizeof(int)) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "missing header in MSH file '%s'", filename.c_str()); } // get sizes from header nvert = ((int*)buffer)[0]; nnormal = ((int*)buffer)[1]; ntexcoord = ((int*)buffer)[2]; nface = ((int*)buffer)[3]; // check sizes if (nvert<4 || nface<0 || nnormal<0 || ntexcoord<0 || (nnormal>0 && nnormal!=nvert) || (ntexcoord>0 && ntexcoord!=nvert)) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "invalid sizes in MSH file '%s'", filename.c_str()); } // check file size if (buffer_sz != 4*sizeof(int) + 3*nvert*sizeof(float) + 3*nnormal*sizeof(float) + 2*ntexcoord*sizeof(float) + 3*nface*sizeof(int)) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "unexpected file size in MSH file '%s'", filename.c_str()); } // allocate and copy float* fdata = (float*)(((int*)buffer) + 4); if (nvert) { vert = (float*) mju_malloc(3*nvert*sizeof(float)); memcpy(vert, fdata, 3*nvert*sizeof(float)); fdata += 3*nvert; } if (nnormal) { normal = (float*) mju_malloc(3*nvert*sizeof(float)); memcpy(normal, fdata, 3*nvert*sizeof(float)); fdata += 3*nvert; } if (ntexcoord) { texcoord = (float*) mju_malloc(2*nvert*sizeof(float)); memcpy(texcoord, fdata, 2*nvert*sizeof(float)); fdata += 2*nvert; } if (nface) { face = (int*) mju_malloc(3*nface*sizeof(int)); facenormal = (int*) mju_malloc(3*nface*sizeof(int)); memcpy(face, fdata, 3*nface*sizeof(int)); memcpy(facenormal, fdata, 3*nface*sizeof(int)); } if (nface && texcoord) { facetexcoord = (int*) mju_malloc(3*nface*sizeof(int)); memcpy(facetexcoord, fdata, 3*nface*sizeof(int)); } // rearange face data if left-handed scaling if (nface && !righthand) { for (int i=0; imjMINVAL) { float scl = 1/sqrtf(len); normal[3*i] *= scl; normal[3*i+1] *= scl; normal[3*i+2] *= scl; } else { normal[3*i] = 0; normal[3*i+1] = 0; normal[3*i+2] = 1; } } // find centroid of faces for (int i=0; i=nvert) { throw mjCError(this, "vertex index out of range in %s (index = %d)", name.c_str(), i); } } // get area and center double a = _triangle(nrm, cen, vert+3*face[3*i], vert+3*face[3*i+1], vert+3*face[3*i+2]); // accumulate for (int j=0; j<3; j++) { facecen[j] += a*cen[j]; } area += a; } // require positive area if (area < mjMINVAL) { validarea = false; return; } // finalize centroid of faces for (int j=0; j<3; j++) { facecen[j] /= area; } } // compute CoM and volume from pyramid volumes GetVolumeRef(type) = 0; for (int i=0; iexactmeshinertia) { vol = fabs(vol); } // add pyramid com GetVolumeRef(type) += vol; for (int j=0; j<3; j++) { CoM[j] += vol*(cen[j]*3.0/4.0 + facecen[j]/4.0); } } // require positive volume if (GetVolumeRef(type) < mjMINVAL) { validvolume = false; return; } // finalize CoM, save as mesh center for (int j=0; j<3; j++) { CoM[j] /= GetVolumeRef(type); } mjuu_copyvec(GetPosPtr(type), CoM, 3); // re-center mesh at CoM if (type==mjVOLUME_MESH) { for (int i=0; iexactmeshinertia) { vol = fabs(vol); } // apply formula, accumulate GetVolumeRef(type) += vol; for (int j=0; j<6; j++) { P[j] += def->geom.density*vol / (type==mjSHELL_MESH ? 12 : 20) * ( 2*(D[k[j][0]] * D[k[j][1]] + E[k[j][0]] * E[k[j][1]] + F[k[j][0]] * F[k[j][1]]) + D[k[j][0]] * E[k[j][1]] + D[k[j][1]] * E[k[j][0]] + D[k[j][0]] * F[k[j][1]] + D[k[j][1]] * F[k[j][0]] + E[k[j][0]] * F[k[j][1]] + E[k[j][1]] * F[k[j][0]]); } } // convert from products of inertia to moments of inertia inert[0] = P[1] + P[2]; inert[1] = P[0] + P[2]; inert[2] = P[0] + P[1]; inert[3] = -P[3]; inert[4] = -P[4]; inert[5] = -P[5]; // get quaternion and diagonal inertia mjtNum eigval[3], eigvec[9], quattmp[4]; mjtNum full[9] = { inert[0], inert[3], inert[4], inert[3], inert[1], inert[5], inert[4], inert[5], inert[2] }; mju_eig3(eigval, eigvec, quattmp, full); // check eigval - SHOULD NOT OCCUR if (eigval[2]<=0) { valideigenvalue = false; return; } if (eigval[0] + eigval[1] < eigval[2] || eigval[0] + eigval[2] < eigval[1] || eigval[1] + eigval[2] < eigval[0]) { validinequality = false; return; } // compute sizes of equivalent inertia box double mass = GetVolumeRef(type) * def->geom.density; double* boxsz = GetInertiaBoxPtr(type); boxsz[0] = sqrt(6*(eigval[1]+eigval[2]-eigval[0])/mass)/2; boxsz[1] = sqrt(6*(eigval[0]+eigval[2]-eigval[1])/mass)/2; boxsz[2] = sqrt(6*(eigval[0]+eigval[1]-eigval[2])/mass)/2; // copy quat for (int j=0; j<4; j++) { GetQuatPtr(type)[j] = type == mjVOLUME_MESH ? quattmp[j] : GetQuatPtr(mjVOLUME_MESH)[j]; } // rotate vertices and normals into axis-aligned frame if (type==mjVOLUME_MESH) { double neg[4] = {quattmp[0], -quattmp[1], -quattmp[2], -quattmp[3]}; double mat[9]; mjuu_quat2mat(mat, neg); for (int i=0; i=0 || invalidorientation.second>=0) throw mjCError(this, "faces of mesh '%s' have inconsistent orientation. Please check the " "faces containing the vertices %d and %d.", name.c_str(), invalidorientation.first, invalidorientation.second); if (!validarea) throw mjCError(this, "mesh surface area is too small: %s", name.c_str()); if (!validvolume) throw mjCError(this, "mesh volume is too small: %s", name.c_str()); if (!valideigenvalue) throw mjCError(this, "eigenvalue of mesh inertia must be positive: %s", name.c_str()); if (!validinequality) throw mjCError(this, "eigenvalues of mesh inertia violate A + B >= C: %s", name.c_str()); } // get inertia pointer double* mjCMesh::GetInertiaBoxPtr(mjtMeshType type) { CheckMesh(); return type==mjSHELL_MESH ? boxsz_surface : boxsz_volume; } double& mjCMesh::GetVolumeRef(mjtMeshType type) { CheckMesh(); return type==mjSHELL_MESH ? surface : volume; } // make graph describing convex hull void mjCMesh::MakeGraph(void) { int adr, ok, curlong, totlong, exitcode; double* data; facetT* facet, **facetp; vertexT* vertex, *vertex1, **vertex1p; char qhopt[10] = "qhull Qt"; // graph not needed for small meshes if (nvert<4) { return; } // convert mesh data to double data = (double*) mju_malloc(3*nvert*sizeof(double)); if (!data) { throw mjCError(this, "could not allocate data for qhull"); } for (int i=0; i<3*nvert; i++) { data[i] = (double)vert[i]; } qhT qh_qh; qhT* qh = &qh_qh; qh_zero(qh, stderr); // qhull basic init qh_init_A(qh, stdin, stdout, stderr, 0, NULL); // install longjmp error handler exitcode = setjmp(qh->errexit); qh->NOerrexit = false; if (!exitcode) { // actual init qh_initflags(qh, qhopt); qh_init_B(qh, data, nvert, 3, False); // construct convex hull qh_qhull(qh); qh_triangulate(qh); qh_vertexneighbors(qh); // allocate graph: // numvert, numface, vert_edgeadr[numvert], vert_globalid[numvert], // edge_localid[numvert+3*numface], face_globalid[3*numface] int numvert = qh->num_vertices; int numface = qh->num_facets; szgraph = 2 + 3*numvert + 6*numface; graph = (int*) mju_malloc(szgraph*sizeof(int)); graph[0] = numvert; graph[1] = numface; // pointers for conveniece int* vert_edgeadr = graph + 2; int* vert_globalid = graph + 2 + numvert; int* edge_localid = graph + 2 + 2*numvert; int* face_globalid = graph + 2 + 3*numvert + 3*numface; // fill in graph data int i = adr = 0; ok = 1; FORALLvertices { // point id of this vertex, check int pid = qh_pointid(qh, vertex->point); if (pid<0 || pid>=nvert) { ok = 0; break; } // save edge address and global id of this vertex vert_edgeadr[i] = adr; vert_globalid[i] = pid; // process neighoring faces and their vertices int start = adr; FOREACHsetelement_(facetT, vertex->neighbors, facet) { int cnt = 0; FOREACHsetelement_(vertexT, facet->vertices, vertex1) { cnt++; // point id of face vertex, check int pid1 = qh_pointid(qh, vertex1->point); if (pid1<0 || pid1>=nvert) { ok = 0; break; } // if different from vertex id, try to insert if (pid!=pid1) { // check for previous record int j; for (j=start; j=adr) { edge_localid[adr++] = pid1; } } } // make sure we have triangle: SHOULD NOT OCCUR if (cnt!=3) { mju_error("Qhull did not return triangle"); } } // insert separator, advance to next vertex edge_localid[adr++] = -1; i++; } // size check: SHOULD NOT OCCUR if (adr!=numvert+3*numface) { mju_error("Wrong size in convex hull graph"); } // add triangle data, reorient faces if flipped adr = 0; FORALLfacets { int ii = 0; int ind[3] = {0, 1, 2}; if (facet->toporient) { ind[0] = 1; ind[1] = 0; } // copy triangle data FOREACHsetelement_(vertexT, facet->vertices, vertex1) { // make sure we have triangle: SHOULD NOT OCCUR if (ii>=3) { mju_error("Qhull did not return triangle"); } face_globalid[adr + ind[ii++]] = qh_pointid(qh, vertex1->point); } // advance to next triangle adr += 3; } // free all qh_freeqhull(qh, !qh_ALL); qh_memfreeshort(qh, &curlong, &totlong); mju_free(data); // bad graph: delete if (!ok) { szgraph = 0; mju_free(graph); graph = 0; mju_warning("Could not construct convex hull graph"); } // replace global ids with local ids in edge data for (int i=0; i=0) { // search vert_globalid for match int adr; for (adr=0; adr=numvert) { mju_error("Vertex id not found in convex hull"); } } } } // longjmp error handler else { // free all qh_freeqhull(qh, !qh_ALL); qh_memfreeshort(qh, &curlong, &totlong); mju_free(data); if (graph) { mju_free(graph); szgraph = 0; } throw mjCError(this, "qhull error"); } } // copy graph into face data void mjCMesh::CopyGraph(void) { // only if face data is missing if (face) { return; } // get info from graph, allocate int numvert = graph[0]; nface = graph[1]; face = (int*) mju_malloc(3*nface*sizeof(int)); // copy faces for (int i=0; imjMINVAL) for (int j=0; j<3; j++) { normal[3*i+j] /= len; } else { normal[3*i] = normal[3*i+1] = 0; normal[3*i+2] = 1; } } } //------------------ class mjCSkin implementation -------------------------------------------------- // constructor mjCSkin::mjCSkin(mjCModel* _model) { // set model pointer model = _model; // clear data file.clear(); material.clear(); rgba[0] = rgba[1] = rgba[2] = 0.5f; rgba[3] = 1.0f; inflate = 0; group = 0; vert.clear(); texcoord.clear(); face.clear(); bodyname.clear(); bindpos.clear(); bindquat.clear(); vertid.clear(); vertweight.clear(); bodyid.clear(); matid = -1; } // destructor mjCSkin::~mjCSkin() { file.clear(); material.clear(); vert.clear(); texcoord.clear(); face.clear(); bodyname.clear(); bindpos.clear(); bindquat.clear(); vertid.clear(); vertweight.clear(); bodyid.clear(); } // compiler void mjCSkin::Compile(const mjVFS* vfs) { // load file if (!file.empty()) { // make sure data is not present if (!vert.empty() || !texcoord.empty() || !face.empty() || !bodyname.empty() || !bindpos.empty() || !bindquat.empty() || !vertid.empty() || !vertweight.empty() || !bodyid.empty()) { throw mjCError(this, "Data already exists, trying to load from skin file: %s", file.c_str()); } // remove path from file if necessary if (model->strippath) { file = mjuu_strippath(file); } // load SKN string ext = mjuu_getext(file); if (!strcasecmp(ext.c_str(), ".skn")) { LoadSKN(vfs); } else { throw mjCError(this, "Unknown skin file type: %s", file.c_str()); } } // make sure all data is present if (vert.empty() || face.empty() || bodyname.empty() || bindpos.empty() || bindquat.empty() || vertid.empty() || vertweight.empty()) { throw mjCError(this, "Missing data in skin"); } // check mesh sizes if (vert.size()%3) { throw mjCError(this, "Vertex data must be multiple of 3"); } if (!texcoord.empty() && texcoord.size()!=2*vert.size()/3) { throw mjCError(this, "Vertex and texcoord data incompatible size"); } if (face.size()%3) { throw mjCError(this, "Face data must be multiple of 3"); } // check bone sizes size_t nbone = bodyname.size(); if (bindpos.size()!=3*nbone) { throw mjCError(this, "Unexpected bindpos size in skin"); } if (bindquat.size()!=4*nbone) { throw mjCError(this, "Unexpected bindquat size in skin"); } if (vertid.size()!=nbone) { throw mjCError(this, "Unexpected vertid size in skin"); } if (vertweight.size()!=nbone) { throw mjCError(this, "Unexpected vertweight size in skin"); } // resolve body names bodyid.resize(nbone); for (int i=0; iFindObject(mjOBJ_BODY, bodyname[i]); if (!pbody) { throw mjCError(this, "unknown body '%s' in skin", bodyname[i].c_str()); } bodyid[i] = pbody->id; } // resolve material name mjCBase* pmat = model->FindObject(mjOBJ_MATERIAL, material); if (pmat) { matid = pmat->id; } else if (!material.empty()) { throw mjCError(this, "unkown material '%s' in skin", material.c_str()); } // set total vertex weights to 0 vector vw; size_t nvert = vert.size()/3; vw.resize(nvert); fill(vw.begin(), vw.end(), 0.0f); // accumulate vertex weights from all bones for (int i=0; i=nvert) { throw mjCError(this, "vertid %d out of range in skin", NULL, jj); } // accumulate vw[jj] += vertweight[i][j]; } } // check coverage for (int i=0; imodelfiledir, model->meshdir, file); // get file data in buffer char* buffer = NULL; int buffer_sz = 0; bool own_buffer = false; if (vfs) { int id = mj_findFileVFS(vfs, filename.c_str()); if (id>=0) { buffer = (char*)vfs->filedata[id]; buffer_sz = vfs->filesize[id]; } } // if not found in vfs, read from file if (!buffer) { buffer = (char*) mju_fileToMemory(filename.c_str(), &buffer_sz); own_buffer = true; } // still not found if (!buffer) { throw mjCError(this, "could not open SKN file '%s'", filename.c_str()); } else if (!buffer_sz) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "SKN file '%s' is empty", filename.c_str()); } // make sure header is present if (buffer_sz<16) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "missing header in SKN file '%s'", filename.c_str()); } // get sizes from header int nvert = ((int*)buffer)[0]; int ntexcoord = ((int*)buffer)[1]; int nface = ((int*)buffer)[2]; int nbone = ((int*)buffer)[3]; // negative sizes not allowed if (nvert<0 || ntexcoord<0 || nface<0 || nbone<0) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "negative size in header of SKN file '%s'", filename.c_str()); } // make sure we have data for vert, texcoord, face if (buffer_sz < 16 + 12*nvert + 8*ntexcoord + 12*nface) { if (own_buffer) { mju_free(buffer); } throw mjCError(this, "insufficient data in SKN file '%s'", filename.c_str()); } // data pointer and counter float* pdata = (float*)(buffer+16); int cnt = 0; // copy vert if (nvert) { vert.resize(3*nvert); memcpy(vert.data(), pdata+cnt, 3*nvert*sizeof(float)); cnt += 3*nvert; } // copy texcoord if (ntexcoord) { texcoord.resize(2*ntexcoord); memcpy(texcoord.data(), pdata+cnt, 2*ntexcoord*sizeof(float)); cnt += 2*ntexcoord; } // copy face if (nface) { face.resize(3*nface); memcpy(face.data(), pdata+cnt, 3*nface*sizeof(int)); cnt += 3*nface; } // allocate bone arrays bodyname.clear(); bindpos.resize(3*nbone); bindquat.resize(4*nbone); vertid.resize(nbone); vertweight.resize(nbone); // read bones for (int i=0; i