far_tutorial_5_2.cpp
System Message: WARNING/2 (/wrkdirs/usr/ports/graphics/opensubdiv/work/.build/documentation/far_tutorial_5_2.rst, line 9)
Cannot analyze code. Pygments package not found.
.. code:: c++ //------------------------------------------------------------------------------ // Tutorial description: // // This tutorial shows how to manage the limit surface of a potentially // large mesh by creating groups of patches for selected faces of the // mesh. Familiarity with construction and evaluation of a PatchTable // is assumed (see tutorial_5_1). // // When the patches for a mesh do not need to be retained for further // use, e.g. when simply computing points for a tessellation, the time // and space required to construct a single large PatchTable can be // considerable. By constructing, evaluating and discarding smaller // PatchTables for subsets of the mesh, the high transient memory cost // can be avoided when computed serially. When computed in parallel, // there may be little memory savings, but the construction time can // then be distributed. // // This tutorial creates simple geometry (currently a lattice of cubes) // that can be expanded in complexity with a simple multiplier. The // collection of faces are then divided into a specified number of groups // from which patches will be constructed and evaluated. A simple // tessellation (a triangle fan around the midpoint of each face) is then // written in Obj format to the standard output. // #include "../../../regression/common/arg_utils.h" #include "../../../regression/common/far_utils.h" #include <opensubdiv/far/topologyDescriptor.h> #include <opensubdiv/far/primvarRefiner.h> #include <opensubdiv/far/patchTableFactory.h> #include <opensubdiv/far/patchMap.h> #include <opensubdiv/far/ptexIndices.h> #include <cassert> #include <cstdio> #include <cstring> #include <fstream> #include <sstream> using namespace OpenSubdiv; using Far::Index; // // Global utilities in this namespace are not relevant to the tutorial. // They simply serve to construct some default geometry to be processed // in the form of a TopologyRefiner and vector of vertex positions. // namespace { // // Simple structs for (x,y,z) position and a 3-tuple for the set // of vertices of a triangle: // struct Pos { Pos() { } Pos(float x, float y, float z) { p[0] = x, p[1] = y, p[2] = z; } Pos operator+(Pos const & op) const { return Pos(p[0] + op.p[0], p[1] + op.p[1], p[2] + op.p[2]); } // Clear() and AddWithWeight() required for interpolation: void Clear( void * =0 ) { p[0] = p[1] = p[2] = 0.0f; } void AddWithWeight(Pos const & src, float weight) { p[0] += weight * src.p[0]; p[1] += weight * src.p[1]; p[2] += weight * src.p[2]; } float p[3]; }; typedef std::vector<Pos> PosVector; struct Tri { Tri() { } Tri(int a, int b, int c) { v[0] = a, v[1] = b, v[2] = c; } int v[3]; }; typedef std::vector<Tri> TriVector; // // Functions to populate the topology and geometry arrays with simple // shapes that we can multiply to increase complexity: // void appendDefaultPrimitive(Pos const & origin, std::vector<int> & vertsPerFace, std::vector<Index> & faceVerts, std::vector<Pos> & positionsPerVert) { // Local topology and position of a cube centered at origin: static float const cubePositions[8][3] = { { -0.5f, -0.5f, -0.5f }, { -0.5f, 0.5f, -0.5f }, { -0.5f, 0.5f, 0.5f }, { -0.5f, -0.5f, 0.5f }, { 0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }, { 0.5f, -0.5f, 0.5f } }; static int const cubeFaceVerts[6][4] = { { 0, 3, 2, 1 }, { 4, 5, 6, 7 }, { 0, 4, 7, 3 }, { 1, 2, 6, 5 }, { 0, 1, 5, 4 }, { 3, 7, 6, 2 } }; // Identify the next vertex before appending vertex positions: int baseVertex = (int) positionsPerVert.size(); for (int i = 0; i < 8; ++i) { float const * p = cubePositions[i]; positionsPerVert.push_back(origin + Pos(p[0], p[1], p[2])); } // Append number of verts-per-face and face-vertices for each face: for (int i = 0; i < 6; ++i) { vertsPerFace.push_back(4); for (int j = 0; j < 4; ++j) { faceVerts.push_back(baseVertex + cubeFaceVerts[i][j]); } } } void createDefaultGeometry(int multiplier, std::vector<int> & vertsPerFace, std::vector<Index> & faceVerts, std::vector<Pos> & positionsPerVert) { // Default primitive is currently a cube: int const vertsPerPrimitive = 8; int const facesPerPrimitive = 6; int const faceVertsPerPrimitive = 24; int nPrimitives = multiplier * multiplier * multiplier; positionsPerVert.reserve(nPrimitives * vertsPerPrimitive); vertsPerFace.reserve(nPrimitives * facesPerPrimitive); faceVerts.reserve(nPrimitives * faceVertsPerPrimitive); for (int x = 0; x < multiplier; ++x) { for (int y = 0; y < multiplier; ++y) { for (int z = 0; z < multiplier; ++z) { appendDefaultPrimitive( Pos((float)x * 2.0f, (float)y * 2.0f, (float)z * 2.0f), vertsPerFace, faceVerts, positionsPerVert); } } } } // // Create a TopologyRefiner from default geometry created above: // Far::TopologyRefiner * createTopologyRefinerDefault(int multiplier, PosVector & posVector) { std::vector<int> topVertsPerFace; std::vector<Index> topFaceVerts; createDefaultGeometry( multiplier, topVertsPerFace, topFaceVerts, posVector); typedef Far::TopologyDescriptor Descriptor; Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK; Sdc::Options options; options.SetVtxBoundaryInterpolation( Sdc::Options::VTX_BOUNDARY_EDGE_AND_CORNER); Descriptor desc; desc.numVertices = (int) posVector.size(); desc.numFaces = (int) topVertsPerFace.size(); desc.numVertsPerFace = &topVertsPerFace[0]; desc.vertIndicesPerFace = &topFaceVerts[0]; // Instantiate a Far::TopologyRefiner from the descriptor. Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Descriptor>::Create(desc, Far::TopologyRefinerFactory<Descriptor>::Options( type, options)); if (refiner == 0) { exit(EXIT_FAILURE); } bool dumpDefaultGeometryToObj = false; if (dumpDefaultGeometryToObj) { int nVerts = (int) posVector.size(); for (int i = 0; i < nVerts; ++i) { float const * p = posVector[i].p; printf("v %f %f %f\n", p[0], p[1], p[2]); } int const * fVerts = &topFaceVerts[0]; int nFaces = (int) topVertsPerFace.size(); for (int i = 0; i < nFaces; ++i) { printf("f"); for (int j = 0; j < topVertsPerFace[i]; ++j) { printf(" %d", 1 + *fVerts++); } printf("\n"); } exit(EXIT_SUCCESS); } return refiner; } // // Create a TopologyRefiner from a specified Obj file: // geometry created internally: // Far::TopologyRefiner * createTopologyRefinerFromObj(std::string const & objFileName, Sdc::SchemeType schemeType, PosVector & posVector) { const char * filename = objFileName.c_str(); const Shape * shape = 0; std::ifstream ifs(filename); if (ifs) { std::stringstream ss; ss << ifs.rdbuf(); ifs.close(); std::string shapeString = ss.str(); shape = Shape::parseObj(shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false); if (shape == 0) { fprintf(stderr, "Error: Cannot create Shape " "from .obj file '%s'\n", filename); return 0; } } else { fprintf(stderr, "Error: Cannot open .obj file '%s'\n", filename); return 0; } Sdc::SchemeType sdcType = GetSdcType(*shape); Sdc::Options sdcOptions = GetSdcOptions(*shape); Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(*shape, Far::TopologyRefinerFactory<Shape>::Options( sdcType, sdcOptions)); if (refiner == 0) { fprintf(stderr, "Error: Unable to construct TopologyRefiner " "from .obj file '%s'\n", filename); return 0; } int numVertices = refiner->GetNumVerticesTotal(); posVector.resize(numVertices); std::memcpy(&posVector[0].p[0], &shape->verts[0], numVertices * 3 * sizeof(float)); delete shape; return refiner; } } // end namespace // // The PatchGroup bundles objects used to create and evaluate a sparse set // of patches. Its construction creates a PatchTable and all other objects // necessary to evaluate patches associated with the specified subset of // faces provided. A simple method to tessellate a specified face is // provided. // // Note that, since the data buffers for the base level and refined levels // are separate (we want to avoid copying primvar data for the base level // of a potentially large mesh), that patch evaluation needs to account // for the separation when combining control points. // struct PatchGroup { PatchGroup(Far::PatchTableFactory::Options patchOptions, Far::TopologyRefiner const & baseRefinerArg, Far::PtexIndices const & basePtexIndicesArg, std::vector<Pos> const & basePositionsArg, std::vector<Index> const & baseFacesArg); ~PatchGroup(); void TessellateBaseFace(int face, PosVector & tessPoints, TriVector & tessTris) const; // Const reference members: Far::TopologyRefiner const & baseRefiner; Far::PtexIndices const & basePtexIndices; std::vector<Pos> const & basePositions; std::vector<Index> const & baseFaces; // Members constructed to evaluate patches: Far::PatchTable * patchTable; Far::PatchMap * patchMap; int patchFaceSize; std::vector<Pos> localPositions; }; PatchGroup::PatchGroup(Far::PatchTableFactory::Options patchOptions, Far::TopologyRefiner const & baseRefinerArg, Far::PtexIndices const & basePtexIndicesArg, std::vector<Pos> const & basePositionsArg, std::vector<Index> const & baseFacesArg) : baseRefiner(baseRefinerArg), basePtexIndices(basePtexIndicesArg), basePositions(basePositionsArg), baseFaces(baseFacesArg) { // Create a local refiner (sharing the base level), apply adaptive // refinement to the given subset of base faces, and construct a patch // table (and its associated map) for the same set of faces: // Far::ConstIndexArray groupFaces(&baseFaces[0], (int)baseFaces.size()); Far::TopologyRefiner *localRefiner = Far::TopologyRefinerFactory<Far::TopologyDescriptor>::Create( baseRefiner); localRefiner->RefineAdaptive( patchOptions.GetRefineAdaptiveOptions(), groupFaces); patchTable = Far::PatchTableFactory::Create(*localRefiner, patchOptions, groupFaces); patchMap = new Far::PatchMap(*patchTable); patchFaceSize = Sdc::SchemeTypeTraits::GetRegularFaceSize(baseRefiner.GetSchemeType()); // Compute the number of refined and local points needed to evaluate the // patches, allocate and interpolate. This varies from tutorial_5_1 in // that the primvar buffer for the base vertices is separate from the // refined vertices and local patch points (which must also be accounted // for when evaluating the patches). // int nBaseVertices = localRefiner->GetLevel(0).GetNumVertices(); int nRefinedVertices = localRefiner->GetNumVerticesTotal() - nBaseVertices; int nLocalPoints = patchTable->GetNumLocalPoints(); localPositions.resize(nRefinedVertices + nLocalPoints); if (nRefinedVertices) { Far::PrimvarRefiner primvarRefiner(*localRefiner); Pos const * src = &basePositions[0]; Pos * dst = &localPositions[0]; for (int level = 1; level < localRefiner->GetNumLevels(); ++level) { primvarRefiner.Interpolate(level, src, dst); src = dst; dst += localRefiner->GetLevel(level).GetNumVertices(); } } if (nLocalPoints) { patchTable->GetLocalPointStencilTable()->UpdateValues( &basePositions[0], nBaseVertices, &localPositions[0], &localPositions[nRefinedVertices]); } delete localRefiner; } PatchGroup::~PatchGroup() { delete patchTable; delete patchMap; } void PatchGroup::TessellateBaseFace(int face, PosVector & tessPoints, TriVector & tessTris) const { // Tesselate the face with points at the midpoint of the face and at // each corner, and triangles connecting the midpoint to each edge. // Irregular faces require an aribrary number of corners points, but // all are at the origin of the child face of the irregular base face: // float const quadPoints[5][2] = { { 0.5f, 0.5f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; float const triPoints[4][2] = { { 0.5f, 0.5f }, { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 0.0f, 1.0f } }; float const irregPoints[4][2] = { { 1.0f, 1.0f }, { 0.0f, 0.0f } }; // Determine the topology of the given base face and the resulting // tessellation points and faces to generate: // int baseFace = baseFaces[face]; int faceSize = baseRefiner.GetLevel(0).GetFaceVertices(baseFace).size(); bool faceIsIrregular = (faceSize != patchFaceSize); int nTessPoints = faceSize + 1; int nTessFaces = faceSize; tessPoints.resize(nTessPoints); tessTris.resize(nTessFaces); // Compute the mid and corner points -- remember that for an irregular // face, we must reference the individual ptex faces for each corner: // int ptexFace = basePtexIndices.GetFaceId(baseFace); int numBaseVerts = (int) basePositions.size(); for (int i = 0; i < nTessPoints; ++i) { // Choose the (s,t) coordinate from the fixed tessellation: float const * st = faceIsIrregular ? irregPoints[i != 0] : ((faceSize == 4) ? quadPoints[i] : triPoints[i]); // Locate the patch corresponding to the face ptex idx and (s,t) // and evaluate: int patchFace = ptexFace; if (faceIsIrregular && (i > 0)) { patchFace += i - 1; } Far::PatchTable::PatchHandle const * handle = patchMap->FindPatch(patchFace, st[0], st[1]); assert(handle); float pWeights[20]; patchTable->EvaluateBasis(*handle, st[0], st[1], pWeights); // Identify the patch cvs and combine with the evaluated weights -- // remember to distinguish cvs in the base level: Far::ConstIndexArray cvIndices = patchTable->GetPatchVertices(*handle); Pos & pos = tessPoints[i]; pos.Clear(); for (int cv = 0; cv < cvIndices.size(); ++cv) { int cvIndex = cvIndices[cv]; if (cvIndex < numBaseVerts) { pos.AddWithWeight(basePositions[cvIndex], pWeights[cv]); } else { pos.AddWithWeight(localPositions[cvIndex - numBaseVerts], pWeights[cv]); } } } // Assign triangles connecting the midpoint of the base face to the // points computed at the ends of each of its edges: // for (int i = 0; i < nTessFaces; ++i) { tessTris[i] = Tri(0, 1 + i, 1 + ((i + 1) % faceSize)); } } // // Command line arguments parsed to provide run-time options: // class Args { public: std::string inputObjFile; Sdc::SchemeType schemeType; int geoMultiplier; int maxPatchDepth; int numPatchGroups; bool noTessFlag; bool noOutputFlag; public: Args(int argc, char ** argv) : inputObjFile(), schemeType(Sdc::SCHEME_CATMARK), geoMultiplier(10), maxPatchDepth(3), numPatchGroups(10), noTessFlag(false), noOutputFlag(false) { // Parse and assign standard arguments and Obj files: ArgOptions args; args.Parse(argc, argv); maxPatchDepth = args.GetLevel(); schemeType = ConvertShapeSchemeToSdcType(args.GetDefaultScheme()); const std::vector<const char *> objFiles = args.GetObjFiles(); if (!objFiles.empty()) { for (size_t i = 1; i < objFiles.size(); ++i) { fprintf(stderr, "Warning: .obj file '%s' ignored\n", objFiles[i]); } inputObjFile = std::string(objFiles[0]); } // Parse remaining arguments specific to this example: const std::vector<const char *> &rargs = args.GetRemainingArgs(); for (size_t i = 0; i < rargs.size(); ++i) { if (!strcmp(rargs[i], "-groups")) { if (++i < rargs.size()) numPatchGroups = atoi(rargs[i]); } else if (!strcmp(rargs[i], "-mult")) { if (++i < rargs.size()) geoMultiplier = atoi(rargs[i]); } else if (!strcmp(rargs[i], "-notess")) { noTessFlag = true; } else if (!strcmp(rargs[i], "-nooutput")) { noOutputFlag = true; } else { fprintf(stderr, "Warning: Argument '%s' ignored\n", rargs[i]); } } } private: Args() { } }; // // Load command line arguments and geometry, then divide the mesh into groups // of faces from which to create and tessellate patches: // int main(int argc, char **argv) { Args args(argc, argv); // // Create or load the base geometry (command line arguments allow a // .obj file to be specified). In addition to the TopologyRefiner // and set of positions for the base vertices, a set of PtexIndices is // also required to evaluate patches, so build it here once for use // elsewhere: // std::vector<Pos> basePositions; Far::TopologyRefiner * baseRefinerPtr = args.inputObjFile.empty() ? createTopologyRefinerDefault(args.geoMultiplier, basePositions) : createTopologyRefinerFromObj(args.inputObjFile, args.schemeType, basePositions); assert(baseRefinerPtr); Far::TopologyRefiner & baseRefiner = *baseRefinerPtr; Far::PtexIndices basePtexIndices(baseRefiner); // // Determine the sizes of the patch groups specified -- there will be // two sizes that differ by one to account for unequal division: // int numBaseFaces = baseRefiner.GetNumFacesTotal(); int numPatchGroups = args.numPatchGroups; if (numPatchGroups > numBaseFaces) { numPatchGroups = numBaseFaces; } else if (numPatchGroups < 1) { numPatchGroups = 1; } int lesserGroupSize = numBaseFaces / numPatchGroups; int numLargerGroups = numBaseFaces - (numPatchGroups * lesserGroupSize); // // Define the options used to construct the patches for each group. // Unless suppressed, a tessellation in Obj format will also be printed // to standard output, so keep track of the vertex indices. // Far::PatchTableFactory::Options patchOptions(args.maxPatchDepth); patchOptions.generateVaryingTables = false; patchOptions.shareEndCapPatchPoints = false; patchOptions.endCapType = Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS; int objVertCount = 0; PosVector tessPoints; TriVector tessFaces; for (int i = 0; i < numPatchGroups; ++i) { // // Initialize a vector with a group of base faces from which to // create and evaluate patches: // Index minFace = i * lesserGroupSize + std::min(i, numLargerGroups); Index maxFace = minFace + lesserGroupSize + (i < numLargerGroups); std::vector<Far::Index> baseFaces(maxFace - minFace); for (int face = minFace; face < maxFace; ++face) { baseFaces[face - minFace] = face; } // // Declare a PatchGroup and tessellate its base faces -- generating // vertices and faces in Obj format to standard output: // PatchGroup patchGroup(patchOptions, baseRefiner, basePtexIndices, basePositions, baseFaces); if (args.noTessFlag) continue; if (!args.noOutputFlag) { printf("g patchGroup_%d\n", i); } for (int j = 0; j < (int) baseFaces.size(); ++j) { patchGroup.TessellateBaseFace(j, tessPoints, tessFaces); if (!args.noOutputFlag) { int nVerts = (int) tessPoints.size(); for (int k = 0; k < nVerts; ++k) { float const * p = tessPoints[k].p; printf("v %f %f %f\n", p[0], p[1], p[2]); } int nTris = (int) tessFaces.size(); int vBase = 1 + objVertCount; for (int k = 0; k < nTris; ++k) { int const * v = tessFaces[k].v; printf("f %d %d %d\n", vBase + v[0], vBase + v[1], vBase + v[2]); } objVertCount += nVerts; } } } delete baseRefinerPtr; return EXIT_SUCCESS; }
Generated on: 2025-04-08 18:05 UTC.