1 //
2 //   Copyright 2018 DreamWorks Animation LLC.
3 //
4 //   Licensed under the Apache License, Version 2.0 (the "Apache License")
5 //   with the following modification; you may not use this file except in
6 //   compliance with the Apache License and the following modification to it:
7 //   Section 6. Trademarks. is deleted and replaced with:
8 //
9 //   6. Trademarks. This License does not grant permission to use the trade
10 //      names, trademarks, service marks, or product names of the Licensor
11 //      and its affiliates, except as required to comply with Section 4(c) of
12 //      the License and to reproduce the content of the NOTICE file.
13 //
14 //   You may obtain a copy of the Apache License at
15 //
16 //       http://www.apache.org/licenses/LICENSE-2.0
17 //
18 //   Unless required by applicable law or agreed to in writing, software
19 //   distributed under the Apache License with the above modification is
20 //   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 //   KIND, either express or implied. See the Apache License for the specific
22 //   language governing permissions and limitations under the Apache License.
23 //
24 
25 
26 //------------------------------------------------------------------------------
27 //  Tutorial description:
28 //
29 //      This tutorial shows how to manage the limit surface of a potentially
30 //      large mesh by creating groups of patches for selected faces of the
31 //      mesh.  Familiarity with construction and evaluation of a PatchTable
32 //      is assumed (see tutorial_5_1).
33 //
34 //      When the patches for a mesh do not need to be retained for further
35 //      use, e.g. when simply computing points for a tessellation, the time
36 //      and space required to construct a single large PatchTable can be
37 //      considerable.  By constructing, evaluating and discarding smaller
38 //      PatchTables for subsets of the mesh, the high transient memory cost
39 //      can be avoided when computed serially.  When computed in parallel,
40 //      there may be little memory savings, but the construction time can
41 //      then be distributed.
42 //
43 //      This tutorial creates simple geometry (currently a lattice of cubes)
44 //      that can be expanded in complexity with a simple multiplier.  The
45 //      collection of faces are then divided into a specified number of groups
46 //      from which patches will be constructed and evaluated.  A simple
47 //      tessellation (a triangle fan around the midpoint of each face) is then
48 //      written in Obj format to the standard output.
49 //
50 
51 #include "../../../regression/common/arg_utils.h"
52 #include "../../../regression/common/far_utils.h"
53 
54 #include <opensubdiv/far/topologyDescriptor.h>
55 #include <opensubdiv/far/primvarRefiner.h>
56 #include <opensubdiv/far/patchTableFactory.h>
57 #include <opensubdiv/far/patchMap.h>
58 #include <opensubdiv/far/ptexIndices.h>
59 
60 #include <cassert>
61 #include <cstdio>
62 #include <cstring>
63 #include <fstream>
64 #include <sstream>
65 
66 using namespace OpenSubdiv;
67 
68 using Far::Index;
69 
70 
71 //
72 //  Global utilities in this namespace are not relevant to the tutorial.
73 //  They simply serve to construct some default geometry to be processed
74 //  in the form of a TopologyRefiner and vector of vertex positions.
75 //
76 namespace {
77     //
78     //  Simple structs for (x,y,z) position and a 3-tuple for the set
79     //  of vertices of a triangle:
80     //
81     struct Pos {
Pos__anonad3a78940111::Pos82         Pos() { }
Pos__anonad3a78940111::Pos83         Pos(float x, float y, float z) { p[0] = x, p[1] = y, p[2] = z; }
84 
operator +__anonad3a78940111::Pos85         Pos operator+(Pos const & op) const {
86             return Pos(p[0] + op.p[0], p[1] + op.p[1], p[2] + op.p[2]);
87         }
88 
89         //  Clear() and AddWithWeight() required for interpolation:
Clear__anonad3a78940111::Pos90         void Clear( void * =0 ) { p[0] = p[1] = p[2] = 0.0f; }
91 
AddWithWeight__anonad3a78940111::Pos92         void AddWithWeight(Pos const & src, float weight) {
93             p[0] += weight * src.p[0];
94             p[1] += weight * src.p[1];
95             p[2] += weight * src.p[2];
96         }
97 
98         float p[3];
99     };
100     typedef std::vector<Pos> PosVector;
101 
102     struct Tri {
Tri__anonad3a78940111::Tri103         Tri() { }
Tri__anonad3a78940111::Tri104         Tri(int a, int b, int c) { v[0] = a, v[1] = b, v[2] = c; }
105 
106         int v[3];
107     };
108     typedef std::vector<Tri> TriVector;
109 
110 
111     //
112     //  Functions to populate the topology and geometry arrays with simple
113     //  shapes that we can multiply to increase complexity:
114     //
115     void
appendDefaultPrimitive(Pos const & origin,std::vector<int> & vertsPerFace,std::vector<Index> & faceVerts,std::vector<Pos> & positionsPerVert)116     appendDefaultPrimitive(Pos const &          origin,
117                            std::vector<int> &   vertsPerFace,
118                            std::vector<Index> & faceVerts,
119                            std::vector<Pos> &   positionsPerVert) {
120 
121         //  Local topology and position of a cube centered at origin:
122         static float const cubePositions[8][3] = { { -0.5f, -0.5f, -0.5f },
123                                                    { -0.5f,  0.5f, -0.5f },
124                                                    { -0.5f,  0.5f,  0.5f },
125                                                    { -0.5f, -0.5f,  0.5f },
126                                                    {  0.5f, -0.5f, -0.5f },
127                                                    {  0.5f,  0.5f, -0.5f },
128                                                    {  0.5f,  0.5f,  0.5f },
129                                                    {  0.5f, -0.5f,  0.5f } };
130 
131         static int const cubeFaceVerts[6][4] = { { 0, 3, 2, 1 },
132                                                  { 4, 5, 6, 7 },
133                                                  { 0, 4, 7, 3 },
134                                                  { 1, 2, 6, 5 },
135                                                  { 0, 1, 5, 4 },
136                                                  { 3, 7, 6, 2 } };
137 
138         //  Identify the next vertex before appending vertex positions:
139         int baseVertex = (int) positionsPerVert.size();
140 
141         for (int i = 0; i < 8; ++i) {
142             float const * p = cubePositions[i];
143             positionsPerVert.push_back(origin + Pos(p[0], p[1], p[2]));
144         }
145 
146         //  Append number of verts-per-face and face-vertices for each face:
147         for (int i = 0; i < 6; ++i) {
148             vertsPerFace.push_back(4);
149             for (int j = 0; j < 4; ++j) {
150                 faceVerts.push_back(baseVertex + cubeFaceVerts[i][j]);
151             }
152         }
153     }
154 
155     void
createDefaultGeometry(int multiplier,std::vector<int> & vertsPerFace,std::vector<Index> & faceVerts,std::vector<Pos> & positionsPerVert)156     createDefaultGeometry(int multiplier,
157                           std::vector<int> &   vertsPerFace,
158                           std::vector<Index> & faceVerts,
159                           std::vector<Pos> &   positionsPerVert) {
160 
161         //  Default primitive is currently a cube:
162         int const vertsPerPrimitive = 8;
163         int const facesPerPrimitive = 6;
164         int const faceVertsPerPrimitive = 24;
165 
166         int nPrimitives = multiplier * multiplier * multiplier;
167 
168         positionsPerVert.reserve(nPrimitives * vertsPerPrimitive);
169         vertsPerFace.reserve(nPrimitives * facesPerPrimitive);
170         faceVerts.reserve(nPrimitives * faceVertsPerPrimitive);
171 
172         for (int x = 0; x < multiplier; ++x) {
173             for (int y = 0; y < multiplier; ++y) {
174                 for (int z = 0; z < multiplier; ++z) {
175                     appendDefaultPrimitive(Pos(x * 2.0f, y * 2.0f, z * 2.0f),
176                         vertsPerFace, faceVerts, positionsPerVert);
177                 }
178             }
179         }
180     }
181 
182     //
183     //  Create a TopologyRefiner from default geometry created above:
184     //
185     Far::TopologyRefiner *
createTopologyRefinerDefault(int multiplier,PosVector & posVector)186     createTopologyRefinerDefault(int multiplier,
187                                  PosVector & posVector) {
188 
189         std::vector<int>   topVertsPerFace;
190         std::vector<Index> topFaceVerts;
191 
192         createDefaultGeometry(
193             multiplier, topVertsPerFace, topFaceVerts, posVector);
194 
195         typedef Far::TopologyDescriptor Descriptor;
196 
197         Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
198 
199         Sdc::Options options;
200         options.SetVtxBoundaryInterpolation(
201             Sdc::Options::VTX_BOUNDARY_EDGE_AND_CORNER);
202 
203         Descriptor desc;
204         desc.numVertices = (int) posVector.size();
205         desc.numFaces = (int) topVertsPerFace.size();
206         desc.numVertsPerFace = &topVertsPerFace[0];
207         desc.vertIndicesPerFace = &topFaceVerts[0];
208 
209         //  Instantiate a Far::TopologyRefiner from the descriptor.
210         Far::TopologyRefiner * refiner =
211             Far::TopologyRefinerFactory<Descriptor>::Create(desc,
212                 Far::TopologyRefinerFactory<Descriptor>::Options(
213                     type, options));
214 
215         if (refiner == 0) {
216             exit(EXIT_FAILURE);
217         }
218 
219         bool dumpDefaultGeometryToObj = false;
220         if (dumpDefaultGeometryToObj) {
221             int nVerts = (int) posVector.size();
222             for (int i = 0; i < nVerts; ++i) {
223                 float const * p = posVector[i].p;
224                 printf("v %f %f %f\n", p[0], p[1], p[2]);
225             }
226 
227             int const * fVerts = &topFaceVerts[0];
228             int nFaces = (int) topVertsPerFace.size();
229             for (int i = 0; i < nFaces; ++i) {
230                 printf("f");
231                 for (int j = 0; j < topVertsPerFace[i]; ++j) {
232                     printf(" %d", 1 + *fVerts++);
233                 }
234                 printf("\n");
235             }
236             exit(EXIT_SUCCESS);
237         }
238         return refiner;
239     }
240 
241     //
242     //  Create a TopologyRefiner from a specified Obj file:
243     //  geometry created internally:
244     //
245     Far::TopologyRefiner *
createTopologyRefinerFromObj(std::string const & objFileName,Sdc::SchemeType schemeType,PosVector & posVector)246     createTopologyRefinerFromObj(std::string const & objFileName,
247                                  Sdc::SchemeType schemeType,
248                                  PosVector & posVector) {
249 
250         const char *  filename = objFileName.c_str();
251         const Shape * shape = 0;
252 
253         std::ifstream ifs(filename);
254         if (ifs) {
255             std::stringstream ss;
256             ss << ifs.rdbuf();
257             ifs.close();
258             std::string shapeString = ss.str();
259 
260             shape = Shape::parseObj(shapeString.c_str(),
261                 ConvertSdcTypeToShapeScheme(schemeType), false);
262             if (shape == 0) {
263                 fprintf(stderr, "Error:  Cannot create Shape "
264                     "from .obj file '%s'\n", filename);
265                 return 0;
266             }
267         } else {
268             fprintf(stderr, "Error:  Cannot open .obj file '%s'\n", filename);
269             return 0;
270         }
271 
272         Sdc::SchemeType sdcType    = GetSdcType(*shape);
273         Sdc::Options    sdcOptions = GetSdcOptions(*shape);
274 
275         Far::TopologyRefiner * refiner =
276             Far::TopologyRefinerFactory<Shape>::Create(*shape,
277                 Far::TopologyRefinerFactory<Shape>::Options(
278                     sdcType, sdcOptions));
279         if (refiner == 0) {
280             fprintf(stderr, "Error:  Unable to construct TopologyRefiner "
281                 "from .obj file '%s'\n", filename);
282             return 0;
283         }
284 
285         int numVertices = refiner->GetNumVerticesTotal();
286         posVector.resize(numVertices);
287         std::memcpy(&posVector[0], &shape->verts[0], numVertices * sizeof(Pos));
288 
289         delete shape;
290         return refiner;
291     }
292 } // end namespace
293 
294 
295 //
296 //  The PatchGroup bundles objects used to create and evaluate a sparse set
297 //  of patches.  Its construction creates a PatchTable and all other objects
298 //  necessary to evaluate patches associated with the specified subset of
299 //  faces provided.  A simple method to tessellate a specified face is
300 //  provided.
301 //
302 //  Note that, since the data buffers for the base level and refined levels
303 //  are separate (we want to avoid copying primvar data for the base level
304 //  of a potentially large mesh), that patch evaluation needs to account
305 //  for the separation when combining control points.
306 //
307 struct PatchGroup {
308     PatchGroup(Far::PatchTableFactory::Options patchOptions,
309                Far::TopologyRefiner const &    baseRefinerArg,
310                Far::PtexIndices const &        basePtexIndicesArg,
311                std::vector<Pos> const &        basePositionsArg,
312                std::vector<Index> const &      baseFacesArg);
313     ~PatchGroup();
314 
315     void TessellateBaseFace(int face, PosVector & tessPoints,
316                                       TriVector & tessTris) const;
317 
318     //  Const reference members:
319     Far::TopologyRefiner const & baseRefiner;
320     Far::PtexIndices const &     basePtexIndices;
321     std::vector<Pos> const &     basePositions;
322     std::vector<Index> const &   baseFaces;
323 
324     //  Members constructed to evaluate patches:
325     Far::PatchTable *   patchTable;
326     Far::PatchMap *     patchMap;
327     int                 patchFaceSize;
328     std::vector<Pos>    localPositions;
329 };
330 
PatchGroup(Far::PatchTableFactory::Options patchOptions,Far::TopologyRefiner const & baseRefinerArg,Far::PtexIndices const & basePtexIndicesArg,std::vector<Pos> const & basePositionsArg,std::vector<Index> const & baseFacesArg)331 PatchGroup::PatchGroup(Far::PatchTableFactory::Options patchOptions,
332                        Far::TopologyRefiner const &    baseRefinerArg,
333                        Far::PtexIndices const &        basePtexIndicesArg,
334                        std::vector<Pos> const &        basePositionsArg,
335                        std::vector<Index> const &      baseFacesArg) :
336         baseRefiner(baseRefinerArg),
337         basePtexIndices(basePtexIndicesArg),
338         basePositions(basePositionsArg),
339         baseFaces(baseFacesArg) {
340 
341     //  Create a local refiner (sharing the base level), apply adaptive
342     //  refinement to the given subset of base faces, and construct a patch
343     //  table (and its associated map) for the same set of faces:
344     //
345     Far::ConstIndexArray groupFaces(&baseFaces[0], (int)baseFaces.size());
346 
347     Far::TopologyRefiner *localRefiner =
348         Far::TopologyRefinerFactory<Far::TopologyDescriptor>::Create(
349             baseRefiner);
350 
351     localRefiner->RefineAdaptive(
352         patchOptions.GetRefineAdaptiveOptions(), groupFaces);
353 
354     patchTable = Far::PatchTableFactory::Create(*localRefiner, patchOptions,
355         groupFaces);
356 
357     patchMap = new Far::PatchMap(*patchTable);
358 
359     patchFaceSize =
360         Sdc::SchemeTypeTraits::GetRegularFaceSize(baseRefiner.GetSchemeType());
361 
362     //  Compute the number of refined and local points needed to evaluate the
363     //  patches, allocate and interpolate.  This varies from tutorial_5_1 in
364     //  that the primvar buffer for the base vertices is separate from the
365     //  refined vertices and local patch points (which must also be accounted
366     //  for when evaluating the patches).
367     //
368     int nBaseVertices    = localRefiner->GetLevel(0).GetNumVertices();
369     int nRefinedVertices = localRefiner->GetNumVerticesTotal() - nBaseVertices;
370     int nLocalPoints     = patchTable->GetNumLocalPoints();
371 
372     localPositions.resize(nRefinedVertices + nLocalPoints);
373 
374     if (nRefinedVertices) {
375         Far::PrimvarRefiner primvarRefiner(*localRefiner);
376 
377         Pos const * src = &basePositions[0];
378         Pos * dst = &localPositions[0];
379         for (int level = 1; level < localRefiner->GetNumLevels(); ++level) {
380             primvarRefiner.Interpolate(level, src, dst);
381             src = dst;
382             dst += localRefiner->GetLevel(level).GetNumVertices();
383         }
384     }
385     if (nLocalPoints) {
386         patchTable->GetLocalPointStencilTable()->UpdateValues(
387                 &basePositions[0], nBaseVertices, &localPositions[0],
388                 &localPositions[nRefinedVertices]);
389     }
390 
391     delete localRefiner;
392 }
393 
~PatchGroup()394 PatchGroup::~PatchGroup() {
395     delete patchTable;
396     delete patchMap;
397 }
398 
399 void
TessellateBaseFace(int face,PosVector & tessPoints,TriVector & tessTris) const400 PatchGroup::TessellateBaseFace(int face, PosVector & tessPoints,
401                                          TriVector & tessTris) const {
402 
403     //  Tesselate the face with points at the midpoint of the face and at
404     //  each corner, and triangles connecting the midpoint to each edge.
405     //  Irregular faces require an aribrary number of corners points, but
406     //  all are at the origin of the child face of the irregular base face:
407     //
408     float const quadPoints[5][2] = { { 0.5f, 0.5f },
409                                      { 0.0f, 0.0f },
410                                      { 1.0f, 0.0f },
411                                      { 1.0f, 1.0f },
412                                      { 0.0f, 1.0f } };
413 
414     float const triPoints[4][2] = { { 0.5f, 0.5f },
415                                     { 0.0f, 0.0f },
416                                     { 1.0f, 0.0f },
417                                     { 0.0f, 1.0f } };
418 
419     float const irregPoints[4][2] = { { 1.0f, 1.0f },
420                                       { 0.0f, 0.0f } };
421 
422     //  Determine the topology of the given base face and the resulting
423     //  tessellation points and faces to generate:
424     //
425     int baseFace = baseFaces[face];
426     int faceSize = baseRefiner.GetLevel(0).GetFaceVertices(baseFace).size();
427 
428     bool faceIsIrregular = (faceSize != patchFaceSize);
429 
430     int nTessPoints = faceSize + 1;
431     int nTessFaces  = faceSize;
432 
433     tessPoints.resize(nTessPoints);
434     tessTris.resize(nTessFaces);
435 
436     //  Compute the mid and corner points -- remember that for an irregular
437     //  face, we must reference the individual ptex faces for each corner:
438     //
439     int ptexFace = basePtexIndices.GetFaceId(baseFace);
440 
441     int numBaseVerts = (int) basePositions.size();
442 
443     for (int i = 0; i < nTessPoints; ++i) {
444         //  Choose the (s,t) coordinate from the fixed tessellation:
445         float const * st = faceIsIrregular ? irregPoints[i != 0]
446                          : ((faceSize == 4) ? quadPoints[i] : triPoints[i]);
447 
448         //  Locate the patch corresponding to the face ptex idx and (s,t)
449         //  and evaluate:
450         int patchFace = ptexFace;
451         if (faceIsIrregular && (i > 0)) {
452             patchFace += i - 1;
453         }
454         Far::PatchTable::PatchHandle const * handle =
455             patchMap->FindPatch(patchFace, st[0], st[1]);
456         assert(handle);
457 
458         float pWeights[20];
459         patchTable->EvaluateBasis(*handle, st[0], st[1], pWeights);
460 
461         //  Identify the patch cvs and combine with the evaluated weights --
462         //  remember to distinguish cvs in the base level:
463         Far::ConstIndexArray cvIndices = patchTable->GetPatchVertices(*handle);
464 
465         Pos & pos = tessPoints[i];
466         pos.Clear();
467         for (int cv = 0; cv < cvIndices.size(); ++cv) {
468             int cvIndex = cvIndices[cv];
469             if (cvIndex < numBaseVerts) {
470                 pos.AddWithWeight(basePositions[cvIndex],
471                     pWeights[cv]);
472             } else {
473                 pos.AddWithWeight(localPositions[cvIndex - numBaseVerts],
474                     pWeights[cv]);
475             }
476         }
477     }
478 
479     //  Assign triangles connecting the midpoint of the base face to the
480     //  points computed at the ends of each of its edges:
481     //
482     for (int i = 0; i < nTessFaces; ++i) {
483         tessTris[i] = Tri(0, 1 + i, 1 + ((i + 1) % faceSize));
484     }
485 }
486 
487 
488 //
489 //  Command line arguments parsed to provide run-time options:
490 //
491 class Args {
492 public:
493     std::string     inputObjFile;
494     Sdc::SchemeType schemeType;
495     int             geoMultiplier;
496     int             maxPatchDepth;
497     int             numPatchGroups;
498     bool            noTessFlag;
499     bool            noOutputFlag;
500 
501 public:
Args(int argc,char ** argv)502     Args(int argc, char ** argv) :
503         inputObjFile(),
504         schemeType(Sdc::SCHEME_CATMARK),
505         geoMultiplier(10),
506         maxPatchDepth(3),
507         numPatchGroups(10),
508         noTessFlag(false),
509         noOutputFlag(false) {
510 
511         //  Parse and assign standard arguments and Obj files:
512         ArgOptions args;
513         args.Parse(argc, argv);
514 
515         maxPatchDepth = args.GetLevel();
516         schemeType = ConvertShapeSchemeToSdcType(args.GetDefaultScheme());
517 
518         const std::vector<const char *> objFiles = args.GetObjFiles();
519         if (!objFiles.empty()) {
520             for (size_t i = 1; i < objFiles.size(); ++i) {
521                 fprintf(stderr,
522                     "Warning: .obj file '%s' ignored\n", objFiles[i]);
523             }
524             inputObjFile = std::string(objFiles[0]);
525         }
526 
527         //  Parse remaining arguments specific to this example:
528         const std::vector<const char *> &rargs = args.GetRemainingArgs();
529         for (size_t i = 0; i < rargs.size(); ++i) {
530             if (!strcmp(rargs[i], "-groups")) {
531                 if (++i < rargs.size()) numPatchGroups = atoi(rargs[i]);
532             } else if (!strcmp(rargs[i], "-mult")) {
533                 if (++i < rargs.size()) geoMultiplier = atoi(rargs[i]);
534             } else if (!strcmp(rargs[i], "-notess")) {
535                 noTessFlag = true;
536             } else if (!strcmp(rargs[i], "-nooutput")) {
537                 noOutputFlag = true;
538             } else {
539                 fprintf(stderr, "Warning: Argument '%s' ignored\n", rargs[i]);
540             }
541         }
542     }
543 
544 private:
Args()545     Args() { }
546 };
547 
548 
549 //
550 //  Load command line arguments and geometry, then divide the mesh into groups
551 //  of faces from which to create and tessellate patches:
552 //
553 int
main(int argc,char ** argv)554 main(int argc, char **argv) {
555 
556     Args args(argc, argv);
557 
558     //
559     //  Create or load the base geometry (command line arguments allow a
560     //  .obj file to be specified).  In addition to the TopologyRefiner
561     //  and set of positions for the base vertices, a set of PtexIndices is
562     //  also required to evaluate patches, so build it here once for use
563     //  elsewhere:
564     //
565     std::vector<Pos> basePositions;
566 
567     Far::TopologyRefiner * baseRefinerPtr = args.inputObjFile.empty() ?
568         createTopologyRefinerDefault(args.geoMultiplier, basePositions) :
569         createTopologyRefinerFromObj(args.inputObjFile, args.schemeType,
570             basePositions);
571     assert(baseRefinerPtr);
572     Far::TopologyRefiner & baseRefiner = *baseRefinerPtr;
573 
574     Far::PtexIndices basePtexIndices(baseRefiner);
575 
576     //
577     //  Determine the sizes of the patch groups specified -- there will be
578     //  two sizes that differ by one to account for unequal division:
579     //
580     int numBaseFaces = baseRefiner.GetNumFacesTotal();
581 
582     int numPatchGroups = args.numPatchGroups;
583     if (numPatchGroups > numBaseFaces) {
584         numPatchGroups = numBaseFaces;
585     } else if (numPatchGroups < 1) {
586         numPatchGroups = 1;
587     }
588     int lesserGroupSize = numBaseFaces / numPatchGroups;
589     int numLargerGroups = numBaseFaces - (numPatchGroups * lesserGroupSize);
590 
591     //
592     //  Define the options used to construct the patches for each group.
593     //  Unless suppressed, a tessellation in Obj format will also be printed
594     //  to standard output, so keep track of the vertex indices.
595     //
596     Far::PatchTableFactory::Options patchOptions(args.maxPatchDepth);
597     patchOptions.generateVaryingTables = false;
598     patchOptions.shareEndCapPatchPoints = false;
599     patchOptions.endCapType =
600         Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
601 
602     int objVertCount = 0;
603 
604     PosVector tessPoints;
605     TriVector tessFaces;
606 
607     for (int i = 0; i < numPatchGroups; ++i) {
608 
609         //
610         //  Initialize a vector with a group of base faces from which to
611         //  create and evaluate patches:
612         //
613         Index minFace = i * lesserGroupSize + std::min(i, numLargerGroups);
614         Index maxFace = minFace + lesserGroupSize + (i < numLargerGroups);
615 
616         std::vector<Far::Index> baseFaces(maxFace - minFace);
617         for (int face = minFace; face < maxFace; ++face) {
618             baseFaces[face - minFace] = face;
619         }
620 
621         //
622         //  Declare a PatchGroup and tessellate its base faces -- generating
623         //  vertices and faces in Obj format to standard output:
624         //
625         PatchGroup patchGroup(patchOptions,
626             baseRefiner, basePtexIndices, basePositions, baseFaces);
627 
628         if (args.noTessFlag) continue;
629 
630         if (!args.noOutputFlag) {
631             printf("g patchGroup_%d\n", i);
632         }
633 
634         for (int j = 0; j < (int) baseFaces.size(); ++j) {
635             patchGroup.TessellateBaseFace(j, tessPoints, tessFaces);
636 
637             if (!args.noOutputFlag) {
638                 int nVerts = (int) tessPoints.size();
639                 for (int k = 0; k < nVerts; ++k) {
640                     float const * p = tessPoints[k].p;
641                     printf("v %f %f %f\n", p[0], p[1], p[2]);
642                 }
643 
644                 int nTris = (int) tessFaces.size();
645                 int vBase = 1 + objVertCount;
646                 for (int k = 0; k < nTris; ++k) {
647                     int const * v = tessFaces[k].v;
648                     printf("f %d %d %d\n",
649                         vBase + v[0], vBase + v[1], vBase + v[2]);
650                 }
651                 objVertCount += nVerts;
652             }
653         }
654     }
655     delete baseRefinerPtr;
656 
657     return EXIT_SUCCESS;
658 }
659