/*
 * Stereo camera plug-in
 */

/**
 * \ingroup rpstereo
 * \page rpstereooverview RpStereo Plugin Overview
 *
 * The RpStereo plugin adds support for stereoscopic rendering to the 
 * Core RenderWare Graphics API’s functionality by extending the RwCamera datatype. 
 *
 * Provision are made for the developer to change focal length, the distance 
 * between the two virtual cameras, and the visual ordering and display of 
 * the stereo images within the main RwCamera raster.
 *
 */

/**********************************************************************
 *
 * File :     stereo.c
 *
 * Abstract : Add Stereo Camera support to RenderWare
 *
 **********************************************************************
 *
 * This file is a product of Criterion Software Ltd.
 *
 * This file is provided as is with no warranties of any kind and is
 * provided without any obligation on Criterion Software Ltd. or
 * Canon Inc. to assist in its use or modification.
 *
 * Criterion Software Ltd. will not, under any
 * circumstances, be liable for any lost revenue or other damages arising
 * from the use of this file.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 ************************************************************************/

/*--- Include files ---*/

#include <stdlib.h>

#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rpstereo.h>          /* Stereo camera structures and functions */

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rpstereo.c,v 1.59 2001/09/21 09:50:59 johns Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

/*--- Local Structures ---*/

/* Data structure that is used to extend the RenderWare global data for
 * use by the Stereo Camera plugin
 */

typedef struct StereoCameraGlobals StereoCameraGlobals;
struct StereoCameraGlobals
{
    RwBool              mainCameraLock; /* To stop recursion on camera initialisation */
    RwCamera           *stereoUpdate; /* Store current camera inside a rendering loop */
};

/* Data structure that is used to extend the RenderWare Camera structure
 * use by the Stereo Camera plugin
 */

typedef struct StereoCamera StereoCamera;
struct StereoCamera
{
    RwCamera           *left;   /* Sub Camera for left eye */
    RwCamera           *right;  /* Sub Camera for right eye */
    RwReal              dist;   /* Eye Separation distance between cameras */
    RwReal              focal;  /* Distance from camera to focal point */
    RpStereoCameraMode  mode;   /* current stereo mode for this camera */
};

/* Data structure that is used to store the Stereo Camera state in a
 * binary file.
 */

typedef struct BinaryStereoCamera BinaryStereoCamera;
struct BinaryStereoCamera
{
    RwReal              dist;   /* Eye separation */
    RwReal              focal;  /* Focal distance */
    RwInt32             mode;   /* Camera mode */
};

/*--- Local Definitions ---*/

#define DEFAULT_EYE_SEPARATION  (RwReal)0.04
#define DEFAULT_FOCAL_DISTANCE  (RwReal)10.0
#define DEFAULT_STEREO_MODE     rpSTEREORIGHTLEFT

/* Some macros to make plugin data access easier */

#define STEREOCAMERAGLOBAL(var)                                         \
    RWPLUGINOFFSET(StereoCameraGlobals, RwEngineInstance, GlobalOffset)->var

#define STEREOCAMERAFROMCAMERA(c)                       \
    *RWPLUGINOFFSET(StereoCamera *, c, CameraOffset)

#define STEREOCAMERAFROMCONSTCAMERA(c)                       \
    *RWPLUGINOFFSETCONST(StereoCamera *, c, CameraOffset)

/* a macro to make the Stereo Camera construction code more readable */

#define ERRORCHECK(x) if (!(x)) { break; }

/*--- Global Variables ---*/

#if (defined(RWDEBUG))
long                rpStereoStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

/* These offsets define the offsets for the Stereo Camera private data into
 * the respective structures
 */

static RwInt32      GlobalOffset = 0; /* Offset into global data */
static RwInt32      CameraOffset = 0; /* Offset into RwCamera */

/************************************************************************
 *
 *      Function:       StereoOpen()
 *
 *      Description:    Called from RenderWare when global data is intialised
 *                      This function is used to initialise the Stereo Camera
 *                      specific global data.
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwEngineRegisterPlugin
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwEngineRegisterPlugin
 *
 ************************************************************************/
static void        *
StereoOpen(void *instance,
           RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("StereoOpen"));
    RWASSERT(instance);

    STEREOCAMERAGLOBAL(mainCameraLock) = FALSE;
    STEREOCAMERAGLOBAL(stereoUpdate) = FALSE;

    RWRETURN(instance);
}

/************************************************************************
 *
 *      Function:       StereoClose()
 *
 *      Description:    Called from RenderWare when global data is terminated
 *                      This function is used to clean up the Stereo Camera
 *                      specific global data. In this case no clean up
 *                      is required.
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwEngineRegisterPlugin
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwEngineRegisterPlugin
 *
 ************************************************************************/
static void        *
StereoClose(void *instance,
            RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("StereoClose"));
    RWASSERT(instance);

    RWRETURN(instance);
}

/************************************************************************
 *
 *      Function:       StereoCameraSetupFrame()
 *
 *      Description:    Initialise the frame data for the left & right
 *                      stereo sub cameras to a known state.
 *
 *      Parameters:     stereoCamera - the stereo camera block to be updated
 *
 ************************************************************************/
static void
StereoCameraSetupFrame(StereoCamera * stereoCamera)
{
    RwV3d               shift;  /* Position offset of left/right camera along viewplane */
    RwV2d               voffset; /* Camera View Offset adjustment for left/right cameras */
    RwReal              offset; /* temp variable used to calculate the above */

    RWFUNCTION(RWSTRING("StereoCameraSetupFrame"));
    RWASSERT(stereoCamera);

    /* first restore the frames to a known state */

    RwFrameSetIdentity(RwCameraGetFrame(stereoCamera->left));
    RwFrameSetIdentity(RwCameraGetFrame(stereoCamera->right));

    offset = (stereoCamera->dist) / (stereoCamera->focal);

    shift.x = (stereoCamera->dist) - (offset);
    shift.y = 0.0f;
    shift.z = 0.0f;

    voffset.x = offset;
    voffset.y = 0.0f;

    RwFrameTranslate(RwCameraGetFrame(stereoCamera->right),
                     &shift, rwCOMBINEREPLACE);
    RwCameraSetViewOffset(stereoCamera->right, &voffset);

    shift.x = -shift.x;
    voffset.x = -voffset.x;

    RwFrameTranslate(RwCameraGetFrame(stereoCamera->left),
                     &shift, rwCOMBINEREPLACE);
    RwCameraSetViewOffset(stereoCamera->left, &voffset);

    RWRETURNVOID();
}

/************************************************************************
 *
 *      Function:       StereoCameraDestroy()
 *
 *      Description:    Called from RenderWare whenever a camera is destroyed.
 *                      This function is used to destroy all data allocated
 *                      in the corresponding constructor function
 *                      StereoCameraCreate()
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwCameraRegisterPlugin
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwCameraRegisterPlugin
 *
 ************************************************************************/
static void        *
StereoCameraDestroy(void *object,
                    RwInt32 offset, RwInt32 __RWUNUSED__ size)
{
    StereoCamera       *stereoCamera = STEREOCAMERAFROMCAMERA(object);

    RWFUNCTION(RWSTRING("StereoCameraDestroy"));
    RWASSERT(object);

    if (stereoCamera)
    {
        RwMemoryFunctions  *memFns = RwOsGetMemoryInterface();
        int                 i;
        RwCamera           *cam;

        for (i = 0, cam = stereoCamera->left;
             i < 2; i++, cam = stereoCamera->right)
        {
            if (cam)
            {
                if (RwCameraGetFrame(cam))
                {
                    RwFrameDestroy(RwCameraGetFrame(cam));
                }
                if (RwCameraGetRaster(cam))
                {
                    RwRasterDestroy(RwCameraGetRaster(cam));
                }
                if (RwCameraGetZRaster(cam))
                {
                    RwRasterDestroy(RwCameraGetZRaster(cam));
                }
                if (RwCameraGetWorld(cam))
                {
                    RpWorldRemoveCamera(RwCameraGetWorld(cam), cam);
                }
                RwCameraDestroy(cam);
            }
        }
        memFns->rwfree(stereoCamera);
        *RWPLUGINOFFSET(StereoCamera *, object, offset) =
            (StereoCamera *) NULL;
    }

    RWRETURN(object);
}

/************************************************************************
 *
 *      Function:       StereoCameraCreate()
 *
 *      Description:    Called from RenderWare whenever a camera is created.
 *                      This function is used to initialise the data block
 *                      attached to every RwCamera
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwCameraRegisterPlugin
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwCameraRegisterPlugin
 *
 ************************************************************************/
static void        *
StereoCameraCreate(void *object, RwInt32 offset, RwInt32 size)
{
    RWFUNCTION(RWSTRING("StereoCameraCreate"));
    RWASSERT(object);

    /* Initialize to a known state */

    *RWPLUGINOFFSET(StereoCamera *, object, offset) =
        (StereoCamera *) NULL;

    /* Since the Stereo Camera module is required to create
     * sub cameras for the left and right images, we need to prevent
     * infinite recursion by limiting the Stereo data to the main
     * camera only. This is achieved by using a global data block semaphore
     */

    if (!STEREOCAMERAGLOBAL(mainCameraLock))
    {
        int                 status = FALSE;
        StereoCamera       *stereoCamera = (StereoCamera *) NULL;

        STEREOCAMERAGLOBAL(mainCameraLock) = TRUE; /* prevent recursion */

        /* There is a lot of construction going on here. Rather than introduce
         * many levels of if..else in order to perform error checking, a single
         * do .. while loop is used with the break statement used to cause an
         * exit as soon as an error has occurred.
         * The cleanup routine is then called to back out gracefully
         */

        do
        {
            RwRaster           *tmpRas;
            RwFrame            *tmpFrame;
            RwMemoryFunctions  *memFns = RwOsGetMemoryInterface();

            /* allocate the stereo camera data */
            stereoCamera =
                (StereoCamera *) memFns->rwmalloc(sizeof(StereoCamera));
            if (!stereoCamera)
            {
                RWERROR((E_RW_NOMEM, sizeof(StereoCamera)));
                break;
            }

            /* create the left & right cameras */
            stereoCamera->left = RwCameraCreate();
            ERRORCHECK(stereoCamera->left);

            stereoCamera->right = RwCameraCreate();
            ERRORCHECK(stereoCamera->right);

            tmpRas = RwRasterCreate(0, 0, 0, rwRASTERTYPECAMERA);
            ERRORCHECK(tmpRas);
            RwCameraSetRasterVoid(stereoCamera->left, tmpRas);

            tmpRas = RwRasterCreate(0, 0, 0, rwRASTERTYPECAMERA);
            ERRORCHECK(tmpRas);
            RwCameraSetRasterVoid(stereoCamera->right, tmpRas);

            tmpRas = RwRasterCreate(0, 0, 0, rwRASTERTYPEZBUFFER);
            ERRORCHECK(tmpRas);
            RwCameraSetZRasterVoid(stereoCamera->left, tmpRas);

            tmpRas = RwRasterCreate(0, 0, 0, rwRASTERTYPEZBUFFER);
            ERRORCHECK(tmpRas);
            RwCameraSetZRasterVoid(stereoCamera->right, tmpRas);

            tmpFrame = RwFrameCreate();
            ERRORCHECK(tmpFrame);
            RwCameraSetFrameVoid(stereoCamera->left, tmpFrame);

            tmpFrame = RwFrameCreate();
            ERRORCHECK(tmpFrame);
            RwCameraSetFrameVoid(stereoCamera->right, tmpFrame);

            stereoCamera->dist = DEFAULT_EYE_SEPARATION;
            stereoCamera->focal = DEFAULT_FOCAL_DISTANCE;
            stereoCamera->mode = DEFAULT_STEREO_MODE;

            StereoCameraSetupFrame(stereoCamera);

            /* All OK so attach it to the Camera */

            *RWPLUGINOFFSET(StereoCamera *, object, offset) =
                stereoCamera;

            status = TRUE;     /* no errors */
        }
        while (0);

        STEREOCAMERAGLOBAL(mainCameraLock) = FALSE;

        if (!status)
        {
            /* an error occurred. Clean up any allocated data.
             * The destruct function has been written to account
             * for partially constructed objects so just use this
             */

            StereoCameraDestroy(object, offset, size);
            RWRETURN(NULL);
        }
    }
    RWRETURN(object);
}

/************************************************************************
 *
 *      Function:       StereoCameraCopy()
 *
 *      Description:    Called from RenderWare whenever a camera is copied.
 *                      Currently RenderWare does not support cloning of
 *                      cameras so this function is implemented as a dummy.
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwCameraRegisterPlugin
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwCameraRegisterPlugin
 *
 ************************************************************************/
static void        *
StereoCameraCopy(void *__RWUNUSED__ dst,
                 const void *__RWUNUSED__ src,
                 RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("StereoCameraCopy"));

    /* A failure status is returned. Since the functionality does not exist
     * we must do the 'right' thing if RenderWare is ever extended to support
     * camera cloning.
     */

    RWRETURN(NULL);
}

/************************************************************************
 *
 *      Function:       StereoCameraWriteStream()
 *
 *      Description:    Called from RenderWare whenever a camera is written
 *                      to a binary stream.  This function will store the
 *                      state of the stereo camera in the stream.
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwCameraRegisterPluginStream
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwCameraRegisterPluginStream
 *
 ************************************************************************/
static RwStream    *
StereoCameraWriteStream(RwStream * stream,
                        RwInt32 __RWUNUSED__ binaryLength,
                        const void *object,
                        RwInt32 __RWUNUSED__ offsetInObject,
                        RwInt32 __RWUNUSED__ sizeInObject)
{
    BinaryStereoCamera  binaryData;
    const StereoCamera *stereoCamera;

    RWFUNCTION(RWSTRING("StereoCameraWriteStream"));
    RWASSERT(stream);
    RWASSERT(object);

    stereoCamera = STEREOCAMERAFROMCONSTCAMERA(object);

    /* Capture the data is a nice convenient structure */
    binaryData.dist = stereoCamera->dist;
    binaryData.focal = stereoCamera->focal;
    binaryData.mode = (RwInt32) stereoCamera->mode;

    /* Convert RwReals to form suitable for binary data storage */
    RwMemRealToFloat32(&binaryData.dist, sizeof(binaryData.dist));
    RwMemRealToFloat32(&binaryData.focal, sizeof(binaryData.focal));

    /* Then make sure the whole block is little endian for storage */
    RwMemLittleEndian(&binaryData, sizeof(binaryData));

    /* Now we can write it to the stream */
    if (!RwStreamWrite(stream, &binaryData, sizeof(binaryData)))
    {
        /* Ooops, failed to write the data, propagate the error back... */
        RWRETURN((RwStream *) NULL);
    }

    /* Return success indicator */
    RWRETURN(stream);
}

/************************************************************************
 *
 *      Function:       StereoCameraReadStream()
 *
 *      Description:    Called from RenderWare whenever a camera containing
 *                      stereo camera data is read from a binary file.  This
 *                      function retrieves the data, and updates the specified
 *                      camera accordingly.
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwCameraRegisterPluginStream
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwCameraRegisterPluginStream
 *
 ************************************************************************/
static RwStream    *
StereoCameraReadStream(RwStream * stream,
                       RwInt32 __RWUNUSED__ binaryLength,
                       void *object,
                       RwInt32 __RWUNUSED__ offsetInObject,
                       RwInt32 __RWUNUSED__ sizeInObject)
{
    BinaryStereoCamera  binaryData;

    RWFUNCTION(RWSTRING("StereoCameraReadStream"));
    RWASSERT(stream);
    RWASSERT(object);

    /* Read in the data */
    if (RwStreamRead(stream, &binaryData, sizeof(binaryData)) !=
        sizeof(binaryData))
    {
        /* Ooops, failed to read the data, propagate the error back... */
        RWRETURN((RwStream *) NULL);
    }

    /* Convert it back to whatever endian machine we are running on */
    RwMemNative(&binaryData, sizeof(binaryData));

    /* Convert the reals back to whatever format this machine likes */
    RwMemFloat32ToReal(&binaryData.dist, sizeof(binaryData.dist));
    RwMemFloat32ToReal(&binaryData.focal, sizeof(binaryData.focal));

    /* And set the items in the camera */
    RpStereoCameraSetMode((RwCamera *) object,
                          (RpStereoCameraMode) binaryData.mode);
    RpStereoCameraSetSeparation((RwCamera *) object, binaryData.dist);
    RpStereoCameraSetFocal((RwCamera *) object, binaryData.focal);

    /* Return success indicator */
    RWRETURN(stream);
}

/************************************************************************
 *
 *      Function:       StereoCameraGetStreamSize()
 *
 *      Description:    Called from RenderWare whenever a camera is written
 *                      to a binary stream.  This function will figure out how
 *                      much data needs to be stored to the binary file, and
 *                      return this size.
 *
 *      Parameters:     The parameters are as specified in the callback
 *                      function definition for RwCameraRegisterPluginStream
 *
 *      Return Value:   The return value is specified in the callback
 *                      function definition for RwCameraRegisterPluginStream
 *
 ************************************************************************/
static              RwInt32
StereoCameraGetStreamSize(const void *__RWUNUSED__ object,
                          RwInt32 __RWUNUSED__ offsetInObject,
                          RwInt32 __RWUNUSED__ sizeInObject)
{
    RWFUNCTION(RWSTRING("StereoCameraGetStreamSize"));

    /* Return how much data we expect to write out to store the state
     * of the stereo camera.
     */
    RWRETURN(sizeof(BinaryStereoCamera));
}

static              RwBool
StereoCameraSetupZBuffer(RwCamera * camera, RwInt32 lineParity)
{
    RwInt32             line;
    RwInt32             width;
    RwInt32             height;
    RwIm2DVertex        vertex[2];
    RwReal              nearClip;
    RwReal              farClip;
    RwReal              recipClip;
    RwReal              recipFarClip;
    RwReal              nearScreenZ;
    RwReal              farScreenZ;
    RwRGBA              colour;
    RwBlendFunction     source;
    RwBlendFunction     destination;

    RWFUNCTION(RWSTRING("StereoCameraSetupZBuffer"));

    colour.red = colour.green = colour.blue = 0;
    colour.alpha = 0xFF;

    width = RwRasterGetWidth(RwCameraGetRaster(camera));
    height = RwRasterGetHeight(RwCameraGetRaster(camera));
    nearClip = RwCameraGetNearClipPlane(camera);
    recipClip = 1.0F / nearClip;
    farClip = RwCameraGetFarClipPlane(camera);
    recipFarClip = 1.0F / farClip;
    nearScreenZ = RwIm2DGetNearScreenZ();
    farScreenZ = RwIm2DGetFarScreenZ();

    RwCameraClear(camera, &colour, rwCAMERACLEARZ);

    RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *) FALSE);
    RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *) TRUE);
    RwRenderStateSet(rwRENDERSTATETEXTURERASTER, NULL);

    RwRenderStateGet(rwRENDERSTATESRCBLEND, &source);
    RwRenderStateGet(rwRENDERSTATEDESTBLEND, &destination);

    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *) rwBLENDZERO);
    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *) rwBLENDONE);

    RwIm2DVertexSetScreenX(&vertex[0], (RwReal) 0);
    RwIm2DVertexSetScreenX(&vertex[1], (RwReal) (width - 1));
    RwIm2DVertexSetIntRGBA(&vertex[0], 0, 0, 0, 255);
    RwIm2DVertexSetIntRGBA(&vertex[1], 0, 0, 0, 255);
    RwIm2DVertexSetCameraZ(&vertex[0], nearClip);
    RwIm2DVertexSetCameraZ(&vertex[1], nearClip);
    RwIm2DVertexSetRecipCameraZ(&vertex[0], recipClip);
    RwIm2DVertexSetRecipCameraZ(&vertex[1], recipClip);
    RwIm2DVertexSetScreenZ(&vertex[0], nearScreenZ);
    RwIm2DVertexSetScreenZ(&vertex[1], nearScreenZ);

    for (line = height - 1 - lineParity; line >= 0; line -= 2)
    {
        RwIm2DVertexSetScreenY(&vertex[0], (RwReal) line);
        RwIm2DVertexSetScreenY(&vertex[1], (RwReal) line);

        RwIm2DRenderLine(vertex, 2, 0, 1);
    }
    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *) source);
    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *) destination);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraSetMode is used to define the stereo mode of 
 * operation of the specified stereo camera using the given value.
 * The default stereo mode is rpSTEREORIGHTLEFT.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param camera  the target RwCamera
 * \param mode  the mode to be set.  
 *              - Valid modes are defined in
 *                the \ref RpStereoCameraMode enumerated type.
 *
 * \return the target RwCamera pointer on success, NULL otherwise
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 */
RwCamera           *
RpStereoCameraSetMode(RwCamera * camera, RpStereoCameraMode mode)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraSetMode"));
    RWASSERT(camera);

    if ((mode <= rpNASTEREOMODE) || (mode >= rpSTEREOLASTMODE))
    {
        RWERROR((E_RP_STEREO_INVMODE));
        RWRETURN((RwCamera *) NULL);
    }

    if (camera)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        /* Since this function needs to be called when most of the camera
         * parameters change, it is a convenient place to propogate the
         * main camera values through to the child cameras
         */

        /* Set the child frames */

        if (RwCameraGetFrame(camera) !=
            RwFrameGetParent(RwCameraGetFrame(stereoCamera->left)))
        {
            RwFrameAddChild(RwCameraGetFrame(camera),
                            RwCameraGetFrame(stereoCamera->left));
        }
        if (RwCameraGetFrame(camera) !=
            RwFrameGetParent(RwCameraGetFrame(stereoCamera->right)))
        {
            RwFrameAddChild(RwCameraGetFrame(camera),
                            RwCameraGetFrame(stereoCamera->right));
        }

        /* Set the clip planes */

        RwCameraSetFarClipPlane(stereoCamera->left,
                                RwCameraGetFarClipPlane(camera));
        RwCameraSetFarClipPlane(stereoCamera->right,
                                RwCameraGetFarClipPlane(camera));

        RwCameraSetNearClipPlane(stereoCamera->left,
                                 RwCameraGetNearClipPlane(camera));
        RwCameraSetNearClipPlane(stereoCamera->right,
                                 RwCameraGetNearClipPlane(camera));

        /* Set the world */

        if (RwCameraGetWorld(stereoCamera->left) !=
            RwCameraGetWorld(camera))
        {
            RpWorldAddCamera(RwCameraGetWorld(camera),
                             stereoCamera->left);
            RpWorldAddCamera(RwCameraGetWorld(camera),
                             stereoCamera->right);
        }

        /* restore the frames to a known state */

        StereoCameraSetupFrame(stereoCamera);
        stereoCamera->mode = mode;

        switch (mode)
        {
            case rpSTEREOROTATE90:
                {
                    const RwV3d        *axis;

                    /* Rotate left & right camera frames by 90 degrees */

                    axis =
                        RwMatrixGetAt(RwFrameGetMatrix
                                      (RwCameraGetFrame
                                       (stereoCamera->left)));

                    RwFrameRotate(RwCameraGetFrame(stereoCamera->left),
                                  axis, 90.0f, rwCOMBINEPOSTCONCAT);
                    RwFrameRotate(RwCameraGetFrame(stereoCamera->right),
                                  axis, -90.0f, rwCOMBINEPOSTCONCAT);

                    /* This mode is almost the same as the rpSTEREOLEFTRIGHT mode so
                     * simply drop through here to set up the rest
                     */
                    mode = rpSTEREOLEFTRIGHT;
                }

            case rpSTEREORIGHTLEFT:
            case rpSTEREOLEFTRIGHT:
                {
                    RwRect              rect;
                    RwCamera           *left, *right;
                    RwV2d               vw;
                    const RwV2d        *source;

                    /* set up the viewwindow first */
                    source = RwCameraGetViewWindow(camera);
                    RwV2dAssign(&vw, source);
                    vw.x *= 0.5f;
                    RwCameraSetViewWindow(stereoCamera->left, &vw);
                    RwCameraSetViewWindow(stereoCamera->right, &vw);

                    rect.x = 0;
                    rect.y = 0;
                    rect.h =
                        RwRasterGetHeight(RwCameraGetRaster(camera));
                    rect.w =
                        RwRasterGetWidth(RwCameraGetRaster(camera)) / 2;

                    if (mode == rpSTEREORIGHTLEFT)
                    {
                        left = stereoCamera->right;
                        right = stereoCamera->left;
                    }
                    else
                    {
                        left = stereoCamera->left;
                        right = stereoCamera->right;
                    }
                    RwRasterSubRaster(RwCameraGetRaster(left),
                                      RwCameraGetRaster(camera), &rect);
                    RwRasterSubRaster(RwCameraGetZRaster(left),
                                      RwCameraGetZRaster(camera),
                                      &rect);

                    rect.x =
                        RwRasterGetWidth(RwCameraGetRaster(camera)) / 2;
                    RwRasterSubRaster(RwCameraGetRaster(right),
                                      RwCameraGetRaster(camera), &rect);
                    RwRasterSubRaster(RwCameraGetZRaster(right),
                                      RwCameraGetZRaster(camera),
                                      &rect);
                    break;
                }
            case rpSTEREOINTERLACEDLEFTRIGHT:
            case rpSTEREOINTERLACEDRIGHTLEFT:
                {
                    RwRect              rect;
                    RwV2d               vw;
                    const RwV2d        *source;

                    /* set up the viewwindow first */
                    source = RwCameraGetViewWindow(camera);
                    RwV2dAssign(&vw, source);

                    RwCameraSetViewWindow(stereoCamera->left, &vw);
                    RwCameraSetViewWindow(stereoCamera->right, &vw);

                    rect.x = 0;
                    rect.y = 0;
                    rect.h =
                        RwRasterGetHeight(RwCameraGetRaster(camera));
                    rect.w =
                        RwRasterGetWidth(RwCameraGetRaster(camera));

                    RwRasterSubRaster(RwCameraGetRaster
                                      (stereoCamera->left),
                                      RwCameraGetRaster(camera), &rect);
                    RwRasterSubRaster(RwCameraGetZRaster
                                      (stereoCamera->left),
                                      RwCameraGetZRaster(camera),
                                      &rect);

                    RwRasterSubRaster(RwCameraGetRaster
                                      (stereoCamera->right),
                                      RwCameraGetRaster(camera), &rect);
                    RwRasterSubRaster(RwCameraGetZRaster
                                      (stereoCamera->right),
                                      RwCameraGetZRaster(camera),
                                      &rect);

                    break;
                }
            case rpSTEREOMONO:
                break;

            case rpSTEREOLASTMODE:
                break;

            case rpNASTEREOMODE:
                RWASSERT(rpNASTEREOMODE != mode);
                break;

            case rpSTEREOCAMERAMODEFORCEENUMSIZEINT:
                break;

        }

        RWRETURN(camera);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RwCamera *) NULL);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraGetMode returns the stereo mode of operation
 * for the current camera.
 *
 * \param camera  the source RwCamera
 *
 * \return the stereo mode on success, rpNASTEREOMODE otherwise.
 *         - Valid modes are defined in 
 *           the \ref RpStereoCameraMode enumerated type.
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 *
 */
RpStereoCameraMode
RpStereoCameraGetMode(RwCamera * camera)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraGetMode"));
    RWASSERT(camera);

    if (camera)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        RWRETURN(stereoCamera->mode);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(rpNASTEREOMODE);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraBeginUpdate is used to indicate that the specified 
 * stereo camera is about to start rendering a view of a scene. This function 
 * is a replacement for \ref RwCameraBeginUpdate when the stereo camera plugin 
 * has been attached.
 *
 * The stereo camera plugin must be attached before using this function.
 * Once this function has been called, only rendering functions should be used until a call to
 * \ref RpStereoCameraEndUpdate
 *
 * \param camera  the target RwCamera
 *
 * \return the target RwCamera on success, NULL otherwise
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 */
RwCamera           *
RpStereoCameraBeginUpdate(RwCamera * camera)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraBeginUpdate"));
    RWASSERT(camera);

    if (camera)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        STEREOCAMERAGLOBAL(stereoUpdate) = camera;

        /* Force a recalculation of the left/right
         * camera parameters
         */
        RpStereoCameraSetMode(camera, RpStereoCameraGetMode(camera));

        if (stereoCamera->mode == rpSTEREOMONO)
        {
            RwCamera           *cameraRet;

            /* Do nothing (but propagate the return value back) */
            cameraRet = RwCameraBeginUpdate(camera);

            RWRETURN(cameraRet);
        }

        /* For other modes, we do the begin update at render time */
        RWRETURN(camera);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RwCamera *) NULL); /* NULL camera */
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraEndUpdate is used to indicate that 3D rendering 
 * to the specified stereo camera has been completed. 
 *
 * This function is a replacement for \ref RwCameraEndUpdate when the stero 
 * camera plugin has been attached.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param camera  the target RwCamera
 *
 * \return the target RwCamera on success, NULL otherwise
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 *
 */
RwCamera           *
RpStereoCameraEndUpdate(RwCamera * camera)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraEndUpdate"));
    RWASSERT(camera);

    if (camera)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        STEREOCAMERAGLOBAL(stereoUpdate) = (RwCamera *) NULL;

        if (stereoCamera->mode == rpSTEREOMONO)
        {
            RwCamera           *cameraRet;

            /* do nothing (except propagate the return back) */

            cameraRet = RwCameraEndUpdate(camera);

            RWRETURN(cameraRet);
        }
        RWRETURN(camera);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RwCamera *) NULL); /* NULL camera */
}

/**
 * \ingroup rpstereo
 * \ref RpStereoWorldRender is used to render the specified world into 
 * the current camera's image raster.  Only objects that have been added to 
 * the world are rendered.  This function will always render static world 
 * geometry.
 *
 * This function is a replacement for \ref RpWorldRender when the stereo 
 * camera plugin has been attached.
 *
 * Note that this function should only be called between \ref RpStereoCameraBeginUpdate
 * and \ref RpStereoCameraEndUpdate to ensure that any rendering that takes 
 * place is directed towards an image raster connected to the current stereo camera.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param world  the source RpWorld to render
 *
 * \return the source RpWorld pointer on success, NULL otherwise
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldSectorRender
 *
 */
RpWorld            *
RpStereoWorldRender(RpWorld * world)
{
    RwCamera           *camera = STEREOCAMERAGLOBAL(stereoUpdate);

    RWAPIFUNCTION(RWSTRING("RpStereoWorldRender"));
    RWASSERT(world);
    RWASSERT(camera);

    if (camera && world)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        if (stereoCamera->mode == rpSTEREOMONO)
        {
            RpWorld            *worldRet;

            /* just render it (and pass back the return value) */

            worldRet = RpWorldRender(world);

            RWRETURN(worldRet);
        }
        else
        {
            /* Render left half */
            if (RwCameraBeginUpdate(stereoCamera->left))
            {
                if ((stereoCamera->mode == rpSTEREOINTERLACEDLEFTRIGHT)
                    || (stereoCamera->mode ==
                        rpSTEREOINTERLACEDRIGHTLEFT))
                {
                    StereoCameraSetupZBuffer(stereoCamera->left,
                                             stereoCamera->mode !=
                                             rpSTEREOINTERLACEDLEFTRIGHT);
                }
                RpWorldRender(world);
                RwCameraEndUpdate(stereoCamera->left);

                /* and then right half */
                if (RwCameraBeginUpdate(stereoCamera->right))
                {
                    if ((stereoCamera->mode ==
                         rpSTEREOINTERLACEDLEFTRIGHT)
                        || (stereoCamera->mode ==
                            rpSTEREOINTERLACEDRIGHTLEFT))
                    {
                        StereoCameraSetupZBuffer(stereoCamera->right,
                                                 stereoCamera->mode ==
                                                 rpSTEREOINTERLACEDLEFTRIGHT);

                    }
                    RpWorldRender(world);
                    RwCameraEndUpdate(stereoCamera->right);

                    RWRETURN(world);
                }
            }
        }

        /* An error during rendering */
        RWRETURN((RpWorld *) NULL);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RpWorld *) NULL);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoClumpRender is used to render the specified clump into 
 * the current camera's image raster.
 *
 * This function is a replacement for \ref RpClumpRender when the stereo camera 
 * plugin has been attached.
 *
 * Note that this function should only be called between \ref RpStereoCameraBeginUpdate
 * and \ref RpStereoCameraEndUpdate to ensure that any rendering that takes 
 * place is directed towards an image raster connected to the current stereo camera.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param clump  the source RpClump to render
 *
 * \return the source RpClump pointer on success, NULL otherwise
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 *
 */
RpClump            *
RpStereoClumpRender(RpClump * clump)
{
    RwCamera           *camera = STEREOCAMERAGLOBAL(stereoUpdate);

    RWAPIFUNCTION(RWSTRING("RpStereoClumpRender"));
    RWASSERT(clump);
    RWASSERT(camera);

    if (camera && clump)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        if (stereoCamera->mode == rpSTEREOMONO)
        {
            RpClump            *clumpRet;

            /* just render it */

            clumpRet = RpClumpRender(clump);

            RWRETURN(clumpRet);
        }
        else
        {
            /* Render left half */
            if (RwCameraBeginUpdate(stereoCamera->left))
            {
                if ((stereoCamera->mode == rpSTEREOINTERLACEDLEFTRIGHT)
                    || (stereoCamera->mode ==
                        rpSTEREOINTERLACEDRIGHTLEFT))
                {
                    StereoCameraSetupZBuffer(stereoCamera->left,
                                             stereoCamera->mode !=
                                             rpSTEREOINTERLACEDLEFTRIGHT);
                }
                RpClumpRender(clump);
                RwCameraEndUpdate(stereoCamera->left);

                /* and then right half */
                if (RwCameraBeginUpdate(stereoCamera->right))
                {
                    if ((stereoCamera->mode ==
                         rpSTEREOINTERLACEDLEFTRIGHT)
                        || (stereoCamera->mode ==
                            rpSTEREOINTERLACEDRIGHTLEFT))
                    {
                        StereoCameraSetupZBuffer(stereoCamera->right,
                                                 stereoCamera->mode ==
                                                 rpSTEREOINTERLACEDLEFTRIGHT);
                    }
                    RpClumpRender(clump);
                    RwCameraEndUpdate(stereoCamera->right);

                    RWRETURN(clump);
                }
            }
        }

        /* An error occured during rendering */
        RWRETURN((RpClump *) NULL);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RpClump *) NULL);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoAtomicRender is used to render the specified atomic 
 * into the current camera's image raster.
 *
 * This function is a replacement for \ref RpAtomicRender when the stereo 
 * camera plugin has been attached.  This function will render the atomic from the perspective
 * of both the left and right cameras.
 * 
 * Note that this function should only be called between \ref RpStereoCameraBeginUpdate
 * and RpStereoCameraEndUpdate to ensure that rendering that takes place is 
 * directed towards an image raster connected to the current stereo camera.
 * 
 * The stereo camera plugin must be attached before using this function. 
 *
 * \param atomic  the source RpAtomic to render
 *
 * \return the source RpAtomic pointer on success, NULL otherwise
 *
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 *
 */
RpAtomic           *
RpStereoAtomicRender(RpAtomic * atomic)
{
    RwCamera           *camera = STEREOCAMERAGLOBAL(stereoUpdate);

    RWAPIFUNCTION(RWSTRING("RpStereoAtomicRender"));
    RWASSERT(atomic);
    RWASSERT(camera);

    if (camera && atomic)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        if (stereoCamera->mode == rpSTEREOMONO)
        {
            RpAtomic           *atomicRet;

            /* just render it */

            atomicRet = RpAtomicRender(atomic);

            RWRETURN(atomicRet);
        }
        else
        {
            /* Render left half */
            if (RwCameraBeginUpdate(stereoCamera->left))
            {
                if ((stereoCamera->mode == rpSTEREOINTERLACEDLEFTRIGHT)
                    || (stereoCamera->mode ==
                        rpSTEREOINTERLACEDRIGHTLEFT))
                {
                    StereoCameraSetupZBuffer(stereoCamera->left,
                                             stereoCamera->mode !=
                                             rpSTEREOINTERLACEDLEFTRIGHT);
                }
                RpAtomicRender(atomic);
                RwCameraEndUpdate(stereoCamera->left);

                /* and then right half */
                if (RwCameraBeginUpdate(stereoCamera->right))
                {
                    if ((stereoCamera->mode ==
                         rpSTEREOINTERLACEDLEFTRIGHT)
                        || (stereoCamera->mode ==
                            rpSTEREOINTERLACEDRIGHTLEFT))
                    {
                        StereoCameraSetupZBuffer(stereoCamera->right,
                                                 stereoCamera->mode ==
                                                 rpSTEREOINTERLACEDLEFTRIGHT);
                    }
                    RpAtomicRender(atomic);
                    RwCameraEndUpdate(stereoCamera->right);

                    RWRETURN(atomic);
                }
            }
        }

        /* An error occured during rendering */
        RWRETURN((RpAtomic *) NULL);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RpAtomic *) NULL);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoWorldSectorRender is used to render the specified world 
 * sector into the current camera's image raster.
 *
 * This function is a replacement for RpWorldSectorRender when the stereo 
 * camera plugin has been attached. This function will render the world 
 * sector  from the perspective of both the left and right cameras.
 *
 * Note that this function should only be called between \ref RpStereoCameraBeginUpdate
 * and \ref RpStereoCameraEndUpdate to ensure that any rendering that takes 
 * place is directed towards an image raster connected to the current stereo camera.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param sector  the source RpWorldSector to render
 *
 * \return the source RpWorldSector pointer on success, NULL otherwise
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 *
 */
RpWorldSector      *
RpStereoWorldSectorRender(RpWorldSector * sector)
{
    RwCamera           *camera = STEREOCAMERAGLOBAL(stereoUpdate);

    RWAPIFUNCTION(RWSTRING("RpStereoWorldSectorRender"));
    RWASSERT(sector);
    RWASSERT(camera);

    if (camera && sector)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        if (stereoCamera->mode == rpSTEREOMONO)
        {
            RpWorldSector      *sectorRet;

            /* just render it */

            sectorRet = RpWorldSectorRender(sector);

            RWRETURN(sectorRet);
        }
        else
        {
            /* Render left half */
            if (RwCameraBeginUpdate(stereoCamera->left))
            {
                if ((stereoCamera->mode == rpSTEREOINTERLACEDLEFTRIGHT)
                    || (stereoCamera->mode ==
                        rpSTEREOINTERLACEDRIGHTLEFT))
                {
                    StereoCameraSetupZBuffer(stereoCamera->left,
                                             stereoCamera->mode !=
                                             rpSTEREOINTERLACEDLEFTRIGHT);
                }
                RpWorldSectorRender(sector);
                RwCameraEndUpdate(stereoCamera->left);

                /* and then right half */
                if (RwCameraBeginUpdate(stereoCamera->right))
                {
                    if ((stereoCamera->mode ==
                         rpSTEREOINTERLACEDLEFTRIGHT)
                        || (stereoCamera->mode ==
                            rpSTEREOINTERLACEDRIGHTLEFT))
                    {
                        StereoCameraSetupZBuffer(stereoCamera->right,
                                                 stereoCamera->mode ==
                                                 rpSTEREOINTERLACEDLEFTRIGHT);
                    }
                    RpWorldSectorRender(sector);
                    RwCameraEndUpdate(stereoCamera->right);

                    RWRETURN(sector);
                }
            }
        }

        /* An error occured during rendering */
        RWRETURN((RpWorldSector *) NULL);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RpWorldSector *) NULL);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraGetSeparation 
 * is used to retrieve the eye-separation 
 * of the specified stereo camera.  The eye-separation is measured parallel 
 * to the stereo camera view-plane and is the distance between the left 
 * and right stereo pair.
 *
 * The default eye-separation is 0.04 units.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param camera  the source RwCamera
 *
 * \return the separation value. 0.0 will be returned on error.
 * This is a valid value though, so \ref RwErrorGet will need
 * to also be used to validate the error condition.
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 */
RwReal
RpStereoCameraGetSeparation(RwCamera * camera)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraGetSeparation"));
    RWASSERT(camera);

    if (camera)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        RWRETURN(stereoCamera->dist);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(0.0f);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraSetSeparation is used to define the eye-separation 
 * of the specified stereo camera using the given value.  The eye-separation 
 * is measured parallel to the stereo camera view-plane and is the distance 
 * between the left and right stereo pair.  The eye-separation must be 
 * non-negative (zero is a valid setting).
 *
 * The default eye-separation is 0.04 units.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param camera  the target RwCamera
 *
 * \return the target RwCamera pointer on success or NULL on error.
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 *
 */
RwCamera           *
RpStereoCameraSetSeparation(RwCamera * camera, RwReal dist)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraSetSeparation"));

    RWASSERT(camera);

    if (camera)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        /* Shove in the value */
        stereoCamera->dist = dist;

        /* force an update of stereo parameters by forcing a mode change */
        RpStereoCameraSetMode(camera, RpStereoCameraGetMode(camera));

        RWRETURN(camera);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RwCamera *) NULL);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraGetFocal is used to retrieve the focal distance 
 * of the specified stereo camera.  The focal distance is measure perpendicular 
 * to the stereo camera view-plane along a line midway between the left and 
 * right stereo pair.
 *
 * The default focal distance is 10 units.
 *
 * The stereo camera plugin must be attached before using this function.
 *
 * \param camera  the source RwCamera
 *
 * \return The focal distance. 0.0 will be returned on error.
 * This is a valid value though so, \ref RwErrorGet will need
 * to also be used to validate the error condition.
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 *
 */
RwReal
RpStereoCameraGetFocal(RwCamera * camera)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraGetFocal"));
    RWASSERT(camera);

    if (camera)
    {
        StereoCamera       *stereoCamera =
            STEREOCAMERAFROMCAMERA(camera);

        RWRETURN(stereoCamera->focal);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(0.0);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoCameraSetFocal is used to define the focal distance 
 * of the specified stereo camera using the given value.  The focal 
 * distance is measured perpendicular to the stereo camera view-plane 
 * along a line midway between the left and right stereo pair.  The 
 * focal distance must be non-zero.
 *
 * The default focal distance is 10 units.
 * 
 * The stereo camera plugin must be attached before using this function.
 *
 * \param camera  the target RwCamera pointer on success, or NULL on error.
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoPluginAttach
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 *
 */
RwCamera           *
RpStereoCameraSetFocal(RwCamera * camera, RwReal focal)
{
    RWAPIFUNCTION(RWSTRING("RpStereoCameraSetFocal"));

    /* Zero length focal will cause error, negative focals are allowed */
    RWASSERT(focal != (RwReal) (0.0));
    RWASSERT(camera);

    if (camera)
    {
        if (focal != (RwReal) (0.0))
        {
            StereoCamera       *stereoCamera =
                STEREOCAMERAFROMCAMERA(camera);

            /* Shove in the value */
            stereoCamera->focal = focal;

            /* force an update of stereo parameters by forcing a mode change */
            RpStereoCameraSetMode(camera,
                                  RpStereoCameraGetMode(camera));

            RWRETURN(camera);
        }

        RWERROR((E_RP_STEREO_INVFOCAL));
        RWRETURN((RwCamera *) NULL);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RwCamera *) NULL);
}

/**
 * \ingroup rpstereo
 * \ref RpStereoPluginAttach is used to attach the stereo camera plugin 
 * to the RenderWare system to enable the rendering of stereoscopic images.  
 * The stereo camera plugin must be attached between initializing the system 
 * with \ref RwEngineInit and opening it with \ref RwEngineOpen.
 * 
 * Note that the stereo camera plugin requires the world plugin to be attached.  
 * The include file rpstereo.h is also required and must be included by an 
 * application wishing to use the stereoscopic cameras.  The stereo camera 
 * plugin library is contained in the file rpstereo.lib.
 *
 * \return True on success, false otherwise
 *
 * \see RpStereoAtomicRender
 * \see RpStereoCameraBeginUpdate
 * \see RpStereoCameraEndUpdate
 * \see RpStereoCameraGetFocal
 * \see RpStereoCameraGetMode
 * \see RpStereoCameraGetSeparation
 * \see RpStereoCameraSetFocal
 * \see RpStereoCameraSetMode
 * \see RpStereoCameraSetSeparation
 * \see RpStereoClumpRender
 * \see RpStereoWorldRender
 * \see RpStereoWorldSectorRender
 * \see RwEngineInit
 * \see RwEngineOpen
 * \see RpWorldPluginAttach
 *
 */
RwBool
RpStereoPluginAttach(void)
{
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpStereoPluginAttach"));

    /* Extend the global data block to include Stereo Camera globals */

    GlobalOffset =
        RwEngineRegisterPlugin(sizeof(StereoCameraGlobals),
                               rwID_STEREOPLUGIN,
                               StereoOpen, StereoClose);

    if (GlobalOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Extend the RwCamera object to include Stereo Camera data */

    CameraOffset =
        RwCameraRegisterPlugin(sizeof(StereoCamera *),
                               rwID_STEREOPLUGIN,
                               StereoCameraCreate,
                               StereoCameraDestroy, StereoCameraCopy);

    if (CameraOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Provide stream functionality for storing the state 
     * of the stereo camera */

    offset =
        RwCameraRegisterPluginStream(rwID_STEREOPLUGIN,
                                     StereoCameraReadStream,
                                     StereoCameraWriteStream,
                                     StereoCameraGetStreamSize);

    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}
