# far_tutorial_5_2.cpp

https://github.com/PixarAnimationStudios/OpenSubdiv/blob/release/tutorials/far/tutorial_5_2/far_tutorial_5_2.cpp

System Message: WARNING/2 (`/build/opensubdiv/src/OpenSubdiv-3_6_1/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;
        }

- User Docs
- API Docs
- Release Notes
- Forum

TM & © 1986 - 2017 PIXAR. ALL RIGHTS RESERVED. Terms of Use