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

/*
 * Projektarbeit '94
 *
 * written by Reto Mani
 *
 */

#include "libcommon/common.h"
#include "geom.h"                 /* Definition of Vector, ... */
#include "../libsurf/surface.h"   /* Definition of SurfaceCreate() */
#include "../libtext/texture.h"   /* Definition of Chaos() */
#include "flame.h"                /* Definition of Flame functions */
#include "../liblight/light.h"    /* Definition of LightCreate() */
#include "../liblight/point.h"    /* Definition of LightPointCreate() */

#define FLAMERATIO   0.2   /* Flamelength/Flamewidth */
/*#define SPEED 5.0 */ /* movement speed of flame */

static Methods *iFlameMethods = NULL;
static char flameName[] = "flame";

unsigned long FlameTests = 0, FlameHits = 0;

static int FlameCalcColor _PROTO((Flame *, Float, Float, Float));

/* Create & return reference to a flame */
GeomRef
FlameCreate(size, pos, lightswitch, speed)   /* Create a Geom Object! */
     Float  size,speed;
     Vector *pos;
     int lightswitch;
{
  Geom    *nob;
  Surface *nsurf;
  Float   delta;
  Vector  vec;
  Color   col;
  Light   *ltmp;
  
  nsurf=SurfaceCreate();
  nsurf->noshadow=TRUE;
  
  nob=GeomCreate(FlameObjCreate(size,pos,speed,nsurf),FlameMethods());
  
  if (!nob)
    return (GeomRef)NULL; /* degenerate problem */
  
  nob->surf=nsurf;

  if (lightswitch) {
    /* Create the lights */
    col.r=.8;
    col.g=.7;
    col.b=.5;
    delta=size*FLAMERATIO;
    vec.x=pos->x+delta;
    vec.y=pos->y+delta;
    vec.z=pos->z+size/2;
    ltmp=LightCreate((LightRef)PointCreate(&vec),PointMethods(),&col);
    LightAddToDefined(ltmp);
    vec.x=pos->x-delta;
    vec.y=pos->y-delta;
    ltmp=LightCreate((LightRef)PointCreate(&vec),PointMethods(),&col);
    LightAddToDefined(ltmp);
    vec.x=pos->x+delta;
    vec.y=pos->y-delta;
    ltmp=LightCreate((LightRef)PointCreate(&vec),PointMethods(),&col);
    LightAddToDefined(ltmp);
    vec.x=pos->x-delta;
    vec.y=pos->y+delta;
    ltmp=LightCreate((LightRef)PointCreate(&vec),PointMethods(),&col);
    LightAddToDefined(ltmp);
  }
  
  NoiseInit();
  
  return (GeomRef)nob;
}


GeomRef
FlameObjCreate(size,pos,speed,newsurf)  /* Create the Flame Object */
     Float    size,speed;
     Vector   *pos;
     Surface  *newsurf;
{
  Flame *flame;
  
  if (fabs(size)<EPSILON) {
    RLerror(RL_WARN,"Degenerate flame.\n");
    return (GeomRef)NULL;
  }
  
  flame = (Flame *)share_malloc(sizeof(Flame));
  
  flame->size    = size;
  flame->x       = pos->x;
  flame->y       = pos->y;
  flame->z       = pos->z;
  flame->surface = newsurf;
  flame->speed   = speed;
  
  return (GeomRef)flame;
}


Methods *
FlameMethods()
{
  if (iFlameMethods == (Methods *)NULL) {
    iFlameMethods = MethodsCreate();
#if 0
    iFlameMethods->create = (GeomCreateFunc *)FlameObjCreate;
#endif
    iFlameMethods->methods = FlameMethods;
    iFlameMethods->name = FlameName;
    iFlameMethods->intersect = FlameIntersect;
    iFlameMethods->normal = FlameNormal;
    iFlameMethods->uv = FlameUV;
    iFlameMethods->enter = FlameEnter;
    iFlameMethods->bounds = FlameBounds;
    iFlameMethods->stats = FlameStats;
    iFlameMethods->checkbounds = TRUE;
    iFlameMethods->closed = TRUE;
  }
  return iFlameMethods;
}


/* Ray/Flame intersection test */
int
FlameIntersect(ref, ray, hl, mindist, maxdist)
     GeomRef ref;
     Ray *ray;
     HitList *hl;
     Float mindist, *maxdist;
{
  Flame *flame = (Flame *)ref;
  Float xadj,yadj,zadj,lambda;
  Float u,v;  /* Coord in Flameplane */
  
  FlameTests++;
  
  xadj=ray->pos.x - flame->x;
  yadj=ray->pos.y - flame->y;
  zadj=ray->pos.z - flame->z;
  
  /* calculate coordinates of ray in the flame plane */
  lambda=-xadj*ray->dir.x-yadj*ray->dir.y;
  lambda/=ray->dir.x*ray->dir.x+ray->dir.y*ray->dir.y;
  u=xadj+lambda*ray->dir.x;
  v=yadj+lambda*ray->dir.y;
  if (u<0.0)
    u=-sqrt(u*u+v*v);
  else
    u=sqrt(u*u+v*v);
  v=zadj+lambda*ray->dir.z;

  if (v<0.0) 
    return FALSE;
  
  if (FlameCalcColor(flame,u,v,ray->time))
    if (lambda > mindist && lambda < *maxdist) {
      *maxdist=lambda;
      FlameHits++;
      return TRUE;
    }
  return FALSE;
}


/* set the color of the flame depending on coordinates and time */
static int
FlameCalcColor(flame,u,v,ftime)
     Flame *flame;
     Float u,v,ftime;
{
  Float rad,uf,vf,chval,turb,tmpv,speed;
  Vector vec;
  Color color;
  
  /* calculate the radius of point (u,v) on the flame */
  
  
  vf=flame->size*flame->size;
  uf=vf*FLAMERATIO*FLAMERATIO;
  vf*=0.25;
  tmpv=v-flame->size*0.5;
  rad=u*u/uf+tmpv*tmpv/vf;
  
  if (rad<1.0) {
    /* This is the color-function */
    color.r=(rad < 0.333) ? 1.0 : 1.33-rad;
    color.g=1.0-0.9*rad;
    color.b=(rad < 0.666) ? 1.0-rad*1.5 : 0.0;
    
    /* adding turbulence */
    speed=flame->speed*10.0;
    vec.x=flame->x+u;
    vec.y=flame->y+v-ftime/speed;  /* motion in vertical direction! */
    vec.z=flame->z+ftime/speed;
    chval = Chaos(&vec,4);         /* calling the turbulence function */
    turb=(3.0*v/flame->size)*(chval-.5);
    color.r+=turb;
    color.g+=turb;
    color.b+=turb;
    
    if (color.r<0.0) color.r=0.0; else if (color.r>1.0) color.r=1.0;
    if (color.g<0.0) color.g=0.0; else if (color.g>1.0) color.g=1.0;
    if (color.b<0.0) color.b=0.0; else if (color.b>1.0) color.b=1.0;
    
    if (color.r==0.0 && color.g==0.0 && color.b==0.0) return FALSE;
    
    /* setting the color */
    flame->surface->amb.r=color.r;
    flame->surface->amb.g=color.g;
    flame->surface->amb.b=color.b;
    
    flame->surface->diff.r=color.r;
    flame->surface->diff.g=color.g;
    flame->surface->diff.b=color.b;
    
    return TRUE;
  }
  else
    return FALSE;
}


/* Compute normal to flame at pos */
int
FlameNormal(ref, pos, nrm, gnrm)
     GeomRef ref;
     Vector *pos, *nrm, *gnrm;
{
  Flame *flame = (Flame *)ref;

  nrm->x=(pos->x - flame->x);
  nrm->y=(pos->y - flame->y);
  nrm->z=0.0;
  (void)VecNormalize(nrm);
  *gnrm=*nrm;
  return FALSE;
}


/* Determine if ray enters (TRUE) or leaves (FALSE) flame at pos */
int
FlameEnter(ref, ray, mind, hitd)
     GeomRef ref;
     Ray *ray;
     Float mind, hitd;
{
  return TRUE;
}


/* ARGSUSED */
void
FlameUV(ref, pos, norm, uv, dpdu, dpdv)
     GeomRef ref;
     Vector *pos, *norm, *dpdu, *dpdv;
     Vec2d *uv;
{
}


void
FlameBounds(ref, bounds)
     GeomRef ref;
     Float bounds[2][3];
{
  Flame *flame = (Flame *)ref;
  Float  diff;
  
  diff = flame->size * FLAMERATIO;
  
  bounds[LOW][X]  = flame->x-diff;
  bounds[HIGH][X] = flame->x+diff;
  bounds[LOW][Y]  = flame->y-diff;
  bounds[HIGH][Y] = flame->y+diff;
  bounds[LOW][Z]  = flame->z;
  bounds[HIGH][Z] = flame->z+flame->size;
}


char *
FlameName()
{
  return flameName;
}


void
FlameStats(tests, hits)
     unsigned long *tests, *hits;
{
  *tests = FlameTests;
  *hits = FlameHits;
}


void
FlameMethodRegister(meth)
     UserMethodType meth;
{
  if (iFlameMethods)
    iFlameMethods->user = meth;
}
