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

/************************************************************************/
/* Object                                                               */
/************************************************************************/

/* Object constructor */
World::Object::Object(World *myw, char *c, float f, float g) {
  dpush5("World::Object::Object(%#010x, %s, %3.1f, %3.1f)",(int)myw,c,f,g);

  uvmul = g;
  vtxmul = f;
  txtscale = g;
  myworld = myw;
  parent = NULL;

  on = TRUE;
  matrix.genIdentity();

  rot.reset();
  pos.reset();

  if (strstr(myworld->util.lcase(c), ".asc"))
    loadtype = FILEASC;
  else
  if (strstr(myworld->util.lcase(c), ".3ds"))
    loadtype = FILE3DS;
  else {
    error2("[%s]: Filetype not supported", c);
    loadtype = 0;
  }

  switch (loadtype) {
    case FILEASC: { loadAsc(c); break; }
    case FILE3DS: { load3ds(c); break; }
  }

  dpop();
}

/* Object destructor */
World::Object::~Object() {
  dpush1("World::Object::~Object()");
  
  trigonlist.kill();                            /* kill polylist */
  myworld->freeMem(vertices);                   /* free allocated memory */
  myworld->freeMem(vertvectors);
  myworld->freeMem(vertvectorsr);
  myworld->freeMem(normals);
  myworld->freeMem(normalsr);

  dpop();
}

/* set object texture */
void World::Object::setMaterial(Material *m) {
  dpush2("World::Object::setMaterial(%#010x)",(int)m);

  setMaterial(m, NULL);

  dpop();
}
void World::Object::setMaterial(Material *m, Material *s) {
  dpush3("World::Object::setMaterial(%#010x, %#010x)",(int)m,(int)s);

  World::Trigon *t = trigonlist.getFirst();

  if (m == NULL) {
    debug("NULL material assignment, applying default material.");
    txt = myworld->defaultmat;

    while (t) {
      t->txt = m;
      t->shademap = s;
      t = trigonlist.getNext();
    }
  } else {
    txt = m;
    while (t) {
      t->txt = m;
      t = trigonlist.getNext();
    }
  }

  if (s == NULL)
    shademap = myworld->defaultmat;
  else {
    shademap = s;
    txt = m;
    while (t) {
      t->txt = m;
      t = trigonlist.getNext();
    }
  }

  dpop();
}

/* set object filltype */
void World::Object::setFillType(int fillt) {
  dpush2("World::Object::setFillType(%i)", fillt);

  World::Trigon *t = trigonlist.getFirst();

  while (t) {
    t->setDraw(fillt);
    t = trigonlist.getNext();
  }

  filltype = fillt;

  dpop();
}

/* Calculate Object normals */
void World::Object::calcNormals() {
  dpush1("World::Object::calcNormals()");

  int normcounter = 0;
  float x;
  Vector nv, mv;
  Trigon *el = trigonlist.getFirst();
  Trigon *tr;

  int *normalcount;

  if (numofnorms == 0) {
    debug("No normals to calculate!\n");
    dpop();
    return;
  }

  normalcount = (int *)myworld->getMem(numofverts * sizeof(int));

  for (int i = 0; i < numofverts; i++) {            /* calculate vtx normals */
    vertices[i].normal.pos  = &normals[normcounter];
    vertices[i].normal.posr = &normalsr[normcounter++];
    vertices[i].normal.pos ->set(0, 0, 0);
    vertices[i].normal.posr->set(0, 0, 0);
    normalcount[i] = 0;
  }

   while (el) {                                     /* calculate poly normals */
    tr = el;
  
    tr->normal.pos = &normals[normcounter];
    tr->normal.posr = &normalsr[normcounter++];
    nv.reset();
    mv.reset();
    nv.set(tr->ap->pos);                            /* set bp-ap */
    nv.sub(tr->bp->pos); 
    mv.set(tr->ap->pos);                            /* set cp-ap */
    mv.sub(tr->cp->pos);
    nv.crossWith(&mv);                              /* cross vectors */
    nv.normalize();                                 /* set length = 1.0 */
    tr->normal.pos->set(&nv);
    el = trigonlist.getNext();
  }
 
  el = trigonlist.getFirst();

  while (el) {
    tr = el;

    tr->ap->normal.pos->add(tr->normal.pos);
    tr->bp->normal.pos->add(tr->normal.pos);
    tr->cp->normal.pos->add(tr->normal.pos);
    normalcount[tr->na]++;
    normalcount[tr->nb]++;
    normalcount[tr->nc]++;

    el = trigonlist.getNext();
  }
  
  for (int i = 0; i < numofverts; i++)
    if (normalcount[i] > 0) {
      x = (float)1.0 / (float)normalcount[i];
      vertices[i].normal.pos->x *= x;
      vertices[i].normal.pos->y *= x;
      vertices[i].normal.pos->z *= x;
      vertices[i].normal.pos->normalize();
    }

  myworld->freeMem(normalcount);
  dpop();
}

/* check for polygon clip */
void World::Object::checkForClip() {
  dpush1("World::Object::checkForClip()");

  World::Trigon *t = trigonlist.getFirst();
  World::Vector *va, *vb, *vc;
  float maxx = (float )myworld->maxx;
  float maxy = (float )myworld->maxy;
  float xa, xb, xc;
  float ya, yb, yc;

  while (t) {
    t->clipped = FALSE;
    va = t->ap->posr;
    vb = t->bp->posr;
    vc = t->cp->posr;

    xa = va->x;
    xb = vb->x;
    xc = vc->x;

    ya = va->y;
    yb = vb->y;
    yc = vc->y;

    if ((xa > maxx)||(xb > maxx)||(xc > maxx)||
	(ya > maxy)||(yb > maxy)||(yc > maxy)||
	(xa < 0)||(xb < 0)||(xc < 0)||
	(ya < 0)||(yb < 0)||(yc < 0)) t->clipped = TRUE;

    t = trigonlist.getNext();
  }

  dpop();
}

/* Load 3D-Studio ASC file */
void World::Object::loadAsc(char *c) {
  dpush2("World::Object::loadAsc(%s)", c);

  FILE *fp;
  char line[256];
  char temp[256];
  int size;
  int vtxcnt  = 0;
  int vectcnt = 0;
  float inx, iny, inz, inu, inv;
  int filesize;
  World::Trigon *t;

  if ((fp = fopen(c, "r"))) {
    fseek(fp, 0, SEEK_END);
    filesize = ftell(fp);
    rewind(fp);

    memset(line, 0, sizeof(line));

    while (!strstr(line, "Tri-mesh")) {
      while (!strstr(line, "Named object:"))
	fgets(line, 255, fp);
      sscanf(line, "%s%s%s", temp, temp, name);
      fgets(line, 255, fp);
    }

    debug3("[%s]: %ik 3D Studio mesh (ASC) {", c, filesize >> 10);
    
    sscanf(line, "%s%s%i%s%i", temp, temp, &numofverts, temp, &numoftrigons);
    numofnorms = numofverts + numoftrigons;

    debug2("  Name:     %s", name);
    debug2("  Vertices: %i", numofverts);
    debug2("  Trigons:  %i", numoftrigons);

    // print3("%i verts, %i faces ", numofverts, numoftrigons);

    size = numofverts * sizeof(Vertex);
    vertices = (Vertex *)myworld->getMem(size);       /* allocate memory for vertices */
    myworld->objectmem += size;

    size = numofverts * sizeof(Vector);
    vertvectors = (Vector *)myworld->getMem(size);    /* allocate memory for vectors */
    vertvectorsr = (Vector *)myworld->getMem(size);   /* translated vectors */
    myworld->objectmem += size * 2;

    for (int i = 0; i < numofverts; i++) {
      vertices[i].pos  = &vertvectors[i];
      vertices[i].posr = &vertvectorsr[i];
    }

    size = numofnorms * sizeof(Vector);
    normals = (Vector *)myworld->getMem(size);        /* allocate memory for normals */
    normalsr = (Vector *)myworld->getMem(size);       /* translated normals */
    myworld->objectmem += size * 2;

    while ((!strstr(line, "Mapped"))&&(!strstr(line, "Vertex list")))
      fgets(line, 255, fp);

    if (strstr(line, "Mapped") != NULL) {
      debug("  Object is mapped.\n");
      mapped = 1;
    } else {
      debug("  Unmapped object.\n");
      mapped = 0;
    }

    debug("  Reading vertices and trigons...\n");

    while (!strstr(line, "Vertex list:"))
      fgets(line, 255, fp);

    while (fgets(line, 255, fp) != NULL) {      
      if (strstr(line, "Vertex")) {                   /* Read vertex */
	inx = myworld->util.getValue(line, "X:") * vtxmul;          /* scale all vertices */
	iny = myworld->util.getValue(line, "Y:") * vtxmul;
	inz = myworld->util.getValue(line, "Z:") * vtxmul;

	vertices[vtxcnt].pos = &vertvectors[vectcnt];
	vertices[vtxcnt].posr = &vertvectorsr[vectcnt++];

	if (mapped) {
	  inu = myworld->util.getValue(line, "U:") * uvmul;
	  inv = myworld->util.getValue(line, "V:") * uvmul;
	  vertices[vtxcnt++].set(inx, iny, inz, inu, inv);
	} else
	  vertices[vtxcnt++].set(inx, iny, inz, 0.0, 0.0);
      }

      if (strstr(line, "Face")&&(strstr(line, "Face list:") == NULL)) {
	t = new Trigon();
	t->myworld = myworld;

	t->na = (int )myworld->util.getValue(line, "A:");           /* read a,b,c */
	t->nb = (int )myworld->util.getValue(line, "B:");
	t->nc = (int )myworld->util.getValue(line, "C:");
	t->ab = (int )myworld->util.getValue(line, "AB:");          /* read a-b, b-c, c-a */
	t->bc = (int )myworld->util.getValue(line, "BC:");
	t->ca = (int )myworld->util.getValue(line, "CA:");

	t->ap = &vertices[t->na];                     /* link pointers to vertices */
	t->bp = &vertices[t->nb];
	t->cp = &vertices[t->nc];

	t->ua = t->ap->u;                             /* set u,v values for corners */
	t->ub = t->bp->u;
	t->uc = t->cp->u;
	t->va = t->ap->v;
	t->vb = t->bp->v;
	t->vc = t->cp->v;

	t->txt = myworld->defaultmat;                 /* set to default texture */
	t->shademap = NULL;
	t->twosided = 0;                              /* assume poly is single sided */

	myworld->trigonmem += sizeof(Trigon);
	myworld->memtotal += sizeof(Trigon);
	myworld->numoftrigons++;
	trigonlist.addel(t);
      }
    }
  } else {
    error2("Error loading meshfile [%s]", c);
    dpop();
    return;
  }

  debug("}\n");

  setFillType(TEXTURE);                              /* set default filltype = texture */
  calcNormals();                                     /* calculate normals */
  makeBsphere();                                     /* create bounding sphere / box */

  dpop();
}

/* Load 3D-Studio 3DS file */
void World::Object::load3ds(char *c) {
  dpush2("World::Object::load3ds(%#010x)",(int)c);

  dpop();
}

/* create object matrix */
void World::Object::createMatrix() {
  dpush1("World::Object::createMatrix()");

  float cosx, cosy, cosz;
  float sinx, siny, sinz;
  float cosxsiny, sinxsiny;

  debug2("Creating matrix for [%s]", name);

  sinx = sin(rot.x);
  siny = sin(rot.y);
  sinz = sin(rot.z);
  cosx = cos(rot.x);
  cosy = cos(rot.y);
  cosz = cos(rot.z);

  cosxsiny = cosx * siny;
  sinxsiny = sinx * siny;

  matrix.genIdentity();

  matrix.m[0] = cosy     * cosz;                 /* x(1,0,0) */
  matrix.m[1] = sinxsiny * cosz - (cosx * sinz); /* x(0,1,0) */
  matrix.m[2] = cosxsiny * cosz + (sinx * sinz); /* x(0,0,1) */

  matrix.m[4] = cosy     * sinz;                 /* y(1,0,0) */
  matrix.m[5] = sinxsiny * sinz + (cosx * cosz); /* y(0,1,0) */
  matrix.m[6] = cosxsiny * sinz - (sinx * cosz); /* y(0,0,1) */

  matrix.m[8] = -siny;                            /* z(1,0,0) */
  matrix.m[9] = sinx * cosy;                      /* z(0,1,0) */
  matrix.m[10]= cosx * cosy;                      /* z(0,0,1) */

  matrix.m[3] += pos.x;
  matrix.m[7] += pos.y;
  matrix.m[11] += pos.z;

  if (parent == NULL)
    matrix.mulWith(&myworld->activecamera->matrix);
  else {
    if (!parent->donematrix)                      /* check if parent matrix is done */
      ((Object *)parent)->createMatrix();         /* if not -> do parent matrix */
    matrix.mulWith(&parent->matrix);
  }

  donematrix = TRUE;
  dpop();
}

/* translate object's bounding sphere */
World::Vector *World::Object::transBsph() {
  dpush1("World::Object::transBsph()");
  
  bsphere.set(matrix.m[3], matrix.m[7], matrix.m[11]);

  dpop();
  return &bsphere;
}

/* reset object */
void World::Object::reset() {
  dpush1("World::Object::reset()");

  for (int i = 0; i < numofverts; i++) {
    vertices[i].gouraud.set(&myworld->ambient);
    vertvectorsr[i].set(&vertvectors[i]);
  }

  for (int i = 0; i < numofnorms; i++)
    normalsr[i].set(&normals[i]);

  visible = TRUE;
  donematrix = 0;

  dpop();
}

/* scale object */
void World::Object::scaleTo(float n) {
  dpush2("World::Object::scaleTo(%3.1f)",n);

  float length = 0.0;
  float nn = 0.0;

  for (int i = 0; i < numofverts; i++) {
    length += vertvectors[i].length();
    nn++;
  }

  if ((nn == 0) || (length == 0)) {
    error("Division by zero in scaleTo.");
    nn = 1.0;
    length = 1.0;
  }

  length /= nn;                                 /* length = average vertex */
  length = n / length;

  for (int i = 0; i < numofverts; i++)
    vertvectors[i].mult(length);

  bsrad = 0;

  for (int i = 0; i < numofverts; i++) {
    length = vertvectors[i].length();
    if (length > bsrad) bsrad = length;
  }

  dpop();
}

/* make object bounding sphere */
void World::Object::makeBsphere() {
  dpush1("World::Object::makeBsphere()");

  float length = 0;

  bsrad = 0;
  for (int i = 0; i < numofverts; i++) {
    length = vertvectors[i].length();
    if (length > bsrad) bsrad = length;
  }

  dpop();
}

/* redefine (u,v) mapping coordinates */
void World::Object::redefMapping(float n) {
  dpush2("World::Object::redefMapping(%6.2f)",n);

  for (int i = 0; i < numofverts; i++) {
    vertices[i].u = 128.0 + (normals[i].x * n);
    vertices[i].v = 128.0 + (normals[i].y * n);
  }
  
  dpop();
}

/* multiply all normals by -1 */
void World::Object::flipNormals() {
  dpush1("World::Object::flipNormals()");

  World::Trigon *t = trigonlist.getFirst();
  World::Vertex *tmp;
  int n;

  for (int i = 0; i < numofnorms; i++)
    normals[i].mult((float )-1);

  while (t) {
    tmp = t->ap;
    t->ap = t->bp;
    t->bp = tmp;

    n = t->na;
    t->na = t->nb;
    t->nb = n;

    t = trigonlist.getNext();
  }

  dpop();
}

/* calculate gouraud light values

   Light->intens = how far away can a light be, and still have effect?

   Intensity:

   is = ((light normal) DOT -(surface normal)) * maxcolor

   Correction for distance:

   is *= 1-(distance/range);
*/
void World::Object::calcGouraudValues() {

  dpush1("World::Object::calcGouraudValues()");

  World::Light *l;                              /* Do light calculations */
  float  il;                                    /* light intensity at normal */
  float  *r, *g, *b;
  float  is;
  float  n, nr, ng, nb;
  float  x1,x2,y1,y2,z1,z2;
  float  length;
  float  divlength;

  if (!on) {
    dpop();
    return;
  }
  if (!visible) {
    dpop();
    return;
  }

  n = 0;
  l = myworld->lightlist.getFirst();

  while (l) {                            // for each light
    if (l->on) {
      /* printf("found a light.\n"); */
      il = l->range * 1;

      /* sdf */
      x1 = l->posr.x - bspherer.x;
      y1 = l->posr.y - bspherer.y;
      z1 = l->posr.z - bspherer.z;

      length = sqrt((x1*x1) + (y1*y1) + (z1*z1)) - bsrad;

      if (length < il) {
        for (int i = 0; i < numofverts; i++) {
          x1 = normalsr[i].x;
          y1 = normalsr[i].y;
          z1 = normalsr[i].z;

          x2 = l->posr.x - vertvectorsr[i].x;
          y2 = l->posr.y - vertvectorsr[i].y;
          z2 = l->posr.z - vertvectorsr[i].z;

          length = sqrt((x2*x2) + (y2*y2) + (z2*z2));

          if ((length < il)&&(length != 0.0)) {
            divlength = (float )1.0 / length;
            x2 *= divlength;
            y2 *= divlength;
            z2 *= divlength;
            is = (x1*x2) + (y1*y2) + (z1*z2);
            is *= (1 - (length / il));
          } else
            is = 0.0;

	  if (length == 0) {
	    error("Division by zero in calcGour");
	  }

          if (is < 0) is = 0;
          if (is > 1) is = 1;

          nr = l->color.r * is * 255.0;
          ng = l->color.g * is * 255.0;
          nb = l->color.b * is * 255.0;
	  
	  r = &vertices[i].gouraud.r;
	  g = &vertices[i].gouraud.g;
	  b = &vertices[i].gouraud.b;

          *r += nr; if (*r > 255.0) *r = 255.0;
          *g += ng; if (*g > 255.0) *g = 255.0;
          *b += nb; if (*b > 255.0) *b = 255.0;
        }
      }
    }
    l = myworld->lightlist.getNext();
  }

  dpop();
}

/* print out object information */
void World::Object::printOut() {
  dpush1("World::Object::printOut()");

  print2("Object [%#010x] printout:\n", (int)this);
  print2("  Name:     [%s]\n", name);
  print2("  Parent:   [%s]\n", parent ? parent->name : myworld->util.charptr("none"));
  print2("  Vertices: %i\n", numofverts);
  print2("  Trigons:  %i\n", numoftrigons);
  print2("  Filltype: %s\n", myworld->util.fillstr(filltype));
  print2("  Material: %s\n", txt ? txt->name : myworld->util.charptr("none"));
  print4("  Position: (%6.2f, %6.2f, %6.2f)\n", pos.x, pos.y, pos.z);
  print4("  Rotation: (%6.2f, %6.2f, %6.2f)\n", rot.x, rot.y, rot.z);
  dpop();
}







