/* 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: object.c,v 2.0 1996/07/09 20:59:55 deaven Exp $
 */

#include <math.h>
#include <stdlib.h>
#include "dmd/memory.h"
#include "dmd/tools.h"
#include "mogul.h"

struct commonAttributes *MogulDefaultAttributes(MogulPlot p,
  struct commonAttributes *attr)
{
	attr->mode = MA_MINIMAL | MA_FILLED | MA_OUTLINE;
	attr->color = p->attr.foreground;
	return attr;
}

/* Add one vertex and return the index of it.
 */
static int add_vertex(MogulPlot p, double x[3])
{
	if(p->nVertex + 1 > p->nVertexMax) {
	  p->nVertexMax += 64;
	  p->vertex = ResizePointerArray(p->vertex, p->nVertexMax * 3);
	  p->tVertex = ResizePointerArray(p->tVertex, p->nVertexMax * 3);
	}

	p->vertex[3 * p->nVertex    ] = x[0];
	p->vertex[3 * p->nVertex + 1] = x[1];
	p->vertex[3 * p->nVertex + 2] = x[2];

	p->tCurrent = 0;

	return (p->nVertex)++;
}

/* Add one object to the plot.
 */
static void add_object(MogulPlot p, MogulObject o)
{
	if(p->nObject + 1 > p->nObjectMax) {
	  p->nObjectMax += 64;
	  p->object = ResizePointerArray(p->object, p->nObjectMax);
	}
	p->object[p->nObject].distance = 0;
	p->object[p->nObject].o = o;
	p->nObject++;
}

static int obj_cmp(struct orderedObject *a, struct orderedObject *b)
{
	return (a->distance < b->distance ? -1 :
	        a->distance > b->distance ? +1 : 0);
}

/* Determine the center and extent of the current vertices.
 * Return the number of vertices.
 */
int MogulObjectExtent(MogulPlot p, double center[3], double *r)
{
	int i, m;
	double r2[3];

	for(m=0; m<3; m++) {
	  center[m] = 0.0;
	  r2[m] = 0.0;
	}

	for(m=0; m<3; m++) {
	  for(i=0; i<p->nVertex; i++) {
	    center[m] += p->vertex[3*i + m];
	    r2[m] += p->vertex[3*i + m] * p->vertex[3*i + m];
	  }
	}

	for(m=0; m<3; m++) {
	  center[m] /= p->nVertex;
	  r2[m] /= p->nVertex;
	}

	(*r) = 0.0;
	for(m=0; m<3; m++) (*r) += r2[m] - center[m] * center[m];
	(*r) = 2.0 * sqrt(*r);

	return p->nVertex;
}

/* transform all of the objects, and reorder them in plot order.
 */
void MogulTransformObjects(MogulPlot p)
{
	int i;
	double dist, dmin = 0.0, dmax = 0.0, s, t[4*4];

	CameraGetTransform(p->camera, t);
	for(i=0; i<p->nVertex; i++) {
	  Transf(p->vertex + 3*i, t, p->tVertex + 3*i);
	  s = p->tVertex[3*i + 2];
	  if(i == 0 || s < dmin) dmin = s;
	  if(i == 0 || s > dmax) dmax = s;
	}
	s = dmax - dmin;
	if(s > 1.e-10) s = 65535.0 / s;

	for(i=0; i<p->nObject; i++) {
	  switch(p->object[i].o->type) {
	    case O_BOND:
	    case O_LINE:
	      dist = 0.5 * (
		p->tVertex[3 * p->object[i].o->v[0] + 2] +
		p->tVertex[3 * p->object[i].o->v[1] + 2] );
	      break;

	    case O_ATOM:
	    case O_LABEL:
	    default:
	      dist = p->tVertex[3 * p->object[i].o->v[0] + 2];
	      break;
	  }
	  p->object[i].distance = s * (dist - dmin);
	}

	qsort(p->object, p->nObject, sizeof(*(p->object)), obj_cmp);

	p->tCurrent = 1;
}

/* Remove all objects (and vertices) from the plot.
 */
void MogulClearObjects(MogulPlot p)
{
	p->nObject = 0;
	p->nVertex = 0;
	p->tCurrent = 1;

	p->nextObjectID = 0;
}

/* Add an atom to the plot, return the object ID.
 * Return negative for error.
 */
int MogulAddAtom(MogulPlot p, struct commonAttributes *attr,
	double x[3], double radius)
{
	int v = add_vertex(p, x);
	MogulObject o = NewPointer(o);

	o->type = O_ATOM;
	o->attr = *attr; /* memcpy */
	o->id = p->nextObjectID++;
	o->v[0] = v;
	o->part.atom.radius = radius;

	add_object(p, o);

	return o->id;
}

/* Add a bond to the plot.
 * Return negative for error.
 */
int MogulAddBond(MogulPlot p, struct commonAttributes *attr,
	double x[3], double y[3], double radius)
{
	int i, v0 = add_vertex(p, x), v1 = add_vertex(p, y);
	double length = 0.0;
	MogulObject o = NewPointer(o);

	for(i=0; i<3; i++)
	  length += (x[i] - y[i]) * (x[i] - y[i]);
	length = sqrt(length);

	o->type = O_BOND;
	o->attr = *attr; /* memcpy */
	o->id = p->nextObjectID++;
	o->v[0] = v0;
	o->v[1] = v1;
	o->part.bond.radius = radius;
	o->part.bond.length = length;

	add_object(p, o);

	return o->id;
}

/* Add a bond to the plot between two atoms, positioned at x1[] and
 * x2[] with radii r1 and r2.  Don't add a bond if the atomic 
 * spheres overlap (return -1 in this case).
 * Return negative for error.
 */
int MogulAddBondBetween(MogulPlot p, struct commonAttributes *attr,
	double x1[3], double r1, double x2[3], double r2, double radius)
{
	int m, id = -1;
	double u[3], x[3], y[3];
	double d1, d2, au;

	/* Get internuclear vector u[].
	 */
        au = 0.0;
        for(m=0; m<3; m++) {
          u[m] = x2[m] - x1[m];
          au += u[m] * u[m];
	}
	au = sqrt(au);
	for(m=0; m<3; m++) u[m] /= au;

	/* Adjust the length of u[], au, to span just between the 
	 * spherical surfaces of the two atoms, and determine 
	 * the origin point on the first atomic sphere.
	 */
        d1 = (radius > r1 ? 0.0 : sqrt(r1*r1 - radius*radius));
        d2 = (radius > r2 ? 0.0 : sqrt(r2*r2 - radius*radius));

        if(au > (d1 + d2)) { /* non-overlapping spheres */
	  for(m=0; m<3; m++) {
	    x[m] = x1[m] + d1 * u[m];
	    y[m] = x2[m] - d2 * u[m];
	  }
	  id = MogulAddBond(p, attr, x, y, radius);
	}

	return id;
}

/* Add a line to the plot.
 * Return negative for error.
 */
int MogulAddLine(MogulPlot p, struct commonAttributes *attr,
	double x[3], double y[3])
{
	int v0 = add_vertex(p, x), v1 = add_vertex(p, y);
	MogulObject o = NewPointer(o);

	o->type = O_LINE;
	o->attr = *attr; /* memcpy */
	o->id = p->nextObjectID++;
	o->v[0] = v0;
	o->v[1] = v1;

	add_object(p, o);

	return o->id;
}

/* Add a text label to the plot.
 * Return negative for error.
 */
int MogulAddLabel(MogulPlot p, struct commonAttributes *attr,
	double x[3], char *text, double height)
{
	int v = add_vertex(p, x);
	MogulObject o = NewPointer(o);

	o->type = O_LABEL;
	o->attr = *attr; /* memcpy */
	o->id = p->nextObjectID++;
	o->v[0] = v;
	o->part.label.text = AddToStringPool(p->sp, text);
	o->part.label.height = height;

	add_object(p, o);

	return o->id;
}

/* Lines can be drawn in multiple segments to allow line hiding.
 * Use the plot size to determine how many segments to draw.
 * Return negative for error.
 */
int MogulAddSegmentedLine(MogulPlot p, struct commonAttributes *attr,
	 double a[3], double b[3])
{
	int i, m, n;
	double dx[3], x[3], y[3];
	double d = 0.0;

	for(m=0; m<3; m++) 
	{
	  dx[m] = b[m] - a[m];
	  d += dx[m] * dx[m];
	}
	d = sqrt(d);
	for(m=0; m<3; m++) dx[m] /= d;

	n = 16 * d / (p->attr.width + p->attr.height) + 2;
	d /= ((double) n);

	for(i=0; i<n; i++)
	{
	  for(m=0; m<3; m++)
	  {
	    x[m] = a[m] + i * d * dx[m];
	    y[m] = x[m] + d * dx[m];
	  }
	  MogulAddLine(p, attr, x, y);
	}

	return 0;
}

#ifdef DEBUG
void debugObjects(MogulPlot p)
{
	int obj;
	MogulObject o;

	for(obj=0; obj<p->nObject; obj++) {
	  o = p->object[obj].o;
	  printf("%d: m%d c=\"%s\" ", obj + 1, o->attr.mode, ColorName(o->attr.color));
	  switch (o->type) {

	    case O_ATOM:
	    { double
		r = o->part.atom.radius,
		*x = p->vertex + 3 * o->v[0];
	      printf("atom r=%g (%g,%g,%g) ", r, x[0], x[1], x[2]);
	    }
	    break;

	    case O_BOND:
	    {
	      double
		*x = p->vertex + 3 * o->v[0],
		*y = p->vertex + 3 * o->v[1],
		r = o->part.bond.radius;
	      printf("bond r=%g (%g,%g,%g) -- (%g,%g,%g) ", r, x[0], x[1], x[2], y[0], y[1], y[2]);
	    }
	    break;

	    case O_LINE:
	    {
	      double
		*x = p->vertex + 3 * o->v[0],
		*y = p->vertex + 3 * o->v[1];
	      printf("line (%g,%g,%g) -- (%g,%g,%g) ", x[0], x[1], x[2], y[0], y[1], y[2]);
	    }
	    break;

	    case O_LABEL:
	    {
	      double
		*x = p->vertex + 3 * o->v[0],
		h = o->part.label.height;
	      char *s = o->part.label.text;
	      printf("label h=%g (%g,%g,%g) \"%s\" ", h, x[0], x[1], x[2], s);
	    } 
	    break;

	    default:
	    {
	      printf("unknown (%d) ", (int) o->type);
	    }
	    break;
	  }

	  printf("\n");
	}
}
#endif
