far_tutorial_5_2.cpp

far_tutorial_5_2.cpp


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


//------------------------------------------------------------------------------
//  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/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(x * 2.0f, y * 2.0f, 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], &shape->verts[0], numVertices * 3 * sizeof(float));

        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) {

        for (int i = 1; i < argc; ++i) {
            if (strstr(argv[i], ".obj")) {
                if (inputObjFile.empty()) {
                    inputObjFile = std::string(argv[i]);
                } else {
                    fprintf(stderr, "Warning: .obj file '%s' ignored\n", argv[i]);
                }
            } else if (!strcmp(argv[i], "-l")) {
                if (++i < argc) maxPatchDepth = atoi(argv[i]);
            } else if (!strcmp(argv[i], "-level")) {
                if (++i < argc) maxPatchDepth = atoi(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], "-groups")) {
                if (++i < argc) numPatchGroups = atoi(argv[i]);
            } else if (!strcmp(argv[i], "-mult")) {
                if (++i < argc) geoMultiplier = atoi(argv[i]);
            } else if (!strcmp(argv[i], "-notess")) {
                noTessFlag = true;
            } else if (!strcmp(argv[i], "-nooutput")) {
                noOutputFlag = true;
            } else {
                fprintf(stderr, "Warning: Argument '%s' ignored\n", argv[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;
}