/* * Copyright (C) 2012 Open Source Robotics Foundation * * 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 "gazebo/common/Assert.hh" #include "gazebo/common/CommonIface.hh" #include "gazebo/common/Console.hh" #include "gazebo/common/Exception.hh" #include "gazebo/common/Mesh.hh" #include "gazebo/common/MeshCSG.hh" #include "gazebo/common/MeshManager.hh" using namespace gazebo; using namespace common; ////////////////////////////////////////////////// MeshCSG::MeshCSG() { } ////////////////////////////////////////////////// MeshCSG::~MeshCSG() { } ////////////////////////////////////////////////// void MeshCSG::MergeVertices(GPtrArray * _vertices, double _epsilon) { GPtrArray *array; GNode *kdtree; GtsVertex **verticesData = reinterpret_cast(_vertices->pdata); array = g_ptr_array_new(); for (unsigned int i = 0; i < _vertices->len; ++i) g_ptr_array_add(array, verticesData[i]); kdtree = gts_kdtree_new(array, NULL); g_ptr_array_free(array, true); // for each vertex, do a bbox kdtree search to find nearby vertices within // _epsilon, merge vertices if they are within the bbox for (unsigned int i = 0; i < _vertices->len; ++i) { GtsVertex *v = reinterpret_cast(verticesData[i]); // make sure vertex v is active (because they could be marked inactive // due to previous merge operation) if (!GTS_OBJECT (v)->reserved) { GtsBBox *bbox; GSList *selected, *j; // build bounding box bbox = gts_bbox_new(gts_bbox_class(), v, GTS_POINT(v)->x - _epsilon, GTS_POINT(v)->y - _epsilon, GTS_POINT(v)->z - _epsilon, GTS_POINT(v)->x + _epsilon, GTS_POINT(v)->y + _epsilon, GTS_POINT(v)->z + _epsilon); // select vertices which are inside bbox using kdtree j = selected = gts_kdtree_range(kdtree, bbox, NULL); while (j) { GtsVertex *sv = reinterpret_cast(j->data); // mark sv as inactive (merged) if (sv != v && !GTS_OBJECT(sv)->reserved) GTS_OBJECT(sv)->reserved = v; j = j->next; } g_slist_free(selected); gts_object_destroy(GTS_OBJECT(bbox)); } } gts_kdtree_destroy(kdtree); // destroy inactive vertices // we want to control vertex destruction gts_allow_floating_vertices = true; for (unsigned int i = 0; i < _vertices->len; ++i) { GtsVertex * v = reinterpret_cast(verticesData[i]); // v is inactive if (GTS_OBJECT(v)->reserved) { verticesData[i] = reinterpret_cast(GTS_OBJECT(v)->reserved); gts_object_destroy(GTS_OBJECT(v)); } } gts_allow_floating_vertices = false; } ////////////////////////////////////////////////// static void FillVertex(GtsPoint *_p, gpointer *_data) { // create a Gazebo vertex from GTS_POINT and add it to the submesh SubMesh *subMesh = reinterpret_cast(_data[0]); GHashTable* vIndex = reinterpret_cast(_data[2]); subMesh->AddVertex(GTS_POINT(_p)->x, GTS_POINT(_p)->y, GTS_POINT(_p)->z); // fill the hash table which will later be used for adding indices to the // submesh in the FillFace function. g_hash_table_insert(vIndex, _p, GUINT_TO_POINTER((*(reinterpret_cast(_data[1])))++)); } ////////////////////////////////////////////////// static void FillFace(GtsTriangle *_t, gpointer *_data) { SubMesh *subMesh = reinterpret_cast(_data[0]); GHashTable* vIndex = reinterpret_cast(_data[2]); GtsVertex * v1, * v2, * v3; gts_triangle_vertices(_t, &v1, &v2, &v3); subMesh->AddIndex(GPOINTER_TO_UINT(g_hash_table_lookup(vIndex, v1))); subMesh->AddIndex(GPOINTER_TO_UINT(g_hash_table_lookup(vIndex, v2))); subMesh->AddIndex(GPOINTER_TO_UINT(g_hash_table_lookup(vIndex, v3))); } ////////////////////////////////////////////////// Mesh *MeshCSG::CreateBoolean(const Mesh *_m1, const Mesh *_m2, int _operation, const ignition::math::Pose3d &_offset) { GtsSurface *s1, *s2, *s3; GtsSurfaceInter *si; GNode *tree1, *tree2; gboolean closed = true; bool isOpen1 = false; bool isOpen2 = false; s1 = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class()); s2 = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class()); s3 = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class()); this->ConvertMeshToGTS(_m1, s1); if (_offset != ignition::math::Pose3d::Zero) { Mesh *m2 = new Mesh(); for (unsigned int i = 0; i < _m2->GetSubMeshCount(); ++i) { SubMesh *m2SubMesh = new SubMesh(); m2->AddSubMesh(m2SubMesh); const SubMesh *subMesh = _m2->GetSubMesh(i); if (subMesh->GetVertexCount() <= 2) continue; for (unsigned int j = 0; j < subMesh->GetVertexCount(); ++j) { m2SubMesh->AddVertex(_offset.Pos() + _offset.Rot()*subMesh->Vertex(j)); } for (unsigned int j = 0; j < subMesh->GetIndexCount(); ++j) { m2SubMesh->AddIndex(subMesh->GetIndex(j)); } } this->ConvertMeshToGTS(m2, s2); delete m2; } else { this->ConvertMeshToGTS(_m2, s2); } // build bounding box tree for first surface tree1 = gts_bb_tree_surface(s1); isOpen1 = gts_surface_volume(s1) < 0. ? true : false; // build bounding box tree for second surface tree2 = gts_bb_tree_surface(s2); isOpen2 = gts_surface_volume(s2) < 0. ? true : false; si = gts_surface_inter_new(gts_surface_inter_class(), s1, s2, tree1, tree2, isOpen1, isOpen2); GZ_ASSERT(gts_surface_inter_check(si, &closed), "si is not an orientable manifold"); if (!closed) { gzerr << "the intersection of " << _m1->GetName() << " and " << _m2->GetName() << " is not a closed curve\n"; return NULL; } // FILE *output1 = fopen("output3.gts", "w"); // gts_surface_write(s1, output1); // fclose(output1); // FILE *output2 = fopen("output4.gts", "w"); // gts_surface_write(s2, output2); // fclose(output2); if (_operation == MeshCSG::UNION) { gts_surface_inter_boolean(si, s3, GTS_1_OUT_2); gts_surface_inter_boolean(si, s3, GTS_2_OUT_1); } else if (_operation == MeshCSG::INTERSECTION) { gts_surface_inter_boolean(si, s3, GTS_1_IN_2); gts_surface_inter_boolean(si, s3, GTS_2_IN_1); } else if (_operation == MeshCSG::DIFFERENCE) { gts_surface_inter_boolean(si, s3, GTS_1_OUT_2); gts_surface_inter_boolean(si, s3, GTS_2_IN_1); gts_surface_foreach_face(si->s2, (GtsFunc) gts_triangle_revert, NULL); gts_surface_foreach_face(s2, (GtsFunc) gts_triangle_revert, NULL); } // FILE *output = fopen("output.gts", "w"); // gts_surface_write(s3, output); // fclose(output); // create the boolean mesh Mesh *mesh = new Mesh(); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); // fill the submesh with data generated by GTS unsigned int n = 0; gpointer data[3]; GHashTable *vIndex = g_hash_table_new(NULL, NULL); data[0] = subMesh; data[1] = &n; data[2] = vIndex; gts_surface_foreach_vertex(s3, (GtsFunc)FillVertex, data); n = 0; gts_surface_foreach_face(s3, (GtsFunc)FillFace, data); g_hash_table_destroy(vIndex); mesh->RecalculateNormals(); // destroy surfaces gts_object_destroy(GTS_OBJECT(s1)); gts_object_destroy(GTS_OBJECT(s2)); gts_object_destroy(GTS_OBJECT(s3)); gts_object_destroy(GTS_OBJECT(si)); // destroy bounding box trees (including bounding boxes) gts_bb_tree_destroy(tree1, true); gts_bb_tree_destroy(tree2, true); return mesh; } ////////////////////////////////////////////////// void MeshCSG::ConvertMeshToGTS(const Mesh *_mesh, GtsSurface *_surface) { if (!_surface) { gzerr << _mesh->GetName() << ": Surface is NULL\n"; // _surface = gts_surface_new(gts_surface_class(), gts_face_class(), // gts_edge_class(), gts_vertex_class()); return; } GPtrArray *vertices = g_ptr_array_new(); for (unsigned int i = 0; i < _mesh->GetSubMeshCount(); ++i) { const SubMesh *subMesh = _mesh->GetSubMesh(i); unsigned int indexCount = subMesh->GetIndexCount(); if (subMesh->GetVertexCount() <= 2) continue; for (unsigned int j = 0; j < subMesh->GetVertexCount(); ++j) { ignition::math::Vector3d vertex = subMesh->Vertex(j); g_ptr_array_add(vertices, gts_vertex_new(gts_vertex_class(), vertex.X(), vertex.Y(), vertex.Z())); } // merge duplicate vertices, otherwise gts produces undesirable results this->MergeVertices(vertices, 0.01); GtsVertex **verticesData = reinterpret_cast(vertices->pdata); for (unsigned int j = 0; j < indexCount/3; ++j) { GtsEdge *e1 = GTS_EDGE(gts_vertices_are_connected( verticesData[subMesh->GetIndex(3*j)], verticesData[subMesh->GetIndex(3*j+1)])); GtsEdge *e2 = GTS_EDGE(gts_vertices_are_connected( verticesData[subMesh->GetIndex(3*j+1)], verticesData[subMesh->GetIndex(3*j+2)])); GtsEdge *e3 = GTS_EDGE(gts_vertices_are_connected( verticesData[subMesh->GetIndex(3*j+2)], verticesData[subMesh->GetIndex(3*j)])); if (e1 == NULL && verticesData[subMesh->GetIndex(3*j)] != verticesData[subMesh->GetIndex(3*j+1)]) { e1 = gts_edge_new(_surface->edge_class, verticesData[subMesh->GetIndex(3*j)], verticesData[subMesh->GetIndex(3*j+1)]); } if (e2 == NULL && verticesData[subMesh->GetIndex(3*j+1)] != verticesData[subMesh->GetIndex(3*j+2)]) { e2 = gts_edge_new(_surface->edge_class, verticesData[subMesh->GetIndex(3*j+1)], verticesData[subMesh->GetIndex(3*j+2)]); } if (e3 == NULL && verticesData[subMesh->GetIndex(3*j+2)] != verticesData[subMesh->GetIndex(3*j)]) { e3 = gts_edge_new(_surface->edge_class, verticesData[subMesh->GetIndex(3*j+2)], verticesData[subMesh->GetIndex(3*j)]); } if (e1 != NULL && e2 != NULL && e3 != NULL) { gts_surface_add_face(_surface, gts_face_new(_surface->face_class, e1, e2, e3)); } else { gzwarn << _mesh->GetName() << ": Ignoring degenerate facet!"; } } } }