/* camera.c */
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "camera.h"
#include "error.h"
#include "vector.h"
#include "boolean.h"
#include "Memory.h"

#ifdef BETTER_MEMMAN
static STORAGE *cameraStor = (STORAGE *)NULL;
#define NEWCAMERA()  	(CAMERA *)New(sizeof(CAMERA), &cameraStor)
#define DISPOSECAMERA(ptr) Dispose((unsigned char *)(ptr), &cameraStor)
#else /*BETTER_MEMMAN*/
#define NEWCAMERA()	(CAMERA *)Alloc(sizeof(CAMERA))
#define DISPOSECAMERA(ptr) Free((char *)ptr, sizeof(CAMERA))
#endif /*BETTER_MEMMAN*/

CAMERA Camera, AltCamera;
static CAMERA CamStack[MAXCAMSTACK], *CamStackPtr=CamStack;
static int altcameraset = FALSE;

CAMERA *CameraCreate(void)
{
	CAMERA *cam;
	cam = NEWCAMERA();
	*cam = Camera;

	return cam;
}

void CameraDestroy(CAMERA *cam)
{
	DISPOSECAMERA(cam);
}

void CameraPrint(FILE *out, CAMERA *Camera)
{
	fprintf(out, "eyepoint: "); VectorPrint(out, Camera->eyep);
	fprintf(out, "\nlooking to: "); VectorPrint(out, Camera->lookp);
	fprintf(out, "\nupdir: "); VectorPrint(out, Camera->updir);
	fprintf(out, "\nfov = %f", (float)Camera->fov/M_PI*180.);
	fprintf(out, "\nhres = %d, vres = %d", Camera->hres, Camera->vres);
	fprintf(out, "\nbackground color: "); RGBPrint(out, Camera->background);
	fprintf(out, "\n");
}

CAMERA *CameraSet(CAMERA *Camera, POINT *eyep, POINT *lookp, VECTOR *updir, 
		  float fov, int hres, int vres, RGB *background)
{
	float n;

	Camera->eyep = *eyep;
	Camera->lookp = *lookp;
	Camera->updir = *updir;
	Camera->fov = fov;
	Camera->hres = hres;
	Camera->vres = vres;
	Camera->background = *background;
	Camera->changed = TRUE;
	
	VECTORSUBSTRACT(*lookp, *eyep, Camera->Z);
	Camera->viewdist = VECTORNORM(Camera->Z);
	if (Camera->viewdist < EPSILON) {
		Error("SetCamera", "eyepoint and look-point coincide");
		return NULL;
	}
	VECTORSCALEINVERSE(Camera->viewdist, Camera->Z, Camera->Z);
	
	VECTORCROSSPRODUCT(Camera->Z, *updir, Camera->X);
	n = VECTORNORM(Camera->X);
	if (n < EPSILON) {
		Error("SetCamera", "up-direction and viewing direction coincide");
		return NULL;
	}
	VECTORSCALEINVERSE(n, Camera->X, Camera->X);

	VECTORCROSSPRODUCT(Camera->Z, Camera->X, Camera->Y);
	VECTORNORMALIZE(Camera->Y);

	if (hres < vres) {
		Camera->hfov = fov; 
		Camera->vfov = atan(tan(fov * M_PI/180.)*(float)vres/(float)hres) * 180./M_PI;
	} else {
		Camera->vfov = fov; 
		Camera->hfov = atan(tan(fov * M_PI/180.)*(float)hres/(float)vres) * 180./M_PI;
	} 

/* default near and far clipping plane afstand */
	Camera->near = EPSILON;
	Camera->far = 2. * Camera->viewdist;
	
	return Camera;
}

/* verwisselt ingestelde camera met alternatief - wordt gebruikt 
 * voor het creeren van bewegende beelden met alternerende camera
 * - vnl voor testdoeleinden. */
CAMERA *CameraAlternate(CAMERA *cam)
{
	CAMERA currentcam = *cam;

	if (!altcameraset) {
		Error(NULL, "No alternate camera set");
		return (CAMERA *)NULL;
	}

	cam = CameraSet(cam, 
			&AltCamera.eyep, 
			&AltCamera.lookp, 
			&AltCamera.updir, 
			AltCamera.fov, 
			cam->hres, cam->vres, 
			&AltCamera.background);	

	AltCamera = currentcam;

	cam->changed = FALSE;	/* mag importance gedoe niet beinvloeden */
	return cam;
}

void CameraSetAlternate(CAMERA *cam)
{
	AltCamera = *cam;
	altcameraset = TRUE;
}

CAMERA *CameraSetEyep(CAMERA *cam, float x, float y, float z)
{
	POINT neweyep;
	VECTORSET(neweyep, x, y, z);
	return CameraSet(cam, &neweyep, &cam->lookp, &cam->updir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);	
}

CAMERA *CameraSetLookp(CAMERA *cam, float x, float y, float z)
{
	POINT newlookp;
	VECTORSET(newlookp, x, y, z);
	return CameraSet(cam, &cam->eyep, &newlookp, &cam->updir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);	
}

CAMERA *CameraSetUpdir(CAMERA *cam, float x, float y, float z)
{
	VECTOR newupdir;
	VECTORSET(newupdir, x, y, z);
	return CameraSet(cam, &cam->eyep, &cam->lookp, &newupdir,
		  cam->fov, cam->hres, cam->vres, &cam->background);	
}

CAMERA *CameraSetFov(CAMERA *cam, float fov)
{
	return CameraSet(cam, &cam->eyep, &cam->lookp, &cam->updir,
		  fov, cam->hres, cam->vres, &cam->background);	
}

CAMERA *CameraMoveForward(CAMERA *cam, float step)
{
	POINT neweyep, newlookp;

	VECTORSUMSCALED(cam->eyep, step, cam->Z, neweyep);
	VECTORSUMSCALED(cam->lookp, step, cam->Z, newlookp);
	return CameraSet(cam, &neweyep, &newlookp, &cam->updir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);
}

CAMERA *CameraMoveRight(CAMERA *cam, float step)
{
	POINT neweyep, newlookp;

	VECTORSUMSCALED(cam->eyep, step, cam->X, neweyep);
	VECTORSUMSCALED(cam->lookp, step, cam->X, newlookp);
	return CameraSet(cam, &neweyep, &newlookp, &cam->updir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);
}

CAMERA *CameraMoveUp(CAMERA *cam, float step)
{
	POINT neweyep, newlookp;

	VECTORSUMSCALED(cam->eyep, step, cam->Y, neweyep);
	VECTORSUMSCALED(cam->lookp, step, cam->Y, newlookp);
	return CameraSet(cam, &neweyep, &newlookp, &cam->updir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);
}

CAMERA *CameraTurnRight(CAMERA *cam, float angle)
{
	POINT newlookp;
	float z=cam->viewdist*cos(angle), 
	       x=cam->viewdist*sin(angle);
	
	VECTORCOMB3(cam->eyep, z, cam->Z, x, cam->X, newlookp);
	return CameraSet(cam, &cam->eyep, &newlookp, &cam->updir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);
}

CAMERA *CameraTurnUp(CAMERA *cam, float angle)
{
	POINT newlookp;
	float z=cam->viewdist*cos(angle), 
	       y=-cam->viewdist*sin(angle);
	
	VECTORCOMB3(cam->eyep, z, cam->Z, y, cam->Y, newlookp);
	return CameraSet(cam, &cam->eyep, &newlookp, &cam->updir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);
}

CAMERA *CameraTilt(CAMERA *cam, float angle)
{
	VECTOR newupdir;
	float x, y, z, r;

	r=VECTORDOTPRODUCT(cam->updir, cam->Y);
	x=-r*sin(angle);
	y=r*cos(angle); 
	z=VECTORDOTPRODUCT(cam->updir, cam->Z);

	VECTORCOORD(x, cam->X, y, cam->Y, z, cam->Z, newupdir);
	return CameraSet(cam, &cam->eyep, &cam->lookp, &newupdir, 
		  cam->fov, cam->hres, cam->vres, &cam->background);
}

CAMERA *CameraZoom(CAMERA *cam, float amount)
{
	cam->fov /= amount;
	cam->hfov /= amount;
	cam->vfov /= amount;

	cam->changed = TRUE;
	return cam;
}

void CameraPush(CAMERA *cam)
{
	if (CamStackPtr - CamStack >= MAXCAMSTACK)
		Error("PushCamera", "Camera Stack depth exceeded");
	else
		*CamStackPtr++ = *cam;
}

CAMERA *CameraPop(CAMERA *cam)
{
	CAMERA poppedcam;

	if (CamStackPtr - CamStack <= 0)
		Error("PopCamera", "Camera Stack empty");
	else {
		poppedcam = *--CamStackPtr;
		cam = CameraSet(cam, 
				&poppedcam.eyep, 
				&poppedcam.lookp, 
				&poppedcam.updir, 
				poppedcam.fov, 
				cam->hres, cam->vres, 
				&poppedcam.background);
	}
	return cam;
}
