/**************************************************************************
  Copyright (C) 1992 Guy Moreillon
  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".
**************************************************************************/
/**************************************************************************

			     PREPRAD
		     (Scene preparation program)
		    	    Version 1.0
			       1992		    	    
		    	    
		  
		    	  Guy Moreillon
		    	  
**************************************************************************/

/**************************************************************************
  Fichier	: ray.c
  Description	: fonctions de calcul des ombres portees (raytracing)
**************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <malloc.h>
#include "types.h"
#include "macros.h"
#include "rad.h"
#include "radio.h"
#include "preprad.h"
#include "display.h"
#include "ray.h"

#define INIT_FAR	10000.0
#define ENL_PRCNT	0.01

BOOLEAN check_inter(VOXELP	voxel,
		    TRIANGLEP	tri)
/**************************************************************************
  But	: Determine si le triangle tri intersecte le voxel
  Entree: voxel :   Le voxel
	  tri :	    Le triangle
  Sortie: TRUE si tri intersecte voxel, FALSE sinon
**************************************************************************/
{
  VECTOR    sc;
  INTERVAL  Vu, Vs;
  VOXEL	    vox;

  Vox_sub_v(vox, (*voxel), tri->tri.poly.vertex[0].point);
  Vox_dot_v(Vu, vox, tri->tri.poly.normal);

  if ((Vu.inf > 0.0) || (Vu.sup < 0.0))
    return FALSE;

  Vox_dot_v(Vs, vox, tri->tri.poly.sa);
  if ((Vs.sup < 0.0) || (Vs.inf > 1.0))
    return FALSE;

  Vox_dot_v(Vs, vox, tri->tri.poly.sb);
  if ((Vs.sup < 0.0) || (Vs.inf > 1.0))
    return FALSE;

  V_add(sc, tri->tri.poly.sa, tri->tri.poly.sb);
  Vox_dot_v(Vs, vox, sc);
  if ((Vs.sup < 0.0) || (Vs.inf > 1.0))
    return FALSE;

  return TRUE;
}

void init_grids(OBJECT_OP   object)
/**************************************************************************
  But	: Initialise la grille de voxels de chaque objet composite ou pure,
	  si celui-ci ne fait pas partie d'un composite (met a NULL le
	  pointeur)
  Entree: object :    l'objet
  Sortie: neant
**************************************************************************/
{
  register  OBJECT_OP obj;

  if (object == NULL)
    return;

  switch (object->type) {
    case assembly_type :
      for(obj = object->object.assembly.sons; obj != NULL; obj = obj->next)
        init_grids(obj);
      break;
    case composite_type :
      object->object.composite.grid = NULL;
      break;
    case pure_type :
      object->object.pure.grid = NULL;
      break;
    default :
      Bye_bye("Major bug: wrong hierarchy\n");
  }
}

BOOLEAN ray_hit_bbox(RAYP   ray,
		     VECTOR bbox[2],
		     float  dis,
		     float  *ti,
		     float  *ta)
/**************************************************************************
  But	: Determine si le rayon ray intersecte la bounding box bbox, et
	  pour quelles valeurs de t (ray = org + t*dir)
  Entree: ray :	    le rayon
	  bbox :    la bounding box
	  dis :	    la distance maximale
	  ti, ta :  les valeurs min et max de t
  Sortie: TRUE s'il touche, FALSE sinon
**************************************************************************/
{
  float     t, bi, ba;

  register  int	    i;

  *ta = dis;
  *ti = 0.0;

  for(i = 0; i < 3; i++)
    {
      if (ray->dir[i] >= 0.0)
	bi = bbox[0][i], ba = bbox[1][i];
      else
	bi = bbox[1][i], ba = bbox[0][i];

      if ((t = (ba - ray->org[i])/ray->dir[i]) < 0.0)
	return FALSE;

      if (t < *ta)
	{
	  if (t < *ti)
	    return FALSE;
	  *ta = t;
	};

      if ((t = (bi - ray->org[i])/ray->dir[i]) >= 0.0)
	if (t >= *ti)
	  {
	    if (t > *ta)
	      return FALSE;
	    *ti = t;
	  };
    };
  
  return TRUE;
}

BOOLEAN ray_hit_tri(RAYP	ray,
		    TRIANGLEP	tri,
		    float 	ti,
		    float 	ta,
		    float 	*t)
/**************************************************************************
  But	: Determine si le rayon ray touche le polygone tri, et rend la
	  la valeur de t correspondante. t doit etre compris entre ti et ta
  Entree: ray :	    le rayon
	  tri :	    le polygone
	  ti, ta :  les valeurs min et max pour t
	  t :	    la valeur (rendue) de t au point d'intersection
  Sortie: TRUE s'il touche, FALSE sinon
**************************************************************************/
{
  VECTOR    p, sc;
  float     al, be, ga;

  *t = (V_dot(tri->tri.poly.normal, tri->tri.poly.vertex[0].point) -
	V_dot(tri->tri.poly.normal, ray->org)) /
       V_dot(tri->tri.poly.normal, ray->dir);
  if ((*t < ti) || (*t > ta))
    return FALSE;

  V_add_mul(p, ray->org, *t, ray->dir);
  V_sub(p, p, tri->tri.poly.vertex[0].point);

  al = V_dot(p, tri->tri.poly.sa);
  if ((al < 0.0) || (al > 1.0))
    return FALSE;

  be = V_dot(p, tri->tri.poly.sb);
  if ((be < 0.0) || (be > 1.0))
    return FALSE;

  V_add(sc, tri->tri.poly.sa, tri->tri.poly.sb);
  ga = V_dot(p, sc);
  if ((ga < 0.0) || (ga > 1.0))
    return FALSE;

  return TRUE;
}

BOOLEAN closest_hit(SELECTIONP	select,
		    RAYP	ray,
		    float	ti,
		    float	ta,
		    OBJECT_OP	object,
		    SCENE_OP	scene)
/**************************************************************************
  But	: Determine le polygone le plus proche que le rayon ray touche
	  dans l'objet object avant la distance maximale ta et apres la
	  distance min ti
  Entree: select :  la struct de selection partiellement completee
	  ray :	    le rayon
	  ti, ta :  les distances min et max
	  object :  l'objet
	  scene	:   la scene
  Sortie: TRUE si le rayon touche un polygone, FALSE sinon
**************************************************************************/
{
  VECTOR    p;
  float     t;

  register  int	    i;

  select->tri  = NULL;
  select->dist = INIT_FAR;

  V_add_mul(p, ray->org, ti, ray->dir);
  
  for(i = 0; i < object->object.pure.nb_polys; i++)
    if (p_visible(&(object->object.pure.polys[i]), scene))
      if (ray_hit_tri(ray, &(object->object.pure.polys[i]), ti, ta, &t))
        if (t < select->dist)
	  {
	    select->dist = t;
	    select->tri  = &(object->object.pure.polys[i]);
	  }
      
  if (select->tri != NULL)
    return TRUE;
  else
    return FALSE;
}

BOOLEAN closest_object_hit(SELECTIONP	select,
			   RAYP		ray,
			   OBJECT_OP	object,
			   SCENE_OP	scene,
			   float 	dis,
			   BOOLEAN	poly)
/**************************************************************************
  But	: Determine si le rayon ray touche l'objet obj avant la distance
	  maximale dis, et si cet objet est un assemblage ou un composite,
	  il determine quel objet pure il touche en premier
  Entree: select :  la struct de selection partiellement completee
	  ray :	    le rayon
	  object :  l'objet
	  scene	:   la scene
	  dis :	    la distance maximale
	  poly:	    TRUE si on selectionne un polygone
  Sortie: TRUE s'il touche, FALSE sinon
**************************************************************************/
{
  OBJECT_OP obj;
  SELECTION s;
  BOOLEAN   hit = FALSE;
  float     ti, ta;

  if (poly && Hidden(object))
    return FALSE;

  select->dist = INIT_FAR;

  if (ray_hit_bbox(ray, object->bbox, dis, &ti, &ta))
    switch (object->type) {
      case assembly_type :
        for(obj = object->object.assembly.sons; obj != NULL; obj = obj->next)
          if (closest_object_hit(&s, ray, obj, scene, dis, poly))
	    if (s.dist < select->dist)
	      {
	        *select	      = s;
		hit	      = TRUE;
	      }
        break;
      case composite_type :
        for(obj = object->object.composite.sons; obj != NULL; obj = obj->next)
          if (closest_object_hit(&s, ray, obj, scene, dis, poly))
	    if (s.dist < select->dist)
	      {
	        *select	      = s;
		hit	      = TRUE;
	      }
        break;
      case pure_type :
        if (hit = closest_hit(select, ray, ti, ta, object, scene))
	  select->pure = object;
	break;
      default :
	Bye_bye("Wrong object type !!\n");
    }
  else
    select->pure = NULL;

  return hit;
}

BOOLEAN object_in_sight(SELECTIONP  select,
			RAYP	    ray,
			SCENE_OP    scene,
			float	    dis,
			BOOLEAN	    poly)
/**************************************************************************
  But	: Determine s'il y un objet dans la ligne de visee
  Entree: select    : la structure de selection completee
	  ray	    : le rayon
	  scene	    : la scene
	  dis	    : la distance maximale
	  poly	    : TRUE si on selectionne un polygone
  Sortie: TRUE s'il existe un objet dans la ligne de visee, FALSE sinon
**************************************************************************/
{
  SELECTION s;
  BOOLEAN   hit = FALSE;

  select->dist = INIT_FAR;

  if (closest_object_hit(&s, ray, scene->scene, scene, dis, poly))
    if (s.dist < select->dist)
      {
     	*select = s;
	hit     = TRUE;
      }

  select->elem = NULL;

  return hit;
}

