//
//   Copyright 2013 Pixar
//
//   Licensed under the terms set forth in the LICENSE.txt file available at
//   https://opensubdiv.org/license.
//
#include "../far/patchTableFactory.h"
#include "../far/patchBuilder.h"
#include "../far/error.h"
#include "../far/ptexIndices.h"
#include "../far/topologyRefiner.h"
#include "../vtr/level.h"
#include "../vtr/fvarLevel.h"
#include "../vtr/refinement.h"
#include "../vtr/stackBuffer.h"

#include <algorithm>
#include <cassert>
#include <cstring>

#include <cstdio>

namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Far {

using Vtr::Array;
using Vtr::ConstArray;
using Vtr::IndexVector;
using Vtr::internal::Level;
using Vtr::internal::StackBuffer;

namespace {
    //
    //  Helpers for compiler warnings and floating point equality tests
    //
#ifdef __INTEL_COMPILER
#pragma warning (push)
#pragma warning disable 1572
#endif
    inline bool isSharpnessEqual(float s1, float s2) { return (s1 == s2); }
#ifdef __INTEL_COMPILER
#pragma warning (pop)
#endif

    inline int
    assignSharpnessIndex(float sharpness, std::vector<float> & sharpnessValues) {

        // linear search
        for (int i=0; i<(int)sharpnessValues.size(); ++i) {
            if (isSharpnessEqual(sharpnessValues[i], sharpness)) {
                return i;
            }
        }
        sharpnessValues.push_back(sharpness);
        return (int)sharpnessValues.size()-1;
    }

    inline bool
    isBoundaryFace(Level const & level, Index face) {

        return (level.getFaceCompositeVTag(face)._boundary != 0);
    }

    inline void
    offsetIndices(Index indices[], int size, int offset) {

        for (int i = 0; i < size; ++i) {
            indices[i] += offset;
        }
    }
} // namespace anon


//
//  The main PatchTableBuilder class with context
//
//  Helper class aggregating transient contextual data structures during the
//  creation of a patch table.  This helps keeping the factory class stateless.
//
class PatchTableBuilder {
public:
    //
    //  Public interface intended for use by the PatchTableFactory -- all
    //  else is solely for internal use:
    //
    typedef PatchTableFactory::Options Options;

    PatchTableBuilder(TopologyRefiner const & refiner, Options options,
                      ConstIndexArray selectedFaces);
    ~PatchTableBuilder();

    bool UniformPolygonsSpecified() const { return _buildUniformLinear; }

    void BuildUniformPolygons();
    void BuildPatches();

    PatchTable * GetPatchTable() const { return _table; };

private:
    typedef PatchTable::StencilTablePtr StencilTablePtr;

    //  Simple struct to store <face,level> pair for a patch:
    struct PatchTuple {
        PatchTuple(Index face, int level) : faceIndex(face), levelIndex(level) { }

        Index faceIndex;
        int   levelIndex;
    };
    typedef std::vector<PatchTuple> PatchTupleVector;

    //  Struct comprising a collection of topological properties for a patch that
    //  may be shared (between vertex and face-varying patches):
    struct PatchInfo {
        PatchInfo() : isRegular(false), isRegSingleCrease(false),
                      regBoundaryMask(0), regSharpness(0.0f),
                      paramBoundaryMask(0) { }

        bool         isRegular;
        bool         isRegSingleCrease;
        int          regBoundaryMask;
        float        regSharpness;
        Level::VSpan irregCornerSpans[4];
        int          paramBoundaryMask;

        SparseMatrix<float>  fMatrix;
        SparseMatrix<double> dMatrix;
    };

private:
    //
    //  Internal LocalPointHelper class
    //
    //  A LocalPointHelper manages the number, sharing of and StencilTable for
    //  the local points of a patch or one of its face-varying channels.  An
    //  instance of the helper does not know anything about the properties of
    //  the Builder classes that use it.  It can combine local points for any
    //  patch types all in one, so the regular and irregular patch types can
    //  both be local and differ, e.g. Bezier for regular and Gregory for
    //  irregular, and points can be effectively shared, appropriate stencils
    //  created, etc.
    //
    //  While not dependent on a particular patch type, some methods do require
    //  a patch type argument in order to know what can be done with its local
    //  points, e.g. which points can be shared with adjacent patches.
    //
    class LocalPointHelper {
    public:
        struct Options {
            Options() : shareLocalPoints(false),
                        reuseSourcePoints(false),
                        createStencilTable(true),
                        createVaryingTable(false),
                        doubleStencilTable(false) { }

            unsigned int shareLocalPoints   : 1;
            unsigned int reuseSourcePoints  : 1;
            unsigned int createStencilTable : 1;
            unsigned int createVaryingTable : 1;
            unsigned int doubleStencilTable : 1;
        };

    public:
        LocalPointHelper(TopologyRefiner const & refiner,
                         Options const & options,
                         int fvarChannel,
                         int numLocalPointsExpected);
        ~LocalPointHelper();

    public:
        int GetNumLocalPoints() const { return _numLocalPoints; }

        template <typename REAL>
        int AppendLocalPatchPoints(int levelIndex, Index faceIndex,
                                   SparseMatrix<REAL> const &  conversionMatrix,
                                   PatchDescriptor::Type       patchType,
                                   Index const                 sourcePoints[],
                                   int                         sourcePointOffset,
                                   Index                       patchPoints[]);

        StencilTablePtr AcquireStencilTable() {
            return _options.doubleStencilTable
                ? acquireStencilTable<double>(_stencilTable)
                : acquireStencilTable<float>(_stencilTable);
        }

    private:
        //  Internal methods:
        template <typename REAL>
        void initializeStencilTable(int numLocalPointsExpected);

        template <typename REAL>
        void appendLocalPointStencil(SparseMatrix<REAL> const &  conversionMatrix,
                                     int                         stencilRow,
                                     Index const                 sourcePoints[],
                                     int                         sourcePointOffset);

        template <typename REAL>
        void appendLocalPointStencils(SparseMatrix<REAL> const &  conversionMatrix,
                                      Index const                 sourcePoints[],
                                      int                         sourcePointOffset);

        //  Methods for local point Varying stencils
        //      XXXX -- hope to get rid of these...
        template <typename REAL>
        void appendLocalPointVaryingStencil(int const *  varyingIndices,
                                            int          patchPointIndex,
                                            Index const  sourcePoints[],
                                            int          sourcePointOffset);

        template <typename REAL>
        StencilTablePtr acquireStencilTable(StencilTablePtr& stencilTableMember);

        Index findSharedCornerPoint(int levelIndex, Index valueIndex,
                                    Index newIndex);
        Index findSharedEdgePoint(int levelIndex, Index edgeIndex, int edgeEnd,
                                  Index newIndex);

    private:
        //  Member variables:
        TopologyRefiner const& _refiner;
        Options                _options;

        int _fvarChannel;
        int _numLocalPoints;
        int _localPointOffset;

        std::vector<IndexVector> _sharedCornerPoints;
        std::vector<IndexVector> _sharedEdgePoints;

        StencilTablePtr _stencilTable;

    //  This was hopefully transitional but will persist -- the should be
    //  no need for Varying local points or stencils associated with them.
    public:
        StencilTablePtr AcquireStencilTableVarying() {
            return _options.doubleStencilTable
                ? acquireStencilTable<double>(_stencilTableVarying)
                : acquireStencilTable<float>(_stencilTableVarying);
        }

        StencilTablePtr _stencilTableVarying;
    };

private:
    //
    //  Internal LegacyGregoryHelper class
    //
    //  This local class helps to populate the arrays in the PatchTable
    //  associated with legacy-Gregory patches, i.e. the quad-offset and
    //  vertex-valence tables.  These patches are always associated with
    //  faces at the last level of refinement, so only the face index in
    //  that level is required to identify them.
    //
    class LegacyGregoryHelper {
    public:
        LegacyGregoryHelper(TopologyRefiner const & ref) : _refiner(ref) { }
        ~LegacyGregoryHelper() { }

    public:
        int GetNumBoundaryPatches() const { return (int)_boundaryFaceIndices.size(); }
        int GetNumInteriorPatches() const { return (int)_interiorFaceIndices.size(); }

        void AddPatchFace(int level, Index face);
        void FinalizeQuadOffsets(  PatchTable::QuadOffsetsTable & qTable);
        void FinalizeVertexValence(PatchTable::VertexValenceTable & vTable,
                                   int lastLevelVertOffset);
    private:
        TopologyRefiner const& _refiner;
        std::vector<Index> _interiorFaceIndices;
        std::vector<Index> _boundaryFaceIndices;
    };

private:
    //  Builder methods for internal use:

    //  Simple queries:
    int getRefinerFVarChannel(int fvcInTable) const {
        return (fvcInTable >= 0) ? _fvarChannelIndices[fvcInTable] : -1;
    }

    bool isFVarChannelLinear(int fvcInTable) const {
        if (_options.generateFVarLegacyLinearPatches) return true;
        return (_refiner.GetFVarLinearInterpolation(
            getRefinerFVarChannel(fvcInTable)) == Sdc::Options::FVAR_LINEAR_ALL);
    }

    bool doesFVarTopologyMatch(PatchTuple const & patch, int fvcInTable) {
        return _patchBuilder->DoesFaceVaryingPatchMatch(
            patch.levelIndex, patch.faceIndex,
            getRefinerFVarChannel(fvcInTable));
    }

    //  Methods for identifying and assigning patch-related data:
    void identifyPatchTopology(PatchTuple const & patch, PatchInfo & patchInfo,
                               int fvcInTable = -1);

    int assignPatchPointsAndStencils(PatchTuple const & patch,
                                     PatchInfo const & patchInfo,
                                     Index * patchPoints,
                                     LocalPointHelper & localHelper,
                                     int fvcInTable = -1);

    int assignFacePoints(PatchTuple const & patch,
                         Index * patchPoints,
                         int fvcInTable = -1) const;

    //  High level methods for assembling the table:
    void identifyPatches();
    void appendPatch(int levelIndex, Index faceIndex);
    void findDescendantPatches(int levelIndex, Index faceIndex, int targetLevel);
    void populatePatches();

    void allocateVertexTables();
    void allocateFVarChannels();

    int estimateLocalPointCount(LocalPointHelper::Options const & options,
                                int fvcInTable = -1) const;

private:
    //  Refiner and Options passed on construction:
    TopologyRefiner const & _refiner;
    Options const           _options;
    ConstIndexArray         _selectedFaces;

    // Flags indicating the need for processing based on provided options
    unsigned int _requiresLocalPoints          : 1;
    unsigned int _requiresRegularLocalPoints   : 1;
    unsigned int _requiresIrregularLocalPoints : 1;
    unsigned int _requiresSharpnessArray       : 1;
    unsigned int _requiresFVarPatches          : 1;
    unsigned int _requiresVaryingPatches       : 1;
    unsigned int _requiresVaryingLocalPoints   : 1;

    unsigned int _buildUniformLinear : 1;

    // The PatchTable being constructed and classes to help its construction:
    PatchTable * _table;

    PatchBuilder *    _patchBuilder;
    PtexIndices const _ptexIndices;

    // Vector of tuples for each patch identified during topology traversal
    // and the numbers of irregular and irregular patches identifed:
    PatchTupleVector _patches;

    int _numRegularPatches;
    int _numIrregularPatches;

    // Vectors for remapping indices of vertices and fvar values as well
    // as the fvar channels (when a subset is chosen)
    std::vector<int>                _levelVertOffsets;
    std::vector< std::vector<int> > _levelFVarValueOffsets;
    std::vector<int>                _fvarChannelIndices;

    // State and helpers for legacy features
    bool                  _requiresLegacyGregoryTables;
    LegacyGregoryHelper * _legacyGregoryHelper;
};

// Constructor
PatchTableBuilder::PatchTableBuilder(
    TopologyRefiner const & refiner, Options opts, ConstIndexArray faces) :
    _refiner(refiner), _options(opts), _selectedFaces(faces),
    _table(0), _patchBuilder(0), _ptexIndices(refiner),
    _numRegularPatches(0), _numIrregularPatches(0),
    _legacyGregoryHelper(0) {

    if (_options.generateFVarTables) {
        // If client-code does not select specific channels, default to all
        // the channels in the refiner.
        if (_options.numFVarChannels==-1) {
            _fvarChannelIndices.resize(_refiner.GetNumFVarChannels());
            for (int fvc=0;fvc<(int)_fvarChannelIndices.size(); ++fvc) {
                _fvarChannelIndices[fvc] = fvc; // std::iota
            }
        } else {
            _fvarChannelIndices.assign(
                _options.fvarChannelIndices,
                _options.fvarChannelIndices + _options.numFVarChannels);
        }
    }

    //
    //  Will need to translate PatchTableFactory options to the newer set of
    //  PatchBuilder options in the near future.  And the state variables are
    //  a potentially complex combination of options and so are handled after
    //  the PatchBuilder construction (to potentially help) rather than in
    //  the initializer list.
    //
    PatchBuilder::Options patchOptions;

    patchOptions.regBasisType = PatchBuilder::BASIS_REGULAR;
    switch (_options.GetEndCapType()) {
        case Options::ENDCAP_BILINEAR_BASIS:
            patchOptions.irregBasisType = PatchBuilder::BASIS_LINEAR;
            break;
        case Options::ENDCAP_BSPLINE_BASIS:
            patchOptions.irregBasisType = PatchBuilder::BASIS_REGULAR;
            break;
        case Options::ENDCAP_GREGORY_BASIS:
            patchOptions.irregBasisType = PatchBuilder::BASIS_GREGORY;
            break;
        default:
            //  The PatchBuilder will infer if left un-specified
            patchOptions.irregBasisType = PatchBuilder::BASIS_UNSPECIFIED;
            break;
    }
    patchOptions.fillMissingBoundaryPoints   = true;
    patchOptions.approxInfSharpWithSmooth    = !_options.useInfSharpPatch;
    patchOptions.approxSmoothCornerWithSharp =
        _options.generateLegacySharpCornerPatches;

    _patchBuilder = PatchBuilder::Create(_refiner, patchOptions);

    //
    //  Initialize member variables that capture specified options:
    //
    _requiresRegularLocalPoints =
        (patchOptions.regBasisType != PatchBuilder::BASIS_REGULAR);
    _requiresIrregularLocalPoints =
        (_options.GetEndCapType() != Options::ENDCAP_LEGACY_GREGORY);
    _requiresLocalPoints =
        _requiresIrregularLocalPoints || _requiresRegularLocalPoints;

    _requiresSharpnessArray = _options.useSingleCreasePatch;
    _requiresFVarPatches = ! _fvarChannelIndices.empty();

    _requiresVaryingPatches = _options.generateVaryingTables;
    _requiresVaryingLocalPoints = _options.generateVaryingTables &&
                                  _options.generateVaryingLocalPoints;

    //  Option to be made public in future:
    bool options_generateNonLinearUniformPatches = false;

    _buildUniformLinear = _refiner.IsUniform() && !options_generateNonLinearUniformPatches;

    //
    //  Create and initialize the new PatchTable instance to be assembled:
    //
    _table = new PatchTable(_refiner.GetMaxValence());

    _table->_numPtexFaces = _ptexIndices.GetNumFaces();

    _table->_vertexPrecisionIsDouble = _options.patchPrecisionDouble;
    _table->_varyingPrecisionIsDouble = _options.patchPrecisionDouble;
    _table->_faceVaryingPrecisionIsDouble = _options.fvarPatchPrecisionDouble;

    _table->_varyingDesc = PatchDescriptor(_patchBuilder->GetLinearPatchType());

    //  State and helper to support LegacyGregory arrays in the PatchTable:
    _requiresLegacyGregoryTables = !_refiner.IsUniform() &&
        (_options.GetEndCapType() == Options::ENDCAP_LEGACY_GREGORY);

    if (_requiresLegacyGregoryTables) {
        _legacyGregoryHelper = new LegacyGregoryHelper(_refiner);
    }

}

PatchTableBuilder::~PatchTableBuilder() {

    delete _patchBuilder;
    delete _legacyGregoryHelper;
}


void
PatchTableBuilder::identifyPatchTopology(PatchTuple const & patch,
        PatchInfo & patchInfo, int fvarInTable) {

    int   patchLevel = patch.levelIndex;
    Index patchFace  = patch.faceIndex;

    int fvarInRefiner = getRefinerFVarChannel(fvarInTable);

    patchInfo.isRegular = _patchBuilder->IsPatchRegular(
        patchLevel, patchFace, fvarInTable);

    bool useDoubleMatrix = (fvarInRefiner < 0)
                         ? _options.patchPrecisionDouble
                         : _options.fvarPatchPrecisionDouble;

    if (patchInfo.isRegular) {
        patchInfo.regBoundaryMask = _patchBuilder->GetRegularPatchBoundaryMask(
            patchLevel, patchFace, fvarInRefiner);

        patchInfo.isRegSingleCrease = false;
        patchInfo.regSharpness      = 0.0f;
        patchInfo.paramBoundaryMask = patchInfo.regBoundaryMask;

        //  If converting to another basis, get the change-of-basis matrix:
        if (_requiresRegularLocalPoints) {
            // _patchBuilder->GetRegularConversionMatrix(...);
        }

        //
        //  Test regular interior patches for a single-crease patch when it
        //  was specified.
        //
        //  Note that the PatchTable clamps the sharpness of single-crease
        //  patches to that of the maximimu refinement level, so any single-
        //  crease patches at the last level will be reduced to regular
        //  patches (maintaining continuity with other semi-sharp patches
        //  also reduced to regular).
        //
        if (_requiresSharpnessArray &&
                (patchInfo.regBoundaryMask == 0) && (fvarInRefiner < 0)) {
            if (patchLevel < (int) _options.maxIsolationLevel) {
                PatchBuilder::SingleCreaseInfo creaseInfo;

                if (_patchBuilder->IsRegularSingleCreasePatch(
                        patchLevel, patchFace, creaseInfo)) {
                    creaseInfo.creaseSharpness =
                        std::min(creaseInfo.creaseSharpness,
                             (float)(_options.maxIsolationLevel - patchLevel));

                    patchInfo.isRegSingleCrease = true;
                    patchInfo.regSharpness      = creaseInfo.creaseSharpness;
                    patchInfo.paramBoundaryMask = (1 << creaseInfo.creaseEdgeInFace);
                }
            }
        }
    } else if (_requiresIrregularLocalPoints) {
        _patchBuilder->GetIrregularPatchCornerSpans(
            patchLevel, patchFace, patchInfo.irregCornerSpans, fvarInRefiner);

        if (useDoubleMatrix) {
            _patchBuilder->GetIrregularPatchConversionMatrix(
                patchLevel, patchFace, patchInfo.irregCornerSpans, patchInfo.dMatrix);
        } else {
            _patchBuilder->GetIrregularPatchConversionMatrix(
                patchLevel, patchFace, patchInfo.irregCornerSpans, patchInfo.fMatrix);
        }

        patchInfo.paramBoundaryMask = 0;
    }
}

int
PatchTableBuilder::assignPatchPointsAndStencils(PatchTuple const & patch,
        PatchInfo const & patchInfo, Index * patchPoints,
        LocalPointHelper & localHelper, int fvarInTable) {

    //
    //  This is where the interesting/complicated new work will take place
    //  when a change-of-basis is determined necessary and previously assigned
    //  to the PatchInfo
    //
    //  No change-of-basis means no local points or stencils associated with
    //  them, which should be trivial but should also only be true in the
    //  regular case.  (It is also the case that no local points will be
    //  generated for irregular patches when the LegacyGregory option is
    //  used -- so that possibility is still accounted for here.)
    //
    //  Regarding the return result, it should just be the size of the patch
    //  associated with the regular/irregular patch type chosen.  This could
    //  be retrieved from the PatchBuilder or PatchDescriptors -- alternatively
    //  a return value could be removed and the client left to increment by
    //  such a fixed step (which is already the case for FVar channels)
    //
    //  The more interesting size here is the number of local points/stencils
    //  added.
    //
    int fvarInRefiner = getRefinerFVarChannel(fvarInTable);

    int sourcePointOffset = (fvarInTable < 0)
                          ? _levelVertOffsets[patch.levelIndex]
                          : _levelFVarValueOffsets[fvarInTable][patch.levelIndex];

    bool useDoubleMatrix = (fvarInTable < 0)
                         ? _options.patchPrecisionDouble
                         : _options.fvarPatchPrecisionDouble;

    int numPatchPoints = 0;
    if (patchInfo.isRegular) {
        if (!_requiresRegularLocalPoints) {
            numPatchPoints = _patchBuilder->GetRegularPatchPoints(
                    patch.levelIndex, patch.faceIndex,
                    patchInfo.regBoundaryMask, patchPoints, fvarInRefiner);

            //  PatchBuilder set to fill missing boundary points so offset all
            offsetIndices(patchPoints, numPatchPoints, sourcePointOffset);
        } else {
            //
            //  Future support for regular patches converted to another basis.
            //  Note the "source points" are not returned in the same
            //  orientation and there may be fewer than expected number in the
            //  case of boundaries:
            /*
            StackBuffer<Index,64,true> sourcePoints(
                patchInfo.matrix.GetNumColumns());

            _patchBuilder->GetRegularPatchSourcePoints(
                    patch.levelIndex, patch.faceIndex,
                    patchInfo.regBoundaryMask, sourcePoints, fvarInRefiner);

            localHelper.AppendLocalPatchPoints(
                    patch.levelIndex, patch.faceIndex,
                    patchInfo.matrix, _patchBuilder->GetRegularPatchType(),
                    sourcePoints, sourcePointOffset, patchPoints);

            numPatchPoints = patchInfo.matrix.GetNumRows();
            */
        }
    } else if (_requiresIrregularLocalPoints) {
        int numSourcePoints = 0;
        if (useDoubleMatrix) {
            numSourcePoints = patchInfo.dMatrix.GetNumColumns();
            numPatchPoints  = patchInfo.dMatrix.GetNumRows();
        } else {
            numSourcePoints = patchInfo.fMatrix.GetNumColumns();
            numPatchPoints  = patchInfo.fMatrix.GetNumRows();
        }

        StackBuffer<Index,64,true> sourcePoints(numSourcePoints);

        _patchBuilder->GetIrregularPatchSourcePoints(
                patch.levelIndex, patch.faceIndex,
                patchInfo.irregCornerSpans, sourcePoints, fvarInRefiner);

        if (useDoubleMatrix) {
            localHelper.AppendLocalPatchPoints(
                    patch.levelIndex, patch.faceIndex,
                    patchInfo.dMatrix, _patchBuilder->GetIrregularPatchType(),
                    sourcePoints, sourcePointOffset, patchPoints);
        } else {
            localHelper.AppendLocalPatchPoints(
                    patch.levelIndex, patch.faceIndex,
                    patchInfo.fMatrix, _patchBuilder->GetIrregularPatchType(),
                    sourcePoints, sourcePointOffset, patchPoints);
        }
    }
    return numPatchPoints;
}

int
PatchTableBuilder::assignFacePoints(PatchTuple const & patch,
                                    Index * patchPoints,
                                    int fvarInTable) const {

    Level const & level = _refiner.getLevel(patch.levelIndex);

    int facePointOffset = (fvarInTable < 0)
                        ? _levelVertOffsets[patch.levelIndex]
                        : _levelFVarValueOffsets[fvarInTable][patch.levelIndex];

    int fvarInRefiner = getRefinerFVarChannel(fvarInTable);

    ConstIndexArray facePoints = (fvarInRefiner < 0)
                    ? level.getFaceVertices(patch.faceIndex)
                    : level.getFaceFVarValues(patch.faceIndex, fvarInRefiner);

    for (int i = 0; i < facePoints.size(); ++i) {
        patchPoints[i] = facePoints[i] + facePointOffset;
    }
    return facePoints.size();
}

//
//  Reserves tables based on contents of the PatchArrayVector in the PatchTable:
//
void
PatchTableBuilder::allocateVertexTables() {

    int ncvs = 0, npatches = 0;
    for (int i=0; i<_table->GetNumPatchArrays(); ++i) {
        npatches += _table->GetNumPatches(i);
        ncvs += _table->GetNumControlVertices(i);
    }

    if (ncvs==0 || npatches==0)
        return;

    _table->_patchVerts.resize( ncvs );

    _table->_paramTable.resize( npatches );

    if (_requiresVaryingPatches && !_buildUniformLinear) {
        _table->allocateVaryingVertices(
            PatchDescriptor(_patchBuilder->GetLinearPatchType()), npatches);
    }

    if (_requiresSharpnessArray) {
        _table->_sharpnessIndices.resize( npatches, Vtr::INDEX_INVALID );
    }
}

//
//  Allocate face-varying tables
//
void
PatchTableBuilder::allocateFVarChannels() {

    int npatches = _table->GetNumPatchesTotal();

    _table->allocateFVarPatchChannels((int)_fvarChannelIndices.size());

    // Initialize each channel
    for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
        int refinerChannel = _fvarChannelIndices[fvc];

        Sdc::Options::FVarLinearInterpolation interpolation =
            _refiner.GetFVarLinearInterpolation(refinerChannel);

        _table->setFVarPatchChannelLinearInterpolation(interpolation, fvc);

        PatchDescriptor::Type regPatchType   = _patchBuilder->GetLinearPatchType();
        PatchDescriptor::Type irregPatchType = regPatchType;
        if (_buildUniformLinear) {
            if (_options.triangulateQuads) {
                regPatchType   = PatchDescriptor::TRIANGLES;
                irregPatchType = regPatchType;
            }
        } else {
            if (!isFVarChannelLinear(fvc)) {
                regPatchType   = _patchBuilder->GetRegularPatchType();
                irregPatchType = _patchBuilder->GetIrregularPatchType();
            }
        }
        _table->allocateFVarPatchChannelValues(
                PatchDescriptor(regPatchType), PatchDescriptor(irregPatchType),
                npatches, fvc);
    }
}

void
PatchTableBuilder::BuildUniformPolygons() {

    // Default behavior is to include base level vertices in the patch vertices
    // for vertex and varying patches, but not face-varying.  Consider exposing
    // these as public options in future so that clients can create consistent
    // behavior:

    bool includeBaseLevelIndices     = _options.includeBaseLevelIndices;
    bool includeBaseLevelFVarIndices = _options.includeFVarBaseLevelIndices;

    // ensure that triangulateQuads is only set for quadrilateral schemes
    bool triangulateQuads =
        _options.triangulateQuads && (_patchBuilder->GetRegularFaceSize() == 4);

    // level=0 may contain n-gons, which are not supported in PatchTable.
    // even if generateAllLevels = true, we start from level 1.

    int maxlevel = _refiner.GetMaxLevel(),
        firstlevel = _options.generateAllLevels ? 1 : maxlevel,
        nlevels = maxlevel-firstlevel+1;

    PatchDescriptor::Type ptype = triangulateQuads
                                ? PatchDescriptor::TRIANGLES
                                : _patchBuilder->GetLinearPatchType();

    //
    //  Allocate and initialize the table's members.
    //
    _table->_isUniformLinear = true;

    _table->reservePatchArrays(nlevels);

    PatchDescriptor desc(ptype);

    // generate patch arrays
    for (int level=firstlevel, poffset=0, voffset=0; level<=maxlevel; ++level) {

        TopologyLevel const & refLevel = _refiner.GetLevel(level);

        int npatches = refLevel.GetNumFaces();
        if (_refiner.HasHoles()) {
            for (int i = npatches - 1; i >= 0; --i) {
                npatches -= refLevel.IsFaceHole(i);
            }
        }
        assert(npatches>=0);

        if (triangulateQuads)
            npatches *= 2;

        _table->pushPatchArray(desc, npatches, &voffset, &poffset, 0);
    }

    // Allocate various tables
    allocateVertexTables();

    if (_requiresFVarPatches) {
        allocateFVarChannels();
    }

    //
    //  Now populate the patches:
    //

    Index          * iptr = &_table->_patchVerts[0];
    PatchParam     * pptr = &_table->_paramTable[0];
    Index         ** fptr  = 0;
    PatchParam    ** fpptr = 0;

    Index levelVertOffset = includeBaseLevelIndices
                          ? _refiner.GetLevel(0).GetNumVertices() : 0;

    Index * levelFVarVertOffsets = 0;
    if (_requiresFVarPatches) {

        levelFVarVertOffsets =
            (Index *)alloca(_fvarChannelIndices.size()*sizeof(Index));
        memset(levelFVarVertOffsets, 0, _fvarChannelIndices.size()*sizeof(Index));

        fptr = (Index **)alloca(_fvarChannelIndices.size()*sizeof(Index *));
        fpptr = (PatchParam **)alloca(_fvarChannelIndices.size()*sizeof(PatchParam *));
        for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
            fptr[fvc] = _table->getFVarValues(fvc).begin();
            fpptr[fvc] = _table->getFVarPatchParams(fvc).begin();

            if (includeBaseLevelFVarIndices) {
                int refinerChannel = _fvarChannelIndices[fvc];
                levelFVarVertOffsets[fvc] =
                    _refiner.GetLevel(0).GetNumFVarValues(refinerChannel);
            }
        }
    }

    for (int level=1; level<=maxlevel; ++level) {

        TopologyLevel const & refLevel = _refiner.GetLevel(level);

        int nfaces = refLevel.GetNumFaces();
        if (level>=firstlevel) {
            for (int face=0; face<nfaces; ++face) {

                if (_refiner.HasHoles() && refLevel.IsFaceHole(face)) {
                    continue;
                }

                ConstIndexArray fverts = refLevel.GetFaceVertices(face);
                for (int vert=0; vert<fverts.size(); ++vert) {
                    *iptr++ = levelVertOffset + fverts[vert];
                }

                PatchParam pparam = _patchBuilder->ComputePatchParam(
                    level, face, _ptexIndices);
                *pptr++ = pparam;

                if (_requiresFVarPatches) {
                    for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
                        int refinerChannel = _fvarChannelIndices[fvc];

                        ConstIndexArray fvalues =
                            refLevel.GetFaceFVarValues(face, refinerChannel);
                        for (int vert=0; vert<fvalues.size(); ++vert) {
                            assert((levelFVarVertOffsets[fvc] + fvalues[vert])
                                < (int)_table->getFVarValues(fvc).size());
                            fptr[fvc][vert] =
                                levelFVarVertOffsets[fvc] + fvalues[vert];
                        }
                        fptr[fvc]+=fvalues.size();
                        *fpptr[fvc]++ = pparam;
                    }
                }

                if (triangulateQuads) {
                    // Triangulate the quadrilateral:
                    //     {v0,v1,v2,v3} -> {v0,v1,v2},{v3,v0,v2}.
                    *iptr = *(iptr - 4); // copy v0 index
                    ++iptr;
                    *iptr = *(iptr - 3); // copy v2 index
                    ++iptr;

                    *pptr++ = pparam;

                    if (_requiresFVarPatches) {
                        for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
                            *fptr[fvc] = *(fptr[fvc]-4); // copy fv0 index
                            ++fptr[fvc];
                            *fptr[fvc] = *(fptr[fvc]-3); // copy fv2 index
                            ++fptr[fvc];

                            *fpptr[fvc]++ = pparam;
                        }
                    }
                }
            }
        }

        if (_options.generateAllLevels) {
            levelVertOffset += _refiner.GetLevel(level).GetNumVertices();

            if (_requiresFVarPatches) {
                for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
                    int refinerChannel = _fvarChannelIndices[fvc];
                    levelFVarVertOffsets[fvc] +=
                        _refiner.GetLevel(level).GetNumFVarValues(refinerChannel);
                }
            }
        }
    }
}

void
PatchTableBuilder::BuildPatches() {

    identifyPatches();
    populatePatches();
}

//
//  Identify all patches required for faces at all levels -- appending the
//  <level,face> pairs to identify each patch for later construction, while
//  accumulating the number of regular vs irregular patches to size tables.
//
inline void
PatchTableBuilder::appendPatch(int levelIndex, Index faceIndex) {

    _patches.push_back(PatchTuple(faceIndex, levelIndex));

    // Count the patches here to simplify subsequent allocation.
    if (_patchBuilder->IsPatchRegular(levelIndex, faceIndex)) {
        ++_numRegularPatches;
    } else {
        ++_numIrregularPatches;

        //  LegacyGregory needs to distinguish boundary vs interior
        if (_requiresLegacyGregoryTables) {
            _legacyGregoryHelper->AddPatchFace(levelIndex, faceIndex);
        }
    }
}

inline void
PatchTableBuilder::findDescendantPatches(int levelIndex, Index faceIndex, int targetLevel) {

    //
    //  If we have reached the target level or a leaf, append the patch (if
    //  the face qualifies), otherwise recursively search the children:
    //
    if ((levelIndex == targetLevel) || _patchBuilder->IsFaceALeaf(levelIndex, faceIndex)) {
        if (_patchBuilder->IsFaceAPatch(levelIndex, faceIndex)) {
            appendPatch(levelIndex, faceIndex);
        }
    } else {
        TopologyLevel const & level = _refiner.GetLevel(levelIndex);
        ConstIndexArray childFaces = level.GetFaceChildFaces(faceIndex);
        for (int i = 0; i < childFaces.size(); ++i) {
            if (Vtr::IndexIsValid(childFaces[i])) {
                findDescendantPatches(levelIndex + 1, childFaces[i], targetLevel);
            }
        }
    }
}

void
PatchTableBuilder::identifyPatches() {

    //
    //  First initialize the offsets for all levels
    //
    _levelVertOffsets.push_back(0);
    _levelFVarValueOffsets.resize(_fvarChannelIndices.size());
    for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
        _levelFVarValueOffsets[fvc].push_back(0);
    }

    for (int levelIndex=0; levelIndex<_refiner.GetNumLevels(); ++levelIndex) {
        Level const & level = _refiner.getLevel(levelIndex);

        _levelVertOffsets.push_back(
            _levelVertOffsets.back() + level.getNumVertices());

        for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
            int refinerChannel = _fvarChannelIndices[fvc];
            _levelFVarValueOffsets[fvc].push_back(
                _levelFVarValueOffsets[fvc].back()
                + level.getNumFVarValues(refinerChannel));
        }
    }

    //
    //  If a set of selected base faces is present, identify the patches
    //  depth first.  Otherwise search breadth first through the levels:
    //
    int uniformLevel = _refiner.IsUniform() ? _options.maxIsolationLevel : -1;

    _patches.reserve(_refiner.GetNumFacesTotal());

    if (_selectedFaces.size()) {
        for (int i = 0; i < (int)_selectedFaces.size(); ++i) {
            findDescendantPatches(0, _selectedFaces[i], uniformLevel);
        }
    } else if (uniformLevel >= 0) {
        int numFaces = _refiner.getLevel(uniformLevel).getNumFaces();

        for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {

            if (_patchBuilder->IsFaceAPatch(uniformLevel, faceIndex)) {
                appendPatch(uniformLevel, faceIndex);
            }
        }
    } else {
        for (int levelIndex=0; levelIndex<_refiner.GetNumLevels(); ++levelIndex) {
            int numFaces = _refiner.getLevel(levelIndex).getNumFaces();

            for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {

                if (_patchBuilder->IsFaceAPatch(levelIndex, faceIndex) &&
                    _patchBuilder->IsFaceALeaf(levelIndex, faceIndex)) {
                    appendPatch(levelIndex, faceIndex);
                }
            }
        }
    }
}

//
//  Populate patches that were previously identified.
//
void
PatchTableBuilder::populatePatches() {

    // State needed to populate an array in the patch table.
    // Pointers in this structure are initialized after the patch array
    // data buffers have been allocated and are then incremented as we
    // populate data into the patch table. Currently, we'll have at
    // most 3 patch arrays: Regular, Irregular, and IrregularBoundary.
    struct PatchArrayBuilder {
        PatchArrayBuilder()
            : patchType(PatchDescriptor::NON_PATCH), numPatches(0)
            , iptr(NULL), pptr(NULL), sptr(NULL), vptr(NULL) { }

        PatchDescriptor::Type patchType;
        int numPatches;

        Index      *iptr;
        PatchParam *pptr;
        Index      *sptr;
        Index      *vptr;

        StackBuffer<Index*,1>      fptr;   // fvar indices
        StackBuffer<PatchParam*,1> fpptr;  // fvar patch-params

    private:
        // Non-copyable
        PatchArrayBuilder(PatchArrayBuilder const &) {}
        PatchArrayBuilder & operator=(PatchArrayBuilder const &) {return *this;}

    } arrayBuilders[3];

    // Regular patches patches will be packed into the first patch array
    // Irregular patches will be packed into arrays according to optional
    // specification -- sharing the array with regular patches or packed
    // into an array of their own.
    int ARRAY_REGULAR   = 0;
    int ARRAY_IRREGULAR = 1;
    int ARRAY_BOUNDARY  = 2; // only used by LegacyGregory

    arrayBuilders[ARRAY_REGULAR].patchType = _patchBuilder->GetRegularPatchType();
    arrayBuilders[ARRAY_REGULAR].numPatches = _numRegularPatches;

    int numPatchArrays = (_numRegularPatches > 0);
    if (_numIrregularPatches > 0) {
        if (!_requiresLegacyGregoryTables) {
            //
            //  Pack irregular patches into same array as regular or separately:
            //
            if (_patchBuilder->GetRegularPatchType() ==
                    _patchBuilder->GetIrregularPatchType()) {
                ARRAY_IRREGULAR = ARRAY_REGULAR;
                numPatchArrays = 1;  // needed in case no regular patches
            } else {
                ARRAY_IRREGULAR = numPatchArrays;
                numPatchArrays ++;
            }
            arrayBuilders[ARRAY_IRREGULAR].patchType =
                _patchBuilder->GetIrregularPatchType();
            arrayBuilders[ARRAY_IRREGULAR].numPatches += _numIrregularPatches;
        } else {
            //
            // Arrays for Legacy-Gregory tables -- irregular patches are split
            // into two arrays for interior and boundary patches
            //
            ARRAY_IRREGULAR = numPatchArrays;
            arrayBuilders[ARRAY_IRREGULAR].patchType =
                PatchDescriptor::GREGORY;
            arrayBuilders[ARRAY_IRREGULAR].numPatches =
                _legacyGregoryHelper->GetNumInteriorPatches();
            numPatchArrays += (arrayBuilders[ARRAY_IRREGULAR].numPatches > 0);

            ARRAY_BOUNDARY = numPatchArrays;
            arrayBuilders[ARRAY_BOUNDARY].patchType =
                PatchDescriptor::GREGORY_BOUNDARY;
            arrayBuilders[ARRAY_BOUNDARY].numPatches =
                _legacyGregoryHelper->GetNumBoundaryPatches();
            numPatchArrays += (arrayBuilders[ARRAY_BOUNDARY].numPatches > 0);
        }
    }

    // Create patch arrays
    _table->reservePatchArrays(numPatchArrays);

    int voffset=0, poffset=0, qoffset=0;
    for (int arrayIndex=0; arrayIndex<numPatchArrays; ++arrayIndex) {
        PatchArrayBuilder & arrayBuilder = arrayBuilders[arrayIndex];
        _table->pushPatchArray(PatchDescriptor(arrayBuilder.patchType),
            arrayBuilder.numPatches, &voffset, &poffset, &qoffset );
    }

    // Allocate patch array data buffers
    allocateVertexTables();

    if (_requiresFVarPatches) {
        allocateFVarChannels();
    }

    // Initialize pointers used while populating patch array data buffers
    for (int arrayIndex=0; arrayIndex<numPatchArrays; ++arrayIndex) {
        PatchArrayBuilder & arrayBuilder = arrayBuilders[arrayIndex];

        arrayBuilder.iptr = _table->getPatchArrayVertices(arrayIndex).begin();
        arrayBuilder.pptr = _table->getPatchParams(arrayIndex).begin();
        if (_requiresSharpnessArray) {
            arrayBuilder.sptr = _table->getSharpnessIndices(arrayIndex);
        }
        if (_requiresVaryingPatches) {
            arrayBuilder.vptr = _table->getPatchArrayVaryingVertices(arrayIndex).begin();
        }

        if (_requiresFVarPatches) {
            arrayBuilder.fptr.SetSize((int)_fvarChannelIndices.size());
            arrayBuilder.fpptr.SetSize((int)_fvarChannelIndices.size());

            for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {

                Index pidx = _table->getPatchIndex(arrayIndex, 0);
                int   ofs  = pidx * _table->GetFVarValueStride(fvc);

                arrayBuilder.fptr[fvc] = &_table->getFVarValues(fvc)[ofs];
                arrayBuilder.fpptr[fvc] = &_table->getFVarPatchParams(fvc)[pidx];
            }
        }
    }

    //
    //  Initializing StencilTable and other helpers.  Note Varying local points
    //  are managed by the LocalPointHelper for Vertex patches (as Varying local
    //  points are tightly coupled to their local points)
    //
    LocalPointHelper * vertexLocalPointHelper = 0;

    StackBuffer<LocalPointHelper*,4> fvarLocalPointHelpers;

    if (_requiresLocalPoints) {
        LocalPointHelper::Options opts;
        opts.createStencilTable = true;
        opts.createVaryingTable = _requiresVaryingLocalPoints;
        opts.doubleStencilTable = _options.patchPrecisionDouble;
        opts.shareLocalPoints   = _options.shareEndCapPatchPoints;
        opts.reuseSourcePoints  = (_patchBuilder->GetIrregularPatchType() ==
                                   _patchBuilder->GetNativePatchType() );

        vertexLocalPointHelper = new LocalPointHelper(
                _refiner, opts, -1, estimateLocalPointCount(opts, -1));

        if (_requiresFVarPatches) {
            opts.createStencilTable = true;
            opts.createVaryingTable = false;
            opts.doubleStencilTable = _options.fvarPatchPrecisionDouble;

            fvarLocalPointHelpers.SetSize((int)_fvarChannelIndices.size());

            for (int fvc = 0; fvc < (int)_fvarChannelIndices.size(); ++fvc) {
                fvarLocalPointHelpers[fvc] = new LocalPointHelper(
                        _refiner, opts, getRefinerFVarChannel(fvc),
                        estimateLocalPointCount(opts, fvc));
            }
        }
    }

    //  Populate patch data buffers
    //
    //  Intentionally declare local vairables to contain patch topology info
    //  outside the loop to avoid repeated memory de-allocation/re-allocation
    //  associated with a change-of-basis.
    PatchInfo patchInfo;
    PatchInfo fvarPatchInfo;

    bool fvarPrecisionMatches = (_options.patchPrecisionDouble ==
                                 _options.fvarPatchPrecisionDouble);

    for (int patchIndex = 0; patchIndex < (int)_patches.size(); ++patchIndex) {

        PatchTuple const & patch = _patches[patchIndex];

        //
        //  Identify and assign points, stencils, sharpness, etc. for this patch:
        //
        identifyPatchTopology(patch, patchInfo);

        PatchArrayBuilder * arrayBuilder = &arrayBuilders[ARRAY_REGULAR];
        if (!patchInfo.isRegular) {
            arrayBuilder = &arrayBuilders[ARRAY_IRREGULAR];
        }

        if (_requiresLegacyGregoryTables && !patchInfo.isRegular) {
            if (isBoundaryFace(
                    _refiner.getLevel(patch.levelIndex), patch.faceIndex)) {
                arrayBuilder = &arrayBuilders[ARRAY_BOUNDARY];
            }
            arrayBuilder->iptr += assignFacePoints(patch, arrayBuilder->iptr);
        } else {
            arrayBuilder->iptr += assignPatchPointsAndStencils(patch,
                        patchInfo, arrayBuilder->iptr, *vertexLocalPointHelper);
        }
        if (_requiresSharpnessArray) {
            *arrayBuilder->sptr++ = assignSharpnessIndex(patchInfo.regSharpness,
                        _table->_sharpnessValues);
        }

        //
        //  Identify the PatchParam -- which may vary slightly for face-varying
        //  channels but will still be used to initialize them when so to avoid
        //  recomputing from scratch
        //
        PatchParam patchParam =
            _patchBuilder->ComputePatchParam(patch.levelIndex, patch.faceIndex,
                  _ptexIndices, patchInfo.isRegular, patchInfo.paramBoundaryMask,
                  true/* condition to compute transition mask */);
        *arrayBuilder->pptr++ = patchParam;

        //
        //  Assignment of face-varying patches is split between the comon/trivial
        //  case of linear patches and the more complex non-linear cases:
        //
        assert((_fvarChannelIndices.size() > 0) == _requiresFVarPatches);
        for (int fvc = 0; fvc < (int)_fvarChannelIndices.size(); ++fvc) {

            if (isFVarChannelLinear(fvc)) {
                assignFacePoints(patch, arrayBuilder->fptr[fvc], fvc);

                *arrayBuilder->fpptr[fvc] = patchParam;
            } else {
                //
                //  For non-linear patches, reuse patch information when the
                //  topology of the face in face-varying space matches the
                //  original patch:
                //
                bool fvcTopologyMatches = fvarPrecisionMatches &&
                                          doesFVarTopologyMatch(patch, fvc);

                PatchInfo & fvcPatchInfo = fvcTopologyMatches
                                         ? patchInfo : fvarPatchInfo;

                if (!fvcTopologyMatches) {
                    identifyPatchTopology(patch, fvcPatchInfo, fvc);
                }
                assignPatchPointsAndStencils(patch, fvcPatchInfo,
                        arrayBuilder->fptr[fvc], *fvarLocalPointHelpers[fvc], fvc);

                //  Initialize and assign the face-varying PatchParam:
                PatchParam & fvcPatchParam = *arrayBuilder->fpptr[fvc];
                fvcPatchParam.Set(
                   patchParam.GetFaceId(),
                   patchParam.GetU(), patchParam.GetV(),
                   patchParam.GetDepth(),
                   patchParam.NonQuadRoot(),
                   (unsigned short) fvcPatchInfo.paramBoundaryMask,
                   patchParam.GetTransition(),
                   fvcPatchInfo.isRegular);
            }
            arrayBuilder->fpptr[fvc] ++;
            arrayBuilder->fptr[fvc] += _table->GetFVarValueStride(fvc);
        }

        if (_requiresVaryingPatches) {
            arrayBuilder->vptr += assignFacePoints(patch, arrayBuilder->vptr);
        }
    }

    //
    //  Finalizing and destroying StencilTable and other helpers:
    //
    if (_requiresLocalPoints) {
        _table->_localPointStencils =
            vertexLocalPointHelper->AcquireStencilTable();
        if (_requiresVaryingLocalPoints) {
            _table->_localPointVaryingStencils =
                vertexLocalPointHelper->AcquireStencilTableVarying();
        }
        delete vertexLocalPointHelper;

        if (_requiresFVarPatches) {
            _table->_localPointFaceVaryingStencils.resize(_fvarChannelIndices.size());

            for (int fvc=0; fvc<(int)_fvarChannelIndices.size(); ++fvc) {
                _table->_localPointFaceVaryingStencils[fvc] =
                        fvarLocalPointHelpers[fvc]->AcquireStencilTable();
                delete fvarLocalPointHelpers[fvc];
            }
        }
    }
    if (_requiresLegacyGregoryTables) {
        _legacyGregoryHelper->FinalizeQuadOffsets(_table->_quadOffsetsTable);
        _legacyGregoryHelper->FinalizeVertexValence(_table->_vertexValenceTable,
                                       _levelVertOffsets[_refiner.GetMaxLevel()]);
    }
}

int
PatchTableBuilder::estimateLocalPointCount(
        LocalPointHelper::Options const & options,
        int fvarChannel) const {

    if ((fvarChannel >= 0) && isFVarChannelLinear(fvarChannel)) return 0;

    //
    //  Estimate the local points required for the Vertex topology in all cases
    //  as it may be used in the estimates for face-varying channels:
    //
    int estLocalPoints = 0;

    if (_requiresRegularLocalPoints) {
        //
        //  Either all regular patches are being converted to a non-native
        //  basis, or boundary points are being extrapolated in the regular
        //  basis.  The latter case typically involves a small fraction of
        //  patches and points, so we don't estimate any local points and
        //  leave it up to incremental allocation later to account for them.
        //
        int numPointsPerPatch = PatchDescriptor(
                _patchBuilder->GetRegularPatchType()).GetNumControlVertices();

        if (_patchBuilder->GetRegularPatchType() !=
                _patchBuilder->GetNativePatchType()) {
            estLocalPoints += _numRegularPatches * numPointsPerPatch;
        }
    } 
    if (_requiresIrregularLocalPoints) {
        bool oldEstimate = false;
        int numIrregularPatches = oldEstimate ?
            _refiner.GetLevel(_refiner.GetMaxLevel()).GetNumFaces() :
            _numIrregularPatches;

        //
        //  If converting to a basis other than regular, its difficult to
        //  predict the degree of point-sharing that may occur, and in general,
        //  the maximal benefit is small so we don't attempt to compensate for
        //  it here.  If converting to the same basis as regular, roughly half
        //  of the points of the patch are involved in approximating the
        //  irregularity (and cannot be shared) so don't include the source
        //  points that will be used in such patches.
        //
        int numPointsPerPatch = PatchDescriptor(
            _patchBuilder->GetIrregularPatchType()).GetNumControlVertices();

        if (options.reuseSourcePoints && (_patchBuilder->GetIrregularPatchType() ==
                                          _patchBuilder->GetNativePatchType())) {
            numPointsPerPatch /= 2;
        }
        estLocalPoints += numIrregularPatches * numPointsPerPatch;
    } 

    //
    //  Its difficult to estimate the differences in number of irregularities
    //  between the vertex topology and a face-varying channel without more
    //  detailed inspection.
    //
    //  A much higher number of fvar-values than vertices is an indication that
    //  the number of differences increases, and that generally lowers the
    //  number of irregular patches due to more regular patches on face-varying
    //  boundaries, but not always.  The use of some interpolation types, e.g.
    //  LINEAR_BOUNDARIES, combined with inf-sharp patches can increase the
    //  number of irregularities significantly.
    //
    if ((fvarChannel >= 0) && (_refiner.GetNumLevels() > 1)) {
        //
        //  We're seeing face-varying stencil table sizes about 1/4 the size of
        //  the vertex stencil table for what seem like typical cases...
        //
        //  Identify the ratio of fvar-values to vertices that typically leads
        //  to these reductions and reduce the number of expected local points
        //  proportionally.  Use the number of fvar-values at level 1:  level 0
        //  can be misleading as there can be many FEWER than the number of
        //  vertices, but subsequent levels will have at least one unique
        //  fvar-value per vertex, and later levels will have a much higher
        //  percentage shared as a result of refinement.
        //
        int fvc = getRefinerFVarChannel(fvarChannel);
        if (_refiner.GetLevel(1).GetNumFVarValues(fvc) >
                _refiner.GetLevel(1).GetNumVertices()) {
            //  Scale this based on ratio of fvar-values to vertices...
            float fvarReduction = 0.5;

            estLocalPoints = (int) ((float)estLocalPoints * fvarReduction);
        }
    }
    return estLocalPoints;
}


//
//  Member function definitions for the LocalPointHelper class:
//
PatchTableBuilder::LocalPointHelper::LocalPointHelper(
        TopologyRefiner const & refiner, Options const & options,
        int fvarChannel, int numLocalPointsExpected) :
            _refiner(refiner), _options(options), _fvarChannel(fvarChannel),
            _numLocalPoints(0), _stencilTable(), _stencilTableVarying() {

    _localPointOffset = (_fvarChannel < 0)
                      ? _refiner.GetNumVerticesTotal()
                      : _refiner.GetNumFVarValuesTotal(_fvarChannel);

    if (_options.createStencilTable) {
        if (_options.doubleStencilTable) {
            initializeStencilTable<double>(numLocalPointsExpected);
        } else {
            initializeStencilTable<float>(numLocalPointsExpected);
        }
    }
}

template <typename REAL>
void
PatchTableBuilder::LocalPointHelper::initializeStencilTable(int numLocalPointsExpected) {

    //
    //  Reserving space for the local-point stencils has been a source of
    //  problems in the past so we rely on the PatchTableBuilder to provide
    //  a reasonable estimate to the LocalPointHelper on construction.  For
    //  large meshes a limit on the size initially reserve is capped.
    //
    //  The average number of entries per stencil has been historically set
    //  at 16, which seemed high and was reduced on further investigation.
    //
    StencilTableReal<REAL> * stencilTable = new StencilTableReal<REAL>(0);
    StencilTableReal<REAL> * varyingTable = _options.createVaryingTable
                                          ? new StencilTableReal<REAL>(0) : 0;

    //  Historic note:  limits to 100M (=800M bytes) entries for reserved size
    size_t const MaxEntriesToReserve  = 100 * 1024 * 1024;
    size_t const AvgEntriesPerStencil = 9;  // originally 16

    size_t numStencilsExpected       = numLocalPointsExpected;
    size_t numStencilEntriesExpected = numStencilsExpected * AvgEntriesPerStencil;

    size_t numEntriesToReserve = std::min(numStencilEntriesExpected,
                                          MaxEntriesToReserve);
    if (numEntriesToReserve) {
        stencilTable->reserve(
                (int)numStencilsExpected, (int)numEntriesToReserve);

        if (varyingTable) {
            //  Varying stencils have only one entry per point
            varyingTable->reserve(
                    (int)numStencilsExpected, (int)numStencilsExpected);
        }
    }

    _stencilTable.Set(stencilTable);
    _stencilTableVarying.Set(varyingTable);
}

template <typename REAL>
PatchTableBuilder::StencilTablePtr
PatchTableBuilder::LocalPointHelper::acquireStencilTable(
        StencilTablePtr& stencilTableMember) {

    StencilTableReal<REAL> * stencilTable = stencilTableMember.Get<REAL>();

    if (stencilTable) {
        if (stencilTable->GetNumStencils() > 0) {
            stencilTable->finalize();
        } else {
            delete stencilTable;
            stencilTable = 0;
        }
    }

    stencilTableMember.Set();
    return StencilTablePtr(stencilTable);
}

PatchTableBuilder::LocalPointHelper::~LocalPointHelper() {

    if (_options.doubleStencilTable) {
        delete _stencilTable.Get<double>();
        delete _stencilTableVarying.Get<double>();
    } else {
        delete _stencilTable.Get<float>();
        delete _stencilTableVarying.Get<float>();
    }
}

Index
PatchTableBuilder::LocalPointHelper::findSharedCornerPoint(int levelIndex,
        Index vertIndex, Index localPointIndex) {

    if (_sharedCornerPoints.empty()) {
        _sharedCornerPoints.resize(_refiner.GetNumLevels());
    }

    IndexVector & vertexPoints = _sharedCornerPoints[levelIndex];
    if (vertexPoints.empty()) {
        Level const & level = _refiner.getLevel(levelIndex);

        if (_fvarChannel < 0) {
            vertexPoints.resize(level.getNumVertices(), INDEX_INVALID);
        } else {
            vertexPoints.resize(level.getNumFVarValues(_fvarChannel), INDEX_INVALID);
        }
    }

    Index & assignedIndex = vertexPoints[vertIndex];
    if (!IndexIsValid(assignedIndex)) {
        assignedIndex = localPointIndex;
    }
    return assignedIndex;
}

Index
PatchTableBuilder::LocalPointHelper::findSharedEdgePoint(int levelIndex,
         Index edgeIndex, int edgeEnd, Index localPointIndex) {

    if (_sharedEdgePoints.empty()) {
        _sharedEdgePoints.resize(_refiner.GetNumLevels());
    }

    IndexVector & edgePoints = _sharedEdgePoints[levelIndex];
    if (edgePoints.empty()) {
        edgePoints.resize(
                2 * _refiner.GetLevel(levelIndex).GetNumEdges(), INDEX_INVALID);
    }

    Index & assignedIndex = edgePoints[2 * edgeIndex + edgeEnd];
    if (!IndexIsValid(assignedIndex)) {
        assignedIndex = localPointIndex;
    } else {
    }
    return assignedIndex;
}

template <typename REAL>
void
PatchTableBuilder::LocalPointHelper::appendLocalPointStencil(
    SparseMatrix<REAL> const &  conversionMatrix,
    int                         stencilRow,
    Index const                 sourcePoints[],
    int                         sourcePointOffset) {

    int               stencilSize   = conversionMatrix.GetRowSize(stencilRow);
    ConstArray<int>   matrixColumns = conversionMatrix.GetRowColumns(stencilRow);
    ConstArray<REAL>  matrixWeights = conversionMatrix.GetRowElements(stencilRow);

    StencilTableReal<REAL>* stencilTable = _stencilTable.Get<REAL>();

    stencilTable->_sizes.push_back(stencilSize);
    for (int i = 0; i < stencilSize; ++i) {
        stencilTable->_weights.push_back(matrixWeights[i]);
        stencilTable->_indices.push_back(
                sourcePoints[matrixColumns[i]] + sourcePointOffset);
    }
}

template <typename REAL>
void
PatchTableBuilder::LocalPointHelper::appendLocalPointStencils(
    SparseMatrix<REAL> const &  conversionMatrix,
    Index const                 sourcePoints[],
    int                         sourcePointOffset) {

    //
    //  Resize the StencilTable members to accomodate all rows and elements from
    //  the given set of points represented by the matrix
    //
    StencilTableReal<REAL>* stencilTable = _stencilTable.Get<REAL>();

    int numNewStencils = conversionMatrix.GetNumRows();
    int numNewElements = conversionMatrix.GetNumElements();

    size_t numOldStencils = stencilTable->_sizes.size();
    size_t numOldElements = stencilTable->_indices.size();

    //  Assign the sizes for the new stencils:
    stencilTable->_sizes.resize(numOldStencils + numNewStencils);

    int * newSizes = &stencilTable->_sizes[numOldStencils];
    for (int i = 0; i < numNewStencils; ++i) {
        newSizes[i] = conversionMatrix.GetRowSize(i);
    }

    //  Assign remapped indices for the stencils:
    stencilTable->_indices.resize(numOldElements + numNewElements);

    int const * mtxIndices = &conversionMatrix.GetColumns()[0];
    int *       newIndices = &stencilTable->_indices[numOldElements];

    for (int i = 0; i < numNewElements; ++i) {
        newIndices[i] = sourcePoints[mtxIndices[i]] + sourcePointOffset;
    }

    //  Copy the stencil weights direct from the matrix elements:
    stencilTable->_weights.resize(numOldElements + numNewElements);

    REAL const * mtxWeights = &conversionMatrix.GetElements()[0];
    REAL *       newWeights = &stencilTable->_weights[numOldElements];

    std::memcpy(newWeights, mtxWeights, numNewElements * sizeof(REAL));
}


//
//  Its unfortunate that varying stencils for local points were ever created
//  and external dependency on them forces a certain coordination here.  Each
//  patch type is expected to have a varying value computed for each patch
//  point and shaders retrieve the varying value associated with particular
//  points.  So we need to store that mapping from control point to varying
//  point (or corner of the patch) somewhere.  We are trying to avoid adding
//  more to the PatchDescriptor interface, so we'll keep it here for now in
//  the hope we may be able to eliminate the need for it.
//
namespace {
    inline int const *
    GetVaryingIndicesPerType(PatchDescriptor::Type type) {

        //  Note that we can use the linear and gregory vectors here for
        //  both quads and tris
        static int const linearIndices[4] = { 0, 1, 2, 3 };
        static int const bsplineIndices[] =
                { 0, 0, 1, 1, 0, 0, 1, 1, 3, 3, 2, 2, 3, 3, 2, 2 };
        static int const boxsplineIndices[] =
                { 0, 0, 1, 0, 0, 1, 1, 2, 2, 1, 2, 2 };
        static int const gregoryIndices[] =
                { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3 };
        static int const gregoryTriIndices[] =
                { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 0, 1, 2 };

        if (type == PatchDescriptor::GREGORY_BASIS) {
            return gregoryIndices;
        } else if (type == PatchDescriptor::GREGORY_TRIANGLE) {
            return gregoryTriIndices;
        } else if (type == PatchDescriptor::REGULAR) {
            return bsplineIndices;
        } else if (type == PatchDescriptor::LOOP) {
            return boxsplineIndices;
        } else if (type == PatchDescriptor::QUADS) {
            return linearIndices;
        } else if (type == PatchDescriptor::TRIANGLES) {
            return linearIndices;
        }
        return 0;
    }
}

template <typename REAL>
void
PatchTableBuilder::LocalPointHelper::appendLocalPointVaryingStencil(
    int const * varyingIndices, int patchPointIndex,
    Index const sourcePoints[], int sourcePointOffset) {

    Index varyingPoint =
        sourcePoints[varyingIndices[patchPointIndex]] + sourcePointOffset;

    StencilTableReal<REAL>* t = _stencilTableVarying.Get<REAL>();

    t->_sizes.push_back(1);
    t->_indices.push_back(varyingPoint);
    t->_weights.push_back((REAL) 1.0);
}

namespace {
    typedef short ShareBits;

    static ShareBits const SHARE_CORNER     = 0x10;
    static ShareBits const SHARE_EDGE_BEGIN = 0x20;
    static ShareBits const SHARE_EDGE_END   = 0x40;
    static ShareBits const SHARE_EDGE       = SHARE_EDGE_BEGIN | SHARE_EDGE_END;
    static ShareBits const SHARE_INDEX_MASK = 0x0f;

    inline ShareBits const *
    GetShareBitsPerType(PatchDescriptor::Type type) {

        static ShareBits const linearQuadBits[5] = { 0x10, 0x11, 0x12, 0x13,
                                                     SHARE_CORNER };
        static ShareBits const gregoryQuadBits[21] = { 0x10, 0x20, 0x43, 0, 0,
                                                       0x11, 0x21, 0x40, 0, 0,
                                                       0x12, 0x22, 0x41, 0, 0,
                                                       0x13, 0x23, 0x42, 0, 0, 
                                                       SHARE_CORNER | SHARE_EDGE };

        if (type == PatchDescriptor::QUADS) {
            return linearQuadBits;
        } else if (type == PatchDescriptor::GREGORY_BASIS) {
            return gregoryQuadBits;
        }
        return 0;
    }
}

template <typename REAL>
int
PatchTableBuilder::LocalPointHelper::AppendLocalPatchPoints(
    int levelIndex, Index faceIndex,
    SparseMatrix<REAL> const &  matrix,
    PatchDescriptor::Type       patchType,
    Index const                 sourcePoints[],
    int                         sourcePointOffset,
    Index                       patchPoints[]) {

    //
    //  If sharing local points, verify the type of this patch supports
    //  sharing and disable it if not:
    //
    int numPatchPoints  = matrix.GetNumRows();

    int firstNewLocalPoint = _localPointOffset + _numLocalPoints;
    int nextNewLocalPoint  = firstNewLocalPoint;

    ShareBits const* shareBitsPerPoint = _options.shareLocalPoints ?
                        GetShareBitsPerType(patchType) : 0;

    bool shareLocalPointsForThisPatch = (shareBitsPerPoint != 0);

    int const * varyingIndices = 0;
    if (_stencilTableVarying) {
        varyingIndices = GetVaryingIndicesPerType(patchType);
    }

    bool applyVertexStencils  = (_stencilTable.Get<REAL>() != 0);
    bool applyVaryingStencils = (varyingIndices != 0);

    //
    //  When point-sharing is not enabled, all patch points are generally
    //  new local points -- the exception to this occurs when "re-using"
    //  source points, i.e. the resulting patch can be a mixture of source
    //  and local points (typically when the irregular patch type is the
    //  same as the regular patch type native to the scheme).
    //
    if (!shareLocalPointsForThisPatch) {
        if (!_options.reuseSourcePoints) {
            if (applyVertexStencils) {
                appendLocalPointStencils(
                    matrix, sourcePoints, sourcePointOffset);
                if (applyVaryingStencils) {
                    for (int i = 0; i < numPatchPoints; ++i) {
                        appendLocalPointVaryingStencil<REAL>(
                            varyingIndices, i, sourcePoints, sourcePointOffset);
                    }
                }
            }
            for (int i = 0; i < numPatchPoints; ++i) {
                patchPoints[i] = nextNewLocalPoint ++;
            }
        } else {
            for (int i = 0; i < numPatchPoints; ++i) {
                if (_options.reuseSourcePoints && (matrix.GetRowSize(i) == 1)) {
                    patchPoints[i] = sourcePoints[matrix.GetRowColumns(i)[0]]
                                   + sourcePointOffset;
                    continue;
                }
                if (applyVertexStencils) {
                    appendLocalPointStencil(
                        matrix, i, sourcePoints, sourcePointOffset);
                    if (applyVaryingStencils) {
                        appendLocalPointVaryingStencil<REAL>(
                            varyingIndices, i, sourcePoints, sourcePointOffset);
                    }
                }
                patchPoints[i] = nextNewLocalPoint ++;
            }
        }
    } else {
        //  Gather topology info according to the sharing for this patch type
        //
        Level const & level = _refiner.getLevel(levelIndex);

        ConstIndexArray fCorners;
        ConstIndexArray fVerts;
        ConstIndexArray fEdges;
        bool            fEdgeReversed[4];
        bool            fEdgeBoundary[4];

        ShareBits const shareBitsForType = shareBitsPerPoint[numPatchPoints];
        if (shareBitsForType) {
            if (shareBitsForType & SHARE_CORNER) {
                fCorners = (_fvarChannel < 0) 
                         ? level.getFaceVertices(faceIndex)
                         : level.getFaceFVarValues(faceIndex, _fvarChannel);
            }
            if (shareBitsForType & SHARE_EDGE) {
                fEdges = level.getFaceEdges(faceIndex);
                fVerts = (_fvarChannel < 0) ? fCorners
                                            : level.getFaceVertices(faceIndex);

                Level::ETag fEdgeTags[4];
                level.getFaceETags(faceIndex, fEdgeTags, _fvarChannel);

                for (int i = 0; i < fEdges.size(); ++i) {
                    fEdgeReversed[i] = level.getEdgeVertices(fEdges[i])[0] != fVerts[i];
                    fEdgeBoundary[i] = fEdgeTags[i]._boundary;
                }
            }
        }

        //  Inspect the sharing bits for each point -- if set, see if a local
        //  point for the corresponding vertex/fvar-value or edge was
        //  previously used:
        //
        for (int i = 0; i < numPatchPoints; ++i) {
            if (_options.reuseSourcePoints && (matrix.GetRowSize(i) == 1)) {
                patchPoints[i] = sourcePoints[matrix.GetRowColumns(i)[0]]
                               + sourcePointOffset;
                continue;
            }

            int patchPoint = nextNewLocalPoint;

            if (shareBitsPerPoint[i]) {
                int index = shareBitsPerPoint[i] & SHARE_INDEX_MASK;

                if (shareBitsPerPoint[i] & SHARE_CORNER) {
                    patchPoint = findSharedCornerPoint(
                        levelIndex, fCorners[index], nextNewLocalPoint);
                } else if (!fEdgeBoundary[index]) {
                    int edgeEnd = (((shareBitsPerPoint[i] & SHARE_EDGE_END) > 0) !=
                                   fEdgeReversed[index]);
                    patchPoint = findSharedEdgePoint(
                        levelIndex, fEdges[index], edgeEnd, nextNewLocalPoint);
                }
            }
            if (patchPoint == nextNewLocalPoint) {
                if (applyVertexStencils) {
                    appendLocalPointStencil(
                        matrix, i, sourcePoints, sourcePointOffset);
                    if (applyVaryingStencils) {
                        appendLocalPointVaryingStencil<REAL>(
                            varyingIndices, i, sourcePoints, sourcePointOffset);
                    }
                }
                nextNewLocalPoint ++;
            }
            patchPoints[i] = patchPoint;
        }
    }

    int numNewLocalPoints = nextNewLocalPoint - firstNewLocalPoint;
    _numLocalPoints += numNewLocalPoints;
    return numNewLocalPoints;
}


//
//  Member function definitions for the LegacyGregoryHelper class:
//
void
PatchTableBuilder::LegacyGregoryHelper::AddPatchFace(int level, Index face) {

    if (_refiner.getLevel(level).getFaceCompositeVTag(face)._boundary) {
        _boundaryFaceIndices.push_back(face);
    } else {
        _interiorFaceIndices.push_back(face);
    }
}

void
PatchTableBuilder::LegacyGregoryHelper::FinalizeQuadOffsets(
        PatchTable::QuadOffsetsTable & qTable) {

    size_t numInteriorPatches = _interiorFaceIndices.size();
    size_t numBoundaryPatches = _boundaryFaceIndices.size();
    size_t numTotalPatches    = numInteriorPatches + numBoundaryPatches;

    struct QuadOffset {
        static int Assign(Level const& level, Index faceIndex,
                          unsigned int offsets[]) {
            ConstIndexArray fVerts = level.getFaceVertices(faceIndex);

            for (int i = 0; i < 4; ++i) {
                ConstIndexArray vFaces = level.getVertexFaces(fVerts[i]);
                int faceInVFaces = vFaces.FindIndex(faceIndex);

                // we have to use number of incident edges to modulo the local
                // index as there could be 2 consecutive edges in the face
                // belonging to the patch
                int vOffset0 = faceInVFaces;
                int vOffset1 = (faceInVFaces + 1) %
                                    level.getVertexEdges(fVerts[i]).size();
                offsets[i] = vOffset0 | (vOffset1 << 8);
            }
            return 4;
        }
    };

    if (numTotalPatches > 0) {
        qTable.resize(numTotalPatches*4);

        // all patches assumed to be at the last level
        Level const &maxLevel = _refiner.getLevel(_refiner.GetMaxLevel());

        PatchTable::QuadOffsetsTable::value_type *p = &(qTable[0]);
        for (size_t i = 0; i < numInteriorPatches; ++i) {
            p += QuadOffset::Assign(maxLevel, _interiorFaceIndices[i], p);
        }
        for (size_t i = 0; i < numBoundaryPatches; ++i) {
            p += QuadOffset::Assign(maxLevel, _boundaryFaceIndices[i], p);
        }
    }
}

void
PatchTableBuilder::LegacyGregoryHelper::FinalizeVertexValence(
        PatchTable::VertexValenceTable & vTable, int lastLevelOffset) {

    //  Populate the "vertex valence" table for Gregory patches -- this table
    //  contains the one-ring of vertices around each vertex.  Currently it is
    //  extremely wasteful for the following reasons:
    //      - it allocates 2*maxvalence+1 for ALL vertices
    //      - it initializes the one-ring for ALL vertices
    //  We use the full size expected (not sure what else relies on that) but
    //  we avoid initializing the vast majority of vertices that are not
    //  associated with gregory patches -- only populating those in the last
    //  level (an older version attempted to avoid vertices not involved with
    //  Gregory patches)
    //
    int vWidth = 2*_refiner.GetMaxValence() + 1;

    vTable.resize((long)_refiner.GetNumVerticesTotal() * vWidth);

    Level const & lastLevel = _refiner.getLevel(_refiner.GetMaxLevel());

    int * vTableEntry = &vTable[lastLevelOffset * vWidth];

    for (int vIndex = 0; vIndex < lastLevel.getNumVertices(); ++vIndex) {
        //  Gather the one-ring around the vertex and set its resulting size
        //  (note negative size used to distinguish between boundary/interior):
        //
        int *ringDest = vTableEntry + 1;
        int  ringSize =
                lastLevel.gatherQuadRegularRingAroundVertex(vIndex, ringDest);

        for (int j = 0; j < ringSize; ++j) {
            ringDest[j] += lastLevelOffset;
        }
        if (ringSize & 1) {
            // boundary: duplicate end vertex index and store negative valence
            ringSize++;
            vTableEntry[ringSize]=vTableEntry[ringSize-1];
            vTableEntry[0] = -ringSize/2;
        } else {
            vTableEntry[0] = ringSize/2;
        }
        vTableEntry += vWidth;
    }
}


//
//  The sole public PatchTableFactory method to create a PatchTable -- deferring
//  to the PatchTableBuilder implementation
//
PatchTable *
PatchTableFactory::Create(TopologyRefiner const & refiner,
                          Options options,
                          ConstIndexArray selectedFaces) {

    PatchTableBuilder builder(refiner, options, selectedFaces);

    if (builder.UniformPolygonsSpecified()) {
        builder.BuildUniformPolygons();
    } else {
        builder.BuildPatches();
    }
    return builder.GetPatchTable();
}


//
//  Implementation of PatchTableFactory::PatchFaceTag -- unintentionally
//  exposed to the public interface, no longer internally used and so
//  planned for deprecation
//
void
PatchTableFactory::PatchFaceTag::clear() {
    std::memset(this, 0, sizeof(*this));
}

void
PatchTableFactory::PatchFaceTag::assignTransitionPropertiesFromEdgeMask(
        int tMask) {

    _transitionMask = tMask;
}

void
PatchTableFactory::PatchFaceTag::assignBoundaryPropertiesFromEdgeMask(
        int eMask) {

    static int const edgeMaskToCount[16] =
        { 0, 1, 1, 2, 1, -1, 2, -1, 1, 2, -1, -1, 2, -1, -1, -1 };
    static int const edgeMaskToIndex[16] =
        { -1, 0, 1, 1, 2, -1, 2, -1, 3, 0, -1, -1, 3, -1, -1,-1 };

    assert(edgeMaskToCount[eMask] != -1);
    assert(edgeMaskToIndex[eMask] != -1);

    _boundaryMask    = eMask;
    _hasBoundaryEdge = (eMask > 0);

    _boundaryCount = edgeMaskToCount[eMask];
    _boundaryIndex = edgeMaskToIndex[eMask];
}

void
PatchTableFactory::PatchFaceTag::assignBoundaryPropertiesFromVertexMask(
        int vMask) {

    // This is only intended to support the case of a single boundary vertex
    // with no boundary edges, which can only occur with an irregular vertex

    static int const singleBitVertexMaskToCount[16] =
        { 0, 1, 1, -1, 1, -1 , -1, -1, 1, -1 , -1, -1, -1, -1 , -1, -1 };
    static int const singleBitVertexMaskToIndex[16] =
        { 0, 0, 1, -1, 2, -1 , -1, -1, 3, -1 , -1, -1, -1, -1 , -1, -1 };

    assert(_hasBoundaryEdge == false);
    assert(singleBitVertexMaskToCount[vMask] != -1);
    assert(singleBitVertexMaskToIndex[vMask] != -1);

    _boundaryMask = vMask;

    _boundaryCount = singleBitVertexMaskToCount[vMask];
    _boundaryIndex = singleBitVertexMaskToIndex[vMask];
}

} // end namespace Far

} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv
