1 //
2 //   Copyright 2020 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 use a Far::LimitStenciTable to repeatedly
30 //      and efficiently evaluate a set of points (and optionally derivatives)
31 //      on the limit surface.
32 //
33 //      A LimitStencilTable derives from StencilTable but is specialized to
34 //      factor the evaluation of limit positions and derivatives into stencils.
35 //      This allows a set of limit properties to be efficiently recomputed in
36 //      response to changes to the vertices of the base mesh.  Constructing
37 //      the different kinds of StencilTables can have a high cost, so whether
38 //      that cost is worth it will depend on your usage (e.g. if points are
39 //      only computed once, using stencil tables is typically not worth the
40 //      added cost).
41 //
42 //      Any points on the limit surface can be identified for evaluation. In
43 //      this example we create a crude tessellation similar to tutorial_5_2.
44 //      The midpoint of each face and points near the corners of the face are
45 //      evaluated and a triangle fan connects them.
46 //
47 
48 #include "../../../regression/common/arg_utils.h"
49 #include "../../../regression/common/far_utils.h"
50 
51 #include <opensubdiv/far/topologyDescriptor.h>
52 #include <opensubdiv/far/patchTableFactory.h>
53 #include <opensubdiv/far/stencilTableFactory.h>
54 #include <opensubdiv/far/ptexIndices.h>
55 
56 #include <cassert>
57 #include <cstdio>
58 #include <cstring>
59 #include <fstream>
60 #include <sstream>
61 
62 using namespace OpenSubdiv;
63 
64 using Far::Index;
65 
66 
67 //
68 //  Global utilities in this namespace are not relevant to the tutorial.
69 //  They simply serve to construct some default geometry to be processed
70 //  in the form of a TopologyRefiner and vector of vertex positions.
71 //
72 namespace {
73     //
74     //  Simple structs for (x,y,z) position and a 3-tuple for the set
75     //  of vertices of a triangle:
76     //
77     struct Pos {
Pos__anon1ff8dbb60111::Pos78         Pos() { }
Pos__anon1ff8dbb60111::Pos79         Pos(float x, float y, float z) { p[0] = x, p[1] = y, p[2] = z; }
80 
operator +__anon1ff8dbb60111::Pos81         Pos operator+(Pos const & op) const {
82             return Pos(p[0] + op.p[0], p[1] + op.p[1], p[2] + op.p[2]);
83         }
84 
85         //  Clear() and AddWithWeight() required for interpolation:
Clear__anon1ff8dbb60111::Pos86         void Clear( void * =0 ) { p[0] = p[1] = p[2] = 0.0f; }
87 
AddWithWeight__anon1ff8dbb60111::Pos88         void AddWithWeight(Pos const & src, float weight) {
89             p[0] += weight * src.p[0];
90             p[1] += weight * src.p[1];
91             p[2] += weight * src.p[2];
92         }
93 
94         float p[3];
95     };
96     typedef std::vector<Pos> PosVector;
97 
98     struct Tri {
Tri__anon1ff8dbb60111::Tri99         Tri() { }
Tri__anon1ff8dbb60111::Tri100         Tri(int a, int b, int c) { v[0] = a, v[1] = b, v[2] = c; }
101 
102         int v[3];
103     };
104     typedef std::vector<Tri> TriVector;
105 
106 
107     //
108     //  Functions to populate the topology and geometry arrays a simple
109     //  shape whose positions may be transformed:
110     //
111     void
createCube(std::vector<int> & vertsPerFace,std::vector<Index> & faceVertsPerFace,std::vector<Pos> & positionsPerVert)112     createCube(std::vector<int> &   vertsPerFace,
113                std::vector<Index> & faceVertsPerFace,
114                std::vector<Pos> &   positionsPerVert) {
115 
116         //  Local topology and position of a cube centered at origin:
117         static float const cubePositions[8][3] = { { -0.5f, -0.5f, -0.5f },
118                                                    { -0.5f,  0.5f, -0.5f },
119                                                    { -0.5f,  0.5f,  0.5f },
120                                                    { -0.5f, -0.5f,  0.5f },
121                                                    {  0.5f, -0.5f, -0.5f },
122                                                    {  0.5f,  0.5f, -0.5f },
123                                                    {  0.5f,  0.5f,  0.5f },
124                                                    {  0.5f, -0.5f,  0.5f } };
125 
126         static int const cubeFaceVerts[6][4] = { { 0, 3, 2, 1 },
127                                                  { 4, 5, 6, 7 },
128                                                  { 0, 4, 7, 3 },
129                                                  { 1, 2, 6, 5 },
130                                                  { 0, 1, 5, 4 },
131                                                  { 3, 7, 6, 2 } };
132 
133         //  Initialize verts-per-face and face-vertices for each face:
134         vertsPerFace.resize(6);
135         faceVertsPerFace.resize(24);
136         for (int i = 0; i < 6; ++i) {
137             vertsPerFace[i] = 4;
138             for (int j = 0; j < 4; ++j) {
139                 faceVertsPerFace[i*4+j] = cubeFaceVerts[i][j];
140             }
141         }
142 
143         //  Initialize vertex positions:
144         positionsPerVert.resize(8);
145         for (int i = 0; i < 8; ++i) {
146             float const * p = cubePositions[i];
147             positionsPerVert[i] = Pos(p[0], p[1], p[2]);
148         }
149     }
150 
151     //
152     //  Create a TopologyRefiner from default geometry created above:
153     //
154     Far::TopologyRefiner *
createTopologyRefinerDefault(PosVector & posVector)155     createTopologyRefinerDefault(PosVector & posVector) {
156 
157         std::vector<int>   topVertsPerFace;
158         std::vector<Index> topFaceVerts;
159 
160         createCube(topVertsPerFace, topFaceVerts, posVector);
161 
162         typedef Far::TopologyDescriptor Descriptor;
163 
164         Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
165 
166         Sdc::Options options;
167         options.SetVtxBoundaryInterpolation(
168             Sdc::Options::VTX_BOUNDARY_EDGE_AND_CORNER);
169 
170         Descriptor desc;
171         desc.numVertices = (int) posVector.size();
172         desc.numFaces = (int) topVertsPerFace.size();
173         desc.numVertsPerFace = &topVertsPerFace[0];
174         desc.vertIndicesPerFace = &topFaceVerts[0];
175 
176         //  Instantiate a Far::TopologyRefiner from the descriptor.
177         Far::TopologyRefiner * refiner =
178             Far::TopologyRefinerFactory<Descriptor>::Create(desc,
179                 Far::TopologyRefinerFactory<Descriptor>::Options(type,options));
180         assert(refiner);
181         return refiner;
182     }
183 
184     //
185     //  Create a TopologyRefiner from a specified Obj file:
186     //  geometry created internally:
187     //
188     Far::TopologyRefiner *
createTopologyRefinerFromObj(std::string const & objFileName,Sdc::SchemeType schemeType,PosVector & posVector)189     createTopologyRefinerFromObj(std::string const & objFileName,
190                                  Sdc::SchemeType schemeType,
191                                  PosVector & posVector) {
192 
193         const char *  filename = objFileName.c_str();
194         const Shape * shape = 0;
195 
196         std::ifstream ifs(filename);
197         if (ifs) {
198             std::stringstream ss;
199             ss << ifs.rdbuf();
200             ifs.close();
201             std::string shapeString = ss.str();
202 
203             shape = Shape::parseObj(shapeString.c_str(),
204                 ConvertSdcTypeToShapeScheme(schemeType), false);
205             if (shape == 0) {
206                 fprintf(stderr,
207                     "Error:  Cannot create Shape from .obj file '%s'\n",
208                     filename);
209                 return 0;
210             }
211         } else {
212             fprintf(stderr, "Error:  Cannot open .obj file '%s'\n", filename);
213             return 0;
214         }
215 
216         Sdc::SchemeType sdcType    = GetSdcType(*shape);
217         Sdc::Options    sdcOptions = GetSdcOptions(*shape);
218 
219         Far::TopologyRefiner * refiner =
220             Far::TopologyRefinerFactory<Shape>::Create(*shape,
221                 Far::TopologyRefinerFactory<Shape>::Options(
222                     sdcType, sdcOptions));
223         if (refiner == 0) {
224             fprintf(stderr, "Error:  Unable to construct TopologyRefiner "
225                 "from .obj file '%s'\n", filename);
226             return 0;
227         }
228 
229         int numVertices = refiner->GetNumVerticesTotal();
230         posVector.resize(numVertices);
231         std::memcpy(&posVector[0], &shape->verts[0], numVertices * sizeof(Pos));
232 
233         delete shape;
234         return refiner;
235     }
236 
237 
238     //
239     //  Simple function to export an Obj file for the limit points -- which
240     //  provides a simple tessllation similar to tutorial_5_2.
241     //
writeToObj(Far::TopologyLevel const & baseLevel,std::vector<Pos> const & vertexPositions,int nextObjVertexIndex)242     int writeToObj(
243         Far::TopologyLevel const & baseLevel,
244         std::vector<Pos> const & vertexPositions,
245         int nextObjVertexIndex) {
246 
247         for (size_t i = 0; i < vertexPositions.size(); ++i) {
248             float const * p = vertexPositions[i].p;
249             printf("v %f %f %f\n", p[0], p[1], p[2]);
250         }
251 
252         //
253         //  Connect the sequences of limit points (center followed by corners)
254         //  into triangle fans for each base face:
255         //
256         for (int i = 0; i < baseLevel.GetNumFaces(); ++i) {
257             int faceSize = baseLevel.GetFaceVertices(i).size();
258 
259             int vCenter = nextObjVertexIndex + 1;
260             int vCorner = vCenter + 1;
261             for (int k = 0; k < faceSize; ++k) {
262                 printf("f %d %d %d\n",
263                     vCenter, vCorner + k, vCorner + ((k + 1) % faceSize));
264             }
265             nextObjVertexIndex += faceSize + 1;
266         }
267         return nextObjVertexIndex;
268     }
269 } // end namespace
270 
271 
272 //
273 //  Command line arguments parsed to provide run-time options:
274 //
275 class Args {
276 public:
277     std::string     inputObjFile;
278     Sdc::SchemeType schemeType;
279     int             maxPatchDepth;
280     int             numPoses;
281     Pos             poseOffset;
282     bool            deriv1Flag;
283     bool            noPatchesFlag;
284     bool            noOutputFlag;
285 
286 public:
Args(int argc,char ** argv)287     Args(int argc, char ** argv) :
288         inputObjFile(),
289         schemeType(Sdc::SCHEME_CATMARK),
290         maxPatchDepth(3),
291         numPoses(0),
292         poseOffset(1.0f, 0.0f, 0.0f),
293         deriv1Flag(false),
294         noPatchesFlag(false),
295         noOutputFlag(false) {
296 
297         //  Parse and assign standard arguments and Obj files:
298         ArgOptions args;
299         args.Parse(argc, argv);
300 
301         maxPatchDepth = args.GetLevel();
302         schemeType = ConvertShapeSchemeToSdcType(args.GetDefaultScheme());
303 
304         const std::vector<const char *> objFiles = args.GetObjFiles();
305         if (!objFiles.empty()) {
306             for (size_t i = 1; i < objFiles.size(); ++i) {
307                 fprintf(stderr,
308                     "Warning: .obj file '%s' ignored\n", objFiles[i]);
309             }
310             inputObjFile = std::string(objFiles[0]);
311         }
312 
313         //  Parse remaining arguments specific to this example:
314         const std::vector<const char *> &rargs = args.GetRemainingArgs();
315         for (size_t i = 0; i < rargs.size(); ++i) {
316             if (!strcmp(rargs[i], "-d1")) {
317                 deriv1Flag = true;
318             } else if (!strcmp(rargs[i], "-nopatches")) {
319                 noPatchesFlag = true;
320             } else if (!strcmp(rargs[i], "-poses")) {
321                 if (++i < rargs.size()) numPoses = atoi(rargs[i]);
322             } else if (!strcmp(rargs[i], "-offset")) {
323                 if (++i < rargs.size()) poseOffset.p[0] = (float)atof(rargs[i]);
324                 if (++i < rargs.size()) poseOffset.p[1] = (float)atof(rargs[i]);
325                 if (++i < rargs.size()) poseOffset.p[2] = (float)atof(rargs[i]);
326             } else if (!strcmp(rargs[i], "-nooutput")) {
327                 noOutputFlag = true;
328             } else {
329                 fprintf(stderr, "Warning: Argument '%s' ignored\n", rargs[i]);
330             }
331         }
332     }
333 
334 private:
Args()335     Args() { }
336 };
337 
338 
339 //
340 //  Assemble the set of locations for the limit points.  The resulting
341 //  vector of LocationArrays can contain arbitrary locations on the limit
342 //  surface -- with multiple locations for the same patch grouped into a
343 //  single array.
344 //
345 //  In this case, for each base face, coordinates for the center and its
346 //  corners are specified -- from which we will construct a triangle fan
347 //  providing a crude tessellation (similar to tutorial_5_2).
348 //
349 typedef Far::LimitStencilTableFactory::LocationArray   LocationArray;
350 
assembleLimitPointLocations(Far::TopologyRefiner const & refiner,std::vector<LocationArray> & locations)351 int assembleLimitPointLocations(Far::TopologyRefiner const & refiner,
352                                 std::vector<LocationArray> & locations) {
353     //
354     //  Coordinates for the center of the face and its corners (slightly
355     //  inset).  Unlike most of the public interface for patches, the
356     //  LocationArray refers to parameteric coordinates as (s,t), so that
357     //  convention will be followed here.
358     //
359     //  Note that the (s,t) coordinates in a LocationArray are referred to
360     //  by reference.  The memory holding these (s,t) values must persist
361     //  while the LimitStencilTable is constructed -- the arrays here are
362     //  declared as static for that purpose.
363     //
364     static float const quadSCoords[5] = { 0.5f, 0.05f, 0.95f, 0.95f, 0.05f };
365     static float const quadTCoords[5] = { 0.5f, 0.05f, 0.05f, 0.95f, 0.95f };
366 
367     static float const triSCoords[4] = { 0.33f, 0.05f, 0.95f, 0.05f };
368     static float const triTCoords[4] = { 0.33f, 0.05f, 0.00f, 0.95f };
369 
370     static float const irregSCoords[2] = { 1.0f, 0.05f };
371     static float const irregTCoords[2] = { 1.0f, 0.05f };
372 
373     //
374     //  Since these are references to patches to be evaluated, we require
375     //  use of the Ptex indices to identify the top-most parameterized
376     //  patch, which is essential to dealing with non-quad faces (in the
377     //  case of Catmark).
378     //
379     Far::TopologyLevel const & baseLevel = refiner.GetLevel(0);
380 
381     Far::PtexIndices basePtexIndices(refiner);
382 
383     int regFaceSize = Sdc::SchemeTypeTraits::GetRegularFaceSize(
384         refiner.GetSchemeType());
385 
386 
387     //
388     //  For each base face, simply refer to the (s,t) arrays for regular quad
389     //  and triangular patches with a single LocationArray.  Otherwise, for
390     //  irregular faces, the corners of the face come from different patches
391     //  and so must be referenced in separate LocationArrays.
392     //
393     locations.clear();
394 
395     int numLimitPoints = 0;
396     for (int i = 0; i < baseLevel.GetNumFaces(); ++i) {
397         int baseFaceSize = baseLevel.GetFaceVertices(i).size();
398         int basePtexId   = basePtexIndices.GetFaceId(i);
399 
400         bool faceIsRegular = (baseFaceSize == regFaceSize);
401         if (faceIsRegular) {
402             //  All coordinates are on the same top-level patch:
403             LocationArray loc;
404             loc.ptexIdx = basePtexId;
405             loc.numLocations = baseFaceSize + 1;
406             if (baseFaceSize == 4) {
407                 loc.s = quadSCoords;
408                 loc.t = quadTCoords;
409             } else {
410                 loc.s = triSCoords;
411                 loc.t = triTCoords;
412             }
413             locations.push_back(loc);
414         } else {
415             //  Center coordinate is on the first sub-patch while those on
416             //  near the corners are on each successive sub-patch:
417             LocationArray loc;
418             loc.numLocations = 1;
419             for (int j = 0; j <= baseFaceSize; ++j) {
420                 bool isPerimeter = (j > 0);
421                 loc.ptexIdx = basePtexId + (isPerimeter ? (j-1) : 0);
422                 loc.s = &irregSCoords[isPerimeter];
423                 loc.t = &irregTCoords[isPerimeter];
424 
425                 locations.push_back(loc);
426             }
427         }
428         numLimitPoints += baseFaceSize + 1;
429     }
430     return numLimitPoints;
431 }
432 
433 
434 //
435 //  Load command line arguments and geometry, build the LimitStencilTable
436 //  for a set of points on the limit surface and compute those points for
437 //  several orientations of the mesh:
438 //
439 int
main(int argc,char ** argv)440 main(int argc, char **argv) {
441 
442     Args args(argc, argv);
443 
444     //
445     //  Create or load the base geometry (command line arguments allow a
446     //  .obj file to be specified), providing a TopologyRefiner and a set
447     //  of base vertex positions to work with:
448     //
449     std::vector<Pos> basePositions;
450 
451     Far::TopologyRefiner * refinerPtr = args.inputObjFile.empty() ?
452             createTopologyRefinerDefault(basePositions) :
453             createTopologyRefinerFromObj(args.inputObjFile, args.schemeType,
454                                          basePositions);
455     assert(refinerPtr);
456     Far::TopologyRefiner & refiner = *refinerPtr;
457 
458     Far::TopologyLevel const & baseLevel = refiner.GetLevel(0);
459 
460     //
461     //  Use of LimitStencilTable requires either explicit or implicit use
462     //  of a PatchTable.  A PatchTable is not required to construct a
463     //  LimitStencilTable -- one will be constructed internally for use
464     //  and discarded -- but explicit construction is recommended to control
465     //  the many legacy options for PatchTable, rather than relying on
466     //  internal defaults.  Adaptive refinement is required in both cases
467     //  to indicate the accuracy of the patches.
468     //
469     //  Note that if a TopologyRefiner and PatchTable are not used for
470     //  any other purpose than computing the limit points, that specifying
471     //  the subset of faces containing those limit points in the adaptive
472     //  refinement and PatchTable construction can avoid unnecessary
473     //  overhead.
474     //
475     Far::PatchTable * patchTablePtr = 0;
476 
477     if (args.noPatchesFlag) {
478         refiner.RefineAdaptive(
479             Far::TopologyRefiner::AdaptiveOptions(args.maxPatchDepth));
480     } else {
481         Far::PatchTableFactory::Options patchOptions(args.maxPatchDepth);
482         patchOptions.useInfSharpPatch = true;
483         patchOptions.generateLegacySharpCornerPatches = false;
484         patchOptions.generateVaryingTables = false;
485         patchOptions.generateFVarTables = false;
486         patchOptions.endCapType =
487             Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
488 
489         refiner.RefineAdaptive(patchOptions.GetRefineAdaptiveOptions());
490 
491         patchTablePtr = Far::PatchTableFactory::Create(refiner, patchOptions);
492         assert(patchTablePtr);
493     }
494 
495     //
496     //  Assemble the set of locations for the limit points.  For each base
497     //  face, coordinates for the center and its corners are specified --
498     //  from which we will construct a triangle fan providing a crude
499     //  tessellation (similar to tutorial_5_2).
500     //
501     std::vector<LocationArray> locations;
502 
503     int numLimitPoints = assembleLimitPointLocations(refiner, locations);
504 
505     //
506     //  Construct a LimitStencilTable from the refiner, patch table (optional)
507     //  and the collection of limit point locations.  Stencils can optionally
508     //  be created for computing dervatives -- the default is to compute 1st
509     //  derivative stencils, so be sure to disable that if not necessary:
510     //
511     Far::LimitStencilTableFactory::Options limitOptions;
512     limitOptions.generate1stDerivatives = args.deriv1Flag;
513 
514     Far::LimitStencilTable const * limitStencilTablePtr =
515         Far::LimitStencilTableFactory::Create(refiner, locations,
516             0,             // optional StencilTable for the refined points
517             patchTablePtr, // optional PatchTable
518             limitOptions);
519     assert(limitStencilTablePtr);
520     Far::LimitStencilTable const & limitStencilTable = *limitStencilTablePtr;
521 
522     //
523     //  Apply the constructed LimitStencilTable to compute limit positions
524     //  from the base level vertex positions.  This is trivial if computing
525     //  all positions in one invokation.  The UpdateValues method (and those
526     //  for derivatives) are overloaded to optionally accept a subrange of
527     //  indices to distribute the computation:
528     //
529     std::vector<Pos> limitPositions(numLimitPoints);
530 
531     limitStencilTable.UpdateValues(basePositions, limitPositions);
532 
533     //  Call with the optional subrange:
534     limitStencilTable.UpdateValues(basePositions, limitPositions,
535                                    0, numLimitPoints / 2);
536     limitStencilTable.UpdateValues(basePositions, limitPositions,
537                                    (numLimitPoints / 2) + 1, numLimitPoints);
538 
539     // Write vertices and faces in Obj format for the original limit points:
540     int objVertCount = 0;
541 
542     if (!args.noOutputFlag) {
543         printf("g base_mesh\n");
544         objVertCount = writeToObj(baseLevel, limitPositions, objVertCount);
545     }
546 
547     //
548     //  Recompute the limit points and output faces for different "poses" of
549     //  the original mesh -- in this case simply translated.  Also optionally
550     //  compute 1st derivatives (though they are not used here):
551     //
552     std::vector<Pos> posePositions(basePositions);
553 
554     std::vector<Pos> limitDu(args.deriv1Flag ? numLimitPoints : 0);
555     std::vector<Pos> limitDv(args.deriv1Flag ? numLimitPoints : 0);
556 
557     for (int i = 0; i < args.numPoses; ++i) {
558         // Trivially transform the base vertex positions and re-compute:
559         for (size_t j = 0; j < basePositions.size(); ++j) {
560             posePositions[j] = posePositions[j] + args.poseOffset;
561         }
562 
563         limitStencilTable.UpdateValues(posePositions, limitPositions);
564         if (args.deriv1Flag) {
565             limitStencilTable.UpdateDerivs(posePositions, limitDu, limitDv);
566         }
567 
568         if (!args.noOutputFlag) {
569             printf("\ng pose_%d\n", i);
570             objVertCount = writeToObj(baseLevel, limitPositions, objVertCount);
571         }
572     }
573     delete refinerPtr;
574     delete patchTablePtr;
575     delete limitStencilTablePtr;
576 
577     return EXIT_SUCCESS;
578 }
579