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

#ifndef OPENSUBDIV3_OSD_PATCH_BASIS_H
#define OPENSUBDIV3_OSD_PATCH_BASIS_H

/// This is an implementation of the functionality from far/patchBasis.h
/// which can be used from client shader and kernel code, i.e for
/// GLSL, HLSL, CUDA, OpenCL, Metal, and C++ shaders and kernels.
///
/// This is defined within the OpenSubdiv::Osd namespace for C++, but
/// the methods and types also have an Osd prefix for consistency with
/// environments (e.g. GLSL, etc.) where C++ namespaces are not available.

#if !defined(OSD_PATCH_BASIS_GLSL) && \
    !defined(OSD_PATCH_BASIS_HLSL) && \
    !defined(OSD_PATCH_BASIS_CUDA) && \
    !defined(OSD_PATCH_BASIS_OPENCL) && \
    !defined(OSD_PATCH_BASIS_METAL)

#include "../version.h"

#include "../osd/patchBasisTypes.h"

namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {

namespace Osd {

#endif

OSD_FUNCTION_STORAGE_CLASS
// template <typename REAL>
int
Osd_EvalBasisLinear(OSD_REAL s, OSD_REAL t,
        OSD_OUT_ARRAY(OSD_REAL, wP, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDs, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDt, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDss, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDst, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDtt, 4)) {

    OSD_REAL sC = 1.0f - s;
    OSD_REAL tC = 1.0f - t;

    if (OSD_OPTIONAL(wP)) {
        wP[0] = sC * tC;
        wP[1] =  s * tC;
        wP[2] =  s * t;
        wP[3] = sC * t;
    }
    if (OSD_OPTIONAL(wDs && wDt)) {
        wDs[0] = -tC;
        wDs[1] =  tC;
        wDs[2] =   t;
        wDs[3] =  -t;

        wDt[0] = -sC;
        wDt[1] =  -s;
        wDt[2] =   s;
        wDt[3] =  sC;

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            for(int i = 0; i < 4; ++i) {
                wDss[i] = 0.0f;
                wDtt[i] = 0.0f;
            }

            wDst[0] =  1.0f;
            wDst[1] = -1.0f;
            wDst[2] =  1.0f;
            wDst[3] = -1.0f;
        }
    }
    return 4;
}

// namespace {
    //
    //  Cubic BSpline curve basis evaluation:
    //
    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_evalBSplineCurve(OSD_REAL t,
        OSD_OUT_ARRAY(OSD_REAL, wP, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDP, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDP2, 4)) {

        const OSD_REAL one6th = OSD_REAL_CAST(1.0f / 6.0f);

        OSD_REAL t2 = t * t;
        OSD_REAL t3 = t * t2;

        wP[0] = one6th * (1.0f - 3.0f*(t -      t2) -      t3);
        wP[1] = one6th * (4.0f           - 6.0f*t2  + 3.0f*t3);
        wP[2] = one6th * (1.0f + 3.0f*(t +      t2  -      t3));
        wP[3] = one6th * (                                 t3);

        if (OSD_OPTIONAL(wDP)) {
            wDP[0] = -0.5f*t2 +      t - 0.5f;
            wDP[1] =  1.5f*t2 - 2.0f*t;
            wDP[2] = -1.5f*t2 +      t + 0.5f;
            wDP[3] =  0.5f*t2;
        }
        if (OSD_OPTIONAL(wDP2)) {
            wDP2[0] = -       t + 1.0f;
            wDP2[1] =  3.0f * t - 2.0f;
            wDP2[2] = -3.0f * t + 1.0f;
            wDP2[3] =         t;
        }
    }

    //
    //  Weight adjustments to account for phantom end points:
    //
    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_adjustBSplineBoundaryWeights(
            int boundary,
            OSD_INOUT_ARRAY(OSD_REAL, w, 16)) {

        if ((boundary & 1) != 0) {
            for (int i = 0; i < 4; ++i) {
                w[i + 8] -= w[i + 0];
                w[i + 4] += w[i + 0] * 2.0f;
                w[i + 0]  = 0.0f;
            }
        }
        if ((boundary & 2) != 0) {
            for (int i = 0; i < 16; i += 4) {
                w[i + 1] -= w[i + 3];
                w[i + 2] += w[i + 3] * 2.0f;
                w[i + 3]  = 0.0f;
            }
        }
        if ((boundary & 4) != 0) {
            for (int i = 0; i < 4; ++i) {
                w[i +  4] -= w[i + 12];
                w[i +  8] += w[i + 12] * 2.0f;
                w[i + 12]  = 0.0f;
            }
        }
        if ((boundary & 8) != 0) {
            for (int i = 0; i < 16; i += 4) {
                w[i + 2] -= w[i + 0];
                w[i + 1] += w[i + 0] * 2.0f;
                w[i + 0]  = 0.0f;
            }
        }
    }

    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_boundBasisBSpline(
            int boundary,
            OSD_INOUT_ARRAY(OSD_REAL, wP, 16),
            OSD_INOUT_ARRAY(OSD_REAL, wDs, 16),
            OSD_INOUT_ARRAY(OSD_REAL, wDt, 16),
            OSD_INOUT_ARRAY(OSD_REAL, wDss, 16),
            OSD_INOUT_ARRAY(OSD_REAL, wDst, 16),
            OSD_INOUT_ARRAY(OSD_REAL, wDtt, 16)) {

        if (OSD_OPTIONAL(wP)) {
            Osd_adjustBSplineBoundaryWeights(boundary, wP);
        }
        if (OSD_OPTIONAL(wDs && wDt)) {
            Osd_adjustBSplineBoundaryWeights(boundary, wDs);
            Osd_adjustBSplineBoundaryWeights(boundary, wDt);

            if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
                Osd_adjustBSplineBoundaryWeights(boundary, wDss);
                Osd_adjustBSplineBoundaryWeights(boundary, wDst);
                Osd_adjustBSplineBoundaryWeights(boundary, wDtt);
            }
        }
    }

// } // end namespace

OSD_FUNCTION_STORAGE_CLASS
int
Osd_EvalBasisBSpline(OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 16)) {

    OSD_REAL sWeights[4], tWeights[4], dsWeights[4], dtWeights[4], dssWeights[4], dttWeights[4];

    Osd_evalBSplineCurve(s, sWeights, OSD_OPTIONAL_INIT(wDs, dsWeights), OSD_OPTIONAL_INIT(wDss, dssWeights));
    Osd_evalBSplineCurve(t, tWeights, OSD_OPTIONAL_INIT(wDt, dtWeights), OSD_OPTIONAL_INIT(wDtt, dttWeights));

    if (OSD_OPTIONAL(wP)) {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                wP[4*i+j] = sWeights[j] * tWeights[i];
            }
        }
    }
    if (OSD_OPTIONAL(wDs && wDt)) {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                wDs[4*i+j] = dsWeights[j] * tWeights[i];
                wDt[4*i+j] = sWeights[j] * dtWeights[i];
            }
        }

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            for (int i = 0; i < 4; ++i) {
                for (int j = 0; j < 4; ++j) {
                    wDss[4*i+j] = dssWeights[j] * tWeights[i];
                    wDst[4*i+j] = dsWeights[j] * dtWeights[i];
                    wDtt[4*i+j] = sWeights[j] * dttWeights[i];
                }
            }
        }
    }
    return 16;
}

// namespace {
    //
    //  Cubic Bezier curve basis evaluation:
    //
    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_evalBezierCurve(
        OSD_REAL t,
        OSD_OUT_ARRAY(OSD_REAL, wP, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDP, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDP2, 4)) {

        // The four uniform cubic Bezier basis functions (in terms of t and its
        // complement tC) evaluated at t:
        OSD_REAL t2 = t*t;
        OSD_REAL tC = 1.0f - t;
        OSD_REAL tC2 = tC * tC;

        wP[0] = tC2 * tC;
        wP[1] = tC2 * t * 3.0f;
        wP[2] = t2 * tC * 3.0f;
        wP[3] = t2 * t;

        // Derivatives of the above four basis functions at t:
        if (OSD_OPTIONAL(wDP)) {
           wDP[0] = -3.0f * tC2;
           wDP[1] =  9.0f * t2 - 12.0f * t + 3.0f;
           wDP[2] = -9.0f * t2 +  6.0f * t;
           wDP[3] =  3.0f * t2;
        }

        // Second derivatives of the basis functions at t:
        if (OSD_OPTIONAL(wDP2)) {
            wDP2[0] =   6.0f * tC;
            wDP2[1] =  18.0f * t - 12.0f;
            wDP2[2] = -18.0f * t +  6.0f;
            wDP2[3] =   6.0f * t;
        }
    }
// } // end namespace

OSD_FUNCTION_STORAGE_CLASS
int
Osd_EvalBasisBezier(OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 16)) {

    OSD_REAL sWeights[4], tWeights[4], dsWeights[4], dtWeights[4], dssWeights[4], dttWeights[4];

    Osd_evalBezierCurve(s, OSD_OPTIONAL_INIT(wP, sWeights), OSD_OPTIONAL_INIT(wDs, dsWeights), OSD_OPTIONAL_INIT(wDss, dssWeights));
    Osd_evalBezierCurve(t, OSD_OPTIONAL_INIT(wP, tWeights), OSD_OPTIONAL_INIT(wDt, dtWeights), OSD_OPTIONAL_INIT(wDtt, dttWeights));

    if (OSD_OPTIONAL(wP)) {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                wP[4*i+j] = sWeights[j] * tWeights[i];
            }
        }
    }
    if (OSD_OPTIONAL(wDs && wDt)) {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                wDs[4*i+j] = dsWeights[j] * tWeights[i];
                wDt[4*i+j] = sWeights[j] * dtWeights[i];
            }
        }

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            for (int i = 0; i < 4; ++i) {
                for (int j = 0; j < 4; ++j) {
                    wDss[4*i+j] = dssWeights[j] * tWeights[i];
                    wDst[4*i+j] = dsWeights[j] * dtWeights[i];
                    wDtt[4*i+j] = sWeights[j] * dttWeights[i];
                }
            }
        }
    }
    return 16;
}

OSD_FUNCTION_STORAGE_CLASS
int
Osd_EvalBasisGregory(OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 20)) {

    //  Indices of boundary and interior points and their corresponding Bezier points
    //  (this can be reduced with more direct indexing and unrolling of loops):
    //
    OSD_DATA_STORAGE_CLASS const int boundaryGregory[12] = OSD_ARRAY_12(int, 0, 1, 7, 5, 2, 6, 16, 12, 15, 17, 11, 10 );
    OSD_DATA_STORAGE_CLASS const int boundaryBezSCol[12] = OSD_ARRAY_12(int, 0, 1, 2, 3, 0, 3,  0,  3,  0,  1,  2,  3 );
    OSD_DATA_STORAGE_CLASS const int boundaryBezTRow[12] = OSD_ARRAY_12(int, 0, 0, 0, 0, 1, 1,  2,  2,  3,  3,  3,  3 );

    OSD_DATA_STORAGE_CLASS const int interiorGregory[8] = OSD_ARRAY_8(int, 3, 4,  8, 9,  13, 14,  18, 19 );
    OSD_DATA_STORAGE_CLASS const int interiorBezSCol[8] = OSD_ARRAY_8(int, 1, 1,  2, 2,   2,  2,   1,  1 );
    OSD_DATA_STORAGE_CLASS const int interiorBezTRow[8] = OSD_ARRAY_8(int, 1, 1,  1, 1,   2,  2,   2,  2 );

    //
    //  Bezier basis functions are denoted with B while the rational multipliers for the
    //  interior points will be denoted G -- so we have B(s), B(t) and G(s,t):
    //
    //  Directional Bezier basis functions B at s and t:
    OSD_REAL Bs[4], Bds[4], Bdss[4];
    OSD_REAL Bt[4], Bdt[4], Bdtt[4];

    Osd_evalBezierCurve(s, Bs, OSD_OPTIONAL_INIT(wDs, Bds), OSD_OPTIONAL_INIT(wDss, Bdss));
    Osd_evalBezierCurve(t, Bt, OSD_OPTIONAL_INIT(wDt, Bdt), OSD_OPTIONAL_INIT(wDtt, Bdtt));

    //  Rational multipliers G at s and t:
    OSD_REAL sC = 1.0f - s;
    OSD_REAL tC = 1.0f - t;

    //  Use <= here to avoid compiler warnings -- the sums should always be non-negative:
    OSD_REAL df0 = s  + t;   df0 = (df0 <= 0.0f) ? 1.0f : (1.0f / df0);
    OSD_REAL df1 = sC + t;   df1 = (df1 <= 0.0f) ? 1.0f : (1.0f / df1);
    OSD_REAL df2 = sC + tC;  df2 = (df2 <= 0.0f) ? 1.0f : (1.0f / df2);
    OSD_REAL df3 = s  + tC;  df3 = (df3 <= 0.0f) ? 1.0f : (1.0f / df3);

    //  Make sure the G[i] for pairs of interior points sum to 1 in all cases:
    OSD_REAL G[8] = OSD_ARRAY_8(OSD_REAL,  s*df0, (1.0f -  s*df0),
                                           t*df1, (1.0f -  t*df1),
                                          sC*df2, (1.0f - sC*df2),
                                          tC*df3, (1.0f - tC*df3) );

    //  Combined weights for boundary and interior points:
    for (int i = 0; i < 12; ++i) {
        wP[boundaryGregory[i]] = Bs[boundaryBezSCol[i]] * Bt[boundaryBezTRow[i]];
    }
    for (int j = 0; j < 8; ++j) {
        wP[interiorGregory[j]] = Bs[interiorBezSCol[j]] * Bt[interiorBezTRow[j]] * G[j];
    }

    //
    //  For derivatives, the basis functions for the interior points are rational and ideally
    //  require appropriate differentiation, i.e. product rule for the combination of B and G
    //  and the quotient rule for the rational G itself.  As initially proposed by Loop et al
    //  though, the approximation using the 16 Bezier points arising from the G(s,t) has
    //  proved adequate (and is what the GPU shaders use) so we continue to use that here.
    //
    //  An implementation of the true derivatives is provided and conditionally compiled for
    //  those that require it, e.g.:
    //
    //    dclyde's note: skipping half of the product rule like this does seem to change the
    //    result a lot in my tests.  This is not a runtime bottleneck for cloth sims anyway
    //    so I'm just using the accurate version.
    //
    if (OSD_OPTIONAL(wDs && wDt)) {
        bool find_second_partials = OSD_OPTIONAL(wDs && wDst && wDtt);

        //  Combined weights for boundary points -- simple tensor products:
        for (int i = 0; i < 12; ++i) {
            int iDst = boundaryGregory[i];
            int tRow = boundaryBezTRow[i];
            int sCol = boundaryBezSCol[i];

            wDs[iDst] = Bds[sCol] * Bt[tRow];
            wDt[iDst] = Bdt[tRow] * Bs[sCol];

            if (find_second_partials) {
                wDss[iDst] = Bdss[sCol] * Bt[tRow];
                wDst[iDst] = Bds[sCol] * Bdt[tRow];
                wDtt[iDst] = Bs[sCol] * Bdtt[tRow];
            }
        }

#ifndef OPENSUBDIV_GREGORY_EVAL_TRUE_DERIVATIVES
        //  Approximation to the true Gregory derivatives by differentiating the Bezier patch
        //  unique to the given (s,t), i.e. having F = (g^+ * f^+) + (g^- * f^-) as its four
        //  interior points:
        //
        //  Combined weights for interior points -- tensor products with G+ or G-:
        for (int j = 0; j < 8; ++j) {
            int iDst = interiorGregory[j];
            int tRow = interiorBezTRow[j];
            int sCol = interiorBezSCol[j];

            wDs[iDst] = Bds[sCol] * Bt[tRow] * G[j];
            wDt[iDst] = Bdt[tRow] * Bs[sCol] * G[j];

            if (find_second_partials) {
                wDss[iDst] = Bdss[sCol] * Bt[tRow] * G[j];
                wDst[iDst] = Bds[sCol] * Bdt[tRow] * G[j];
                wDtt[iDst] = Bs[sCol] * Bdtt[tRow] * G[j];
            }
        }
#else
        //  True Gregory derivatives using appropriate differentiation of composite functions:
        //
        //  Note that for G(s,t) = N(s,t) / D(s,t), all N' and D' are trivial constants (which
        //  simplifies things for higher order derivatives).  And while each pair of functions
        //  G (i.e. the G+ and G- corresponding to points f+ and f-) must sum to 1 to ensure
        //  Bezier equivalence (when f+ = f-), the pairs of G' must similarly sum to 0.  So we
        //  can potentially compute only one of the pair and negate the result for the other
        //  (and with 4 or 8 computations involving these constants, this is all very SIMD
        //  friendly...) but for now we treat all 8 independently for simplicity.
        //
        //float N[8] = OSD_ARRAY_8(float,    s,     t,      t,     sC,      sC,     tC,      tC,     s );
        OSD_REAL D[8] = OSD_ARRAY_8(OSD_REAL,  df0,   df0,    df1,    df1,     df2,    df2,     df3,   df3 );

        OSD_DATA_STORAGE_CLASS const OSD_REAL Nds[8] = OSD_ARRAY_8(OSD_REAL, 1.0f, 0.0f,  0.0f, -1.0f, -1.0f,  0.0f,  0.0f,  1.0f );
        OSD_DATA_STORAGE_CLASS const OSD_REAL Ndt[8] = OSD_ARRAY_8(OSD_REAL, 0.0f, 1.0f,  1.0f,  0.0f,  0.0f, -1.0f, -1.0f,  0.0f );

        OSD_DATA_STORAGE_CLASS const OSD_REAL Dds[8] = OSD_ARRAY_8(OSD_REAL, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,  1.0f );
        OSD_DATA_STORAGE_CLASS const OSD_REAL Ddt[8] = OSD_ARRAY_8(OSD_REAL, 1.0f, 1.0f,  1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f );
        //  Combined weights for interior points -- (scaled) combinations of B, B', G and G':
        for (int k = 0; k < 8; ++k) {
            int iDst = interiorGregory[k];
            int tRow = interiorBezTRow[k];
            int sCol = interiorBezSCol[k];

            //  Quotient rule for G' (re-expressed in terms of G to simplify (and D = 1/D)):
            OSD_REAL Gds = (Nds[k] - Dds[k] * G[k]) * D[k];
            OSD_REAL Gdt = (Ndt[k] - Ddt[k] * G[k]) * D[k];

            //  Product rule combining B and B' with G and G':
            wDs[iDst] = (Bds[sCol] * G[k] + Bs[sCol] * Gds) * Bt[tRow];
            wDt[iDst] = (Bdt[tRow] * G[k] + Bt[tRow] * Gdt) * Bs[sCol];

            if (find_second_partials) {
                OSD_REAL Dsqr_inv = D[k]*D[k];

                OSD_REAL Gdss = 2.0f * Dds[k] * Dsqr_inv * (G[k] * Dds[k] - Nds[k]);
                OSD_REAL Gdst = Dsqr_inv * (2.0f * G[k] * Dds[k] * Ddt[k] - Nds[k] * Ddt[k] - Ndt[k] * Dds[k]);
                OSD_REAL Gdtt = 2.0f * Ddt[k] * Dsqr_inv * (G[k] * Ddt[k] - Ndt[k]);

                wDss[iDst] = (Bdss[sCol] * G[k] + 2.0f * Bds[sCol] * Gds + Bs[sCol] * Gdss) * Bt[tRow];
                wDst[iDst] =  Bt[tRow] * (Bs[sCol] * Gdst + Bds[sCol] * Gdt) +
                             Bdt[tRow] * (Bds[sCol] * G[k] + Bs[sCol] * Gds);
                wDtt[iDst] = (Bdtt[tRow] * G[k] + 2.0f * Bdt[tRow] * Gdt + Bt[tRow] * Gdtt) * Bs[sCol];
            }
        }
#endif
    }
    return 20;
}


OSD_FUNCTION_STORAGE_CLASS
// template <typename REAL>
int
Osd_EvalBasisLinearTri(OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 3),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 3),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 3),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 3),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 3),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 3)) {

    if (OSD_OPTIONAL(wP)) {
        wP[0] = 1.0f - s - t;
        wP[1] = s;
        wP[2] = t;
    }
    if (OSD_OPTIONAL(wDs && wDt)) {
        wDs[0] = -1.0f;
        wDs[1] =  1.0f;
        wDs[2] =  0.0f;

        wDt[0] = -1.0f;
        wDt[1] =  0.0f;
        wDt[2] =  1.0f;

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            wDss[0] = wDss[1] = wDss[2] = 0.0f;
            wDst[0] = wDst[1] = wDst[2] = 0.0f;
            wDtt[0] = wDtt[1] = wDtt[2] = 0.0f;
        }
    }
    return 3;
}


// namespace {
    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_evalBivariateMonomialsQuartic(
        OSD_REAL s, OSD_REAL t,
        OSD_OUT_ARRAY(OSD_REAL, M, 15)) {

        M[0] = 1.0;

        M[1] = s;
        M[2] = t;

        M[3] = s * s;
        M[4] = s * t;
        M[5] = t * t;

        M[6] = M[3] * s;
        M[7] = M[4] * s;
        M[8] = M[4] * t;
        M[9] = M[5] * t;

        M[10] = M[6] * s;
        M[11] = M[7] * s;
        M[12] = M[3] * M[5];
        M[13] = M[8] * t;
        M[14] = M[9] * t;
    }

    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_evalBoxSplineTriDerivWeights(
        OSD_INOUT_ARRAY(OSD_REAL, /*stMonomials*/M, 15),
        int ds, int dt,
        OSD_OUT_ARRAY(OSD_REAL, w, 12)) {

        // const OSD_REAL M[15] = stMonomials;

        OSD_REAL S = 1.0f;

        int totalOrder = ds + dt;
        if (totalOrder == 0) {
            S *= OSD_REAL_CAST(1.0f / 12.0f);

            w[0]  = S * (1 - 2*M[1] - 4*M[2]          + 6*M[4] + 6*M[5] + 2*M[6]          - 6*M[8] - 4*M[9] -   M[10] - 2*M[11] + 2*M[13] +   M[14]);
            w[1]  = S * (1 + 2*M[1] - 2*M[2]          - 6*M[4]          - 4*M[6]          + 6*M[8] + 2*M[9] + 2*M[10] + 4*M[11] - 2*M[13] -   M[14]);
            w[2]  = S * (                                                 2*M[6]                            -   M[10] - 2*M[11]                    );
            w[3]  = S * (1 - 4*M[1] - 2*M[2] + 6*M[3] + 6*M[4]          - 4*M[6] - 6*M[7]          + 2*M[9] +   M[10] + 2*M[11] - 2*M[13] -   M[14]);
            w[4]  = S * (6                   -12*M[3] -12*M[4] -12*M[5] + 8*M[6] +12*M[7] +12*M[8] + 8*M[9] -   M[10] - 2*M[11] - 2*M[13] -   M[14]);
            w[5]  = S * (1 + 4*M[1] + 2*M[2] + 6*M[3] + 6*M[4]          - 4*M[6] - 6*M[7] -12*M[8] - 4*M[9] -   M[10] - 2*M[11] + 4*M[13] + 2*M[14]);
            w[6]  = S * (                                                                                       M[10] + 2*M[11]                    );
            w[7]  = S * (1 - 2*M[1] + 2*M[2]          - 6*M[4]          + 2*M[6] + 6*M[7]          - 4*M[9] -   M[10] - 2*M[11] + 4*M[13] + 2*M[14]);
            w[8]  = S * (1 + 2*M[1] + 4*M[2]          + 6*M[4] + 6*M[5] - 4*M[6] -12*M[7] - 6*M[8] - 4*M[9] + 2*M[10] + 4*M[11] - 2*M[13] -   M[14]);
            w[9]  = S * (                                                 2*M[6] + 6*M[7] + 6*M[8] + 2*M[9] -   M[10] - 2*M[11] - 2*M[13] -   M[14]);
            w[10] = S * (                                                                            2*M[9]                     - 2*M[13] -   M[14]);
            w[11] = S * (                                                                                                         2*M[13] +   M[14]);
        } else if (totalOrder == 1) {
            S *= OSD_REAL_CAST(1.0f / 6.0f);

            if (ds != 0) {
                w[0]  = S * (-1          + 3*M[2] + 3*M[3]          - 3*M[5] - 2*M[6] - 3*M[7] +   M[9]);
                w[1]  = S * ( 1          - 3*M[2] - 6*M[3]          + 3*M[5] + 4*M[6] + 6*M[7] -   M[9]);
                w[2]  = S * (                       3*M[3]                   - 2*M[6] - 3*M[7]         );
                w[3]  = S * (-2 + 6*M[1] + 3*M[2] - 6*M[3] - 6*M[4]          + 2*M[6] + 3*M[7] -   M[9]);
                w[4]  = S * (   -12*M[1] - 6*M[2] +12*M[3] +12*M[4] + 6*M[5] - 2*M[6] - 3*M[7] -   M[9]);
                w[5]  = S * ( 2 + 6*M[1] + 3*M[2] - 6*M[3] - 6*M[4] - 6*M[5] - 2*M[6] - 3*M[7] + 2*M[9]);
                w[6]  = S * (                                                  2*M[6] + 3*M[7]         );
                w[7]  = S * (-1          - 3*M[2] + 3*M[3] + 6*M[4]          - 2*M[6] - 3*M[7] + 2*M[9]);
                w[8]  = S * ( 1          + 3*M[2] - 6*M[3] -12*M[4] - 3*M[5] + 4*M[6] + 6*M[7] -   M[9]);
                w[9]  = S * (                       3*M[3] + 6*M[4] + 3*M[5] - 2*M[6] - 3*M[7] -   M[9]);
                w[10] = S * (                                                                  -   M[9]);
                w[11] = S * (                                                                      M[9]);
            } else {
                w[0]  = S * (-2 + 3*M[1] + 6*M[2]          - 6*M[4] - 6*M[5]  -   M[6] + 3*M[8] + 2*M[9]);
                w[1]  = S * (-1 - 3*M[1]                   + 6*M[4] + 3*M[5]  + 2*M[6] - 3*M[8] - 2*M[9]);
                w[2]  = S * (                                                 -   M[6]                  );
                w[3]  = S * (-1 + 3*M[1]          - 3*M[3]          + 3*M[5]  +   M[6] - 3*M[8] - 2*M[9]);
                w[4]  = S * (   - 6*M[1] -12*M[2] + 6*M[3] +12*M[4] +12*M[5]  -   M[6] - 3*M[8] - 2*M[9]);
                w[5]  = S * ( 1 + 3*M[1]          - 3*M[3] -12*M[4] - 6*M[5]  -   M[6] + 6*M[8] + 4*M[9]);
                w[6]  = S * (                                                 +   M[6]                  );
                w[7]  = S * ( 1 - 3*M[1]          + 3*M[3]          - 6*M[5]  -   M[6] + 6*M[8] + 4*M[9]);
                w[8]  = S * ( 2 + 3*M[1] + 6*M[2] - 6*M[3] - 6*M[4] - 6*M[5]  + 2*M[6] - 3*M[8] - 2*M[9]);
                w[9]  = S * (                     + 3*M[3] + 6*M[4] + 3*M[5]  -   M[6] - 3*M[8] - 2*M[9]);
                w[10] = S * (                                         3*M[5]           - 3*M[8] - 2*M[9]);
                w[11] = S * (                                                            3*M[8] + 2*M[9]);
            }
        } else if (totalOrder == 2) {
            if (ds == 2) {
                w[0]  = S * (   +   M[1]          -   M[3] -   M[4]);
                w[1]  = S * (   - 2*M[1]          + 2*M[3] + 2*M[4]);
                w[2]  = S * (       M[1]          -   M[3] -   M[4]);
                w[3]  = S * ( 1 - 2*M[1] -   M[2] +   M[3] +   M[4]);
                w[4]  = S * (-2 + 4*M[1] + 2*M[2] -   M[3] -   M[4]);
                w[5]  = S * ( 1 - 2*M[1] -   M[2] -   M[3] -   M[4]);
                w[6]  = S * (                         M[3] +   M[4]);
                w[7]  = S * (   +   M[1] +   M[2] -   M[3] -   M[4]);
                w[8]  = S * (   - 2*M[1] - 2*M[2] + 2*M[3] + 2*M[4]);
                w[9]  = S * (       M[1] +   M[2] -   M[3] -   M[4]);
                w[10] =     0;
                w[11] =     0;
            } else if (dt == 2) {
                w[0]  = S * ( 1 -   M[1] - 2*M[2] +   M[4] +   M[5]);
                w[1]  = S * (   +   M[1] +   M[2] -   M[4] -   M[5]);
                w[2]  =     0;
                w[3]  = S * (            +   M[2] -   M[4] -   M[5]);
                w[4]  = S * (-2 + 2*M[1] + 4*M[2] -   M[4] -   M[5]);
                w[5]  = S * (   - 2*M[1] - 2*M[2] + 2*M[4] + 2*M[5]);
                w[6]  =     0;
                w[7]  = S * (            - 2*M[2] + 2*M[4] + 2*M[5]);
                w[8]  = S * ( 1 -   M[1] - 2*M[2] -   M[4] -   M[5]);
                w[9]  = S * (   +   M[1] +   M[2] -   M[4] -   M[5]);
                w[10] = S * (                M[2] -   M[4] -   M[5]);
                w[11] = S * (                         M[4] +   M[5]);
            } else {
                S *= OSD_REAL_CAST(1.0f / 2.0f);

                w[0]  = S * ( 1          - 2*M[2] -   M[3] +   M[5]);
                w[1]  = S * (-1          + 2*M[2] + 2*M[3] -   M[5]);
                w[2]  = S * (                     -   M[3]         );
                w[3]  = S * ( 1 - 2*M[1]          +   M[3] -   M[5]);
                w[4]  = S * (-2 + 4*M[1] + 4*M[2] -   M[3] -   M[5]);
                w[5]  = S * ( 1 - 2*M[1] - 4*M[2] -   M[3] + 2*M[5]);
                w[6]  = S * (                     +   M[3]         );
                w[7]  = S * (-1 + 2*M[1]          -   M[3] + 2*M[5]);
                w[8]  = S * ( 1 - 4*M[1] - 2*M[2] + 2*M[3] -   M[5]);
                w[9]  = S * (   + 2*M[1] + 2*M[2] -   M[3] -   M[5]);
                w[10] = S * (                              -   M[5]);
                w[11] = S * (                                  M[5]);
            }
        } else {
            // assert(totalOrder <= 2);
        }
    }

    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_adjustBoxSplineTriBoundaryWeights(
        int boundaryMask,
        OSD_INOUT_ARRAY(OSD_REAL, weights, 12)) {

        if (boundaryMask == 0) return;

        //
        //  Determine boundary edges and vertices from the lower 3 and upper
        //  2 bits of the 5-bit mask:
        //
        int upperBits = (boundaryMask >> 3) & 0x3;
        int lowerBits = boundaryMask & 7;

        int eBits = lowerBits;
        int vBits = 0;

        if (upperBits == 1) {
            //  Boundary vertices only:
            vBits = eBits;
            eBits = 0;
        } else if (upperBits == 2) {
            //  Opposite vertex bit is edge bit rotated one to the right:
            vBits = ((eBits & 1) << 2) | (eBits >> 1);
        }

        bool edge0IsBoundary = (eBits & 1) != 0;
        bool edge1IsBoundary = (eBits & 2) != 0;
        bool edge2IsBoundary = (eBits & 4) != 0;

        //
        //  Adjust weights for the 4 boundary points and 3 interior points
        //  to account for the 3 phantom points adjacent to each
        //  boundary edge:
        //
        if (edge0IsBoundary) {
            OSD_REAL w0 = weights[0];
            if (edge2IsBoundary) {
                //  P0 = B1 + (B1 - I1)
                weights[4] += w0;
                weights[4] += w0;
                weights[8] -= w0;
            } else {
                //  P0 = B1 + (B0 - I0)
                weights[4] += w0;
                weights[3] += w0;
                weights[7] -= w0;
            }

            //  P1 = B1 + (B2 - I1)
            OSD_REAL w1 = weights[1];
            weights[4] += w1;
            weights[5] += w1;
            weights[8] -= w1;

            OSD_REAL w2 = weights[2];
            if (edge1IsBoundary) {
                //  P2 = B2 + (B2 - I1)
                weights[5] += w2;
                weights[5] += w2;
                weights[8] -= w2;
            } else {
                //  P2 = B2 + (B3 - I2)
                weights[5] += w2;
                weights[6] += w2;
                weights[9] -= w2;
            }
            //  Clear weights for the phantom points:
            weights[0] = weights[1] = weights[2] = 0.0f;
        }
        if (edge1IsBoundary) {
            OSD_REAL w0 = weights[6];
            if (edge0IsBoundary) {
                //  P0 = B1 + (B1 - I1)
                weights[5] += w0;
                weights[5] += w0;
                weights[4] -= w0;
            } else {
                //  P0 = B1 + (B0 - I0)
                weights[5] += w0;
                weights[2] += w0;
                weights[1] -= w0;
            }

            //  P1 = B1 + (B2 - I1)
            OSD_REAL w1 = weights[9];
            weights[5] += w1;
            weights[8] += w1;
            weights[4] -= w1;

            OSD_REAL w2 = weights[11];
            if (edge2IsBoundary) {
                //  P2 = B2 + (B2 - I1)
                weights[8] += w2;
                weights[8] += w2;
                weights[4] -= w2;
            } else {
                //  P2 = B2 + (B3 - I2)
                weights[8]  += w2;
                weights[10] += w2;
                weights[7]  -= w2;
            }
            //  Clear weights for the phantom points:
            weights[6] = weights[9] = weights[11] = 0.0f;
        }
        if (edge2IsBoundary) {
            OSD_REAL w0 = weights[10];
            if (edge1IsBoundary) {
                //  P0 = B1 + (B1 - I1)
                weights[8] += w0;
                weights[8] += w0;
                weights[5] -= w0;
            } else {
                //  P0 = B1 + (B0 - I0)
                weights[8]  += w0;
                weights[11] += w0;
                weights[9]  -= w0;
            }

            //  P1 = B1 + (B2 - I1)
            OSD_REAL w1 = weights[7];
            weights[8] += w1;
            weights[4] += w1;
            weights[5] -= w1;

            OSD_REAL w2 = weights[3];
            if (edge0IsBoundary) {
                //  P2 = B2 + (B2 - I1)
                weights[4] += w2;
                weights[4] += w2;
                weights[5] -= w2;
            } else {
                //  P2 = B2 + (B3 - I2)
                weights[4] += w2;
                weights[0] += w2;
                weights[1] -= w2;
            }
            //  Clear weights for the phantom points:
            weights[10] = weights[7] = weights[3] = 0.0f;
        }

        //
        //  Adjust weights for the 3 boundary points and the 2 interior
        //  points to account for the 2 phantom points adjacent to
        //  each boundary vertex:
        //
        if ((vBits & 1) != 0) {
            //  P0 = B1 + (B0 - I0)
            OSD_REAL w0 = weights[3];
            weights[4] += w0;
            weights[7] += w0;
            weights[8] -= w0;

            //  P1 = B1 + (B2 - I1)
            OSD_REAL w1 = weights[0];
            weights[4] += w1;
            weights[1] += w1;
            weights[5] -= w1;

            //  Clear weights for the phantom points:
            weights[3] = weights[0] = 0.0f;
        }
        if ((vBits & 2) != 0) {
            //  P0 = B1 + (B0 - I0)
            OSD_REAL w0 = weights[2];
            weights[5] += w0;
            weights[1] += w0;
            weights[4] -= w0;

            //  P1 = B1 + (B2 - I1)
            OSD_REAL w1 = weights[6];
            weights[5] += w1;
            weights[9] += w1;
            weights[8] -= w1;

            //  Clear weights for the phantom points:
            weights[2] = weights[6] = 0.0f;
        }
        if ((vBits & 4) != 0) {
            //  P0 = B1 + (B0 - I0)
            OSD_REAL w0 = weights[11];
            weights[8] += w0;
            weights[9] += w0;
            weights[5] -= w0;

            //  P1 = B1 + (B2 - I1)
            OSD_REAL w1 = weights[10];
            weights[8] += w1;
            weights[7] += w1;
            weights[4] -= w1;

            //  Clear weights for the phantom points:
            weights[11] = weights[10] = 0.0f;
        }
    }

    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_boundBasisBoxSplineTri(
            int boundary,
            OSD_INOUT_ARRAY(OSD_REAL, wP, 12),
            OSD_INOUT_ARRAY(OSD_REAL, wDs, 12),
            OSD_INOUT_ARRAY(OSD_REAL, wDt, 12),
            OSD_INOUT_ARRAY(OSD_REAL, wDss, 12),
            OSD_INOUT_ARRAY(OSD_REAL, wDst, 12),
            OSD_INOUT_ARRAY(OSD_REAL, wDtt, 12)) {

        if (OSD_OPTIONAL(wP)) {
            Osd_adjustBoxSplineTriBoundaryWeights(boundary, wP);
        }
        if (OSD_OPTIONAL(wDs && wDt)) {
            Osd_adjustBoxSplineTriBoundaryWeights(boundary, wDs);
            Osd_adjustBoxSplineTriBoundaryWeights(boundary, wDt);

            if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
                Osd_adjustBoxSplineTriBoundaryWeights(boundary, wDss);
                Osd_adjustBoxSplineTriBoundaryWeights(boundary, wDst);
                Osd_adjustBoxSplineTriBoundaryWeights(boundary, wDtt);
            }
        }
    }
// }  // namespace

OSD_FUNCTION_STORAGE_CLASS
// template <typename REAL>
int
Osd_EvalBasisBoxSplineTri(OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 12),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 12),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 12),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 12),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 12),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 12)) {

    OSD_REAL stMonomials[15];
    Osd_evalBivariateMonomialsQuartic(s, t, stMonomials);

    if (OSD_OPTIONAL(wP)) {
        Osd_evalBoxSplineTriDerivWeights(stMonomials, 0, 0, wP);
    }
    if (OSD_OPTIONAL(wDs && wDt)) {
        Osd_evalBoxSplineTriDerivWeights(stMonomials, 1, 0, wDs);
        Osd_evalBoxSplineTriDerivWeights(stMonomials, 0, 1, wDt);

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            Osd_evalBoxSplineTriDerivWeights(stMonomials, 2, 0, wDss);
            Osd_evalBoxSplineTriDerivWeights(stMonomials, 1, 1, wDst);
            Osd_evalBoxSplineTriDerivWeights(stMonomials, 0, 2, wDtt);
        }
    }
    return 12;
}


// namespace {
    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_evalBezierTriDerivWeights(
        OSD_REAL s, OSD_REAL t, int ds, int dt,
        OSD_OUT_ARRAY(OSD_REAL, wB, 15)) {

        OSD_REAL u  = s;
        OSD_REAL v  = t;
        OSD_REAL w  = 1 - u - v;

        OSD_REAL uu = u * u;
        OSD_REAL vv = v * v;
        OSD_REAL ww = w * w;

        OSD_REAL uv = u * v;
        OSD_REAL vw = v * w;
        OSD_REAL uw = u * w;

        int totalOrder = ds + dt;
        if (totalOrder == 0) {
            wB[0]  =      ww * ww;
            wB[1]  =  4 * uw * ww;
            wB[2]  =  6 * uw * uw;
            wB[3]  =  4 * uw * uu;
            wB[4]  =      uu * uu;
            wB[5]  =  4 * vw * ww;
            wB[6]  = 12 * ww * uv;
            wB[7]  = 12 * uu * vw;
            wB[8]  =  4 * uv * uu;
            wB[9]  =  6 * vw * vw;
            wB[10] = 12 * vv * uw;
            wB[11] =  6 * uv * uv;
            wB[12] =  4 * vw * vv;
            wB[13] =  4 * uv * vv;
            wB[14] =      vv * vv;
        } else if (totalOrder == 1) {
            if (ds == 1) {
                wB[0]  =  -4 * ww * w;
                wB[1]  =   4 * ww * (w - 3 * u);
                wB[2]  =  12 * uw * (w - u);
                wB[3]  =   4 * uu * (3 * w - u);
                wB[4]  =   4 * uu * u;
                wB[5]  = -12 * vw * w;
                wB[6]  =  12 * vw * (w - 2 * u);
                wB[7]  =  12 * uv * (2 * w - u);
                wB[8]  =  12 * uv * u;
                wB[9]  = -12 * vv * w;
                wB[10] =  12 * vv * (w - u);
                wB[11] =  12 * vv * u;
                wB[12] =  -4 * vv * v;
                wB[13] =   4 * vv * v;
                wB[14] =   0;
            } else {
                wB[0]  =  -4 * ww * w;
                wB[1]  = -12 * ww * u;
                wB[2]  = -12 * uu * w;
                wB[3]  =  -4 * uu * u;
                wB[4]  =   0;
                wB[5]  =   4 * ww * (w - 3 * v);
                wB[6]  =  12 * uw * (w - 2 * v);
                wB[7]  =  12 * uu * (w - v);
                wB[8]  =   4 * uu * u;
                wB[9]  =  12 * vw * (w - v);
                wB[10] =  12 * uv * (2 * w - v);
                wB[11] =  12 * uv * u;;
                wB[12] =   4 * vv * (3 * w - v);
                wB[13] =  12 * vv * u;
                wB[14] =   4 * vv * v;
            }
        } else if (totalOrder == 2) {
            if (ds == 2) {
                wB[0]  =  12 * ww;
                wB[1]  =  24 * (uw - ww);
                wB[2]  =  12 * (uu - 4 * uw + ww);
                wB[3]  =  24 * (uw - uu);
                wB[4]  =  12 * uu;
                wB[5]  =  24 * vw;
                wB[6]  =  24 * (uv - 2 * vw);
                wB[7]  =  24 * (vw - 2 * uv);
                wB[8]  =  24 * uv;
                wB[9]  =  12 * vv;
                wB[10] = -24 * vv;
                wB[11] =  12 * vv;
                wB[12] =   0;
                wB[13] =   0;
                wB[14] =   0;
            } else if (dt == 2) {
                wB[0]  =  12 * ww;
                wB[1]  =  24 * uw;
                wB[2]  =  12 * uu;
                wB[3]  =   0;
                wB[4]  =   0;
                wB[5]  =  24 * (vw - ww);
                wB[6]  =  24 * (uv - 2 * uw);
                wB[7]  = -24 * uu;
                wB[8]  =   0;
                wB[9]  =  12 * (vv - 4 * vw + ww);
                wB[10] =  24 * (uw - 2 * uv);
                wB[11] =  12 * uu;
                wB[12] =  24 * (vw - vv);
                wB[13] =  24 * uv;
                wB[14] =  12 * vv;
            } else {
                wB[0]  =  12 * ww;
                wB[3]  = -12 * uu;
                wB[13] =  12 * vv;
                wB[11] =  24 * uv;
                wB[1]  =  24 * uw - wB[0];
                wB[2]  = -24 * uw - wB[3];
                wB[5]  =  24 * vw - wB[0];
                wB[6]  = -24 * vw + wB[11] - wB[1];
                wB[8]  = - wB[3];
                wB[7]  = -(wB[11] + wB[2]);
                wB[9]  =   wB[13] - wB[5] - wB[0];
                wB[10] = -(wB[9] + wB[11]);
                wB[12] = - wB[13];
                wB[4]  =   0;
                wB[14] =   0;
            }
        } else {
            // assert(totalOrder <= 2);
        }
    }
// } // end namespace

OSD_FUNCTION_STORAGE_CLASS
// template <typename REAL>
int
Osd_EvalBasisBezierTri(OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 15),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 15),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 15),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 15),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 15),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 15)) {

    if (OSD_OPTIONAL(wP)) {
        Osd_evalBezierTriDerivWeights(s, t, 0, 0, wP);
    }
    if (OSD_OPTIONAL(wDs && wDt)) {
        Osd_evalBezierTriDerivWeights(s, t, 1, 0, wDs);
        Osd_evalBezierTriDerivWeights(s, t, 0, 1, wDt);

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            Osd_evalBezierTriDerivWeights(s, t, 2, 0, wDss);
            Osd_evalBezierTriDerivWeights(s, t, 1, 1, wDst);
            Osd_evalBezierTriDerivWeights(s, t, 0, 2, wDtt);
        }
    }
    return 15;
}


// namespace {
    //
    //  Expanding a set of 15 Bezier basis functions for the 6 (3 pairs) of
    //  rational weights for the 18 Gregory basis functions:
    //
    OSD_FUNCTION_STORAGE_CLASS
    // template <typename REAL>
    void
    Osd_convertBezierWeightsToGregory(
        OSD_INOUT_ARRAY(OSD_REAL, wB, 15),
        OSD_INOUT_ARRAY(OSD_REAL, rG,  6),
        OSD_OUT_ARRAY(OSD_REAL, wG, 18)) {

        wG[0]  = wB[0];
        wG[1]  = wB[1];
        wG[2]  = wB[5];
        wG[3]  = wB[6] * rG[0];
        wG[4]  = wB[6] * rG[1];

        wG[5]  = wB[4];
        wG[6]  = wB[8];
        wG[7]  = wB[3];
        wG[8]  = wB[7] * rG[2];
        wG[9]  = wB[7] * rG[3];

        wG[10] = wB[14];
        wG[11] = wB[12];
        wG[12] = wB[13];
        wG[13] = wB[10] * rG[4];
        wG[14] = wB[10] * rG[5];

        wG[15] = wB[2];
        wG[16] = wB[11];
        wG[17] = wB[9];
    }
// } // end namespace

OSD_FUNCTION_STORAGE_CLASS
// template <typename REAL>
int
Osd_EvalBasisGregoryTri(OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 18),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 18),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 18),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 18),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 18),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 18)) {

    //
    //  Bezier basis functions are denoted with B while the rational multipliers for the
    //  interior points will be denoted G -- so we have B(s,t) and G(s,t) (though we
    //  switch to barycentric (u,v,w) briefly to compute G)
    //
    OSD_REAL BP[15], BDs[15], BDt[15], BDss[15], BDst[15], BDtt[15];

    OSD_REAL G[6] = OSD_ARRAY_6(OSD_REAL, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f );
    OSD_REAL u = s;
    OSD_REAL v = t;
    OSD_REAL w = 1 - u - v;

    if ((u + v) > 0) {
        G[0]  = u / (u + v);
        G[1]  = v / (u + v);
    }
    if ((v + w) > 0) {
        G[2] = v / (v + w);
        G[3] = w / (v + w);
    }
    if ((w + u) > 0) {
        G[4] = w / (w + u);
        G[5] = u / (w + u);
    }

    //
    //  Compute Bezier basis functions and convert, adjusting interior points:
    //
    if (OSD_OPTIONAL(wP)) {
        Osd_evalBezierTriDerivWeights(s, t, 0, 0, BP);
        Osd_convertBezierWeightsToGregory(BP, G, wP);
    }
    if (OSD_OPTIONAL(wDs && wDt)) {
        //  TBD -- ifdef OPENSUBDIV_GREGORY_EVAL_TRUE_DERIVATIVES

        Osd_evalBezierTriDerivWeights(s, t, 1, 0, BDs);
        Osd_evalBezierTriDerivWeights(s, t, 0, 1, BDt);

        Osd_convertBezierWeightsToGregory(BDs, G, wDs);
        Osd_convertBezierWeightsToGregory(BDt, G, wDt);

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            Osd_evalBezierTriDerivWeights(s, t, 2, 0, BDss);
            Osd_evalBezierTriDerivWeights(s, t, 1, 1, BDst);
            Osd_evalBezierTriDerivWeights(s, t, 0, 2, BDtt);

            Osd_convertBezierWeightsToGregory(BDss, G, wDss);
            Osd_convertBezierWeightsToGregory(BDst, G, wDst);
            Osd_convertBezierWeightsToGregory(BDtt, G, wDtt);
        }
    }
    return 18;
}

// The following functions are low-level internal methods which
// were exposed in earlier releases, but were never intended to
// be part of the supported public API.

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdGetBezierWeights(
    OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 4),
    OSD_OUT_ARRAY(OSD_REAL, wDP, 4),
    OSD_OUT_ARRAY(OSD_REAL, wDP2, 4)) {

    Osd_evalBezierCurve(t, wP, wDP, wDP2);
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdGetBSplineWeights(
    OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 4),
    OSD_OUT_ARRAY(OSD_REAL, wDP, 4),
    OSD_OUT_ARRAY(OSD_REAL, wDP2, 4)) {

    Osd_evalBSplineCurve(t, wP, wDP, wDP2);
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdGetBoxSplineWeights(
    float s, float t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 12)) {

    OSD_REAL stMonomials[15];
    Osd_evalBivariateMonomialsQuartic(s, t, stMonomials);

    if (OSD_OPTIONAL(wP)) {
        Osd_evalBoxSplineTriDerivWeights(stMonomials, 0, 0, wP);
    }
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdAdjustBoundaryWeights(
        int boundary,
        OSD_INOUT_ARRAY(OSD_REAL, sWeights, 4),
        OSD_INOUT_ARRAY(OSD_REAL, tWeights, 4)) {

    if ((boundary & 1) != 0) {
        tWeights[2] -= tWeights[0];
        tWeights[1] += tWeights[0] * 2.0f;
        tWeights[0]  = 0.0f;
    }
    if ((boundary & 2) != 0) {
        sWeights[1] -= sWeights[3];
        sWeights[2] += sWeights[3] * 2.0f;
        sWeights[3]  = 0.0f;
    }
    if ((boundary & 4) != 0) {
        tWeights[1] -= tWeights[3];
        tWeights[2] += tWeights[3] * 2.0f;
        tWeights[3]  = 0.0f;
    }
    if ((boundary & 8) != 0) {
        sWeights[2] -= sWeights[0];
        sWeights[1] += sWeights[0] * 2.0f;
        sWeights[0]  = 0.0f;
    }
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdComputeTensorProductPatchWeights(
    float dScale, int boundary,
    OSD_IN_ARRAY(float, sWeights, 4),
    OSD_IN_ARRAY(float, tWeights, 4),
    OSD_IN_ARRAY(float, dsWeights, 4),
    OSD_IN_ARRAY(float, dtWeights, 4),
    OSD_IN_ARRAY(float, dssWeights, 4),
    OSD_IN_ARRAY(float, dttWeights, 4),
    OSD_OUT_ARRAY(float, wP, 16),
    OSD_OUT_ARRAY(float, wDs, 16),
    OSD_OUT_ARRAY(float, wDt, 16),
    OSD_OUT_ARRAY(float, wDss, 16),
    OSD_OUT_ARRAY(float, wDst, 16),
    OSD_OUT_ARRAY(float, wDtt, 16)) {

    if (OSD_OPTIONAL(wP)) {
        // Compute the tensor product weight of the (s,t) basis function
        // corresponding to each control vertex:

        OsdAdjustBoundaryWeights(boundary, sWeights, tWeights);

        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                wP[4*i+j] = sWeights[j] * tWeights[i];
            }
        }
    }

    if (OSD_OPTIONAL(wDs && wDt)) {
        // Compute the tensor product weight of the differentiated (s,t) basis
        // function corresponding to each control vertex (scaled accordingly):

        OsdAdjustBoundaryWeights(boundary, dsWeights, dtWeights);

        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                wDs[4*i+j] = dsWeights[j] * tWeights[i] * dScale;
                wDt[4*i+j] = sWeights[j] * dtWeights[i] * dScale;
            }
        }

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            // Compute the tensor product weight of appropriate differentiated
            // (s,t) basis functions for each control vertex (scaled accordingly):
            float d2Scale = dScale * dScale;

            OsdAdjustBoundaryWeights(boundary, dssWeights, dttWeights);

            for (int i = 0; i < 4; ++i) {
                for (int j = 0; j < 4; ++j) {
                    wDss[4*i+j] = dssWeights[j] * tWeights[i] * d2Scale;
                    wDst[4*i+j] = dsWeights[j] * dtWeights[i] * d2Scale;
                    wDtt[4*i+j] = sWeights[j] * dttWeights[i] * d2Scale;
                }
            }
        }
    }
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdGetBilinearPatchWeights(
        OSD_REAL s, OSD_REAL t, OSD_REAL d1Scale,
        OSD_OUT_ARRAY(OSD_REAL, wP, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDs, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDt, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDss, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDst, 4),
        OSD_OUT_ARRAY(OSD_REAL, wDtt, 4)) {

    int nPoints = Osd_EvalBasisLinear(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
    if (OSD_OPTIONAL(wDs && wDt)) {
        for (int i = 0; i < nPoints; ++i) {
            wDs[i] *= d1Scale;
            wDt[i] *= d1Scale;
        }
        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            OSD_REAL d2Scale = d1Scale * d1Scale;

            for (int i = 0; i < nPoints; ++i) {
                wDss[i] *= d2Scale;
                wDst[i] *= d2Scale;
                wDtt[i] *= d2Scale;
            }
        }
    }
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdGetBSplinePatchWeights(
    OSD_REAL s, OSD_REAL t, OSD_REAL d1Scale, int boundary,
    OSD_OUT_ARRAY(OSD_REAL, wP, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 16)) {

    int nPoints = Osd_EvalBasisBSpline(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
    Osd_boundBasisBSpline(boundary, wP, wDs, wDt, wDss, wDst, wDtt);
    if (OSD_OPTIONAL(wDs && wDt)) {
        for (int i = 0; i < nPoints; ++i) {
            wDs[i] *= d1Scale;
            wDt[i] *= d1Scale;
        }
        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            OSD_REAL d2Scale = d1Scale * d1Scale;

            for (int i = 0; i < nPoints; ++i) {
                wDss[i] *= d2Scale;
                wDst[i] *= d2Scale;
                wDtt[i] *= d2Scale;
            }
        }
    }
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdGetBezierPatchWeights(
    OSD_REAL s, OSD_REAL t, OSD_REAL d1Scale,
    OSD_OUT_ARRAY(OSD_REAL, wP, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 16),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 16)) {
    int nPoints = Osd_EvalBasisBezier(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
    if (OSD_OPTIONAL(wDs && wDt)) {
        for (int i = 0; i < nPoints; ++i) {
            wDs[i] *= d1Scale;
            wDt[i] *= d1Scale;
        }
        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            OSD_REAL d2Scale = d1Scale * d1Scale;

            for (int i = 0; i < nPoints; ++i) {
                wDss[i] *= d2Scale;
                wDst[i] *= d2Scale;
                wDtt[i] *= d2Scale;
            }
        }
    }
}

//  Deprecated -- prefer use of OsdEvaluatePatchBasis() and
//  OsdEvaluatePatchBasisNormalized() methods.
OSD_FUNCTION_STORAGE_CLASS
void
OsdGetGregoryPatchWeights(
    OSD_REAL s, OSD_REAL t, OSD_REAL d1Scale,
    OSD_OUT_ARRAY(OSD_REAL, wP, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 20)) {
    int nPoints = Osd_EvalBasisGregory(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
    if (OSD_OPTIONAL(wDs && wDt)) {
        for (int i = 0; i < nPoints; ++i) {
            wDs[i] *= d1Scale;
            wDt[i] *= d1Scale;
        }
        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            OSD_REAL d2Scale = d1Scale * d1Scale;

            for (int i = 0; i < nPoints; ++i) {
                wDss[i] *= d2Scale;
                wDst[i] *= d2Scale;
                wDtt[i] *= d2Scale;
            }
        }
    }
}

//
//  Higher level basis evaluation functions that deal with parameterization and
//  boundary issues (reflected in PatchParam) for all patch types:
//
OSD_FUNCTION_STORAGE_CLASS
// template <typename REAL>
int
OsdEvaluatePatchBasisNormalized(
    int patchType, OsdPatchParam param,
    OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 20)) {

    int boundaryMask = OsdPatchParamGetBoundary(param);

    int nPoints = 0;
    if (patchType == OSD_PATCH_DESCRIPTOR_REGULAR) {
#if OSD_ARRAY_ARG_BOUND_OPTIONAL
        nPoints = Osd_EvalBasisBSpline(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
        if (boundaryMask != 0) {
            Osd_boundBasisBSpline(
                boundaryMask, wP, wDs, wDt, wDss, wDst, wDtt);
        }
#else
        OSD_REAL wP16[16], wDs16[16], wDt16[16],
                 wDss16[16], wDst16[16], wDtt16[16];
        nPoints = Osd_EvalBasisBSpline(
                s, t, wP16, wDs16, wDt16, wDss16, wDst16, wDtt16);
        if (boundaryMask != 0) {
            Osd_boundBasisBSpline(
                boundaryMask, wP16, wDs16, wDt16, wDss16, wDst16, wDtt16);
        }
        for (int i=0; i<nPoints; ++i) {
            wP[i] = wP16[i];
            wDs[i] = wDs16[i]; wDt[i] = wDt16[i];
            wDss[i] = wDss16[i]; wDst[i] = wDst16[i]; wDtt[i] = wDtt16[i];
        }
#endif
    } else if (patchType == OSD_PATCH_DESCRIPTOR_LOOP) {
#if OSD_ARRAY_ARG_BOUND_OPTIONAL
        nPoints = Osd_EvalBasisBoxSplineTri(
                s, t, wP, wDs, wDt, wDss, wDst, wDtt);
        if (boundaryMask != 0) {
            Osd_boundBasisBoxSplineTri(
                boundaryMask, wP, wDs, wDt, wDss, wDst, wDtt);
        }
#else
        OSD_REAL wP12[12], wDs12[12], wDt12[12],
                 wDss12[12], wDst12[12], wDtt12[12];
        nPoints = Osd_EvalBasisBoxSplineTri(
                s, t, wP12, wDs12, wDt12, wDss12, wDst12, wDtt12);
        if (boundaryMask != 0) {
            Osd_boundBasisBoxSplineTri(
                boundaryMask, wP12, wDs12, wDt12, wDss12, wDst12, wDtt12);
        }
        for (int i=0; i<nPoints; ++i) {
            wP[i] = wP12[i];
            wDs[i] = wDs12[i]; wDt[i] = wDt12[i];
            wDss[i] = wDss12[i]; wDst[i] = wDst12[i]; wDtt[i] = wDtt12[i];
        }
#endif
    } else if (patchType == OSD_PATCH_DESCRIPTOR_GREGORY_BASIS) {
        nPoints = Osd_EvalBasisGregory(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
    } else if (patchType == OSD_PATCH_DESCRIPTOR_GREGORY_TRIANGLE) {
#if OSD_ARRAY_ARG_BOUND_OPTIONAL
        nPoints = Osd_EvalBasisGregoryTri(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
#else
        OSD_REAL wP18[18], wDs18[18], wDt18[18],
                 wDss18[18], wDst18[18], wDtt18[18];
        nPoints = Osd_EvalBasisGregoryTri(
                s, t, wP18, wDs18, wDt18, wDss18, wDst18, wDtt18);
        for (int i=0; i<nPoints; ++i) {
            wP[i] = wP18[i];
            wDs[i] = wDs18[i]; wDt[i] = wDt18[i];
            wDss[i] = wDss18[i]; wDst[i] = wDst18[i]; wDtt[i] = wDtt18[i];
        }
#endif
    } else if (patchType == OSD_PATCH_DESCRIPTOR_QUADS) {
#if OSD_ARRAY_ARG_BOUND_OPTIONAL
        nPoints = Osd_EvalBasisLinear(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
#else
        OSD_REAL wP4[4], wDs4[4], wDt4[4],
                 wDss4[4], wDst4[4], wDtt4[4];
        nPoints = Osd_EvalBasisLinear(
                s, t, wP4, wDs4, wDt4, wDss4, wDst4, wDtt4);
        for (int i=0; i<nPoints; ++i) {
            wP[i] = wP4[i];
            wDs[i] = wDs4[i]; wDt[i] = wDt4[i];
            wDss[i] = wDss4[i]; wDst[i] = wDst4[i]; wDtt[i] = wDtt4[i];
        }
#endif
    } else if (patchType == OSD_PATCH_DESCRIPTOR_TRIANGLES) {
#if OSD_ARRAY_ARG_BOUND_OPTIONAL
        nPoints = Osd_EvalBasisLinearTri(s, t, wP, wDs, wDt, wDss, wDst, wDtt);
#else
        OSD_REAL wP3[3], wDs3[3], wDt3[3],
                 wDss3[3], wDst3[3], wDtt3[3];
        nPoints = Osd_EvalBasisLinearTri(
                s, t, wP3, wDs3, wDt3, wDss3, wDst3, wDtt3);
        for (int i=0; i<nPoints; ++i) {
            wP[i] = wP3[i];
            wDs[i] = wDs3[i]; wDt[i] = wDt3[i];
            wDss[i] = wDss3[i]; wDst[i] = wDst3[i]; wDtt[i] = wDtt3[i];
        }
#endif
    } else {
        // assert(0);
    }
    return nPoints;
}

OSD_FUNCTION_STORAGE_CLASS
// template <typename REAL>
int
OsdEvaluatePatchBasis(
    int patchType, OsdPatchParam param,
    OSD_REAL s, OSD_REAL t,
    OSD_OUT_ARRAY(OSD_REAL, wP, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDs, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDt, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDss, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDst, 20),
    OSD_OUT_ARRAY(OSD_REAL, wDtt, 20)) {

    OSD_REAL derivSign = 1.0f;

    if ((patchType == OSD_PATCH_DESCRIPTOR_LOOP) ||
        (patchType == OSD_PATCH_DESCRIPTOR_GREGORY_TRIANGLE) ||
        (patchType == OSD_PATCH_DESCRIPTOR_TRIANGLES)) {
        OSD_REAL uv[2] = OSD_ARRAY_2(OSD_REAL, s, t);
        OsdPatchParamNormalizeTriangle(param, uv);
        s = uv[0];
        t = uv[1];
        if (OsdPatchParamIsTriangleRotated(param)) {
            derivSign = -1.0f;
        }
    } else {
        OSD_REAL uv[2] = OSD_ARRAY_2(OSD_REAL, s, t);
        OsdPatchParamNormalize(param, uv);
        s = uv[0];
        t = uv[1];
    }

    int nPoints = OsdEvaluatePatchBasisNormalized(
        patchType, param, s, t, wP, wDs, wDt, wDss, wDst, wDtt);

    if (OSD_OPTIONAL(wDs && wDt)) {
        OSD_REAL d1Scale =
                derivSign * OSD_REAL_CAST(1 << OsdPatchParamGetDepth(param));

        for (int i = 0; i < nPoints; ++i) {
            wDs[i] *= d1Scale;
            wDt[i] *= d1Scale;
        }

        if (OSD_OPTIONAL(wDss && wDst && wDtt)) {
            OSD_REAL d2Scale = derivSign * d1Scale * d1Scale;

            for (int i = 0; i < nPoints; ++i) {
                wDss[i] *= d2Scale;
                wDst[i] *= d2Scale;
                wDtt[i] *= d2Scale;
            }
        }
    }
    return nPoints;
}

#if !defined(OSD_PATCH_BASIS_GLSL) && \
    !defined(OSD_PATCH_BASIS_HLSL) && \
    !defined(OSD_PATCH_BASIS_CUDA) && \
    !defined(OSD_PATCH_BASIS_OPENCL) && \
    !defined(OSD_PATCH_BASIS_METAL)

}  // end namespace Osd

}  // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;

}  // end namespace OpenSubdiv

#endif

#endif /* OPENSUBDIV3_OSD_PATCH_BASIS_H */
