/* * 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 #include "gazebo/math/Plane.hh" #include "gazebo/math/Matrix3.hh" #include "gazebo/math/Matrix4.hh" #include "gazebo/math/Vector2i.hh" #include "gazebo/common/CommonIface.hh" #include "gazebo/common/Exception.hh" #include "gazebo/common/Console.hh" #include "gazebo/common/Mesh.hh" #include "gazebo/common/ColladaLoader.hh" #include "gazebo/common/ColladaExporter.hh" #include "gazebo/common/STLLoader.hh" #include "gazebo/common/OBJLoader.hh" #include "gazebo/gazebo_config.h" #ifdef HAVE_GTS #include "gazebo/common/MeshCSG.hh" #include "gazebo/common/GTSMeshUtils.hh" #endif #include "gazebo/common/MeshManager.hh" using namespace gazebo; using namespace common; // added here for ABI compatibility // TODO move to header / private class when merging forward. static OBJLoader objLoader; ////////////////////////////////////////////////// MeshManager::MeshManager() { this->colladaLoader = new ColladaLoader(); this->colladaExporter = new ColladaExporter(); this->stlLoader = new STLLoader(); // Create some basic shapes this->CreatePlane("unit_plane", ignition::math::Planed( ignition::math::Vector3d(0, 0, 1), ignition::math::Vector2d(1, 1), 0), ignition::math::Vector2d(1, 1), ignition::math::Vector2d(1, 1)); this->CreateSphere("unit_sphere", 0.5, 32, 32); this->CreateSphere("joint_anchor", 0.01, 32, 32); this->CreateBox("body_cg", ignition::math::Vector3d(0.014, 0.014, 0.014), ignition::math::Vector2d(0.014, 0.014)); this->CreateBox("unit_box", ignition::math::Vector3d(1, 1, 1), ignition::math::Vector2d(1, 1)); // Note: axis_box is added and currently only used by SelectionObj in // replacement of unit_box to avoid a weird ogre 1.9 problem in // UNIT_Projection_TEST on OSX this->CreateBox("axis_box", ignition::math::Vector3d::One, ignition::math::Vector2d::One); this->CreateCylinder("unit_cylinder", 0.5, 1.0, 1, 32); this->CreateCone("unit_cone", 0.5, 1.0, 5, 32); this->CreateCamera("unit_camera", 0.5); this->CreateCylinder("axis_shaft", 0.01, 0.2, 1, 16); this->CreateCone("axis_head", 0.02, 0.08, 1, 16); this->CreateTube("selection_tube", 1.0, 1.2, 0.01, 1, 64); this->fileExtensions.push_back("stl"); this->fileExtensions.push_back("dae"); this->fileExtensions.push_back("obj"); } ////////////////////////////////////////////////// MeshManager::~MeshManager() { delete this->colladaLoader; delete this->colladaExporter; delete this->stlLoader; std::map::iterator iter; for (iter = this->meshes.begin(); iter != this->meshes.end(); ++iter) delete iter->second; this->meshes.clear(); } ////////////////////////////////////////////////// const Mesh *MeshManager::Load(const std::string &_filename) { if (!this->IsValidFilename(_filename)) { gzerr << "Invalid mesh filename extension[" << _filename << "]\n"; return NULL; } Mesh *mesh = NULL; std::string extension; if (this->HasMesh(_filename)) { return this->meshes[_filename]; // This breaks trimesh geom. Each new trimesh should have a unique name. /* // erase mesh from this->meshes. This allows a mesh to be modified and // inserted into gazebo again without closing gazebo. std::map::iterator iter; iter = this->meshes.find(_filename); delete iter->second; iter->second = NULL; this->meshes.erase(iter); */ } std::string fullname = common::find_file(_filename); if (!fullname.empty()) { extension = fullname.substr(fullname.rfind(".")+1, fullname.size()); std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); MeshLoader *loader = NULL; if (extension == "stl" || extension == "stlb" || extension == "stla") loader = this->stlLoader; else if (extension == "dae") loader = this->colladaLoader; else if (extension == "obj") loader = &objLoader; else { gzerr << "Unsupported mesh format for file[" << _filename << "]\n"; return nullptr; } try { // This mutex prevents two threads from loading the same mesh at the // same time. boost::mutex::scoped_lock lock(this->mutex); if (!this->HasMesh(_filename)) { if ((mesh = loader->Load(fullname)) != NULL) { mesh->SetName(_filename); this->meshes.insert(std::make_pair(_filename, mesh)); } else gzerr << "Unable to load mesh[" << fullname << "]\n"; } else { mesh = this->meshes[_filename]; } } catch(gazebo::common::Exception &e) { gzerr << "Error loading mesh[" << fullname << "]\n"; gzerr << e << "\n"; gzthrow(e); } } else gzerr << "Unable to find file[" << _filename << "]\n"; return mesh; } ////////////////////////////////////////////////// void MeshManager::Export(const Mesh *_mesh, const std::string &_filename, const std::string &_extension, bool _exportTextures) { if (_extension == "dae") { this->colladaExporter->Export(_mesh, _filename, _exportTextures); } else { gzerr << "Unsupported mesh format for file[" << _filename << "]\n"; } } ////////////////////////////////////////////////// bool MeshManager::IsValidFilename(const std::string &_filename) { std::string extension; extension = _filename.substr(_filename.rfind(".")+1, _filename.size()); if (extension.empty()) return false; std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); return std::find(this->fileExtensions.begin(), this->fileExtensions.end(), extension) != this->fileExtensions.end(); } ////////////////////////////////////////////////// void MeshManager::GetMeshAABB(const Mesh *_mesh, ignition::math::Vector3d &_center, ignition::math::Vector3d &_minXYZ, ignition::math::Vector3d &_maxXYZ) { if (this->HasMesh(_mesh->GetName())) this->meshes[_mesh->GetName()]->GetAABB(_center, _minXYZ, _maxXYZ); } ////////////////////////////////////////////////// void MeshManager::GenSphericalTexCoord(const Mesh *_mesh, const ignition::math::Vector3d &_center) { if (this->HasMesh(_mesh->GetName())) this->meshes[_mesh->GetName()]->GenSphericalTexCoord(_center); } ////////////////////////////////////////////////// void MeshManager::AddMesh(Mesh *_mesh) { if (!this->HasMesh(_mesh->GetName())) this->meshes[_mesh->GetName()] = _mesh; } ////////////////////////////////////////////////// const Mesh *MeshManager::GetMesh(const std::string &_name) const { std::map::const_iterator iter; iter = this->meshes.find(_name); if (iter != this->meshes.end()) return iter->second; return NULL; } ////////////////////////////////////////////////// bool MeshManager::HasMesh(const std::string &_name) const { if (_name.empty()) return false; std::map::const_iterator iter; iter = this->meshes.find(_name); return iter != this->meshes.end(); } ////////////////////////////////////////////////// void MeshManager::CreateSphere(const std::string &name, float radius, int rings, int segments) { if (this->HasMesh(name)) { return; } int ring, seg; float deltaSegAngle = (2.0 * M_PI / segments); float deltaRingAngle = (M_PI / rings); ignition::math::Vector3d vert, norm; unsigned int verticeIndex = 0; Mesh *mesh = new Mesh(); mesh->SetName(name); this->meshes.insert(std::make_pair(name, mesh)); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); // Generate the group of rings for the sphere for (ring = 0; ring <= rings; ++ring) { float r0 = radius * sinf(ring * deltaRingAngle); vert.Y() = radius * cosf(ring * deltaRingAngle); // Generate the group of segments for the current ring for (seg = 0; seg <= segments; ++seg) { vert.X() = r0 * sinf(seg * deltaSegAngle); vert.Z() = r0 * cosf(seg * deltaSegAngle); norm = vert; norm.Normalize(); // Add one vertex to the strip which makes up the sphere subMesh->AddVertex(vert); subMesh->AddNormal(norm); subMesh->AddTexCoord( static_cast(seg) / static_cast(segments), static_cast(ring) /static_cast(rings)); if (ring != rings) { // each vertex (except the last) has six indices pointing to it subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex + segments); subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex + 1); subMesh->AddIndex(verticeIndex); verticeIndex++; } } } } ////////////////////////////////////////////////// void MeshManager::CreatePlane(const std::string &_name, const ignition::math::Planed &_plane, const ignition::math::Vector2d &_segments, const ignition::math::Vector2d &_uvTile) { this->CreatePlane(_name, _plane.Normal(), _plane.Offset(), _plane.Size(), _segments, _uvTile); } ////////////////////////////////////////////////// void MeshManager::CreatePlane(const std::string &_name, const ignition::math::Vector3d &_normal, const double _d, const ignition::math::Vector2d &_size, const ignition::math::Vector2d &_segments, const ignition::math::Vector2d &_uvTile) { if (this->HasMesh(_name)) { return; } Mesh *mesh = new Mesh(); mesh->SetName(_name); this->meshes.insert(std::make_pair(_name, mesh)); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); ignition::math::Vector3d zAxis, yAxis, xAxis; zAxis = _normal; zAxis.Normalize(); yAxis = zAxis.Perpendicular(); xAxis = yAxis.Cross(zAxis); ignition::math::Matrix4d xlate, xform, rot; xlate = rot = ignition::math::Matrix4d::Identity; ignition::math::Matrix3d rot3; rot3.Axes(xAxis, yAxis, zAxis); rot = rot3; xlate.Translate(_normal * -_d); xform = xlate * rot; ignition::math::Vector3d vec; ignition::math::Vector3d norm(0, 0, 1); double xSpace = _size.X() / _segments.X(); double ySpace = _size.Y() / _segments.Y(); double halfWidth = _size.X() / 2.0; double halfHeight = _size.Y() / 2.0; double xTex = _uvTile.X() / _segments.X(); double yTex = _uvTile.Y() / _segments.Y(); // Give it some thickness to reduce shadow artifacts. double thickness = 0.01; for (int i = 0; i <= 1; ++i) { double z = i*thickness; for (int y = 0; y <= _segments.Y(); ++y) { for (int x = 0; x <= _segments.X(); ++x) { // Compute the position of the vertex vec.X() = (x * xSpace) - halfWidth; vec.Y() = (y * ySpace) - halfHeight; vec.Z() = -z; vec = xform.TransformAffine(vec); subMesh->AddVertex(vec); // Compute the normal vec = xform.TransformAffine(norm); subMesh->AddNormal(vec); // Compute the texture coordinate subMesh->AddTexCoord(x * xTex, 1 - (y * yTex)); } } } this->Tesselate2DMesh(subMesh, _segments.X() + 1, _segments.Y() + 1, false); } ////////////////////////////////////////////////// void MeshManager::CreateBox(const std::string &_name, const ignition::math::Vector3d &_sides, const ignition::math::Vector2d &_uvCoords) { int i, k; if (this->HasMesh(_name)) { return; } Mesh *mesh = new Mesh(); mesh->SetName(_name); this->meshes.insert(std::make_pair(_name, mesh)); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); // Vertex values float v[8][3] = { {-1, -1, -1}, {-1, -1, +1}, {+1, -1, +1}, {+1, -1, -1}, {-1, +1, -1}, {-1, +1, +1}, {+1, +1, +1}, {+1, +1, -1} }; // Normals for each face float n[6][3]= { {+0, -1, +0}, {+0, +1, +0}, {+0, +0, +1}, {-1, +0, +0}, {+0, +0, -1}, {+1, +0, +0}, }; // Texture coords double t[4][2] = { {_uvCoords.X(), 0}, {0, 0}, {0, _uvCoords.Y()}, {_uvCoords.X(), _uvCoords.Y()} }; // Vertices int faces[6][4] = { {2, 1, 0, 3}, {5, 6, 7, 4}, {2, 6, 5, 1}, {1, 5, 4, 0}, {0, 4, 7, 3}, {6, 2, 3, 7} }; // Indices int ind[36] = { 0, 1, 2, 2, 3, 0, 4, 5, 7, 7, 5, 6, 11, 8, 9, 9, 10, 11, 12, 13, 15, 15, 13, 14, 16, 17, 18, 18, 19, 16, 21, 22, 23, 23, 20, 21, }; // Compute the vertices for (i = 0; i < 8; ++i) { v[i][0] *= _sides.X() * 0.5; v[i][1] *= _sides.Y() * 0.5; v[i][2] *= _sides.Z() * 0.5; } // For each face for (i = 0; i < 6; ++i) { // For each vertex in the face for (k = 0; k < 4; k++) { subMesh->AddVertex(v[faces[i][k]][0], v[faces[i][k]][1], v[faces[i][k]][2]); subMesh->AddNormal(n[i][0], n[i][1], n[i][2]); subMesh->AddTexCoord(t[k][0], t[k][1]); } } // Set the indices for (i = 0; i < 36; ++i) subMesh->AddIndex(ind[i]); } ////////////////////////////////////////////////// void MeshManager::CreateExtrudedPolyline(const std::string &_name, const std::vector > &_polys, double _height) { // distance tolerence between 2 points. This is used when creating a list // of distinct points in the polylines. double tol = 1e-4; #if !HAVE_GTS gzerr << "GTS library not found. Can not extrude polyline" << std::endl; return; #endif auto polys = _polys; // close all the loops for (auto &poly : polys) { // does the poly ends with the first point? auto first = poly[0]; auto last = poly[poly.size()-1]; double d = (first.X() - last.X()) * (first.X() - last.X()); d += (first.Y() - last.Y()) * (first.Y() - last.Y()); // within range if (d > tol * tol) { // add the first point at the end poly.push_back(first); } } if (this->HasMesh(_name)) { return; } Mesh *mesh = new Mesh(); mesh->SetName(_name); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); std::vector vertices; std::vector edges; MeshManager::ConvertPolylinesToVerticesAndEdges(polys, tol, vertices, edges); #if HAVE_GTS if (!GTSMeshUtils::DelaunayTriangulation(vertices, edges, subMesh)) { gzerr << "Unable to triangulate polyline." << std::endl; delete mesh; return; } #endif std::vector normals; for (unsigned int i = 0; i < edges.size(); ++i) { // we retrieve each edge's coordinates int i0 = edges[i][0]; int i1 = edges[i][1]; ignition::math::Vector2d edgeV0 = vertices[i0]; ignition::math::Vector2d edgeV1 = vertices[i1]; // we look for those points in the subMesh (where indices may have changed) for (unsigned int j = 0; j < subMesh->GetIndexCount(); j+=3) { ignition::math::Vector3d v0 = subMesh->Vertex(subMesh->GetIndex(j)); ignition::math::Vector3d v1 = subMesh->Vertex(subMesh->GetIndex(j+1)); ignition::math::Vector3d v2 = subMesh->Vertex(subMesh->GetIndex(j+2)); std::vector triangle; triangle.push_back(v0); triangle.push_back(v1); triangle.push_back(v2); int ev0 = -1; for (unsigned int k = 0; k < triangle.size(); ++k) { if (ignition::math::Vector2d(triangle[k].X(), triangle[k].Y()) == edgeV0) { // found a vertex in triangle that matches the vertex of the edge ev0 = k; break; } } if (ev0 >=0) { int ev1 = -1; int ev2 = -1; for (unsigned int k = 0; k < triangle.size()-1; ++k) { int index = (ev0 + k + 1) % triangle.size(); ignition::math::Vector3d triV = triangle[index]; if (math::Vector2d(triV.X(), triV.Y()) == edgeV1) { // found another vertex in triangle that matches the vertex of the // other edge. ev1 = index; // Store the index of the third triangle vertex. // It's either 0, 1, or 2. Find it using simple bitwise operation. ev2 = ~(ev1 | ev0) & 0x03; break; } } if (ev1 >= 0 && ev2 >= 0 && ev0 != ev1 && ev0 != ev2) { // Found an edge in triangle that matches the exterior edge. // Now find its normal. ignition::math::Vector3d edgeVec = triangle[ev0] - triangle[ev1]; edgeVec.Normalize(); ignition::math::Vector3d normal(edgeVec.Y(), -edgeVec.X(), 0); ignition::math::Vector3d otherEdgeVec = triangle[ev0] - triangle[ev2]; otherEdgeVec.Normalize(); double angle0 = otherEdgeVec.Dot(normal); double angle1 = otherEdgeVec.Dot(-normal); if (angle0 > angle1) { if (angle0 >= 0) normals.push_back(normal); } else { if (angle1 >= 0) normals.push_back(-normal); } } } } } // number of exterior edge normals found should be equal to the number of // exterior edges if (normals.size() != edges.size()) { gzerr << "Unable to extrude mesh. Triangulation failed" << std::endl; delete mesh; return; } unsigned int numVertices = subMesh->GetVertexCount(); // add normal for bottom face for (unsigned int i = 0; i < numVertices; ++i) subMesh->AddNormal(-ignition::math::Vector3d::UnitZ); // create the top face for (unsigned int i = 0; i < numVertices; ++i) { ignition::math::Vector3d v = subMesh->Vertex(i); subMesh->AddVertex(v.X(), v.Y(), _height); subMesh->AddNormal(ignition::math::Vector3d::UnitZ); } unsigned int numIndices = subMesh->GetIndexCount(); for (unsigned int i = 0; i < numIndices; i+=3) { unsigned int i0 = subMesh->GetIndex(i); unsigned int i1 = subMesh->GetIndex(i+1); unsigned int i2 = subMesh->GetIndex(i+2); subMesh->AddIndex(numVertices+i0); subMesh->AddIndex(numVertices+i2); subMesh->AddIndex(numVertices+i1); } // create the side faces for (unsigned int i = 0; i < edges.size(); ++i) { // we retrieve each edge's coordinates int i0 = edges[i][0]; int i1 = edges[i][1]; ignition::math::Vector2d v0 = vertices[i0]; ignition::math::Vector2d v1 = vertices[i1]; ignition::math::Vector2d edge2d = v1 - v0; ignition::math::Vector3d edge(edge2d.X(), edge2d.Y(), 0); ignition::math::Vector3d cross = edge.Cross(normals[i]); unsigned int vCount = subMesh->GetVertexCount(); subMesh->AddVertex(ignition::math::Vector3d(v0.X(), v0.Y(), 0)); if (cross.Z() >0) { subMesh->AddVertex(ignition::math::Vector3d(v0.X(), v0.Y(), _height)); subMesh->AddVertex(ignition::math::Vector3d(v1.X(), v1.Y(), _height)); } else { subMesh->AddVertex(ignition::math::Vector3d(v1.X(), v1.Y(), _height)); subMesh->AddVertex(ignition::math::Vector3d(v0.X(), v0.Y(), _height)); } subMesh->AddVertex(ignition::math::Vector3d(v0.X(), v0.Y(), 0)); if (cross.Z() >0) { subMesh->AddVertex(ignition::math::Vector3d(v1.X(), v1.Y(), _height)); subMesh->AddVertex(ignition::math::Vector3d(v1.X(), v1.Y(), 0)); } else { subMesh->AddVertex(ignition::math::Vector3d(v1.X(), v1.Y(), 0)); subMesh->AddVertex(ignition::math::Vector3d(v1.X(), v1.Y(), _height)); } for (unsigned int j = 0; j < 6; ++j) { subMesh->AddIndex(vCount++); subMesh->AddNormal(normals[i]); } } this->meshes.insert(std::make_pair(_name, mesh)); return; } ////////////////////////////////////////////////// void MeshManager::CreateCamera(const std::string &_name, float _scale) { int i, k; if (this->HasMesh(_name)) { return; } Mesh *mesh = new Mesh(); mesh->SetName(_name); this->meshes.insert(std::make_pair(_name, mesh)); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); // Vertex values float v[8][3] = { {-1, -1, -1}, {-1, -1, +1}, {+1, -1, +1}, {+1, -1, -1}, {-1, +1, -1}, {-1, +1, +1}, {+1, +1, +1}, {+1, +1, -1} }; // Normals for each vertex float n[8][3]= { {-0.577350, -0.577350, -0.577350}, {-0.577350, -0.577350, 0.577350}, {0.577350, -0.577350, 0.577350}, {0.577350, -0.577350, -0.577350}, {-0.577350, 0.577350, -0.577350}, {-0.577350, 0.577350, 0.577350}, {0.577350, 0.577350, 0.577350}, {0.577350, 0.577350, -0.577350} }; // Texture coords /*float t[4][2] = { {uvCoords.X(), 0}, {0, 0}, {0, uvCoords.Y()}, {uvCoords.X(), uvCoords.Y()} };*/ // Vertices int faces[6][4] = { {2, 1, 0, 3}, {5, 6, 7, 4}, {2, 6, 5, 1}, {1, 5, 4, 0}, {0, 4, 7, 3}, {6, 2, 3, 7} }; // Indices int ind[36] = { 0, 1, 2, 2, 3, 0, 4, 5, 7, 7, 5, 6, 11, 8, 9, 9, 10, 11, 12, 13, 15, 15, 13, 14, 16, 17, 18, 18, 19, 16, 21, 22, 23, 23, 20, 21, }; // Compute the vertices for (i = 0; i < 8; ++i) { v[i][0] *= _scale * 0.5; v[i][1] *= _scale * 0.5; v[i][2] *= _scale * 0.5; } // For each face for (i = 0; i < 6; ++i) { // For each vertex in the face for (k = 0; k < 4; k++) { subMesh->AddVertex(v[faces[i][k]][0], v[faces[i][k]][1], v[faces[i][k]][2]); subMesh->AddNormal(n[faces[i][k]][0], n[faces[i][k]][1], n[faces[i][k]][2]); // subMesh->AddTexCoord(t[k][0], t[k][1]); } } // Set the indices for (i = 0; i < 36; ++i) subMesh->AddIndex(ind[i]); mesh->RecalculateNormals(); } ////////////////////////////////////////////////// void MeshManager::CreateCylinder(const std::string &name, float radius, float height, int rings, int segments) { ignition::math::Vector3d vert, norm; unsigned int verticeIndex = 0; int ring, seg; float deltaSegAngle = (2.0 * M_PI / segments); if (this->HasMesh(name)) { return; } Mesh *mesh = new Mesh(); mesh->SetName(name); this->meshes.insert(std::make_pair(name, mesh)); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); // Generate the group of rings for the cylinder for (ring = 0; ring <= rings; ++ring) { vert.Z() = ring * height/rings - height/2.0; // Generate the group of segments for the current ring for (seg = 0; seg <= segments; ++seg) { vert.Y() = radius * cosf(seg * deltaSegAngle); vert.X() = radius * sinf(seg * deltaSegAngle); // TODO: Don't think these normals are correct. norm = vert; norm.Z() = 0; norm.Normalize(); // Add one vertex to the strip which makes up the sphere subMesh->AddVertex(vert); subMesh->AddNormal(norm); subMesh->AddTexCoord( static_cast(seg) / static_cast(segments), static_cast(ring) / static_cast(rings)); if (ring != rings) { // each vertex (except the last) has six indices pointing to it subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex + segments); subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex + 1); subMesh->AddIndex(verticeIndex); verticeIndex++; } } } // This block generates the top cap { vert.Z() = height/2.0; // Generate the group of segments for the top ring for (seg = 0; seg <= segments; ++seg) { vert.Y() = radius * cosf(seg * deltaSegAngle); vert.X() = radius * sinf(seg * deltaSegAngle); subMesh->AddVertex(vert); subMesh->AddNormal(0, 0, 1); subMesh->AddTexCoord( static_cast(seg) / static_cast(segments), 1.0); } // The top-middle cap vertex subMesh->AddVertex(0, 0, height/2.0); subMesh->AddNormal(0, 0, 1); subMesh->AddTexCoord(0, 0); // Create the top fan verticeIndex = subMesh->GetVertexCount()-1; for (seg = 0; seg < segments; seg++) { subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex - segments + seg); subMesh->AddIndex(verticeIndex - segments + seg - 1); } } // This block generates the bottom cap { vert.Z() = -height/2.0; // Generate the group of segments for the bottom ring for (seg = 0; seg <= segments; ++seg) { vert.Y() = radius * cosf(seg * deltaSegAngle); vert.X() = radius * sinf(seg * deltaSegAngle); subMesh->AddVertex(vert); subMesh->AddNormal(0, 0, -1); subMesh->AddTexCoord( static_cast(seg) / static_cast(segments), 0.0); } // The bottom-middle cap vertex subMesh->AddVertex(0, 0, -height/2.0); subMesh->AddNormal(0, 0, -1); subMesh->AddTexCoord(0, 0); // Create the bottom fan verticeIndex = subMesh->GetVertexCount()-1; for (seg = 0; seg < segments; seg++) { subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex - segments + seg - 1); subMesh->AddIndex(verticeIndex - segments + seg); } } } ////////////////////////////////////////////////// void MeshManager::CreateCone(const std::string &name, float radius, float height, int rings, int segments) { ignition::math::Vector3d vert, norm; unsigned int verticeIndex = 0; unsigned int i, j; int ring, seg; if (this->HasMesh(name)) { return; } Mesh *mesh = new Mesh(); mesh->SetName(name); this->meshes.insert(std::make_pair(name, mesh)); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); if (segments <3) segments = 3; float deltaSegAngle = (2.0 * M_PI / segments); // Generate the group of rings for the cone for (ring = 0; ring < rings; ring++) { vert.Z() = ring * height/rings - height/2.0; double ringRadius = ((height - (vert.Z()+height/2.0)) / height) * radius; // Generate the group of segments for the current ring for (seg = 0; seg <= segments; seg++) { vert.Y() = ringRadius * cosf(seg * deltaSegAngle); vert.X() = ringRadius * sinf(seg * deltaSegAngle); // TODO: Don't think these normals are correct. norm = vert; norm.Normalize(); // Add one vertex to the strip which makes up the sphere subMesh->AddVertex(vert); subMesh->AddNormal(norm); subMesh->AddTexCoord( static_cast(seg) / static_cast(segments), static_cast(ring) / static_cast(rings)); if (ring != (rings-1)) { // each vertex (except the last) has six indices pointing to it subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex + segments); subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex + 1); subMesh->AddIndex(verticeIndex); verticeIndex++; } } } /// The top point vertex subMesh->AddVertex(0, 0, height/2.0); subMesh->AddNormal(0, 0, 1); subMesh->AddTexCoord(0, 0); // The bottom cap vertex subMesh->AddVertex(0, 0, -height/2.0); subMesh->AddNormal(0, 0, -1); subMesh->AddTexCoord(0, 0); // Create the top fan verticeIndex += segments+1; for (seg = 0; seg < segments; seg++) { subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex - segments + seg); subMesh->AddIndex(verticeIndex - segments + seg - 1); } // Create the bottom fan verticeIndex++; for (seg = 0; seg < segments; seg++) { subMesh->AddIndex(verticeIndex); subMesh->AddIndex(seg); subMesh->AddIndex(seg+1); } // Fix all the normals for (i = 0; i + 3 < subMesh->GetIndexCount(); i += 3) { norm.Set(); for (j = 0; j < 3; ++j) norm += subMesh->Normal(subMesh->GetIndex(i+j)); norm /= 3; norm.Normalize(); for (j = 0; j < 3; ++j) subMesh->SetNormal(subMesh->GetIndex(i+j), norm); } mesh->RecalculateNormals(); } ////////////////////////////////////////////////// void MeshManager::CreateTube(const std::string &_name, float _innerRadius, float _outerRadius, float _height, int _rings, int _segments, double _arc) { ignition::math::Vector3d vert, norm; unsigned int verticeIndex = 0; int ring, seg; // Needs at lest 1 ring, and 3 segments int rings = std::max(_rings, 1); int segments = std::max(_segments, 3); float deltaSegAngle = (_arc / segments); float radius = 0; radius = _outerRadius; if (this->HasMesh(_name)) return; Mesh *mesh = new Mesh(); mesh->SetName(_name); this->meshes.insert(std::make_pair(_name, mesh)); SubMesh *subMesh = new SubMesh(); mesh->AddSubMesh(subMesh); // Generate the group of rings for the outsides of the cylinder for (ring = 0; ring <= rings; ++ring) { vert.Z() = ring * _height/rings - _height/2.0; // Generate the group of segments for the current ring for (seg = 0; seg <= segments; ++seg) { vert.Y() = radius * cosf(seg * deltaSegAngle); vert.X() = radius * sinf(seg * deltaSegAngle); // TODO: Don't think these normals are correct. norm = vert; norm.Normalize(); // Add one vertex to the strip which makes up the tube subMesh->AddVertex(vert); subMesh->AddNormal(norm); subMesh->AddTexCoord( static_cast(seg) / static_cast(segments), static_cast(ring) / static_cast(rings)); // outer triangles connecting ring [ring] to ring [ring + 1] if (ring != rings) { if (seg != 0) { subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex + segments); } if (seg != segments) { subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex + 1); subMesh->AddIndex(verticeIndex); } } // ring [rings] is the edge of the top cap else if (seg != segments) { // These indices form the top cap subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex+1); subMesh->AddIndex(verticeIndex+1); subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex + segments + 2); } // ring [0] is the edge of the bottom cap if (ring == 0 && seg < segments) { // These indices form the bottom cap subMesh->AddIndex(verticeIndex+1); subMesh->AddIndex(verticeIndex + (segments+1) * (((rings+1)*2)-1)); subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex + (segments+1) * (((rings+1)*2)-1) + 1); subMesh->AddIndex(verticeIndex + (segments+1) * (((rings+1)*2)-1)); subMesh->AddIndex(verticeIndex+1); } verticeIndex++; } } // Generate the group of rings for the inside of the cylinder radius = _innerRadius; for (ring = 0; ring <= rings; ++ring) { vert.Z() = (_height/2.0) - (ring * _height/rings); // Generate the group of segments for the current ring for (seg = 0; seg <= segments; ++seg) { vert.Y() = radius * cosf(seg * deltaSegAngle); vert.X() = radius * sinf(seg * deltaSegAngle); // TODO: Don't think these normals are correct. norm = vert; norm.Normalize(); // Add one vertex to the strip which makes up the tube subMesh->AddVertex(vert); subMesh->AddNormal(norm); subMesh->AddTexCoord( static_cast(seg) / static_cast(segments), static_cast(ring) / static_cast(rings)); // inner triangles connecting ring [ring] to ring [ring + 1] if (ring != rings) { // each vertex has six indices (2 triangles) if (seg != 0) { subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex); subMesh->AddIndex(verticeIndex + segments); } if (seg != segments) { subMesh->AddIndex(verticeIndex + segments + 1); subMesh->AddIndex(verticeIndex + 1); subMesh->AddIndex(verticeIndex); } } verticeIndex++; } } // Close ends in case it's not a full circle if (!ignition::math::equal(_arc, 2.0 * M_PI)) { for (ring = 0; ring < rings; ++ring) { // Close beginning subMesh->AddIndex((segments+1)*(ring+1)); subMesh->AddIndex((segments+1)*ring); subMesh->AddIndex((segments+1)*((rings+1)*2-2-ring)); subMesh->AddIndex((segments+1)*((rings+1)*2-2-ring)); subMesh->AddIndex((segments+1)*ring); subMesh->AddIndex((segments+1)*((rings+1)*2-1-ring)); // Close end subMesh->AddIndex((segments+1)*((rings+1)*2-2-ring)+segments); subMesh->AddIndex((segments+1)*((rings+1)*2-1-ring)+segments); subMesh->AddIndex((segments+1)*(ring+1)+segments); subMesh->AddIndex((segments+1)*(ring+1)+segments); subMesh->AddIndex((segments+1)*((rings+1)*2-1-ring)+segments); subMesh->AddIndex((segments+1)*ring+segments); } } mesh->RecalculateNormals(); } ////////////////////////////////////////////////// void MeshManager::Tesselate2DMesh(SubMesh *sm, int meshWidth, int meshHeight, bool doubleSided) { int vInc, v, iterations; int uCount; if (doubleSided) { iterations = 2; vInc = 1; v = 0; } else { iterations = 1; vInc = 1; v = 0; } int v1, v2, v3; while (iterations--) { // Make tris in a zigzag pattern (compatible with strips) int u = 0; int uInc = 1; int vCount = meshHeight - 1; while (vCount--) { uCount = meshWidth - 1; while (uCount--) { // First tri in cell v1 = ((v + vInc) * meshWidth) + u; v2 = (v * meshWidth) + u; v3 = ((v + vInc) * meshWidth) + (u + uInc); // Output indexes sm->AddIndex(v1); sm->AddIndex(v2); sm->AddIndex(v3); // Second Tri in cell v1 = ((v + vInc) * meshWidth) + (u + uInc); v2 = (v * meshWidth) + u; v3 = (v * meshWidth) + (u + uInc); // Output indexes sm->AddIndex(v1); sm->AddIndex(v2); sm->AddIndex(v3); // Next column u += uInc; } // Next row v += vInc; u = 0; } // Reverse vInc for double sided v = meshHeight - 1; vInc = -vInc; } } #ifdef HAVE_GTS ////////////////////////////////////////////////// void MeshManager::CreateBoolean(const std::string &_name, const Mesh *_m1, const Mesh *_m2, int _operation, const ignition::math::Pose3d &_offset) { if (this->HasMesh(_name)) return; MeshCSG csg; Mesh *mesh = csg.CreateBoolean(_m1, _m2, _operation, _offset); mesh->SetName(_name); this->meshes.insert(std::make_pair(_name, mesh)); } #endif ////////////////////////////////////////////////// size_t MeshManager::AddUniquePointToVerticesTable( std::vector &_vertices, const ignition::math::Vector2d &_p, double _tol) { double sqrTol = _tol * _tol; for (auto i = 0u; i != _vertices.size(); ++i) { auto v = _vertices[i] - _p; double d = (v.X() * v.X() + v.Y() * v.Y()); if ( d < sqrTol) { return i; } } _vertices.push_back(_p); size_t r = _vertices.size() -1; return r; } ////////////////////////////////////////////////// void MeshManager::ConvertPolylinesToVerticesAndEdges( const std::vector > &_polys, double _tol, std::vector &_vertices, std::vector &edges) { for (auto poly : _polys) { ignition::math::Vector2d previous = poly[0]; for (auto i = 1u; i != poly.size(); ++i) { auto p = poly[i]; auto startPointIndex = AddUniquePointToVerticesTable(_vertices, previous, _tol); auto endPointIndex = AddUniquePointToVerticesTable(_vertices, p, _tol); // current end point is now the starting point for the next edge previous = p; if (startPointIndex == endPointIndex) { gzwarn << "Ignoring edge without 2 distinct vertices" << std::endl; continue; } // add the new edge ignition::math::Vector2i e(startPointIndex, endPointIndex); edges.push_back(e); } } }