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

#include "../bfr/regularPatchBuilder.h"

#include <cstring>
#include <cstdio>

namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {

namespace Bfr {

//
//  Internal helper functions:
//
namespace {
    inline int
    encodeTriBoundaryMask(int eBits, int vBits) {

        int upperBits = 0;
        int lowerBits = eBits;

        if (vBits) {
            if (eBits == 0) {
                upperBits = 1;
                lowerBits = vBits;
            } else if ((vBits == 7) &&
                       ((eBits == 1) || (eBits == 2) || (eBits == 4))) {
                upperBits = 2;
                lowerBits = eBits;
            }
        }
        return (upperBits << 3) | lowerBits;
    }
}

//
//  Static methods:
//
int
RegularPatchBuilder::GetBoundaryMask(int faceSize, Index const cvs[]) {

    bool isQuad = (faceSize == 4);
    if (isQuad) {
        int eMask = ((cvs[ 1] < 0) << 0) |
                    ((cvs[ 7] < 0) << 1) |
                    ((cvs[14] < 0) << 2) |
                    ((cvs[ 8] < 0) << 3);
        return eMask;
    } else {
        int eMask = ((cvs[ 1] < 0) << 0) |
                    ((cvs[ 9] < 0) << 1) |
                    ((cvs[ 7] < 0) << 2) ;
        int vMask = (((cvs[ 0] < 0) | (cvs[ 3] < 0)) << 0) |
                    (((cvs[ 2] < 0) | (cvs[ 6] < 0)) << 1) |
                    (((cvs[10] < 0) | (cvs[11] < 0)) << 2) ;
        return encodeTriBoundaryMask(eMask, vMask);
    }
}

//
//  Constructor (empty destructor is inline):
//
RegularPatchBuilder::RegularPatchBuilder(FaceSurface const & surface) :
        _surface(surface) {

    _isQuad = (_surface.GetTopology()._faceSize == 4);
    if (_isQuad) {
        assert(_surface.GetTopology()._regFaceSize == 4);

        _patchType = Far::PatchDescriptor::REGULAR;
        _patchSize = 16;
    } else {
        assert(_surface.GetTopology()._faceSize == 3);
        assert(_surface.GetTopology()._regFaceSize == 3);

        _patchType = Far::PatchDescriptor::LOOP;
        _patchSize = 12;
    }

    _isBoundary = _surface.GetTag().HasBoundaryVertices();
    if (!_isBoundary) {
        _boundaryMask = 0;
    } else if (_isQuad) {
        //  Boundary mask for quad trivial -- bit for each boundary edge:
        FaceVertexSubset const * C = _surface.GetSubsets();
        int eMask = ((C[0].IsBoundary() & (C[0]._numFacesBefore == 0)) << 0) |
                    ((C[1].IsBoundary() & (C[1]._numFacesBefore == 0)) << 1) |
                    ((C[2].IsBoundary() & (C[2]._numFacesBefore == 0)) << 2) |
                    ((C[3].IsBoundary() & (C[3]._numFacesBefore == 0)) << 3);
        _boundaryMask = eMask;
    } else {
        //  Boundary mask for tris not so trivial -- boundary verts can exist
        //  on tris without boundary edges, so bits for both are combined:
        FaceVertexSubset const * C = _surface.GetSubsets();
        int eMask = ((C[0].IsBoundary() & (C[0]._numFacesBefore == 0)) << 0) |
                    ((C[1].IsBoundary() & (C[1]._numFacesBefore == 0)) << 1) |
                    ((C[2].IsBoundary() & (C[2]._numFacesBefore == 0)) << 2);
        int vMask =  (C[0].IsBoundary() << 0) |
                     (C[1].IsBoundary() << 1) |
                     (C[2].IsBoundary() << 2);
        _boundaryMask = encodeTriBoundaryMask(eMask, vMask);
    }
}

//
//  Methods for gathering control vertices:
//
void
RegularPatchBuilder::gatherInteriorPatchPoints4(Index P[]) const {

    Index const * fvIndices  = &_surface.GetIndices()[0];
    Index const * fvOpposite = 0;

    //
    //  For each of the 4 corners, identify the opposite face in the ring
    //  and assign its 4 indices to the corresponding quadrant of the patch:
    //
    FaceVertex const & cTop0 = _surface.GetCornerTopology(0);
    fvOpposite = fvIndices + cTop0.GetFaceIndexOffset(cTop0.GetFaceAfter(2));
    P[ 5] = fvOpposite[0];
    P[ 4] = fvOpposite[1];
    P[ 0] = fvOpposite[2];
    P[ 1] = fvOpposite[3];
    fvIndices += cTop0.GetNumFaceVertices();

    FaceVertex const & cTop1 = _surface.GetCornerTopology(1);
    fvOpposite = fvIndices + cTop1.GetFaceIndexOffset(cTop1.GetFaceAfter(2));
    P[ 6] = fvOpposite[0];
    P[ 2] = fvOpposite[1];
    P[ 3] = fvOpposite[2];
    P[ 7] = fvOpposite[3];
    fvIndices += cTop1.GetNumFaceVertices();

    FaceVertex const & cTop2 = _surface.GetCornerTopology(2);
    fvOpposite = fvIndices + cTop2.GetFaceIndexOffset(cTop2.GetFaceAfter(2));
    P[10] = fvOpposite[0];
    P[11] = fvOpposite[1];
    P[15] = fvOpposite[2];
    P[14] = fvOpposite[3];
    fvIndices += cTop2.GetNumFaceVertices();

    FaceVertex const & cTop3 = _surface.GetCornerTopology(3);
    fvOpposite = fvIndices + cTop3.GetFaceIndexOffset(cTop3.GetFaceAfter(2));
    P[ 9] = fvOpposite[0];
    P[13] = fvOpposite[1];
    P[12] = fvOpposite[2];
    P[ 8] = fvOpposite[3];
}

void
RegularPatchBuilder::gatherBoundaryPatchPoints4(Index P[]) const {

    Index const * fvIndices = &_surface.GetIndices()[0];

    //
    //  For each of the 4 corners -- whether boundary or interior -- one
    //  incident face contains all indices that will contribute to the points
    //  of the corresponding patch.  Identify it first and then retrieve and
    //  assign the indices accordingly:
    //
    for (int i = 0; i < 4; ++i) {
        FaceVertex       const & cTop = _surface.GetCornerTopology(i);
        FaceVertexSubset const & cSub = _surface.GetCornerSubset(i);

        int faceCorner = cTop.GetFace();

        int faceOther = faceCorner;
        if (!cSub.IsBoundary()) {
            faceOther = cTop.GetFaceAfter(2);
        } else if (cSub._numFacesAfter) {
            faceOther = cTop.GetFaceNext(faceCorner);
        } else if (cSub._numFacesBefore) {
            faceOther = cTop.GetFacePrevious(faceCorner);
        }

        Index const * fvOther = fvIndices + cTop.GetFaceIndexOffset(faceOther);

        Index fvPhantom = fvOther[0];

        switch (i) {
        case 0:
            P[5] = fvOther[0];
            if (!cSub.IsBoundary()) {
                P[4] = fvOther[1];
                P[0] = fvOther[2];
                P[1] = fvOther[3];
            } else {
                P[4] = cSub._numFacesAfter  ? fvOther[3] : fvPhantom;
                P[0] = fvPhantom;
                P[1] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
            }
            break;
        case 1:
            P[6] = fvOther[0];
            if (!cSub.IsBoundary()) {
                P[2] = fvOther[1];
                P[3] = fvOther[2];
                P[7] = fvOther[3];
            } else {
                P[2] = cSub._numFacesAfter  ? fvOther[3] : fvPhantom;
                P[3] = fvPhantom;
                P[7] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
            }
            break;
        case 2:
            P[10] = fvOther[0];
            if (!cSub.IsBoundary()) {
                P[11] = fvOther[1];
                P[15] = fvOther[2];
                P[14] = fvOther[3];
            } else {
                P[11] = cSub._numFacesAfter  ? fvOther[3] : fvPhantom;
                P[15] = fvPhantom;
                P[14] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
            }
            break;
        case 3:
            P[ 9] = fvOther[0];
            if (!cSub.IsBoundary()) {
                P[13] = fvOther[1];
                P[12] = fvOther[2];
                P[ 8] = fvOther[3];
            } else {
                P[13] = cSub._numFacesAfter  ? fvOther[3] : fvPhantom;
                P[12] = fvPhantom;
                P[ 8] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
            }
            break;
        }
        fvIndices += cTop.GetNumFaceVertices();
    }
}

void
RegularPatchBuilder::gatherInteriorPatchPoints3(Index P[]) const {

    Index const * fvIndices = &_surface.GetIndices()[0];

    //
    //  For each of the 3 corners, the indices for the four contributing
    //  points come from the 2nd and 3rd faces following the corner face:
    //
    Index const * fvNext2 = 0;
    Index const * fvNext3 = 0;

    FaceVertex const & cTop0 = _surface.GetCornerTopology(0);
    fvNext2 = fvIndices + cTop0.GetFaceIndexOffset(cTop0.GetFaceAfter(2));
    fvNext3 = fvIndices + cTop0.GetFaceIndexOffset(cTop0.GetFaceAfter(3));
    P[ 4] = fvNext2[0];
    P[ 7] = fvNext2[1];
    P[ 3] = fvNext2[2];
    P[ 0] = fvNext3[2];
    fvIndices += cTop0.GetNumFaceVertices();

    FaceVertex const & cTop1 = _surface.GetCornerTopology(1);
    fvNext2 = fvIndices + cTop1.GetFaceIndexOffset(cTop1.GetFaceAfter(2));
    fvNext3 = fvIndices + cTop1.GetFaceIndexOffset(cTop1.GetFaceAfter(3));
    P[ 5] = fvNext2[0];
    P[ 1] = fvNext2[1];
    P[ 2] = fvNext2[2];
    P[ 6] = fvNext3[2];
    fvIndices += cTop1.GetNumFaceVertices();

    FaceVertex const & cTop2 = _surface.GetCornerTopology(2);
    fvNext2 = fvIndices + cTop2.GetFaceIndexOffset(cTop2.GetFaceAfter(2));
    fvNext3 = fvIndices + cTop2.GetFaceIndexOffset(cTop2.GetFaceAfter(3));
    P[ 8] = fvNext2[0];
    P[ 9] = fvNext2[1];
    P[11] = fvNext2[2];
    P[10] = fvNext3[2];
}

void
RegularPatchBuilder::gatherBoundaryPatchPoints3(Index P[]) const {

    Index const * fvIndices = &_surface.GetIndices()[0];

    //
    //  For each of the 3 corners, one incident face contains all indices
    //  that will contribute to the points of the corresponding patch, but
    //  interior vertices require two:
    //
    for (int i = 0; i < 3; ++i) {
        FaceVertex       const & cTop = _surface.GetCornerTopology(i);
        FaceVertexSubset const & cSub = _surface.GetCornerSubset(i);

        int faceCorner = cTop.GetFace();

        int faceOther = -1;
        if (!cSub.IsBoundary()) {
            faceOther = cTop.GetFaceAfter(2);
        } else if (cSub._numFacesTotal == 1) {
            faceOther = faceCorner;
        } else if (cSub._numFacesBefore == 0) {
            faceOther = cTop.GetFaceAfter(2);
        } else if (cSub._numFacesAfter == 0) {
            faceOther = cTop.GetFaceBefore(2);
        } else {
            faceOther = cTop.GetFaceNext(faceCorner);
        }
        assert(faceOther >= 0);

        Index const * fvOther = fvIndices + cTop.GetFaceIndexOffset(faceOther);

        Index fvPhantom = fvOther[0];

        switch (i) {
        case 0:
            P[4] = fvOther[0];
            if (!cSub.IsBoundary()) {
                P[7] = fvOther[1];
                P[3] = fvOther[2];
                fvOther = fvIndices +
                          cTop.GetFaceIndexOffset(cTop.GetFaceNext(faceOther));
                P[0] = fvOther[2];
            } else {
                P[7] = (cSub._numFacesAfter) ? fvOther[3 - cSub._numFacesAfter]
                                             : fvPhantom;
                P[3] = (cSub._numFacesAfter  == 2) ? fvOther[2] : fvPhantom;
                P[0] = (cSub._numFacesBefore == 2) ? fvOther[1] : fvPhantom;
            }
            break;
        case 1:
            P[5] = fvOther[0];
            if (!cSub.IsBoundary()) {
                P[1] = fvOther[1];
                P[2] = fvOther[2];
                fvOther = fvIndices +
                          cTop.GetFaceIndexOffset(cTop.GetFaceNext(faceOther));
                P[6] = fvOther[2];
            } else {
                P[1] = (cSub._numFacesAfter) ? fvOther[3 - cSub._numFacesAfter]
                                             : fvPhantom;
                P[2] = (cSub._numFacesAfter  == 2) ? fvOther[2] : fvPhantom;
                P[6] = (cSub._numFacesBefore == 2) ? fvOther[1] : fvPhantom;
            }
            break;
        case 2:
            P[8] = fvOther[0];
            if (!cSub.IsBoundary()) {
                P[ 9] = fvOther[1];
                P[11] = fvOther[2];
                fvOther = fvIndices +
                          cTop.GetFaceIndexOffset(cTop.GetFaceNext(faceOther));
                P[10] = fvOther[2];
            } else {
                P[ 9] = (cSub._numFacesAfter) ? fvOther[3 - cSub._numFacesAfter]
                                              : fvPhantom;
                P[11] = (cSub._numFacesAfter  == 2) ? fvOther[2] : fvPhantom;
                P[10] = (cSub._numFacesBefore == 2) ? fvOther[1] : fvPhantom;
            }
            break;
        }
        fvIndices += cTop.GetNumFaceVertices();
    }
}

int
RegularPatchBuilder::GatherControlVertexIndices(Index cvIndices[]) const {

    if (_isQuad) {
        if (_isBoundary) {
            gatherBoundaryPatchPoints4(cvIndices);
        } else {
            gatherInteriorPatchPoints4(cvIndices);
        }
    } else {
        if (_isBoundary) {
            gatherBoundaryPatchPoints3(cvIndices);
        } else {
            gatherInteriorPatchPoints3(cvIndices);
        }
    }
    return _patchSize;
}


//
//  Methods for debugging...
//
void
RegularPatchBuilder::print(Index const P[]) const {

    printf("RegularPatchBuilder:\n");

    if (_patchType == Far::PatchDescriptor::REGULAR) {
        printf("    patch type  = REGULAR (B-Spline, quad)\n");
    } else if (_patchType == Far::PatchDescriptor::LOOP) {
        printf("    patch type  = LOOP (Box-Spline, tri)\n");
    } else {
        assert("Unknown _patchType for RegularPatchBuilder" == 0);
    }

    printf("    patch size  = %d\n", _patchSize);
    printf("    is quad     = %d\n", _isQuad);
    printf("    is boundary = %d\n", _isBoundary);

    if (P) {
        const char * label  = "    patch points:";
        const char * indent = "                 ";
        if (_isQuad) {
            printf("%s %4d %4d %4d %4d\n", label,  P[12], P[13], P[14], P[15]);
            printf("%s %4d %4d %4d %4d\n", indent, P[ 8], P[ 9], P[10], P[11]);
            printf("%s %4d %4d %4d %4d\n", indent, P[ 4], P[ 5], P[ 6], P[ 7]);
            printf("%s %4d %4d %4d %4d\n", indent, P[ 0], P[ 1], P[ 2], P[ 3]);
        } else {
            printf("%s       %4d  %4d\n",     label,  P[10], P[11]);
            printf("%s    %4d  %4d  %4d\n",   indent, P[7], P[8], P[9]);
            printf("%s %4d  %4d  %4d  %4d\n", indent, P[3], P[4], P[5], P[6]);
            printf("%s    %4d  %4d  %4d\n",   indent, P[0], P[1], P[2]);
        }
    }
}

} // end namespace Bfr

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