/**********************************************************************
 *
 * File :     doscyber.c
 *
 * Abstract : This is the main module of the cyberstreet demo. It contains
 *            the code that handles all of the user interaction and the
 *            interaction between the other modules.
 *
 **********************************************************************
 *
 * 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) 1995 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 ************************************************************************/

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

#define DEFINE_GLOBAL
#include "global.h"     /* Application general includes */
#include "palette.h"

/*--- Magic Number Definitions ---*/

#define M_PI 3.1415926535897932

/* Default size of the viewer's window. */

#define CLOSEST         0.1  /* Closest we can get to a wall */
#define LOWEST          0.2  /* Closest we can get to the ground */

#define STREET_HIGHEST  2.5  /* Maximum height we can fly above street */
#define HALL_HIGHEST    0.4  /* Maximum height we can fly above hall */
#define POOL_HIGHEST    1.4  /* Maximum height we can fly above pool */

#define MAX_TILT        80.0 /* The maximum tilt angle for the camera */

/*--- Structure and enumerated type definitions ---*/

/* This enumerated type tells us what kind of action should be taken
 * on mouse events.
 */

typedef enum
{
    MMNoAction,
    MMTravelCamera,
    MMTiltCamera,
    MMChangeHeight
} MMMode;

/* This enumerated type determines which cell of the world we are
 * currently in
 */

typedef enum
{
    CELL_STREET,
    CELL_HALL,
    CELL_POOL
} Cell;

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

/* These variables are used to hold the polygons that define the lights
 * above the billboard. The lights are turned on and off by varying the
 * ambient surface property of these polygons.
 */

RwPolygon3d *ppGLight1;
RwPolygon3d *ppGLight2;

/* These variables are used to hold the textures for the billboard. Each
 * of these textures contains 2 frames. One image shows the billboard lit
 * and the other is unlit. The billboard is made to appear lit or unlit by
 * the lights defined above by varying the current texture frame.
 */

RwTexture *tpGBillboardLeft;
RwTexture *tpGBillboardRight;

/* These variables hold the clumps that make up the world.
 */

static RwClump *cpGCity   = NULL;  /* The main street clump */
static RwClump *cpGHall   = NULL;  /* The hall between the street and the poolroom */
static RwClump *cpGPool   = NULL;  /* The poolroom clump */
static RwClump *cpGSky1,*cpGSky2;  /* These 2 clumps are dummies that are used
                                      to calculate the area of the display
                                      that is covered by the skyline backdrop */

/* The camera can fly around a predetermined path. These variables define the
   path and the control variable.
 */

static RwSpline *spGPath = NULL;    /* The flight path for the camera */
static RwReal nGAlpha;              /* The control variable for the path */

static RwReal nGSpeed;              /* The current speed of the camera */
static RwReal nGTilt;               /* The absolute tilt angle of the camera */
static RwReal nGGroundHeight;       /* The current ground height */

static int nGOnPad = 0;             /* If Camera is over the pad that activates
                                       the door then this variable is TRUE */
static int nGDoorPos = 0;           /* The current position for the door.
                                       0 = closed, DOOR_OPEN = open */

static RwV3d vaGDoor[20];           /* The door appears to open by having
                                       the vertices modified. This variable
                                       is used to hold the current value of
                                       vertices used */
static RwInt32 naGDoor[20];            /* This variable holds the indices for the
                                       vertices defined above */

static Cell eGWhichCell;            /* The current cell (STREET, HALL or
                                       POOLROOM) */
static int nGFlyCamera;             /* TRUE is camera is flying, FALSE
                                       otherwise */

/*
 * This variable tells us what kind of action to take on a mouse move.
 * The action depends on the object that was picked when the mouse button
 * went down, and on the selection of virtual keys that were depressed at
 * that time. By default no action is taken on mouse move.
 */

static MMMode mGMouseMoveMode = MMNoAction;

/*
 * Global variables used to remember the last mouse X and Y coordinates
 * when involved in a pan, zoom, drag or spin.
 */
static RwInt32 nGLastX;
static RwInt32 nGLastY;

/*--- Function Definitions ---*/

/************************************************************************
 *
 *      Function:       GetPosHeight()
 *
 *      Description:    Get the height of the ground at a given position
 *
 *      Parameters:     vpPos - 3D position for which we want the ground
 *                              height.
 *
 *      Return Value:   height of the ground at the specified point.
 *
 ************************************************************************/
RwReal
GetPosHeight(RwV3d *vpPos)
{
    RwInt32 nStep;

    if (vpPos->x < CREAL(RIGHT_STREET))
    {
        /* In the street */

        if ((vpPos->x > CREAL(LEFT_KERB)) && (vpPos->x < CREAL(RIGHT_KERB)))
        {
            return (CREAL(STREET_HEIGHT + LOWEST));
        }
        else
        {
            return (CREAL(KERB_HEIGHT + LOWEST));
        }
    }
    else
    {
        /* In pool room or on steps */

        nStep = REAL2INT(RDiv(RSub(vpPos->x, CREAL(RIGHT_STREET)), CREAL(STEP_WIDTH)));
        if (nStep < 0)
        {
            nStep = 0;
        }
        if (nStep > MAX_STEPS)
      {
            nStep = MAX_STEPS;
        }
        return RSub(CREAL(KERB_HEIGHT + LOWEST), RMul(INT2REAL(nStep), CREAL(STEP_HEIGHT)));
    }
}

/************************************************************************
 *
 *      Function:       WhichCell()
 *
 *      Description:    Work out which cell the given position is in.
 *
 *      Parameters:     vpPos - 3D point that we want the cell for
 *
 *      Return Value:   Cell that contains the point
 *
 ************************************************************************/
static Cell
WhichCell(RwV3d *vpPos)
{
    /* Check if we are in the street */

    if ((vpPos->x > CREAL(LEFT_STREET)) &&
        (vpPos->x < CREAL(RIGHT_STREET)) &&
        (vpPos->z > CREAL(FAR_STREET)) &&
        (vpPos->z < CREAL(NEAR_STREET)))
    {
        /* We're in the street */
        return(CELL_STREET);
    }

    /* Check if we are in the hall */

    if ( (vpPos->x > CREAL(LEFT_HALL)) &&
         (vpPos->x < CREAL(RIGHT_HALL)) &&
         (vpPos->z > CREAL(FAR_HALL)) &&
         (vpPos->z < CREAL(NEAR_HALL)) )
    {
        /* We're in the hall */
        return(CELL_HALL);
    }

    /* The only place left is the poolroom */

    return(CELL_POOL);
}

/************************************************************************
 *
 *      Function:       MoveForward()
 *
 *      Description:    Move the global Camera forward by the specified
 *                      distance.
 *
 *      Parameters:     nDist The distance to move forward
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void
MoveForward(RwReal nDist)
{
    RwV3d vAt, vOld, vNew;
    RwReal nOldHeight;
    RwReal nNewHeight;

    /* Find the forward movement vector for the Camera */

    RwGetCameraLookAt(cpGCamera, &vAt);

    /* We only want to move in x & z so remove the Y component and
       renormalise the forward vector */

    vAt.y = CREAL(0.0);
    RwNormalize(&vAt);

    /* Move the camera forward and get the new position */

    RwGetCameraPosition(cpGCamera, &vOld);
    RwWCMoveCamera(cpGCamera,
                    RMul(vAt.x, nDist), CREAL(0.0), RMul(vAt.z, nDist));
    RwGetCameraPosition(cpGCamera, &vNew);

    /* Find out which cell we were in */

    eGWhichCell = WhichCell(&vOld);

    switch(eGWhichCell)
    {
    case CELL_STREET:
        /* In the street so constrain movement to within street bounds */

        if (vNew.z < CREAL(FAR_STREET + CLOSEST))
        {
            vNew.z = CREAL(FAR_STREET + CLOSEST);
        }
        else if (vNew.z > CREAL(NEAR_STREET - CLOSEST))
        {
            vNew.z = CREAL(NEAR_STREET - CLOSEST);
        }

        if (vNew.x < CREAL(LEFT_STREET + CLOSEST))
        {
            vNew.x = CREAL(LEFT_STREET + CLOSEST);
        }
        else if (vNew.x > CREAL(RIGHT_STREET - CLOSEST))
        {
            /* constrain if door is not open or not in front of door */

            if ((nGDoorPos < DOOR_OPEN) ||
                (vNew.y > RAdd(nGGroundHeight, CREAL(DOOR_HEIGHT + CLOSEST))) ||
                (vNew.z < CREAL(FAR_HALL)) ||
                (vNew.z > CREAL(NEAR_HALL)))
            {
                vNew.x = CREAL(RIGHT_STREET - CLOSEST);
            }
        }

        /* check if we're over pressure pad */

        if ((vNew.x > CREAL(PAD_X)) &&
            (vNew.y < RAdd(nGGroundHeight, CREAL(PAD_Y))) &&
            (vNew.z > CREAL(FAR_HALL)) &&
            (vNew.z < CREAL(NEAR_HALL)))
        {
            nGOnPad = TRUE;
        }
        else
        {
            nGOnPad = FALSE;
        }
        break;

    case CELL_HALL:
        /* In the hall so constrain against passage walls */

        if (vNew.z < CREAL(FAR_HALL + CLOSEST))
        {
            vNew.z = CREAL(FAR_HALL + CLOSEST);
        }
        else if (vNew.z > CREAL(NEAR_HALL - CLOSEST))
        {
            vNew.z = CREAL(NEAR_HALL - CLOSEST);
        }
        break;

      case CELL_POOL:
        /* In the poolroom so constrain movement to within poolroom bounds */

        if (vNew.z < CREAL(FAR_POOL + CLOSEST))
        {
            vNew.z = CREAL(FAR_POOL + CLOSEST);
        }
        else if (vNew.z > CREAL(NEAR_POOL - CLOSEST))
        {
            vNew.z = CREAL(NEAR_POOL - CLOSEST);
        }

        if (vNew.x < CREAL(LEFT_POOL + CLOSEST))
        {
            /* constrain if not in front of door otherwise allow through
               the door */

            if ((vNew.y > RAdd(nGGroundHeight, CREAL(DOOR_HEIGHT - CLOSEST))) ||
                (vNew.z < CREAL(FAR_HALL)) ||
                (vNew.z > CREAL(NEAR_HALL)))
            {
                vNew.x = CREAL(LEFT_POOL + CLOSEST);
            }
        }
        else if (vNew.x > CREAL(RIGHT_POOL - CLOSEST))
        {
            vNew.x = CREAL(RIGHT_POOL - CLOSEST);
        }
        break;
    }

    /* The camera height follows the ground height so adjust the height
       of the camera by any change in the ground height */

    nOldHeight = GetPosHeight(&vOld);
    nNewHeight = GetPosHeight(&vNew);

    nGGroundHeight = nNewHeight;
    vNew.y += nNewHeight - nOldHeight;

    RwSetCameraPosition(cpGCamera, vNew.x, vNew.y, vNew.z);
}

/************************************************************************
 *
 *      Function:       MoveUp()
 *
 *      Description:    Move the global Camera up by the specified
 *                      distance.
 *
 *      Parameters:     nDist The distance to move up
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void
MoveUp(RwReal up)
{
    RwV3d vPos;

    /* Move the camera up (or down!) and get the new position */

    RwWCMoveCamera(cpGCamera, CREAL(0.0), up, CREAL(0.0));
    RwGetCameraPosition(cpGCamera, &vPos);

    /* Check new height against upper and lower limits. The lower limit
       is the current ground height. The upper limit is dependant on which
       cell we are in */

    if (vPos.y < nGGroundHeight)
    {
        /* We can't move below the ground so clamp at this height */
    	vPos.y = nGGroundHeight;
    }
    else switch(eGWhichCell)
    {
        /* Check the upper limit against the current cell and clamp
           if above the limit */

    case CELL_STREET:
        if (vPos.y > CREAL(STREET_HEIGHT + STREET_HIGHEST))
            vPos.y = CREAL(STREET_HEIGHT + STREET_HIGHEST);
        break;

    case CELL_HALL:
        if (vPos.y > RAdd(nGGroundHeight, CREAL(HALL_HIGHEST)))
            vPos.y = RAdd(nGGroundHeight, CREAL(HALL_HIGHEST));
        break;

    case CELL_POOL:
        if (vPos.y > RAdd(nGGroundHeight, CREAL(POOL_HIGHEST)))
            vPos.y = RAdd(nGGroundHeight, CREAL(POOL_HIGHEST));
       	break;
    }
    /* Move the camera to the new (possibly clamped) position */

    RwSetCameraPosition(cpGCamera, vPos.x, vPos.y, vPos.z);
}

/************************************************************************
 *
 *      Function:       RandomVec()
 *
 *      Description:    Generate a random vector
 *
 *      Parameters:     vpVec - the vector to store the random value.
 *
 *      Return Value:   None
 *
 ************************************************************************/
void RandomVec(RwV3d *vpVec)
{
    vpVec->x = RANDOM_REAL(CREAL(-1.0), CREAL(1.0));
    vpVec->y = RANDOM_REAL(CREAL(-1.0), CREAL(1.0));
    vpVec->z = RANDOM_REAL(CREAL(-1.0), CREAL(1.0));
}

/************************************************************************
 *
 *      Function:       DoRwReadShape()
 *
 *      Description:    Read a RenderWare script file and generate an
 *                      error on failure.
 *
 *      Parameters:     cpShape - the name of the script to load
 *
 *      Return Value:   None
 *
 ************************************************************************/
RwClump
*DoRwReadShape(char *cpShape)
{
    RwClump *cpClump;

    cpClump = RwReadShape(cpShape);

    if (!cpClump)
    {
        OsError("Unable to read <%s>", cpShape);
    }
    return cpClump;
}

/************************************************************************
 *
 *      Function:       ResetCamera()
 *
 *      Description:    Move the camera back to its initial position
 *                      and orientation.
 *
 *      Parameters:     cpShape - the name of the script to load
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
ResetCamera(void)
{
    RwV3d vPos;

    nGSpeed = CREAL(0.0);	/* Camera is stationary */
    nGTilt = CREAL(0.0); 	/* Camera is looking straight ahead */

    /* Reset camera position */
    RwSetCameraPosition(cpGCamera,
                        CREAL(CAMERA_START_X),
                        CREAL(CAMERA_START_Y),
                        CREAL(CAMERA_START_Z));

    /* Camera is looking in the same direction as the -ve Z axis */
    RwSetCameraLookAt(cpGCamera, CREAL(0.0),CREAL(0.0),CREAL(-1.0));

    /* And is level */
    RwSetCameraLookUp(cpGCamera, CREAL(0.0),CREAL(1.0),CREAL(0.0));

    /* Set the ground height for this new position */
    RwGetCameraPosition(cpGCamera, &vPos);
    nGGroundHeight = GetPosHeight(&vPos);

    /* Set the current cell */
    eGWhichCell = WhichCell(&vPos);

    nGOnPad = 0;     /* Not on the door pad */
    nGFlyCamera = 0; /* Not flying */
}

/************************************************************************
 *
 *      Function:       ToggleFlyCamera()
 *
 *      Description:    If camera is flying then stop it and move back to
 *                      the start position. Otherwise start the camera flying
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
ToggleFlyCamera(void)
{
    if (nGFlyCamera)
    {
        /* We are currently flying - so stop and reset camera */
    	nGFlyCamera = 0;
        ResetCamera();
    }
    else
    {
        nGOnPad = 0;                /* If we're flying then we're not on
                                       the door pad */
        nGFlyCamera = 1;
        eGWhichCell = CELL_STREET;  /* The spline path is in the street */
        nGAlpha = CREAL(0.0);       /* Reset to the start of the path */
    }
}

/************************************************************************
 *
 *      Function:       Render()
 *
 *      Description:    This function controls the rendering of the scene
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void
Render(void)
{
    RwV3d lookat;
    RwReal    dot;
    RwInt32 w, h, dx, dy, cw, ch;
    static Cell eOldCell;

    /* If we have moved into or out of the street cell then enable or
       disable the gun */

    if (eOldCell != eGWhichCell)
    {
        /* We've changed cells */

        eOldCell = eGWhichCell;

        if (eGWhichCell == CELL_STREET)
        {
            /* Moving into the street - Enable the gun */
            GunEnable();
        }
        else
        {
            /* Disable the Gun */
            GunDisable();
        }
    }


    /* Only the STREET cell uses a bitmap backdrop. Enable it for this cell
       and disable for the others */

    switch(eGWhichCell)
    {
        case CELL_STREET:
            RwGetCameraViewport(cpGCamera, NULL, NULL, &cw, &ch);
            RwSetCameraBackdropViewportRect(cpGCamera, 0, 0, cw, ch);
            break;
        case CELL_HALL:
        case CELL_POOL:
            GunDisable();   /* the gun should never be on here */
            /* Don't need bitmap backdrop */
            RwSetCameraBackdropViewportRect(cpGCamera, 0, 0, 0, 0);
            break;
    }

    /* clear the old damaged areas */

    RwUndamageCameraViewport(cpGCamera, 0,0, (RwInt32)512, (RwInt32)512);

    if (eGWhichCell == CELL_STREET)
    {
        /* Determine the position of the bitmap backdrop based on the current
           camera orientation. This is achieved by assuming that the backdrop 
           bitmap is applied to a cylinder which wraps around the world. Taking
           the current view direction from the camera, it is possible to calculate
           the point on the cylinder and hence the portion if the bitmap that 
           should be in the centre of the screen */

           RwGetCameraLookAt(cpGCamera, &lookat);
    
           w = RwGetRasterWidth(RwGetCameraBackdrop(cpGCamera));
           h = RwGetRasterHeight(RwGetCameraBackdrop(cpGCamera));
    
        /* Use the Y component of the look at directly. This is accurate
           as long as the camera doesn't point directly up or down. This can't
           happen in this application */

        /* Set the Y offset such that for a camera angle that is parallel
           with the horizon, we are looking at the centre row of the backdrop
           bitmap */

        dy = REAL2INT(RMul(lookat.y, INT2REAL(-h/2)));

        /* Then add a little bit to move the backdrop up to where we want it */

        dy += h - (ch * 3)/4;

        /* The X component of the backdrop offset needs to take account of both
           the X and Z components of the look at vector. An range of angles from
           0 to 360 degrees (0 - PI radians) maps onto the backdrop as 0 - w */

        dot = FL2REAL((M_PI - atan2(REAL2FL(lookat.x), REAL2FL(lookat.z)))/M_PI);
        dx = REAL2INT(RMul(INT2REAL(w),dot));

        /* Set our calculated offset */

        RwSetCameraBackdropOffset(cpGCamera, dx,dy);

        /* RwSetCameraBackdropOffset damages the backdrop portion of the camera
           we want to override this since we know that only portions of the
           backdrop are visible through the scene */

        /* We use 2 dummy clumps to determine which portions of the
           backdrop need updating */

        /* first the portion visible as sky above everything */

        RwGetClumpViewportRect(cpGSky1, cpGCamera, &dx ,&dy, &w, &h);
        RwDamageCameraViewport(cpGCamera, dx-32, dy, w+64, h+64);

        /* Then the portion visible through the fence at the end */

        RwGetClumpViewportRect(cpGSky2, cpGCamera, &dx ,&dy, &w, &h);
        RwDamageCameraViewport(cpGCamera, dx-32, dy, w+64, h+64);
    }

    if ((eGWhichCell == CELL_STREET) || (eGWhichCell == CELL_HALL)) 
    {
        /* If we are in the street or the hall then update all of
           these things */

        AllBitsUpdate();
        AllObjectsUpdate();
        GunUpdate();
    }

    /* Render the objects */

    OsBeginCameraUpdate(cpGCamera);
    RwClearCameraViewport(cpGCamera);

    switch (eGWhichCell)
    {
    case CELL_STREET:
        /* We're in the street */
        if (nGDoorPos) 
        {
            /* The door is open so render the pool room and the hall first */
            RwRenderScene(spGPoolScene);
            RwRenderClump(cpGHall);
        }

        /* Render the street */
        RwRenderScene(spGScene);

        /* Render all the objects in the street (rats + explosions) */
        AllObjectsRender();
        break;

    case CELL_HALL:
        /* We're in the hall */
        /* From here we can see everything so render it all */

        /* Render the street */

        RwRenderScene(spGScene);
        
        /* Render the rats and explosions */

        AllObjectsRender();
        
        /* Render the pool room and the hall */
        RwRenderScene(spGPoolScene);
        RwRenderClump(cpGHall);
        break;

    case CELL_POOL:
        /* We're in the poolroom */

        /* render the hall first */
        RwRenderClump(cpGHall);

        /* Then the pool room */

        RwRenderScene(spGPoolScene);
        break;
    }
    /* Overlay the gun on top of the scene */

    GunRender();
    
#ifdef WITH_SCORE
    if (GunVisible())
    {
        OsDisplayScore(NumDeadRats(), NumRats());
    }
#endif

    RwEndCameraUpdate(cpGCamera);
    OsShowCameraImage();
}

/************************************************************************
 *
 *      Function:       AddLamppost()
 *                      
 *      Description:    Add a lampost to the street
 *
 *      Parameters:     x, z the position on the ground for the lamppost.
 *
 *      Return Value:   TRUE if lamppost added, FALSE otherwise
 *
 ************************************************************************/
#define LAMPPOST_HEIGHT 1.5
#define LAMPPOST_OFFSET 0.075

static int AddLamppost(RwReal x, RwReal z)
{
    if (!AddStaticObject(x, CREAL(0.1), z, "lamppost.rwx") ||
        !AddStaticObject((x < CREAL(0.0)) ? RAdd(x, CREAL(LAMPPOST_OFFSET)) 
                                          : RSub(x, CREAL(LAMPPOST_OFFSET)), 
        CREAL(0.1 + LAMPPOST_HEIGHT), z,  "lamplite.rwx"))
    {
        return(FALSE);
    }
    return (TRUE);
}

/************************************************************************
 *
 *      Function:       Init3D()
 *                      
 *      Description:    Initialise the 3D components. This function creates
 *                      the global camera, creates all the scenes and lights 
 *                      and loads all of the scripts.
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
int
Init3D(void)
{
    RwLight 		*Light;
    int      		i;
    RwRaster            *rpBackdrop;

    RwSRandom((RwInt32)time(0)); /* Seed the random number generator */

    if (!OsOpen())      /* Open RenderWare in an OS specific way */
        return(FALSE);

    /* Load the palette that this program will use. Note: this palette must
       be loaded before any of the textures that will be matched to it */

    CheckAndReadPalette("rwcyber.pal");

    /* Create the global camera that will be used for all of the rendering.
       The size of the camera is OS specific */

    cpGCamera = RwCreateCamera(OsMaxCameraWidth(), OsMaxCameraHeight(), NULL);

    if (!cpGCamera)
    {
        /*
    	 * The most common cause for a failure to create a camera is 
         * insufficient memory so we will explicitly check for this 
         * condition and report it. Otherwise a general error is issued.
    	 */

    	if (RwGetError() == E_RW_NOMEM)
    	{
            OsError("Insufficient memory to create the RenderWare(tm) camera");
    	}
    	else
    	{
            OsError("Error Creating the RenderWare camera");
    	}
        RwClose();
        return FALSE;
    }

    /* Do the OS Specific initialisation stuff */

    if (!OsInit())
    {
        RwClose();
        return(FALSE);
    }

    /* Set the camera's background color to black.*/

    RwSetCameraBackColor(cpGCamera, CREAL(0.0), CREAL(0.0), CREAL(0.0));

    /* Read the cityscape backdrop */

    if (!(rpBackdrop = RwReadRaster("backdrop", 0L))) 
    {
        OsError("Unable to read <backdrop>");
        RwClose();
        return FALSE;
    }
    RwSetCameraBackdrop(cpGCamera, rpBackdrop);
    RwSetCameraBackdropOffset(cpGCamera, 0,0);

    /* Setup initial position and field of view for the camera */
    ResetCamera();
    RwSetCameraViewwindow(cpGCamera, CREAL(1.0), CREAL(0.75));

    /* Create a scene which will contain the clumps and lights for the 
     *  street
     */

    spGScene = RwCreateScene();
    if (!spGScene)
    {
        RwDestroyCamera(cpGCamera);
        RwClose();
        return FALSE;
    }

    /* Create a scene which will contain the clumps and lights for the 
     *  poolroom
     */

    spGPoolScene = RwCreateScene();
    if (!spGPoolScene)
    {
        RwDestroyCamera(cpGCamera);
        RwClose();
        return FALSE;
    }

    /* Create a scene which will contain the clumps which are used as targets
     * for the rats
     */

    spGTargetScene = RwCreateScene();
    if (!spGTargetScene)
    {
        RwDestroyCamera(cpGCamera);
        RwClose();
        return FALSE;
    }

    /* Create the light that will illuminate the poolroom. The light is a
       directional light with an illumination vector of (0.5, -1.0, -0.5)
     */

    Light = RwCreateLight(rwDIRECTIONAL, CREAL(0.5), CREAL(-1.0), CREAL(-0.5),
                          CREAL(1.0));

    /* Add the light to the poolroom scene */
    RwAddLightToScene(spGPoolScene, Light);

    /* Read the geometry for the street */
    if (!(cpGCity = DoRwReadShape("city.rwx"))) 
    {
        RwClose();
        return FALSE;
    }

    /* Find the polygons that represent the billboard lights */

    ppGLight1 = RwFindTaggedPolygon(cpGCity, 1);
    ppGLight2 = RwFindTaggedPolygon(cpGCity, 2);

    /* And the textures that represent the billboard */

    tpGBillboardLeft = RwFindNamedTexture("bbleft");
    tpGBillboardRight = RwFindNamedTexture("bbrite");

    /* And add the clump to the scene */
    RwAddClumpToScene(spGScene, cpGCity);

    /* Get the vertices of the secret door. These are the first 18
       vertices in the street script */
    for (i=0; i<18; i++)
    {
        naGDoor[i] = i + 1;
        RwGetClumpVertex(cpGCity, naGDoor[i], &vaGDoor[i]);
    }

    /* Duplicate the light and add it to the default scene. This light 
       will light all of the clumps not explicitly added to a scene (ie the
       rats and bits of explosions) */

    Light = RwDuplicateLight(Light);
    RwAddLightToScene(RwDefaultScene(), Light);

    /* Read the script that represents the get hallway between the street and
       the poolroom */
    if (!(cpGHall = DoRwReadShape("hall.rwx"))) 
    {
        RwClose();
        return FALSE;
    }

    /* Read the script that represents the poolroom */
    if (!(cpGPool = DoRwReadShape("pool.rwx"))) 
    {
        RwClose();
        return FALSE;
    }

    /* And add it to the poolroom scene */    
    RwAddClumpToScene(spGPoolScene, cpGPool);

    /* Set up the rest of the objects */

    if (!AllObjectsSetup() ||
        !GunSetup() ||
        !ManSetup() ||
	!TrophySetup() ||
        !AllRatsSetup() ||
        !AllBitsSetup())
    {
        RwClose();
        return FALSE;
    }

#ifdef WITH_SOUND
    if (!(AllSoundsSetup())) 
    {
        /* Theres no sound card but dont do anything about it */
    }
#endif

    /* Add all of the static objects */

    if (!AddStaticObject(CREAL(1.5), CREAL(0.1), CREAL(1.0), "bin.rwx") ||
        !AddStaticObject(CREAL(1.3), CREAL(0.1), CREAL(1.5), "bin.rwx") ||
        !AddStaticObject(CREAL(0.4), CREAL(0.0), CREAL(0.5), "binboxes.rwx") ||
        !AddStaticObject(CREAL(-0.2),CREAL(0.0), CREAL(0.35),"rubbish.rwx") ||
        !AddStaticObject(CREAL(1.5), CREAL(0.1), CREAL(2.0), "rubbish.rwx") ||
        !AddLamppost(CREAL(-1.35), CREAL(4.2)) ||
        !AddLamppost(CREAL(1.35),  CREAL(5.5)) ||
        !AddLamppost(CREAL(-1.35), CREAL(7.6)) ||
        !AddLamppost(CREAL(1.35), CREAL(9.0)) ||
        !AddLamppost(CREAL(-1.35), CREAL(11.0)))
    {
        RwClose();
        return FALSE;
    }

    /* Create 2 dummy clumps. These clumps are used to determine the 
       area of the viewport that represents the bitmap backdrop on each frame.
       One clump represents the Sky and the other the view of the skyline
       at the end of the alley */

    /* Create the Sky clump */

    RwModelBegin();
    RwClumpBegin();
    RwVertex(CREAL(LEFT_STREET),  CREAL(3.1), CREAL(-0.5));
    RwVertex(CREAL(RIGHT_STREET), CREAL(3.1), CREAL(-0.5));
    RwVertex(CREAL(LEFT_STREET),  CREAL(3.1), CREAL(NEAR_STREET));
    RwVertex(CREAL(RIGHT_STREET), CREAL(3.1), CREAL(NEAR_STREET));
    RwVertex(CREAL(0.0), CREAL(4.0), CREAL(4.0));
    RwQuad(1,3,4,2);
    RwClumpEnd(&cpGSky1);
    RwModelEnd();

    /* Create the End of Alley dummy clump */

    RwModelBegin();
    RwClumpBegin();
    RwVertex(CREAL(LEFT_STREET), CREAL(0.0), CREAL(NEAR_STREET));
    RwVertex(CREAL(RIGHT_STREET),CREAL(0.0), CREAL(NEAR_STREET));
    RwVertex(CREAL(LEFT_STREET), CREAL(4.0), CREAL(NEAR_STREET));
    RwVertex(CREAL(RIGHT_STREET),CREAL(4.0), CREAL(NEAR_STREET));
    RwQuad(1,3,4,2);
    RwClumpEnd(&cpGSky2);
    RwModelEnd();

/* Create a spline that the camera will fly along in flythrough mode */

#define ASSIGNVECTOR(A,X,Y,Z)    A.x = CREAL(X); A.y = CREAL(Y); A.z = CREAL(Z);

    {    
        RwV3d points[20];

        ASSIGNVECTOR(points[0], 1.7,  1.8, 7);
        ASSIGNVECTOR(points[1], 1,    1.0, 8);
        ASSIGNVECTOR(points[2], 0,    0.7, 5);
        ASSIGNVECTOR(points[3],-1.8,  0.5, 3);
        ASSIGNVECTOR(points[4],-1.5,  0.3, 1);
        ASSIGNVECTOR(points[5],-0.5,  0.5, 0.5);
        ASSIGNVECTOR(points[6], 0,    0.4, 1);
        ASSIGNVECTOR(points[7], 1,    0.5, 1.5);
        ASSIGNVECTOR(points[8], 1,    0.5, 4.5);
        ASSIGNVECTOR(points[9], 1,    0.5, 7.5);
        ASSIGNVECTOR(points[10], 0,    0.5, 8.5);
        ASSIGNVECTOR(points[11],-1,    0.3, 8.5);
        ASSIGNVECTOR(points[12],-1,    0.2, 7.5);
        ASSIGNVECTOR(points[13],-1,    0.2, 4.5);
        ASSIGNVECTOR(points[14],-1,    0.4, 1.5);
        ASSIGNVECTOR(points[15], 0,    0.6, 1.5);
        ASSIGNVECTOR(points[16], 1.2,  0.4, 1.5);
        ASSIGNVECTOR(points[17], 1.7,  1.5, 5.0);

        spGPath = RwCreateSpline(18, rwCLOSEDLOOP, points);
    }
    return TRUE;
}

/************************************************************************
 *
 *      Function:       TidyUp3D()
 *                      
 *      Description:    Clean Up the 3D components of the application. This
 *                      function destroys all the clumps, lights, scenes
 *                      and splines that are created in Init3D.
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
TidyUp3D(void)
{
    RwRaster *rpBackdrop;    

    /* Tidy up the OS Specific 3D stuff */

    OsTidy();   

    /* Destroy the camera's flight path */

    RwDestroySpline(spGPath);

    /* Destroy all of the 3D components created by the modules */

    AllBitsDestroy();
    AllRatsDestroy();
    ManDestroy();
    GunDestroy();
    AllObjectsDestroy();

    /* Destroy the scenes. This will destroy all the clumps and lights currently
       contained within the scenes */
    
    RwDestroyScene(spGPoolScene);
    RwDestroyScene(spGScene);
    RwDestroyScene(spGTargetScene);

    /* Destroy the camera and the backdrop */

    rpBackdrop = RwGetCameraBackdrop(cpGCamera);
    if (rpBackdrop)
        RwDestroyRaster(rpBackdrop);
    RwDestroyCamera(cpGCamera);

    /* Destroy everything else */

    if (rpGPanel)
        RwDestroyRaster(rpGPanel);


#ifdef WITH_SOUND
    AllSoundsDestroy();
#endif

    /* Close the library. This will destroy all of the clumps and lights
     * that aren't explicitly added to another Scene. All of the memory
     * allocated for internal resources and textures will also be freed.
     */
    RwClose();
}


/************************************************************************
 *
 *      Function:       HandlePaint()
 *                      
 *      Description:    Redraw the entire viewport. The viewport may be 
 *                      larger than the camera so the backing panel is drawn
 *                      in sections.
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
HandlePaint(void)
{
    RwInt32 x, y;
    RwInt32 vx,vy,vw,vh;
    RwInt32 offx,offy;
    RwRaster *raster;

    /* Save all of the camera settings that we will modify and restore 
     * them once we are finished with them.
     */

    RwGetCameraViewport(cpGCamera, &vx, &vy, &vw, &vh);
    RwGetCameraBackdropOffset(cpGCamera, &offx, &offy);
    raster = RwGetCameraBackdrop(cpGCamera);

    /* If no panel exists then there is no surround to draw so skip this bit */

    if (rpGPanel)
    {
        /* Use all of the camera's viewport to draw the panel */

        RwSetCameraBackdrop(cpGCamera, rpGPanel);
        RwSetCameraBackdropViewportRect(cpGCamera, 0,0, OsMaxCameraWidth(), OsMaxCameraHeight());

        for (y=0; y < OsMaxScreenHeight(); y += OsMaxCameraHeight())
        {
            for (x=0; x < OsMaxScreenWidth(); x += OsMaxCameraWidth())
            {
                RwSetCameraViewport(cpGCamera, x,y, OsMaxCameraWidth(), OsMaxCameraHeight());
                RwSetCameraBackdropOffset(cpGCamera, x,y);
                RwInvalidateCameraViewport(cpGCamera);
                OsBeginCameraUpdate(cpGCamera);
                    RwClearCameraViewport(cpGCamera);
                RwEndCameraUpdate(cpGCamera);
                OsShowCameraImage();
            }
        }
    }

    /* Restore the saved camera settings */

    RwSetCameraViewport(cpGCamera, vx, vy, vw, vh);
    RwSetCameraBackdrop(cpGCamera, raster);
    RwSetCameraBackdropOffset(cpGCamera, offx, offy);

    /* We need to redraw the entire viewport next time we copy to the display */
    RwInvalidateCameraViewport(cpGCamera);

}

/************************************************************************
 *
 *      Function:       HandleLeftButtonDown()
 *                      
 *      Description:    Set up the action to be performed when 
 *                      left mouse button is pressed. 
 *                      
 *
 *      Parameters:     x, y - Camera Viewport co-ordinates for the current
 *                      cursor position.
 *                      control - the status of the Control Key. 
 *                        TRUE = key pressed
 *                      shift - the status of the Shift Key. 
 *                        TRUE = key pressed
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
HandleLeftButtonDown(RwInt32 x, RwInt32 y, int control, int shift)
{
    if (nGFlyCamera)
    {
        /* If we are flying then turn it off */

        nGFlyCamera = 0;
        ResetCamera();
    }

    /* Define the action that will be taken by subsequent mouse movements */

    if (control)
    {
        /* Raise or lower the Camera */
        mGMouseMoveMode = MMChangeHeight;
    }
    else if (shift)
    {
        /* Tilt the Camera */
        mGMouseMoveMode = MMTiltCamera;
    }
    else
    {
        /* Rotate the camera and move backwards and forwards */
        mGMouseMoveMode = MMTravelCamera;
        nGSpeed = CREAL(0);
    }
 
    /* Remember the current x & y position to allow a relative
       movement determination to be performed later */
    nGLastX = x;
    nGLastY = y;
}

/************************************************************************
 *
 *      Function:       HandleRightButtonDown()
 *                      
 *      Description:    Set up the action to be performed when 
 *                      right mouse button is pressed. For this 
 *                      application the action is the same as the
 *                      left button with the additional feature that the
 *                      gun also fires.
 *
 *      Parameters:     x, y - Camera Viewport co-ordinates for the current
 *                      cursor position.
 *                      control - the status of the Control Key. 
 *                        TRUE = key pressed
 *                      shift - the status of the Shift Key. 
 *                        TRUE = key pressed
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
HandleRightButtonDown(RwInt32 x, RwInt32 y, int control, int shift)
{
    GunFiring(TRUE);

    /* Define the action that will be taken by subsequent mouse movements */

    if (control)
    {
        /* Raise or lower the Camera */
        mGMouseMoveMode = MMChangeHeight;
    }
    else if (shift)
    {
        /* Tilt the Camera */
        mGMouseMoveMode = MMTiltCamera;
    }
    else
    {
        /* Rotate the camera and move backwards and forwards */
        mGMouseMoveMode = MMTravelCamera;
        nGSpeed = CREAL(0);
    }
 
    /* Remember the current x & y position to allow a relative
       movement determination to be performed later */
    nGLastX = x;
    nGLastY = y;
}

/************************************************************************
 *
 *      Function:       HandleMouseMove()
 *                      
 *      Description:    Handle a movement of the mouse. If a previous left 
 *                      or right mouse button down event has set a mouse 
 *                      move mode then this function will take the necessary 
 *                      actions.
 *
 *      Parameters:     x, y - Camera Viewport co-ordinates for the current
 *                      cursor position.
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
HandleMouseMove(RwInt32 x, RwInt32 y)
{
    RwV3d vAt;

    /* mGMouseMoveMode tells us what kind of action to perform. */

    switch (mGMouseMoveMode)
    {
    case MMNoAction:
        break;

    case MMTravelCamera:
        /* We are travelling through the scene. Movement of the
         * mouse in the X direction will rotate the camera about the
         * Y Axis, and movement of the mouse in the Y direction will 
         * move the camera forwards and backwards through the scene.
         */

        /* Rotate the camera by mouse X delta degrees. */

        RwRotateMatrix(RwScratchMatrix(), CREAL(0.0), CREAL(1.0), CREAL(0.0),
                       INT2REAL(nGLastX - x), rwREPLACE);
        RwTransformCameraOrientation(cpGCamera, RwScratchMatrix());

        /* Move the camera forward. The speed of forward motion is deterimined
         * by the mouse Y delta divided by 100 units.
         */

        nGSpeed = RDiv(INT2REAL(nGLastY - y), CREAL(100.0));
        MoveForward(nGSpeed);
        break;

    case MMChangeHeight:
        /* Raised or lower the eyepoint of the camera */
        MoveUp(RDiv(INT2REAL(nGLastY - y),CREAL(50)));
        break;

    case MMTiltCamera:
        /* Movement of the mouse in the X axis rotates about the Y axis as
         *   above. Movement in the Y axis tilts the camera
         */

        /* Rotate the camera */
        RwRotateMatrix(RwScratchMatrix(), CREAL(0.0),CREAL(1.0),CREAL(0.0),
                      INT2REAL(nGLastX - x),rwREPLACE);
        RwTransformCameraOrientation(cpGCamera, RwScratchMatrix());

        /* Tilt the Camera by maintaining an absolute tilt value. This is
         * necessary to prevent accumulative roundoff errors and to allow the
         * amount of tilt to be limited.
         */
        nGTilt = RAdd(nGTilt, INT2REAL(y - nGLastY));
      
        if (nGTilt > CREAL(MAX_TILT))
            nGTilt = CREAL(MAX_TILT);
        else if (nGTilt < CREAL(-MAX_TILT))
            nGTilt = CREAL(-MAX_TILT);
	 
        /* force the camera to lie in a plane parallel with the ground 
         * plane */
        RwGetCameraLookAt(cpGCamera, &vAt);
        RwSetCameraLookAt(cpGCamera, vAt.x, CREAL(0.0),  vAt.z);
        RwSetCameraLookUp(cpGCamera, CREAL(0.0), CREAL(1.0), CREAL(0.0));
        /* Then tilt it */
        RwTiltCamera(cpGCamera, nGTilt);
        break;
    }

    /* Remember the current X and Y for next time. */
    nGLastX = x;
    nGLastY = y;
}

/************************************************************************
 *
 *      Function:       HandleLeftButtonUp()
 *                      
 *      Description:    Handle the release of the left mouse button. This
 *                      resets any current mouse movement action.
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
HandleLeftButtonUp(void)
{
  if (mGMouseMoveMode != MMNoAction)
  {
    mGMouseMoveMode = MMNoAction;
  }
}

/************************************************************************
 *
 *      Function:       HandleRightButtonUp()
 *                      
 *      Description:    Handle the release of the right mouse button. This
 *                      has the same behaviour as the raising of the left
 *                      mouse button with the additional feature that the 
 *                      gun will stop firing.
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
HandleRightButtonUp()
{
    /* Stop the gun firing */

    GunFiring(FALSE);
    HandleLeftButtonUp();
}

/************************************************************************
 *
 *      Function:       HandleTimer()
 *                      
 *      Description:    Handle the action that should be taken on each 
 *                      frame. This includes advancing all animated clumps
 *                      and textures by a frame and rerendering the scene
 *                      based on the current camera position
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
HandleTimer(void)
{
    int i;                      /* temporary loop control variable */
    static int nGState = 0;     /* Remember the current state of the 
                                   billboard lights */

    if ((RwRandom() & 0xff) < 16) 
    {
        /* At a random interval, trigger the effect of the billboard lights
         * flickering. This is achieved by altering the ambient surface
         * property of the polygons representing the light and advancing the 
         * billboard textures to the next frame 
         */

        if ((nGState++)&1) 
        {
            /* Lights are off, turn them on */
            RwSetPolygonAmbient(ppGLight1,CREAL(1.0));
            RwSetPolygonAmbient(ppGLight2,CREAL(1.0));
            RwSetTextureFrame(tpGBillboardLeft, 0);
            RwSetTextureFrame(tpGBillboardRight, 0);
        } 
        else 
        {
            /* Lights are on, turn them off */
            RwSetPolygonAmbient(ppGLight1,CREAL(0.0));
            RwSetPolygonAmbient(ppGLight2,CREAL(0.0));
            RwSetTextureFrame(tpGBillboardLeft, 1);
            RwSetTextureFrame(tpGBillboardRight, 1);
        }
    }

    /* Every so often release another Rat */
    AllRatsRelease();

    /* If the camera is flying then advance it to the next position along
     * the spline path 
     */

    if (nGFlyCamera)
    { 
        RwV3d up;

        /* Force the camera to preserve the correct up direction */
        up.x = CREAL(0.0);
        up.y = CREAL(1.0);
        up.z = CREAL(0.0);
        
        /* Get the current spline transform matrix */
        RwSplineTransform(spGPath, rwSMOOTH, nGAlpha, &up, RwScratchMatrix());

        /* And apply it to the camera */
        RwTransformCamera(cpGCamera, RwScratchMatrix(), rwREPLACE);

        /* Increment the spline control variable */
        nGAlpha = RAdd(nGAlpha, CREAL(0.002));
    }

    /* Update the animation of the Man in the poolroom */

    ManUpdate();

    /* If we are over the pad that activates the door then open the door */
    if (nGOnPad)
    {
        if (nGDoorPos < 20)
        {
            /* The door isn't open yet. Open it some more */
            for (i=0; i<9; i++)
            {
                vaGDoor[i].z -= CREAL(0.02);
                vaGDoor[i+9].z += CREAL(0.02);
            }
            nGDoorPos++;
            RwSetClumpVertices(cpGCity, naGDoor, vaGDoor, 18);
            /* Setting clump vertices forces the rwEDITABLE hint 
             * we don't need this so turn it off again
             */
            RwSetClumpHints(cpGCity, 0L);
        }
    }
    else
    {
        /* We're not over the pad. Close the door if it is open */
        if (nGDoorPos > 0)
        {
            /* The door is still open. Close it some more */
            for (i=0; i<9; i++)
            {
                vaGDoor[i].z += CREAL(0.02);
                vaGDoor[i+9].z -= CREAL(0.02);
            }
            nGDoorPos--;
            RwSetClumpVertices(cpGCity, naGDoor, vaGDoor, 18);
            /* Setting clump vertices forces the rwEDITABLE hint 
             * we don't need this so turn it off again
             */
            RwSetClumpHints(cpGCity, 0L);
        }
    }

    /* The camera has momentum and will continue to move forward once the 
       mouse button is released */

    if (mGMouseMoveMode == MMNoAction)
    {
        /* No mouse button is pressed. Check if the camera was moving
           forward */

        if (nGSpeed) 
        {
            /* The camera is moving so update its position */
            MoveForward(nGSpeed);

            /* slow it down */
            nGSpeed = RMul(nGSpeed, CREAL(0.9));
            if (RAbs(nGSpeed) < CREAL(0.005)) 
            {
                nGSpeed = CREAL(0);
            }
        }
    }
    /* Call the main render function to perform the rendering of the 
     * view
     */
    Render();
}

