/* disc.c,v 1.1.1.1 1995/02/27 07:38:32 explorer Exp */

/*
 * Copyright (C) 1989, 1991, Craig E. Kolb
 * 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 "geom.h"
#include "disc.h"

static Methods *iDiscMethods = NULL;
static char discName[] = "disc";

unsigned long DiscTests, DiscHits;

GeomRef
DiscCreate(r, pos, norm)
     Float r;
     Vector *pos, *norm;
{
  Disc *disc;		/* Pointer to new disc. */
  
  if (r < EPSILON) {
    RLerror(RL_WARN, "Degenerate disc.\n");
    /*
     * Don't create this primitive.
     */
    return (GeomRef)NULL;
  }
  
  if (VecNormalize(norm) == 0.) {
    RLerror(RL_WARN, "Degenerate disc normal.\n");
    return (GeomRef)NULL;
  }
  /*
   * Allocate new Disc.
   */
  disc = (Disc *)share_malloc(sizeof(Disc));
  /*
   * Initialize new disc.
   * We store the square of the radius to save us a sqrt().
   */
  disc->radius = r*r;
  disc->pos = *pos;
  disc->norm = *norm;
  /*
   * Compute plane constant.
   */
  disc->d = dotp(pos, norm);
  /*
   * Allocate new primitive
   */
  return (GeomRef)disc;
}

Methods *
DiscMethods()
{
  if (iDiscMethods == (Methods *)NULL) {
    iDiscMethods = MethodsCreate();
    iDiscMethods->name = DiscName;
#if 0
    iDiscMethods->create = (GeomCreateFunc *)DiscCreate;
#endif
    iDiscMethods->methods = DiscMethods;
    iDiscMethods->intersect = DiscIntersect;
    iDiscMethods->normal = DiscNormal;
    iDiscMethods->uv = DiscUV;
    iDiscMethods->bounds = DiscBounds;
    iDiscMethods->stats = DiscStats;
    iDiscMethods->checkbounds = FALSE;
    iDiscMethods->closed = FALSE;
  }
  return iDiscMethods;
}

int
DiscIntersect(ref, ray, hl, mindist, maxdist)
     GeomRef ref;
     Ray *ray;
     HitList *hl;  /* unused here */
     Float mindist, *maxdist;
{
  Disc *disc = (Disc *)ref;
  Vector hit;
  Float denom, dist;
  
  DiscTests++;
  
  denom = dotp(&disc->norm, &ray->dir);
  if (fabs(denom) < EPSILON)
    /* Edge-on intersection */
    return FALSE;
  
  dist = (disc->d - dotp(&disc->norm, &ray->pos)) / denom;
  if (dist < mindist || dist > *maxdist)
    /* Too close or too far */
    return FALSE;
  /*
   *  Find difference between point of intersection and center of disc.
   */
  VecAddScaled(ray->pos, dist, ray->dir, &hit);
  VecSub(hit, disc->pos, &hit);
  /*
   * If hit point is <= disc->radius from center, we've hit the disc.
   */
  if (dotp(&hit, &hit) <= disc->radius) {
    *maxdist = dist;
    DiscHits++;
    return TRUE;
  }
  return FALSE;
}

int
DiscNormal(ref, pos, nrm, gnrm)
     GeomRef ref;
     Vector *pos, *nrm, *gnrm;
{
  Disc *disc = (Disc *)ref;

  *gnrm = *nrm = disc->norm;
  return FALSE;
}

void
DiscUV(ref, pos, norm, uv, dpdu, dpdv)
     GeomRef ref;
     Vector *pos, *norm, *dpdu, *dpdv;
     Vec2d *uv;
{
  Disc *disc = (Disc *)ref;
  Float dist, val;
  
  dist =((pos->x - disc->pos.x) * (pos->x - disc->pos.x) +
	 (pos->y - disc->pos.y) * (pos->y - disc->pos.y) +
	 (pos->z - disc->pos.z) * (pos->z - disc->pos.z));
  
  if (dist < EPSILON) {
    uv->u = uv->v = 0.;
    return;
  }
  
  dist = sqrt(dist);
  uv->v = dist / sqrt(disc->radius); /* should store r and r*r */
  
  val = pos->x / dist;
  
  if (fabs(val) > 1.)
    uv->u = 0.5;
  else {
    uv->u = acos(val) / TWOPI; 
    if (pos->y < 0.)
      uv->u = 1. - uv->u;
  }
  
  if (dpdu) {
    VecSub(*pos, disc->pos, dpdv);
    /* dpdu = dpdv X norm */
    VecCross(dpdv, norm, dpdu);
  }
}

void
DiscBounds(ref, bounds)
     GeomRef ref;
     Float bounds[2][3];
{
  Disc *disc = (Disc *)ref;
  Float extent, rad;
  
  rad = sqrt(disc->radius);
  /*
   * Project disc along each of X, Y and Z axes.
   */
  extent = rad * sqrt(1. - disc->norm.x * disc->norm.x);
  bounds[LOW][X] = disc->pos.x - extent;
  bounds[HIGH][X] = disc->pos.x + extent;
  extent = rad * sqrt(1. - disc->norm.y * disc->norm.y);
  bounds[LOW][Y] = disc->pos.y - extent;
  bounds[HIGH][Y] = disc->pos.y + extent;
  extent = rad * sqrt(1. - disc->norm.z * disc->norm.z);
  bounds[LOW][Z] = disc->pos.z - extent;
  bounds[HIGH][Z] = disc->pos.z + extent;
}

char *
DiscName()
{
  return discName;
}

void
DiscStats(tests, hits)
     unsigned long *tests, *hits;
{
  *tests = DiscTests;
  *hits = DiscHits;
}

void
DiscMethodRegister(meth)
     UserMethodType meth;
{
  if (iDiscMethods)
    iDiscMethods->user = meth;
}
