
/****************************************************************************
 *
 * 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.
 *
 */

/****************************************************************************
 *                                                                         
 * prtcls1.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"
#include "rpprtsys.h"

#include "skeleton.h"

#include "prtcls1.h"

static RwRGBA ParticleColors[] = 
{ 
    {255, 255, 255, 255},
    {  0,   0,   0, 255},
    {255,   0,   0, 255},
    {  0,   0, 255, 255},
    {  0, 255,   0, 255},
    {  0, 255, 255, 255},
    {255,   0, 255, 255},
    {255, 255,   0, 255}
};

static RpClump *CurrentClump = NULL;
static RpClump *ParticleSystems[NUMSYSTEMS];
static RwTexture *ParticleTextures[NUMTEXTURES];

/*
 * Particle system sources.
 * First one is procedurally-defined, others are loaded
 * from a clump DFF file...
 */
static const RwChar *ParticlesFilenames[NUMSYSTEMS] = 
{ 
    RWSTRING(""),
    RWSTRING("./models/smoke.dff"),
    RWSTRING("./models/fire.dff"),
    RWSTRING("./models/water.dff")
};


RwInt32 ParticlesCurrentSystem = 0;

const RwChar *ParticleTextureNames[NUMTEXTURES] = 
{ 
    RWSTRING("circle"),
    RWSTRING("square"),
    RWSTRING("helix"), 
    RWSTRING("droplet"),
    RWSTRING("snow"),
    RWSTRING("cloud")
};


/*
 * Initial particle parameters for the user-defined system...
 */
RwInt32 ParticlesCurrentTexture = 0;

RwInt32 ParticlesNumber = 2000; 

RwReal ParticlesEmitterWidth = 1.0f;
RwReal ParticlesEmitterLength = 1.0f;
RwReal ParticlesEmitterAngle = 20.0f;

RwReal ParticlesSize = 1.0f;
RwReal ParticlesGrowth = 1.0f;
RwReal ParticlesAspectRatio = 1.0f;

RwReal ParticlesFlightTime = 1.0f;
RwReal ParticlesMinSpeed = 6.0f;
RwReal ParticlesMaxSpeed = 10.0f;
RwReal ParticlesDamping = 0.0f;

RwV3d ParticlesForce = {0.0f, 0.0f, 0.0f};

RwInt32 ParticlesStartColor = 1;
RwInt32 ParticlesStartAlpha = 255;
RwInt32 ParticlesEndColor = 0;
RwInt32 ParticlesEndAlpha = 255;



/*
 *****************************************************************************
 */
static RpClump *
ParticlesCreate(void)
{
    RpMaterial *material = NULL;
    RpAtomic *particles = NULL;
    RpClump *particleSystem = NULL;
    RwRGBA start, end;
    RwFrame *clumpFrame, *atomicFrame;

    material = RpMaterialCreate();
    if( !material )
    {
        return FALSE;
    }

    particles = RpParticlesAtomicCreate(ParticlesNumber, material);
    if( particles == NULL )
    {
        return NULL;
    }

    /*
     * Remove the application's ownership. This will allow us to
     * destroy the particle system clump without worrying about 
     * disposing of the material explicitly when the clump
     * is destroyed...
     */
    RpMaterialDestroy(material);

    RpMaterialSetTexture(material, ParticleTextures[ParticlesCurrentTexture]);
    
    RpMaterialSetColor(material, &ParticleColors[0]);

    /*
     * Setup the new particles with the currently selected parameters...
     */
    RpParticlesAtomicSetEmitterSize(particles, 
                                    ParticlesEmitterWidth, ParticlesEmitterLength);
    
    RpParticlesAtomicSetEmitterAngle(particles, ParticlesEmitterAngle);
    
    RpParticlesAtomicSetSize(particles, 
                             ParticlesSize, ParticlesGrowth, ParticlesAspectRatio);
    
    RpParticlesAtomicSetSpeed(particles, 
                              ParticlesMinSpeed, ParticlesMaxSpeed, ParticlesDamping);

    start = ParticleColors[ParticlesStartColor];
    start.alpha = (RwUInt8)ParticlesStartAlpha;

    end = ParticleColors[ParticlesEndColor];
    end.alpha = (RwUInt8)ParticlesEndAlpha;

    RpParticlesAtomicSetColors(particles, &start, &end);

    RpParticlesAtomicSetFlightTime(particles, ParticlesFlightTime);
    
    RpParticlesAtomicSetForce(particles, &ParticlesForce);

    /*
     * Create a clump to contain the new particle system
     * (Note: particles atomic is created with a frame)...
     */
    particleSystem = RpClumpCreate();
    clumpFrame = RwFrameCreate();
    RpClumpSetFrame(particleSystem, clumpFrame);

    atomicFrame = RpAtomicGetFrame(particles);
    RwFrameAddChild(clumpFrame, atomicFrame);

    RpClumpAddAtomic(particleSystem, particles);

    return particleSystem;
}


/*
 *****************************************************************************
 */
static RpClump *
ParticlesLoad(const RwChar *pathname)
{
    RpClump *particleSystem = NULL;
    RwStream *stream = NULL;
    RwChar *path;

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

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

        RwStreamClose(stream, NULL);
    }

    return particleSystem;
}


/*
 *****************************************************************************
 */
RwBool
ParticlesInitialize(RpWorld *world)
{
    RwChar *path;
    RwInt32 i;
    RwV3d xAxis = {1.0f, 0.0f, 0.0f};
    RwV3d pos = {0.0f, 0.0f, 50.0f};

    /*
     * Load the particle textures...
     */
    path = RsPathnameCreate(RWSTRING("./models/textures/"));    
    RwImageSetPath(path);   
    RsPathnameDestroy(path);

    for(i=0; i<NUMTEXTURES; i++)
    {
        ParticleTextures[i] = RwTextureRead(ParticleTextureNames[i], NULL);

        if( !ParticleTextures[i] )
        {
            return FALSE;
        }
    }

    /* 
     * Create or load the particle systems...
     */
    for(i=0; i<NUMSYSTEMS; i++)
    {
        RwFrame *frame;

        if( i == 0 )
        {
            /* 
             * Create user-defined particle system...
             */
            ParticleSystems[0] = ParticlesCreate();
            if( !ParticleSystems[0] )
            {
                return FALSE;
            }

            RpWorldAddClump(world, ParticleSystems[0]);

            CurrentClump = ParticleSystems[0];
        }
        else
        {
            /* 
             * Load particle system from DFF...
             */
            ParticleSystems[i] = ParticlesLoad(ParticlesFilenames[i]);
            if( !ParticleSystems[i] )
            {
                return FALSE;
            }
        }

        /*
         * Position and orient the clump in front of the camera...
         */
        frame = RpClumpGetFrame(ParticleSystems[i]);
        RwFrameRotate(frame, &xAxis, -90.0f, rwCOMBINEREPLACE);
        RwFrameTranslate(frame, &pos, rwCOMBINEPOSTCONCAT);
    }
           
    return TRUE;
}


/*
 *****************************************************************************
 */
RwBool 
ParticlesTerminate(void)
{
    RwInt32 i;

    for(i=0; i<NUMSYSTEMS; i++)
    {
        if( ParticleSystems[i] )
        {
            if( RpClumpGetWorld(ParticleSystems[i]) )
            {
                RpWorldRemoveClump(World, ParticleSystems[i]);
            }
                
            RpClumpDestroy(ParticleSystems[i]);
        }
    }

    for(i=0; i<NUMTEXTURES; i++)
    {
        if( ParticleTextures[i] )
        {
            RwTextureDestroy(ParticleTextures[i]);
        }
    }
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicUpdate(RpAtomic *atomic,
                      void *data)
{
    RwReal delta = *(RwReal *)data;    

    RpParticlesAtomicUpdate(atomic, delta);
    
    return atomic;
}


RwBool 
ParticlesUpdate(RwReal timeStep)
{
    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicUpdate, &timeStep);

    return TRUE;
}


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

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

    frame = RpClumpGetFrame(ParticleSystems[ParticlesCurrentSystem]);
    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
ParticlesZTranslateEmitter(RwReal zDelta)
{
    RwFrame *cameraFrame;
    RwV3d delta;

    cameraFrame = RwCameraGetFrame(Camera);

    RwV3dScale(&delta, RwMatrixGetAt(RwFrameGetMatrix(cameraFrame)), zDelta);

    RwFrameTranslate(
                     RpClumpGetFrame(ParticleSystems[ParticlesCurrentSystem]), 
                     &delta, rwCOMBINEPOSTCONCAT);

    return;
}


/*
 *****************************************************************************
 */
RwBool 
ParticlesSetSystemCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return TRUE;
    }

    if( CurrentClump && RpClumpGetWorld(CurrentClump) )
    {
        RpWorldRemoveClump(World, CurrentClump);
    }

    RpWorldAddClump(World, ParticleSystems[ParticlesCurrentSystem]);

    CurrentClump = ParticleSystems[ParticlesCurrentSystem];

    return TRUE;
}


/*
 *****************************************************************************
 */
RwBool
ParticlesSetNumberCallback(RwBool testEnable)
{
    RwMatrix *ltm;

    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }

    /*
     * Only way of changing the number of particles is to destroy
     * and recreate the particles atomic -- we'll just do the 
     * whole clump...
     */

    /*
     * Remember where the clump is...
     */
    ltm = RwMatrixCreate();
    RwMatrixCopy(ltm, RwFrameGetLTM(RpClumpGetFrame(ParticleSystems[0])));

    RpWorldRemoveClump(World, ParticleSystems[0]);
    RpClumpDestroy(ParticleSystems[0]);
    CurrentClump = NULL;

    ParticleSystems[0] = ParticlesCreate();
    if( ParticleSystems[0] )
    {
        RpWorldAddClump(World, ParticleSystems[0]);

        /*
         * Put it back where it was...
         */
        RwFrameTransform(RpClumpGetFrame(ParticleSystems[0]), 
                         ltm, rwCOMBINEREPLACE);

        CurrentClump = ParticleSystems[0];
    }

    RwMatrixDestroy(ltm);

    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetEmitterAngle(RpAtomic *atomic,
                               void *data __RWUNUSED__)
{
    RpParticlesAtomicSetEmitterAngle(atomic, ParticlesEmitterAngle);        

    return atomic;
}


RwBool
ParticlesSetEmitterAngleCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }  
    
    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetEmitterAngle, NULL);   
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetEmitterSize(RpAtomic *atomic,
                              void *data __RWUNUSED__)
{
    RpParticlesAtomicSetEmitterSize(atomic, 
                                    ParticlesEmitterWidth, ParticlesEmitterLength);
        
    return atomic;
}


RwBool
ParticlesSetEmitterSizeCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }    

    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetEmitterSize, NULL); 
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetSize(RpAtomic *atomic,
                       void *data __RWUNUSED__)
{
    RpParticlesAtomicSetSize(atomic, 
                             ParticlesSize, ParticlesGrowth, ParticlesAspectRatio);
        
    return atomic;
}


RwBool
ParticlesSetSizeCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }    

    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetSize, NULL);    
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetSpeed(RpAtomic *atomic,
                        void *data __RWUNUSED__)
{
    RpParticlesAtomicSetSpeed(atomic, 
                              ParticlesMinSpeed, ParticlesMaxSpeed, ParticlesDamping);
        
    return atomic;
}


RwBool
ParticlesSetSpeedCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }

    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetSpeed, NULL);    
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetFlightTime(RpAtomic *atomic,
                             void *data __RWUNUSED__)
{
    RpParticlesAtomicSetFlightTime(atomic, ParticlesFlightTime);
        
    return atomic;
}


RwBool
ParticlesSetFlightTimeCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }

    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetFlightTime, NULL);    
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetColors(RpAtomic *atomic,
                         void *data __RWUNUSED__)
{
    RwRGBA start, end;

    start = ParticleColors[ParticlesStartColor];
    start.alpha = (RwUInt8)ParticlesStartAlpha;

    end = ParticleColors[ParticlesEndColor];
    end.alpha = (RwUInt8)ParticlesEndAlpha;

    RpParticlesAtomicSetColors(atomic, &start, &end);
        
    return atomic;
}


RwBool
ParticlesSetColorsCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }

    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetColors, NULL);    
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetForce(RpAtomic *atomic,
                        void *data __RWUNUSED__)
{
    RpParticlesAtomicSetForce(atomic, &ParticlesForce);
        
    return atomic;
}


RwBool
ParticlesSetForceCallback(RwBool testEnable)
{
    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }

    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetForce, NULL);    
    
    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAtomic *
ParticlesAtomicSetTexture(RpAtomic *atomic,
                          void *data)
{
    RpMaterial *material = NULL;
    RpGeometry *geometry = NULL;

    geometry = RpAtomicGetGeometry(atomic);
    if( geometry )
    {
        material = RpGeometryGetMaterial(geometry, 0);
        if( material )
        {
            RpMaterialSetTexture(material, (RwTexture *)data);
        }
    }

    return atomic;
}


RwBool
ParticlesSetTextureCallback(RwBool testEnable)
{
    if ( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }

    RpClumpForAllAtomics(ParticleSystems[ParticlesCurrentSystem], 
                         ParticlesAtomicSetTexture, 
                         (void *)ParticleTextures[ParticlesCurrentTexture]);
    
    return TRUE;  
}


/*
 *****************************************************************************
 */
RwBool
ParticlesSaveCallback(RwBool testEnable)
{
    RwStream *stream = NULL;

    if( testEnable )
    {
        return ParticlesCurrentSystem == 0 ? TRUE : FALSE;
    }

    stream = RwStreamOpen(rwSTREAMFILENAME, 
                          rwSTREAMWRITE, RWSTRING("./particle.dff"));
    
    if( stream )
    {
        RpClumpStreamWrite(ParticleSystems[ParticlesCurrentSystem], stream);

        RwStreamClose(stream, NULL);
    }

    return TRUE;
}

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