1 /* -------------------------------------------------------------------------- *
2  *                       Simbody(tm): SimTKcommon                             *
3  * -------------------------------------------------------------------------- *
4  * This is part of the SimTK biosimulation toolkit originating from           *
5  * Simbios, the NIH National Center for Physics-Based Simulation of           *
6  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
7  * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody.  *
8  *                                                                            *
9  * Portions copyright (c) 2008-13 Stanford University and the Authors.        *
10  * Authors: Peter Eastman                                                     *
11  * Contributors: Michael Sherman                                              *
12  *                                                                            *
13  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
14  * not use this file except in compliance with the License. You may obtain a  *
15  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
16  *                                                                            *
17  * Unless required by applicable law or agreed to in writing, software        *
18  * distributed under the License is distributed on an "AS IS" BASIS,          *
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
20  * See the License for the specific language governing permissions and        *
21  * limitations under the License.                                             *
22  * -------------------------------------------------------------------------- */
23 
24 #include "PolygonalMeshImpl.h"
25 #include "SimTKcommon/internal/Xml.h"
26 #include "SimTKcommon/internal/String.h"
27 #include "SimTKcommon/internal/Pathname.h"
28 
29 #include <cassert>
30 #include <sstream>
31 #include <string>
32 #include <set>
33 #include <map>
34 #include <fstream>
35 
36 using namespace SimTK;
37 
38 //==============================================================================
39 //                            POLYGONAL MESH
40 //==============================================================================
41 
42 // If the handle is empty, reconstruct it to be an owner handle whose
43 // implementation is present but contains no vertices.
initializeHandleIfEmpty()44 void PolygonalMesh::initializeHandleIfEmpty() {
45     if (isEmptyHandle())
46         new(this) PolygonalMesh(new PolygonalMeshImpl());
47 }
48 
49 // default (shallow) copy constructor, copy assignment, destructor
50 
clear()51 void PolygonalMesh::clear() {
52     if (!isEmptyHandle()) updImpl().clear();
53 }
54 
getNumFaces() const55 int PolygonalMesh::getNumFaces() const {
56     return isEmptyHandle() ? 0 : getImpl().faceVertexStart.size()-1;
57 }
58 
getNumVertices() const59 int PolygonalMesh::getNumVertices() const {
60     return isEmptyHandle() ? 0 : getImpl().vertices.size();
61 }
62 
getVertexPosition(int vertex) const63 const Vec3& PolygonalMesh::getVertexPosition(int vertex) const {
64     assert(0 <= vertex && vertex < getNumVertices());
65     return getImpl().vertices[vertex];
66 }
67 
getNumVerticesForFace(int face) const68 int PolygonalMesh::getNumVerticesForFace(int face) const {
69     assert(0 <= face && face < getNumFaces());
70     const Array_<int>& faceVertexStart = getImpl().faceVertexStart;
71     return faceVertexStart[face+1]-faceVertexStart[face];
72 }
73 
getFaceVertex(int face,int vertex) const74 int PolygonalMesh::getFaceVertex(int face, int vertex) const {
75     assert(face >= 0 && face < getNumFaces());
76     assert(vertex >= 0 && vertex < getNumVerticesForFace(face));
77     return getImpl().faceVertexIndex[getImpl().faceVertexStart[face]+vertex];
78 }
79 
addVertex(const Vec3 & position)80 int PolygonalMesh::addVertex(const Vec3& position) {
81     initializeHandleIfEmpty();
82     updImpl().vertices.push_back(position);
83     return getImpl().vertices.size()-1;
84 }
85 
addFace(const Array_<int> & vertices)86 int PolygonalMesh::addFace(const Array_<int>& vertices) {
87     initializeHandleIfEmpty();
88     for (int i = 0; i < (int) vertices.size(); i++)
89         updImpl().faceVertexIndex.push_back(vertices[i]);
90 
91     // faceVertexStart is preloaded to have its first element 0 before any
92     // faces have been added. So the back() element of faceVertexStart is
93     // already the starting entry for the face we're adding.
94     // This is where the *next* face will begin.
95     updImpl().faceVertexStart.push_back(getImpl().faceVertexIndex.size());
96     // The current face start is now at end()-2 (back()-1).
97     return getImpl().faceVertexStart.size()-2;
98 }
99 
scaleMesh(Real scale)100 PolygonalMesh& PolygonalMesh::scaleMesh(Real scale) {
101     if (!isEmptyHandle()) {
102         Array_<Vec3>& vertices = updImpl().vertices;
103         for (int i = 0; i < (int) vertices.size(); i++)
104             vertices[i] *= scale;
105     }
106     return *this;
107 }
108 
transformMesh(const Transform & X_AM)109 PolygonalMesh& PolygonalMesh::transformMesh(const Transform& X_AM) {
110     if (!isEmptyHandle()) {
111         Array_<Vec3>& vertices = updImpl().vertices;
112         for (int i = 0; i < (int) vertices.size(); i++)
113             vertices[i] = X_AM*vertices[i];
114     }
115     return *this;
116 }
117 
118 //------------------------------------------------------------------------------
119 //                                 LOAD FILE
120 //------------------------------------------------------------------------------
loadFile(const String & pathname)121 void PolygonalMesh::loadFile(const String& pathname) {
122     std::string dir,fn,ext;
123     bool isAbsolutePath;
124     Pathname::deconstructPathname(pathname,isAbsolutePath,dir,fn,ext);
125     String lext = String::toLower(ext);
126 
127     if (lext==".obj") loadObjFile(pathname);
128     else if (lext==".vtp") loadVtpFile(pathname);
129     else if (lext==".stl"||lext==".stla") loadStlFile(pathname);
130     else {
131         SimTK_ERRCHK1_ALWAYS(!"unrecognized extension",
132             "PolygonalMesh::loadFile()",
133             "Unrecognized file extension on mesh file '%s':\n"
134             "  expected .obj, .stl, .stla, or .vtp.", pathname.c_str());
135     }
136 }
137 
138 //------------------------------------------------------------------------------
139 //                              LOAD OBJ FILE
140 //------------------------------------------------------------------------------
141 
142 // For the pathname signature just open and punt to the istream signature.
loadObjFile(const String & pathname)143 void PolygonalMesh::loadObjFile(const String& pathname) {
144     std::ifstream ifs(pathname);
145     SimTK_ERRCHK1_ALWAYS(ifs.good(), "PolygonalMesh::loadObjFile()",
146         "Failed to open file '%s'", pathname.c_str());
147     loadObjFile(ifs);
148     ifs.close();
149 }
150 
loadObjFile(std::istream & file)151 void PolygonalMesh::loadObjFile(std::istream& file) {
152     const char* methodName = "PolygonalMesh::loadObjFile()";
153     SimTK_ERRCHK_ALWAYS(file.good(), methodName,
154         "The supplied std::istream object was not in good condition"
155         " on entrance -- did you check whether it opened successfully?");
156 
157     std::string line;
158     Array_<int> indices;
159     int initialVertices = getNumVertices();
160     while (!file.eof()) {
161         SimTK_ERRCHK_ALWAYS(file.good(), methodName,
162             "An error occurred while reading the input file.");
163 
164         std::getline(file, line);
165         while (line.size() > 0 && line[line.size()-1] == '\\') {
166             line[line.size()-1] = ' ';
167             std::string continuation;
168             std::getline(file, continuation);
169             line += continuation;
170         }
171         std::stringstream s(line);
172         std::string command;
173         s >> command;
174         if (command == "v") {
175             // A vertex
176 
177             Real x, y, z;
178             s >> x;
179             s >> y;
180             s >> z;
181             SimTK_ERRCHK1_ALWAYS(!s.fail(), methodName,
182                 "Found invalid vertex description: %s", line.c_str());
183             addVertex(Vec3(x, y, z));
184         }
185         else if (command == "f") {
186             // A face
187 
188             indices.clear();
189             int index;
190             while (s >> index) {
191                 s.ignore(line.size(), ' ');
192                 if (index < 0)
193                     index += getNumVertices()-initialVertices;
194                 else
195                     index--;
196                 indices.push_back(index);
197             }
198             addFace(indices);
199         }
200     }
201 }
202 
203 
204 //------------------------------------------------------------------------------
205 //                              LOAD VTP FILE
206 //------------------------------------------------------------------------------
207 
208 /* Use our XML reader to parse VTK's PolyData file format and add the polygons
209 found there to whatever is currently in this PolygonalMesh object. OpenSim uses
210 this format for its geometric objects.
211 
212 Here is a somewhat stripped down and annotated version of Kitware's description
213 from vtk.org:
214 
215 All the metadata is case sensitive.
216 
217 PolyData -- Each PolyData piece specifies a set of points and cells
218 independently from the other pieces. [Simbody Note: we will read in only the
219 first Piece element.] The points are described explicitly by the
220 Points element. The cells are described explicitly by the Verts, Lines, Strips,
221 and Polys elements.
222     <VTKFile type="PolyData" ...>
223         <PolyData>
224             <Piece NumberOfPoints="#" NumberOfVerts="#" NumberOfLines="#"
225                    NumberOfStrips="#" NumberOfPolys="#">
226                 <PointData>...</PointData>
227                 <CellData>...</CellData>
228                 <Points>...</Points>
229                 <Verts>...</Verts>
230                 <Lines>...</Lines>
231                 <Strips>...</Strips>
232                 <Polys>...</Polys>
233             </Piece>
234         </PolyData>
235     </VTKFile>
236 
237 PointData and CellData -- Every dataset describes the data associated with
238 its points and cells with PointData and CellData XML elements as follows:
239     <PointData Scalars="Temperature" Vectors="Velocity">
240         <DataArray Name="Velocity" .../>
241         <DataArray Name="Temperature" .../>
242         <DataArray Name="Pressure" .../>
243     </PointData>
244 
245 VTK allows an arbitrary number of data arrays to be associated with the points
246 and cells of a dataset. Each data array is described by a DataArray element
247 which, among other things, gives each array a name. The following attributes
248 of PointData and CellData are used to specify the active arrays by name:
249     Scalars - The name of the active scalars array, if any.
250     Vectors - The name of the active vectors array, if any.
251     Normals - The name of the active normals array, if any.
252     Tensors - The name of the active tensors array, if any.
253     TCoords - The name of the active texture coordinates array, if any.
254 That is, for each attribute of the form Sometype="Somename" there must be a
255 DataArray element with attribute Name="Somename" whose text contains
256 NumberOfPoints values each of type Sometype.
257 
258 Points -- The Points element explicitly defines coordinates for each point
259 individually. It contains one DataArray element describing an array with
260 three components per value, each specifying the coordinates of one point.
261     <Points>
262         <DataArray NumberOfComponents="3" .../>
263     </Points>
264 
265 Verts, Lines, Strips, and Polys -- The Verts, Lines, Strips, and Polys elements
266 define cells explicitly by specifying point connectivity. Cell types are
267 implicitly known by the type of element in which they are specified. Each
268 element contains two DataArray elements. The first array specifies the point
269 connectivity. All the cells' point lists are concatenated together. The second
270 array specifies the offset into the connectivity array for the end of each
271 cell.
272     <Polys>
273         <DataArray type="Int32" Name="connectivity" .../>
274         <DataArray type="Int32" Name="offsets" .../>
275     </Polys>
276 (same format for Verts, Lines, and Strips)
277 
278 DataArray -- All of the data and geometry specifications use DataArray elements
279 to describe their actual content as follows:
280 
281 The DataArray element stores a sequence of values of one type. There may be
282 one or more components per value. [Simbody Note: there are also "binary" and
283 "appended" formats which we do not support -- be sure to check that the
284 format attribute for every DataArray is "ascii".]
285     <DataArray type="Int32" Name="offsets" format="ascii">
286     10 20 30 ... </DataArray>
287 
288 The attributes of the DataArray elements are described as follows:
289     type -- The data type of a single component of the array. This is one of
290         Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float32,
291         Float64.
292     Name -- The name of the array. This is usually a brief description of the
293         data stored in the array. [Note that the PolyData element uses the
294         DataArray Name attribute to figure out what's being provided.]
295     NumberOfComponents -- The number of components per value in the array.
296     format -- The means by which the data values themselves are stored in the
297         file. This is "ascii", "binary", or "appended". [Simbody only supports
298         "ascii".]
299     format="ascii" -- The data are listed in ASCII directly inside the
300         DataArray element. Whitespace is used for separation.
301 */
loadVtpFile(const String & pathname)302 void PolygonalMesh::loadVtpFile(const String& pathname) {
303   try
304   { const char* method = "PolygonalMesh::loadVtpFile()";
305     Xml::Document vtp(pathname);
306     // The file has been read in and parsed into memory by the Xml system.
307 
308     SimTK_ERRCHK1_ALWAYS(vtp.getRootTag() == "VTKFile", method,
309         "Expected to see document tag <VTKFile> but saw <%s> instead.",
310         vtp.getRootTag().c_str());
311     // This is a VTKFile document.
312 
313     Xml::Element root = vtp.getRootElement();
314     SimTK_ERRCHK1_ALWAYS(root.getRequiredAttributeValue("type") == "PolyData",
315         method, "Expected VTK file type='PolyData' but got type='%s'.",
316         root.getRequiredAttributeValue("type").c_str());
317     // This is a VTK PolyData document.
318 
319     Xml::Element polydata = root.getRequiredElement("PolyData");
320     Xml::Element piece    = polydata.getRequiredElement("Piece");
321     Xml::Element points   = piece.getRequiredElement("Points");
322     const int numPoints =
323         piece.getRequiredAttributeValueAs<int>("NumberOfPoints");
324     const int numPolys  =
325         piece.getRequiredAttributeValueAs<int>("NumberOfPolys");
326 
327     // Remember this because we'll have to use it to adjust the indices we use
328     // when referencing the vertices we're about to read in. This number is
329     // the index that our first vertex will be assigned.
330     // EDIT: We actually don't use this variable. Leaving for reference.
331     // TODO const int firstVertex = getNumVertices();
332 
333     // The lone DataArray element in the Points element contains the points'
334     // coordinates. Read it in as a Vector of Vec3s.
335     Xml::Element pointData = points.getRequiredElement("DataArray");
336     SimTK_ERRCHK1_ALWAYS(pointData.getRequiredAttributeValue("format")
337                          == "ascii", method,
338         "Only format=\"ascii\" is supported for .vtp file DataArray elements,"
339         " got format=\"%s\" for Points DataArray.",
340         pointData.getRequiredAttributeValue("format").c_str());
341 
342     Vector_<Vec3> coords =
343         points.getRequiredElementValueAs< Vector_<Vec3> >("DataArray");
344 
345     SimTK_ERRCHK2_ALWAYS(coords.size() == numPoints, method,
346         "Expected coordinates for %d points but got %d.",
347         numPoints, coords.size());
348 
349     // Now that we have the point coordinates, use them to create the vertices
350     // in our mesh.
351     for (int i=0; i < numPoints; ++i)
352         addVertex(coords[i]);
353 
354     // Polys are given by a connectivity array which lists the points forming
355     // each polygon in a long unstructured list, then an offsets array, one per
356     // polygon, which gives the index+1 of the *last* connectivity entry for
357     // each polygon.
358     Xml::Element polys = piece.getRequiredElement("Polys");
359 
360     // Find the connectivity and offset DataArrays.
361     Xml::Element econnectivity, eoffsets;
362     for (Xml::element_iterator p = polys.element_begin("DataArray");
363          p != polys.element_end(); ++p)
364     {
365         const String& name = p->getRequiredAttributeValue("Name");
366         SimTK_ERRCHK2_ALWAYS(p->getRequiredAttributeValue("format")
367                              == "ascii", method,
368             "Only format=\"ascii\" is supported for .vtp file DataArray"
369             " elements, but format=\"%s\" for DataArray '%s'.",
370             p->getRequiredAttributeValue("format").c_str(), name.c_str());
371 
372         if (name == "connectivity") econnectivity = *p;
373         else if (name == "offsets") eoffsets = *p;
374     }
375 
376     SimTK_ERRCHK_ALWAYS(econnectivity.isValid() && eoffsets.isValid(), method,
377         "Expected to find a DataArray with name='connectivity' and one with"
378         " name='offsets' in the VTK PolyData file's <Polys> element but at"
379         " least one of them was missing.");
380 
381     // Read in the arrays.
382     Array_<int> offsets = eoffsets.getValueAs< Array_<int> >();
383     // Size may have changed if file is bad.
384     SimTK_ERRCHK2_ALWAYS(offsets.size() == numPolys, method,
385         "The number of offsets (%d) should have matched the stated "
386         " NumberOfPolys value (%d).", offsets.size(), numPolys);
387 
388     // We expect that the last entry in the offsets array is one past the
389     // end of the last polygon described in the connectivity array and hence
390     // is the size of the connectivity array.
391     const int expectedSize = numPolys ? offsets.back() : 0;
392     Array_<int> connectivity = econnectivity.getValueAs< Array_<int> >();
393 
394     SimTK_ERRCHK2_ALWAYS(connectivity.size()==expectedSize, method,
395         "The connectivity array was the wrong size (%d). It should"
396         " match the last entry in the offsets array which was %d.",
397         connectivity.size(), expectedSize);
398 
399     int startPoly = 0;
400     for (int i=0; i < numPolys; ++i) {
401         // Now read in the face in [startOffs,endOffs]
402         addFace(connectivity(startPoly, offsets[i]-startPoly));
403         startPoly = offsets[i]; // move to the next poly
404     }
405 
406   } catch (const std::exception& e) {
407       // This will throw a new exception with an enhanced message that
408       // includes the original one.
409       SimTK_ERRCHK2_ALWAYS(!"failed", "PolygonalMesh::loadVtpFile()",
410           "Attempt to load a VTK PolyData (.vtp) file from file name"
411           " '%s' failed with message:\n  %s", pathname.c_str(), e.what());
412   }
413 }
414 
415 //------------------------------------------------------------------------------
416 //                              VERTEX MAP
417 //------------------------------------------------------------------------------
418 // This is a local utility class for use in weeding out duplicate vertices.
419 // Set an appropriate tolerance in the constructor, then vertices all of
420 // whose coordinates are within tol can be considered the same vertex.
421 // This uses a map to make the complexity n log n.
422 namespace {
423 struct VertKey {
VertKey__anonc1e9142d0111::VertKey424     VertKey(const Vec3& v, Real tol=SignificantReal) : v(v), tol(tol) {}
operator <__anonc1e9142d0111::VertKey425     bool operator<(const VertKey& other) const {
426         const Vec3 diff = v - other.v;
427         if (diff[0] < -tol) return true;
428         if (diff[0] >  tol) return false;
429         if (diff[1] < -tol) return true;
430         if (diff[1] >  tol) return false;
431         if (diff[2] < -tol) return true;
432         if (diff[2] >  tol) return false;
433         return false; // they are numerically equal
434     }
435     Vec3 v;
436     Real tol;
437 };
438 typedef std::map<VertKey,int> VertMap;
439 }
440 
441 //------------------------------------------------------------------------------
442 //                              LOAD STL FILE
443 //------------------------------------------------------------------------------
444 namespace {
445 
446 class STLFile {
447 public:
STLFile(const String & pathname,const PolygonalMesh & mesh)448     STLFile(const String& pathname, const PolygonalMesh& mesh)
449     :   m_pathname(pathname), m_pathcstr(pathname.c_str()),
450         m_vertexTol(NTraits<float>::getSignificant()),
451         m_lineNo(0), m_sigLineNo(0)
452     {   preLoadVertMap(mesh); }
453 
454     // Examine file contents to determine whether this is an ascii-format
455     // STL; otherwise it is binary.
456     bool isStlAsciiFormat();
457 
458     void loadStlAsciiFile(PolygonalMesh& mesh);
459     void loadStlBinaryFile(PolygonalMesh& mesh);
460 
461 private:
462     bool getSignificantLine(bool eofOK);
463 
464     // If we're appending to an existing mesh we'll need to preload the
465     // vertex map with the existing vertices.
preLoadVertMap(const PolygonalMesh & mesh)466     void preLoadVertMap(const PolygonalMesh& mesh) {
467         for (int i=0; i < mesh.getNumVertices(); ++i) {
468             const Vec3& v = mesh.getVertexPosition(i);
469             m_vertMap.insert(std::make_pair(VertKey(v,m_vertexTol), i));
470         }
471     }
472 
473     // Look for a vertex close enough to this one and return its index if found,
474     // otherwise add to the mesh.
getVertex(const Vec3 & v,PolygonalMesh & mesh)475     int getVertex(const Vec3& v, PolygonalMesh& mesh) {
476         const VertKey key(v, m_vertexTol);
477         VertMap::const_iterator p = m_vertMap.find(key);
478         int ix;
479         if (p != m_vertMap.end())
480             ix = p->second;
481         else {
482             ix = mesh.addVertex(v);
483             m_vertMap.insert(std::make_pair(key,ix));
484         }
485         return ix;
486     }
487 
488     // The ascii/binary determination reads some lines; counts must restart.
resetLineCounts()489     void resetLineCounts() {m_lineNo=m_sigLineNo=0;}
490 
491     const String&     m_pathname;
492     const char* const m_pathcstr;
493     const Real        m_vertexTol;
494     VertMap           m_vertMap;
495 
496     std::ifstream     m_ifs;
497     int               m_lineNo;         // current line in file
498     int               m_sigLineNo;      // line # not counting blanks, comments
499     String            m_keyword;        // first non-blank token on line
500     std::stringstream m_restOfLine;     // full line except first token
501 };
502 
503 }
504 
loadStlFile(const String & pathname)505 void PolygonalMesh::loadStlFile(const String& pathname) {
506     bool isAbsolutePath;
507     std::string directory, fileName, extension;
508     Pathname::deconstructPathname(pathname, isAbsolutePath,
509                                   directory, fileName, extension);
510     const bool hasAsciiExt = String::toLower(extension) == ".stla";
511 
512     STLFile stlfile(pathname, *this);
513 
514     if (hasAsciiExt || stlfile.isStlAsciiFormat()) {
515         stlfile.loadStlAsciiFile(*this);
516     } else {
517         stlfile.loadStlBinaryFile(*this);
518     }
519 }
520 
521 // The standard format for an ASCII STL file is:
522 //
523 //   solid name
524 //   facet normal ni nj nk
525 //       outer loop
526 //           vertex v1x v1y v1z
527 //           vertex v2x v2y v2z
528 //           vertex v3x v3y v3z
529 //       endloop
530 //   endfacet
531 //   ...
532 //   endsolid name
533 //
534 // The "..." indicates that the facet/endfacet block repeats for each face. We
535 // will ignore the normal on the facet line and don't care if it is present.
536 // The 'name' is optional and we ignore it. Extra whitespace and case are
537 // ignored. We'll recognize "facetnormal" and "outerloop" if the spaces are
538 // missing; apparently that was once allowed.
539 //
540 // Extensions (mostly for compatibility with Gazebo's STL reader):
541 // - Allow comment lines that begin with #, !, or $; they are skipped.
542 // - Allow lines beginning with 'color'; they are ignored.
543 // - Allow negative numbers in vertices (stl standard says only +ve).
544 // - Allow more than three vertices per face.
545 // - Allow 'outer loop'/'endloop' to be left out.
546 //
547 // If there are multiple solids in the STL file we'll just read the first one.
548 
549 // We have to decide if this is really an ascii format stl; it might be
550 // binary. Unfortunately, some binary stl files also start with 'solid' so
551 // that isn't enough. We will simply try to parse the file as ascii and then
552 // if that leads to an inconsistency will try binary instead.
isStlAsciiFormat()553 bool STLFile::isStlAsciiFormat() {
554     m_ifs.open(m_pathname);
555     SimTK_ERRCHK1_ALWAYS(m_ifs.good(), "PolygonalMesh::loadStlFile()",
556         "Can't open file '%s'", m_pathcstr);
557 
558     bool isAscii = false;
559     if (getSignificantLine(true) && m_keyword == "solid") {
560         // Still might be binary. Look for a "facet" or "endsolid" line.
561         while (getSignificantLine(true)) {
562             if (m_keyword=="color") continue; // ignore
563             isAscii = (   m_keyword=="facet"
564                        || m_keyword=="facetnormal"
565                        || m_keyword=="endsolid");
566             break;
567         }
568     }
569 
570     m_ifs.close();
571     resetLineCounts();
572     return isAscii;
573 }
574 
575 
loadStlAsciiFile(PolygonalMesh & mesh)576 void STLFile::loadStlAsciiFile(PolygonalMesh& mesh) {
577     m_ifs.open(m_pathname);
578     SimTK_ERRCHK1_ALWAYS(m_ifs.good(), "PolygonalMesh::loadStlFile()",
579         "Can't open file '%s'", m_pathcstr);
580 
581     Array_<int> vertices;
582 
583     // Don't allow EOF until we've seen two significant lines.
584     while (getSignificantLine(m_sigLineNo >= 2)) {
585         if (m_sigLineNo==1 && m_keyword == "solid") continue;
586         if (m_sigLineNo>1 && m_keyword == "endsolid")
587             break;
588         if (m_keyword == "color") continue;
589 
590         if (m_keyword == "facet" || m_keyword == "facetnormal") {
591             // We're ignoring the normal on the facet line.
592             getSignificantLine(false);
593 
594             bool outerLoopSeen=false;
595             if (m_keyword=="outer" || m_keyword=="outerloop") {
596                 outerLoopSeen = true;
597                 getSignificantLine(false);
598             }
599 
600             // Now process vertices.
601             vertices.clear();
602             while (m_keyword == "vertex") {
603                 Vec3 vertex;
604                 m_restOfLine >> vertex;
605                 SimTK_ERRCHK2_ALWAYS(m_restOfLine.eof(),
606                     "PolygonalMesh::loadStlFile()",
607                     "Error at line %d in ASCII STL file '%s':\n"
608                     "  badly formed vertex.", m_lineNo, m_pathcstr);
609                 vertices.push_back(getVertex(vertex, mesh));
610                 getSignificantLine(false);
611             }
612 
613             // Next keyword is not "vertex".
614             SimTK_ERRCHK3_ALWAYS(vertices.size() >= 3,
615                 "PolygonalMesh::loadStlFile()",
616                 "Error at line %d in ASCII STL file '%s':\n"
617                 "  a facet had %d vertices; at least 3 required.",
618                 m_lineNo, m_pathcstr, vertices.size());
619 
620             mesh.addFace(vertices);
621 
622             // Vertices must end with 'endloop' if started with 'outer loop'.
623             if (outerLoopSeen) {
624                 SimTK_ERRCHK3_ALWAYS(m_keyword=="endloop",
625                     "PolygonalMesh::loadStlFile()",
626                     "Error at line %d in ASCII STL file '%s':\n"
627                     "  expected 'endloop' but got '%s'.",
628                     m_lineNo, m_pathcstr, m_keyword.c_str());
629                 getSignificantLine(false);
630             }
631 
632             // Now we expect 'endfacet'.
633             SimTK_ERRCHK3_ALWAYS(m_keyword=="endfacet",
634                 "PolygonalMesh::loadStlFile()",
635                 "Error at line %d in ASCII STL file '%s':\n"
636                 "  expected 'endfacet' but got '%s'.",
637                 m_lineNo, m_pathcstr, m_keyword.c_str());
638         }
639     }
640 
641     // We don't care if there is extra stuff in the file.
642     m_ifs.close();
643 }
644 
645 // This is the binary STL format:
646 //   uint8[80] - Header (ignored)
647 //   uint32    - Number of triangles
648 //   for each triangle
649 //      float[3]    - normal vector (we ignore this)
650 //      float[3]    - vertex 1  (counterclockwise order about the normal)
651 //      float[3]    - vertex 2
652 //      float[3]    - vertex 3
653 //      uint16      - "attribute byte count" (ignored)
654 //   end
655 //
656 // TODO: the STL binary format is always little-endian, like an Intel chip.
657 // The code here won't work properly on a big endian machine!
loadStlBinaryFile(PolygonalMesh & mesh)658 void STLFile::loadStlBinaryFile(PolygonalMesh& mesh) {
659     // This should never fail since the above succeeded, but we'll check.
660     m_ifs.open(m_pathname, std::ios_base::binary);
661     SimTK_ERRCHK1_ALWAYS(m_ifs.good(), "PolygonalMesh::loadStlFile()",
662         "Can't open file '%s'", m_pathcstr);
663 
664     unsigned char header[80];
665     m_ifs.read((char*)header, 80);
666     SimTK_ERRCHK1_ALWAYS(m_ifs.good() && m_ifs.gcount()==80,
667         "PolygonalMesh::loadStlFile()", "Bad binary STL file '%s':\n"
668         "  couldn't read header.", m_pathcstr);
669 
670     unsigned nFaces;
671     m_ifs.read((char*)&nFaces, sizeof(unsigned));
672     SimTK_ERRCHK1_ALWAYS(m_ifs.good() && m_ifs.gcount()==sizeof(unsigned),
673         "PolygonalMesh::loadStlFile()", "Bad binary STL file '%s':\n"
674         "  couldn't read triangle count.", m_pathcstr);
675 
676     Array_<int> vertices(3);
677     float vbuf[3]; unsigned short sbuf;
678     const unsigned vz = 3*sizeof(float);
679     for (unsigned fx=0; fx < nFaces; ++fx) {
680         m_ifs.read((char*)vbuf, vz); // normal ignored
681         for (int vx=0; vx < 3; ++vx) {
682             m_ifs.read((char*)vbuf, vz);
683             SimTK_ERRCHK3_ALWAYS(m_ifs.good() && m_ifs.gcount()==vz,
684                 "PolygonalMesh::loadStlFile()", "Bad binary STL file '%s':\n"
685                 "  couldn't read vertex %d for face %d.", m_pathcstr, vx, fx);
686             const Vec3 vertex((Real)vbuf[0], (Real)vbuf[1], (Real)vbuf[2]);
687             vertices[vx] = getVertex(vertex, mesh);
688         }
689         mesh.addFace(vertices);
690         // Now read and toss the "attribute byte count".
691         m_ifs.read((char*)&sbuf,sizeof(short));
692         SimTK_ERRCHK2_ALWAYS(m_ifs.good() && m_ifs.gcount()==sizeof(short),
693             "PolygonalMesh::loadStlFile()", "Bad binary STL file '%s':\n"
694             "  couldn't read attribute for face %d.", m_pathcstr, fx);
695     }
696 
697     // We don't care if there is extra stuff in the file.
698     m_ifs.close();
699 }
700 
701 // Return the next line from the formatted input stream, ignoring blank
702 // lines and comment lines, and downshifting the returned keyword. Sets
703 // m_keyword and m_restOfLine and increments line counts. If eofOK==false,
704 // issues an error message if we hit EOF, otherwise it will quitely return
705 // false at EOF.
getSignificantLine(bool eofOK)706 bool STLFile::getSignificantLine(bool eofOK) {
707     std::string line;
708     std::getline(m_ifs, line);
709     while (m_ifs.good()) {
710         ++m_lineNo;
711         m_keyword = String::trimWhiteSpace(line); // using keyword as a temp
712         if (   m_keyword.empty()
713             || m_keyword[0]=='#' || m_keyword[0]=='!' || m_keyword[0]=='$')
714         {
715             std::getline(m_ifs, line);
716             continue; // blank or comment
717         }
718         // Found a significant line.
719         ++m_sigLineNo;
720         m_keyword.toLower();
721         m_restOfLine.clear();
722         m_restOfLine.str(m_keyword);
723         m_restOfLine >> m_keyword; // now it's the keyword at beginning of line
724         return true;
725     }
726 
727     SimTK_ERRCHK2_ALWAYS(!(m_ifs.fail()||m_ifs.bad()),
728         "PolygonalMesh::loadStlFile()",
729         "Error at line %d in ASCII STL file '%s':\n"
730         "  error while reading file.", m_lineNo, m_pathcstr);
731 
732     // Must be EOF.
733     SimTK_ERRCHK2_ALWAYS(eofOK, "PolygonalMesh::loadStlFile()",
734         "Error at line %d in ASCII STL file '%s':\n"
735         "  unexpected end of file.", m_lineNo, m_pathcstr);
736     return false;
737 }
738 
739 //------------------------------------------------------------------------------
740 //                            CREATE SPHERE MESH
741 //------------------------------------------------------------------------------
742 
743 // Use unnamed namespace to keep VertKey class and VertMap type private to
744 // this file, as well as a few helper functions.
745 namespace {
746 
747 
748     /* Search a list of vertices for one close enough to this one and
749     return its index if found, otherwise add to the end. */
getVertex(const Vec3 & v,VertMap & vmap,Array_<Vec3> & verts)750     int getVertex(const Vec3& v, VertMap& vmap, Array_<Vec3>& verts) {
751         VertMap::const_iterator p = vmap.find(VertKey(v));
752         if (p != vmap.end()) return p->second;
753         const int ix = (int)verts.size();
754         verts.push_back(v);
755         vmap.insert(std::make_pair(VertKey(v),ix));
756         return ix;
757     }
758 
759     /* Each face comes in as below, with vertices 0,1,2 on the surface
760     of a sphere or radius r centered at the origin. We bisect the edges to get
761     points a',b',c', then move out from the center to make points a,b,c
762     on the sphere.
763              1
764             /\
765            /  \
766         c /____\ b      Then construct new triangles
767          /\    /\            [0,b,a]
768         /  \  /  \           [a,b,c]
769        /____\/____\          [c,2,a]
770       2      a     0         [b,1,c]
771     */
refineSphere(Real r,VertMap & vmap,Array_<Vec3> & verts,Array_<int> & faces)772     void refineSphere(Real r, VertMap& vmap,
773                              Array_<Vec3>& verts, Array_<int>&  faces) {
774         assert(faces.size() % 3 == 0);
775         const int nVerts = faces.size(); // # face vertices on entry
776         for (int i=0; i < nVerts; i+=3) {
777             const int v0=faces[i], v1=faces[i+1], v2=faces[i+2];
778             const Vec3 a = r*UnitVec3(verts[v0]+verts[v2]);
779             const Vec3 b = r*UnitVec3(verts[v0]+verts[v1]);
780             const Vec3 c = r*UnitVec3(verts[v1]+verts[v2]);
781             const int va=getVertex(a,vmap,verts),
782                       vb=getVertex(b,vmap,verts),
783                       vc=getVertex(c,vmap,verts);
784             // Replace the existing face with the 0ba triangle, then add the
785             // rest. Refer to the above picture.
786             faces[i+1] = vb; faces[i+2] = va;
787             faces.push_back(va); faces.push_back(vb); faces.push_back(vc);//abc
788             faces.push_back(vc); faces.push_back(v2); faces.push_back(va);//c2a
789             faces.push_back(vb); faces.push_back(v1); faces.push_back(vc);//b1c
790         }
791     }
792 
793 
makeOctahedralMesh(const Vec3 & r,Array_<Vec3> & vertices,Array_<int> & faceIndices)794     void makeOctahedralMesh(const Vec3& r, Array_<Vec3>& vertices,
795                                    Array_<int>&  faceIndices) {
796         vertices.push_back(Vec3( r[0],  0,  0));   //0
797         vertices.push_back(Vec3(-r[0],  0,  0));   //1
798         vertices.push_back(Vec3( 0,  r[1],  0));   //2
799         vertices.push_back(Vec3( 0, -r[1],  0));   //3
800         vertices.push_back(Vec3( 0,  0,  r[2]));   //4
801         vertices.push_back(Vec3( 0,  0, -r[2]));   //5
802         int faces[8][3] = {{0, 2, 4}, {4, 2, 1}, {1, 2, 5}, {5, 2, 0},
803                            {4, 3, 0}, {1, 3, 4}, {5, 3, 1}, {0, 3, 5}};
804         for (int i = 0; i < 8; i++)
805             for (int j = 0; j < 3; j++)
806                 faceIndices.push_back(faces[i][j]);
807     }
808 
809 } // end unnamed namespace
810 
811 /*static*/ PolygonalMesh PolygonalMesh::
createSphereMesh(Real radius,int resolution)812 createSphereMesh(Real radius, int resolution) {
813     SimTK_ERRCHK1_ALWAYS(radius > 0, "PolygonalMesh::createSphereMesh()",
814         "Radius %g illegal.", radius);
815     SimTK_ERRCHK1_ALWAYS(resolution >= 0, "PolygonalMesh::createSphereMesh()",
816         "Resolution %d illegal.", resolution);
817 
818     Array_<Vec3> vertices;
819     Array_<int> faceIndices;
820     makeOctahedralMesh(Vec3(radius), vertices, faceIndices);
821 
822     VertMap vmap;
823     for (unsigned i=0; i < vertices.size(); ++i)
824         vmap[vertices[i]] = i;
825 
826     int level = resolution;
827     while (level > 0) {
828         refineSphere(radius, vmap, vertices, faceIndices);
829         --level;
830     }
831 
832     PolygonalMesh sphere;
833     for (unsigned i=0; i < vertices.size(); ++i)
834         sphere.addVertex(vertices[i]);
835     for (unsigned i=0; i < faceIndices.size(); i += 3) {
836         const Array_<int> verts(&faceIndices[i], &faceIndices[i]+3);
837         sphere.addFace(verts);
838     }
839 
840     return sphere; // just a shallow copy
841 }
842 
843 
844 //------------------------------------------------------------------------------
845 //                            CREATE BRICK MESH
846 //------------------------------------------------------------------------------
847 // Resolution 0 is just the four corners and six quads.
848 // Resolution n means divide longest edge with n extra vertices; then make the
849 // other faces about the same size.
850 
851 // Extend the unnamed namespace with another local function.
852 namespace {
853 
854     // Given the number of vertices along the x,y,z edges and the three (i,j,k)
855     // coordinates of a particular vertex, return a packed representation of the
856     // (i,j,k) location that we can use as an index. Using a long long here
857     // just to avoid trouble, an int probably would have been fine.
locCode(int nv[3],int i,int j,int k)858     long long locCode(int nv[3], int i, int j, int k)
859     {   return (long long)i*nv[1]*nv[2] + (long long)j*nv[2] + (long long)k; }
860 
861 } // end of unnamed namespace
862 
863 /*static*/ PolygonalMesh PolygonalMesh::
createBrickMesh(const Vec3 & halfDims,int resolution)864 createBrickMesh(const Vec3& halfDims, int resolution) {
865     SimTK_ERRCHK3_ALWAYS(halfDims > 0, "PolygonalMesh::createBrickMesh()",
866         "Bad brick dimensions %g %g %g.", halfDims[0], halfDims[1], halfDims[2]);
867     SimTK_ERRCHK1_ALWAYS(resolution >= 0, "PolygonalMesh::createBrickMesh()",
868         "Resolution %d illegal.", resolution);
869 
870     const Vec3 dims(2*halfDims);
871 
872     PolygonalMesh brick;
873     const Real longest = max(dims);
874     const Real edgeLengthTarget = longest/(resolution+1);
875     int nv[3]; // number of vertices along each edge
876     for (int i=0; i<3; ++i)
877         nv[i] = 1 + std::max((int)(dims[i]/edgeLengthTarget + 0.49), 1);
878     const Vec3 edgeLengths(dims[0]/(nv[0]-1), dims[1]/(nv[1]-1),
879                            dims[2]/(nv[2]-1));
880 
881     // Add regularly-spaced vertices on the surfaces.
882     std::map<long long,int> vertLoc2Vert; // map i,j,k -> vertex number
883     for (int i=0; i < nv[0]; ++i) {
884         bool xface = (i==0 || i==nv[0]-1);
885         const Real iloc = i*edgeLengths[0] - halfDims[0];
886         for (int j=0; j < nv[1]; ++j) {
887             bool yface = (j==0 || j==nv[1]-1);
888             const Real jloc = j*edgeLengths[1] - halfDims[1];
889             for (int k=0; k < nv[2]; ++k) {
890                 bool zface = (k==0 || k==nv[2]-1);
891                 if (!(xface||yface||zface))
892                     continue; // skip interior vertices
893                 const Real kloc = k*edgeLengths[2] - halfDims[2];
894                 const int vnum = brick.addVertex(Vec3(iloc,jloc,kloc));
895                 vertLoc2Vert[locCode(nv,i,j,k)] = vnum;
896             }
897         }
898     }
899 
900     // Add quad faces oriented with normal outwards.
901     Array_<int> face(4);
902 
903     // This is the -x surface (y,z rectangle).
904     for (int j=0; j < nv[1]-1; ++j)
905         for (int k=0; k < nv[2]-1; ++k) {
906             face[0] = vertLoc2Vert[locCode(nv,0,j,  k+1)];
907             face[1] = vertLoc2Vert[locCode(nv,0,j+1,k+1)];
908             face[2] = vertLoc2Vert[locCode(nv,0,j+1,k)];
909             face[3] = vertLoc2Vert[locCode(nv,0,j,  k)];
910             brick.addFace(face);
911         }
912     // This is the +x surface (y,z rectangle).
913     for (int j=0; j < nv[1]-1; ++j)
914         for (int k=0; k < nv[2]-1; ++k) {
915             face[3] = vertLoc2Vert[locCode(nv,nv[0]-1,j,  k+1)];
916             face[2] = vertLoc2Vert[locCode(nv,nv[0]-1,j+1,k+1)];
917             face[1] = vertLoc2Vert[locCode(nv,nv[0]-1,j+1,k)];
918             face[0] = vertLoc2Vert[locCode(nv,nv[0]-1,j,  k)];
919             brick.addFace(face);
920         }
921     // This is the -y surface (x,z rectangle).
922     for (int i=0; i < nv[0]-1; ++i)
923         for (int k=0; k < nv[2]-1; ++k) {
924             face[0] = vertLoc2Vert[locCode(nv,i,  0,k)];
925             face[1] = vertLoc2Vert[locCode(nv,i+1,0,k)];
926             face[2] = vertLoc2Vert[locCode(nv,i+1,0,k+1)];
927             face[3] = vertLoc2Vert[locCode(nv,i,  0,k+1)];
928             brick.addFace(face);
929         }
930     // This is the +y surface (x,z rectangle).
931     for (int i=0; i < nv[0]-1; ++i)
932         for (int k=0; k < nv[2]-1; ++k) {
933             face[3] = vertLoc2Vert[locCode(nv,i,  nv[1]-1,k)];
934             face[2] = vertLoc2Vert[locCode(nv,i+1,nv[1]-1,k)];
935             face[1] = vertLoc2Vert[locCode(nv,i+1,nv[1]-1,k+1)];
936             face[0] = vertLoc2Vert[locCode(nv,i,  nv[1]-1,k+1)];
937             brick.addFace(face);
938         }
939     // This is the -z surface (x,y rectangle).
940     for (int i=0; i < nv[0]-1; ++i)
941         for (int j=0; j < nv[1]-1; ++j) {
942             face[0] = vertLoc2Vert[locCode(nv,i,  j+1,0)];
943             face[1] = vertLoc2Vert[locCode(nv,i+1,j+1,0)];
944             face[2] = vertLoc2Vert[locCode(nv,i+1,j,  0)];
945             face[3] = vertLoc2Vert[locCode(nv,i,  j,  0)];
946             brick.addFace(face);
947         }
948     // This is the +z surface (x,y rectangle).
949     for (int i=0; i < nv[0]-1; ++i)
950         for (int j=0; j < nv[1]-1; ++j) {
951             face[3] = vertLoc2Vert[locCode(nv,i,  j+1,nv[2]-1)];
952             face[2] = vertLoc2Vert[locCode(nv,i+1,j+1,nv[2]-1)];
953             face[1] = vertLoc2Vert[locCode(nv,i+1,j,  nv[2]-1)];
954             face[0] = vertLoc2Vert[locCode(nv,i,  j,  nv[2]-1)];
955             brick.addFace(face);
956         }
957 
958     return brick; // just a shallow copy
959 }
960 
961 
962 //------------------------------------------------------------------------------
963 //                           CREATE CYLINDER MESH
964 //------------------------------------------------------------------------------
965 // Minimum end surface is a hexagon (resolution 0).
966 // Think of the axis as the +z axis, with the end caps in x-y planes at
967 // height -z and +z.
968 /*static*/ PolygonalMesh PolygonalMesh::
createCylinderMesh(const UnitVec3 & axis,Real radius,Real halfLength,int resolution)969 createCylinderMesh(const UnitVec3& axis, Real radius, Real halfLength,
970                    int resolution)
971 {
972     SimTK_ERRCHK1_ALWAYS(radius > 0, "PolygonalMesh::createCylinderMesh()",
973         "Bad radius %g.", radius);
974     SimTK_ERRCHK1_ALWAYS(halfLength > 0, "PolygonalMesh::createCylinderMesh()",
975         "Bad half length %g.", halfLength);
976     SimTK_ERRCHK1_ALWAYS(resolution >= 0, "PolygonalMesh::createCylinderMesh()",
977         "Resolution %d illegal.", resolution);
978 
979     int rezAround = 6*(resolution+1);
980 
981     Real angle = 2*Pi/rezAround;
982     Real chordLen = 2*radius*std::sin(angle/2);
983     int rezRadial = (int)(radius/chordLen+0.5);
984     Real edgeLenRad = radius/rezRadial;
985 
986     int rezAlong  = 1 + std::max((int)(halfLength/edgeLenRad + 0.5), 1);
987     Real edgeLenAlong = 2*halfLength/(rezAlong-1);
988 
989     PolygonalMesh cyl;
990     Rotation R_ZG = Rotation(axis, ZAxis);
991 
992     int nv[3] = {rezRadial,rezAround,rezAlong};
993     // Do the tube.
994     std::map<long long, int> rak2Vert;
995     for (int k=0; k < rezAlong; ++k) {
996         bool isEndCap = (k==0 || k==rezAlong-1);
997         Real z = -halfLength + k*edgeLenAlong;
998         for (int a=0; a < rezAround; ++a) {
999             Real x = edgeLenRad*std::sin(a*angle);
1000             Real y = edgeLenRad*std::cos(a*angle);
1001             for (int r=1; r <= rezRadial; ++r) {
1002                 if (r < rezRadial && !isEndCap)
1003                     continue; // skip interior vertices
1004                 int vnum = cyl.addVertex(R_ZG*Vec3(r*x,r*y,z));
1005                 rak2Vert[locCode(nv,r,a,k)] = vnum;
1006             }
1007         }
1008     }
1009 
1010     Array_<int> qface(4), tface(3);
1011     for (int a=0; a < rezAround; ++a) {
1012         int ap = (a+1)%rezAround;
1013         for (int k=0; k < rezAlong-1; ++k) {
1014             int r = rezRadial;
1015             qface[0] = rak2Vert[locCode(nv,r, a,k)];
1016             qface[1] = rak2Vert[locCode(nv,r, a,k+1)];
1017             qface[2] = rak2Vert[locCode(nv,r,ap,k+1)];
1018             qface[3] = rak2Vert[locCode(nv,r,ap,k)];
1019             cyl.addFace(qface);
1020         }
1021     }
1022 
1023     // Add central face vertices.
1024     rak2Vert[locCode(nv,0,0,0)] = cyl.addVertex(R_ZG*Vec3(0,0,-halfLength));
1025     rak2Vert[locCode(nv,0,0,rezAlong-1)] = cyl.addVertex(R_ZG*Vec3(0,0,halfLength));
1026 
1027     // Tri faces from center to first ring.
1028     for (int a=0; a < rezAround; ++a) {
1029         int ap = (a+1)%rezAround;
1030         tface[0] = rak2Vert[locCode(nv,0,0, 0)];
1031         tface[1] = rak2Vert[locCode(nv,1,a, 0)];
1032         tface[2] = rak2Vert[locCode(nv,1,ap,0)];
1033         cyl.addFace(tface);
1034         tface[0] = rak2Vert[locCode(nv,0,0, rezAlong-1)];
1035         tface[1] = rak2Vert[locCode(nv,1,ap, rezAlong-1)];
1036         tface[2] = rak2Vert[locCode(nv,1,a,rezAlong-1)];
1037         cyl.addFace(tface);
1038     }
1039 
1040 
1041     // Quad faces from first ring out.
1042     for (int a=0; a < rezAround; ++a) {
1043         int ap = (a+1)%rezAround;
1044         for (int r=1; r <= rezRadial-1; ++r) {
1045         qface[0] = rak2Vert[locCode(nv,r,  a, 0)];
1046         qface[1] = rak2Vert[locCode(nv,r+1,a, 0)];
1047         qface[2] = rak2Vert[locCode(nv,r+1,ap,0)];
1048         qface[3] = rak2Vert[locCode(nv,r,  ap,0)];
1049         cyl.addFace(qface);
1050         qface[3] = rak2Vert[locCode(nv,r,  a, rezAlong-1)];
1051         qface[2] = rak2Vert[locCode(nv,r+1,a, rezAlong-1)];
1052         qface[1] = rak2Vert[locCode(nv,r+1,ap,rezAlong-1)];
1053         qface[0] = rak2Vert[locCode(nv,r,  ap,rezAlong-1)];
1054         cyl.addFace(qface);
1055         }
1056     }
1057 
1058     return cyl; // just a shallow copy
1059 }
1060 
1061