/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>



#include "cameraClass.h"

/*
 * Compute the camera matrix from the current parameters
 */

#define ZERO(v) (v.x==0 && v.y==0 && v.z==0)

static Camera
matrix(Camera c)
{
    Vector xaxis, yaxis, zaxis, v;
    Matrix t;
    float xres, yres;

    /* compute resolution */
    xres = c->resolution, yres = c->resolution*c->aspect/c->pix_aspect;

    /* translate to origin of camera coordinate system */
    t = DXTranslate(DXNeg(c->from));

    /* camera coordinate system axes */
    v = DXSub(c->from, c->to);
    if (ZERO(v))
	DXErrorReturn(ERROR_BAD_PARAMETER,
		    "camera from and to points are identical");
    zaxis = DXNormalize(v);
    v = DXCross(c->up, zaxis);
    if (ZERO(v))
	DXErrorReturn(ERROR_BAD_PARAMETER, "degenerate camera up vector");
    xaxis = DXNormalize(v);
    yaxis = DXNormalize(DXCross(zaxis, xaxis));
    t = DXConcatenate(t, DXMat(
        xaxis.x, yaxis.x, zaxis.x,
        xaxis.y, yaxis.y, zaxis.y,
        xaxis.z, yaxis.z, zaxis.z,
	0,       0,       0		       
    ));
    c->rot = t;

    /*
     * scaling to resolution
     * In ortho case, we scale z similarly to x and y so that transformed
     * objects retain their shape, so that e.g. volume rendering can
     * look at voxel shape after transformation to determine what algorithm
     * to use.  Is this the best way to do this?  XXX - what about
     * perspective case?  XXX - fails if pix_aspect is not 1
     * XXX - undid this, because it distorts z values for volume
     * rendering - how to handle this?
     */
#if 1
    if (c->width==0)
	DXErrorReturn(ERROR_BAD_PARAMETER, "camera width is zero");
    t = DXConcatenate(t, DXScale(xres/c->width, xres/c->width/c->pix_aspect, 1));
    if (c->ortho) {
	Vector xto;
	xto = DXApply(c->to, t);
	t.b[2] -= xto.z;
    }
#else
    if (c->ortho)
	t = DXConcatenate(t, DXScale(
	    xres/c->width, xres/c->width/c->pix_aspect, xres/c->width));
    else
	t = DXConcatenate(t, DXScale(
	    xres/c->width, xres/c->width/c->pix_aspect, 1));
#endif
    c->m = t;
    return c;
}


/*
 * Camera object
 */

Camera
_NewCamera(struct camera_class *class)
{
    Camera c = (Camera) _dxf_NewObject((struct object_class *)class);
    if (!c)
	return NULL;

    c->from = DXPt(0,0,1);
    c->to = DXPt(0,0,0);
    c->up = DXVec(0,1,0);
    c->ortho = 1;
    c->width = 2;  /* XXX - +/- 1 */
    c->aspect = 3.0/4.0;
    c->pix_aspect = 1;
    c->resolution = 640;
    c->background.r = 0.0;
    c->background.g = 0.0;
    c->background.b = 0.0;
    return matrix(c);
}

Camera
DXNewCamera()
{
    return _NewCamera(&_dxdcamera_class);
}

int
_dxfCamera_Delete(Camera c)
{
    return OK;
}


Object
_dxfCamera_Copy(Camera old, enum copy copy)
{
    Camera new;
    new = DXNewCamera();
    if (!new)
	return NULL;
    if (!_dxf_CopyObject((Object)new, (Object)old, copy))
	return NULL;
    new->from = old->from;
    new->to = old->to;
    new->up = old->up;
    new->ortho = old->ortho;
    new->width = old->width;
    new->aspect = old->aspect;
    new->pix_aspect = old->pix_aspect;
    new->resolution = old->resolution;
    new->m = old->m;
    new->rot = old->rot;
    new->background = old->background;
    return (Object)new;
}


Camera
DXSetView(Camera c, Point from, Point to, Vector up)
{
    CHECK(c, CLASS_CAMERA);

    c->from = from;
    c->to = to;
    c->up = up;
    return matrix(c);
}

Camera
DXSetPerspective(Camera c, double fov, double aspect)
{
    CHECK(c, CLASS_CAMERA);

    c->width = fov;
    c->aspect = aspect;
    c->ortho = 0;
    return matrix(c);
}

Camera
DXSetOrthographic(Camera c, double width, double aspect)
{
    CHECK(c, CLASS_CAMERA);

    c->width = width;
    c->aspect = aspect;
    c->ortho = 1;
    return matrix(c);
}

Camera
DXSetResolution(Camera c, int hres, double pix_aspect)
{
    CHECK(c, CLASS_CAMERA);

    c->resolution = hres;
    c->pix_aspect = pix_aspect;
    return matrix(c);
}

/*
 * Access routines
 */

Matrix
DXGetCameraMatrix(Camera c)
{
    return c? c->m : Identity;
}


Matrix
DXGetCameraMatrixWithFuzz(Camera c, float fuzz)
{
    Matrix m;
    if (!c) {
	m = Identity;
	m.b[2] = fuzz;
    } else {
	m = c->m;
	if (c->ortho) {
	    m = c->m;
	    m.b[2] += fuzz*c->width/c->resolution;
	} else {
	    float f = 1.0 - fuzz*c->width/c->resolution;
	    m.A[0][0] *= f;  m.A[0][1] *= f;  m.A[0][2] *= f;
	    m.A[1][0] *= f;  m.A[1][1] *= f;  m.A[1][2] *= f;
	    m.A[2][0] *= f;  m.A[2][1] *= f;  m.A[2][2] *= f;
	    m.b[0] *= f;     m.b[1] *= f;     m.b[2] *= f;
	}
    }
    return m;
}

Matrix
DXGetCameraRotation(Camera c)
{
    return c? c->rot : Identity;
}


Camera
DXGetCameraResolution(Camera c, int *width, int *height)
{
    CHECK(c, CLASS_CAMERA);

    if (width)
	*width = c->resolution;
    if (height)
	*height = c->resolution*c->aspect;

    return c;
}

Camera
DXGetView(Camera c, Point *from, Point *to, Vector *up)
{
    CHECK(c, CLASS_CAMERA);
    if (from)
	*from = c->from;
    if (to)
	*to = c->to;
    if (up)
	*up = c->up;
    return c;
}

Camera
DXGetPerspective(Camera c, float *fov, float *aspect)
{
    CHECK(c, CLASS_CAMERA);

    if (c->ortho)
	return NULL;
    if (fov)
	*fov = c->width;
    if (aspect)
	*aspect = c->aspect;

    return c;
}

Camera
DXGetOrthographic(Camera c, float *width, float *aspect)
{
    CHECK(c, CLASS_CAMERA);

    if (!c->ortho)
	return NULL;
    if (width)
	*width = c->width;
    if (aspect)
	*aspect = c->aspect;

    return c;
}

Camera
DXSetBackgroundColor(Camera c, RGBColor b)
{
    CHECK(c, CLASS_CAMERA);

    c->background = b;

    return c;
}

Camera
DXGetBackgroundColor(Camera c, RGBColor *b)
{
    CHECK(c, CLASS_CAMERA);

    *b = c->background;

    return c;
}
