
/****************************************************************************
 *
 * 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: Nicolas Vale.
 * Reviewed by: John Irwin.
 *                                                                         
 * Purpose: To illustrate how to load or create a particle clump, 
 *          and how it's different properties can be adjusted to produce
 *          different particle effects.
 *                         
 ****************************************************************************/

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

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

#include "rprandom.h"
#include "rpprtsys.h"
#include "rtcharse.h"

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

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

#include "prtcls1.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};

static RtCharset *Charset = NULL;

RpWorld *World = NULL;
RwCamera *Camera = NULL;



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

    bb.inf.x = bb.inf.y = bb.inf.z = -1000.0f;
    bb.sup.x = bb.sup.y = bb.sup.z = 1000.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, 200.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 Particles Example");

        RsGlobal.maxFPS = 120;

        return TRUE;
    }

    return FALSE;
}


/*
 *****************************************************************************
 */
static RwBool 
InitializeMenu(void)
{
    static RwChar systemLabel[]  = RWSTRING("Particle system_P");
    static RwChar textureLabel[] = RWSTRING("Particle texture_T");
    static RwChar numberLabel[]  = RWSTRING("Particle number");

    static RwChar emitterWidthLabel[]  = RWSTRING("Emitter width");
    static RwChar emitterLengthLabel[] = RWSTRING("Emitter length");
    static RwChar emitterAngleLabel[]  = RWSTRING("Emitter angle");

    static RwChar sizeLabel[]        = RWSTRING("Particle size");
    static RwChar aspectRatioLabel[] = RWSTRING("Aspect-ratio");
    static RwChar growthLabel[]      = RWSTRING("Particle growth");

    static RwChar flightTimeLabel[] = RWSTRING("Flight time");
    static RwChar minSpeedLabel[]   = RWSTRING("Minimum speed");
    static RwChar maxSpeedLabel[]   = RWSTRING("Maximum Speed");
    static RwChar dampingLabel[]    = RWSTRING("Damping");

    static RwChar forceXLabel[] = RWSTRING("Force-X");
    static RwChar forceYLabel[] = RWSTRING("Force-Y");
    static RwChar forceZLabel[] = RWSTRING("Force-Z");

    static RwChar startColorLabel[] = RWSTRING("Start color");
    static RwChar startAlphaLabel[] = RWSTRING("Start alpha");
    static RwChar endColorLabel[]   = RWSTRING("End color");
    static RwChar endAlphaLabel[]   = RWSTRING("End alpha");

    static RwChar saveLabel[] = RWSTRING("Save_S");
    static RwChar fpsLabel[]  = RWSTRING("FPS_F");

    static const RwChar *colorNames[] = 
    {
        RWSTRING("white"), 
        RWSTRING("black"),
        RWSTRING("red"), 
        RWSTRING("blue"), 
        RWSTRING("green"),
        RWSTRING("cyan"),
        RWSTRING("magenta"),
        RWSTRING("yellow")
    };

    static const RwChar *systemNames[] = 
    {
        RWSTRING("user"), 
        RWSTRING("smoke"),
        RWSTRING("fire"), 
        RWSTRING("water")
    };

    if( MenuOpen(TRUE, &ForegroundColor, &BackgroundColor) )
    {
        /*
         * System type and texture controls...
         */
        MenuAddEntryInt(systemLabel, &ParticlesCurrentSystem, 
            ParticlesSetSystemCallback, 0, NUMSYSTEMS-1, 1, systemNames);

        MenuAddEntryInt(textureLabel, &ParticlesCurrentTexture, 
            ParticlesSetTextureCallback, 0, NUMTEXTURES-1, 1, 
            ParticleTextureNames);
        
        MenuAddSeparator();

        /*
         * Emitter controls...
         */
        MenuAddEntryInt(numberLabel, &ParticlesNumber, 
            ParticlesSetNumberCallback, 25, 8000, 25, NULL); 

        MenuAddEntryReal(emitterWidthLabel, &ParticlesEmitterWidth, 
            ParticlesSetEmitterSizeCallback, 0.0f, 1000.0f, 0.5f);

        MenuAddEntryReal(emitterLengthLabel, &ParticlesEmitterLength, 
            ParticlesSetEmitterSizeCallback, 0.0f, 1000.0f, 0.5f);

        MenuAddEntryReal(emitterAngleLabel, &ParticlesEmitterAngle, 
            ParticlesSetEmitterAngleCallback, 0.0f, 180.0f, 0.5f);

        MenuAddSeparator();

        /*
         * Size and shape controls...
         */
        MenuAddEntryReal(sizeLabel, &ParticlesSize, 
            ParticlesSetSizeCallback, 0.1f, 1000.0f, 0.1f);

        MenuAddEntryReal(aspectRatioLabel, &ParticlesAspectRatio, 
            ParticlesSetSizeCallback, 0.0f, 50.0f, 0.05f);

        MenuAddEntryReal(growthLabel, &ParticlesGrowth, 
            ParticlesSetSizeCallback, 0.0f, 50.0f, 0.1f);

        MenuAddSeparator();

        /*
         * Kinematic controls...
         */
        MenuAddEntryReal(flightTimeLabel, &ParticlesFlightTime, 
            ParticlesSetFlightTimeCallback, 0.1f, 1000.0f, 0.1f);

        MenuAddEntryReal(minSpeedLabel,  &ParticlesMinSpeed, 
            ParticlesSetSpeedCallback, 0.0f, 1000.0f, 0.1f);

        MenuAddEntryReal(maxSpeedLabel, &ParticlesMaxSpeed, 
            ParticlesSetSpeedCallback, 0.0f, 1000.0f, 0.1f);

        MenuAddEntryReal(dampingLabel, &ParticlesDamping, 
            ParticlesSetSpeedCallback, 0.0f, 1.0f, 0.1f);

        MenuAddSeparator();

        /*
         * Force controls...
         */
        MenuAddEntryReal(forceXLabel, &ParticlesForce.x, 
            ParticlesSetForceCallback, -100.0f, 100.0f, 0.1f);

        MenuAddEntryReal(forceYLabel, &ParticlesForce.y, 
            ParticlesSetForceCallback, -100.0f, 100.0f, 0.1f);

        MenuAddEntryReal(forceZLabel, &ParticlesForce.z, 
            ParticlesSetForceCallback, -100.0f, 100.0f, 0.1f);        

        MenuAddSeparator();

        /*
         * Color controls...
         */
        MenuAddEntryInt(startColorLabel, &ParticlesStartColor, 
            ParticlesSetColorsCallback, 0, 7, 1, colorNames);

        MenuAddEntryInt(startAlphaLabel, &ParticlesStartAlpha, 
            ParticlesSetColorsCallback, 0, 255, 5, NULL);

        MenuAddEntryInt(endColorLabel, &ParticlesEndColor, 
            ParticlesSetColorsCallback, 0, 7, 1, colorNames);

        MenuAddEntryInt(endAlphaLabel, &ParticlesEndAlpha, 
            ParticlesSetColorsCallback, 0, 255, 5, NULL);

        MenuAddSeparator();

        MenuAddEntryTrigger(saveLabel, ParticlesSaveCallback);

        MenuAddEntryBool(fpsLabel, &FPSOn, NULL);

        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...
     */
    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 particle systems...
     */
    if( !ParticlesInitialize(World) )
    {
        RsErrorMessage(RWSTRING("Cannot create particle systems."));

        return FALSE;
    }

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

        return FALSE;
    }

#ifdef RWMETRICS
    RsMetricsOpen(Camera);
#endif

    return TRUE;
}


/*
 *****************************************************************************
 */
static void 
Terminate3D(void)
{
#ifdef RWMETRICS
    RsMetricsClose();
#endif

    MenuClose();

    ParticlesTerminate();

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

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

    if( World )
    {
        RpWorldDestroy(World);
    }

    if( Charset )
    {
        RwRasterDestroy(Charset);
    }

    RsRwTerminate();

    return;
}


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

    /* 
     * Attach particles plug-in ...
     */
    if( !RpParticlesAtomicPluginAttach() )
    {
        return FALSE;
    }
    
    /* 
     * Attach random plug-in (required by particles plug-in)...
     */
    if( !RpRandomPluginAttach() )
    {
        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)
{
    RwUInt32 thisTime;
    RwReal deltaTime;

    static RwBool firstCall = TRUE;
    static RwUInt32 lastFrameTime, lastAnimTime;

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

        firstCall = FALSE;
    }

    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;
    }

    /* 
     * Update particle system...
     */
    deltaTime = (thisTime - lastAnimTime) * 0.001f;
    
    ParticlesUpdate(deltaTime);

    lastAnimTime = thisTime;
    
    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;
        }
    }
}

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