/* rotspline.c,v 1.1.1.1 1995/02/27 07:38:35 explorer Exp */

/*
 * Copyright (C) 1991, Gerald Iles.
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 */

#include "libcommon/common.h"
#include "roots.h"
#include "geom.h"
#include "rotspline.h"

static Methods *iRotsplineMethods = NULL;
static char rotsplineName[] = "rotspline";

unsigned long RotsplineTests, RotsplineHits;

GeomRef
RotsplineCreate(bot, x0, d0, top, xw, dw, coeffs)
     Vector *bot, *top;
     Float x0, xw, d0, dw, *coeffs;
{
  Rotspline *rotspline;
  Float w;
  Vector axis;
  
  VecSub(*top, *bot, &axis);
  w = VecNormalize(&axis);
  if (w < EPSILON) {
    RLerror(RL_WARN, "Degenerate rotspline.\n");
    return (GeomRef)NULL;
  }
  
  rotspline = (Rotspline *)Malloc(sizeof(Rotspline));
  
  rotspline->w = w;
  
  if (coeffs == NULL) {
    rotspline->k = (Float *)Calloc(4, sizeof(Float));
    rotspline->k[3] = 2.0 * (x0 * x0 - xw * xw + w * x0 * d0 + w * xw * dw)
      / (w * w * w);
    rotspline->k[2] = (3.0 * (xw * xw - x0 * x0) - 4.0*w*x0*d0 - 2.0*w*xw*dw)
      / (w * w);
    rotspline->k[1] = 2.0 * x0 * d0;
    rotspline->k[0] = x0 * x0;
  } else
    rotspline->k = coeffs;
  CoordSysTransform(bot, &axis, 1.0, 1.0, &rotspline->trans);
  
  return (GeomRef)rotspline;
}

Methods *
RotsplineMethods()
{
  if (iRotsplineMethods == (Methods *)NULL) {
    iRotsplineMethods = MethodsCreate();
    iRotsplineMethods->name = RotsplineName;
#if 0
    iRotsplineMethods->create = (GeomCreateFunc *)RotsplineCreate;
#endif
    iRotsplineMethods->methods = RotsplineMethods;
    iRotsplineMethods->intersect = RotsplineIntersect;
    iRotsplineMethods->normal = RotsplineNormal;
    iRotsplineMethods->uv = RotsplineUV;
    iRotsplineMethods->bounds = RotsplineBounds;
    iRotsplineMethods->stats = RotsplineStats;
    iRotsplineMethods->checkbounds = TRUE;
    iRotsplineMethods->closed = FALSE;
  }
  return iRotsplineMethods;
}

int
RotsplineIntersect(ref, ray, hl, mindist, maxdist)
     GeomRef ref;
     Ray *ray;
     HitList *hl;  /* unused here */
     Float mindist, *maxdist;
{
  Rotspline *rotspline = (Rotspline *)ref;
  double c[4], s[3], pp, pd, pz, dz, tmp, dist;
  Ray newray;
  Vector ndir, npos;
  int num, i;
  
  RotsplineTests++;
  
  newray = *ray;
  (void) RayTransform(&newray, &rotspline->trans.itrans);
  ndir = newray.dir;
  npos = newray.pos;
  
  pp = npos.x*npos.x + npos.y*npos.y +npos.z*npos.z;
  pd = npos.x*ndir.x + npos.y*ndir.y +npos.z*ndir.z;
  pz = npos.z;
  dz = ndir.z;
  
  c[3] = rotspline->k[3] * dz * dz * dz;
  c[2] = dz * dz * (3.0 * rotspline->k[3] * pz + rotspline->k[2] + 1.0) - 1.0;
  c[1] = dz * (pz * (3.0 * rotspline->k[3] * pz + 2.0 * rotspline->k[2] + 2.0)
	       + rotspline->k[1]) - 2.0 * pd;
  c[0] = pz *(pz *(rotspline->k[3] * pz + rotspline->k[2] +1.0)
	      + rotspline->k[1]) + rotspline->k[0] - pp;
  
  if (fabs(c[3]) > EPSILON)
    num = SolveCubic(c, s);
  else if (fabs(c[2]) > EPSILON)
    num = SolveQuadric(c, s);
  else if (fabs(c[1]) > EPSILON) {
    s[0] = -c[0]/c[1];
    num = 1;
  } else
    num = 0;
  
  if (num==0)
    return FALSE;
  dist = 0.0;
  for(i=0;i<num;i++) {
    if (s[i] > mindist) {
      tmp = pz + dz * s[i];
      if (tmp > 0.0 && tmp < rotspline->w) {
	if (dist == 0.0)
	  dist = s[i];
	else if (s[i] < dist)
	  dist = s[i];
      }
    }
  }
  if (dist > mindist && dist < *maxdist) {
    *maxdist = dist;
    RotsplineHits++;
    return TRUE;
  }
  return FALSE;
}

int
RotsplineNormal(ref, pos, nrm, gnrm)
     GeomRef ref;
     Vector *pos, *nrm, *gnrm;
{
  Rotspline *rotspline = (Rotspline *)ref;
  Vector npos;
  Float du, r;
  
  npos = *pos;
  PointTransform(&npos, &rotspline->trans.itrans);
  
  du = rotspline->k[3] * 3.0 * npos.z * npos.z
    + rotspline->k[2] * 2.0 * npos.z
      + rotspline->k[1];
  
  r = sqrt(npos.x * npos.x + npos.y * npos.y);
  
  nrm->x = r * npos.x;
  nrm->y = r * npos.y;
  nrm->z = -du / 2.0;
  
  NormalTransform(nrm, &rotspline->trans.itrans);
  *gnrm = *nrm;
  return FALSE;
}

void
RotsplineUV(ref, pos, norm, uv, dpdu, dpdv)
     GeomRef ref;
     Vector *pos, *norm, *dpdu, *dpdv;
     Vec2d *uv;
{
  Rotspline *rotspline = (Rotspline *)ref;
  Vector npos;
  Float val, d;
  
  npos = *pos;
  PointTransform(&npos, &rotspline->trans.itrans);
  
  uv->v = npos.z / rotspline->w;
  d = sqrt(npos.x * npos.x + npos.y * npos.y);
  if (d < EPSILON)
    uv->u = 0.;
  else {
    val = npos.x / d;
    if (val > 1.)
      uv->u = 0.;
    else if (val < -1.)
      uv->u = 0.5;
    else {
      uv->u = acos(val) / TWOPI;
      if (npos.y < 0.)
	uv->u = 1. - uv->u;
    }
  }
  
  if (dpdu) {
    dpdu->x = -npos.y;
    dpdu->y = npos.x;
    dpdu->z = 0.;
    dpdv->x = rotspline->k[3] * 3.0 * npos.z * npos.z
      + rotspline->k[2] * 2.0 * npos.z
	+ rotspline->k[1];
    dpdv->y = 0.;
    dpdv->z = (d < EPSILON)?0.:d;
    VecTransform(dpdu, &rotspline->trans.trans);
    VecTransform(dpdv, &rotspline->trans.trans);
    (void)VecNormalize(dpdu);
    (void)VecNormalize(dpdv);
  }
}

void
RotsplineBounds(ref, bounds)
     GeomRef ref;
     Float bounds[2][3];
{
  Rotspline *rotspline = (Rotspline *)ref;
  Float r0, rw, rmax, d, z, rz;
  
  r0 = (rotspline->k[0] < EPSILON ? 0.0 : sqrt(rotspline->k[0]));
  rw = EvalSpline(rotspline->k, rotspline->w);
  rw = (rw < EPSILON ? 0.0 : sqrt(rw));
  rmax = max(r0, rw);
  d = 4.*rotspline->k[2]*rotspline->k[2] - 12.*rotspline->k[3]*rotspline->k[1];
  if (d > 0.) {
    z = (-2. * rotspline->k[2] + sqrt(d))/(6. * rotspline->k[3]);
    if (z > 0. && z < rotspline->w) {
      rz = EvalSpline(rotspline->k, z);
      rz = (rz < EPSILON ? 0.0 : sqrt(rz));
      rmax = max(rmax, rz);
    }
    z = (-2. * rotspline->k[2] - sqrt(d))/(6. * rotspline->k[3]);
    if (z > 0. && z < rotspline->w) {
      rz = EvalSpline(rotspline->k, z);
      rz = (rz < EPSILON ? 0.0 : sqrt(rz));
      rmax = max(rmax, rz);
    }
  }
  bounds[LOW][X] = bounds[LOW][Y] = -rmax;
  bounds[HIGH][X] = bounds[HIGH][Y] = rmax;
  bounds[LOW][Z] = 0.;
  bounds[HIGH][Z] = rotspline->w;
  BoundsTransform(&rotspline->trans.trans, bounds);
}

char *
RotsplineName()
{
  return rotsplineName;
}

void
RotsplineStats(tests, hits)
     unsigned long *tests, *hits;
{
  *tests = RotsplineTests;
  *hits = RotsplineHits;
}
