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

/* Manipulation of camera position and view.
 * Copyright (c) 1994 David Deaven
 */
#include <stdio.h>
#include <math.h>
#include "dmd/tools.h"
#include "dmd/memory.h"
#include "camera.h"

struct CameraRec {
	double xcam[3];		/* camera position */
	double vcam[3];		/* camera view direction */
	double up[3];		/* camera up direction */
	double tcam[4*4];	/* camera transformation */
	double scam[3*3];	/* camera coordinates */
	double glassSensitivity;
	int updated;		/* if modifications have been made */
};

#define store3(DST, X, Y, Z) {\
  (DST)[0] = X; (DST)[1] = Y; (DST)[2] = Z; }

static void update(Camera c)
{
	Gentra(c->xcam, c->vcam, c->up, c->tcam, c->scam);
	Norm(c->vcam);
	Norm(c->up);
	c->updated = 1;
}

Camera NewCamera(void)
{
	Camera c = NewPointer(c);
	store3(c->xcam, 0.0, 0.0, 1.0);
	store3(c->up,   0.0, 1.0, 0.0);
	store3(c->vcam, 0.0, 0.0, -1.0);
	c->glassSensitivity = 4.0;
	update(c);
	return c;
}

void DeleteCamera(Camera c)
{
	if(c) {
	  myfree(c);
	}
}

Camera CameraCopy(Camera src, Camera dst)
{
	if(dst == NULL) dst = NewCamera();
	if(src && src != dst) *dst = *src;
	return dst;
}

Camera CameraSetPosition(Camera c, double x[3])
{
	store3(c->xcam, x[0], x[1], x[2]);
	update(c);
	return c;
}

Camera CameraSetView(Camera c, double x[3])
{
	store3(c->vcam, x[0], x[1], x[2]);
	update(c);
	return c;
}

Camera CameraSetUp(Camera c, double x[3])
{
	store3(c->up, x[0], x[1], x[2]);
	update(c);
	return c;
}

Camera CameraSet(Camera c, double x[3], double v[3], double up[3])
{
	store3(c->xcam, x[0], x[1], x[2]);
	store3(c->vcam, v[0], v[1], v[2]);
	store3(c->up, up[0], up[1], up[2]);
	update(c);
	return c;
}

double *CameraGetPosition(Camera c, double x[3])
{
	store3(x, c->xcam[0], c->xcam[1], c->xcam[2]);
	return x;
}

double *CameraGetView(Camera c, double x[3])
{
	store3(x, c->vcam[0], c->vcam[1], c->vcam[2]);
	return x;
}

double *CameraGetUp(Camera c, double x[3])
{
	store3(x, c->up[0], c->up[1], c->up[2]);
	return x;
}

double *CameraGetTransform(Camera c, double t[4 * 4])
{
	int i;
	for(i=0; i<16; i++) t[i] = c->tcam[i];
	return t;
}

/* Move the camera in its own coordinate system.
 */
Camera CameraPan(Camera c, double dx, double dy, double dz)
{
	int i;
	for(i=0; i<3; ++i)
	  c->xcam[i] += dx * c->scam[i] + dy * c->scam[i + 3] + dz * c->scam[i + 6];
	update(c);
	return c;
}

/* Given another transformation and a point (x0, y0) within the unit 
 * circle, move this camera so that it appears to have moved on a
 * glass sphere whose center is the given radius from the reference,
 * along the reference view direction.  The motion on the sphere
 * mimics the motion of a point on the unit hemisphere facing the
 * viewer, which has moved from (x0, y0) to (x1, y1).
 */
Camera CameraMoveOnGlassSphere(Camera c,
  double x1, double y1, double x0, double y0, double radius, Camera reference)
{
	int i, j;
	double
	  r = sqrt(x0*x0 + y0*y0),
	  dx = c->glassSensitivity * (x0 - x1),
	  dy = c->glassSensitivity * (y0 - y1),
	  vr, vt, xrot, yrot, zrot;

	double cp[3], cu[3]; /* camera-frame position and up */

	/* save the center of the sphere for later */
	for(i=0; i<3; i++)
	  c->xcam[i] = reference->xcam[i] + radius * reference->vcam[i];

	if(r == 0.0)
	  r = 1.0;
	else
	  r = 1.0 / r;

	vr = r * dx * x0 + r * dy * y0;
	vt = r * dy * x0 - r * dx * y0;

	yrot = vr * r * x0;
	xrot = vr * r * y0;

	cp[0] = sin(yrot);
	cp[1] = cos(yrot) * sin(xrot);
	cp[2] = cos(yrot) * cos(xrot);

	zrot = vt;

	cu[0] = -sin(zrot);
	cu[1] = cos(zrot);
	cu[2] = 0.0;

	/* now cp[] and cu[] have the camera coordinates in the camera system.
	 * convert to space coordinates.
	 */
	for(i=0; i<3; i++)
	{
	  c->vcam[i] = c->xcam[i]; /* the sphere center */
	  c->up[i] = 0.0;
	  for(j=0; j<3; ++j)
	  {
	    c->xcam[i] += radius * cp[j] * c->scam[i + 3*j];
	    c->up[i] += cu[j] * c->scam[i + 3*j];
	  }
	  c->vcam[i] -= c->xcam[i]; /* (center - real position) */
	}
	update(c);
	return c;
}

int CameraWasUpdated(Camera c)
{
	int r = c->updated;
	c->updated = 0;
	return r;
}
