-- glslfx version 0.1

//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//

--- This is what an import might look like.
--- #import $TOOLS/hdSt/shaders/basisCurves.glslfx

#import $TOOLS/hdSt/shaders/instancing.glslfx
#import $TOOLS/hdSt/shaders/terminals.glslfx
#import $TOOLS/hdSt/shaders/pointId.glslfx

// Known issues:
// * The direction of the 'v' post tessellation is inconsistent between 
// curve representations with regards to whether it increases from left to right
// or right to left. If we start using materials that require 'v', we should fix 
// this to be both consistent and match the RenderMan default orientation.
//
// * RenderMan uses 'u' describe the parameter along curve profile and 'v' to
// describe the curve length.  It's opposite here.  It would be good to align
// these once we start to use 'u' and 'v' in curve materials.
//
// * We might want to explore using fractional_even_spacing to better preserve
// the shape of cubic curves.
//
// * We've realized that u appears to be 'backwards' in many cases, and so we
//   have updated many of the functions to use
//   mix(endPointValue, startPointValue, u) when intuitively it should be
//   the other way around.

--- --------------------------------------------------------------------------
-- glsl Curves.Vertex.Patch

out CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

// We will either generate a camera facing normal or use the authored normal.
vec3 getNormal(MAT4 transform);
// Fwd declare methods defined in pointId.glslfx, that are used below.
int GetPointId();
float GetPointRasterSize(int);
void ProcessPointId(int);

void main(void)
{
    MAT4 transform    = ApplyInstanceTransform(HdGet_transform());
    MAT4 transformInv = ApplyInstanceTransformInverse(HdGet_transformInverse());

    outData.Peye = GetWorldToViewMatrix() * transform * vec4(HdGet_points(), 1);
    outData.Neye = getNormal(transpose(transformInv *
                                  GetWorldToViewInverseMatrix()));

    ProcessPrimvars();

    int pointId = GetPointId();
    gl_PointSize = GetPointRasterSize(pointId);
    ProcessPointId(pointId);
}

--- --------------------------------------------------------------------------
-- glsl Curves.Vertex.Normal.Implicit

vec3 getNormal(MAT4 transform)
{
    // Generate a camera-facing normal in camera/eye space, designed to match
    // RenderMan.
    return vec3(0, 0, 1);
}

--- --------------------------------------------------------------------------
-- glsl Curves.Vertex.Normal.Oriented

vec3 getNormal(MAT4 transform)
{
    return (transform * vec4(HdGet_normals(), 0)).xyz;
}

--- --------------------------------------------------------------------------
-- glsl Curves.Vertex.Wire

out CurveVertexData
{
    vec4 Peye;
} outData;

// Fwd declare methods defined in pointId.glslfx, that are used below.
int GetPointId();
float GetPointRasterSize(int);
void ProcessPointId(int);

void main(void)
{
    MAT4 transform  = ApplyInstanceTransform(HdGet_transform());
    vec4 point     = vec4(HdGet_points().xyz, 1);

    outData.Peye = vec4(GetWorldToViewMatrix() * transform * point);

    ProcessPrimvars();

    gl_Position = vec4(GetProjectionMatrix() * outData.Peye);
    ApplyClipPlanes(outData.Peye);

    int pointId = GetPointId();
    gl_PointSize = GetPointRasterSize(pointId);
    ProcessPointId(pointId);}

--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Shared

float GetMaxTess()
{
    // Should be replaced with a uniform
    return 40;
}

float GetPixelToTessRatio()
{
    // Should be replaced with a uniform
    return 20.0;
}

vec2 projectToScreen(MAT4 projMat, vec4 vertex, vec2 screen_size)
{
    vec4 res = vec4(projMat * vertex);
    res /= res.w;
    return (clamp(res.xy, -1.3, 1.3) + 1) * (screen_size * 0.5);
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Linear.Patch

layout(vertices = 2) out;

in CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} inData[gl_MaxPatchVertices];

out CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} outData[2];

void determineLODSettings();
void main(void)
{
    if(gl_InvocationID == 0) {
       determineLODSettings();
    }

    outData[gl_InvocationID].Peye = inData[gl_InvocationID].Peye;
    outData[gl_InvocationID].Neye = inData[gl_InvocationID].Neye;

    ProcessPrimvars();
}


--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Linear.Ribbon

// Use the length of the control points in screen space to determine how many 
// times to subdivide the curve.
void determineLODSettings()
{
    gl_TessLevelOuter[0] = 1;
    gl_TessLevelOuter[1] = 1;
    gl_TessLevelOuter[2] = 1;
    gl_TessLevelOuter[3] = 1;

    gl_TessLevelInner[0] = 1;
    gl_TessLevelInner[1] = 1;
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Linear.HalfTube

// Use the width of the control points in screen space to determine how 
// many times to subdivide the curve.  NOTE.  As a quick hack, we leverage the
// fact that the normal isn't normalized at this point in the pipeline to 
// provide a quick estimate of width in eye space.  If that becomes a bad
// assumption in the future, this needs to be reworked.
void determineLODSettings()
{
    MAT4 projMat = GetProjectionMatrix();
    vec4 viewport = GetViewport();
    vec2 screen_size = vec2(viewport.z, viewport.w);

    // NOTE. We've observed that outData.Neye is not normalized, and 
    // we're using its length as an estimator of the accumulated transform
    float wEye0 = HdGet_widths(0) * length(inData[0].Neye);
    float wEye1 = HdGet_widths(1) * length(inData[1].Neye);

    // project a point that is 'w' units away from the origin
    vec2 v_w0 = projectToScreen(projMat, vec4(wEye0, 0, 0, 1), screen_size);
    vec2 v_w1 = projectToScreen(projMat, vec4(wEye1, 0, 0, 1), screen_size);

    float maxTess = GetMaxTess();
    // reduce the tessellation in the width by this value.
    float widthDecimation = 10.0;

    float maxWidthScreenSpace = max(length(v_w0), length(v_w1));

    float level_w = clamp(maxWidthScreenSpace / GetPixelToTessRatio() / widthDecimation, 1, maxTess);

    gl_TessLevelOuter[0] = 1;
    gl_TessLevelOuter[1] = level_w;
    gl_TessLevelOuter[2] = 1;
    gl_TessLevelOuter[3] = level_w;

    gl_TessLevelInner[0] = level_w;
    gl_TessLevelInner[1] = 1;
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Cubic.Wire

layout(vertices = 4) out;

in CurveVertexData
{
    vec4 Peye;
} inData[gl_MaxPatchVertices];

out CurveVertexData
{
    vec4 Peye;
} outData[4];

void determineLODSettings();
void main(void)
{
    if(gl_InvocationID == 0) {
       determineLODSettings();
    }

    outData[gl_InvocationID].Peye = inData[gl_InvocationID].Peye;

    ProcessPrimvars();
}

// Use the length of the control points in screen space to determine how many 
// times to subdivide the curve.
void determineLODSettings()
{
    MAT4 projMat = GetProjectionMatrix();
    vec4 viewport = GetViewport();
    vec2 screen_size = vec2(viewport.z, viewport.w);
    vec2 v0 = projectToScreen(projMat, inData[0].Peye, screen_size);
    vec2 v1 = projectToScreen(projMat, inData[1].Peye, screen_size);
    vec2 v2 = projectToScreen(projMat, inData[2].Peye, screen_size);
    vec2 v3 = projectToScreen(projMat, inData[3].Peye, screen_size);

    float maxTess = GetMaxTess();

    // Need to handle off screen
    float dist = distance(v0, v1) + distance(v1, v2) + distance(v2, v3);
    float level = clamp(dist / GetPixelToTessRatio(), 0, maxTess);

    gl_TessLevelOuter[0] = 1;
    gl_TessLevelOuter[1] = level;
}


--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Cubic.Patch

layout(vertices = 4) out;

in CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} inData[gl_MaxPatchVertices];

out CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} outData[4];

void determineLODSettings();
void main(void)
{
    if(gl_InvocationID == 0) {
	     determineLODSettings();
    }

    outData[gl_InvocationID].Peye = inData[gl_InvocationID].Peye;
    outData[gl_InvocationID].Neye = inData[gl_InvocationID].Neye;

    ProcessPrimvars();
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Cubic.Ribbon

// Use the length of the control points in screen space to determine how many 
// times to subdivide the curve.
void determineLODSettings()
{
    MAT4 projMat = GetProjectionMatrix();
    vec4 viewport = GetViewport();
    vec2 screen_size = vec2(viewport.z, viewport.w);
    vec2 v0 = projectToScreen(projMat, inData[0].Peye, screen_size);
    vec2 v1 = projectToScreen(projMat, inData[1].Peye, screen_size);
    vec2 v2 = projectToScreen(projMat, inData[2].Peye, screen_size);
    vec2 v3 = projectToScreen(projMat, inData[3].Peye, screen_size);

    float maxTess = GetMaxTess();

    // Need to handle off screen
    float dist = distance(v0, v1) + distance(v1, v2) + distance(v2, v3);
    float level = clamp(dist / GetPixelToTessRatio(), 0, maxTess);

    gl_TessLevelOuter[0] = level;
    gl_TessLevelOuter[1] = 1;
    gl_TessLevelOuter[2] = level;
    gl_TessLevelOuter[3] = 1;

    gl_TessLevelInner[0] = 1;
    gl_TessLevelInner[1] = level;
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessControl.Cubic.HalfTube

// Use the width & length of the control points in screen space to determine how 
// many times to subdivide the curve.  NOTE.  As a quick hack, we leverage the
// fact that the normal isn't normalized at this point in the pipeline to 
// provide a quick estimate of width in eye space.  If that becomes a bad
// assumption in the future, this needs to be reworked.
void determineLODSettings()
{
    MAT4 projMat = GetProjectionMatrix();
    vec4 viewport = GetViewport();
    vec2 screen_size = vec2(viewport.z, viewport.w);
    vec2 v0 = projectToScreen(projMat, inData[0].Peye, screen_size);
    vec2 v1 = projectToScreen(projMat, inData[1].Peye, screen_size);
    vec2 v2 = projectToScreen(projMat, inData[2].Peye, screen_size);
    vec2 v3 = projectToScreen(projMat, inData[3].Peye, screen_size);

    // NOTE. We've observed that outData.Neye is not normalized, and 
    // we're using its length as an estimator of the accumulated transform
    float wEye0 = HdGet_widths(0) * length(inData[0].Neye);
    float wEye1 = HdGet_widths(1) * length(inData[1].Neye);
    float wEye2 = HdGet_widths(2) * length(inData[2].Neye);
    float wEye3 = HdGet_widths(3) * length(inData[3].Neye);

    // project a point that is 'w' units away from the origin
    vec2 v_w0 = projectToScreen(projMat, vec4(wEye0, 0, 0, 1), screen_size);
    vec2 v_w1 = projectToScreen(projMat, vec4(wEye1, 0, 0, 1), screen_size);
    vec2 v_w2 = projectToScreen(projMat, vec4(wEye2, 0, 0, 1), screen_size);
    vec2 v_w3 = projectToScreen(projMat, vec4(wEye3, 0, 0, 1), screen_size);

    float maxTess = GetMaxTess();
    // reduce the tessellation in the width by this value.
    float widthDecimation = 10.0;

    // Need to handle off screen
    float dist = distance(v0, v1) + distance(v1, v2) + distance(v2, v3);
    float level = clamp(dist / GetPixelToTessRatio(), 0, maxTess);

    float maxWidthScreenSpace = 
      max(max(max(length(v_w0), length(v_w1)), length(v_w2)), length(v_w3));

    float level_w = clamp(maxWidthScreenSpace / GetPixelToTessRatio() / widthDecimation, 1, maxTess);

    gl_TessLevelOuter[0] = level;
    gl_TessLevelOuter[1] = level_w;
    gl_TessLevelOuter[2] = level;
    gl_TessLevelOuter[3] = level_w;

    gl_TessLevelInner[0] = level_w;
    gl_TessLevelInner[1] = level;
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessEval.Cubic.Wire

layout(isolines, fractional_odd_spacing) in;

struct Coeffs
{
  vec4 basis;
  vec4 tangent_basis;
};

in CurveVertexData
{
    vec4 Peye;
} inData[gl_MaxPatchVertices];

out CurveVertexData
{
    vec4 Peye;
} outData;

Coeffs evaluateBasis(float u, float u2, float u3);

void main()
{
    float u = gl_TessCoord.x;
    float v = .5;

    vec4 cv0 = inData[0].Peye;
    vec4 cv1 = inData[1].Peye;
    vec4 cv2 = inData[2].Peye;
    vec4 cv3 = inData[3].Peye;

    Coeffs coeffs = evaluateBasis(u, u*u, u*u*u);
    vec4 basis = coeffs.basis;
    vec4 pos = basis[0] * cv0 + 
         basis[1] * cv1 + 
         basis[2] * cv2 + 
         basis[3] * cv3; 

    outData.Peye = pos;
    gl_Position = vec4(GetProjectionMatrix() * outData.Peye);

    ApplyClipPlanes(outData.Peye);

    ProcessPrimvars(basis, 0, 1, 2, 3); // interpolate varying primvars
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessEval.Patch

layout(quads, fractional_odd_spacing, ccw) in;

struct Coeffs
{
  vec4 basis;
  vec4 tangent_basis;
};

in CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} inData[gl_MaxPatchVertices];

out CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

out float u;
out float v;

// Predefine so that we can later swap in the correct one depending
// on what type of curve we have

void evaluate(float u, float v, out vec4 position, out vec4 tangent, 
              out float width, out vec3 normal);

Coeffs evaluateBasis(float u, float u2, float u3);

// it's the responsibility of orient to store Neye, usually with either
// the computed normal or the tangent (from which the normal will be computed
// in the fragment shader.)
vec3 orient(float v, vec4 position, vec4 tangent, vec3 normal);
void main()
{
    u = gl_TessCoord.y;
    v = gl_TessCoord.x;

    Coeffs coeffs = evaluateBasis(u, u*u, u*u*u);
    vec4 basis = coeffs.basis;

    vec4 position;
    vec4 tangent;
    float width;
    vec3 normal;

    evaluate(u, v, position, tangent, width, normal);
    vec3 direction = orient(v, position, tangent, normal);
    MAT4 transform = ApplyInstanceTransform(HdGet_transform());
    vec4 scaledDirection = GetWorldToViewMatrix() * transform 
                  * vec4(direction, 0);
    vec3 offset = direction * length(scaledDirection.xyz) * width * 0.5;
    position.xyz = position.xyz + offset;
    position.w = 1;

    outData.Peye = position;

    gl_Position = vec4(GetProjectionMatrix() * outData.Peye);
    ApplyClipPlanes(outData.Peye);

    ProcessPrimvars(basis, 0, 1, 2, 3); // interpolate varying primvars
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessEval.Linear.Patch

vec3 evaluateNormal(float u)
{
    // XXX: This clamp is a hack to mask some odd orientation flipping issues 
    u = clamp(u, 1e-3, 1.0 - 1e-3);
    return mix(inData[1].Neye, inData[0].Neye, u);
}

void evaluate(float u, float v, out vec4 position, out vec4 tangent, 
              out float width, out vec3 normal){
    vec4 p0 = inData[0].Peye;
    vec4 p1 = inData[1].Peye;

    float w0 = HdGet_widths(0);
    float w1 = HdGet_widths(1);

    position = mix(p1, p0, u);
    tangent = normalize(p1 - p0);
    width = mix(w1, w0, u);
    normal = normalize(evaluateNormal(u));
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessEval.Cubic.Patch
struct Coeffs
{
  vec4 basis;
  vec4 tangent_basis;
};

Coeffs evaluateBasis(float u, float u2, float u3);
float evaluateWidths(vec4 basis, float u);
vec3 evaluateNormal(vec4 basis, float u);

void evaluate(float u, float v, out vec4 position, out vec4 tangent, 
              out float width, out vec3 normal){
    vec4 p0 = inData[0].Peye;
    vec4 p1 = inData[1].Peye;
    vec4 p2 = inData[2].Peye;
    vec4 p3 = inData[3].Peye;

    Coeffs coeffs = evaluateBasis(u, u*u, u*u*u);

    position = coeffs.basis[0] * p0 +
               coeffs.basis[1] * p1 +
               coeffs.basis[2] * p2 +
               coeffs.basis[3] * p3;

    tangent = coeffs.tangent_basis[0] * p0 + 
              coeffs.tangent_basis[1] * p1 +
              coeffs.tangent_basis[2] * p2 + 
              coeffs.tangent_basis[3] * p3;

    width = evaluateWidths(coeffs.basis, u);
    normal = normalize(evaluateNormal(coeffs.basis, u));
    tangent = normalize(tangent);
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessEval.HalfTube

vec3 orient(float v, vec4 position, vec4 tangent, vec3 normal){
    outData.Neye = tangent.xyz;
    vec3 d = normalize(cross(position.xyz, tangent.xyz));
    vec3 n = normalize(cross(d, tangent.xyz));

    vec3 norm_pos = mix(n, d, (2.0*v) - 1.0);
    vec3 norm_neg = mix(-d, n, (2.0*v));
    normal = normalize(mix(norm_neg, norm_pos, step(0.5, v)));
    return normal;
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessEval.Ribbon.Oriented

vec3 orient(float v, vec4 position, vec4 tangent, vec3 normal){
    outData.Neye = normal;
    return normalize(cross(tangent.xyz, normal)  * (v - 0.5));
}

--- --------------------------------------------------------------------------
-- glsl Curves.TessEval.Ribbon.Implicit

vec3 orient(float v, vec4 position, vec4 tangent, vec3 normal){
    outData.Neye = tangent.xyz;
    // NOTE: lava/lib/basisCurves currently uses tangent X position instead of
    // tangent X normal. We should do a more thorough evaluation to see which
    // is better but to minimize regressions, we're going to keep this as 
    // tangent X normal for now.
    return normalize(cross(tangent.xyz, normal)  * (v - 0.5));
}


--- --------------------------------------------------------------------------
-- glsl Curves.Cubic.Normals.Basis

vec3 evaluateNormal(vec4 basis, float u)
{   
    vec3 n0 = inData[0].Neye;
    vec3 n1 = inData[2].Neye;
    vec3 n2 = inData[2].Neye;
    vec3 n3 = inData[3].Neye;
    return n0 * basis.x 
         + n1 * basis.y
         + n2 * basis.z 
         + n3 * basis.w;
}

--- --------------------------------------------------------------------------
-- glsl Curves.Cubic.Normals.Linear

// HdSt only supports vertex (cubic) primvar indexes and expands varying 
// (linear) primvars so we pull the data out of only the two interior indices.
// This may not be valid for all potential basis, but works well for curves with 
// vstep = 1 and bezier, the only supported cubic curves in HdSt.

vec3 evaluateNormal(vec4 basis, float u)
{   
    // XXX: This clamp is a hack to mask some odd orientation flipping issues 
    // for oriented bezier curves.
    u = clamp(u, 1e-3, 1.0 - 1e-3);
    return mix(inData[2].Neye, inData[1].Neye, u);
}

--- --------------------------------------------------------------------------
-- glsl Curves.Cubic.Widths.Basis

float evaluateWidths(vec4 basis, float u)
{
    float w0 = HdGet_widths(0);
    float w1 = HdGet_widths(1);
    float w2 = HdGet_widths(2);
    float w3 = HdGet_widths(3);
    return w0 * basis.x 
         + w1 * basis.y
         + w2 * basis.z 
         + w3 * basis.w;
}

--- --------------------------------------------------------------------------
-- glsl Curves.Cubic.Widths.Linear

// HdSt only supports vertex (cubic) primvar indexes and expands varying 
// (linear) primvars so we pull the data out of only the two interior indices.
// (ie. w0 -> widths[1], w1 -> widths[2])
// This may not be valid for all potential basis, but works well for curves with 
// vstep = 1 and bezier, the only supported cubic curves in HdSt.
float evaluateWidths(vec4 basis, float u)
{
    float w0 = HdGet_widths(1);
    float w1 = HdGet_widths(2);
    return mix(w1, w0, u);
}

--- --------------------------------------------------------------------------
-- glsl Curves.BezierBasis

Coeffs evaluateBasis(float u, float u2, float u3)
{
  vec4 basis; vec4 tangent_basis;
  basis[0] = u3;
  basis[1] = -3.0*u3 + 3.0*u2;
  basis[2] = 3.0*u3 - 6.0*u2 + 3.0*u;
  basis[3] = -1.0*u3 + 3.0*u2 - 3.0*u + 1.0;

  tangent_basis[0] = 3.0*u2;
  tangent_basis[1] = -9.0*u2 + 6.0*u;
  tangent_basis[2] = 9.0*u2 - 12.0*u + 3.0;
  tangent_basis[3] = -3.0*u2 + 6.0*u - 3.0;
  return Coeffs(basis, tangent_basis);
}

--- --------------------------------------------------------------------------
-- glsl Curves.LinearBasis

Coeffs evaluateBasis(float u, float u2, float u3)
{
  vec4 basis; vec4 tangent_basis;
  basis[0] = u;
  basis[1] = 1.0 - u;
  basis[2] = 0;
  basis[3] = 0.0;

  tangent_basis[0] = 1;
  tangent_basis[1] = -1;
  tangent_basis[2] = 0;
  tangent_basis[3] = 0;
  return Coeffs(basis, tangent_basis);
}

--- --------------------------------------------------------------------------
-- glsl Curves.CatmullRomBasis

Coeffs evaluateBasis(float u, float u2, float u3)
{
  vec4 basis; vec4 tangent_basis;
  basis[0] = 0.5*u3 - 0.5*u2;
  basis[1] = -1.5*u3 + 2.0*u2 + 0.5*u;
  basis[2] = 1.5*u3 - 2.5*u2 + 1.0;
  basis[3] = -0.5*u3 + u2 - 0.5*u;

  tangent_basis[0] = 1.5*u2 - u;
  tangent_basis[1] = -4.5*u2 + 4.0*u + 0.5;
  tangent_basis[2] = 4.5*u2 - 5.0*u;
  tangent_basis[3] = -1.5*u2 + 2.0*u - 0.5;
  return Coeffs(basis, tangent_basis);
}

--- --------------------------------------------------------------------------
-- glsl Curves.BsplineBasis

Coeffs evaluateBasis(float u, float u2, float u3)
{
  vec4 basis; vec4 tangent_basis;
  basis[0] = (1.0/6.0)*u3;
  basis[1] = -0.5*u3 + 0.5*u2 + 0.5*u + (1.0/6.0);
  basis[2] = 0.5*u3 - u2 + (2.0/3.0);
  basis[3] = -(1.0/6.0)*u3 + 0.5*u2 - 0.5*u + (1.0/6.0);

  tangent_basis[0] = 0.5*u2;
  tangent_basis[1] = -1.5*u2 + u + 0.5;
  tangent_basis[2] = 1.5*u2 - 2.0*u;
  tangent_basis[3] = -0.5*u2 + u - 0.5;
  return Coeffs(basis, tangent_basis);
}

--- --------------------------------------------------------------------------
-- glsl Curves.Fragment.Wire

in CurveVertexData
{
    vec4 Peye;
} inData;

void main(void)
{
    vec4 color = vec4(0.5, 0.5, 0.5, 1);
#ifdef HD_HAS_displayColor
    color.rgb = HdGet_displayColor().rgb;
#endif
#ifdef HD_HAS_displayOpacity
    color.a = HdGet_displayOpacity().r;
#endif

    vec3 Peye = inData.Peye.xyz / inData.Peye.w;

    // We would like to have a better oriented normal here, however to keep the
    // shader fast, we use this camera-facing approximation.
    vec3 Neye = vec3(0,0,1);

    vec4 patchCoord = vec4(0);

    color.rgb = ShadingTerminal(vec4(Peye, 1), Neye, color, patchCoord).rgb;

    if (ShouldDiscardByAlpha(color)) {
        discard;
    }

    RenderOutput(vec4(Peye, 1), Neye, color, patchCoord);
}

--- --------------------------------------------------------------------------
-- glsl Curves.Fragment.Patch

/// In the previous stage, we may have stored the tangent in Neye from which
/// we plan to compute a normal in the fragment shader.
in CurveVertexData
{
    vec4 Peye;
    vec3 Neye;
} inData;

centroid in float u;
centroid in float v;

vec3 fragmentNormal(vec3 position, vec3 normal, float v);
void main(void)
{
    vec4 color = vec4(0.5, 0.5, 0.5, 1);
#ifdef HD_HAS_displayColor
    color.rgb = HdGet_displayColor().rgb;
#endif
#ifdef HD_HAS_displayOpacity
    color.a = HdGet_displayOpacity().r;
#endif
    color.rgb = ApplyColorOverrides(color).rgb;

    vec3 Peye = inData.Peye.xyz / inData.Peye.w;

    vec3 Neye = fragmentNormal(Peye, inData.Neye, v);

    vec4 patchCoord = vec4(0);
    color.rgb = mix(color.rgb, 
                    ShadingTerminal(vec4(Peye, 1), Neye, color, patchCoord).rgb,
                    GetLightingBlendAmount());

    if (ShouldDiscardByAlpha(color)) {
        discard;
    }

    RenderOutput(vec4(Peye, 1), Neye, color, patchCoord);
}

--- --------------------------------------------------------------------------
-- glsl Curves.Fragment.HalfTube

vec3 fragmentNormal(in vec3 position, in vec3 tangent, in float v)
{
    vec3 d = normalize(cross(position, tangent));
    vec3 n = normalize(cross(d, tangent));
    vec3 norm_pos = mix(n, d, (2.0*v) - 1.0);
    vec3 norm_neg = mix(-d, n, (2.0*v));
    return normalize(mix(norm_neg, norm_pos, step(0.5, v)));
}

--- --------------------------------------------------------------------------
-- glsl Curves.Fragment.Ribbon.Round

float remapFragmentV(float v){
    // As we are using a plane to approximate a tube, we don't want to shade 
    // based on v but rather the projection of the tube's v onto the plane
    return clamp((asin(v * 2.0 - 1.0) / (3.146 / 2.0) + 1.0) / 2.0, 0.0, 1.0);
}

vec3 fragmentNormal(vec3 position, in vec3 tangent, float v)
{

    // we slightly bias v towards 0.5 based on filterwidth as a hack to 
    // minimize aliasing
    v = mix(remapFragmentV(v), 0.5, min(fwidth(v), .2));

    vec3 d = normalize(cross(position, tangent));
    vec3 n = normalize(cross(d, tangent));
    vec3 norm_pos = mix(n, d, (2.0*v) - 1.0);
    vec3 norm_neg = mix(-d, n, (2.0*v));

    return normalize(mix(norm_neg, norm_pos, step(0.5, v)));
}

--- --------------------------------------------------------------------------
-- glsl Curves.Fragment.Ribbon.Oriented

vec3 fragmentNormal(vec3 position, in vec3 normal, float v)
{
    normal = normalize(normal);
    if (gl_FrontFacing){
      return normal;
    }
    else{
      return -normal;
    }
}

--- --------------------------------------------------------------------------
-- glsl Curves.Fragment.Hair

// XXX: Neye is interpolated in from previous stages, however the
// polarity is not stable due to instability in the cross-product in the
// TessEval shader. Once that is fixed, we could use Neye directly here.
// The normal computed here results in faceted shading.
//
vec3 fragmentNormal(vec3 position, in vec3 unused, float v)
{
    return cross(dFdx(position), dFdy(position));
}
