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