/*
 * NurbEval.c - Code for evaluating NURB surfaces.
 *
 * John Peterson
 */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "nurbs.h"
#include "drawing.h"


/*
 * Return the current knot the parameter u is less than or equal to.
 * Find this "breakpoint" allows the evaluation routines to concentrate on
 * only those control points actually effecting the curve around u.]
 *
 *	m   is the number of points on the curve (or surface direction)
 *	k   is the order of the curve (or surface direction)
 *	kv  is the knot vector ([0..m+k-1]) to find the break point in.
 */

long
FindBreakPoint( double u, double * kv, long m, long k )
{
    long i;

    if (u == kv[m+1])	/* Special case for closed interval */
	return m;

    i = m + k;
    while ((u < kv[i]) && (i > 0))
	i--;
    return( i );
}

/*
 * Compute Bi,k(u), for i = 0..k.
 *  u		is the parameter of the spline to find the basis functions for
 *  brkPoint	is the start of the knot interval ("segment")
 *  kv		is the knot vector
 *  k		is the order of the curve
 *  bvals	is the array of returned basis values.
 *
 * (From Bartels, Beatty & Barsky, p.387)
 */

static void
BasisFunctions( double u, long brkPoint, double * kv, long k, double * bvals )
{
    register long r, s, i;
    register double omega;

    bvals[0] = 1.0;
    for (r = 2; r <= k; r++)
    {
	i = brkPoint - r + 1;
	bvals[r - 1] = 0.0;
	for (s = r-2; s >= 0; s--)
	{
	    i++;
	    if (i < 0)
		omega = 0;
	    else
		omega = (u - kv[i]) / (kv[i + r - 1] - kv[i]);
	    bvals[s + 1] = bvals[s + 1] + (1 - omega) * bvals[s];
	    bvals[s] = omega * bvals[s];
	}
    }
}

/*
 * Compute derivatives of the basis functions Bi,k(u)'
 */
static void
BasisDerivatives( double u, long brkPoint, double * kv, long k, double * dvals )
{
    register long s, i;
    register double omega, knotScale;

    BasisFunctions( u, brkPoint, kv, k - 1, dvals );

    dvals[k-1] = 0.0;	    /* BasisFunctions misses this */

    knotScale = kv[brkPoint + 1L] - kv[brkPoint];

    i = brkPoint - k + 1L;
    for (s = k - 2L; s >= 0L; s--)
    {
	i++;
	omega = knotScale * ((double)(k-1L)) / (kv[i+k-1L] - kv[i]);
	dvals[s + 1L] += -omega * dvals[s];
	dvals[s] *= omega;
    }
}

/*
 * Calculate a point p on NurbSurface n at a specific u, v using the tensor product.
 * If utan and vtan are not nil, compute the u and v tangents as well.
 *
 * Note the valid parameter range for u and v is
 * (kvU[orderU] <= u < kvU[numU), (kvV[orderV] <= v < kvV[numV])
 */

void
CalcPoint(double u, double v, NurbSurface * n,
	   Point3 * p, Point3 * utan, Point3 * vtan)
{
    register long i, j, ri, rj;
    register Point4 * cp;
    register double tmp;
    register double wsqrdiv;
    long ubrkPoint, ufirst;
    double bu[MAXORDER], buprime[MAXORDER];
    long vbrkPoint, vfirst;
    double bv[MAXORDER], bvprime[MAXORDER];
    Point4 r, rutan, rvtan;

    r.x = 0.0;
    r.y = 0.0;
    r.z = 0.0;
    r.w = 0.0;

    rutan = r;
    rvtan = r;

    /* Evaluate non-uniform basis functions (and derivatives) */

    ubrkPoint = FindBreakPoint( u, n->kvU, n->numU-1, n->orderU );
    ufirst = ubrkPoint - n->orderU + 1;
    BasisFunctions( u, ubrkPoint, n->kvU, n->orderU, bu );
    if (utan)
	BasisDerivatives( u, ubrkPoint, n->kvU, n->orderU, buprime );

    vbrkPoint = FindBreakPoint( v, n->kvV, n->numV-1, n->orderV );
    vfirst = vbrkPoint - n->orderV + 1;
    BasisFunctions( v, vbrkPoint, n->kvV, n->orderV, bv );
    if (vtan)
	BasisDerivatives( v, vbrkPoint, n->kvV, n->orderV, bvprime );

    /* Weight control points against the basis functions */

    for (i = 0; i < n->orderV; i++)
	for (j = 0; j < n->orderU; j++)
	{
	    ri = n->orderV - 1L - i;
	    rj = n->orderU - 1L - j;

	    tmp = bu[rj] * bv[ri];
	    cp = &( n->points[i+vfirst][j+ufirst] );
	    r.x += cp->x * tmp;
	    r.y += cp->y * tmp;
	    r.z += cp->z * tmp;
	    r.w += cp->w * tmp;

	    if (utan)
	    {
		tmp = buprime[rj] * bv[ri];
		rutan.x += cp->x * tmp;
		rutan.y += cp->y * tmp;
		rutan.z += cp->z * tmp;
		rutan.w += cp->w * tmp;
	    }
	    if (vtan)
	    {
		tmp = bu[rj] * bvprime[ri];
		rvtan.x += cp->x * tmp;
		rvtan.y += cp->y * tmp;
		rvtan.z += cp->z * tmp;
		rvtan.w += cp->w * tmp;
	    }
	}

    /* Project tangents, using the quotient rule for differentiation */

    wsqrdiv = 1.0 / (r.w * r.w);
    if (utan)
    {
	utan->x = (r.w * rutan.x - rutan.w * r.x) * wsqrdiv;
	utan->y = (r.w * rutan.y - rutan.w * r.y) * wsqrdiv;
	utan->z = (r.w * rutan.z - rutan.w * r.z) * wsqrdiv;
    }
    if (vtan)
    {
	vtan->x = (r.w * rvtan.x - rvtan.w * r.x) * wsqrdiv;
	vtan->y = (r.w * rvtan.y - rvtan.w * r.y) * wsqrdiv;
	vtan->z = (r.w * rvtan.z - rvtan.w * r.z) * wsqrdiv;
    }

    p->x = r.x / r.w;
    p->y = r.y / r.w;
    p->z = r.z / r.w;
}

/*
 * Draw a mesh of points by evaluating the surface at evenly spaced
 * points.
 */
void
DrawEvaluation( NurbSurface * n )
{
    Point3 p, utan, vtan;
    register long i, j;
    register double u, v, d;
    SurfSample ** pts, *sptr;

    long Granularity = 10;  /* Controls the number of steps in u and v */

    /* Allocate storage for the grid of points generated */

    CHECK( pts = (SurfSample**) malloc( (Granularity+1L) * sizeof( SurfSample* )));
    CHECK( pts[0] = (SurfSample*) malloc( (Granularity+1L)*(Granularity+1L)
		    * sizeof( SurfSample )));
    for (i = 1; i <= Granularity; i++)
	pts[i] = &(pts[0][(Granularity+1L) * i]);
    sptr = pts[0];

    /* Compute points on curve */

    for (i = 0; i <= Granularity; i++)
    {
	v = ((double) i / (double) Granularity)
	    * (n->kvV[n->numV] - n->kvV[n->orderV-1])
	    + n->kvV[n->orderV-1];

	for (j = 0; j <= Granularity; j++)
	{
	    u = ((double) j / (double) Granularity)
		* (n->kvU[n->numU] - n->kvU[n->orderU-1])
		+ n->kvU[n->orderU-1];

	    CalcPoint( u, v, n, &(pts[i][j].point), &utan, &vtan );
	    (void) V3Cross( &utan, &vtan, &p );
	    d = V3Length( &p );
	    if (d != 0.0)
	    {
		p.x /= d;
		p.y /= d;
		p.z /= d;
	    }
	    else
	    {
		p.x = 0;
		p.y = 0;
		p.z = 0;
	    }
	    pts[i][j].normLen = d;
	    pts[i][j].normal = p;
	    pts[i][j].u = u;
	    pts[i][j].v = v;
	}
    }

    /* Draw the grid */

    for (i = 0; i < Granularity; i++)
	for (j = 0; j < Granularity; j++)
	{
	    (*DrawTriangle)( &pts[i][j], &pts[i+1][j+1], &pts[i+1][j] );
	    (*DrawTriangle)( &pts[i][j], &pts[i][j+1],	 &pts[i+1][j+1] );
	}

    free( pts[0] );
    free( pts );
}

