//
//   Copyright 2021 Pixar
//
//   Licensed under the terms set forth in the LICENSE.txt file available at
//   https://opensubdiv.org/license.
//

#include "../bfr/hash.h"
#include "../bfr/limits.h"
#include "../bfr/surface.h"
#include "../bfr/surfaceFactory.h"
#include "../bfr/surfaceFactoryCache.h"
#include "../bfr/faceTopology.h"
#include "../bfr/faceSurface.h"
#include "../bfr/regularPatchBuilder.h"
#include "../bfr/irregularPatchBuilder.h"
#include "../bfr/patchTree.h"

#include <map>
#include <cstdio>

namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {

namespace Bfr {
//
//  DEBUG - static variables to keep track of constructed Surfaces
//        - note that these global variables have extremely limited use:
//            - they are initialized once per process
//            - reported and reset on destruction of a SurfaceFactory
//
//#define _BFR_DEBUG_TOP_TYPE_STATS
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
static int __numLinearPatches     = 0;
static int __numExpRegularPatches = 0;
static int __numRegularPatches    = 0;
static int __numIrregularPatches  = 0;
static int __numIrregularUncached = 0;
static int __numIrregularInCache  = 0;
#endif

//
//  Definition of the private/nested SurfaceSet class:
//
//  This class (essentially a struct) encapsulates a clients specification
//  of a set of multiple surfaces and their intended interpolation types
//  (vertex, varying, and face-varying).  The multiple public creation
//  methods to request common subsets of surfaces all populate an instance
//  of SurfaceSet for internal use.
//
class SurfaceFactory::SurfaceSet {
public:
    SurfaceSet() : numSurfs(0), numFVarSurfs(0),
                   vtxSurf(0), varSurf(0),
                   fvarSurfs(0), fvarSurfPtrs(0), fvarIDs(0) { }

public:
    //  Assignment to member variable is intended to be explicit:
    int numSurfs;
    int numFVarSurfs;

    typedef internal::SurfaceData SurfaceType;

    SurfaceType  * vtxSurf;
    SurfaceType  * varSurf;
    SurfaceType  * fvarSurfs;
    SurfaceType ** fvarSurfPtrs;

    FVarID const * fvarIDs;

    void InitializeSurfaces() const {
        if (vtxSurf) vtxSurf->reinitialize();
        if (varSurf) varSurf->reinitialize();
        for (int i = 0; i < numFVarSurfs; ++i) {
            GetFVarSurface(i)->reinitialize();
        }
    }

public:
    //  Access to member variables is preferred through these methods,
    //  which may require a little more logic than expected:
    int GetNumSurfaces() const { return numSurfs; }

    bool          HasVertexSurface() const { return (vtxSurf != 0); }
    SurfaceType * GetVertexSurface() const { return vtxSurf; }

    bool          HasVaryingSurface() const { return (varSurf != 0); }
    SurfaceType * GetVaryingSurface() const { return varSurf; }

    //  More than one FVar surface may be present, and each may have
    //  a unique ID:
    bool HasFVarSurfaces()    const { return numFVarSurfs > 0; }
    int  GetNumFVarSurfaces() const { return numFVarSurfs; }

    FVarID GetFVarSurfaceID(int i) const {
        return fvarIDs ? fvarIDs[i] : FVarID(i);
    }

    SurfaceType * GetFVarSurface(int i)   const {
        //  Note that FVar Surfaces may be specified either as an
        //  array of Surfaces or an array of Surface pointers:
        return fvarSurfs ? (fvarSurfs + i) : fvarSurfPtrs[i];
    }
};


//
//  Main constructor and supporting initialization methods:
//
SurfaceFactory::SurfaceFactory(Sdc::SchemeType      subdivScheme,
                               Sdc::Options const & subdivOptions,
                               Options      const & factoryOptions) :
        _topologyCache(0) {

    //  Order of operations not important here:
    setSubdivisionOptions(subdivScheme, subdivOptions);
    setFactoryOptions(factoryOptions);
}

void
SurfaceFactory::setSubdivisionOptions(Sdc::SchemeType      subdivScheme,
                                      Sdc::Options const & subdivOptions) {

    //  Assign the main member variables before others derived from them:
    _subdivScheme  = subdivScheme;
    _subdivOptions = subdivOptions;

    //  Initialize members dependent on subdivision topology:
    _regFaceSize = Sdc::SchemeTypeTraits::GetRegularFaceSize(_subdivScheme);

    _linearScheme =
        (Sdc::SchemeTypeTraits::GetLocalNeighborhoodSize(_subdivScheme) == 0);

    _linearFVarInterp = _linearScheme ||
                       (_subdivOptions.GetFVarLinearInterpolation() ==
                                 Sdc::Options::FVAR_LINEAR_ALL);

    //  Initialize members related to the "face has limit" test:
    _rejectSmoothBoundariesForLimit = !_linearScheme &&
                       (_subdivOptions.GetVtxBoundaryInterpolation() ==
                                 Sdc::Options::VTX_BOUNDARY_NONE);

    _rejectIrregularFacesForLimit = !_linearScheme && (_regFaceSize == 3);

    _testNeighborhoodForLimit = _rejectSmoothBoundariesForLimit ||
                                _rejectIrregularFacesForLimit;
}

void
SurfaceFactory::setFactoryOptions(Options const & factoryOptions) {

    //  Assign the main member variable before others derived from them:
    _factoryOptions = factoryOptions;

    if (_factoryOptions.IsCachingEnabled()) {
        if (_factoryOptions.GetExternalCache()) {
            _topologyCache = _factoryOptions.GetExternalCache();
        }
    }
}

void
SurfaceFactory::setInternalCache(SurfaceFactoryCache * cache) {

    //  Remember caching must be on and an external cache takes precedence
    if (_factoryOptions.IsCachingEnabled()) {
        if (_factoryOptions.GetExternalCache() == 0) {
            _topologyCache = cache;
        }
    }
}

SurfaceFactory::~SurfaceFactory() {

#ifdef _BFR_DEBUG_TOP_TYPE_STATS
//  DEBUG - report and reset inventory:
printf("SurfaceFactory destructor:\n");
printf("    __numLinearPatches     = %6d\n", __numLinearPatches);
printf("    __numExpRegularPatches = %6d\n", __numExpRegularPatches);
printf("    __numRegularPatches    = %6d\n", __numRegularPatches);
printf("    __numIrregularPatches  = %6d\n", __numIrregularPatches);
if (!_factoryOptions.DisableTopologyCache()) {
printf("\n");
printf("    __numIrregularUncached = %6d\n", __numIrregularUncached);
printf("    __numIrregularInCache  = %6d\n", __numIrregularInCache);
}
__numLinearPatches     = 0;
__numExpRegularPatches = 0;
__numRegularPatches    = 0;
__numIrregularPatches  = 0;
__numIrregularUncached = 0;
__numIrregularInCache  = 0;
#endif
}

//
//  Notes on presence/absence of a limit surface...
//
//  Unfortunately it is not trivial to detect when a face does not have
//  an associated limit surface.  There are a few cases when a face will
//  not have a limit surface -- divided into simple and complex cases:
//
//      - simple:
//          - the face is a hole
//          - the face is degenerate (< 3 edges)
//      - complex:
//          - boundary interpolation option "none" is assigned:
//              - in which case some, not all, boundary faces have no limit
//          - Loop subdivision is applied to non-triangles
//
//  The simple cases are, as the name suggests, simple.  But the complex
//  cases require a greater inspection of the topological neighborhood of
//  the face.
//
//  With boundary faces when "boundary none" is set (not very often) it is
//  not enough to test if a face is a boundary -- if a boundary face has all
//  of its incident boundary edges (i.e. all boundary edges incident to all
//  of its face-vertices) then the boundary face has a limit surface.  This
//  requires a complete topological description of each corner of the face.
//
//  Similarly, the case of Loop subdivision in the presence of non-triangles
//  required determining if any corner of the face has an incident face
//  that is not a triangle.
//
//  The method here inspects a corner at a time and tries to reject a face
//  without a limit surface as soon as possible. But most cases are going to
//  require inspection of all corners -- and that same inspection is likely
//  to be applied later when constructing the limit.
//
inline bool
SurfaceFactory::faceHasLimitSimple(Index faceIndex, int faceSize) const {

    return (faceSize >= 3) && (faceSize <= Limits::MaxFaceSize()) &&
           !isFaceHole(faceIndex);
}

bool
SurfaceFactory::faceHasLimitNeighborhood(FaceTopology const & topology) const {

    assert(_testNeighborhoodForLimit);

    MultiVertexTag tag = topology.GetTag();

    if ((_rejectSmoothBoundariesForLimit && tag.HasNonSharpBoundary()) ||
        (_rejectIrregularFacesForLimit   && tag.HasIrregularFaceSizes())) {
        return false;
    }
    return true;
}

bool
SurfaceFactory::faceHasLimitNeighborhood(Index faceIndex) const {

    assert(_testNeighborhoodForLimit);

    //
    //  The FaceTopology was not available, and rather than construct it
    //  in its entirety, determine a corner at a time and return if any
    //  corner warrants it:
    //
    typedef Vtr::internal::StackBuffer<Index,32,true> CornerIndexBuffer;

    CornerIndexBuffer cFaceVertIndices;

    FaceVertex         faceVtx;
    VertexDescriptor & vtxDesc = faceVtx.GetVertexDescriptor();

    int faceSize = getFaceSize(faceIndex);
    for (int i = 0; i < faceSize; ++i) {
        //  Have the subclass load VertexDescriptor and finalize:
        faceVtx.Initialize(faceSize, _regFaceSize);

        int faceInRing = populateFaceVertexDescriptor(faceIndex, i, &vtxDesc);
        if (faceInRing < 0) return false;

        faceVtx.Finalize(faceInRing);

        //  Inspect the tag to reject cases with no limit surface:
        VertexTag faceVtxTag = faceVtx.GetTag();

        if (_rejectSmoothBoundariesForLimit) {
            if (faceVtxTag.IsUnOrdered()) {
                //  Need to load face-vertices, connect faces and inspect...
                cFaceVertIndices.SetSize(faceVtx.GetNumFaceVertices());

                if (getFaceVertexIncidentFaceVertexIndices(
                        faceIndex, i, cFaceVertIndices) < 0) return false;

                faceVtx.ConnectUnOrderedFaces(cFaceVertIndices);
            }
            if (faceVtxTag.HasNonSharpBoundary()) return false;
        }
        if (_rejectIrregularFacesForLimit) {
            if (faceVtxTag.HasIrregularFaceSizes()) return false;
        }
    }
    return true;
}

bool
SurfaceFactory::FaceHasLimitSurface(Index faceIndex) const {

    if (!faceHasLimitSimple(faceIndex, getFaceSize(faceIndex))) {
        return false;
    }
    if (_testNeighborhoodForLimit) {
        if (!isFaceNeighborhoodRegular(faceIndex, 0, 0)) {
            return faceHasLimitNeighborhood(faceIndex);
        }
    }
    return true;
}

Parameterization
SurfaceFactory::GetFaceParameterization(Index faceIndex) const {

    return Parameterization(_subdivScheme, getFaceSize(faceIndex));
}


//
//  Namespace with internal utilities to compute keys for unique surface
//  topologies to help cache their limit surface representations:
//
namespace {
    //  Need to redefine since SurfaceFactoryCache::Key is protected:
    typedef std::uint64_t KeyIntType;

    //
    //  Note that the data used in determining a topology key is not
    //  purely topological.  While most data determines a unique limit
    //  surface, a few parameters determine the approximation to it
    //  (e.g. the various adaptive refinement levels) or dictate other
    //  properties of its representation (e.g. double precision).
    //
    //  It may be worth separating these -- writing a method to deal
    //  with the pure topology first, then combining it with details
    //  of the representation.
    //

#ifdef _OPENSUBDIV_BFR_INCLUDE_PACKED_TOPOLOGY_KEY_
    //
    //  This alternate function for computing the cache key was applied
    //  to common topologies with low-valence and simply packs integer
    //  bitfields with the topology of the face and all of its corners.
    //
    //  It is typically 3x faster than the hashing method, but is suited
    //  for general use. Given the relatively low cost of computing the
    //  cache key (compared to building an irregular patch) use of this
    //  faster function is not generally significant, but could become
    //  so in future when/if other costs are reduced.
    //
    //  When the cost of building new patches is very low due to a high
    //  percentage of cache hits, the cost of computing the cache keys
    //  (relative to constructing the entire Surface) can rise to over
    //  10% in extreme cases.
    //
    KeyIntType
    packTopologyKey(FaceSurface const & surface,
                    IrregularPatchBuilder::Options options) {

        //
        //  Keep the bitfield struct local in scope unless needed elsewhere:
        //
        struct KeyBits {
            //  Be sure to clear to avoid uninitialized bits:
            KeyBits() { std::memset(this, 0, sizeof(*this)); }

            //  Bits for general options:
            KeyIntType subdScheme       :  2;
            KeyIntType sharpLevel       :  4;
            KeyIntType smoothLevel      :  4;
            KeyIntType usesDouble       :  1;
            KeyIntType unused           :  1;  // future use
            //  Bits for the corners:
            KeyIntType v0Valence        :  6;
            KeyIntType v1Valence        :  6;
            KeyIntType v2Valence        :  6;
            KeyIntType v3Valence        :  6;
            KeyIntType v0IsSharp        :  1;
            KeyIntType v1IsSharp        :  1;
            KeyIntType v2IsSharp        :  1;
            KeyIntType v3IsSharp        :  1;
            KeyIntType v0IsBoundary     :  1;
            KeyIntType v1IsBoundary     :  1;
            KeyIntType v2IsBoundary     :  1;
            KeyIntType v3IsBoundary     :  1;
            KeyIntType v0FaceInBoundary :  5;
            KeyIntType v1FaceInBoundary :  5;
            KeyIntType v2FaceInBoundary :  5;
            KeyIntType v3FaceInBoundary :  5;

            //
            //  Static methods to determine if bitfields can be used:
            //
            static bool InteriorValenceFits(int n) { return n < (1 << 6); }
            static bool BoundaryValenceFits(int n) { return n < (1 << 5); }

            //  A place holder if future Sdc::Options inhibit use of bitfields:
            static bool OptionsInhibitUsage(Sdc::Options) { return false; }
        };
        assert(sizeof(KeyBits) == sizeof(KeyIntType));

        //
        //  Quickly test if the topology can be packed into bitfields, or
        //  if hashing must be used.  Bitfields cannot be used when the
        //  following features are present:
        //
        //      - any sharp edges of any kind (semi-sharp or inf-sharp)
        //      - any semi-sharp vertices (inf-sharp is 1-bit per corner)
        //      - any incident irregular faces
        //
        //  These can quickly be determined by inspecting the topology tags.
        //  Two other situations are:
        //
        //      - any vertex with valence too high (more than ~6 bits)
        //      - any Sdc::Option that cannot be encoded (in theory only)
        //
        //  The former must inspect the valence of each face-vertex, while
        //  the latter requires inspecting the Sdc::Options -- and this is
        //  called out more as a future possibility...
        //
        //  In theory, if certain Sdc::Options impact the limit surface,
        //  they might need to be encoded, or might not be able to be fully
        //  encoded in future.  This is not currently the case in practice:
        //  boundary interpolation options are essentially unused as the
        //  boundary conditions are explicitly applied; the creasing method
        //  can be ignored here because creases cannot be packed; and the
        //  Catmark triangle subdivision option can be ignored because the
        //  presence of any irregular faces cannot be packed.
        //

        //  Immediate rejection of bitfields:
        MultiVertexTag combinedTag = surface.GetTag();
        if (combinedTag.HasSharpEdges() ||
            combinedTag.HasSemiSharpVertices() ||
            combinedTag.HasIrregularFaceSizes()) {
            return false;
        }

        //  Conditional rejection of bitfields for high valence:
        FaceVertexSubset const * subsets = surface.GetSubsets();

        for (int i = 0; i < surface.GetFaceSize(); ++i) {
            int valence = subsets[i]._numFacesTotal;
            if (subsets[i].IsBoundary()) {
                if (!KeyBits::BoundaryValenceFits(valence)) return false;
            } else {
                if (!KeyBits::InteriorValenceFits(valence)) return false;
            }
        }

        //  Conditional rejection of bitfields for specific Sdc::Options:
        if (KeyBits::OptionsInhibitUsage(surface.GetSdcOptionsInEffect())) {
            return false;
        }

        //
        //  Pack the topology of each FaceVertexSubset into bitfields:
        //
        KeyBits keyBits;

        keyBits.subdScheme  = surface.GetSdcScheme();
        keyBits.sharpLevel  = options.sharpLevel;
        keyBits.smoothLevel = options.smoothLevel;
        keyBits.usesDouble  = options.doublePrecision;

        keyBits.v0Valence        = subsets[0]._numFacesTotal;
        keyBits.v0IsSharp        = subsets[0].IsSharp();
        keyBits.v0IsBoundary     = subsets[0].IsBoundary();
        keyBits.v0FaceInBoundary = subsets[0]._numFacesBefore;

        keyBits.v1Valence        = subsets[1]._numFacesTotal;
        keyBits.v1IsSharp        = subsets[1].IsSharp();
        keyBits.v1IsBoundary     = subsets[1].IsBoundary();
        keyBits.v1FaceInBoundary = subsets[1]._numFacesBefore;

        keyBits.v2Valence        = subsets[2]._numFacesTotal;
        keyBits.v2IsSharp        = subsets[2].IsSharp();
        keyBits.v2IsBoundary     = subsets[2].IsBoundary();
        keyBits.v2FaceInBoundary = subsets[2]._numFacesBefore;

        if (surface.GetFaceSize() == 4) {
            keyBits.v3Valence        = subsets[3]._numFacesTotal;
            keyBits.v3IsSharp        = subsets[3].IsSharp();
            keyBits.v3IsBoundary     = subsets[3].IsBoundary();
            keyBits.v3FaceInBoundary = subsets[3]._numFacesBefore;
        }

        KeyIntType keyValue = 0;
        std::memcpy(&keyValue, &keyBits, sizeof(KeyIntType));
        return keyValue;
    }
#endif

    //
    //  Function to assign the topology of any FaceSurface to the desired
    //  integer using a hashing function that considers all topological
    //  features (incident face sizes, crease and corner sharpness, etc.):
    //
    KeyIntType
    hashTopologyKey(FaceSurface const & surface,
                    IrregularPatchBuilder::Options options) {

        //
        //  Structs for "headers" for the entire surface and each corner,
        //  to be assigned and copied into a larger buffer to be hashed:
        //
        typedef unsigned char uchar;

        struct SurfaceHeader {
            //  Be sure to clear to avoid uninitialized bits:
            SurfaceHeader() { std::memset(this, 0, sizeof(*this)); }

            short faceSize;
            uchar subdScheme;
            uchar subdCreasing;
            uchar subdTriSmooth;
            uchar sharpLevel;
            uchar smoothLevel;
            uchar usesDouble;
        };
        struct CornerHeader {
            //  Be sure to clear to avoid uninitialized bits:
            CornerHeader() { std::memset(this, 0, sizeof(*this)); }

            short numFaces;
            short faceInBoundary;

            uchar isBoundary    : 1;
            uchar isInfSharp    : 1;
            uchar isSemiSharp   : 1;
            uchar hasFaceSizes  : 1;
            uchar hasSharpEdges : 1;
        };

        //
        //  Consider using a "delimiter" between corners in the buffer of
        //  data to be hashed, i.e. a value with a distinct bit pattern
        //  such as -1, to help prevent aliasing (may not be needed):
        //
        int  delimiter = -1;
        bool useCornerDelimiter = false;

        //
        //  Local buffers for accumulating and hashing:
        //
        Vtr::internal::StackBuffer<float,16,true> floatBuffer;
        Vtr::internal::StackBuffer<short,16,true> shortBuffer;

        Vtr::internal::StackBuffer<char,256,true> hashBuffer;

        //
        //  Determine size of the main buffer to hash ahead of time:
        //
        //  Note there is some redundancy in the use of the uncommon
        //  faces sizes and sharp edges around each corner due to the
        //  way the corners' incident faces overlap. For typical cases
        //  the extra data used is not large. Only in extreme cases is
        //  it likely to be an issue -- but then the added processing
        //  and construction costs associated with such cases (e.g.
        //  high valence vertices, heavy use of creasing) will make
        //  make the overhead here insignificant.
        //
        size_t hashBufferSize = sizeof(SurfaceHeader);

        int faceSize = surface.GetFaceSize();
        for (int i = 0; i < faceSize; ++i) {
            FaceVertexSubset const & cSub = surface.GetCornerSubset(i);

            int N = cSub.GetNumFaces();

            hashBufferSize += sizeof(CornerHeader);

            hashBufferSize += cSub._tag.IsSemiSharp() ?
                              sizeof(float) : 0;
            hashBufferSize += cSub._tag.HasUnCommonFaceSizes() ?
                             (sizeof(short) * N) : 0;
            hashBufferSize += cSub._tag.HasSharpEdges() ?
                             (sizeof(float) * (N - cSub.IsBoundary())) : 0;

            hashBufferSize += useCornerDelimiter ? sizeof(delimiter) : 0;
        }
        hashBuffer.SetSize((int)hashBufferSize);

        //
        //  Start populating the buffer with the surface header:
        //
        Sdc::Options subdOptions = surface.GetSdcOptionsInEffect();

        SurfaceHeader sHeader;
        sHeader.faceSize      = (short) faceSize;
        sHeader.subdScheme    = (uchar) surface.GetSdcScheme();
        sHeader.subdCreasing  = (uchar) subdOptions.GetCreasingMethod();
        sHeader.subdTriSmooth = (uchar) subdOptions.GetTriangleSubdivision();
        sHeader.sharpLevel    = (uchar) options.sharpLevel;
        sHeader.smoothLevel   = (uchar) options.smoothLevel;
        sHeader.usesDouble    = (uchar) options.doublePrecision;

        std::memcpy(hashBuffer, &sHeader, sizeof(sHeader));

        //
        //  Populate the buffer for each corner of the surface:
        //
        char * bufferPtr = hashBuffer + sizeof(sHeader);

        for (int corner = 0; corner < faceSize; ++corner) {
            FaceVertex       const & cTop = surface.GetCornerTopology(corner);
            FaceVertexSubset const & cSub = surface.GetCornerSubset(corner);

            //  Assign the corner header:
            CornerHeader cHeader;
            cHeader.numFaces       = (short) cSub.GetNumFaces();
            cHeader.faceInBoundary = cSub._numFacesBefore;
            cHeader.isBoundary     = cSub.IsBoundary();
            cHeader.isInfSharp     = cSub.IsSharp();
            cHeader.isSemiSharp    = cSub._tag.IsSemiSharp();
            cHeader.hasFaceSizes   = cSub._tag.HasUnCommonFaceSizes();
            cHeader.hasSharpEdges  = cSub._tag.HasSharpEdges();

            std::memcpy(bufferPtr, &cHeader, sizeof(cHeader));
            bufferPtr += sizeof(cHeader);

            if (cHeader.isSemiSharp) {
                float sharpness = (cSub._localSharpness > 0.0f)
                                ?  cSub._localSharpness
                                :  cTop.GetVertexSharpness();
                std::memcpy(bufferPtr, &sharpness, sizeof(sharpness));
                bufferPtr += sizeof(sharpness);
            }
            if (cHeader.hasFaceSizes) {
                int n = cSub.GetNumFaces();
                shortBuffer.SetSize(n);
                for (int i = 0, f = cTop.GetFaceFirst(cSub); i < n;
                                f = cTop.GetFaceNext(f), ++i) {
                    shortBuffer[i] = (short) cTop.GetFaceSize(f);
                }
                std::memcpy(bufferPtr, shortBuffer, n * sizeof(short));
                bufferPtr += n * sizeof(short);
            }
            if (cHeader.hasSharpEdges) {
                int n = cSub.GetNumFaces() - cSub.IsBoundary();
                floatBuffer.SetSize(n);
                for (int i = 0, f = cTop.GetFaceFirst(cSub); i < n;
                                f = cTop.GetFaceNext(f), ++i) {
                    floatBuffer[i] = cTop.GetFaceEdgeSharpness(f, 1);
                }
                std::memcpy(bufferPtr, floatBuffer, n * sizeof(float));
                bufferPtr += n * sizeof(float);
            }

            if (useCornerDelimiter) {
                std::memcpy(bufferPtr, &delimiter, sizeof(delimiter));
                bufferPtr += sizeof(delimiter);
            }
        }
        assert((bufferPtr - hashBuffer) == (int)hashBufferSize);

        return internal::Hash64(hashBuffer, hashBufferSize);
    }
}

//
//  Methods supporting construction of linear, regular and irregular patches:
//
void
SurfaceFactory::assignLinearSurface(SurfaceType * surfacePtr,
        Index faceIndex, FVarID const * fvarPtrOrVtx) const {

    SurfaceType & surface = *surfacePtr;

    //  Initialize instance members from the associated irregular patch:
    int faceSize  = getFaceSize(faceIndex);

    surface.setParam(Parameterization(_subdivScheme, faceSize));

    surface.setRegular(faceSize == _regFaceSize);
    surface.setLinear(true);

    surface.setRegPatchMask(0);
    if (_regFaceSize == 4) {
        surface.setRegPatchType(Far::PatchDescriptor::QUADS);
    } else {
        surface.setRegPatchType(Far::PatchDescriptor::TRIANGLES);
    }

    //
    //  Finally, gather patch control points from the appropriate indices:
    //
    Index * surfaceCVs = surface.resizeCVs(faceSize);

    int count = 0;
    if (fvarPtrOrVtx == 0) {
        count = getFaceVertexIndices(faceIndex, surfaceCVs);
    } else {
        count = getFaceFVarValueIndices(faceIndex, *fvarPtrOrVtx, surfaceCVs);
    }
    //  If subclass fails to get indices, Surface will remain invalid
    if (count < faceSize) return;

    surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numLinearPatches ++;
#endif
}

void
SurfaceFactory::assignRegularSurface(SurfaceType * surfacePtr,
        Index const patchPoints[]) const {

    SurfaceType & surface = *surfacePtr;

    //
    //  Assign the parameterization and discriminants first:
    //
    surface.setParam(Parameterization(_subdivScheme, _regFaceSize));

    surface.setRegular(true);
    surface.setLinear(false);

    //
    //  Assemble the regular patch:
    //
    surface.setRegPatchType(RegularPatchBuilder::GetPatchType(_regFaceSize));
    surface.setRegPatchMask(RegularPatchBuilder::GetBoundaryMask(_regFaceSize,
                                                                  patchPoints));

    //
    //  Copy the patch control points from the given indices:
    //
    int patchSize = RegularPatchBuilder::GetPatchSize(_regFaceSize);

    Index const * pSrc = patchPoints;
    Index       * pDst = surface.resizeCVs(patchSize);

    //  Remember to replace negative indices in boundary patches:
    if (surface.getRegPatchMask() == 0) {
        std::memcpy(pDst, pSrc, patchSize * sizeof(Index));
    } else {
        //  Consider delegating this task to the RegularPatchBuilder:
        Index pPhantom = pSrc[5];
        assert(pPhantom >= 0);
        for (int i = 0; i < patchSize; ++i) {
            pDst[i] = (pSrc[i] < 0) ? pPhantom : pSrc[i];
        }
    }

    surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numExpRegularPatches ++;
#endif
}

void
SurfaceFactory::assignRegularSurface(SurfaceType * surfacePtr,
        FaceSurface const & descriptor) const {

    SurfaceType & surface = *surfacePtr;

    //
    //  Assign the parameterization and discriminants first:
    //
    surface.setParam(Parameterization(_subdivScheme, _regFaceSize));

    surface.setRegular(true);
    surface.setLinear(false);

    //
    //  Assemble the regular patch:
    //
    RegularPatchBuilder builder(descriptor);

    surface.setRegPatchType(builder.GetPatchType());
    surface.setRegPatchMask(builder.GetPatchParamBoundaryMask());

    //
    //  Gather the patch control points from the given indices:
    //
    builder.GatherControlVertexIndices(
            surface.resizeCVs(builder.GetNumControlVertices()));

    surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numRegularPatches ++;
#endif
}

void
SurfaceFactory::assignIrregularSurface(SurfaceType * surfacePtr,
        FaceSurface const & descriptor) const {

    //
    //  A builder for the irregular patch is required regardless of
    //  whether a new instance is constructed:
    //
    IrregularPatchBuilder::Options buildOptions;

    buildOptions.sharpLevel      = _factoryOptions.GetApproxLevelSharp();
    buildOptions.smoothLevel     = _factoryOptions.GetApproxLevelSmooth();
    buildOptions.doublePrecision = surfacePtr->isDouble();

    IrregularPatchBuilder builder(descriptor, buildOptions);

    //
    //  Construct a new irregular patch or identify one from the cache:
    //
    //  Note that in rare cases where control faces overlap, the current
    //  implementation of IrregularPatchBuilder may create patches whose
    //  size varies with the indices of neighboring vertices (producing a
    //  unique set). So we cannot use a cached result in such cases.
    //
    internal::IrregularPatchSharedPtr patch(0);

    if ((_topologyCache == 0) || builder.ControlHullDependsOnMeshIndices()) {
        patch = builder.Build();
    } else {
        //
        //  Compute the cache key for the topology of this face, search the
        //  cache for an existing patch and build/add one if not found:
        //
        //  Be sure to use the return result of Add() when adding as it may
        //  be the case that another thread added a patch with the same key
        //  while this one was being built. Using the instance assigned to
        //  the cache intentionally releases the one built here.
        //
        SurfaceFactoryCache::KeyType key =
                hashTopologyKey(descriptor, buildOptions);

        patch = _topologyCache->Find(key);
        if (patch == 0) {
            patch = _topologyCache->Add(key, builder.Build());
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numIrregularInCache ++;
#endif
        }
    }

    //
    //  Assign the Surface parameterization, discriminants and patch:
    //
    SurfaceType & surface = *surfacePtr;

    surface.setParam(Parameterization(_subdivScheme, descriptor.GetFaceSize()));

    surface.setRegular(false);
    surface.setLinear(false);

    surface.setIrregPatchPtr(patch);

    //  Gather the patch control points from the given indices:
    builder.GatherControlVertexIndices(
            surface.resizeCVs(patch->GetNumControlPoints()));

    surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numIrregularPatches  ++;
__numIrregularUncached += surface.ownsIrregPatch();
#endif
}

void
SurfaceFactory::copyNonLinearSurface(
        SurfaceType       * surfaceDstPtr,
        SurfaceType const & surfaceSrc,
        FaceSurface const & descriptor) const {

    SurfaceType & surfaceDst = *surfaceDstPtr;

    //  Should be creating a linear patch directly rather than copying:
    assert(!surfaceSrc.isLinear());

    //
    //  Assign the topological fields of the patch first:
    //
    surfaceDst.setParam(surfaceSrc.getParam());

    surfaceDst.setLinear(surfaceSrc.isLinear());
    surfaceDst.setRegular(surfaceSrc.isRegular());

    surfaceDst.resizeCVs(surfaceSrc.getNumCVs());

    //
    //  Assign regular/irregular fields and gather control points:
    //
    if (surfaceDst.isRegular()) {
        surfaceDst.setRegPatchType(surfaceSrc.getRegPatchType());
        surfaceDst.setRegPatchMask(surfaceSrc.getRegPatchMask());

        RegularPatchBuilder builder(descriptor);
        assert(builder.GetNumControlVertices() == surfaceDst.getNumCVs());

        builder.GatherControlVertexIndices(surfaceDst.getCVIndices());
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numRegularPatches ++;
#endif
    } else {
        surfaceDst.setIrregPatchPtr(surfaceSrc.getIrregPatchPtr());

        IrregularPatchBuilder builder(descriptor);
        assert(builder.GetNumControlVertices() == surfaceDst.getNumCVs());

        builder.GatherControlVertexIndices(surfaceDst.getCVIndices());
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numIrregularPatches  ++;
#endif
    }

    surfaceDst.setValid(true);
}


//
//  Methods to deal with topology assembly and inspection:
//
//  Note the difference between the "init" and "gather" methods:  "init"
//  fully resolves and initializes the topology by gathering face indices
//  locally and dealing with unordered faces if present, while the "gather"
//  method simply gathers the corner information -- allowing indices to be
//  provided for further use if needed.
//
bool
SurfaceFactory::initFaceNeighborhoodTopology(Index faceIndex,
        FaceTopology * faceTopologyPtr) const {

    FaceTopology & topology = *faceTopologyPtr;

    if (!gatherFaceNeighborhoodTopology(faceIndex, &topology)) {
        return false;
    }
    if (!topology.HasUnOrderedCorners()) {
        return true;
    }

    //  Gather the indices to determine topology between unordered faces:
    typedef Vtr::internal::StackBuffer<Index,72,true> IndexBuffer;

    IndexBuffer indices(topology._numFaceVertsTotal);
    if (gatherFaceNeighborhoodIndices(faceIndex, topology, 0, indices) < 0) {
        return false;
    }
    topology.ResolveUnOrderedCorners(indices);
    return true;
}

bool
SurfaceFactory::gatherFaceNeighborhoodTopology(Index faceIndex,
        FaceTopology * faceTopologyPtr) const {

    FaceTopology & faceTopology = *faceTopologyPtr;

    int N = getFaceSize(faceIndex);

    faceTopology.Initialize(N);

    for (int i = 0; i < N; ++i) {
        FaceVertex       & faceVtx = faceTopology.GetTopology(i);
        VertexDescriptor & vtxDesc = faceVtx.GetVertexDescriptor();

        faceVtx.Initialize(N, _regFaceSize);

        //  Subclass returning negative here indicates unsupported features
        //  or some other kind of failure:
        int faceInRing = populateFaceVertexDescriptor(faceIndex, i, &vtxDesc);
        if (faceInRing < 0) return false;

        faceVtx.Finalize(faceInRing);
    }

    faceTopology.Finalize();

    return true;
}

int
SurfaceFactory::gatherFaceNeighborhoodIndices(Index faceIndex,
        FaceTopology const & faceTopology,
        FVarID       const * fvarPtrOrVtx,
        Index                controlIndices[]) const {

    int faceSize = faceTopology.GetFaceSize();

    Index * indices  = controlIndices;
    int     nIndices = 0;

    for (int i = 0; i < faceSize; ++i) {
        int numFaceVerts = (fvarPtrOrVtx == 0) ?
                getFaceVertexIncidentFaceVertexIndices(faceIndex, i,
                        indices) :
                getFaceVertexIncidentFaceFVarValueIndices(faceIndex, i,
                        *fvarPtrOrVtx, indices);

        if (numFaceVerts != faceTopology.GetNumFaceVertices(i)) {
            return -1;
        }

        indices  += numFaceVerts;
        nIndices += numFaceVerts;
    }
    return nIndices;
}

bool
SurfaceFactory::isFaceNeighborhoodRegular(Index          faceIndex,
                                          FVarID const * fvarPtrOrVtx,
                                          Index          indices[]) const {
    return (fvarPtrOrVtx == 0) ?
        getFaceNeighborhoodVertexIndicesIfRegular(faceIndex, indices) :
        getFaceNeighborhoodFVarValueIndicesIfRegular(faceIndex, *fvarPtrOrVtx,
                                                     indices);
}

//
//  Main internal methods to populate set of limit Surfaces:
//
bool
SurfaceFactory::populateAllSurfaces(Index faceIndex,
        SurfaceSet * surfaceSetPtr) const {

    SurfaceSet & surfaces = *surfaceSetPtr;

    //  Abort if no Surfaces are specified to populate:
    if (surfaces.GetNumSurfaces() == 0) {
        return false;
    }

    //
    //  Be sure to re-initialize all Surfaces up-front, rather than
    //  deferring it to the assignment of each.  A failure of any one
    //  surface may leave others unvisited -- leaving it unchanged
    //  from previous use.
    //
    surfaces.InitializeSurfaces();

    //  Quickly reject faces with no limit (typically holes) -- some cases
    //  require full topological inspection and will be rejected later:
    if (!faceHasLimitSimple(faceIndex, getFaceSize(faceIndex))) {
        return false;
    }

    //  Determine if we have any non-linear cases to deal with -- which
    //  require gathering and inspection of the full neighborhood around
    //  the given face:
    int numFVarSurfaces = surfaces.GetNumFVarSurfaces();

    bool hasNonLinearSurfaces =
                (surfaces.HasVertexSurface() && !_linearScheme) ||
                (numFVarSurfaces && !_linearFVarInterp);

    bool hasLinearSurfaces =
                 surfaces.HasVaryingSurface() ||
                (surfaces.HasVertexSurface() && _linearScheme) ||
                (numFVarSurfaces && _linearFVarInterp);

    if (hasNonLinearSurfaces || _testNeighborhoodForLimit) {
        if (!populateNonLinearSurfaces(faceIndex, &surfaces)) {
            return false;
        }
    }
    if (hasLinearSurfaces) {
        if (!populateLinearSurfaces(faceIndex, &surfaces)) {
            return false;
        }
    }
    return true;
}

bool
SurfaceFactory::populateLinearSurfaces(Index faceIndex,
        SurfaceSet * surfaceSetPtr) const {

    SurfaceSet & surfaces = *surfaceSetPtr;

    if (surfaces.HasVaryingSurface()) {
        assignLinearSurface(surfaces.GetVaryingSurface(), faceIndex, 0);
    }

    if (_linearScheme && surfaces.HasVertexSurface()) {
        assignLinearSurface(surfaces.GetVertexSurface(), faceIndex, 0);
    }

    if (_linearFVarInterp) {
        int numFVarSurfaces = surfaces.GetNumFVarSurfaces();
        for (int i = 0; i < numFVarSurfaces; ++i) {
            FVarID fvarID = surfaces.GetFVarSurfaceID(i);
            assignLinearSurface(surfaces.GetFVarSurface(i), faceIndex, &fvarID);
        }
    }
    return true;
}

bool
SurfaceFactory::populateNonLinearSurfaces(Index faceIndex,
        SurfaceSet * surfaceSetPtr) const {

    SurfaceSet & surfaces = *surfaceSetPtr;

    typedef Vtr::internal::StackBuffer<Index,72,true> IndexBuffer;

    bool vtxIsNonLinear  = surfaces.HasVertexSurface() && !_linearScheme;
    bool fvarIsNonLinear = surfaces.HasFVarSurfaces()  && !_linearFVarInterp;
    bool anyNonLinear    = vtxIsNonLinear || fvarIsNonLinear;

    //
    //  First need to determine the vertex topology of the face and take
    //  appropriate action based on inputs.  It may be the case that the
    //  topology is only used to determine if the non-linear face has a
    //  limit surface and no non-linear surfaces are generated here (and
    //  linear varying or face-varying surfaces are determined elsewhere).
    //
    //  So determine the topology and deal with any required tests for
    //  the presence of limit surface.
    //
    //  If the face is "explicitly regular", i.e. the subclass can provide
    //  an immediate regular patch representation, the more tedious work
    //  to assemble the more general topological representation is avoided.
    //
    //  Note that while the vertex surface may be explicitly regular, if
    //  the face-varying topology does not match, i.e. there is a UV seam
    //  present around the face, the more general topological representation
    //  will be necessary to deal with a potentially irregular face-varying
    //  surface.
    //
    FaceTopology faceTopology(_subdivScheme, _subdivOptions);
    IndexBuffer  vtxIndices(16);
    FaceSurface  vtxSurfDesc;

    bool vtxIsExplicitlyRegular =
                isFaceNeighborhoodRegular(faceIndex, 0, vtxIndices);
    if (vtxIsExplicitlyRegular) {
        if (_testNeighborhoodForLimit && !anyNonLinear) {
            return true;
        }
    } else {
        //
        //  Three steps are required to get full topological description:
        //      - gathering the full description of the neighborhood
        //      - gathering vertex indices for the neighborhood
        //      - using the indices to resolve any unordered topology
        //  Gathering indices for the vertex surface and/or to resolve
        //  unordered topology is conditional.
        //
        if (!gatherFaceNeighborhoodTopology(faceIndex, &faceTopology)) {
            return false;
        }
        if (vtxIsNonLinear || faceTopology.HasUnOrderedCorners()) {
            vtxIndices.SetSize(faceTopology._numFaceVertsTotal);
            if (gatherFaceNeighborhoodIndices(faceIndex, faceTopology, 0,
                        vtxIndices) < 0) {
                return false;
            }
            if (faceTopology.HasUnOrderedCorners()) {
                faceTopology.ResolveUnOrderedCorners(vtxIndices);
            }
        }
        if (_testNeighborhoodForLimit) {
            if (!faceHasLimitNeighborhood(faceTopology)) {
                return false;
            } else if (!anyNonLinear) {
                return true;
            }
        }

        //  Initialize the vertex surface descriptor for use creating both
        //  the vertex Surface and any non-linear FVar Surfaces:
        vtxSurfDesc.Initialize(faceTopology, vtxIndices);
    }

    //
    //  Construct the Surface for vertex topology first, as face-varying
    //  surfaces that match topology may make use of it:
    //
    bool vtxSurfIsValid = false;
    if (vtxIsNonLinear) {
        SurfaceType & vtxSurf = *surfaces.GetVertexSurface();

        if (vtxIsExplicitlyRegular) {
            assignRegularSurface(&vtxSurf, vtxIndices);
        } else if (vtxSurfDesc.IsRegular()) {
            assignRegularSurface(&vtxSurf, vtxSurfDesc);
        } else {
            assignIrregularSurface(&vtxSurf, vtxSurfDesc);
        }
        vtxSurfIsValid = vtxSurf.isValid();
    }

    //
    //  Construct the Surface for the given face-varying topologies --
    //  all of which are potentially distinct.
    //
    //  If the vertex topology is explicitly regular, the face-varying
    //  surface can only make use of it if it shares the same topology
    //  and the subclass provides corresponding control points.
    //
    //  In all other cases the full topological description and the full
    //  description of the vertex surface must be provided.  The set of
    //  face-varying indices must then be gathered and used to create a
    //  face-varying surface descriptor, which uses the indices to find
    //  the relevant face-varying subsets for each corner.
    //
    if (fvarIsNonLinear) {
        //  We can re-use the vertex index buffer for face-varying indices:
        IndexBuffer & fvIndices = vtxIndices;

        int numFVarSurfaces = surfaces.GetNumFVarSurfaces();
        for (int i = 0; i < numFVarSurfaces; ++i) {
            SurfaceType & fvarSurf = *surfaces.GetFVarSurface(i);
            FVarID        fvarID   =  surfaces.GetFVarSurfaceID(i);

            //  First check if trivially regular, quickly assign and continue:
            bool fvarIsExplicitlyRegular = vtxIsExplicitlyRegular &&
                    isFaceNeighborhoodRegular(faceIndex, &fvarID, fvIndices);

            if (fvarIsExplicitlyRegular) {
                assignRegularSurface(&fvarSurf, fvIndices);
                continue;
            }

            //  Make sure topology, indices and vertex surface are initialized
            //  (will not be if vertex surface was explicitly regular):
            if (!vtxSurfDesc.IsInitialized()) {
                if (!initFaceNeighborhoodTopology(faceIndex, &faceTopology)) {
                    return false;
                }
                vtxSurfDesc.Initialize(faceTopology, 0);
            }
            fvIndices.SetSize(faceTopology._numFaceVertsTotal);

            //  Gather FVar indices and initialize FVar surface descriptor:
            if (gatherFaceNeighborhoodIndices(faceIndex, faceTopology,
                    &fvarID, fvIndices) < 0) {
                return false;
            }

            FaceSurface fvarSurfDesc(vtxSurfDesc, fvIndices);

            //  Detect matching or other topology and dispatch accordingly:
            if (fvarSurfDesc.FVarTopologyMatchesVertex() && vtxSurfIsValid) {
                copyNonLinearSurface(&fvarSurf, *surfaces.GetVertexSurface(),
                                     fvarSurfDesc);
            } else if (fvarSurfDesc.IsRegular()) {
                assignRegularSurface(&fvarSurf, fvarSurfDesc);
            } else {
                assignIrregularSurface(&fvarSurf, fvarSurfDesc);
            }
        }
    }
    return true;
}

//
//  Main internal method to initialize instances of Surface:
//
bool
SurfaceFactory::initSurfaces(Index faceIndex,
        internal::SurfaceData * vtxSurface,
        internal::SurfaceData * varSurface,
        internal::SurfaceData * fvarSurfaces,
        int                     fvarCount,
        FVarID const            fvarIDs[]) const {

    //  Note the SurfaceData for the first FVar Surface is assumed below
    //  to be the head of an array of SurfaceData, which will not be true
    //  if additional members are added to Surface<REAL> in future:
    assert(sizeof(internal::SurfaceData) == sizeof(Surface<float>));

    SurfaceSet surfaces;

    surfaces.vtxSurf      = vtxSurface;
    surfaces.varSurf      = varSurface;
    surfaces.fvarSurfs    = fvarSurfaces;
    surfaces.fvarIDs      = fvarIDs;
    surfaces.numFVarSurfs = fvarCount;
    surfaces.numSurfs     = fvarCount + (vtxSurface != 0) + (varSurface != 0);

    return populateAllSurfaces(faceIndex, &surfaces);
}

} // end namespace Bfr

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