/**********************************************************************
 *
 * File :     animate.c
 *
 * Abstract : Animation handler. This module is a general animation handler
 *            that parses the .rwv files produced by the 3DS convertor
 *            and attaches the frame data to the user data field of the
 *            required clump(s).
 *
 **********************************************************************
 *
 * 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 ---*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "rwlib.h"
#include "animate.h"

/*--- Type Definitions ---*/

/* TyFrame: This structure is attached to the user data field of each clump
 * that is being animated. It contains an RwMatrix4d for each frame of the 
 * animation.
 */

typedef struct {
    int frames;             /* Total number of frames of animation */
    int current;            /* Current frame being animated */
    RwMatrix4d **frame;     /* Array of matrices representing the frames */
} TyFrame;


/* The .rwv file contains 2 different tokens:
 * 1. Frame <n> - defines the frame number for all subsequent tokens
 * 2. Transform <tag> <float[12]> - defines the matrix representing the frame
 *                                  and the clump that it will be attached to.
 */

/* EnToken: Enumerated type defining the available token types */

typedef enum {
    TK_NULL,
    TK_FRAME,
    TK_TRANSFORM,
} EnToken;

/* TyTokTransform: contains the data for a Transform token */

typedef struct {
    RwInt32     tag;
    float       m[12];
} TyTokTransform;

/* TyToken: A single global variable of this type is declared and 
 * used when parsing the .rwv file.
 */
typedef struct {
  union {
    RwInt32        frame;
    TyTokTransform transform;
  } u;
} TyToken;

static TyToken tok;

/************************************************************************
 *
 *      Function:       ReadInt()
 *                      
 *      Description:    Read an integer value from the input stream
 *
 *      Parameters:     stream - inupt file stream
 *                      val - location to store the value read
 *
 *      Return Value:   0 on failure, any other value for success
 *
 ************************************************************************/
static int
ReadInt(FILE * stream, RwInt32 *val)
{
    char buffer[256];

    /* Read a single string from the input stream */
    while (fscanf(stream, "%250s", buffer) == 1)
    {
        if (buffer[0] == '#')
        {
            /* Its a comment. Read to the end of line and ignore */ 
            fgets(buffer, sizeof(buffer), stream);
        }
        else
        {
            /* Convert the string into an int value */
            return(sscanf(buffer, "%d", val));
        }
    }
    /* Error reading stream */
    return(0);
}
/************************************************************************
 *
 *      Function:       ReadFloat()
 *                      
 *      Description:    Read a floating point value from the input stream
 *
 *      Parameters:     stream - inupt file stream
 *                      val - location to store the value read
 *
 *      Return Value:   0 on failure, any other value for success
 *
 ************************************************************************/
static int
ReadFloat(FILE *stream, float *val)
{
    char buffer[256];

    /* Read a single string from the input stream */
    while (fscanf(stream, "%250s", buffer) == 1)
    {
        if (buffer[0] == '#')
        {
            /* Its a comment. Read to the end of line and ignore */ 
            fgets(buffer, sizeof(buffer), stream);
        }
        else
        {
            /* Convert the string into a float value */
            return(sscanf(buffer, "%f", val));
        }
    }
    /* Error reading stream */
    return(0);
}
/************************************************************************
 *
 *      Function:       ReadString()
 *                      
 *      Description:    Read a string from the input stream
 *
 *      Parameters:     stream - inupt file stream
 *                      string - location to store the value read
 *
 *      Return Value:   0 on failure, any other value for success
 *
 ************************************************************************/
static int
ReadString(FILE * stream, char *string)
{
    char buffer[256];

   /* Read a single string from the input stream */
   while (fscanf(stream, "%250s", buffer) == 1)
	{
	    if (buffer[0] == '#')
	    {
            /* Its a comment. Read to the end of line and ignore */ 
		    fgets(buffer, sizeof(buffer), stream);
	    }
	    else
	    {
            /* copy the string into the variable supplied */
		    strcpy(string, buffer);
            return(1);
	    }
	}
    return(0);
}

/************************************************************************
 *
 *      Function:       ReadToken()
 *                      
 *      Description:    Read a token from the input stream
 *
 *      Parameters:     stream - inupt file stream
 *
 *      Return Value:   returns the token type read. The global variable
 *                      tok contains the token data
 *
 ************************************************************************/
static EnToken 
ReadToken(FILE *stream)
{
    char string[128];

    /* Read the next string from the input stream */
    ReadString(stream, string);

    strlwr(string);

    /* Is it a frame keyword */
    if (!strcmp(string, "frame"))
    {
        /* Yes. Read the frame number */
        if (ReadInt(stream, &tok.u.frame))
            return(TK_FRAME);
    }

    /* Is it a transform keyword */
    else if (!strcmp(string, "transform"))
    {
        /* Yes. Read the clump tag and the transform data */
        if (ReadInt(stream, &tok.u.transform.tag) &&
            ReadFloat(stream, &tok.u.transform.m[0]) &&
            ReadFloat(stream, &tok.u.transform.m[1]) &&
            ReadFloat(stream, &tok.u.transform.m[2]) &&
            ReadFloat(stream, &tok.u.transform.m[3]) &&
            ReadFloat(stream, &tok.u.transform.m[4]) &&
            ReadFloat(stream, &tok.u.transform.m[5]) &&
            ReadFloat(stream, &tok.u.transform.m[6]) &&
            ReadFloat(stream, &tok.u.transform.m[7]) &&
            ReadFloat(stream, &tok.u.transform.m[8]) &&
            ReadFloat(stream, &tok.u.transform.m[9]) &&
            ReadFloat(stream, &tok.u.transform.m[10]) &&
            ReadFloat(stream, &tok.u.transform.m[11]))
            return(TK_TRANSFORM);
    }

    /* Couldn't read a valid toke so return error */
    return (TK_NULL);
}

/************************************************************************
 *
 *      Function:       LoadAnimation()
 *                      
 *      Description:    Load the animation from the specified .rwv file
 *                      and attach it to the clump.
 *
 *      Parameters:     clump - the clump to attach the animation to. This
 *                      will usually be the parent clump of a hierarchical
 *                      model.
 *                      filename - the name of the .rwv file to parse
 *
 *      Return Value:   returns TRUE on success otherwise FALSE
 *
 ************************************************************************/
int 
LoadAnimation(RwClump *clump, char *filename)
{
    int current_frame;
    RwClump *active_clump;
    TyFrame *frame;
    FILE *fp;

    fp = fopen(filename, "r");
    if (!fp) 
    {
        /* Can't open file - error */
       return FALSE;
    }

    /* Keep on reading until an error occurs or we reach the end of the file */

    for(;;)
    {
        /* Read a token fro the file */
        switch (ReadToken(fp))
        {
            case TK_FRAME:
                /* Its a frame so define the current frame of animation */
                current_frame = tok.u.frame;
                break;
            case TK_TRANSFORM:
                /* It's a transform. Find the clump to attach the transform to */
                active_clump = RwFindTaggedClump(clump, tok.u.transform.tag);
                if (active_clump)
                {
                    /* Clump found. Allocate space for the frame */
                    frame = RwGetClumpData(active_clump);
                    if (!frame)
                    {
                            frame = malloc(sizeof(TyFrame));
                            frame->frames = 1;
                            frame->current = 0;
                            frame->frame = malloc(sizeof(RwMatrix4d *));
                            RwSetClumpData(active_clump, frame);
                    }
                    else
                    {
                            frame->frames = current_frame + 1;
                            frame->frame = realloc(frame->frame, sizeof(RwMatrix4d *) * (current_frame + 1));
                    }
                    /* Create a RenderWare matrix and initialise it with the transform values */
                    frame->frame[current_frame] = RwCreateMatrix();
                    RwSetMatrixElement(frame->frame[current_frame], 0, 0, FL2REAL(tok.u.transform.m[0]));
                    RwSetMatrixElement(frame->frame[current_frame], 0, 1, FL2REAL(tok.u.transform.m[1]));
                    RwSetMatrixElement(frame->frame[current_frame], 0, 2, FL2REAL(tok.u.transform.m[2]));
                    RwSetMatrixElement(frame->frame[current_frame], 0, 3, CREAL(0.0));
                    RwSetMatrixElement(frame->frame[current_frame], 1, 0, FL2REAL(tok.u.transform.m[3]));
                    RwSetMatrixElement(frame->frame[current_frame], 1, 1, FL2REAL(tok.u.transform.m[4]));
                    RwSetMatrixElement(frame->frame[current_frame], 1, 2, FL2REAL(tok.u.transform.m[5]));
                    RwSetMatrixElement(frame->frame[current_frame], 1, 3, CREAL(0.0));
                    RwSetMatrixElement(frame->frame[current_frame], 2, 0, FL2REAL(tok.u.transform.m[6]));
                    RwSetMatrixElement(frame->frame[current_frame], 2, 1, FL2REAL(tok.u.transform.m[7]));
                    RwSetMatrixElement(frame->frame[current_frame], 2, 2, FL2REAL(tok.u.transform.m[8]));
                    RwSetMatrixElement(frame->frame[current_frame], 2, 3, CREAL(0.0));
                    RwSetMatrixElement(frame->frame[current_frame], 3, 0, FL2REAL(tok.u.transform.m[9]));
                    RwSetMatrixElement(frame->frame[current_frame], 3, 1, FL2REAL(tok.u.transform.m[10]));
                    RwSetMatrixElement(frame->frame[current_frame], 3, 2, FL2REAL(tok.u.transform.m[11]));
                    RwSetMatrixElement(frame->frame[current_frame], 3, 3, CREAL(1.0));
                }
                break;
            default:
                /* Not a valid token. Its probably the end of the file so just 
                   shutup and return success */
                fclose(fp);
                return TRUE;
         }
    }
}

/************************************************************************
 *
 *      Function:       DestroyCallback()
 *                      
 *      Description:    Function called for each clump in the hierarchy to 
 *                      return all frame data back to the system.
 *
 *      Parameters:     clump - The clump to remove the frame data from.
 *
 *      Return Value:   returns the clump passed in 
 *                      (required for RwForAllClumps...)
 *
 ************************************************************************/
RwClump *DestroyCallback(RwClump *clump)
{
    TyFrame *frame;
    int i;

    /* Retrieve the frame data for the clump and free it up if it exists */
    frame = RwGetClumpData(clump);
    if (frame)
    {
        if (frame->frame)
        {
            for (i = 0; i < frame->frames; i++)
            {
                /* Destroy the RenderWare matrix objects */
                RwDestroyMatrix(frame->frame[i]);
            }
            free(frame->frame);
        }
        free(frame);
    }
	return(clump);
}

/************************************************************************
 *
 *      Function:       DestroyAnimation()
 *                      
 *      Description:    Detach the animation data from a clump and
 *                      return all allocated memory back to the system
 *
 *      Parameters:     clump - The clump to remove the frame data from.
 *
 *      Return Value:   None
 *
 ************************************************************************/
void DestroyAnimation(RwClump *clump)
{
	RwForAllClumpsInHierarchy(clump, DestroyCallback);
}

/************************************************************************
 *
 *      Function:       AnimateClump()
 *                      
 *      Description:    Animate the clump. For this example we just advance
 *                      to the next frame of animation and cycle back to the
 *                      start once we reach the end.
 *
 *      Parameters:     clump - The clump to advance.
 *
 *      Return Value:   the clump passed in. This allows the function to be
 *                      called by RwForAllClumps... functions
 *
 ************************************************************************/
RwClump *AnimateClump(RwClump *clump)
{
    TyFrame *clump_frame;
    int current_frame;

    /* Get the frame data */
    clump_frame = (TyFrame *)RwGetClumpData(clump);

    if (clump_frame)
    {
       /* We've got the frame data so advance the clump to the next frame */
       current_frame = clump_frame->current % clump_frame->frames;
       
       /* And apply the transform to the clump */
       RwTransformClump(clump, clump_frame->frame[current_frame], rwREPLACE);
       clump_frame->current++;
    }
    return(clump);
}

