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