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