/*****************************************************************************
*   Animation module - machine independent part. 			     *
*									     *
* Written by:  Haggay Dagan, Zvika Zilberman and Gershon Elber		     *
*							Ver 0.1, Feb. 1995.  *
*****************************************************************************/

#ifdef USE_VARARGS
#include <varargs.h>
#else
#include <stdarg.h>
#endif /* USE_VARARGS */
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include "irit_sm.h"
#include "iritprsr.h"
#include "attribut.h"
#include "allocate.h"
#include "geomat3d.h"
#include "iritgrap.h"
#include "animate.h"

static CagdRType *EvalCurveObject(IPObjectStruct *CrvObj, RealType t);
static int IsAnimation(IPObjectStruct *PObjs);
static void ExecuteAnimation(AnimationStruct *Anim, IPObjectStruct *PObjs);

/*****************************************************************************
* DESCRIPTION:								     M
*   Resets the slots of an animation structure.                              M
*									     *
* PARAMETERS:								     M
*   Anim:      The animation state to reset.				     M
*									     *
* RETURN VALUE:								     M
*   void								     M
*                                                                            *
* KEYWORDS:                                                                  M
*   AnimResetAnimStruct, animation                                           M
*****************************************************************************/
void AnimResetAnimStruct(AnimationStruct *Anim)
{
    Anim -> StartT = 0.0;
    Anim -> FinalT = 1.0;
    Anim -> Dt = 0.01;
    Anim -> RunTime = 0.0;
    Anim -> TwoWaysAnimation = FALSE;
    Anim -> SaveAnimation = FALSE;
    Anim -> BackToOrigin = FALSE;
    Anim -> NumOfRepeat = 1;
    Anim -> StopAnim = FALSE;
    Anim -> SingleStep = FALSE;
    Anim -> TextInterface = TRUE;
    Anim -> ExecEachStep = NULL;
    strcpy(Anim -> BaseFileName, ANIM_DEFAULT_ANIM_FILE_NAME);
}

/*****************************************************************************
* DESCRIPTION:								     M
*   Getting input parameters of animation from user using textual user       M
* interface.								     M
*									     *
* PARAMETERS:								     M
*   Anim:      The animation state to update.				     M
*									     *
* RETURN VALUE:								     M
*   void								     M
*                                                                            *
* KEYWORDS:                                                                  M
*   AnimGenAnimInfoText, animation                                           M
*****************************************************************************/
void AnimGetAnimInfoText(AnimationStruct *Anim)
{
    char Line[LINE_LEN];

#ifdef DOUBLE
#    define REAL_FRMT "%lf"
#else
#    define REAL_FRMT "%f"
#endif /* DOUBLE */
    do {
	printf("Start time [%f] : ", Anim -> StartT);
	gets(Line);
    }
    while (strlen(Line) > 0 &&
	   sscanf(Line, REAL_FRMT, &Anim -> StartT) != 1);

    do {
	printf("Final time [%f] : ", Anim -> FinalT);
	gets(Line);
    }
    while (strlen(Line) > 0 &&
	   sscanf(Line, REAL_FRMT, &Anim -> FinalT) != 1);

    do {
	printf("Interval of time [%f] : ", Anim -> Dt);
	gets(Line);
    }
    while (strlen(Line) > 0 &&
	   sscanf(Line, REAL_FRMT, &Anim -> Dt) != 1);

    printf("\nSpecial Commands (y/n) [n] : ");
    gets(Line);
    if (Line[0] != 'y' && Line[0] != 'Y') {
    	Anim -> TwoWaysAnimation = FALSE;
    	Anim -> SaveAnimation = FALSE;
	Anim -> BackToOrigin = FALSE;
    	Anim -> NumOfRepeat = 1;
    	return;
    }

    printf("Bounce Animation (y/n) [n] : ");
    gets(Line);
    if (Line[0] == 'y' || Line[0] == 'Y') {
      	Anim -> TwoWaysAnimation = TRUE;
	Anim -> BackToOrigin = FALSE;
    }
    else {
      	Anim -> TwoWaysAnimation = FALSE;
	printf("Back to origin (y/n) [n] : ");
	gets(Line);
	if (Line[0] == 'y' || Line[0] == 'Y')
	    Anim -> BackToOrigin = TRUE;
	else
	    Anim -> BackToOrigin = FALSE;
    }

    do {
	printf("Number of repetitions [%d] : ", Anim -> NumOfRepeat);
	gets(Line);
    }
    while (strlen(Line) > 0 &&
	   sscanf(Line, "%d", &Anim -> NumOfRepeat) != 1);
    Anim -> NumOfRepeat = MAX(Anim -> NumOfRepeat, 1);

    printf("Save iterations into data files (y/n) [n] : ");
    gets(Line);
    if (Line[0] == 'y' || Line[0] == 'Y') {
    	Anim -> SaveAnimation = TRUE; 
	do {
	    printf("Base name of data files : ");
	    gets(Line);
	}
	while (strlen(Line) > 0 &&
	       sscanf(Line, "%s", Anim -> BaseFileName) != 1 &&
	       strlen(Anim -> BaseFileName) <= 0);
    } 
    else
	Anim -> SaveAnimation = FALSE;
}

/*****************************************************************************
* DESCRIPTION:							     	     *
*   Evaluate curve in specified parameter value and project it to Euclidean  *
* space, if necessary.							     *
*									     *
* PARAMETERS:								     *
*   CrvObj:	a pointer to the curve's object.			     *
*   t:		the parameter of time to calculate the value of the curve.   * 
*									     *
* RETURN VALUE:								     *
*   CagdRType *: The value(s) of the curve at t.			     *
*****************************************************************************/
static CagdRType *EvalCurveObject(IPObjectStruct *CrvObj, RealType t)
{
    int i;
    CagdRType
	*Pt = CagdCrvEval(CrvObj -> U.Crvs, t);

    switch (CrvObj -> U.Crvs -> PType)  {
        case CAGD_PT_E1_TYPE:
        case CAGD_PT_E3_TYPE:
             break;
        case CAGD_PT_P1_TYPE:
	     Pt[1] /= Pt[0];
	     break;
        case CAGD_PT_P3_TYPE:
             for (i = 1; i <= 3; i++)
	          Pt[i] /= Pt[0];
	     break;
         default:
	     break;
    }
    return Pt;
}


/*****************************************************************************
* DESCRIPTION:								     *
*   Scan the given geometry for possible animation attributes.		     *
*									     *
* PARAMETERS:								     *
*   PObjs:      Objects to scan for animation attributes.		     *
*									     *
* RETURN VALUE:								     *
*   int:	TRUE if there are animation attributes. FALSE otherwise.     *
*****************************************************************************/
static int IsAnimation(IPObjectStruct *PObjs)
{
    IPObjectStruct *PObj;

    for (PObj = PObjs; PObj != NULL; PObj = PObj -> Pnext)
        if (AttrGetObjectObjAttrib(PObj, "animation") != NULL)
	    return TRUE; 

    return FALSE;   
}

/*****************************************************************************
* DESCRIPTION:								     M
*   Computes the time span for which the animation executes.		     M
*									     *
* PARAMETERS:								     M
*   Anim:	Animation structure to update.				     M
*   PObjs:      Objects to scan for animation attributes.		     M
*									     *
* RETURN VALUE:								     M
*   void								     M
*                                                                            *
* KEYWORDS:                                                                  M
*   AnimFindAnimationTime, animation                                         M
*****************************************************************************/
void AnimFindAnimationTime(AnimationStruct *Anim, IPObjectStruct *PObjs)
{
    IPObjectStruct *PObj, *ObjPtr, *AnimationP;
    RealType T1, T2, StartT, FinalT;

    StartT = INFINITY;
    FinalT = -INFINITY;
    for (PObj = PObjs; PObj != NULL; PObj = PObj -> Pnext) {
        if ((AnimationP = AttrGetObjectObjAttrib(PObj, "animation")) != NULL) {
	    int i = 0;

	    if (IP_IS_OLST_OBJ(AnimationP)) {
		while ((ObjPtr = ListObjectGet(AnimationP, i++)) != NULL) {
		    if (IP_IS_CRV_OBJ(ObjPtr)) {
			CagdCrvDomain(ObjPtr -> U.Crvs, &T1, &T2);
			StartT = MIN(StartT, T1);
			FinalT = MAX(FinalT, T2);
		    }
		}
	    }
	    else if (IP_IS_CRV_OBJ(AnimationP)) {
		CagdCrvDomain(AnimationP -> U.Crvs, &T1, &T2);
		StartT = MIN(StartT, T1);
		FinalT = MAX(FinalT, T2);
	    }
	}
    }

    if (StartT < INFINITY)
	Anim -> RunTime = Anim -> StartT = StartT;
    if (FinalT > -INFINITY)
	Anim -> FinalT = FinalT;
}

/*****************************************************************************
* DESCRIPTION:								     *
*   executes one time step of the animation. 				     *
*									     *
* PARAMETERS:								     *
*   Anim:	Animation structure.					     *
*   PObjs:	Objects to render.					     *
*									     *
* RETURN VALUE:								     *
*   void 								     *
*****************************************************************************/
static void ExecuteAnimation(AnimationStruct *Anim, IPObjectStruct *PObjs)
{
    IPObjectStruct *AnimationP, *PObj, *ObjPtr;
    MatrixType ObjMat, Mat;
    int i, NeedToMult;

    if (Anim -> TextInterface) {
	printf("\b\b\b\b\b\b\b%7.3f", Anim -> RunTime);
	fflush(stdout);
    }

    for ( ; PObjs != NULL && !Anim -> StopAnim; PObjs = PObjs -> Pnext) {
        MatGenUnitMat(ObjMat);
        
        if ((AnimationP = AttrGetObjAttrib(PObjs -> Attrs, "animation"))
								!= NULL) {
	    i = 0;
            while (IP_IS_OLST_OBJ(AnimationP) ?
		   (PObj = ListObjectGet(AnimationP, i++)) != NULL :
		   (PObj = (i++ == 0 ? AnimationP : NULL)) != NULL) {
		CagdRType *CurveResult, CurveRes, TMin, TMax, t;
		char
		    *Name = PObj -> Name; 

		AttrSetObjectIntAttrib(PObjs, "_isvisible", TRUE);

		NeedToMult = TRUE;
	  	switch (PObj -> ObjType) {
     	    	    case IP_OBJ_MATRIX:
 		        GEN_COPY(Mat, *PObj -> U.Mat, sizeof(MatrixType)); 
                        break;
            	    case IP_OBJ_CURVE :
		        CagdCrvDomain(PObj -> U.Crvs, &TMin, &TMax);

                        t = Anim -> RunTime;
                        if (t < TMin)
			    t = TMin;
                        else if (t > TMax)
			    t = TMax;

                        CurveResult = EvalCurveObject(PObj, t); 
                        CurveRes = CurveResult[1];
			if (strnicmp(Name, "scl", 3) == 0) {
		            if (stricmp(Name, "scl") == 0) 
			        MatGenMatUnifScale(CurveRes, Mat);
			    else if (stricmp(Name, "scl_x") == 0)
			        MatGenMatScale(CurveRes, 1, 1, Mat);
			    else if (stricmp(Name, "scl_y") == 0)
			        MatGenMatScale(1, CurveRes, 1, Mat);
			    else if (stricmp(Name, "scl_z") == 0)
			        MatGenMatScale(1, 1, CurveRes, Mat);
			}
		        else if (strnicmp(Name, "rot", 3) == 0) {
			    if (stricmp(Name, "rot_x") == 0)
				MatGenMatRotX1(-DEG2RAD(CurveRes), Mat );
			    else if (stricmp(Name, "rot_y") == 0)
				MatGenMatRotY1(-DEG2RAD(CurveRes), Mat);
			    else if (stricmp(Name, "rot_z") == 0)
				MatGenMatRotZ1(-DEG2RAD(CurveRes), Mat);
			}
			else if (strnicmp(Name, "mov", 3) == 0) {
			    if (stricmp(Name, "mov_x") == 0)
			        MatGenMatTrans(CurveRes, 0, 0, Mat);
			    else if (stricmp(Name, "mov_y") == 0)
			        MatGenMatTrans(0, CurveRes, 0, Mat);
			    else if (stricmp(Name, "mov_z") == 0)
			        MatGenMatTrans(0, 0, CurveRes, Mat);
			    else if (stricmp(Name, "mov_xyz") == 0)
				MatGenMatTrans(CurveResult[1],
					       CurveResult[2],
					       CurveResult[3],
					       Mat);
			}
			else if (stricmp(Name, "visible") == 0) { 
			    NeedToMult = FALSE;
		   	    if (CurveRes < 0)
				AttrSetObjectIntAttrib(PObjs,
						       "_isvisible", FALSE); 
			}
                        break;
                    default:
			NeedToMult = FALSE;
			fprintf(stderr,
				"Only matrices and curves are supported in animation, name = \"%s\"\n",
				Name);
		        break;
		}
		if (NeedToMult)
		    MatMultTwo4by4(ObjMat, ObjMat, Mat);
	    }
	}

      	ObjPtr = GenMatObject("transform", ObjMat, NULL);
      	AttrSetObjAttrib(&PObjs -> Attrs, "_animation_mat", ObjPtr, FALSE);

      	if (AnimCheckInterrupt(Anim))
	    break;
    }

    IGRedrawViewWindow();
}

/*****************************************************************************
* DESCRIPTION:								     M
*   Routine to run a sequence of objects through an animation according to   M
* animation attributes of matrices and curves that are attached to them.     M
* 									     *
* PARAMETERS:								     M
*   Anim:	Animation structure.					     M
*   PObjs:	Objects to render.					     M
*									     *
* RETURN VALUE:								     M
*   void.								     M
*									     *
* KEYWORDS:								     M
*   AnimDoAnimation, animation						     M
*****************************************************************************/
void AnimDoAnimation(AnimationStruct *Anim, IPObjectStruct *PObjs)
{ 
    int Loops;

    Anim -> StopAnim = FALSE;

    if (!IsAnimation(PObjs)) {	      /* Checking if there is any animation */ 
        fprintf(stderr, "No animation attributes were found.");
        return;
    }

    if (Anim -> TextInterface) {
	printf("Animate from %f to %f step %f\n",
	       Anim -> StartT, Anim -> FinalT, Anim -> Dt);
	printf("\nAnimation time:        ");
    }

    Anim -> _Count = 1;
    for (Loops = 1; Loops <= Anim -> NumOfRepeat; Loops++) {
        for (Anim -> RunTime = Anim -> StartT;
	     Anim -> RunTime <= Anim -> FinalT + EPSILON &&
	     !Anim -> StopAnim;
	     Anim -> RunTime += Anim -> Dt) {
    	    ExecuteAnimation(Anim, PObjs);
	    if (Loops == 1) {
		if (Anim -> SaveAnimation)
		    AnimSaveIterationsToFiles(Anim, PObjs);
		else if (Anim -> ExecEachStep != NULL) {
		    char Line[LINE_LEN];

		    sprintf(Line, "%s %d",
			    Anim -> ExecEachStep, Anim -> _Count++);
		    system(Line);
		}
	    }
	}

    	if (Anim -> TwoWaysAnimation) 
	    for (Anim -> RunTime = Anim -> FinalT;
		 Anim -> RunTime >= Anim -> StartT - EPSILON &&
		 !Anim -> StopAnim;
		 Anim -> RunTime -= Anim -> Dt)
 	    	ExecuteAnimation(Anim, PObjs);
    }

    if (Anim -> BackToOrigin && !APX_EQ(Anim -> RunTime, Anim -> StartT)) {
	Anim -> RunTime = Anim -> StartT;
	ExecuteAnimation(Anim, PObjs);
    }

    if (Anim -> TextInterface) {
	printf("\n\nAnimation is done.\n");
	fflush(stdout);
    }
}

/*****************************************************************************
* DESCRIPTION:								     M
*   Routine to exectue a single step the animation, at current time.         M
* 									     *
* PARAMETERS:								     M
*   Anim:	Animation structure.					     M
*   PObjs:	Objects to render.					     M
*									     *
* RETURN VALUE:								     M
*   void.								     M
*									     *
* KEYWORDS:								     M
*   AnimDoSingleStep, animation						     M
*****************************************************************************/
void AnimDoSingleStep(AnimationStruct *Anim, IPObjectStruct *PObjs)
{ 
    Anim -> StopAnim = FALSE;

    if (!IsAnimation(PObjs)) {	      /* Checking if there is any animation */ 
        fprintf(stderr, "No animation attributes were found.");
        return;
    }

    ExecuteAnimation(Anim, PObjs);
}

/*****************************************************************************
* DESCRIPTION:								     M
*   Saves one iteration of the animation sequence as IRIT data (*.dat).      M
*   The objects that are saved are those that are visibled on the current    M
* time frame as set via current animation_mat attribute.	             M
*									     *
* PARAMETERS:								     M
*   Anim:	Animation structure.					     M
*   PObjs:	Objects to render.					     M
*									     *
* RETURN VALUE:								     M
*   void								     M
*									     *
* KEYWORDS:								     M
*   AnimSaveIterationsToFiles, animation				     M
*****************************************************************************/
void AnimSaveIterationsToFiles(AnimationStruct *Anim, IPObjectStruct *PObjs)
{
    IPObjectStruct *MatObj, *PObj;
    int Handler;
    char FileName[LINE_LEN];

    sprintf(FileName, "%s%03d.dat", Anim -> BaseFileName, Anim -> _Count++);
    Handler = IritPrsrOpenDataFile(FileName, FALSE, TRUE);

    for (PObj = PObjs; PObj != NULL; PObj = PObj -> Pnext) {
	if ((MatObj = AttrGetObjectObjAttrib(PObj, "_animation_mat")) != NULL &&
            IP_IS_MAT_OBJ(MatObj) &&
	    AttrGetObjectIntAttrib(PObj, "_isvisible")) {
	    IPObjectStruct *CopyObj, *CopyTObj;

	    CopyObj = CopyObject(NULL, PObj, TRUE);
	    AttrFreeOneAttribute(&CopyObj -> Attrs, "animation");
	    CopyObj -> Pnext = NULL;
	    CopyTObj = GMTransformObject(CopyObj, *MatObj -> U.Mat);

	    IritPrsrPutObjectToHandler(Handler, CopyTObj);

	    IPFreeObject(CopyObj);
	    IPFreeObject(CopyTObj);
	}
    }

    PObj = GenMatObject("view_mat", IritPrsrViewMat, NULL);
    IritPrsrPutObjectToHandler(Handler, PObj);
    IPFreeObject(PObj);
    if (IGGlblViewMode == IG_VIEW_PERSPECTIVE) {
	PObj = GenMatObject("prsp_mat", IritPrsrPrspMat, NULL);
	IritPrsrPutObjectToHandler(Handler, PObj);
    	IPFreeObject(PObj);
    }

    IritPrsrCloseStream(Handler, TRUE); 
}
