/*
 * WireDevice.C - wire frame device driver.
 *
 * Copyright (C) 1992, Christoph Streit (streit@iam.unibe.ch)
 *                     University of Berne, Switzerland
 * 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".
 *
 */

#include "WireDevice.h"
#include "Polygon.h"
#include "Sphere.h"

static const int defaultConeResolution = 3;
static const int defaultSphereResolution = 4;

//___________________________________________________________ WireDevice

WireDevice::WireDevice(BaseWindow* window, Options* options)
: DeviceDriver(options), w(window), polys(NULL)
{
  coneResolution = (theOptions->coneResolution <= 0) 
    ? defaultConeResolution
    : theOptions->coneResolution;
  sphereResolution = (theOptions->sphereResolution <= 0) 
    ? defaultSphereResolution
    : theOptions->sphereResolution;

  /*
   * Make up table for cone generation.
   */
  sintable = new real[coneResolution];
  costable = new real[coneResolution];

  for (register int i=0; i<coneResolution; i++) {
    real alpha = (2*M_PI*i)/coneResolution;

    sintable[i] = sin(alpha);
    costable[i] = cos(alpha);
  }
  top = new Vector[coneResolution];
  bottom = new Vector[coneResolution];

  /*
   * Generate unit sphere.
   */
  unitSphere = Sphere::tesselation(Vector(0,0,0), 1, sphereResolution);
}

WireDevice::~WireDevice()
{
  delete [] sintable;
  delete [] costable;
  delete [] top;
  delete [] bottom;
  delete w;

  for (register long i=0; i<unitSphere->count(); i++)
    delete unitSphere->item(i);
  delete unitSphere;

  StringTable_Iterator macroItr(macroNames);
  while(macroItr.more()){
    PolygonList* polys = (PolygonList*)macroItr.cur_value();
    for (i = 0; i<polys->count(); i++)
      delete polys->item(i);
    delete polys;
    macroItr.next();
  } 
}

void WireDevice::begin()
{
  if (!theOptions->autoscale) {
    w->disableBuffering();
    w->setView(theOptions->eye, theOptions->lookat, theOptions->up, 
	       theOptions->fov);
  }
  w->open(theOptions->resX, theOptions->resY, "L-System " + LSystemName);
  if (theOptions->autoscale)
    w->writeText("Please wait ...", 
		 theOptions->resX/2-40, theOptions->resY/2-5);
}

void WireDevice::end(const BoundingBox& b)
{
  char key;

  if (theOptions->verbose)
    cerr << "primitives: "<< primitives << '\n';
  if (theOptions->autoscale) {
    w->setView(b, theOptions->up, theOptions->fov);
    w->clear();
  }
  w->flush();
  do {
    key = w->waitForKey();
  } while(key != 'q' && key != 'Q');
  w->close();
}

void WireDevice::cylinder(const Vector& p1, const Vector& p2, real r)
{
  cone(p1, r, p2, r);
}

void WireDevice::cone(const Vector& p1, real r1, const Vector& p2, real r2)
{
  Vector axis = p2-p1;

  /*
   * Degenerated cone?
   */
  if (axis.normalize() == 0)
    return;

  Vector u, v;
  
  /*
   * Calculate 2 vectors normal to cone axis and to each other.
   */
  u[0] = -axis[1]; u[1] =  axis[0]; u[2] = 0;
  if (u.normalize() == 0) {
    u[0] = axis[2]; u[1] = 0;  u[2] = -axis[0];
    u.normalize();
  }
  v = axis*u;

  Vector d;
  for (register int i=0; i<coneResolution; i++) {
    d = costable[i]*u + sintable[i]*v;
    *(bottom+i) = p1 + d*r1;
    *(top+i)    = p2 + d*r2;   
  }
	    
  for (i=0; i<coneResolution; i++) {
    Polygon* p = new Polygon(*(bottom+i), *(top+i), 
			     *(top+((i+1)%coneResolution)), 
			     *(bottom+((i+1)%coneResolution)));
    if (definingMacro)
      polys->append(p);
    else {
      primitives++;
      w->polygon(p);
    }
  }
}

void WireDevice::polygon(Polygon* p)
{
  if (definingMacro) 
    polys->append(p);
  else {
    primitives++;
    w->polygon(p);
  }
}

void WireDevice::sphere(const Vector& pos, real r)
{
  /*
   * Transform the unit sphere to radius r and position pos.
   */
  for (register long i=0; i<unitSphere->count(); i++) {
    Polygon* p = unitSphere->item(i);
    Polygon* transformedPoly = new Polygon(p->numVertices());
    for (register long j=0; j<p->numVertices(); j++)
      transformedPoly->addVertex(p->vertex(j)*r+pos); 

    if (definingMacro)
      polys->append(transformedPoly);
    else {
      primitives++;
      w->polygon(transformedPoly);
    }
  }
}

void WireDevice::beginMacro(const rcString& macroName)
{
  definingMacro = 1;
  currentMacroName = macroName;
  polys = new PolygonList(100);
}

void WireDevice::endMacro()
{
  definingMacro = 0;
  macroNames.find_and_replace(currentMacroName, polys);
  polys = NULL;
}

void WireDevice::executeMacro(const rcString& macroName, 
			      const TransMatrix& tmat)
{
  anyPtr argument;
  if (!macroNames.lookup(macroName, argument))
    Error(ERR_PANIC, "WireDevice::executeMacro: macro " 
	             + macroName + " does not exist");

  PolygonList* polyList = (PolygonList*) argument;
  for (register long i=0; i<polyList->count(); i++) {
    Polygon* p = new Polygon(*polyList->item(i));
    p->transform(tmat);

    if (definingMacro)
      polys->append(p);
    else {
      primitives++;
      w->polygon(p);
    }
  }
}
