/*
 * FlatDevice.C - zBuffer 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 "FlatDevice.h"
#include "Polygon.h"
#include "Sphere.h"

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

//___________________________________________________________ PolyWithColor

class PolyWithColor {
friend class FlatDevice;
public:
  PolyWithColor()
  : p(NULL) {}
  PolyWithColor(Polygon* poly, const Color& color)
  : p(poly), c(color) {}
  ~PolyWithColor() { if (p) delete p; }

private:
  Polygon* p;
  Color c;
};

typedef PolyWithColor* PolyWithColorPtr;
declareList(PolyWithColorList, PolyWithColorPtr);
implementList(PolyWithColorList, PolyWithColorPtr);

//___________________________________________________________ FlatDevice

FlatDevice::FlatDevice(Options* options)
: DeviceDriver(options), 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];

  real alpha;
  for (register int i=0; i<coneResolution; i++) {
    alpha = (2*M_PI*i)/coneResolution;
    SinCos(alpha, sintable[i], costable[i]);
  }
  top = new Vector[coneResolution];
  bottom = new Vector[coneResolution];
  
  /*
   * Generate unit sphere.
   */
  unitSphere = Sphere::tesselation(Vector(0,0,0), 1, sphereResolution);

  /*
   * Initialize z-buffer renderer
   */
  zBuffer = new Z_Buffer(new ViewTransform(theOptions->eye, 
					   theOptions->lookat, 
					   theOptions->up, theOptions->fov,
					   theOptions->resX, theOptions->resY),
			 "Image rendered with graphtal",
			 theOptions->oname);
}

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

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

  delete zBuffer;

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

void FlatDevice::begin(){}

void FlatDevice::end(const BoundingBox&)
{
  if (theOptions->verbose)
    cerr << "primitives: "<< zBuffer->primitives() << '\n';

  zBuffer->writePixmap();
}

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

void FlatDevice::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;

  /*
   * Find 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++) {
    if (definingMacro) {
      Polygon* p = new Polygon(*(bottom+i), *(top+i), 
			       *(top+((i+1)%coneResolution)), 
			       *(bottom+((i+1)%coneResolution)));
      PolyWithColor* pwc = new PolyWithColor(p, currentColor);
      polys->append(pwc);
    }
    else
      zBuffer->renderRectangle(currentColor,
			       *(bottom+i), *(top+i), 
			       *(top+((i+1)%coneResolution)), 
			       *(bottom+((i+1)%coneResolution)));
  }
}

void FlatDevice::polygon(Polygon* p)
{
  if (definingMacro) {
    PolyWithColor* pwc = new PolyWithColor(p, currentColor);
    polys->append(pwc);
  }
  else 
    zBuffer->renderPolygon(currentColor, p);
}

void FlatDevice::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) {
      PolyWithColor* pwc = new PolyWithColor(transformedPoly, currentColor);
      polys->append(pwc);
    }
    else
      zBuffer->renderPolygon(currentColor, transformedPoly);
  }
}

void FlatDevice::color(const Color& color)
{
  currentColor = color;
}

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

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

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

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

    if (definingMacro) {
      PolyWithColor* pwc = new PolyWithColor(p, polyList->item(i)->c);
      polys->append(pwc);
    }
    else
      zBuffer->renderPolygon(polyList->item(i)->c, p);
  }
}

