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

/****************************************************************************
 *
 * clmpanim.c
 *
 * Copyright (C) 2001 Criterion Technologies.
 *
 * Original author: Alexandre Hadjadj
 * Reviewed by:
 *
 * Purpose: Handling animations.
 *
 ****************************************************************************/

#include "rwcore.h"
#include "rpworld.h"
#include "rpanim.h"
#include "rpbone.h"

#include "skeleton.h"
#include "menu.h"

#include "clmpview.h"
#include "clmpskin.h"
#include "clmpanim.h"

static RwBool AnimMenuActive = FALSE;

static RwBool JoinAnims = FALSE;

static RwInt32 RequiredAnimIndex = 0;
static RwInt32 NumAnimSequences = 0;

RwInt32 AtomicTotalRotationKeys = 0;
RwInt32 AtomicTotalTranslationKeys = 0;

RwBool ClumpHasFrameAnimation = FALSE;
RwBool ClumpIsBoneAndSkinSystem = FALSE;

RwBool FrameAnimOn = FALSE;

RwBool FinishedLastSequence = FALSE;

RwChar *AnimSequenceNames[NUMANIMSEQUENCESMAX];

RwBool AllAnimsTogether = FALSE;
RwReal FrameAnimSpeed = 1.0f;

RwInt32 CurrentAnimSequenceIndex = -1;


/*
 *****************************************************************************
 */
void
AnimRestart(RpClump *clump)
{
    if( ClumpHasFrameAnimation )
    {
        RpAnimClumpStopSequence(clump,
            AnimSequenceNames[CurrentAnimSequenceIndex]);

        RpAnimClumpStartSequence(clump,
            AnimSequenceNames[0], TRUE, 0, 0.0f);

        if (ClumpIsBoneAndSkinSystem)
        {
            /*
             * This is to be called every time we add time to the sequence.
             * It changes the vertices of the skin atomic based on the bones
             * (each bone specifies a frame, a matrix and the indices of
             * the attached vertices)...
             */
            RpBoneClumpSkinUpdate(clump);
        }

        RwFrameUpdateObjects(RpClumpGetFrame(clump));
    }

    return;
}


/*
 *****************************************************************************
 */
static RwBool
UniqueSequenceName(RwChar *name)
{
    RwInt32 i;

    for( i = 0; i < NumAnimSequences; i++ )
    {
        if( !rwstrcmp(AnimSequenceNames[i], name) )
        {
            return FALSE;
        }
    }

    return TRUE;
}


/*
 *****************************************************************************
 */
static RpAnimSequence*
FrameAnimGetSequenceNames(RpAnimSequence *animSequence, void *data)
{
    RwChar *name;

    if( NumAnimSequences >= NUMANIMSEQUENCESMAX )
    {
        return (RpAnimSequence *)NULL;
    }

    name = RpAnimSequenceGetName(animSequence);

    if( UniqueSequenceName(name) )
    {
        AnimSequenceNames[NumAnimSequences] = name;
        NumAnimSequences++;
    }

    return animSequence;
}


/*
 *****************************************************************************
 */
static RwFrame *
FrameGetSequenceNames(RwFrame *frame, void *data)
{
    RpAnimFrameForAllSequences(frame, FrameAnimGetSequenceNames, NULL);

    RwFrameForAllChildren(frame, FrameGetSequenceNames, NULL);

    return frame;
}


/*
 *****************************************************************************
 */
void
ClumpIncrementFrameAnimationSeq(RpClump *clump, RwReal inc)
{
    RpAnimClumpAddTime(clump, inc);
    if( ClumpIsBoneAndSkinSystem )
    {
        /*
         * This is to be called every time we add time to the sequence.
         * It changes the vertices of the skin atomic based on the bones
         * (each bone specifies a frame, a matrix and the indices of
         * the attached vertices)...
         */
        RpBoneClumpSkinUpdate(clump);
    }

    return;
}


/*
 *****************************************************************************
 */
static void
ClumpAnimGetSequenceNames(RpClump *clump)
{
    RwFrame *frame;

    frame = RpClumpGetFrame(clump);

    RpAnimFrameForAllSequences(frame, FrameAnimGetSequenceNames, NULL);

    RwFrameForAllChildren(frame, FrameGetSequenceNames, NULL);

    return;
}


/*
 *****************************************************************************
 */
RwFrame*
FrameCountKeys(RwFrame *frame, void *data)
{
    RpAnimSequence *seq;
    RwInt32 i;
    RwBool recurse;

    if (data)
    {
        recurse = TRUE;
    }
    else
    {
        recurse = FALSE;
    }

    AtomicTotalRotationKeys = 0;
    AtomicTotalTranslationKeys = 0;

    for( i = 0; i < NumAnimSequences; i++ )
    {
        seq = RpAnimFrameGetRotationSequence(frame, AnimSequenceNames[i]);

        if (seq)
        {
            AtomicTotalRotationKeys += RpAnimSequenceGetNumKeys(seq);
        }
        seq = RpAnimFrameGetTranslationSequence(frame, AnimSequenceNames[i]);

        if (seq)
        {
            AtomicTotalTranslationKeys += RpAnimSequenceGetNumKeys (seq);
        }
    }

    if (recurse)
    {
        ClumpStats.totalRotationKeys += AtomicTotalRotationKeys;
        ClumpStats.totalTranslationKeys += AtomicTotalTranslationKeys;
        RwFrameForAllChildren (frame, FrameCountKeys, data);
    }

    return frame;
}


/*
 *****************************************************************************
 */
static RpClump*
ClumpAnimSequenceCallback(RpClump *clump, 
                          RpAnimClumpSequence *animClumpSeq,
                          RwReal pos, void *data)
{
    if( !AllAnimsTogether )
    {
        if( JoinAnims )
        {
            CurrentAnimSequenceIndex++;
            if( CurrentAnimSequenceIndex >= NumAnimSequences )
            {
                FinishedLastSequence = TRUE;
                CurrentAnimSequenceIndex = 0;
            }
        }
        RpAnimClumpStartSequence(clump,
            AnimSequenceNames[CurrentAnimSequenceIndex], TRUE, 0, pos);
    }
    else
    {
        RpAnimClumpStartSequence(clump,
            RpAnimClumpSequenceGetName(animClumpSeq), TRUE, 0, pos);
    }

    RequiredAnimIndex = CurrentAnimSequenceIndex;

    return clump;
}


/*
 *****************************************************************************
 */
void
AnimUpdateClump(RwReal delta)
{
    if( FrameAnimOn )
    {
        RwReal inc;

        if( RequiredAnimIndex != CurrentAnimSequenceIndex )
        {
            RpAnimClumpStopSequence(Clump,
                AnimSequenceNames[CurrentAnimSequenceIndex]);

            CurrentAnimSequenceIndex = RequiredAnimIndex;

            RpAnimClumpStartSequence (Clump,
                AnimSequenceNames[CurrentAnimSequenceIndex], TRUE, 0, 0.0f);
        }

        /*
         * Frame animation
         */
        inc = delta * FrameAnimSpeed;

        ClumpIncrementFrameAnimationSeq(Clump, inc);
    }

    return;
}


/*
 *****************************************************************************
 */
static RwBool
FrameAnimOnCB(RwBool justCheck)
{
    if( justCheck )
    {
        if( ClumpHasSkinAnimation || !ClumpLoaded || !ClumpHasFrameAnimation )
        {
            FrameAnimOn = FALSE;
            return FALSE;
        }
        return TRUE;
    }
    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
AllAnimsTogetherCB(RwBool justCheck)
{
    RwInt32 i;

    if( justCheck )
    {
        if( !FrameAnimOnCB (justCheck) || NumAnimSequences == 1 )
        {
            return FALSE;
        }
        return TRUE;
    }

    if( AllAnimsTogether )
    {
        RpAnimClumpStopSequence(Clump,
            AnimSequenceNames[CurrentAnimSequenceIndex]);

        for( i = 0; i < NumAnimSequences; i++ )
        {
            RpAnimClumpStartSequence (Clump,
                AnimSequenceNames[i], TRUE, 0, 0.0f);
        }
    }
    else
    {
        for( i = 0; i < NumAnimSequences; i++ )
        {
            RpAnimClumpStopSequence(Clump, AnimSequenceNames[i]);
        }

        RpAnimClumpStartSequence(Clump,
            AnimSequenceNames[CurrentAnimSequenceIndex], TRUE, 0, 0.0f);
    }

    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
FrameAnimSpeedCB(RwBool justCheck)
{
    if( justCheck )
    {
        if( !FrameAnimOnCB(justCheck) )
        {
            return FALSE;
        }
        return TRUE;
    }

    FrameAnimOn = TRUE;

    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
RequiredAnimIndexCB(RwBool justCheck)
{
    if( justCheck )
    {
        if( !ClumpLoaded || !ClumpHasFrameAnimation || AllAnimsTogether )
        {
            return FALSE;
        }
        return TRUE;
    }

    FrameAnimOn = TRUE;

    return TRUE;
}

/*
 *****************************************************************************
 */
static RwBool
JoinAnimsCB(RwBool justCheck)
{
    if( justCheck )
    {
        if( !FrameAnimOnCB (justCheck) || NumAnimSequences == 1 )
        {
            return FALSE;
        }
        return TRUE;
    }
    return TRUE;
}


/*
 *****************************************************************************
 */
static void
AnimMenuSetup(void)
{
    static RwChar FrameAnimOnLabel[] = RWSTRING ("Frame Animation_A");
    static RwChar FrameAnimSpeedLabel[] = RWSTRING ("Frame Anim Speed");
    static RwChar AllAnimsTogetherLabel[] = RWSTRING ("Mix All Anims_U");
    static RwChar JoinAnimsLabel[] = RWSTRING ("Concatenate Anims_J");
    static RwChar RequiredAnimIndexLabel[] = RWSTRING ("Anim Sequence");
    static RwChar RequiredAnimDisabledStr[] = RWSTRING ("-");
    static RwChar *RequiredAnimDisabled = &RequiredAnimDisabledStr[0];

    if( !AnimMenuActive )
    {
        MenuAddEntryBool (FrameAnimOnLabel, &FrameAnimOn, FrameAnimOnCB);
        MenuAddEntryReal (FrameAnimSpeedLabel,
            &FrameAnimSpeed, FrameAnimSpeedCB, 0.1f, 10.0f, 1.0f);
        MenuAddEntryBool (AllAnimsTogetherLabel,
            &AllAnimsTogether, AllAnimsTogetherCB);
        MenuAddEntryBool (JoinAnimsLabel, &JoinAnims, JoinAnimsCB);
        MenuAddEntryInt (RequiredAnimIndexLabel, &RequiredAnimIndex,
            RequiredAnimIndexCB, 0, 0, 1, (const RwChar**)&RequiredAnimDisabled);

        AnimMenuActive = TRUE;
    }
    return;
}


/*
 *****************************************************************************
 */
static void
AnimMenuDestroy( void )
{
    MenuRemoveEntry(&FrameAnimOn);
    MenuRemoveEntry(&FrameAnimSpeed);
    MenuRemoveEntry(&AllAnimsTogether);
    MenuRemoveEntry(&JoinAnims);
    MenuRemoveEntry(&RequiredAnimIndex);

    AnimMenuActive = FALSE;

    return;
}


/*
 *****************************************************************************
 */
RwBool
AnimClumpInitialize(RpClump *clump)
{
    /*
     * Detect bones and skin system in order to run the animation.
     * Also, if this is not done, the bones and skin system will
     * not be destroyed properly at the end...
     */
    if( RpBoneClumpInitialize(clump) )
    {
        ClumpIsBoneAndSkinSystem = TRUE;
    }

    /*
     * Determine if the clump has any frame animations....
     */
    NumAnimSequences = 0;
    RequiredAnimIndex = 0;

    ClumpHasFrameAnimation = FALSE;

    ClumpAnimGetSequenceNames(clump);

    if( NumAnimSequences )
    {
        RwInt32 i;

        for( i = 0; i < NumAnimSequences; i++ )
        {
            RpAnimClumpAddSequence(clump,
                AnimSequenceNames[i], ClumpAnimSequenceCallback, NULL);
        }

        RpAnimClumpStartSequence(clump,
            AnimSequenceNames[0], TRUE, 0, 0.0f);

        CurrentAnimSequenceIndex = 0;

        ClumpHasFrameAnimation = TRUE;

        AnimMenuSetup();

        MenuSetRangeInt(&RequiredAnimIndex,
            0, NumAnimSequences - 1, 1, (const RwChar**)&AnimSequenceNames[0]);
    }
    else
    {
        AnimMenuDestroy();
    }

    /*
     * Gather some more statistics about the new clump...
     */
    ClumpStats.totalRotationKeys = 0;
    ClumpStats.totalTranslationKeys = 0;
    FrameCountKeys(RpClumpGetFrame(Clump), (void *)TRUE);

    if( ClumpHasFrameAnimation )
    {
        FrameAnimOn = TRUE;
    }
    else
    {
        FrameAnimOn = FALSE;
    }

    AllAnimsTogether = FALSE;
    JoinAnims = TRUE;

    FrameAnimSpeed = 1.0f;

    return TRUE;
}

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