/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmcamera.c,v 1.10 2005/09/12 04:02:24 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.10 $
 * $Log: rmcamera.c,v $
 * Revision 1.10  2005/09/12 04:02:24  wes
 * Minor documentation updates.
 *
 * Revision 1.9  2005/02/19 16:22:50  wes
 * Distro sync and consolidation.
 *
 * Revision 1.8  2005/01/23 17:00:22  wes
 * Copyright updated to 2005.
 *
 * Revision 1.7  2004/04/09 14:48:03  wes
 * Fix some compile warnings about unused variables.
 *
 * Revision 1.6  2004/03/30 14:13:31  wes
 * Fixed declarations and man page docs for several routines.
 *
 * Revision 1.5  2004/02/13 00:46:31  wes
 * Updated inline documentation.
 *
 * Revision 1.4  2004/01/16 16:43:24  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.3  2003/06/13 03:08:39  wes
 * Minor documentation tweak.
 *
 * Revision 1.2  2003/02/02 02:07:15  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.7  2003/01/16 22:21:17  wes
 * Updated all source files to reflect new organization of header files:
 * all header files formerly located in include/rmaux, include/rmi, include/rmv
 * are now located in include/rm.
 *
 * Revision 1.6  2002/08/17 15:07:46  wes
 * rmCamera3DComputeViewFromGeometry() - updated computation of near/far
 * clip planes to allow more front-to-back maneuvering space.
 *
 * Revision 1.5  2002/04/30 19:28:55  wes
 * Updated copyright dates.
 *
 * Revision 1.4  2001/03/31 17:12:38  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.3  2000/08/23 23:24:52  wes
 * Updated documentation.
 *
 * Revision 1.2  2000/04/20 16:29:47  wes
 * Documentation additions/enhancements, some code rearragement.
 *
 * Revision 1.1.1.1  2000/02/28 21:29:40  wes
 * OpenRM 1.2 Checkin
 *
 * Revision 1.1.1.1  2000/02/28 17:18:47  wes
 * Initial entry - pre-RM120 release, source base for OpenRM 1.2.
 *
 */

#include <rm/rm.h>
#include "rmprivat.h"

/* default camera parameters */
extern float      RM_DEFAULT_2DCAMERA_XMIN, RM_DEFAULT_2DCAMERA_YMIN, RM_DEFAULT_2DCAMERA_XMAX,  RM_DEFAULT_2DCAMERA_YMAX;
extern float      RM_DEFAULT_2DCAMERA_ASPECT;
extern float 	  RM_DEFAULT_3DCAMERA_HITHER, RM_DEFAULT_3DCAMERA_YON, RM_DEFAULT_3DCAMERA_ASPECT, RM_DEFAULT_3DCAMERA_FOV;
extern RMenum     RM_DEFAULT_3DCAMERA_PROJECTION;
extern RMvertex3D RM_DEFAULT_3DCAMERA_EYE, RM_DEFAULT_3DCAMERA_LOOKAT, RM_DEFAULT_3DCAMERA_UP;

/* PRIVATE declarations */
static void private_rmCamera2DComputeMatrix (const RMcamera2D *c, RMmatrix *m);

/*
 * ----------------------------------------------------
 * @Name rmCamera2DNew
 @pstart
 RMcamera2D * rmCamera2DNew (void)
 @pend

 @astart
 No arguments.
 @aend

 @dstart

 A default 2D camera is created and a handle to the requested
 RMcamera2D is returned.  If the 2D camera cannot be created, then a
 NULL pointer is returned.

 @dend
 * ----------------------------------------------------
 */
RMcamera2D *
rmCamera2DNew (void)
{
    RMcamera2D *c;

    if ((c = (RMcamera2D *)malloc(sizeof(RMcamera2D))) == NULL)
	return(NULL);

    /* defaults */
    rmCamera2DSetExtents(c, RM_DEFAULT_2DCAMERA_XMIN, RM_DEFAULT_2DCAMERA_YMIN, RM_DEFAULT_2DCAMERA_XMAX, RM_DEFAULT_2DCAMERA_YMAX);
    rmCamera2DSetAspectRatio(c, RM_DEFAULT_2DCAMERA_ASPECT);

    return(c);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DCopy
 @pstart
 RMenum rmCamera2DCopy (RMcamera2D *dst,
	                const RMcamera2D *src)
 @pend

 @astart
 RMcamera2D *dst - a handle to an RMcamera2D object to be modified
    (output).

 const RMcamera2D *src - a handle to an RMcamera2D object that will be
    copied (input). 
 @aend

 @dstart

 Copies all parameters and attributes from one RMcamera2D object to
 another. Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DCopy (RMcamera2D *dst,
	        const RMcamera2D *src)
{
    if ((RM_ASSERT(dst, "rmCamera2DCopy error: the dst RMcamera2D pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(src, "rmCamera2DCopy error: the src RMcamera2D pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    memcpy((void *)dst, (void *)src, sizeof(RMcamera2D));

    return(RM_CHILL);
}
 

/*
 * ----------------------------------------------------
 * @Name rmCamera2DDelete
 @pstart
 void rmCamera2DDelete (RMcamera2D *toDelete)
 @pend

 @astart
 RMcamera2D *toDelete - a handle to  RMcamera2D object to delete (modified).
 @aend

 @dstart

 Free the RMcamera2D resources pointed to by c.  It is assumed that c
 points to a valid RMcamera2D previously created with rmCamera2DNew().

 @dend
 * ----------------------------------------------------
 */
void
rmCamera2DDelete (RMcamera2D *c)
{
    if (c != NULL)
        free(c);
}


/*
 * ----------------------------------------------------
 * @Name rmDefaultCamera2D
 @pstart
 RMenum rmDefaultCamera2D (RMcamera2D *toModify)
 @pend

 @astart
 RMcamera2D *toModify - a handle to a caller supplied RMcamera2D
    object that will be modified by this routine (modified).
 @aend

 @dstart

 This routine assigns a set of default parameters to an RMcamera2D
 object.  All existing settings of the input RMcamera2D object will be
 overwritten.  Returns RM_CHILL upon success, or RM_WHACKED upon
 failure.

 The new values written by this routine are:

 1. 2D extents set to: (-1,-1)..(1,1)

 2. Aspect ratio set to 1.0.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmDefaultCamera2D (RMcamera2D *c)
{
    if (RM_ASSERT(c, "rmDefaultCamera2D error: the input RMcamera2D object is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    rmCamera2DSetExtents(c, RM_DEFAULT_2DCAMERA_XMIN, RM_DEFAULT_2DCAMERA_YMIN, RM_DEFAULT_2DCAMERA_XMAX, RM_DEFAULT_2DCAMERA_YMAX);
    rmCamera2DSetAspectRatio(c, RM_DEFAULT_2DCAMERA_ASPECT);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DSetAspectRatio
 @pstart
 RMenum rmCamera2DSetAspectRatio (RMcamera2D *toModify,
			          float newAspect)

 @pend

 @astart
 RMcamera2D *toModify - a handle to the RMcamera2D object that will be
    modified by this routine.
    
 float newAspect - a floating point value specifying the new aspect
    ratio for the RMcamera2D object.
 @aend

 @dstart

 Use this routine to set the aspect ratio of a 2D camera. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.
 
 The aspect ratio is a fraction that is the width of the field of view
 divided by the height. It is used to control the stretch and squish
 of rendering. For example, consider rendering into a window whose
 width is 400 pixels and height is 300 pixels and using a logically
 square viewport (0,0..1,1). If the camera aspect ratio is set to 1.0,
 the resulting image will appear to be stretched along the horizontal
 axis because the square viewport is being mapped to a rectangular
 window.

 In this example, we would want to set the aspect ratio to 400/300 to
 avoid stretching. The aspect ratio is applied at the stage when we
 compute the view transformation matrix.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DSetAspectRatio (RMcamera2D *toModify,
			  float newAspect)
{
    if (RM_ASSERT(toModify, "rmCamera2DSetAspectRatio error: the input RMcamera2D object is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    toModify->aspect_ratio = newAspect;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DResetAspectRatio
 @pstart
 RMenum rmCamera2DResetAspectRatio (RMcamera2D *toModify,
			            const float *vp,
			            int windowWidth,
				    int windowWeight)
 @pend

 @astart
 RMcamera2D *toModify - a handle to the RMcamera2D that will be
    modified by this routine.
 
 const float *vp - an array of floating point values of length 4. This
    is a viewport specification, ordered {xmin, ymin, xmax, ymax}. The
    viewport coordinates are in the range 0..1, ranging from the lower
    left corner of the display window (0,0) to the upper right corner
    (1,1).
 
 int windowWidth, windowWeight - integer values specifying the size in
    pixels of the display window. These values are used in computing a
    new aspect ratio for the RMcamera2D object.
 @aend

 @dstart

 Given viewport and window geometry, compute an aspect ratio that will
 cause pixels to "remain square." In other words, an aspect ratio will
 be computed so that there is no stretch or squish in the rendered
 image: a square in world coordinates will remain square in the
 rendered image, regardless of viewport or window geometry.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DResetAspectRatio (RMcamera2D *toModify,
			    const float vp[4],
			    int windowWidth,
			    int windowHeight)
{
    float sx, sy, new_aspect;

    if ((RM_ASSERT(toModify, "rmCamera2DResetAspectRatio error: the input RMcamera2D error is NULL") == RM_WHACKED) ||
	(RM_ASSERT(vp, "rmCamera2DResetAspectRatio error: the input viewport is NULL")) == RM_WHACKED)
	return(RM_WHACKED);

    sx = vp[2] - vp[0];
    sy = vp[3] - vp[1];
    
    new_aspect = ((float)(windowWidth) * sx) / ((float)(windowHeight) * sy);
    rmCamera2DSetAspectRatio(toModify, new_aspect);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DGetAspectRatio
 @pstart
 RMenum rmCamera2DGetAspectRatio (const RMcamera2D *toQuery,
			          float *retValue)
 @pend

 @astart
 const RMcamera2D *toQuery - a handle to the RMcamera2D object that
    will be queried by this routine.

 float *retValue - a handle to a caller-supplied float. The
    RMcamera2D's aspect ratio attribute will be copied into this
    memory location.
 @aend

 @dstart

 Use this routine to query the aspect ratio of an RMcamera2D object.
 RM_CHILL is returned upon success, otherwise RM_WHACKED is returned.

 The aspect ratio is a fraction that is the width of the field of view
 divided by the height. It is used to control the stretch and squish
 of rendering. For example, consider rendering into a window whose
 width is 400 pixels and height is 300 pixels and using a logically
 square viewport (0,0..1,1). If the camera aspect ratio is set to 1.0,
 the resulting image will appear to be stretched along the horizontal
 axis because the square viewport is being mapped to a rectangular
 window.

 In this example, we would want to set the aspect ratio to 400/300 to
 avoid stretching. The aspect ratio is applied at the stage when we
 compute the view transformation matrix.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DGetAspectRatio (const RMcamera2D *toQuery,
			  float *retValue)
{
    if ((RM_ASSERT(toQuery, "rmCamera2DGetAspectRatio error: the input RMcamera2D object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retValue, "rmCamera2DGetAspectRatio error: the return float pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);
    
    *retValue = toQuery->aspect_ratio;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DSetExtents
 @pstart
 RMenum rmCamera2DSetExtents (RMcamera2D *toModify,
		              float xmin,
			      float ymin,
			      float xmax,
			      float ymax)

 @pend

 @astart
 RMcamera2D *toModify - a handle to an RMcamera2D object that will be
    modified by this routine.

 float xmin, ymin, xmax, ymax - floating point values that specify the
    new x/y extents "seen" by the 2D camera. These values are
    specified in world coordinates.
 @aend

 @dstart

 Use this routine to specify the extents for a 2D camera. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.

 A 2D camera is composed of two parameters in OpenRM: an extents and
 an aspect ratio. The extents defines a 2D region in world coordinates
 that will be seen by the 2D camera, these extents are specified with
 a minimum and maximum coordinate. The aspect ratio defines the
 relationship between the camera width and height and controls the
 stretch and squish of rendering, something that is useful if your
 window is not square.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DSetExtents (RMcamera2D *c,
		      float xmin,
		      float ymin,
		      float xmax,
		      float ymax)
{
    if (RM_ASSERT(c, "rmCamera2DSetExtents error: the input RMcamera2D object is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    c->xmin = xmin;
    c->ymin = ymin;
    c->xmax = xmax;
    c->ymax = ymax;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DGetExtents
 @pstart
 RMenum rmCamera2DGetExtents (const RMcamera2D *toQuery,
		              float *xmin,
			      float *ymin,
			      float *xmax,
			      float *ymax)

 @pend

 @astart
 const RMcamera2D *toQuery - a handle to an RMcamera2D object that
    will be queried by this routine.

 float *xmin, *ymin, *xmax, *ymax - pointers to caller-supplied
    floats. The 2D cameras extents parameters will be written into
    these memory locations.  It is permissible to specify NULL for any
    or all of these parameters - the corresponding 2D camera extents
    parameter will not be reported for NULL return parameters.
 @aend

 @dstart

 Use this routine to query the extents of a 2D camera. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.

 A 2D camera is composed of two parameters in OpenRM: an extents and
 an aspect ratio. The extents defines a 2D region in world coordinates
 that will be seen by the 2D camera, these extents are specified with
 a minimum and maximum coordinate. The aspect ratio defines the
 relationship between the camera width and height and controls the
 stretch and squish of rendering, something that is useful if your
 window is not square.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DGetExtents (const RMcamera2D *c,
		      float *xmin,
		      float *ymin,
		      float *xmax,
		      float *ymax)
{
    if (RM_ASSERT(c, "rmCamera2DGetExtents error: the input RMcamera2D object is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (xmin != NULL)
	*xmin = c->xmin;

    if (ymin != NULL)
	*ymin = c->ymin;

    if (xmax != NULL)
	*xmax = c->xmax;

    if (ymax != NULL)
	*ymax = c->ymax;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DComputeViewMatrix
 @pstart
 RMenum rmCamera2DComputeViewMatrix (const RMcamera2D *camera,
			             RMmatrix *matrixReturn)
 @pend

 @astart
 const RMcamera2D *camera - a handle to an RMcamera2D object (input).

 RMmatrix *matrixReturn - a handle to a caller-supplied RMmatrix
    object (result).
 @aend

 @dstart

 Computes the view matrix defined by the 2D input camera, copying the
 result into caller supplied memory. Upon success, RM_CHILL is
 returned. Upon failure, RM_WHACKED is returned, and matrixReturn
 remains undisturbed.

 Note that the 2D camera does not produce a projection matrix, unlike
 the 3D camera.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DComputeViewMatrix (const RMcamera2D *c,
			     RMmatrix *m)
{
    if ((RM_ASSERT(c, "rmCamera2DComputeViewMatrix error: the input RMcamera2D object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(m, "rmCamera2DComputeViewMatrix error: the return view RMmatrix object is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    private_rmCamera2DComputeMatrix(c, m);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera2DComputeViewFromGeometry
 @pstart
 RMenum rmCamera2DComputeViewFromGeometry (RMcamera2D *toModify,
				           const RMnode *source)
 @pend

 @astart
 RMcamera2D *toModify - a handle to an RMcamera2D object that will be
    modified by this routine.

 const RMnode *source - a handle to an RMnode.
 @aend

 @dstart

 Use this routine to automatically compute 2D view parameters from the
 2D bounding box of scene graph node. Returns RM_CHILL upon success,
 or RM_WHACKED upon failure.

 The 2D camera's parameters will be set to reflect the min/max extents
 of the input RMnode's 2D bounding box. The aspect ratio of the 2D
 camera remains unmodified.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera2DComputeViewFromGeometry (RMcamera2D *c,
				   const RMnode *r)
{
    RMvertex3D bmin, bmax;
    
    if ((RM_ASSERT(c, "rmCamera2DComputeViewFromGeometry error: the input RMcamera2D object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(r, "rmCamera2DComputeViewFromGeometry error: the input RMnode is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    rmNodeGetBoundingBox (r, &bmin, &bmax);
    rmCamera2DSetExtents(c, bmin.x, bmin.y, bmax.x, bmax.y);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DNew
 @pstart
 RMcamera3D * rmCamera3DNew (void)
 @pend

 @astart
 No arguments.
 @aend

 @dstart

 This routine creates a new RMcamera3D object, initializes it to some
 default values, and returns a handle to the caller. If there is an error
 (malloc failure), an error message is displayed with rmError and NULL is
 returned.

 For initialization, rmDefaultCamera3D is applied to the newly created
 RMcamera3D object so that all fields are initialized. See the description
 of rmDefaultCamera3D for particulars about the default values.

 @dend
 * ----------------------------------------------------
 */
RMcamera3D *
rmCamera3DNew (void)
{
    RMcamera3D  *c;

    if ((c = (RMcamera3D *)malloc(sizeof(RMcamera3D))) == NULL)
    {
	rmError("rmCamera3DNew() error: unable to malloc memory for a new RMcamera3D struct. ");
	return(NULL);
    }
	
    /* defaults */
    rmDefaultCamera3D(c);

    return(c);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DCopy
 @pstart
 RMenum rmCamera3DCopy (RMcamera3D *dst,
	                const RMcamera3D *src)
 @pend

 @astart
 RMcamera3D *dst - a handle to an RMcamera3D object to be modified
    (output).

 const RMcamera3D *src - a handle to an RMcamera3D object that will be
    copied (input).
 @aend

 @dstart

 Copies all parameters and attributes from one RMcamera3D object to
 another. Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DCopy (RMcamera3D *dst,
	        const RMcamera3D *src)
{
    if ((RM_ASSERT(dst, "rmCamera3DCopy error: the dst RMcamera3D pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(src, "rmCamera3DCopy error: the src RMcamera3D pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    memcpy((void *)dst, (void *)src, sizeof(RMcamera3D));

    return(RM_CHILL);
}
 

/*
 * ----------------------------------------------------
 * @Name rmCamera3DDelete
 @pstart
 void rmCamera3DDelete (RMcamera3D *toDelete)
 @pend

 @astart
 RMcamera3D *toDelete - a handle to RMcamera3D object to delete (modified).
 @aend

 @dstart

 Free the RMcamera3D resources pointed to by c.  It is assumed that c
 points to a valid RMcamera3D previously created with rmCamera3DNew().

 @dend
 * ----------------------------------------------------
 */
void
rmCamera3DDelete (RMcamera3D *c)
{
    if (c != NULL)
        free((void *)c);
}


/*
 * ----------------------------------------------------
 * @Name rmDefaultCamera3D
 @pstart
 RMenum rmDefaultCamera3D (RMcamera3D *toModify)

 @pend

 @astart
 RMcamera3D *toModify - a handle to an RMcamera3D object that will be
    modified by this routine. 
 @aend

 @dstart

 Assigns some default parameters to an RMcamera3D object. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.

 The parameters set by this routine are:

 1. Eye point set to (10,10,50). (rmCamera3DSetEye)

 2. Lookat point is the origin, (0,0,0). (rmCamera3DSetAt)

 3. Up vector is (0,1,0) (rmCamera3DSetUp)

 4. Field of view set to 45 degrees (rmCamera3DSetFOV)

 5. Monoscopic camera (rmCamera3DSetStereo)

 6. Perspective projection (rmCamera3DSetProjection)

 7. Front and back clipping planes set to 2.0 and 40.0, respectively.
    (rmCamera3DSetHither, rmCamera3DSetYon)
    
 Returns RM_CHILL upon success, or RM_WHACKED if the input RMcamera3D
 object is NULL.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmDefaultCamera3D (RMcamera3D *c)
{
    if (RM_ASSERT(c, "rmDefaultCamera3D error: the input RMcamera3D object is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    /*
     * assign some default values:
     * the camera's default position has the
     * - eye at (10,10,50)
     * - lookat point is the origin (0,0,0)
     * - up vector is +y axis
     * - hither is 2 and yon is 40
     * - perspective projection
     * - monoscopic
     */
    rmCamera3DSetFOV(c, RM_DEFAULT_3DCAMERA_FOV);
    rmCamera3DSetAspectRatio(c, RM_DEFAULT_3DCAMERA_ASPECT);

    rmCamera3DSetEye(c, &RM_DEFAULT_3DCAMERA_EYE);
    rmCamera3DSetAt(c, &RM_DEFAULT_3DCAMERA_LOOKAT);
    rmCamera3DSetUpVector(c, &RM_DEFAULT_3DCAMERA_UP);

    rmCamera3DSetHither(c, RM_DEFAULT_3DCAMERA_HITHER);
    rmCamera3DSetYon(c, RM_DEFAULT_3DCAMERA_YON);
    rmCamera3DSetProjection(c, RM_DEFAULT_3DCAMERA_PROJECTION);
    rmCamera3DSetStereo(c, RM_FALSE);
    rmCamera3DSetEyeSeparation (c, 0.0F);
    rmCamera3DSetFocalDistance (c, 1.0F);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetAspectRatio
 @pstart
 RMenum rmCamera3DSetAspectRatio (RMcamera3D *toModify,
		                  const float newAspect)

 @pend

 @astart
 RMcamera3D *toModify - a handle to an RMcamera3D object (modified).

 const float newAspect - a floating point value used to specify the
    new aspect ratio for the RMcamera3D object.
 @aend

 @dstart

 Use this routine to modify the aspect ratio attribute of an
 RMcamera3D object. Returns RM_CHILL upon success, or RM_WHACKED upon
 failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetAspectRatio (RMcamera3D *toModify,
		          const float newAspect)
{
    if (RM_ASSERT(toModify, "rmCamera3DSetAspectRatio error: the input RMcamera3D object is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    toModify->aspect = newAspect;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DResetAspectRatio
 @pstart
 RMenum rmCamera3DResetAspectRatio (RMcamera3D *toModify,
			            const float *vp,
				    int windowWidth,
				    int windowHeight)

 @pend

 @astart
 RMcamera3D *toModify - a handle to the RMcamera3D object that will be
    modified by this routine.
    
 const float *vp - an array of floating point values of length 4. This
    is a viewport specification, ordered {xmin, ymin, xmax, ymax}. The
    viewport coordinates are in the range 0..1, ranging from the lower
    left corner of the display window (0,0) to the upper right corner
    (1,1).
 
 int windowWidth, windowWeight - integer values specifying the size in
    pixels of the display window. These values are used in computing a
    new aspect ratio for the RMcamera3D object.
 @aend

 @dstart

 Given viewport and window geometry, compute an aspect ratio that will
 cause pixels to "remain square." In other words, an aspect ratio will
 be computed so that there is no stretch or squish in the rendered
 image: a square in world coordinates will remain square in the
 rendered image, regardless of viewport or window geometry.

 All other camera parameters are ignored and remain unaffected.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DResetAspectRatio (RMcamera3D *toModify,
			    const float *vp,
			    int windowWidth,
			    int windowHeight)
{
    float sx, sy;
    float new_aspect;

    if ((RM_ASSERT(toModify, "rmCamera3DResetAspectRatio error: input RMcamera3D object is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(vp, "rmCamera3DResetAspectRatio error: input viewport is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    sx = vp[2] - vp[0];
    sy = vp[3] - vp[1];
    
    new_aspect = ((float)windowWidth * sx) / ((float)windowHeight * sy);
    rmCamera3DSetAspectRatio(toModify, new_aspect);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetAspectRatio
 @pstart
 float rmCamera3DGetAspectRatio (const RMcamera3D *toQuery)
 @pend

 @astart
 const RMcamera3D *toQuery - a handle to the RMcamera3D object that
    will be queried by this routine.
 @aend

 @dstart

 Use this routine to query the aspect ratio of an RMcamera3D object.
 RM_CHILL is returned upon success, otherwise RM_WHACKED is returned.

 The aspect ratio is a fraction that is the width of the field of view
 divided by the height. It is used to control the stretch and squish
 of rendering. For example, consider rendering into a window whose
 width is 400 pixels and height is 300 pixels and using a logically
 square viewport (0,0..1,1). If the camera aspect ratio is set to 1.0,
 the resulting image will appear to be stretched along the horizontal
 axis because the square viewport is being mapped to a rectangular
 window.

 In this example, we would want to set the aspect ratio to 400/300 to
 avoid stretching. The aspect ratio is applied at the stage when we
 compute the view transformation matrix.

 @dend
 * ----------------------------------------------------
 */
float
rmCamera3DGetAspectRatio (const RMcamera3D *toQuery)
{
    if (RM_ASSERT(toQuery, "rmCamera3DGetAspectRatio error: input RMcamera3D pointer is NULL. returning 1.0") == RM_WHACKED)
	return(1.0F);
    
    return(toQuery->aspect);

}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetProjection
 @pstart
 RMenum rmCamera3DSetProjection (RMcamera3D *toModify,
                                 RMenum newVal)
 @pend

 @astart
 RMcamera3D *toModify - a handle to an RMcamera3D object that will be
    modified by this routine (modified).

 RMenum newVal - an RMenum value specifying the new projection method
    for the RMcamera3D object. Should be either
    RM_PROJECTION_PERSPECTIVE or RM_PROJECTION_ORTHOGRAPHIC.
 @aend

 @dstart

 Use this routine to assign a projection method to a 3D
 camera. Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 Perspective projections are used to achieve a "perspective
 foreshortening" effect. This is how humans see in the real world:
 objects that are further away appear smaller than similarly-sized
 objects that are closer.  In contrast, in orthographics projections
 size does not diminish as a function of distance to the viewer. This
 type of projection is often used in CAD systems because it does not
 distort angles.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetProjection (RMcamera3D *toModify,
			 RMenum newVal)
{
    if (RM_ASSERT(toModify, "rmCamera3DSetProjection error: input camera is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((newVal != RM_PROJECTION_PERSPECTIVE) && (newVal != RM_PROJECTION_ORTHOGRAPHIC))
        return(RM_WHACKED);

    toModify->projection = newVal;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetProjection
 @pstart
 RMenum rmCamera3DGetProjection (const RMcamera3D *toQuery)
 @pend

 @astart

 const RMcamera3D *toQuery - a handle to an RMcamera3D object that
    will be queried by this routine (input). 
 @aend

 @dstart
 
 Use this routine to obtain the projection method attribute of the
 RMcamera3D object. If successful, either RM_PROJECTION_PERSPECTIVE or
 RM_PROJECTION_ORTHOGRAPHIC will be returned to the caller. Otherwise,
 upon failure, RM_WHACKED will be returned.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DGetProjection (const RMcamera3D *toQuery)
{
    if (RM_ASSERT(toQuery, "rmCamera3DGetProjection error: input camera is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    return(toQuery->projection);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetEye
 @pstart
 RMenum rmCamera3DSetEye (RMcamera3D *toModify,
		          const RMvertex3D *newEye)

 @pend

 @astart
 RMcamera3D *toModify - a handle to the RMcamera3D object that will be
    modified by this routine.
    
 const RMvertex3D *newEye - a handle to an RMvertex3D object. 
 @aend

 @dstart

 Use this routine to specify a new eye point for a 3D camera. The eye
 point is the location of the viewer, and is specified in world
 coordinates.  The new eye point is copied from the caller-supplied
 RMvertex3D object into the RMcamera3D object.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetEye (RMcamera3D *toModify,
		  const RMvertex3D *newEye)
{
    if ((RM_ASSERT(toModify, "rmCamera3DSetEye error: input camera is null.") == RM_WHACKED) ||
	(RM_ASSERT(newEye, "rmCamera3DSetEye error: input RMvertex3D pointer is null.") == RM_WHACKED))
	return(RM_WHACKED);
	
    VCOPY(newEye, &(toModify->eye));

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetEye
 @pstart
 RMenum rmCamera3DGetEye (const RMcamera3D *toQuery,
		          RMvertex3D *returnEye)

 @pend

 @astart

 const RMcamera3D *toQuery - a handle to an RMcamera3D object to be
    queried (input).

 RMvertex3D *returnEye - a handle to a caller-supplied RMvertex3D
    object (modified). 
 @aend

 @dstart

 Use this routine to obtain the 3D eye position of an RMcamera3D
 object.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DGetEye (const RMcamera3D *toQuery,
		  RMvertex3D *returnEye)
{
    if ((RM_ASSERT(toQuery, "rmCamera3DGetEye error: input camera is null.") == RM_WHACKED) ||
	(RM_ASSERT(returnEye, "rmCamera3DGetEye error: input RMvertex3D pointer is null.") == RM_WHACKED))
	return(RM_WHACKED);
	
    VCOPY(&(toQuery->eye), returnEye);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetAt
 @pstart
 RMenum rmCamera3DSetAt (RMcamera3D *toModify,
		         const RMvertex3D *newAt)

 @pend

 @astart 
 RMcamera3D *toModify - a handle to an RMcamera3D object (modified).

 const RMvertex3D *newAt - a handle to an RMvertex3D object containing
    the new look-at point (input).
 @aend

 @dstart

 Use this routine to set the look-at point of a 3D camera. The
 contents of the caller-supplied newAt RMvertex3D object is copied
 into the RMcamera3D object's look-at point attribute.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetAt (RMcamera3D *toModify,
		 const RMvertex3D *newAt)
{
    if ((RM_ASSERT(toModify, "rmCamera3DSetAt error: input camera is null.") == RM_WHACKED) ||
	(RM_ASSERT(newAt, "rmCamera3DSetAt error: input RMvertex3D pointer is null.") == RM_WHACKED))
	return(RM_WHACKED);
	
    VCOPY(newAt, &(toModify->at));

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetAt
 @pstart
 RMenum rmCamera3DGetAt (const RMcamera3D *toQuery,
		         RMvertex3D *returnAt)

 @pend

 @astart
 const RMcamera3D *toQuery - a handle to an RMcamera3D object to be
    queried (input).

 RMvertex3D *returnAt - a handle to an RMvertex3D object that will
    contain the RMcamera3D's look-at point upon return (modified).

 @aend

 @dstart

 Use this routine to obtain the look-at point of a 3D camera. The
 RMcamera3D's look-at point is copied into the caller-supplied object.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DGetAt (const RMcamera3D *toQuery,
		 RMvertex3D *returnAt)
{
    if ((RM_ASSERT(toQuery, "rmCamera3DGetAt error: input camera is null.") == RM_WHACKED) ||
	(RM_ASSERT(returnAt, "rmCamera3DGetAt error: input RMvertex3D pointer is null.") == RM_WHACKED))
	return(RM_WHACKED);
	
    VCOPY(&(toQuery->at), returnAt);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetUpVector
 @pstart
 RMenum rmCamera3DSetUpVector (RMcamera3D *toModify,
		               const RMvertex3D *newUpVector)

 @pend

 @astart
 RMcamera3D *toModify - a handle to the RMcamera3D object to be
    modified (result).

 const RMvertex3D *newUpVector - a handle to an RMvertex3D object
    supplying the new up vector (input).
 @aend

 @dstart

 Use this routine to set the Up vector attribute of an RMcamera3D
 object.  The contents of the caller-supplied newUpVector is copied to
 the RMcamera3D object's up vector attribute.
 
 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetUpVector (RMcamera3D *toModify,
		       const RMvertex3D *newUpVector)
{
    if ((RM_ASSERT(toModify, "rmCamera3DSetUpVector error: input RMcamera3D pointer is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(newUpVector, "rmCamera3DSetUpVector error: input RMvertex3D pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    VCOPY(newUpVector, &(toModify->up));

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetUpVector
 @pstart
 RMenum rmCamera3DGetUpVector (const RMcamera3D *toQuery,
		               RMvertex3D *returnUpVector)

 @pend

 @astart
 const RMcamera3D *toQuery - a handle to the RMcamera3D object to
    query (input).

 RMvertex3D *returnUpVector - a handle to an RMvertex3D object
    supplied by the caller (modified).
 @aend

 @dstart

 Use this routine to query the Up vector attribute of an RMcamera3D
 object.  The RMcamera3D object's up vector is copied into the
 caller-supplied memory.
 
 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DGetUpVector (const RMcamera3D *toQuery,
		       RMvertex3D *returnUpVector)
{
    if ((RM_ASSERT(toQuery, "rmCamera3DGetUpVector error: input RMcamera3D pointer is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(returnUpVector, "rmCamera3DGetUpVector error: input RMvertex3D pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);
	
    VCOPY(&(toQuery->up), returnUpVector);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetFOV
 @pstart
 RMenum rmCamera3DSetFOV (RMcamera3D *toModify,
                          float newFOV)
 @pend

 @astart
 RMcamera3D *toModify - a handle to an RMcamera3D object that will be
    modified by this routine (modified).

 float newFOV - a floating point value, the new FOV parameter (input).
 @aend

 @dstart

 Use this routine to set the FOV parameter at a 3D camera object. If
 successful, RM_CHILL is returned, otherwise, RM_WHACKED is returned.

 FOV in OpenRM refers to the horizontal field of view, not the
 vertical field of view. Increasing the FOV in a 3D camera will cause
 more of the scene to be visible along the horizontal axis.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetFOV (RMcamera3D *toModify,
		  float newFOV)
{
    if (RM_ASSERT(toModify, "rmCamera3DSetFOV error: input camera is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    toModify->fov = newFOV;	/* range check?? */

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetFOV
 @pstart
 float rmCamera3DGetFOV (const RMcamera3D *toQuery)
 @pend

 @astart
 const RMcamera3D *toQuery - a handle to an RMcamera3D object that
    will be queried (input).
 @aend

 @dstart

 Use this routine to obtain the FOV parameter from a 3D camera object.
 A floating point value is returned to the caller.

 FOV in OpenRM refers to the horizontal field of view, not the
 vertical field of view. Increasing the FOV in a 3D camera will cause
 more of the scene to be visible along the horizontal axis.

 @dend
 * ----------------------------------------------------
 */
float
rmCamera3DGetFOV (const RMcamera3D *toQuery)
{
    if (RM_ASSERT(toQuery, "rmCamera3DGetFOV error: input camera is NULL. Returning 0.0F.") == RM_WHACKED)
	return(0.0F);
    
    return(toQuery->fov);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetHither
 @pstart
 RMenum rmCamera3DSetHither (RMcamera3D *toModify,
                            float newHither)
 @pend

 @astart
 RMcamera3D *toModify - a handle to the RMcamera3D object (modified).

 float newHither - a floating point value containing the new front
    clip plane value (input).
 @aend

 @dstart

 Used to set the value of the front clip plane attribute of the
 RMcamera3D object. The front clip plane value is a distance, in world
 coordinates, from the eye point to the front clip plane.  Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetHither (RMcamera3D *toModify,
		     float newHither)
{
    if (RM_ASSERT(toModify, "rmCamera3DSetHither error: input camera is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    toModify->hither = newHither;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetHither
 @pstart
 float rmCamera3DGetHither (const RMcamera3D *toQuery)
 @pend

 @astart
 const RMcamera3D *toQuery - a handle to the RMcamera3D object to be
    queried (input).
 @aend

 @dstart

 Returns to the caller the value of the front clip plane attribute of
 the RMcamera3D object. The front clip plane value is a distance, in
 world coordinates, from the eye point to the front clip plane.
 
 @dend
 * ----------------------------------------------------
 */
float
rmCamera3DGetHither (const RMcamera3D *toQuery)
{
    if (RM_ASSERT(toQuery, "rmCamera3DGetHither error: input camera is NULL, returning 0.0F") == RM_WHACKED)
	return(0.0F);
    
    return(toQuery->hither);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetYon
 @pstart
 RMenum rmCamera3DSetYon (RMcamera3D *toModify,
                          float newYon)
 @pend

 @astart
 RMcamera3D *toModify - a handle to the RMcamera3D object (modified).

 float newYon - a floating point value containing the new back clip
    plane value (input).
 @aend

 @dstart

 Used to set the value of the back clip plane attribute of the
 RMcamera3D object. The back clip plane value is a distance, in world
 coordinates, from the eye point to the back clip plane.  Returns
 RM_CHILL upon success, or RM_WHACKED upon failure.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetYon(RMcamera3D *toModify,
		 float newYon)
{
    if (RM_ASSERT(toModify,"rmCamera3DSetYon error: input RMcamera3D pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    toModify->yon = newYon;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetYon
 @pstart
 float rmCamera3DGetYon(const RMcamera3D *toQuery)
 @pend

 @astart

 const RMcamera3D *toQuery - a handle to the RMcamera3D object to be
    queried (input). 
 @aend

 @dstart

 Returns to the caller the value of the clip plane attribute of the
 RMcamera3D object. The back clip plane value is a distance, in world
 coordinates, from the eye point to the back clip plane.
 
 @dend
 * ----------------------------------------------------
 */
float
rmCamera3DGetYon (const RMcamera3D *toQuery)
{
    if (RM_ASSERT(toQuery, "rmCamera3DGetYon error: input RMcamera3D pointer is NULL. Returning 0.0F") == RM_WHACKED)
	return(0.0F);
    
    return(toQuery->yon);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetStereo
 @pstart
 RMenum rmCamera3DSetStereo (RMcamera3D *toModify,
		             RMenum newBoolValue)
 @pend

 @astart
 RMcamera3D *toModify - a handle to an RMcamera3D object (modified).

 RMenum newBoolValue - a boolean value, either RM_TRUE or RM_FALSE
    (input).
 @aend

 @dstart

 Use this routine to set the stereo attribute of a 3D camera. Returns
 RM_CHILL upon success or RM_WHACKED upon failure.

 A stereo 3D camera in OpenRM defines a binocular viewing geometry.
 The binocular viewing geometry is a function of eye separation
 (rmCamera3DSetEyeSeparation) and focal length
 (rmCamera3DSetFocalDistance).

 When the 3D camera is set to be a stereo camera with this routine,
 the confluence of multipass rendering control and stereo 3D camera
 parameters are used to internally compute left- and right-eye views
 used for rendering. If the 3D camera's "is stereo" attribute is set
 to RM_FALSE, a monocular view will be used during a stereo multipass
 rendering.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetStereo (RMcamera3D *c,
		     RMenum rm_bool)
{
    if (RM_ASSERT(c, "rmCamera3DSetStereo error: input RMcamera3D object is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);
    
    if ((rm_bool != RM_TRUE) && (rm_bool != RM_FALSE))
	return(RM_WHACKED);
    else
	c->isStereo = rm_bool;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetStereo
 @pstart
 RMenum rmCamera3DGetStereo (const RMcamera3D *toQuery)
 @pend

 @astart
 const RMcamera3D *toQuery - a handle to an RMcamera3D object that
    will be queried by this routine (input).
 @aend

 @dstart

 Use this routine to obtain the stereo attribute of a 3D
 camera. Returns RM_WHACKED upon failure, otherwise returns RM_TRUE or
 RM_FALSE.

 A stereo 3D camera in OpenRM defines a binocular viewing geometry.
 The binocular viewing geometry is a function of eye separation
 (rmCamera3DSetEyeSeparation) and focal length
 (rmCamera3DSetFocalDistance).

 When the 3D camera is set to be a stereo camera with this routine,
 the confluence of multipass rendering control and stereo 3D camera
 parameters are used to internally compute left- and right-eye views
 used for rendering. If the 3D camera's "is stereo" attribute is set
 to RM_FALSE, a monocular view will be used during a stereo multipass
 rendering.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DGetStereo (const RMcamera3D *c)
{
    if (RM_ASSERT(c, "rmCamera3DGetStereo error: input RMcamera3D object is NULL. Returning RM_WHACKED.") == RM_WHACKED)
	return(RM_WHACKED);
    
    return(c->isStereo);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetEyeSeparation
 @pstart
 RMenum rmCamera3DSetEyeSeparation (RMcamera3D *toModify, float newVal)
 @pend

 @astart
 RMcamera3D *toModify - a handle to an RMcamera3D object (modified).

 float newVal - a floating point value specifying the interocular
    displacement in degrees (input).
 @aend

 @dstart

 Use this routine to modify the interocular separation parameter of a
 stereo 3D camera. Returns RM_WHACKED upon failure, otherwise returns
 RM_CHILL.

 A stereo 3D camera in OpenRM defines a binocular viewing geometry.
 The binocular viewing geometry is a function of eye separation
 (rmCamera3DSetEyeSeparation) and focal length
 (rmCamera3DSetFocalDistance).

 The stereo camera's binocular geometry, specifically the interocular
 distance, is computed using the 3D camera's eye point, the look-at
 point, the focal length and the interocular distance. The "real" look
 at point of a 3D stereo camera is computed as the product of the
 focal length with the (unitized) eye/look-at vector. The left and
 right eyes both look at the "real" stereo look-at point (not the one
 specified by rmCamera3DSetAt). The apex of the triangle formed by the
 left and right eyes and the "real" stereo look at point will have an
 angular measure of X degrees, where X is specified as the parameter
 to the routine rmCamera3DSetEyeSeparation. We have found that values
 in the range 2.5-3.5 degrees work well for most viewers.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetEyeSeparation (RMcamera3D *c,
			    float newval)
{
    if (RM_ASSERT(c, "rmCamera3DSetEyeSeparation error: input RMcamera3D points is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);
    
    c->degrees_eye_separation = newval;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetEyeSeparation
 @pstart
 RMenum rmCamera3DGetEyeSeparation (RMcamera3D *toQuery)
 @pend

 @astart

 const RMcamera3D *toQuery - a handle to an RMcamera3D object that
    will be queried by this routine (input).

 @aend

 @dstart

 Use this routine to query the interocular separation parameter of a
 stereo 3D camera. Returns a floating point value representing the 3D
 stereo camera's interocular distance parameter.

 A stereo 3D camera in OpenRM defines a binocular viewing geometry.
 The binocular viewing geometry is a function of eye separation
 (rmCamera3DSetEyeSeparation) and focal length
 (rmCamera3DSetFocalDistance).

 The stereo camera's binocular geometry, specifically the interocular
 distance, is computed using the 3D camera's eye point, the look-at
 point, the focal length and the interocular distance. The "real" look
 at point of a 3D stereo camera is computed as the product of the
 focal length with the (unitized) eye/look-at vector. The left and
 right eyes both look at the "real" stereo look-at point (not the one
 specified by rmCamera3DSetAt). The apex of the triangle formed by the
 left and right eyes and the "real" stereo look at point will have an
 angular measure of X degrees, where X is specified as the parameter
 to the routine rmCamera3DSetEyeSeparation. We have found that values
 in the range 2.5-3.5 degrees work well for most viewers.

 @dend
 * ----------------------------------------------------
 */
float
rmCamera3DGetEyeSeparation (const RMcamera3D *toQuery)
{
    if (RM_ASSERT(toQuery, "rmCamera3DGetEyeSeparation error: input RMcamera3D points is NULL. Returning 1.0F.") == RM_WHACKED)
	return(1.0F);
    
    return(toQuery->degrees_eye_separation);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DSetFocalDistance
 @pstart
 RMenum rmCamera3DSetFocalDistance (RMcamera3D *toModify, float newVal)
 @pend

 @astart
 RMcamera3D *toModify - a handle to an RMcamera3D object (modified).

 float newVal - a floating point value specifying new focal distance
    length (input).
 @aend

 @dstart

 Use this routine to modify the focal distance parameter of a stereo
 3D camera. Returns RM_WHACKED upon failure, otherwise returns
 RM_CHILL.

 A stereo 3D camera in OpenRM defines a binocular viewing geometry.
 The binocular viewing geometry is a function of eye separation
 (rmCamera3DSetEyeSeparation) and focal length
 (rmCamera3DSetFocalDistance).

 In a binocular view model, the left and right eyes look at a point
 somewhere in space. This point is computed from the 3D stereo camera
 parameters: eye point (rmCamera3DSetEye), the look-at point
 (rmCamera3DSetAt) and the focal distance
 (rmCamera3DSetFocalDistance). The point is computed as the eye point
 plus the focal distance times the eye-at vector.

 A value of 1.0 for the focal distance places the stereo look-at point
 at the look-at point specified by rmCamera3DSetAt. A value of, say,
 0.707, places the stereo look at point in front of the 3D camera's
 look-at point, and a value of, say, 1.414, places the stereo look-at
 point somewhere behind the 3D camera's look at point.

 Manipulation of the focal distance and the interocular separation
 parameters can have a profound effect upon the perceived stereo view.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DSetFocalDistance (RMcamera3D *toModify,
			    float newval)
{
    if (RM_ASSERT(toModify, "rmCamera3DSetFocalDistance error: input RMcamera3D is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    toModify->focalDistance = newval;

    return(RM_CHILL);
}

			  
/*
 * ----------------------------------------------------
 * @Name rmCamera3DGetFocalDistance
 @pstart
 RMenum rmCamera3DGetFocalDistance (const RMcamera3D *toQuery)
 @pend

 @astart
 const RMcamera3D *toQuery - a handle to an RMcamera3D object that
    will be queried by this routine (input).
 @aend

 @dstart

 Use this routine to query the focal distance parameter of a stereo 3D
 camera. A floating point value is returned by this routine.

 A stereo 3D camera in OpenRM defines a binocular viewing geometry.
 The binocular viewing geometry is a function of eye separation
 (rmCamera3DSetEyeSeparation) and focal length
 (rmCamera3DSetFocalDistance).

 In a binocular view model, the left and right eyes look at a point
 somewhere in space. This point is computed from the 3D stereo camera
 parameters: eye point (rmCamera3DSetEye), the look-at point
 (rmCamera3DSetAt) and the focal distance
 (rmCamera3DSetFocalDistance). The point is computed as the eye point
 plus the focal distance times the eye-at vector.

 A value of 1.0 for the focal distance places the stereo look-at point
 at the look-at point specified by rmCamera3DSetAt. A value of, say,
 0.707, places the stereo look at point in front of the 3D camera's
 look-at point, and a value of, say, 1.414, places the stereo look-at
 point somewhere behind the 3D camera's look at point.

 Manipulation of the focal distance and the interocular separation
 parameters can have a profound effect upon the perceived stereo view.

 @dend
 * ----------------------------------------------------
 */
float
rmCamera3DGetFocalDistance (const RMcamera3D *c)
{
    if (RM_ASSERT(c, "rmCamera3DGetFocalDistance error: input RMcamera3D is NULL. Returning 1.0F") == RM_WHACKED)
	return(1.0F);
    
    return(c->focalDistance);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DComputeViewMatrix
 @pstart
 RMenum rmCamera3DComputeViewMatrix (const RMcamera3D *source,
			             RMmatrix *viewReturn,
				     RMmatrix *projectionReturn)
 @pend

 @astart
 const RMcamera3D *source - a handle to an RMcamera3D object (input).

 RMmatrix *viewReturn - a handle to an RMmatrix object (result).

 RMmatrix *projectionReturn - a handle to an RMmatrix object (result).
 @aend

 @dstart

 Computes a view and projection matrix from an RMcamera3D object,
 returning the computed matrices in the "viewReturn" and
 "projectionReturn" parameters, respectively. RM_CHILL is returned
 upon success, or RM_WHACKED upon failure. Failure can occur if either
 of the input parameters is NULL, or if there is some type of fatal
 numerical error caused by bogus 3D camera parameters. These latter
 problems are detected and reported via rmError().

 The view matrix represents the affine transformation that transforms
 from world coordinates into "eye coordinates," assuming a monocular
 view.  The same matrix is returned regardless of whether or not the
 input camera is a stereo or mono camera.

 The projection matrix represents the transformation from eye space
 coordinates to NDC coordinates. If the camera uses perspective
 projection, that transformation will be present in the
 projectionReturn matrix.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DComputeViewMatrix (const RMcamera3D *c,
			     RMmatrix *m,
			     RMmatrix *p)
{
    if ((RM_ASSERT(c, "rmCamera3DComputeViewMatrix error: the input RMcamera3D object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(m, "rmCamera3DComputeViewMatrix error: the return view RMmatrix object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(p, "rmCamera3DComputeViewMatrix error: the return projection RMmatrix object is NULL") == RM_WHACKED))
	return(RM_WHACKED);
    
    private_rmComputeViewMatrix(c, m, p);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmCamera3DComputeViewFromGeometry
 @pstart
 RMenum rmCamera3DComputeViewFromGeometry (RMcamera3D *toModify,
			                   const RMnode *source,
					   int windowWidth, 
					   int windowHeight)

 @pend

 @astart
 RMcamera3D *toModify - a handle to the RMcamera3D object (modified).

 const RMnode *source - a handle to an RMnode. the bounding box of
    this RMnode will be used in computing 3D camera parameters
    (input).

 int windowWidth, windowHeight - int values specifying the width and
    height in pixels of the display window. These values are used in
    computing an aspect ratio for the camera (input). 
 @aend

 @dstart

 This routine computes and assigns 3D camera parameters such that a
 volume defined by the bounding box attribute of the input RMnode will
 be visible, regardless of box orientation. If present, the scale matrix
 transformation attribute of the RMnode will be used to enlarge or shrink 
 the bounding box parameter of the RMnode for the purposes of computing 
 new camera parameters.

 The 3D camera must be a valid RMcamera3D object allocated by the
 caller.  This routine merely computes new values for an existing
 camera using the bounding box of an RMnode. It neither creates nor
 destroys an RMcamera3D.

 The 3D camera's FOV parameter is used as part of the computation, and
 will not be overwritten by this routine. All stereo parameters, if
 any, are ignored and remain unmodified by this routine.

 The new 3D camera parameters are computed thus:
 
 1. The 3D camera's look-at point will be the center of the RMnode's
    bounding box.

 2. The 3D camera's new eye position is the same as the new look-at
    point, but offset along the Z axis a distance D. The distance D is
    computed as boxsize/tan(FOV/2) where boxsize is the distance from
    the bounding box's min vertex to the box's max vertex. The basic
    idea is to move the eye point away from the look-at point a
    distance which is sufficient for the entire box to be visible,
    given the requested FOV.

 3. The near and far clip planes are set to a distance F which is
    computed as D-boxsize*1.4 and D+boxsize*1.4, respectively, where D
    is the same D computed in (2). Again, the point is to have the
    entire bounding box within the view frustum regardless of
    orientation.
	
    Note: 8/16/02 - changed computation of near/far to D-boxsize*2.0,
    D+boxsize*5.0, respectively. This gives more room to maneuver before
    the object gets clipped.

 4. The aspect ratio for the 3D camera is set using the input values
    windowWidth and WindowHeight.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmCamera3DComputeViewFromGeometry (RMcamera3D *c,	/* input and output */
				   const RMnode *r,	/* input, has geometry & stuff */
				   int w,		/* image width, for aspect ratio */
				   int h)		/* image height, for aspect ratio */
{
    float         new_hither, new_yon;
    float         vp[] = {0.0, 0.0, 1.0, 1.0};
    double        d, d2, boxsize;
    RMvertex3D    new_at;
    RMvertex3D    new_eye;
    RMvertex3D    new_up;
    RMvertex3D    bmin, bmax;
    const RMnode *root;
    RMmatrix      scale, C, minusC;
    extern double sqrt(double);

    if ((RM_ASSERT(c, "rmCamera3DComputeViewFromGeometry error: the input RMcamera3D object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(r, "rmCamera3DComputeViewFromGeometry error: the input RMnode is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    root = r;
    rmNodeGetBoundingBox(root, &bmin, &bmax);

    /* get the scale matrix */
    if (rmNodeGetScaleMatrix(r, &scale) == RM_WHACKED)
	rmMatrixIdentity(&scale);

    /* multiply the bounding box with the scale matrix */
    {
        RMvertex3D center;
	RMmatrix   composite;

	rmNodeGetCenter(r, &center);
        rmMatrixIdentity(&C);
	rmMatrixIdentity(&minusC);

	C.m[3][0] = center.x;
	C.m[3][1] = center.y;
	C.m[3][2] = center.z;
	
	minusC.m[3][0] = -1.0 * center.x;
	minusC.m[3][1] = -1.0 * center.y;
	minusC.m[3][2] = -1.0 * center.z;

	rmMatrixIdentity(&composite);
	rmMatrixMultiply(&minusC, &composite, &composite);
	rmMatrixMultiply(&composite, &scale, &composite);
	rmMatrixMultiply(&composite, &C, &composite);

	rmPointMatrixTransform(&bmin, &composite, &bmin);
	rmPointMatrixTransform(&bmax, &composite, &bmax);
    }
    
    /* we're going to assume that the bounding boxes are all sync'ed up. */
    new_up.x = new_up.z = 0.0;
    new_up.y = 1.0;

    /* the new "at" point will be a point at the x/y center of the
     front face of the bounding box. */
    
    new_at.x = bmin.x + ((bmax.x - bmin.x) * 0.5);
    new_at.y = bmin.y + ((bmax.y - bmin.y) * 0.5);
    /*    new_at.z = bmax.z; */
    new_at.z = bmin.z + ((bmax.z - bmin.z) * 0.5);

    /* compute the length of the diagonal of the box */
    d = ((bmax.x - bmin.x) * (bmax.x - bmin.x)) + ((bmax.y - bmin.y) * (bmax.y - bmin.y)) + ((bmax.z - bmin.z) * (bmax.z - bmin.z));
    boxsize = sqrt(d);
    boxsize *= 0.5;

    /* the new eye point will lie some distance along the +z axis
     * away from the new at point.  "some distance" is defined as a
     * distance which is "far enough" away so that the front face of
     * the bounding box will lie within FOV degrees of view.
     */

    d = boxsize;
    
    d2 = RM_DEGREES_TO_RADIANS(c->fov);
    d2 *= 0.5;
    d2 = tan(d2);

    d = boxsize / d2;
    d2 = d;

    /* the choices for hither and yon are somewhat arbitrary. what we
     * want is a choice of hither/yon that will permit the entire
     * object to be displayed w/o clipping regardless of orientation. so,
     * we need to look at the maximum dimension of the box.  also, such
     * a goal may not be achievable under some conditions.  for example, when
     * the requested fov is > 90, then the eye starts getting closer to
     * the box rather than further away from it.  oh well...
     *
     * hither/yon are a function of the boxsize.  these are set so that
     * the entire bbox will always be visible.  possible error conditions
     * include the case of d being < boxsize.
     */
    if (d < boxsize * 2.0)
      fprintf(stderr," error: we're about to set the front clip plane behind the eye. \n");
    
    new_hither = d - (boxsize * 2.0);
    new_yon = d + (boxsize * 5.0F);

    rmCamera3DResetAspectRatio(c, vp, w, h);

    new_eye.x = new_at.x;
    new_eye.y = new_at.y;
    new_eye.z = new_at.z + (d2 * 1.1);

    rmCamera3DSetEye(c, &new_eye);
    rmCamera3DSetAt(c, &new_at);
    rmCamera3DSetUpVector(c, &new_up);
    rmCamera3DSetHither(c, new_hither);
    rmCamera3DSetYon(c, new_yon);

    return(RM_CHILL);
}


/* PRIVATE */
static void
private_rmCamera2DComputeMatrix (const RMcamera2D *c,
				 RMmatrix *m)
{
    float    x, y, z;
    float    tx, ty, tz;
    float    cwidth, center, cxmax, cxmin;
    double   left ,right, top, bottom;
    RMmatrix P;

    rmMatrixIdentity(&P);

    cwidth = c->xmax - c->xmin;
    center = ((c->xmax - c->xmin) * 0.5F) + c->xmin;
    cxmax = center + (cwidth * 0.5F) * c->aspect_ratio;
    cxmin = center - (cwidth * 0.5F) * c->aspect_ratio;
    
    left = cxmin;
    right = cxmax;
    bottom = c->ymin;
    top = c->ymax;
	
    x = 2.0 / (right - left);
    y = 2.0 / (top - bottom);
    z = 1.0;
    tx = -(right + left) / (right  -left);
    ty = -(top + bottom) / (top - bottom);
    tz = 0.;

    P.m[3][0] = tx;
    P.m[3][1] = ty;
    P.m[3][2] = tz;

    P.m[0][0] = x;
    P.m[1][1] = y;
    P.m[2][2] = z;

    *m = P;
}


/* PRIVATE */
void
private_rmComputeViewMatrix (const RMcamera3D *c,
			     RMmatrix *m,
			     RMmatrix *proj)
{
    /* compute both the view and projection matrices  */
    RMmatrix   R, T, TR, P;
    RMvertex3D z, y, x;

    /* Make the translation matrix */
    rmMatrixIdentity(&T);
    T.m[3][0] = -1.0 * c->eye.x;
    T.m[3][1] = -1.0 * c->eye.y;
    T.m[3][2] = -1.0 * c->eye.z;

    /* Make rotation matrix */
    rmMatrixIdentity(&R);
    
    /* Z vector */
    rmVertex3DDiff(&(c->eye), &(c->at),&z);
    rmVertex3DNormalize(&z);

    y = c->up;
    rmVertex3DNormalize(&y);

    /* X vector = Y cross Z */
    rmVertex3DCross(&y, &z, &x);

    /* Recompute Y = Z cross X */
    rmVertex3DCross(&z, &x, &y);

   /* cross product gives area of parallelogram, which is < 1.0 for
    non-perpendicular unit-length vectors; so normalize x, y here */

    /* this might be transposed */
    R.m[0][0] = x.x;
    R.m[1][0] = x.y;
    R.m[2][0] = x.z;

    R.m[0][1] = y.x;
    R.m[1][1] = y.y;
    R.m[2][1] = y.z;

    R.m[0][2] = z.x;
    R.m[1][2] = z.y;
    R.m[2][2] = z.z;

    /* concatenate the translational and rotational pieces together */
    rmMatrixMultiply(&T, &R, &TR);

    /* build the projection matrix */
    rmMatrixIdentity(&P);

    if (rmCamera3DGetProjection(c) == RM_PROJECTION_PERSPECTIVE)
    {
        double fovy, aspect, znear, zfar;
        double xmin, xmax, ymin, ymax;
        double x, y, a, b, C, d;
        double left, right, bottom, top, nearval, farval;

        fovy = c->fov;
        aspect = c->aspect;
        znear = c->hither;
        zfar = c->yon;

        ymax = znear * tan(fovy * RM_PI / 360.0);
        ymin = -ymax;

        xmin = ymin * aspect;
        xmax = ymax * aspect;

        left = xmin;
        right = xmax;
        bottom = ymin;
        top = ymax;
        nearval = znear;
        farval = zfar;

        x = (2.0 * nearval) / (right - left);
        y = (2.0 * nearval) / (top - bottom);
        a = (right + left) / (right - left);
        b = (top + bottom) / (top - bottom);
        C = -(farval + nearval) / ( farval - nearval);
        d = -(2.0 * farval * nearval) / (nearval - farval);  /* error? */

        P.m[0][0] = x;
        P.m[2][0] = a;
        P.m[1][1] = y;
        P.m[2][1] = b;
        P.m[2][2] = C;
        P.m[2][3] = -1.0;
        P.m[3][2] = -d;
        P.m[3][3] = 1.0;		/* should be 0, but OGL puts a 1 there */
    }
    else /* RM_PROJECTION_ORTHOGONAL */
    {
        float      x, y, z;
        float      tx, ty, tz;
        double     width, height;
        double     sfov, mag;
        double     left, right, top, bottom, hither, yon;
        RMvertex3D eye_at;

        /* set the width of the view volume to be proportional to
	 * the field-of-view angle.  we do this a little differently
	 * than OpenGL, using the FOV to specify the horizontal field
	 * of view, rather than the vertical field of view.
	 */
	sfov = sin((double)c->fov);

	eye_at.x = c->eye.x - c->at.x;
        eye_at.y = c->eye.y - c->at.y;
        eye_at.z = c->eye.z - c->at.z;
	
        mag = rmVertex3DMag(&eye_at);
        width = mag * sfov;

        height = width / c->aspect;

        left = -width;
        right = width;
        bottom = -height;
        top = height;
        hither = c->hither;
        yon = c->yon;
	
        x = 2.0 / (right - left);
        y = 2.0 / (top - bottom);
        z = -2.0 / (yon - hither); 
        tx = -(right + left) / (right - left);
        ty = -(top + bottom) / (top - bottom);
        tz = -(yon + hither) / (yon - hither);

        P.m[3][0] = tx;
        P.m[3][1] = ty;
        P.m[3][2] = tz;

        P.m[0][0] = x;
        P.m[1][1] = y;
        P.m[2][2] = z;
    }
    *proj = P;
    *m = TR;
}
/* EOF */
