# bfr_tutorial_2_2.cpp

https://github.com/PixarAnimationStudios/OpenSubdiv/blob/release/tutorials/bfr/tutorial_2_2/bfr_tutorial_2_2.cpp

System Message: WARNING/2 (`/build/opensubdiv/src/OpenSubdiv-3_6_1/build/documentation/bfr_tutorial_2_2.rst`, line 9)

Cannot analyze code. Pygments package not found.

    .. code:: c++

        //------------------------------------------------------------------------------
        //  Tutorial description:
        //
        //      This tutorial builds on others using the SurfaceFactory, Surface
        //      and Tessellation classes by using more of the functionality of the
        //      Tessellation class to construct a tessellation of the mesh that is
        //      topologically watertight, i.e. resulting points evaluated along
        //      shared edges or vertices are shared and not duplicated.
        //
        //      Since Tessellation provides points around its boundary first, the
        //      evaluated points for shared vertices and edges are identified when
        //      constructed and reused when shared later. The boundary of the
        //      tessellation of a face is therefore a collection of shared points
        //      and methods of Tessellation help to remap the faces generated to
        //      the shared set of points.
        //

        #include <opensubdiv/far/topologyRefiner.h>
        #include <opensubdiv/bfr/refinerSurfaceFactory.h>
        #include <opensubdiv/bfr/surface.h>
        #include <opensubdiv/bfr/tessellation.h>

        #include <vector>
        #include <string>
        #include <cstring>
        #include <cstdio>
        #include <cassert>

        //  Local headers with support for this tutorial in "namespace tutorial"
        #include "./meshLoader.h"
        #include "./objWriter.h"

        using namespace OpenSubdiv;

        using Far::Index;
        using Far::IndexArray;
        using Far::ConstIndexArray;

        //
        //  Simple command line arguments to provide input and run-time options:
        //
        class Args {
        public:
            std::string     inputObjFile;
            std::string     outputObjFile;
            Sdc::SchemeType schemeType;
            int             tessUniformRate;
            bool            tessQuadsFlag;

        public:
            Args(int argc, char * argv[]) :
                inputObjFile(),
                outputObjFile(),
                schemeType(Sdc::SCHEME_CATMARK),
                tessUniformRate(5),
                tessQuadsFlag(false) {

                for (int i = 1; i < argc; ++i) {
                    if (strstr(argv[i], ".obj")) {
                        if (inputObjFile.empty()) {
                            inputObjFile = std::string(argv[i]);
                        } else {
                            fprintf(stderr,
                                "Warning: Extra Obj file '%s' ignored\n", argv[i]);
                        }
                    } else if (!strcmp(argv[i], "-o")) {
                        if (++i < argc) outputObjFile = std::string(argv[i]);
                    } else if (!strcmp(argv[i], "-bilinear")) {
                        schemeType = Sdc::SCHEME_BILINEAR;
                    } else if (!strcmp(argv[i], "-catmark")) {
                        schemeType = Sdc::SCHEME_CATMARK;
                    } else if (!strcmp(argv[i], "-loop")) {
                        schemeType = Sdc::SCHEME_LOOP;
                    } else if (!strcmp(argv[i], "-res")) {
                        if (++i < argc) tessUniformRate = atoi(argv[i]);
                    } else if (!strcmp(argv[i], "-quads")) {
                        tessQuadsFlag = true;
                    } else {
                        fprintf(stderr,
                            "Warning: Unrecognized argument '%s' ignored\n", argv[i]);
                    }
                }
            }

        private:
            Args() { }
        };

        //
        //  Simple local structs supporting shared points for vertices and edges:
        //
        namespace {
            struct SharedVertex {
                SharedVertex() : pointIndex(-1) { }

                bool IsSet() const { return pointIndex >= 0; }
                void Set(int index) { pointIndex = index; }

                int pointIndex;
            };

            struct SharedEdge {
                SharedEdge() : pointIndex(-1), numPoints(0) { }

                bool IsSet() const { return pointIndex >= 0; }
                void Set(int index, int n) { pointIndex = index, numPoints = n; }

                int pointIndex;
                int numPoints;
            };
        } // end namespace

        //
        //  The main tessellation function:  given a mesh and vertex positions,
        //  tessellate each face -- writing results in Obj format.
        //
        //  This tessellation function differs from earlier tutorials in that it
        //  computes and reuses shared points at vertices and edges of the mesh.
        //  There are several ways to compute these shared points, and which is
        //  best depends on context.
        //
        //  Dealing with shared data poses complications for threading in general,
        //  so computing all points for the vertices and edges up front may be
        //  preferred -- despite the fact that faces will be visited more than once
        //  (first when generating potentially shared vertex or edge points, and
        //  later when generating any interior points). The loops for vertices and
        //  edges can be threaded and the indexing of the shared points is simpler.
        //
        //  For the single-threaded case here, the faces are each processed in
        //  order and any shared points will be computed and used as needed. So
        //  each face is visited once (and so each Surface initialized once) but
        //  the bookkeeping to deal with indices of shared points becomes more
        //  complicated.
        //
        void
        tessellateToObj(Far::TopologyRefiner const & meshTopology,
                        std::vector<float>   const & meshVertexPositions,
                        Args                 const & options) {

            //
            //  Use simpler local type names for the Surface and its factory:
            //
            typedef Bfr::RefinerSurfaceFactory<> SurfaceFactory;
            typedef Bfr::Surface<float>          Surface;

            //
            //  Initialize the SurfaceFactory for the given base mesh (very low
            //  cost in terms of both time and space) and tessellate each face
            //  independently (i.e. no shared vertices):
            //
            //  Note that the SurfaceFactory is not thread-safe by default due to
            //  use of an internal cache.  Creating a separate instance of the
            //  SurfaceFactory for each thread is one way to safely parallelize
            //  this loop.  Another (preferred) is to assign a thread-safe cache
            //  to the single instance.
            //
            //  First declare any evaluation options when initializing (though
            //  none are used in this simple case):
            //
            SurfaceFactory::Options surfaceOptions;

            SurfaceFactory meshSurfaceFactory(meshTopology, surfaceOptions);

            //
            //  The Surface to be constructed and evaluated for each face -- as
            //  well as the intermediate and output data associated with it -- can
            //  be declared in the scope local to each face. But since dynamic
            //  memory is involved with these variables, it is preferred to declare
            //  them outside that loop to preserve and reuse that dynamic memory.
            //
            Surface faceSurface;

            std::vector<float> facePatchPoints;

            std::vector<float> outCoords;
            std::vector<float> outPos, outDu, outDv;
            std::vector<int>   outFacets;

            //
            //  Assign Tessellation Options applied for all faces.  Tessellations
            //  allow the creating of either 3- or 4-sided faces -- both of which
            //  are supported here via a command line option:
            //
            int const tessFacetSize = 3 + options.tessQuadsFlag;

            Bfr::Tessellation::Options tessOptions;
            tessOptions.SetFacetSize(tessFacetSize);
            tessOptions.PreserveQuads(options.tessQuadsFlag);

            //
            //  Declare vectors to identify shared tessellation points at vertices
            //  and edges and their indices around the boundary of a face:
            //
            Far::TopologyLevel const & baseLevel = meshTopology.GetLevel(0);

            std::vector<SharedVertex> sharedVerts(baseLevel.GetNumVertices());
            std::vector<SharedEdge>   sharedEdges(baseLevel.GetNumEdges());

            std::vector<int> tessBoundaryIndices;

            //
            //  Process each face, writing the output of each in Obj format:
            //
            tutorial::ObjWriter objWriter(options.outputObjFile);

            int numMeshPointsEvaluated = 0;

            int numFaces = meshSurfaceFactory.GetNumFaces();
            for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {
                //
                //  Initialize the Surface for this face -- if valid (skipping
                //  holes and boundary faces in some rare cases):
                //
                if (!meshSurfaceFactory.InitVertexSurface(faceIndex, &faceSurface)) {
                    continue;
                }

                //
                //  Declare a simple uniform Tessellation for the Parameterization
                //  of this face and identify coordinates of the points to evaluate:
                //
                Bfr::Tessellation tessPattern(faceSurface.GetParameterization(),
                                              options.tessUniformRate, tessOptions);

                int numOutCoords = tessPattern.GetNumCoords();

                outCoords.resize(numOutCoords * 2);

                tessPattern.GetCoords(outCoords.data());

                //
                //  Prepare the patch points for the Surface, then use them to
                //  evaluate output points for all identified coordinates:
                //
                //  Resize patch point and output arrays:
                int pointSize = 3;

                facePatchPoints.resize(faceSurface.GetNumPatchPoints() * pointSize);

                outPos.resize(numOutCoords * pointSize);
                outDu.resize(numOutCoords * pointSize);
                outDv.resize(numOutCoords * pointSize);

                //  Populate the patch point array:
                faceSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
                                               facePatchPoints.data(), pointSize);

                //
                //  Evaluate the sample points of the Tessellation:
                //
                //  First traverse the boundary of the face to determine whether
                //  to evaluate or share points on vertices and edges of the face.
                //  Both pre-existing and new boundary points are identified by
                //  index in an array for later use.  The interior points are all
                //  trivially computed after the boundary is dealt with.
                //
                //  Identify the boundary and interior coords and initialize the
                //  index array for the potentially shared boundary points:
                //
                int numBoundaryCoords = tessPattern.GetNumBoundaryCoords();
                int numInteriorCoords = numOutCoords - numBoundaryCoords;

                float const * tessBoundaryCoords = &outCoords[0];
                float const * tessInteriorCoords = &outCoords[numBoundaryCoords*2];

                ConstIndexArray fVerts = baseLevel.GetFaceVertices(faceIndex);
                ConstIndexArray fEdges = baseLevel.GetFaceEdges(faceIndex);

                tessBoundaryIndices.resize(numBoundaryCoords);

                //
                //  Walk around the face, inspecting each vertex and outgoing edge,
                //  and populating the index array of boundary points:
                //
                float * patchPointData = facePatchPoints.data();

                int boundaryIndex = 0;
                int numFacePointsEvaluated = 0;
                for (int i = 0; i < fVerts.size(); ++i) {
                    Index vertIndex = fVerts[i];
                    Index edgeIndex = fEdges[i];
                    int   edgeRate  = options.tessUniformRate;

                    //
                    //  Evaluate/assign or retrieve the shared point for the vertex:
                    //
                    SharedVertex & sharedVertex = sharedVerts[vertIndex];
                    if (!sharedVertex.IsSet()) {
                        //  Identify indices of the new shared point in both the
                        //  mesh and face and increment their inventory:
                        int indexInMesh = numMeshPointsEvaluated++;
                        int indexInFace = numFacePointsEvaluated++;

                        sharedVertex.Set(indexInMesh);

                        //  Evaluate new shared point and assign index to boundary:
                        float const * uv = &tessBoundaryCoords[boundaryIndex*2];

                        int pIndex = indexInFace * pointSize;
                        faceSurface.Evaluate(uv, patchPointData, pointSize,
                                &outPos[pIndex], &outDu[pIndex], &outDv[pIndex]);

                        tessBoundaryIndices[boundaryIndex++] = indexInMesh;
                    } else {
                        //  Assign shared vertex point index to boundary:
                        tessBoundaryIndices[boundaryIndex++] = sharedVertex.pointIndex;
                    }

                    //
                    //  Evaluate/assign or retrieve all shared points for the edge:
                    //
                    //  To keep this simple, assume the edge is manifold. So the
                    //  second face sharing the edge has that edge in the opposite
                    //  direction in its boundary relative to the first face --
                    //  making it necessary to reverse the order of shared points
                    //  for the boundary of the second face.
                    //
                    //  To support a non-manifold edge, all subsequent faces that
                    //  share the assigned shared edge must determine if their
                    //  orientation of that edge is reversed relative to the first
                    //  face for which the shared edge points were evaluated. So a
                    //  little more book-keeping and/or inspection is required.
                    //
                    if (edgeRate > 1) {
                        int pointsPerEdge = edgeRate - 1;

                        SharedEdge & sharedEdge = sharedEdges[edgeIndex];
                        if (!sharedEdge.IsSet()) {
                            //  Identify indices of the new shared points in both the
                            //  mesh and face and increment their inventory:
                            int nextInMesh = numMeshPointsEvaluated;
                            int nextInFace = numFacePointsEvaluated;

                            numFacePointsEvaluated += pointsPerEdge;
                            numMeshPointsEvaluated += pointsPerEdge;

                            sharedEdge.Set(nextInMesh, pointsPerEdge);

                            //  Evaluate shared points and assign indices to boundary:
                            float const * uv = &tessBoundaryCoords[boundaryIndex*2];

                            for (int j = 0; j < pointsPerEdge; ++j, uv += 2) {
                                int pIndex = (nextInFace++) * pointSize;
                                faceSurface.Evaluate(uv, patchPointData, pointSize,
                                    &outPos[pIndex], &outDu[pIndex], &outDv[pIndex]);

                                tessBoundaryIndices[boundaryIndex++] = nextInMesh++;
                            }
                        } else {
                            //  See note above on simplification for manifold edges
                            assert(!baseLevel.IsEdgeNonManifold(edgeIndex));

                            //  Assign shared points to boundary in reverse order:
                            int nextInMesh = sharedEdge.pointIndex + pointsPerEdge - 1;
                            for (int j = 0; j < pointsPerEdge; ++j) {
                                tessBoundaryIndices[boundaryIndex++] = nextInMesh--;
                            }
                        }
                    }
                }

                //
                //  Evaluate any interior points unique to this face -- appending
                //  them to those shared points computed above for the boundary:
                //
                if (numInteriorCoords) {
                    float const * uv = tessInteriorCoords;

                    int iLast = numFacePointsEvaluated + numInteriorCoords;
                    for (int i = numFacePointsEvaluated; i < iLast; ++i, uv += 2) {
                        int pIndex = i * pointSize;
                        faceSurface.Evaluate(uv, patchPointData, pointSize,
                                 &outPos[pIndex], &outDu[pIndex], &outDv[pIndex]);
                    }
                    numFacePointsEvaluated += numInteriorCoords;
                    numMeshPointsEvaluated += numInteriorCoords;
                }

                //
                //  Remember to trim/resize the arrays storing evaluation results
                //  for new points to reflect the size actually populated.
                //
                outPos.resize(numFacePointsEvaluated * pointSize);
                outDu.resize(numFacePointsEvaluated * pointSize);
                outDv.resize(numFacePointsEvaluated * pointSize);

                //
                //  Identify the faces of the Tessellation:
                //
                //  Note that the coordinate indices used by the facets are local
                //  to the face (i.e. they range from [0..N-1], where N is the
                //  number of coordinates in the pattern) and so need to be offset
                //  when writing to Obj format.
                //
                //  For more advanced use, the coordinates associated with the
                //  boundary and interior of the pattern are distinguishable so
                //  that those on the boundary can be easily remapped to refer to
                //  shared edge or corner points, while those in the interior can
                //  be separately offset or similarly remapped.
                //
                //  So transform the indices of the facets here as needed using
                //  the indices of shared boundary points assembled above and a
                //  suitable offset for the new interior points added:
                //
                int tessInteriorOffset = numMeshPointsEvaluated - numOutCoords;

                int numFacets = tessPattern.GetNumFacets();
                outFacets.resize(numFacets * tessFacetSize);
                tessPattern.GetFacets(outFacets.data());

                tessPattern.TransformFacetCoordIndices(outFacets.data(),
                                tessBoundaryIndices.data(), tessInteriorOffset);

                //
                //  Write the evaluated points and faces connecting them as Obj:
                //
                objWriter.WriteGroupName("baseFace_", faceIndex);

                objWriter.WriteVertexPositions(outPos);
                objWriter.WriteVertexNormals(outDu, outDv);

                objWriter.WriteFaces(outFacets, tessFacetSize, true, false);
            }
        }

        //
        //  Load command line arguments, specified or default geometry and process:
        //
        int
        main(int argc, char * argv[]) {

            Args args(argc, argv);

            Far::TopologyRefiner * meshTopology = 0;
            std::vector<float>     meshVtxPositions;
            std::vector<float>     meshFVarUVs;

            meshTopology = tutorial::createTopologyRefiner(
                    args.inputObjFile, args.schemeType, meshVtxPositions, meshFVarUVs);
            if (meshTopology == 0) {
                return EXIT_FAILURE;
            }

            tessellateToObj(*meshTopology, meshVtxPositions, args);

            delete meshTopology;
            return EXIT_SUCCESS;
        }

        //------------------------------------------------------------------------------

- User Docs
- API Docs
- Release Notes
- Forum

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