// This is camera.cc CVS version: $Id: camera.cc,v 1.2 2000/02/28 18:06:24 andreaha Exp $
#include "eng.h"

/************************************************************************/
/* World::Camera                                                        */
/************************************************************************/

/* constructor */
World::Camera::Camera() {
  dpush1("World::Camera::Camera()");

  strcpy(name, "unnamed camera");

  matrix.genIdentity();

  parent = NULL;
  roll = 0;
  fov  = 45;
  parint     = 0;
  hierarchnr = 0;
  focus = 0;
  focuscam = NULL;
  on = FALSE;

  pos.set(0, 0, -300);
  target.set(0, 0, 0);

  pivot = NULL;

  dpop();
}

/* destructor */
World::Camera::~Camera() {
  dpush1("World::Camera::~Camera()");

  if (focus) {
    myworld->freeMem(divtable);
    myworld->freeMem(focuscam);
    myworld->freeMem(addbuffer);
  }

  dpop();
}

/* set camera focus mode */
void World::Camera::setFocus(int i, float r) {
  dpush2("World::Camera::setFocus(%i)", i);

  focus = i;
  rad = r;

  if (i) {
    debug("Preparing world for focus rendering.\n");

    focusscale = (float )1.0;
    focuscam = (Camera **)myworld->getMem(focus * sizeof(Camera *));
    addbuffer = (unsigned int *)myworld->getMem(myworld->rendbuffersize * sizeof(int));  
    divtable = (char *)myworld->getMem((focus + 1) * 256 * sizeof(char));

    for (int n = 0; n < focus; n++) {
      focuscam[n] = myworld->newCamera();  
      focuscam[n]->focusscale = (float)(rand()%10000) / ((float )10000.0);
    }

    for (int n = 0; n < ((focus + 1) * 256); n++)
      divtable[n] = n / (focus + 1);

    myworld->cameramem += myworld->rendbuffersize * sizeof(int);
    myworld->cameramem += (focus + 1) * 256 * sizeof(char);
  }

  dpop();
}

/* set camera roll */
void World::Camera::setRoll(float f) {
  dpush2("World::Camera::setRoll(%3.1f)", f);

  roll = f;
  dpop();
}

/* render scene */
void World::Camera::render(void *screen) {
  dpush2("World::Camera::render(%#010x)",(int)screen);

  myworld->setActiveCamera(this);

  char *s = (char *)myworld->rendbuffer;
  unsigned int *f;
  unsigned int read;
  float p0mul, p1mul;
  float radius;
  static World::Vector p0, p1, A, Aold, tmp;
  static float topi = 2 * 3.1415927;

  if (focus) {
    myworld->render();
    memset(addbuffer, 0, myworld->rendbuffersize * sizeof(int));

    f = (unsigned int *)s;
    switch (myworld->endian) {
    case BIG: 
      for (unsigned int n = 0; n < myworld->rendbuffersize; n+=4) {
	read = *f++;
	addbuffer[n+0] += (read >> 24) & 0xff;
	addbuffer[n+1] += (read >> 16) & 0xff;
	addbuffer[n+2] += (read >> 8) & 0xff;
      } break;
    case LITTLE:
      for (unsigned int n = 0; n < myworld->rendbuffersize; n+=4) {
	read = *f++;
	addbuffer[n+1] += (read >> 8) & 0xff;
	addbuffer[n+2] += (read >> 16) & 0xff;
	addbuffer[n+3] += (read >> 24) & 0xff;
      } break;
    }

    for (int m = 0; m < focus; m++) {
      myworld->setActiveCamera(focuscam[m]);

      focuscam[m]->target.set(&target);

      /* A er plannormalen */

      A.set(&target);
      A.sub(&pos);
      Aold.set(&A);

      radius = tan(rad * topi / 360.0) * A.length() * focusscale;
      radius *= (rand() % 100) / 100.0;

      p0mul = cos((float )m * topi / (float )focus) * radius;
      p1mul = sin((float )m * topi / (float )focus) * radius;

      A.normalize();

      /* Finn p0 ved å krysse feks 0,1,0 med A */

      p0.set(1,0,0);
      p0.crossWith(&A);
      p0.normalize();

      /* Finn p1 ved å krysse p0 med A */

      p1.set(&p0);
      p1.crossWith(&A);
      p1.normalize();

      /* Gang opp størrelsen */

      p0.mult(p0mul);
      p1.mult(p1mul);

      /* Summer vektorene, legg sum-vektoren til kameraposisjon */

      p0.add(&pos);
      p0.add(&p1);

      /* Finn en vektor som representerer feilen (sphere / plane) */

      tmp.set(&target);
      tmp.sub(&p0);
      tmp.normalize();
      tmp.mult(Aold.length() - p0.length());
      p0.add(&tmp);

      focuscam[m]->pos.set(&p0);
      focuscam[m]->setRoll(roll);

      myworld->render();

      f = (unsigned int *)s;
      switch (myworld->endian) {
      case BIG: 
	for (unsigned int n = 0; n < myworld->rendbuffersize; n += 4) {
	  read = *f++;
	  addbuffer[n+0] += (read >> 24) & 0xff;
	  addbuffer[n+1] += (read >> 16) & 0xff;
	  addbuffer[n+2] += (read >> 8) & 0xff;
	} break;
      case LITTLE:
	for (unsigned int n = 0; n < myworld->rendbuffersize; n += 4) {
	  read = *f++;
	  addbuffer[n+1] += (read >> 8) & 0xff;
	  addbuffer[n+2] += (read >> 16) & 0xff;
	  addbuffer[n+3] += (read >> 24) & 0xff;
	} break;
      }
    }

    for (unsigned int n = 0; n < myworld->rendbuffersize; n++)
      *s++ = divtable[addbuffer[n]];
    
  }
  else
    myworld->render();

  myworld->blitTo(screen);
  
  dpop();
}

/*************
  void World::createMatrix(World::Camera *c)

  param: c == activecamera

  Builds a transformation matrix for one camera,
  including camera roll. This procedure crosses
  the target vector of the camera with an "up"
  vector (0,-1,0). Therefore the camera must not 
  point straight down!

  ************/
void World::Camera::createMatrix() {

  dpush1("World::Camera::createMatrix()");
  debug("Creating camera matrix.\n");

  Matrix mm;

  float  sinr, cosr;
  Vector up, u, v, n;
  Vector tar;

  mm.genIdentity();

  matrix.genTranslationWith( -pos.x, -pos.y, -pos.z);

  up.set(0, -1, 0);

  tar.set(&target);                             /* copy vectors */
  tar.sub(&pos);                                /* find distance to camera */
  tar.normalize();                              /* length = 1.0 */

  n.set(&tar);
  u.set(&tar);
  v.set(&tar);

  u.crossWith(&up);                             /* cross with (0,-1,0) */
  v.crossWith(&u);                              /* cross with target */

  mm.m[0] = u.x; mm.m[1] = u.y; mm.m[2] = u.z;
  mm.m[4] = v.x; mm.m[5] = v.y; mm.m[6] = v.z;
  mm.m[8] = n.x; mm.m[9] = n.y; mm.m[10]= n.z;

  /* pointing transformation */

  matrix.mulWith(&mm);

  sinr = sin(roll);
  cosr = cos(roll);

  mm.m[0] = cosr;
  mm.m[1] = -sinr;
  mm.m[2] = 0;
  mm.m[4] = sinr;
  mm.m[5] = cosr;
  mm.m[6] = 0;
  mm.m[8] = 0;
  mm.m[9] = 0;
  mm.m[10]= 1;

  /* roll transformation */

  matrix.mulWith(&mm);

  dpop();
}

/* camera printout */
void World::Camera::printOut() {
  dpush1("World::Camera::printOut()");

  print2("Camera [%#010x] printout:\n", (int)this);
  print4("  Position: (%6.2f, %6.2f, %6.2f)\n", pos.x, pos.y, pos.z);
  print4("  Target:   (%6.2f, %6.2f, %6.2f)\n", target.x, target.y, target.z);
  print2("  Focus:    %s\n", focus ? myworld->util.charptr("Yes") : myworld->util.charptr("No"));
  if (focus) {
    print2("    Oversampling: %ix\n", focus);
    print2("    Angle:        %f\n", rad);
  }
  print2("  Roll:     %f\n", roll);
  print2("  FOV:      %f\n", fov);

  dpop();
}




