
// CPUStrokeTessellatorControl.cpp - control shading implementation of polar stroking geometry on CPU

#include <cmath>
#include "../common/CommonStrokeTessellatorControlPrivate.hpp"

#include "CPUStrokeTessellator.hpp"

#include "../common/CommonStrokeTessellatorMath.hpp"  // for project

#include "../common/CommonStrokeTessellatorControl.hpp"

#include <Cg/any.hpp>

#include "countof.h"

static GLuint indexToArrayElement(int i)
{
    // Avoid id zero since it is special to analyzeLink.
    return 2*i+2;
}

bool StrokeTessellatorMETHOD setupGradientControlPoints(float3 link_control_points[], GLuint ndx[])
{
    int ii;
    // Determine initial gradient control point index.
    for (ii=1; ii<4; ii++) {
        if (any(link_control_points[ii] != link_control_points[0])) {
            // Distinct control points; use for gradient.
            break;
        }
    }
    if (ii == 4) {
        return true;  // Degenerate, all control points identical.
    } else {
        const int initial_gradient_i = ii;
        const int initial_gradient_slot = 4;
        ndx[initial_gradient_slot] = indexToArrayElement(initial_gradient_i);
        link_control_points[initial_gradient_slot] = link_control_points[initial_gradient_i];
    }
    // Determine terminal gradient control point index.
    for (ii=3; ii>0; ii--) {
        if (any(link_control_points[3] != link_control_points[ii-1])) {
            // Distinct control points; use for gradient.
            break;
        }
    }
    if (ii == 0) {
        assert(!"should have already been detected");
        return true;  // Degenerate, all control points identical.
    } else {
        const int terminal_gradient_i = ii-1;
        const int terminal_gradient_slot = 5;
        ndx[terminal_gradient_slot] = indexToArrayElement(terminal_gradient_i);
        link_control_points[terminal_gradient_slot] = link_control_points[terminal_gradient_i];
    }
    return false;
}

bool StrokeTessellatorMETHOD completeLinkSetup(int link_type, float3 link_control_points[], GLuint ndx[])
{
    // Second, determine initial and terminal gradient indices.
    const bool degenerate = setupGradientControlPoints(link_control_points, ndx);
    if (degenerate) {
        return true;
    }

    // Third, transform control points and perform control processing.
    setupLinkControlPoints(link_control_points, ndx);
    const float clamped_steps = linkControlProcess(link_type);
    if (clamped_steps < 1) {
        return true;
    }
    establishSteps(clamped_steps);
    return false;
}

// Returns true if should be culled due to degenerating to a point.
bool StrokeTessellatorMETHOD setupCubic(const float2 cp_orig[4])
{
    float3 link_control_points[countof(v_orig)];
    GLuint ndx[countof(vertex_id)];

    // First, copy the 4 control points.
    for (int i=0; i<4; i++) {
        link_control_points[i] = float3(cp_orig[i], 1);
        ndx[i] = indexToArrayElement(i);
    }

    return completeLinkSetup(LT_CUBIC_SEGMENT, link_control_points, ndx);
}

// Returns true if should be culled due to degenerating to a point.
bool StrokeTessellatorMETHOD setupQuadratic(const float2 cp_orig[3])
{
    float3 link_control_points[countof(v_orig)];
    GLuint ndx[countof(vertex_id)];

    // First, copy the 3 quadratic control points, replicating the extrapolating control point twice.
    link_control_points[0] = float3(cp_orig[0], 1);
    ndx[0] = indexToArrayElement(0);

    link_control_points[1] = float3(cp_orig[1], 1);
    ndx[1] = indexToArrayElement(1);
    // Replicate extrapolating control point.
    link_control_points[2] = link_control_points[1];
    ndx[2] = ndx[1];

    link_control_points[3] = float3(cp_orig[2], 1);
    ndx[3] = indexToArrayElement(1);

    return completeLinkSetup(LT_QUADRATIC_SEGMENT, link_control_points, ndx);
}

// Returns true if should be culled due to degenerating to a point.
bool StrokeTessellatorMETHOD setupConic(const float2 cp_orig[3], float interp_w)
{
    float3 link_control_points[countof(v_orig)];
    GLuint ndx[countof(vertex_id)];

    // First, copy the 3 conic control points, replicating the extrapolating control point twice.
    link_control_points[0] = float3(cp_orig[0], 1);
    ndx[0] = indexToArrayElement(0);

    // Include W from interpolating control point.
    link_control_points[1] = float3(cp_orig[1]*interp_w, interp_w);
    ndx[1] = indexToArrayElement(1);
    // Replicate extrapolating control point.
    link_control_points[2] = link_control_points[1];
    ndx[2] = ndx[1];

    link_control_points[3] = float3(cp_orig[2], 1);
    ndx[3] = indexToArrayElement(1);

    return completeLinkSetup(LT_QUADRATIC_SEGMENT, link_control_points, ndx);
}

static float2 offsetPosition(const float3& p, const float2& offset, float radius)
{
    const float p_w = p[2];
    const float3 offset_p = float3(p.xy + radius*offset*p_w, p_w);
    return project(offset_p);
}

void StrokeTessellatorMETHOD tessellateRibOffsets(TessellateOffsetFunc func, void* handle)
{
    const uint steps = Steps();
    // For each rib...
    for (uint rib_index=0; rib_index<=steps; rib_index++) {
        const RibIndexResult rib(rib_index);
        evalRib(rib);
        const float2 P[2] = {
            offsetPosition(pervertex_p, pervertex_offset, pervertex_radius[0]),  // negsative offset
            offsetPosition(pervertex_p, pervertex_offset, pervertex_radius[1])   // positive offset
        };
        func(rib_index, P, pervertex_t, handle);
    }
}

bool StrokeTessellatorMETHOD tessellateCubicOffsets(TessellateOffsetFunc func, void* handle, const float2 cp_orig[4])
{
    bool culled = setupCubic(cp_orig);
    if (culled) {
        // Do nothing; no callbacks.
        return true;
    } else {
        tessellateRibOffsets(func, handle);
        return false;
    }
}

bool StrokeTessellatorMETHOD tessellateQuadraticOffsets(TessellateOffsetFunc func, void* handle, const float2 cp_orig[3])
{
    bool culled = setupQuadratic(cp_orig);
    if (culled) {
        // Do nothing; no callbacks.
        return true;
    } else {
        tessellateRibOffsets(func, handle);
        return false;
    }
}

bool StrokeTessellatorMETHOD tessellateConicOffsets(TessellateOffsetFunc func, void* handle, const float2 cp_orig[3], float interp_w)
{
    bool culled = setupConic(cp_orig, interp_w);
    if (culled) {
        // Do nothing; no callbacks.
        return true;
    } else {
        tessellateRibOffsets(func, handle);
        return false;
    }
}

void StrokeTessellatorMETHOD tessellateRibPosition(TessellatePositionFunc func, void* handle)
{
    const uint steps = Steps();
    // For each rib...
    for (uint rib_index=0; rib_index<=steps; rib_index++) {
        const RibIndexResult rib(rib_index);
        evalRib(rib);
        assert(pervertex_p.z == 1);
        func(rib_index, pervertex_p.xy, pervertex_t, handle);
    }
}

bool StrokeTessellatorMETHOD tessellateCubicPosition(TessellatePositionFunc func, void* handle, const float2 cp_orig[4])
{
    bool culled = setupCubic(cp_orig);
    if (culled) {
        // Do nothing; no callbacks.
        return true;
    } else {
        tessellateRibPosition(func, handle);
        return false;
    }
}

bool StrokeTessellatorMETHOD tessellateQuadraticPosition(TessellatePositionFunc func, void* handle, const float2 cp_orig[3])
{
    bool culled = setupQuadratic(cp_orig);
    if (culled) {
        // Do nothing; no callbacks.
        return true;
    } else {
        tessellateRibPosition(func, handle);
        return false;
    }
}

void StrokeTessellatorMETHOD tessellateRibRationalPosition(TessellatePositionFunc func, void* handle)
{
    const uint steps = Steps();
    // For each rib...
    for (uint rib_index=0; rib_index<=steps; rib_index++) {
        const RibIndexResult rib(rib_index);
        evalRib(rib);
        const float pervertex_pw = pervertex_p.z;
        // Tolerate division by zero and expect func to handle a position
        // containing NaN values.  This can happen when a conic projects
        // to infinity.
        func(rib_index, pervertex_p.xy/pervertex_pw, pervertex_t, handle);
    }
}

bool StrokeTessellatorMETHOD tessellateConicPosition(TessellatePositionFunc func, void* handle, const float2 cp_orig[3], float interp_w)
{
    bool culled = setupConic(cp_orig, interp_w);
    if (culled) {
        // Do nothing; no callbacks.
        return true;
    } else {
        tessellateRibRationalPosition(func, handle);
        return false;
    }
}
