/****************************************************************************
 *
 * 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. and Canon Inc. will not, under any
 * circumstances, be liable for any lost revenue or other damages
 * arising from the use of this file.
 *
 * Copyright (c) 2001 Criterion Software Ltd.
 * All Rights Reserved.
 *
 */

/****************************************************************************
 *
 * main.c
 *
 * Copyright (C) 2001 Criterion Technologies.
 *
 * Original author: Alexandre Hadjadj
 * Reviewed by:
 *
 * Purpose: To illustrate the possibility of creating a custom VU pipe
 *
 ****************************************************************************/

#include "rwcore.h"
#include "rpworld.h"

#ifdef RWLOGO
#include "rplogo.h"
#endif

#include "rtcharse.h"

#include "skeleton.h"
#include "menu.h"
#include "events.h"
#include "camera.h"

#ifdef RWMETRICS
#include "metrics.h"
#endif

#include "cfx.h"
#include "main.h"

#define DEFAULT_SCREEN_WIDTH (640)
#define DEFAULT_SCREEN_HEIGHT (480)

#ifdef WIDE_SCREEN
#define DEFAULT_ASPECTRATIO (16.0f/9.0f)
#else
#define DEFAULT_ASPECTRATIO (4.0f/3.0f)
#endif

#define DEFAULT_VIEWWINDOW (0.5f)

static RwBool FPSOn = FALSE;

static RwInt32 FrameCounter = 0;
static RwInt32 FramesPerSecond = 0;

static RwRGBA ForegroundColor = {200, 200, 200, 255};
static RwRGBA BackgroundColor = { 64,  64,  64,   0};

RpWorld *World = NULL;
RwCamera *Camera = NULL;
RtCharset *Charset = NULL;
RpClump *Clump = NULL;
RpLight *Light = NULL;
RpLight *AmbientLight = NULL;

/*
 *****************************************************************************
 */
void
ClumpRotate(RwReal angleX, RwReal angleY)
{
    RwMatrix *cameraMatrix;
    RwV3d right, up, pos;
    RwFrame *frame;

    cameraMatrix = RwFrameGetMatrix(RwCameraGetFrame(Camera));
    right = *RwMatrixGetRight(cameraMatrix);
    up = *RwMatrixGetUp(cameraMatrix);

    frame = RpClumpGetFrame(Clump);
    pos = *RwMatrixGetPos(RwFrameGetMatrix(frame));

    /*
     * First translate back to the origin...
     */
    RwV3dScale(&pos, &pos, -1.0f);
    RwFrameTranslate(frame, &pos, rwCOMBINEPOSTCONCAT);

    /*
     * ...do the rotations...
     */
    RwFrameRotate(frame, &up, angleX, rwCOMBINEPOSTCONCAT);
    RwFrameRotate(frame, &right, angleY, rwCOMBINEPOSTCONCAT);

    /*
     * ...and translate back...
     */
    RwV3dScale(&pos, &pos, -1.0f);
    RwFrameTranslate(frame, &pos, rwCOMBINEPOSTCONCAT);

    return;
}


/*
 *****************************************************************************
 */
void
ClumpTranslateZ(RwReal deltaZ)
{
    RwMatrix *cameraMatrix;
    RwV3d at;
    RwFrame *frame;

    cameraMatrix = RwFrameGetMatrix(RwCameraGetFrame(Camera));
    at = *RwMatrixGetAt(cameraMatrix);

    frame = RpClumpGetFrame(Clump);

    RwV3dScale(&at, &at, deltaZ);

    RwFrameTranslate(frame, &at, rwCOMBINEPOSTCONCAT);

    return;
}


/*
 *****************************************************************************
 */
static RpClump *
LoadClump(const RwChar *filename)
{
    RwStream *stream = NULL;
    RpClump *clump = NULL;
    RwChar *path;

    path = RsPathnameCreate(filename);
    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, path);
    RsPathnameDestroy(path);

    if( stream )
    {
        if( RwStreamFindChunk(stream, rwID_CLUMP, NULL, NULL) )
        {
            clump = RpClumpStreamRead(stream);
        }

        RwStreamClose(stream, NULL);
    }

    return clump;
}


/*
 *****************************************************************************
 */
static RpAtomic *
AtomicCB(RpAtomic *a, void *data __RWUNUSED__)
{
    return CustomFXAtomicSetup(a);
}


/*
 *****************************************************************************
 */
static RpClump*
CreateClump(void)
{
    RpClump *clump = NULL;
    RwTexture *customTexture = NULL;
    RwChar *path;

    path = RsPathnameCreate(RWSTRING("models/textures/"));
    RwImageSetPath(path);
    RsPathnameDestroy(path);

    customTexture = RwTextureRead(RWSTRING("rw"),NULL);

    CustomFXSetTexture(customTexture);

    clump = LoadClump(RWSTRING("models/bucky.dff"));

    {
        RwV3d yAxis = {0.0f, 1.0f, 0.0f};
        RwV3d xAxis = {1.0f, 0.0f, 0.0f};
        RwV3d pos   = {0.0f, 0.0f, 10.0f};
        RwFrame *clumpFrame = NULL;

        clumpFrame = RpClumpGetFrame(clump);

        RwFrameRotate(clumpFrame, &xAxis, 90.0f, rwCOMBINEREPLACE);
        RwFrameRotate(clumpFrame, &yAxis, -90.0f, rwCOMBINEPOSTCONCAT);

        RwFrameTranslate(clumpFrame, &pos, rwCOMBINEPOSTCONCAT);

        RpWorldAddClump(World, clump);

        /*
         * Setup CustomFX...
         */
        RpClumpForAllAtomics(clump, AtomicCB, NULL);

        return clump;
    }

    return NULL;
}


/*
 *****************************************************************************
 */
static void
LightResetFrame(RpLight *light)
{
    RwFrame *frame;
    RwV3d xAxis = {1.0f, 0.0f, 0.0f};
    RwV3d yAxis = {0.0f, 1.0f, 0.0f};

    frame = RpLightGetFrame(light);

    RwFrameRotate(frame, &xAxis, 45.0f, rwCOMBINEREPLACE);
    RwFrameRotate(frame, &yAxis, 45.0f, rwCOMBINEPOSTCONCAT);

    return;
}


/*
 *****************************************************************************
 */
static RpLight*
CreateAmbientLight(RpWorld *world)
{
    RpLight *light = NULL;
    light = RpLightCreate(rpLIGHTAMBIENT);
    if( light )
    {
        RwRGBAReal AmbientLightColor = { 0.5f, 0.5f, 0.5f, 1.0f };

        RpLightSetColor(light,&AmbientLightColor);

        RpWorldAddLight(world, light);

        return light;
    }

    return NULL;
}


/*
 *****************************************************************************
 */
static RpLight*
CreateLight(RpWorld *world)
{
    RpLight *light = NULL;

    light = RpLightCreate(rpLIGHTDIRECTIONAL);
    if( light )
    {
        RwFrame *frame = NULL;
        RwRGBAReal LightColor = { 1.0f, 1.0f, 1.0f, 1.0f };

        frame = RwFrameCreate();
        if( frame )
        {
            RpLightSetFrame(light, frame);

            RpLightSetColor(light,&LightColor);

            LightResetFrame(light);

            RpWorldAddLight(world, light);

            return light;
        }

        RpLightDestroy(light);
    }

    return NULL;
}


/*
 *****************************************************************************
 */
static RpWorld *
CreateWorld(void)
{
    RpWorld *world;
    RwBBox bb;

    bb.inf.x = bb.inf.y = bb.inf.z = -100.0f;
    bb.sup.x = bb.sup.y = bb.sup.z = 100.0f;

    world = RpWorldCreate(&bb);

    return world;
}


/*
 *****************************************************************************
 */
static RwCamera *
CreateCamera(RpWorld *world)
{
    RwCamera *camera;

    camera = CameraCreate(RsGlobal.maximumWidth, RsGlobal.maximumHeight, TRUE);

    if( camera )
    {
        RwCameraSetNearClipPlane(camera, 0.1f);
        RwCameraSetFarClipPlane(camera, 30.0f);

        RpWorldAddCamera(world, camera);

        return camera;
    }

    return NULL;
}


/*
 *****************************************************************************
 */
static RwBool
Initialize(void)
{
    if( RsInitialize() )
    {
        if( !RsGlobal.maximumWidth )
        {
            RsGlobal.maximumWidth = DEFAULT_SCREEN_WIDTH;
        }

        if( !RsGlobal.maximumHeight )
        {
            RsGlobal.maximumHeight = DEFAULT_SCREEN_HEIGHT;
        }

        RsGlobal.appName = RWSTRING("RW3 ppvu Example");

        RsGlobal.maxFPS = 160;

        return TRUE;
    }

    return FALSE;
}


/*
 *****************************************************************************
 */
static RwBool
InitializeMenu(void)
{
    static RwChar fpsLabel[] = RWSTRING("FPS_F");

    if( MenuOpen(TRUE, &ForegroundColor, &BackgroundColor) )
    {
        MenuAddEntryBool(fpsLabel, &FPSOn, NULL);

        MenuSetStatus(MENUOFF);

        return TRUE;
    }

    return FALSE;
}


/*
 *****************************************************************************
 */
static RwBool
Initialize3D(void *param)
{
    if( !RsRwInitialize(param) )
    {
        RsErrorMessage(RWSTRING("Error initializing RenderWare."));

        return FALSE;
    }

    Charset = RtCharsetCreate(&ForegroundColor, &BackgroundColor);
    if( Charset == NULL )
    {
        RsErrorMessage(RWSTRING("Cannot create raster charset."));

        return FALSE;
    }

    /*
     * Create an empty world if not loading a BSP...
     */
    World = CreateWorld();
    if( World == NULL )
    {
        RsErrorMessage(RWSTRING("Cannot create world."));

        return FALSE;
    }

    /*
     * Create a camera using the democom way...
     */
    Camera = CreateCamera(World);
    if( Camera == NULL )
    {
        RsErrorMessage(RWSTRING("Cannot create camera."));

        return FALSE;
    }

    /*
     * Create a directional light...
     */
    Light = CreateLight(World);
    if( Light == NULL )
    {
        RsErrorMessage(RWSTRING("Cannot create light sources"));
    }

    /*
     * Create an ambient light...
     */
    AmbientLight = CreateAmbientLight(World);
    if( AmbientLight == NULL )
    {
        RsErrorMessage(RWSTRING("Cannot create Ambient light sources"));
    }

    /*
     * Create CustomFX pipeline...
     */
    if( !CustomFXCreate() )
    {
        RsErrorMessage(RWSTRING("Cannot create CustomFX pipeline"));
    }

    /*
     * Load a clump...
     */
    Clump = CreateClump();
    if( Clump == NULL)
    {
        RsErrorMessage(RWSTRING("Cannot create clump."));

        return FALSE;
    }

    if( !InitializeMenu() )
    {
        RsErrorMessage(RWSTRING("Error initializing menu."));

        return FALSE;
    }

#ifdef RWMETRICS
    RsMetricsOpen(Camera);
#endif

    return TRUE;
}


/*
 *****************************************************************************
 */
static void
Terminate3D(void)
{
    /*
     * Close or destroy RenderWare components in the reverse order they
     * were opened or created...
     */

#ifdef RWMETRICS
    RsMetricsClose();
#endif

    MenuClose();

    if( Camera )
    {
        RpWorldRemoveCamera(World, Camera);

        /*
         * This assumes the camera was created the democom way...
         */
        CameraDestroy(Camera);
    }

    if( Light )
    {
        RwFrame *frame = NULL;

        RpWorldRemoveLight(World, Light);

        frame = RpLightGetFrame(Light);
        if( frame )
        {
            RpLightSetFrame(Light, NULL);

            RwFrameDestroy(frame);
        }

        RpLightDestroy(Light);

    }

    if( AmbientLight )
    {
        RpWorldRemoveLight(World, AmbientLight);

        RpLightDestroy(AmbientLight);
    }

    if( Clump )
    {
        if( RpClumpGetWorld(Clump) )
        {
            RpWorldRemoveClump(World, Clump);
        }

        RpClumpDestroy(Clump);
    }

    if( World )
    {
        RpWorldDestroy(World);
    }

    if( Charset )
    {
        RwRasterDestroy(Charset);
    }

    CustomFXDestroy();

    RsRwTerminate();

    return;
}


/*
 *****************************************************************************
 */
static RwBool
AttachPlugins(void)
{
    /*
     * Attach world plug-in...
     */
    if( !RpWorldPluginAttach() )
    {
        return FALSE;
    }


#ifdef RWLOGO
    /*
     * Attach logo plug-in...
     */
    if( !RpLogoPluginAttach() )
    {
        return FALSE;
    }
#endif

    return TRUE;
}


/*
 *****************************************************************************
 */
static void
DisplayOnScreenInfo(RwCamera *camera)
{
    RwChar caption[256];
    RtCharsetDesc charsetDesc;
    RwInt32 crw, crh;

    crw = RwRasterGetWidth(RwCameraGetRaster(camera));
    crh = RwRasterGetHeight(RwCameraGetRaster(camera));

    RtCharsetGetDesc(Charset, &charsetDesc);

    if( FPSOn )
    {
        rwsprintf(caption, RWSTRING("FPS: %03d"), FramesPerSecond);

        RtCharsetPrint(Charset, caption,
                       (crw - charsetDesc.width * (rwstrlen(caption) + MARGIN_RIGHT) ),
                        charsetDesc.height * MARGIN_TOP );

    }

    return;
}


/*
 *****************************************************************************
 */
static void
Render(void)
{
    RwCameraClear(Camera, &BackgroundColor, rwCAMERACLEARZ|rwCAMERACLEARIMAGE);

    if( RwCameraBeginUpdate(Camera) )
    {
        if( MenuGetStatus() != HELPMODE )
        {
            RpWorldRender(World);

            DisplayOnScreenInfo(Camera);
        }

        MenuRender(Camera, NULL);

#ifdef RWMETRICS
        RsMetricsRender();
#endif

        RwCameraEndUpdate(Camera);
    }

    /*
     * Display camera's raster...
     */
    RsCameraShowRaster(Camera);

    FrameCounter++;

    return;
}


/*
 *****************************************************************************
 */
static void
Idle(void)
{
    static RwBool firstCall = TRUE;
    static RwUInt32 lastFrameTime, lastAnimTime;
    static RwReal cumulDelta = 0.0f;
    RwUInt32 thisTime;
    RwReal deltaTime;
    RwV2d setupVector;

    thisTime = RsTimer();

    /*
     * Has a second elapsed since we last updated the FPS...
     */
    if( thisTime > (lastFrameTime + 1000) )
    {
        /*
         * Capture the frame counter...
         */
        FramesPerSecond = FrameCounter;

        /*
         * ...and reset...
         */
        FrameCounter = 0;

        lastFrameTime = thisTime;
    }

    /*
     * Animation update time in seconds...
     */
    deltaTime = (thisTime - lastAnimTime) * 0.001f;

    /*
     * Update any animations here...
     */
    lastAnimTime = thisTime;

    /*
     * Update texture Datas...
     */
    cumulDelta += deltaTime;

    CustomFXSetAngle(&cumulDelta);

    setupVector.x = RwCos(cumulDelta/2.0f);
    setupVector.y = RwSin(cumulDelta/2.0f);
    CustomFXSetTranslation(&setupVector);

    setupVector.x = RwCos(cumulDelta/8.0f)*4.0f;
    setupVector.y = RwCos(cumulDelta/8.0f)*4.0f;
    CustomFXSetScale(&setupVector);

    if( firstCall )
    {
        lastFrameTime = lastAnimTime = RsTimer();

        firstCall = FALSE;
    }

    Render();

    return;
}


/*
 *****************************************************************************
 */
RsEventStatus
AppEventHandler(RsEvent event, void *param)
{
    switch( event )
    {
        case rsINITIALIZE:
        {
            return Initialize() ? rsEVENTPROCESSED : rsEVENTERROR;
        }

        case rsCAMERASIZE:
        {
            CameraSize(Camera, (RwRect *)param,
                DEFAULT_VIEWWINDOW, DEFAULT_ASPECTRATIO);

            return rsEVENTPROCESSED;
        }

        case rsRWINITIALIZE:
        {
            return Initialize3D(param) ? rsEVENTPROCESSED : rsEVENTERROR;
        }

        case rsRWTERMINATE:
        {
            Terminate3D();

            return rsEVENTPROCESSED;
        }

        case rsPLUGINATTACH:
        {
            return AttachPlugins() ? rsEVENTPROCESSED : rsEVENTERROR;
        }

        case rsINPUTDEVICEATTACH:
        {
            AttachInputDevices();

            return rsEVENTPROCESSED;
        }

        case rsIDLE:
        {
            Idle();

            return rsEVENTPROCESSED;
        }

        default:
        {
            return rsEVENTNOTPROCESSED;
        }
    }
}


/*
 *****************************************************************************
 */
