/* Scene composee de spheres et de lumieres     */
/*                                              */
/* Auteur: Nicolas JANEY                        */
/* nicolas.janey@univ-fcomte.fr                 */
/* Mars 2014                                    */

#include <stdlib.h>

#include "Scene.h"
#include "Objet.h"
#include "Lumiere.h"
#include "RayonLumineux.h"
#include "Couleur.h"
#include "Energie.h"
#include "Pos3D.h"
#include "Dir3D.h"
#include "Materiel.h"

Scene::Scene(void) {
  nbObjets = 0;
  objets = NULL;
  nbLumieres = 0;
  lumieres = NULL;
}

Scene::Scene(Scene *sc) {
  nbObjets = 0;
  objets = NULL;
  nbLumieres = 0;
  lumieres = NULL;
  nbObjets = sc->nbObjets;
  for ( int i = 0 ; i < sc->nbObjets ; i++ )
    add(sc->objets[i]);
  nbLumieres = sc->nbLumieres;
  for ( int i = 0 ; i < sc->nbLumieres ; i++ )
    add(sc->lumieres[i]);
}

Scene::~Scene(void) {
  if ( objets ) {
    for ( int i = 0 ; i < nbObjets ; i++ )
      delete(objets[i]);
    free(objets); }
  if ( lumieres ) {
    for ( int i = 0 ; i < nbLumieres ; i++ )
      delete(lumieres[i]);
    free(lumieres); }
}

void Scene::add(Objet *o) {
  if ( objets == NULL ) {
    objets =(Objet **) calloc(1,sizeof(Objet *));
    objets[0] = o->clone();
    nbObjets = 1; }
    else {
    objets =(Objet **) realloc(objets,(nbObjets+1)*sizeof(Objet *));
    objets[nbObjets] = o->clone();
    nbObjets++; }
}

void Scene::add(Lumiere *l) {
  if ( lumieres == NULL ) {
    lumieres =(Lumiere **) calloc(1,sizeof(Lumiere *));
    lumieres[0] = l->clone();
    nbLumieres = 1; }
    else {
    lumieres =(Lumiere **) realloc(lumieres,(nbLumieres+1)*sizeof(Lumiere *));
    lumieres[nbLumieres] = l->clone();
    nbLumieres++; }
}

void Scene::print(void) {
}

int Scene::intersection(RayonLumineux *rl) {
  for ( int i = 0 ; i < nbObjets ; i++ )
    if ( objets[i]->intersection(rl) ) {
      return(1); }
  return(0);
}

int Scene::intersection(RayonLumineux *rl,double d) {
  double dist;
  for ( int i = 0 ; i < nbObjets ; i++ )
    if ( objets[i]->intersection(rl,&dist) ) {
      if ( dist < d )
        return(1); }
  return(0);
}

void Scene::calculDiffusion(Energie *e,Pos3D *p,Dir3D *n,Couleur *kd) {
  e->r = e->v = e->b = 0.0;
  int i;
  for ( i = 0 ; i < nbLumieres ; i++ ) {
    Lumiere *l = lumieres[i]; 
    if ( l->eclaire(p,this,l->distance(p)) ) {
      Energie ed;
      l->energieDiffusee(&ed,p,n,kd);
      e->r += ed.r;
      e->v += ed.v;
      e->b += ed.b; } }
}

/* Fonction de test de nullite d'une couleur    */
/* (noir)                                       */

static int estNul(Couleur *cf) {
  return(( cf->r == 0.0 ) && ( cf->v == 0.0 ) && ( cf->b == 0.0 ) );
}

/* Fonction de calcul de la position d'un point */
/* sur un rayon lumineux                        */

static void calculPositionSurRayon(Pos3D *p,RayonLumineux *rl,double d) {
  p->x =(float) (rl->origine->x + d*rl->direction->x);
  p->y =(float) (rl->origine->y + d*rl->direction->y);
  p->z =(float) (rl->origine->z + d*rl->direction->z);
}

/* Fonction principale recursive de calcul      */
/* de ray-tracing                               */

void Scene::rayTracing(Energie *e,
                       RayonLumineux *rl,
                       int niveau,
                       double indiceRefraction,
                       int exterieur,
                       long *nbRayons,
                       long *nbRayonsOmbre) {
  (*nbRayons)++;
  double dmin = 1000000000.0;
  int obj = -1;
  int i;
  for ( i = 0 ; i < nbObjets ; i++ ) {
    double dist;
    if ( objets[i]->intersection(rl,&dist) ) {
      if ( dist < dmin ) {
        dmin = dist;
        obj = i; } } }
  if ( obj != -1 ) {
    e->r = e->v = e->b = 0.0;
    Pos3D p;
    calculPositionSurRayon(&p,rl,dmin);
    Dir3D n;
    objets[obj]->calculNormale(&n,&p);
    if ( exterieur ) {
      Energie ed;
      *nbRayonsOmbre += nbLumieres;
      calculDiffusion(&ed,&p,&n,objets[obj]->materiel->kd);
      e->r += ed.r;
      e->v += ed.v;
      e->b += ed.b; }
    if ( niveau != 1 ) {
      Objet *o = objets[obj];
      Couleur *kr = o->materiel->kr;
      Couleur *kt = o->materiel->kt;
      if ( !estNul(kr) && exterieur ) {
        RayonLumineux rr;
        rl->rayonReflechi(&rr,&p,&n);
        Energie er;
        rayTracing(&er,&rr,niveau-1,indiceRefraction,1,nbRayons,nbRayonsOmbre);
        e->r += er.r*kr->r;
        e->v += er.v*kr->v;
        e->b += er.b*kr->b; }
      if ( !estNul(kt) ) {
        RayonLumineux rt;
        double niSurnt = indiceRefraction/o->materiel->indice;
        if ( rl->rayonTransmis(&rt,&p,&n,(float) niSurnt,exterieur) ) {
          Energie et;
          rayTracing(&et,&rt,niveau-1,
                     (exterieur) ? o->materiel->indice : 1.0,
                     !exterieur,nbRayons,nbRayonsOmbre);
          e->r += et.r*kt->r;
          e->v += et.v*kt->v;
          e->b += et.b*kt->b; } } } }
    else
    e->r = e->v = e->b = 0.0;
}
