/*
	Copyright (c) 2024 PTC Inc. and/or Its Subsidiary Companies. All Rights Reserved.
*/




/*--------------------------------------------------------------------*\
    Pro/TOOLKIT includes
\*--------------------------------------------------------------------*/
#include "ProToolkit.h"
#include "ProMenu.h"
#include "ProSelection.h"
#include "ProModelitem.h"
#include "ProMdl.h"
#include "ProArray.h"
#include "ProUtil.h"
#include "ProAnimate.h"
#include "ProTKRunTime.h"

/*--------------------------------------------------------------------*\
    Pro/DEVELOP includes
\*--------------------------------------------------------------------*/


/*--------------------------------------------------------------------*\
    C System includes
\*--------------------------------------------------------------------*/
#include <math.h>


/*--------------------------------------------------------------------*\
    Application includes
\*--------------------------------------------------------------------*/
#include "TestError.h"
#include "TestAnimation.h"
#include "UtilMessage.h"
#include "UtilString.h"
#include "UtilCollect.h"

/*--------------------------------------------------------------------*\
    Macros
\*--------------------------------------------------------------------*/
#define ANI_OBJECT( i, f )  (ani_obj_seq[i].anims[f])
#define PI		    (3.1451)
#define MAX_FRAMES_NUM	    30


/*--------------------------------------------------------------------*\
    Data types
\*--------------------------------------------------------------------*/
/*---------------------------------------------------------------*\
    This structure contains information about animation objects 
    associated with the model. Macro ANI_OBJECT(i,f) is used to 
    get animation object handle (ProAnimObj) of the model i in
    the frame f.
\*---------------------------------------------------------------*/
typedef struct tag_AniObjectInstance
	{
	    ProMdl	    model;	/* Handle to the animated model */
	    ProAsmcomppath  comp_path;	/* Component path of the object */
	    ProAnimObj	    anims[ MAX_FRAMES_NUM ]; /* Array of the ani objs */
	} AniObjectInstance;


/*--------------------------------------------------------------------*\
    Application global/external data
\*--------------------------------------------------------------------*/
static AniObjectInstance*   ani_obj_seq = NULL; /* Array of  of ani objs */
static int		    n_objects = 0;  /* Current number of ani objs */
#if 0
static ProAnimFrame	    frames[ MAX_FRAMES_NUM ]; /* Animation frames */
#endif
static ProAnimFrame	    *frames; /* Animation frames */
static int		    n_frames; /* Current number of frames */


/*---------------------------------------------------------------------*\
    Functions declaration
\*---------------------------------------------------------------------*/
ProError    ProUtilSelectedMdlGet( ProSelection* , ProMdl* );
ProError    ProUtilAniObjectSelect( char option[], ProSelection**, 
				    AniObjectInstance* );
ProBoolean  ProUtilMdlEqual( ProMdl, ProMdl );
int    ProUtilAniObjectFind( ProArray, AniObjectInstance );
ProError    ProUtilAniObjectInstanceInit( ProSelection**, AniObjectInstance* );
ProError    ProTestBatchAnimAct( ProAnimFrame, int, ProAppData );
void	    ProUtilRotX( double angle, ProMatrix mx );



/*---------------------------------------------------------------------*\
    Function:	ProTestAnimation()
    Purpose:	Top-level function. Initialize some data, 
		create and run ANIMATION menu, then free used resources.
    Returns:	0 - success; -1 - error. Now ignored.
\*---------------------------------------------------------------------*/
int ProTestAnimation( void* p_dummy, int int_dummy )
{
    ProError	    status;
    int		    menu_id;    /* The identifier of the created menu */
    int		    action;


    /* Init the array of animation objects */
    n_objects = 0;
    status = ProArrayAlloc( 0, sizeof(AniObjectInstance), 1, (ProArray *)&ani_obj_seq );
    TEST_CALL_REPORT( "ProArrayAlloc()", "ProTestAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return -1;

    /* Init the array of animation frames */
    n_frames = 0;
    status = ProArrayAlloc( 0, sizeof(ProAnimFrame), 1, (ProArray *)&frames );
    TEST_CALL_REPORT( "ProArrayAlloc()", "ProTestAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return -1;
    status = ProArraySizeSet( (ProArray *)&frames, MAX_FRAMES_NUM );
    TEST_CALL_REPORT( "ProArraySizeSet()", "ProTestAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return -1;

    /*-----------------------------*\
	Create new menu ANIMATION
    \*-----------------------------*/

    /* Load base menu from file */
    status = ProMenuFileRegister((char *)"Animation", (char *)"tkanim.mnu",
               &menu_id );
    TEST_CALL_REPORT( "ProMenuFileRegister()", "ProTestAnimation()", 
			status, status != PRO_TK_NO_ERROR );

    /* Define menu buttons */
    ProMenubuttonActionSet((char *)"Animation", (char *)"Create", 
       (ProMenubuttonAction)ProTestAnimframeCreate, NULL, 0 );
    ProMenubuttonActionSet((char *)"Animation", (char *)"Delete", 
       (ProMenubuttonAction)ProTestAnimframeDelete, NULL, 0 );
    ProMenubuttonActionSet((char *)"Animation", (char *)"AddObject", 
       (ProMenubuttonAction)ProTestAniObjectAdd, NULL, 0 );
    ProMenubuttonActionSet((char *)"Animation", (char *)"DeleteObject",
       (ProMenubuttonAction)ProTestSelAniObjectDelete, NULL, 0 );
    ProMenubuttonActionSet((char *)"Animation", (char *)"PlaySingle",
       (ProMenubuttonAction)ProTestSingleAnimation, NULL, 0 );
    ProMenubuttonActionSet((char *)"Animation", (char *)"PlayBatch",
       (ProMenubuttonAction)ProTestBatchAnimation, NULL, 0 );
    ProMenubuttonActionSet((char *)"Animation", (char *)"Animation", 
       (ProMenubuttonAction)ProMenuDelete, NULL, 0 );


    /*-----------------------*\
	Run menu ANIMATION
    \*-----------------------*/

    status = ProMenuCreate( PROMENUTYPE_MAIN, (char *)"Animation", &menu_id );
    TEST_CALL_REPORT( "ProMenuCreate()", "ProTestAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status == PRO_TK_NO_ERROR )
    {
	status = ProMenuProcess( (char *)"Animation", &action );
	TEST_CALL_REPORT( "ProMenuProcess()", "ProTestAnimation()", 
			    status, status != PRO_TK_NO_ERROR );
    }

    status = ProArrayFree( (ProArray *)&ani_obj_seq );
    TEST_CALL_REPORT( "ProArrayFree()", "ProTestAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    n_objects = 0;
    status = ProArrayFree( (ProArray *)&frames );
    TEST_CALL_REPORT( "ProArrayFree()", "ProTestAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    n_frames = 0;

    return 0;
}



/*---------------------------------------------------------------------*\
    Function:	ProTestAnimframeCreate()
    Purpose:	On-button function. Initialize frames sequence structure.
    Returns:	0 - success; -1 - error. Now ignored.
\*---------------------------------------------------------------------*/
int ProTestAnimframeCreate( void* p_dummy, int int_dummy )
{
    ProError	    status;
    int		    i;
    ProMatrix	    m_frame_view = {
				    {1.0, 0.0, 0.0, 0.0},
				    {0.0, 1.0, 0.0, 0.0},
				    {0.0, 0.0, 1.0, 0.0},
				    {0.0, 0.0, 0.0, 1.0} 
				   };
    ProVector	    v_x = {1.0, 0.0, 0.0};
    ProVector	    v_y = {0.0, 1.0, 0.0};
    ProVector	    v_z = {0.0, 0.0, 1.0};
    ProPoint3d	    origin = {0.0, 0.0, 1.0};


    /* Check animation frames */
    if( n_frames > 0 )
    {
	ProUtilMsgPrint( "gen", "TEST %0s", "Animation already exists" );
	return -1;
    }

    /* Get a number of frames */
    ProUtilMsgPrint( "gen", "TEST %0s", "Enter a number of frames: " );
    if( !ProUtilIntGet( NULL, NULL, &n_frames ) )
	n_frames = MAX_FRAMES_NUM;
    if( n_frames > MAX_FRAMES_NUM )
	n_frames = MAX_FRAMES_NUM;


    /* Init the frame view matrix */
    status = ProMatrixInit( v_x, v_y, v_z, origin, m_frame_view );
    TEST_CALL_REPORT( "ProMatrixInit()", "ProTestAnimframeCreate()", 
			status, status != PRO_TK_NO_ERROR );

    /* Initialize the frames */
    for( i=0; i<n_frames; i++ )
    {
	status = ProAnimframeCreate( m_frame_view, &(frames[i]) );
	TEST_CALL_REPORT( "ProAnimframeCreate()", "ProTestAnimframeCreate()", 
			    status, status != PRO_TK_NO_ERROR );
	if( status != PRO_TK_NO_ERROR )
	    return -1;
    }


    return 0; /* Upon success */
}



/*---------------------------------------------------------------------*\
    Function:	ProTestAnimframeDelete()
    Purpose:	On-button function. Remove animation objects from the frames, 
		delete animation frames and free an array of the handles of
		the frames.
    Returns:	0 - success; -1 - error. Now ignored.
    Note:	Arguments are ignored.
\*---------------------------------------------------------------------*/
int ProTestAnimframeDelete( void* p_dummy, int int_dummy )
{
    ProError	    status;
    int		    i;


    /* Check animation frames */
    if( n_frames < 1 )
	return -1;

    /* Delete the animation objects */
    for( i=n_objects-1; i>=0; i-- )
	ProTestAniObjectDelete( i );

    /* Delete animation frames */
    for( i=0; i<n_frames; i++ )
    {
	status = ProAnimframeDelete( frames[i] );
	TEST_CALL_REPORT( "ProAnimframeDelete()", "ProTestAnimframeDelete()", 
			    status, status != PRO_TK_NO_ERROR );
    }

    n_frames = 0;


    return 0;
}



/*---------------------------------------------------------------------*\
    Function:	ProTestAniObjectAdd()
    Purpose:	On-button function. Select an animation object and add it 
		to the animation frames.
    Returns:	0 - success; -1 - error. Now ignored.
    Note:	Arguments are ignored.
\*---------------------------------------------------------------------*/
int ProTestAniObjectAdd( void* p_dummy, int int_dummy )
{
    ProError		status;
    ProSelection*	p_selection;
    AniObjectInstance	ani_object;	/* Created animation object */
    int			i_frame;
    double		angle;
    double		add_angle;
    ProMode             mode;
    ProMatrix		m_transform = {
					{1.0, 0.0, 0.0, 0.0},
					{0.0, 1.0, 0.0, 0.0},
					{0.0, 0.0, 1.0, 0.0},
					{0.0, 0.0, 0.0, 1.0} 
				      };


    /* Check animation frames */
    if( n_frames < 1 )
    {
	ProUtilMsgPrint( "gen", "TEST %0s", "First, create an animation" );
	return -1;
    }

    /* Select ONE element to animate */
    status = ProUtilAniObjectSelect( (char *)"prt_or_asm", &p_selection, &ani_object );
    if( status == PRO_TK_USER_ABORT )
	return -1;
    if( status != PRO_TK_E_NOT_FOUND )
    {
	/* User have selected already animated object */
	ProUtilMsgPrint( "gen", "TEST %0s", "The item is already animated" );
	return -1;
    }

    /* Insert object to the array of the animated elements */
    status = ProArrayObjectAdd( (ProArray *)&ani_obj_seq, 0, 1, (void*)&ani_object );
    TEST_CALL_REPORT( "ProArrayObjectAdd()", "ProTestAniObjectAdd()", 
			status, status != PRO_TK_NO_ERROR );
    if( status == PRO_TK_NO_ERROR )
	n_objects++;
    else
	return -1;
        
    status = ProModeCurrentGet(&mode);
    TEST_CALL_REPORT( "ProModeCurrentGet()", "ProTestAniObjectAdd()", 
			status, status != PRO_TK_NO_ERROR );
    if (mode == PRO_MODE_ASSEMBLY)
    {
        /* Retrieve the transformation matrix */
        status = ProAsmcomppathTrfGet( &ani_obj_seq[0].comp_path, 
				    PRO_B_TRUE, m_transform );
        TEST_CALL_REPORT( "ProAsmcomppathTrfGet()", "ProTestAniObjectAdd()", 
			status, status != PRO_TK_NO_ERROR );

        /* Assign the transformation matrix. Used for test only */
        status = ProAsmcomppathTrfSet( &ani_obj_seq[0].comp_path, 
				    PRO_B_TRUE, m_transform );
        TEST_CALL_REPORT( "ProAsmcomppathTrfSet()", "ProTestAniObjectAdd()", 
			status, status != PRO_TK_NO_ERROR );
    }
    /* Init rotate angle */
    angle = 0.0;
    add_angle = 360.0 / (double)n_frames;

    /* Create animation objects and add it to the frames */
    for( i_frame=0; i_frame<n_frames; i_frame++ )
    {
	/* Add rotation to the transformation matrix */
	angle += add_angle;
	ProUtilRotX( angle, m_transform );

	/* Create an animation object... */
	status = ProAnimobjectCreate( *p_selection, m_transform, 
					&ANI_OBJECT(0, i_frame) );
	TEST_CALL_REPORT( "ProAnimobjectCreate()", "ProTestAniObjectAdd()",
			    status, status != PRO_TK_NO_ERROR );
	if( status != PRO_TK_NO_ERROR )
	    return -1;

	/* ...and add it to the frame */
	status = ProAnimframeObjAdd( frames[i_frame], ANI_OBJECT(0, i_frame) );
	TEST_CALL_REPORT( "ProAnimframeObjAdd()", "ProTestAniObjectAdd()", 
			    status, status != PRO_TK_NO_ERROR );
	if( status != PRO_TK_NO_ERROR )
	    return -1;
    }


    return 0;
}



/*---------------------------------------------------------------------*\
    Function:	ProTestSelAniObjectDelete()
    Purpose:	On-button function. Select ani object and delete it.
    Returns:	0 - success; -1 - error. Now ignored.
    Note:	Arguments are ignored.
\*---------------------------------------------------------------------*/
int ProTestSelAniObjectDelete( void* p_dummy, int int_dummy )
{
    ProSelection*	p_selection;
    int			i_obj;


    /* Select an animation object */
    i_obj = ProUtilAniObjectSelect( (char *)"prt_or_asm", &p_selection, NULL );
    if( i_obj < PRO_TK_NO_ERROR )
    {
	ProUtilMsgPrint( "gen", "TEST %0s", "Invalid selection" );
	return -1;
    }

    /* Delete specified animation object */
    if( ProTestAniObjectDelete( i_obj ) != PRO_TK_NO_ERROR )
	return -1;
    else
	return 0;
}



/*---------------------------------------------------------------------*\
    Function:	ProTestAniObjectDelete()
    Purpose:	Delete specified animation object 
		and removes it from the array..
    Returns:	0 - success; -1 - error. Now ignored.
    Note:	Arguments are ignored.
\*---------------------------------------------------------------------*/
int ProTestAniObjectDelete( 
    int i_obj )	    /* (In)	The object index in the sequence of the ani
				objects */
{
    ProError	    status;
    int		    i_frame;


    /* Check index */
    if( (i_obj < 0) || (i_obj >= n_objects) )
	return -1;

    /* Delete the animation objects */
    for( i_frame=0; i_frame<n_frames; i_frame++ )
    {
	/* Remove the animation object from the frame */
	status = ProAnimframeObjRemove( frames[i_frame], 
					ANI_OBJECT(i_obj, i_frame) );
	TEST_CALL_REPORT( "ProAnimframeObjRemove()", 
			    "ProTestAniObjectDelete()", 
			    status, status != PRO_TK_NO_ERROR );

	/* Delete the animation object */
	status = ProAnimobjectDelete( ANI_OBJECT(i_obj, i_frame) );
	TEST_CALL_REPORT( "ProAnimobjectDelete()", "ProTestAniObjectDelete()", 
			    status, status != PRO_TK_NO_ERROR );
    }

    /* Remove the animation object from the array */
    status = ProArrayObjectRemove( (ProArray *)&ani_obj_seq, i_obj, 1 );
    TEST_CALL_REPORT( "ProArrayObjectRemove()", "ProTestAniObjectDelete()", 
			status, status != PRO_TK_NO_ERROR );
    if( status == PRO_TK_NO_ERROR )
	n_objects--;
    else
	return -1;


    return 0;
}



/*---------------------------------------------------------------------*\
    Function:	ProTestSingleAnimationAct()
    Purpose:	This is the generic function for visiting animation objects.
    Returns:	1 - success;
    Note:	I also use this function as a filter one.
\*---------------------------------------------------------------------*/
ProError ProTestSingleAnimAct( 
    ProAnimObj ani_object,	/* (In)	The handle to the animation object */
    ProAppData app_data )	/* (In)	The pointer to the frame number */
{
    TEST_CALL_REPORT ("ProAnimObjAct()","ProTestSingleAnimAct()", PRO_TK_NO_ERROR, 0);
    if( app_data != NULL )
    {
	ProTKPrintf( "Single frame %d, object %X\n", *(int*)app_data, ani_object );
    }


    return PRO_TK_NO_ERROR;
}

/*---------------------------------------------------------------------*\
    Function:	ProTestSingleAnimation()
    Purpose:	On-button function. 
    Returns:	0 - success; -1 - error. Now ignored.
    Note:	Arguments are ignored.
\*---------------------------------------------------------------------*/
int ProTestSingleAnimation( void* p_dummy, int int_dummy )
{
    ProError	    status;
    ProSingleAnim   single_animation;
    int		    i_frame;
    int		    i_obj;
    ProAnimObj	    *anim_objects;
    int             anim_obj_num, i;


    /* Check animation frames */
    if( n_frames < 1 )
    {
	ProUtilMsgPrint( "gen", "TEST %0s", "First, create an animation" );
	return -1;
    }

    /* At least one animation object must exist */
    if( n_objects < 1 )
    {
	ProUtilMsgPrint( "gen", "TEST %0s", "First, add objects to animate" );
	return -1;
    }

    /* Initialize an animation in single mode. */
    status = ProSingleAnimationInit( ani_obj_seq[0].model,
			frames[0], &single_animation );
    TEST_CALL_REPORT( "ProSingleAnimationInit()", "ProTestSingleAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return -1;

    /* Play back the animation frame by frame */
    for( i_frame=0; i_frame<n_frames; i_frame++ )
    {
	/* Play back the frame */
	status = ProSingleAnimationPlay( single_animation, frames[i_frame] );
	TEST_CALL_REPORT( "ProSingleAnimationPlay()", 
			    "ProTestSingleAnimation()", 
			    status, status != PRO_TK_NO_ERROR );
	if( status != PRO_TK_NO_ERROR )
	    return -1;
    }

    /* Visit objects in the frames */
    puts( "Visit objects in the frames:" );
    for( i_frame=0; i_frame<n_frames; i_frame++ )
	for( i_obj=0; i_obj<n_objects; i_obj++ )
	{				
	    status = ProUtilCollectAnimObjects (frames[i_frame], &anim_objects);
	    if (status == PRO_TK_NO_ERROR)
	    {
                status = ProArraySizeGet ((ProArray)anim_objects, &anim_obj_num);
	        TEST_CALL_REPORT( "ProArraySizeGet()", "ProTestSingleAnimation()", 
				status, status != PRO_TK_NO_ERROR );
                for (i = 0; i < anim_obj_num; i++)
	        {
                    status = ProTestSingleAnimAct (anim_objects[i],
	                (ProAppData)&i_frame);
                }
                status = ProArrayFree ((ProArray*)&anim_objects);
                TEST_CALL_REPORT( "ProArrayFree()", "ProTestSingleAnimation()", 
                    status, status != PRO_TK_NO_ERROR );
            }
	}

    /* Clear the data generated for single mode animation.*/
    status = ProSingleAnimationClear( single_animation );
    TEST_CALL_REPORT( "ProSingleAnimationClear()", 
			"ProTestSingleAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return -1;


    return 0;
}



/*---------------------------------------------------------------------*\
    Function:	ProTestAnimFrameAct()
    Purpose:	This is the generic function for visiting animation objects.
    Returns:	1 - success;
\*---------------------------------------------------------------------*/
ProError ProTestAnimFrameAct( 
    ProAnimFrame ani_frame,	/* (In)	The handle to the animation frame */
    ProAppData app_data )	/* (In)	The pointer to the frame number */
{
    TEST_CALL_REPORT ("ProAnimFrameAct()", "ProTestAnimFrameAct()",
        PRO_TK_NO_ERROR, 0);
    if( app_data != NULL )
    {
	ProTKPrintf( "Movie frame %d: %X\n", *(int*)app_data, ani_frame );
    }

    return PRO_TK_NO_ERROR;
}


/*---------------------------------------------------------------------*\
    Function:	ProTestBatchAnimAct()
    Purpose:	This is the generic function for callbacks in the
		middle of a batch animation.
    Returns:	PRO_TK_NO_ERROR - success;
    Note:
\*---------------------------------------------------------------------*/
ProError ProTestBatchAnimAct( ProAnimFrame  animation_frame, int frame_no, 
				ProAppData app_data )
{
    TEST_CALL_REPORT ("ProBatchAnimAct()", "ProTestBatchAnimAct()",
        PRO_TK_NO_ERROR, 0);
    ProTKPrintf( "Batch frame %d\n", frame_no );

    return PRO_TK_NO_ERROR;
}



/*---------------------------------------------------------------------*\
    Function:	ProTestBatchAnimation()
    Purpose:	On-button function. 
    Returns:	0 - success; -1 - error. Now ignored.
    Note:	Arguments are ignored.
\*---------------------------------------------------------------------*/
int ProTestBatchAnimation( void* p_dummy, int int_dummy )
{
    ProError		status;
    ProAnimMovie	animation_movie;
    int			i_frame;
    ProAnimFrame        *anim_frames;
    int                 anim_frm_num;
    int			i;

    /* Check animation frames */
    if( n_frames < 1 )
    {
	ProUtilMsgPrint( "gen", "TEST %0s", "First, create an animation" );
	return -1;
    }

    /* Check the number of the objects to be abimated */
    if( n_objects < 1 )
    {
	ProUtilMsgPrint( "gen", "TEST %0s", "First, add objects to animate" );
	return -1;
    }

    /* Create an animation movie */
    status = ProAnimmovieCreate( ani_obj_seq[0].model, &animation_movie );
    TEST_CALL_REPORT( "ProAnimmovieCreate()", "ProTestBatchAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return -1;

    /* Add frames to the movie */
    for( i_frame=0; i_frame<n_frames; i_frame++ )
    {
	status = ProAnimmovieFrameAdd( animation_movie, frames[i_frame] );
	TEST_CALL_REPORT( "ProAnimmovieFrameAdd()", "ProTestBatchAnimation()", 
			    status, status != PRO_TK_NO_ERROR );
	if( status != PRO_TK_NO_ERROR )
	    return -1;
    }

    /* Visit animation frames in the movie */
/*    status = ProAnimmovieFrameVisit( animation_movie,
					(ProAnimFrameAct)ProTestAnimFrameAct,
					(ProAnimFrameAct)ProTestAnimFrameFilter,
					NULL );
    TEST_CALL_REPORT( "ProAnimmovieFrameVisit()", 
			"ProTestBatchAnimation()", 
			status, status != PRO_TK_NO_ERROR );*/
    status = ProUtilCollectAnimFrames (animation_movie, &anim_frames);
    if (status == PRO_TK_NO_ERROR)
    {
        status = ProArraySizeGet ((ProArray)anim_frames, &anim_frm_num);
        TEST_CALL_REPORT( "ProArraySizeGet()", "ProTestBatchAnimation()", 
            status, status != PRO_TK_NO_ERROR );
        for (i = 0; i < anim_frm_num; i++)
        {
            status = ProTestAnimFrameAct (anim_frames[i],
	        (ProAppData)NULL);
        }
        status = ProArrayFree ((ProArray*)&anim_frames);
        TEST_CALL_REPORT( "ProArrayFree()", "ProTestBatchAnimation()", 
            status, status != PRO_TK_NO_ERROR );
    }


    /* Allow spinning */
    ProAnimmovieSpinflagSet(animation_movie, PRO_B_TRUE);
    /* Start a batch animation process */
    status = ProBatchAnimationStart( animation_movie, 
					(ProBatchAnimAct)ProTestBatchAnimAct, NULL );
    TEST_CALL_REPORT( "ProBatchAnimationStart()", "ProTestBatchAnimation()", 
			status, status != PRO_TK_NO_ERROR &&
                        status != PRO_TK_GENERAL_ERROR);
    
    /* Remove frames from the movie */
    for( i_frame=0; i_frame<n_frames; i_frame++ )
    {
	status = ProAnimmovieFrameRemove( animation_movie, frames[i_frame] );
	TEST_CALL_REPORT( "ProAnimmovieFrameRemove()", "ProTestBatchAnimation()", 
			    status, status != PRO_TK_NO_ERROR );
    }

    /* remove movie */
    status = ProAnimmovieDelete( animation_movie );
    TEST_CALL_REPORT( "ProAnimmovieDelete()", "ProTestBatchAnimation()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return -1;


    return 0;
}



/*---------------------------------------------------------------------*\
    Function:	ProUtilSelectedMdlGet()
    Purpose:	Retrieves a handle of the model from the selection object.
    Returns:	PRO_TK_NO_ERROR - The function successfully get 
				    the model name.
		PRO_TK_GENERAL_ERROR - Error.
    Note:	
\*---------------------------------------------------------------------*/
ProError ProUtilSelectedMdlGet( ProSelection* p_selection, ProMdl* p_model )
{
    ProError	    status;
    ProModelitem    model_item;


    /* Check out the input parameters */
    if( !p_selection || !p_model )
	return PRO_TK_GENERAL_ERROR;

    /* Get the model item from a selection object. */
    status = ProSelectionModelitemGet( *p_selection, &model_item );
    TEST_CALL_REPORT( "ProSelectionModelitemGet()", "ProUtilSelectedMdlGet()", 
			status, status != PRO_TK_NO_ERROR );
    if( status == PRO_TK_NO_ERROR )
    {
	/* Retrieve the model the owns the specified item. */
	status = ProModelitemMdlGet( &model_item, p_model );
        TEST_CALL_REPORT( "ProModelitemMdlGet()", "ProUtilSelectedMdlGet()", 
			    status, status != PRO_TK_NO_ERROR );
	if( status == PRO_TK_NO_ERROR )
	    return PRO_TK_NO_ERROR;
    }


    return PRO_TK_GENERAL_ERROR;
}



/*---------------------------------------------------------------------*\
    Function:	ProUtilMdlEqual()
    Purpose:	Compares two models.
    Returns:	PRO_B_TRUE - The models are equal.
		PRO_B_FALSE - The models are different.
    Note:	Here I compare models names, types and ids although 
		possibly it's enough to use only the types and ids.
\*---------------------------------------------------------------------*/
ProBoolean ProUtilMdlEqual( ProMdl model1, ProMdl model2 )
{
    ProError	    status;
    ProMdlName	    name1;
    ProMdlName	    name2;
    ProType	    type1;
    ProType	    type2;
    int		    id1;	
    int		    id2;	


    /* Get and compare types of the models. */
    status = ProMdlTypeGet( model1, (ProMdlType *)&type1 );
    TEST_CALL_REPORT( "ProMdlTypeGet()", "ProUtilMdlEqual()", 
			status, status != PRO_TK_NO_ERROR );
    status = ProMdlTypeGet( model2, (ProMdlType *)&type2 );
    TEST_CALL_REPORT( "ProMdlTypeGet()", "ProUtilMdlEqual()", 
			status, status != PRO_TK_NO_ERROR );
    if( type1 != type2 )
	    return PRO_B_FALSE;
    
    /* Get and compare ids of the models. */
    status = ProMdlIdGet( model1, &id1 );
    TEST_CALL_REPORT( "ProMdlIdGet()", "ProUtilMdlEqual()", 
			status, status != PRO_TK_NO_ERROR );
    status = ProMdlIdGet( model2, &id2 );
    TEST_CALL_REPORT( "ProMdlIdGet()", "ProUtilMdlEqual()", 
			status, status != PRO_TK_NO_ERROR );
    if( id1 != id2 )
	    return PRO_B_FALSE;

    /* Get and compare names of the models. */
    status = ProMdlMdlnameGet( model1, name1 );
    TEST_CALL_REPORT( "ProMdlMdlnameGet()", "ProUtilMdlEqual()", 
			status, status != PRO_TK_NO_ERROR );
    status = ProMdlMdlnameGet( model2, name2 );
    TEST_CALL_REPORT( "ProMdlMdlnameGet()", "ProUtilMdlEqual()", 
			status, status != PRO_TK_NO_ERROR );
    if( ProUtilWstrCmp( name1, name2 ) != 0 )
	    return PRO_B_FALSE;

    
    return PRO_B_TRUE;
}



/*---------------------------------------------------------------------*\
    Function:	ProUtilAsmcomppathEqual()
    Purpose:	Compares two component pathes.
    Returns:	PRO_B_TRUE - The pathes are equal.
		PRO_B_FALSE - The pathes are different.
    Note:	
\*---------------------------------------------------------------------*/
ProBoolean ProUtilAsmcomppathEqual( ProAsmcomppath path1, ProAsmcomppath path2 )
{
    int		    i;


    /* Compare owners handles and number of the component ids */
    if( (path1.owner != path2.owner) || (path1.table_num != path2.table_num) )
	return PRO_B_FALSE;

    /* Compare path ids */
    for( i=0; i<path1.table_num; i++ )
	if( path1.comp_id_table[i] != path2.comp_id_table[i] )
	    return PRO_B_FALSE;


    return PRO_B_TRUE;
}



/*---------------------------------------------------------------------*\
    Function:	ProUtilAniObjectSelect()
    Purpose:	Select an animation object.
    Returns:	If the element is successfuly selected and found 
		in the array index is returned.
		PRO_TK_E_NOT_FOUND - The selected element is not 
					an animation object.
		PRO_TK_USER_ABORT - Selection was interrupted by a user.

    Note:	When I used ProSelection* p_selection calling ProSelect
		with &p_selection...
\*---------------------------------------------------------------------*/
ProError ProUtilAniObjectSelect( 
    char option[],		    /* (In)	The selection filter. */
    ProSelection** p_selection,	    /* (Out)	The pointer to selected item */
    AniObjectInstance* p_ani_object )	/* (Out)    Animation object to 
					initialize. Can be NULL */
{
    ProError	    status;
    int		    n_selected = 0;	/* Actual number of sections made. */
    AniObjectInstance	ani_object;


    /* Check arguments */
    if( !p_selection )
	return PRO_TK_USER_ABORT;

    ProUtilMsgPrint( "gen", "TEST %0s", "Select an object" );

    /* Select ONE object. */
    status = ProSelect( option, 1, 
			NULL, NULL, NULL, NULL, p_selection, &n_selected  );
    TEST_CALL_REPORT( "ProSelect()", "ProUtilAniObjectSelect()", 
			status, status != PRO_TK_NO_ERROR );
    if( (status != PRO_TK_NO_ERROR) || (n_selected != 1) )
	return PRO_TK_USER_ABORT;

    if( p_ani_object == NULL )
	p_ani_object = &ani_object;

    /* Use ProSelection data to init the animation object */
    if( ProUtilAniObjectInstanceInit( p_selection, p_ani_object ) 
							!= PRO_TK_NO_ERROR )
	return PRO_TK_USER_ABORT;

    /* Try to find out the selected object among array of the 
	animation objects */
    return (ProError) ProUtilAniObjectFind( ani_obj_seq, *p_ani_object );
}



/*---------------------------------------------------------------------*\
    Function:	ProUtilAniObjectFind()
    Purpose:	Find specified animation object (AniObjectInstance) in 
		the array.
    Returns:	
    Note:	Animation objects are compared using ProMdl data and 
		ProAsmcomppath data. Animation object handles (ProAnimObj)
		are not used.
\*---------------------------------------------------------------------*/
int ProUtilAniObjectFind( 
    ProArray array,	    /* (In) An array of the animation objects */
    AniObjectInstance ani_object )  /* (In)	The animation objects 
						to looking for */
{
    ProError	    status;
    int		    n_ani_objects = 0;	/* Variable to get the size of the 
					    array of the animation objects */
    int		    i;


    /* Get the current size of the array. */
    status = ProArraySizeGet( array, &n_ani_objects );
    TEST_CALL_REPORT( "ProArraySizeGet()", "ProUtilAniObjectFind()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return PRO_TK_E_NOT_FOUND;

    /* Try to find out the name of the selected object among array of the
	animation objects */
    for( i=0; i<n_ani_objects; i++ )
	if( (ProUtilMdlEqual( ((AniObjectInstance*)array)[i].model, 
				ani_object.model ) == PRO_B_TRUE ) &&
	    (ProUtilAsmcomppathEqual( ((AniObjectInstance*)array)[i].comp_path,
				ani_object.comp_path ) == PRO_B_TRUE ) )
	    return  i;


    /* The specified object is not found */
    return PRO_TK_E_NOT_FOUND;
}



/*---------------------------------------------------------------------*\
    Function:	ProUtilAniObjectInstanceInit()
    Purpose:	Initialize the AniObjectInstance structure using 
		ProSelection object.
    Returns:	PRO_TK_NO_ERROR - success; PRO_TK_GENERAL_ERROR - error
    Note:	
\*---------------------------------------------------------------------*/
ProError ProUtilAniObjectInstanceInit( 
    ProSelection** p_selection,	/* (In)	    The pointer to selected item */
    AniObjectInstance* p_ani_object ) /* (Out)	Animation object to init */
{
    ProError	    status;


    /* Get a model from the selection */
    if( ProUtilSelectedMdlGet( *p_selection, &(p_ani_object->model) ) 
							!= PRO_TK_NO_ERROR )
	return PRO_TK_GENERAL_ERROR;	

    /* Get a component path of the model */
    status = ProSelectionAsmcomppathGet( **p_selection, 
					    &(p_ani_object->comp_path) );
    TEST_CALL_REPORT( "ProSelectionAsmcomppathGet()", 
			"ProUtilAniObjectInstanceInit()", 
			status, status != PRO_TK_NO_ERROR );
    if( status != PRO_TK_NO_ERROR )
	return PRO_TK_GENERAL_ERROR;


    return PRO_TK_NO_ERROR;
}



/*---------------------------------------------------------------------*\
    Function:	ProUtilRotX()
    Purpose:	Add x rotation values to the transformation matrix.
    Returns:	None
    Note:	Only the rotation components of the matrix are modified.
\*---------------------------------------------------------------------*/
void ProUtilRotX( 
    double angle,	/* (In)	    The rotation angle */
    ProMatrix mx )	/* (Out)    The transformation matrix */
{
    mx[0][0] = 1.0;
    mx[0][1] = mx[0][2] = mx[1][0] = mx[2][0] = 0.0;
    mx[1][1] = mx[2][2] = cos( angle * PI / 180.0);
    mx[1][2] = sin( angle * PI / 180.0);
    mx[2][1] = - mx[1][2];
}