/* This file is part of `mogul'
 * Copyright (c) 1995 David Deaven
 *
 * In the following, "you" and "You" refer to any entity other than 
 * the Microsoft corporation and its subsidiaries.  All rights are
 * reserved by the author in connection with Microsoft and its
 * subsidiaries.  The Microsoft Network is explicity denied the right
 * to copy, reproduce, or transmit this software.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Author: deaven $
 * $Id: plot.c,v 2.0 1996/07/09 20:59:55 deaven Exp deaven $
 */

/* Device-independent basic drawing operations.
 * Copyright (c) 1994 David Deaven
 */
#include <stdio.h>
#include <math.h>
#include "dmd/tools.h"
#include "dmd/dmdtimer.h"
#include "dmd/random.h"
#include "dmd/memory.h"
#include "mogul.h"
#include "site.h"

#define TWOPI (2.0 * M_PI)
#define ZERO 0.0

#define CROSS(U,V,W) { \
      (W)[0] =  (U)[1] * (V)[2] - (V)[1] * (U)[2]; \
      (W)[1] = -(U)[0] * (V)[2] + (V)[0] * (U)[2]; \
      (W)[2] =  (U)[0] * (V)[1] - (V)[0] * (U)[1]; }

/* Half-moon shading mode for atoms using multiple lines.
 */
static int plot_moon(MogulPlot p, MogulDevice d, 
  double x[3], double u[3], double v[3], double r)
{
	int i;
	double s, c, t, e;
	double z[3];
	double s0, c0;

	ENTER("plot_moon");

	s0 = sin(ATOM_MOON_THETA+0.5*M_PI);
	c0 = cos(ATOM_MOON_THETA+0.5*M_PI);
	d->Pcolor(p->attr.foreground, d->instance);
	for(i=0; i<ATOM_MOON_LINES; i++) {
	  t = ((double) i) * (M_PI / ATOM_MOON_LINES);
	  s = r * sin(t + ATOM_MOON_THETA);
	  c = r * cos(t + ATOM_MOON_THETA);
	  e = r * ATOM_MOON_ECC * sin(t);
	  z[0] = x[0] + c*u[0] + s*v[0];
	  z[1] = x[1] + c*u[1] + s*v[1];
	  z[2] = x[2] + c*u[2] + s*v[2];
	  d->Pmove(z, d->instance);
	  z[0] -= e*(c0*u[0] + s0*v[0]);
	  z[1] -= e*(c0*u[1] + s0*v[1]);
	  z[2] -= e*(c0*u[2] + s0*v[2]);
	  d->Pline(z, d->instance);
	}

	LEAVE();

	return MogulErrno;
}

/* Render object obj in plot p on device d.
 */
static int plot(MogulPlot p, int obj, MogulDevice d)
{
	MogulObject o = p->object[obj].o;

	switch (o->type) {



	  /* Atom */
	  case O_ATOM:
	  { double
	      r = o->part.atom.radius,
	      *x = p->tVertex + 3 * o->v[0];
	    static double
	      u[3] = {1.0, 0.0, 0.0},
	      v[3] = {0.0, 1.0, 0.0};

	    ENTER("plot: atom");

	    d->Pcolor(o->attr.color, d->instance);
	    /* printf("atom %d color %d %d %d\n", obj, o->attr.color.r, o->attr.color.g,
	    o->attr.color.b); */

	    if(o->attr.mode & MA_MINIMAL) {
	      d->Pmove(x, d->instance);
	      d->Pcircle(x, r, d->instance);
	      d->Pclose(d->instance);
	      if(o->attr.mode & MA_OUTLINE) {
		d->Pcolor(p->attr.foreground, d->instance);
		d->Pcircle(x, r, d->instance);
	      }
	    }
	    else
	    {
	      if(o->attr.mode & MA_FILLED)
	      {
		/* fill the circle */
		int i;
		double w[3];
		for(i=0; i<3; i++) w[i] = x[i] + r * u[i];
		d->Pmove(w, d->instance);
		d->Parc(x, r, u, v, ZERO, TWOPI, d->instance);
	      }
	      else if(o->attr.mode & MA_SOLID)
	      {
		/* Device-dependent realistic shading */
		d->Pshade(x, r, u, v, d->instance);
	      }
	      else if(o->attr.mode & MA_TRANSLUCENT)
	      {
		/* make a translucent effect (expensive) */
		int i, j;
		double dr, rr;
		double uu[3], vv[3], ww[3], xx[3];
		for(i=0; i<3; i++) uu[i] = ran3() - 0.5;
		Norm(uu);
		CROSS(uu, u, vv);
		Norm(vv);
		CROSS(uu, vv, ww);
		for(j=1; j<16; ++j) {
		  dr = j * 2.0 / 16.0 * r - r;
		  rr = sqrt(r * r - dr * dr);
		  for(i=0; i<3; i++) xx[i] = x[i] + dr * ww[i];
		  for(i=0; i<3; i++) xx[i] += rr * uu[i];
		  d->Pmove(xx, d->instance);
		  for(i=0; i<3; i++) xx[i] -= rr * uu[i];
		  d->Parc(xx, rr, uu, vv, ZERO, TWOPI, d->instance);
		}
	      }

	      if(o->attr.mode & MA_OUTLINE)  {
		/* outline the circle */
		int i;
		double w[3];
		for(i=0; i<3; i++) w[i] = x[i] + r * u[i];
		d->Pcolor(p->attr.foreground, d->instance);
		d->Pmove(w, d->instance);
		d->Parc(x, r, u, v, ZERO, TWOPI, d->instance);
	      }

	      if(o->attr.mode & MA_MOON) {
		d->Pcolor(p->attr.foreground, d->instance);
		plot_moon(p, d, x, u, v, r);
	      }
	    }

	    LEAVE();
	  }
	  break;



	  /* Bond */
	  case O_BOND:
	  {
	    double
	      *x = p->tVertex + 3 * o->v[0],
	      *y = p->tVertex + 3 * o->v[1];

	    ENTER("plot: bond");

	    d->Pcolor(o->attr.color, d->instance);

	    if(p->speedy & SPEEDY_MODE || o->attr.mode & MA_MINIMAL)
	    {
	      d->Pmove(x, d->instance);
	      d->Pline(y, d->instance);
	    }
	    else
	    {
	      int i, m;
	      double
		theta,
		u[3], v[3], w[3],
		r = o->part.bond.radius;

	      /* get normalized internuclear vector */
	      for(m=0; m<3; m++)
		u[m] = (y[m] - x[m]) / o->part.bond.length;

	      /* get local orthogonal coordinate system.
		 v[], w[] are perpendicular to u[]. */
	      i = 0;
	      do {
		for(m=0; m<3; m++)
		  v[m] = u[m] + r * ((double) i++);
		CROSS(u, v, w);
	      } while (Norm(w) == 0.0); /* never happens! */
	      CROSS(u, w, v);

	      /* Next, rotate v[] and w[] around u[] so that 
	         v[] has no z-component, so it lies in the
		 plane of the camera lens, and w[] is perpendicular
		 to the lens. */
	      if( v[2] != 0.0 ) {
		if( w[2] == 0.0 ) {
		  double swap;
		  for(m=0; m<3; m++) {
		    swap = v[m]; v[m] = w[m]; w[m] = swap;
		  }
		} else {
		  double s, a = -v[2] / w[2];
		  s = 1.0 / sqrt(1.0 + a*a); 
		  for(m=0; m<3; m++) v[m] = s * (v[m] + a*w[m]);
		  CROSS(u, v, w);
		}
	      }

	      /* u[] is normalized internuclear vector.  The bond
	         starts at x[] and is length long with radius r,
	         ending at y[];  v[] and w[] are perpendicular to u[].  */

	      if(o->attr.mode & MA_SOLID)
	      {
		/* Draw a solid cylinder between the two atoms.
		   The trigonometry is a little tricky here.  Follow 
		   on paper by drawing a horizontal cylinder with
		   rounded ends.  u[] points from left to right,
		   v[] and w[] are perpendicular to u[], v[] has no
		   z-component.  */

		double z[3];

		if(w[2] < 0.0)
		   for(m=0; m<3; m++) w[m] *= -1.0;

		for(m=0; m<3; m++)
		  z[m] = x[m] + r * v[m]; /* lower left */

		d->Pmove(z, d->instance);
		d->Parc(y, r, v, w, ZERO, M_PI, d->instance); /* right arc */
		d->Parc(x, r, v, w, M_PI, ZERO, d->instance); /* left arc */
		d->Pclose(d->instance);

		if(o->attr.mode & MA_OUTLINE) {
		  d->Pcolor(p->attr.foreground, d->instance);
		  d->Pmove(z, d->instance);
		  d->Parc(y, r, v, w, ZERO, M_PI, d->instance); /* right arc */
		  d->Parc(x, r, v, w, M_PI, ZERO, d->instance); /* left arc */
		  d->Pline(z, d->instance);
		}
	      }
	      else if(o->attr.mode & MA_FILLED)
	      {
		/* draw a rectangle in the camera lens plane */

		double z[4][3];

		for(m=0; m<3; m++) {
		  z[0][m] = x[m] + r * v[m];
		  z[1][m] = x[m] - r * v[m];
		  z[2][m] = y[m] - r * v[m];
		  z[3][m] = y[m] + r * v[m];
		}

		d->Pmove(z[0], d->instance);
		d->Pline(z[1], d->instance);
		d->Pline(z[2], d->instance);
		d->Pline(z[3], d->instance);
		d->Pclose(d->instance);

		if(o->attr.mode & MA_OUTLINE) {
		  d->Pcolor(p->attr.foreground, d->instance);
		  d->Pmove(z[0], d->instance);
		  d->Pline(z[1], d->instance);
		  d->Pline(z[2], d->instance);
		  d->Pline(z[3], d->instance);
		  d->Pline(z[0], d->instance);
		}
	      }
	      else if(o->attr.mode & MA_STICK)
	      {
		/* Plot a series of lines around the bond cylinder */

		double d1, d2, z[3];

		d->Pcolor(p->attr.foreground, d->instance);
		for(i=0; i<(r==0.0 ? 1 : BOND_STICK_LINES); i++) {
		  theta = (double) i * TWOPI / (double) BOND_STICK_LINES;
		  d1 = r * sin(theta);
		  d2 = r * cos(theta);
		  for(m=0; m<3; m++) z[m] = x[m] + d1*v[m] + d2*w[m];
		  d->Pmove(z, d->instance);
		  for(m=0; m<3; m++) z[m] += o->part.bond.length * u[m];
		  d->Pline(z, d->instance);
		}
	      }

	    }
	    LEAVE();
	  }
	  break;



	  /* Line */
	  case O_LINE:
	  {
	    ENTER("plot: line");

	    if(p->speedy & SPEEDY_MODE) {
	      if(p->speedy & SPEEDY_LINES)
	      {
		if(p->nSkip++ % p->speedySkip) break;
	      }
	      else
		break;
	    }

	    d->Pcolor(o->attr.color, d->instance);

	    d->Pmove(p->tVertex + 3 * o->v[0], d->instance);
	    d->Pline(p->tVertex + 3 * o->v[1], d->instance);

	    LEAVE();
	  }
	  break;



	  /* Label */
	  case O_LABEL:
	  {
	    ENTER("plot: label");
	    d->Pcolor(o->attr.color, d->instance);
	    d->Pmove(p->tVertex + 3 * o->v[0], d->instance);
	    d->Ptext(o->part.label.text, o->part.label.height, d->instance);
	    LEAVE();
	  } 
	  break;



	  /* Unknown object type */
	  default:
	  {
	    fprintf(stderr, "plot(): unexpected object type %d\n", (int) o->type);
	    exit(1);
	  }
	  break;
	}

	return MogulErrno;
}

/* Put the objects in distance order from the viewer
 * and plot them.  The idea is that given a constructed plot, this
 * operation may be done as many times as necessary on different
 * devices, or on the same device.  The camera settings may change
 * between calls.
 */
int MogulRender(MogulPlot p, MogulDevice d)
{
	int i;

	if(!p->tCurrent || CameraWasUpdated(p->camera)) 
	  MogulTransformObjects(p);

	if(d->Pstart(p, d->instance)) return -1;

	if(p->speedy & SPEEDY_MODE) {
	  p->speedySkip = 4;
	  p->nSkip = 0;
	}

	for(i=0; i<p->nObject; i++) {
	  if(p->object[i].distance >= 0) {
	    if(plot(p, i, d)) return -1;
	  }
	}

	if(d->Pend(p, d->instance)) return -1;

	return 0;
}

/* Allocate space for a plot.
 */
MogulPlot NewMogulPlot(char *author)
{
	MogulPlot p = NewPointer(p);

	p->camera = NewCamera();
	p->attr.width = 1.0;
	p->attr.height = 1.0;
	p->attr.vdist = 0.0;
	p->attr.foreground = ColorFromName("black");
	p->attr.background = ColorFromName("white");

	p->nObjectMax = p->nObject = 0;
	p->object = NULL;
	p->nextObjectID = 1;

	p->nVertexMax = p->nVertex = 0;
	p->vertex = p->tVertex = NULL;
	p->tCurrent = 1;

	p->speedy = 0;

	p->sp = NewStringPool();

	p->author = AddToStringPool(p->sp, author);

	return p;
}

void DeleteMogulPlot(MogulPlot p)
{
	if(p) {
	  MogulClearObjects(p);
	  myfree(p->vertex);
	  myfree(p->tVertex);
	  DeleteCamera(p->camera);
	  DeleteStringPool(p->sp);
	  myfree(p);
	}
}

