2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Anim/AnimPlay.cpp $
15 * C module for playing back anim files
18 * Revision 1.7 2006/04/26 19:48:58 taylor
19 * various big-endian fixes, mainly networking support related
21 * Revision 1.6 2005/08/12 08:55:13 taylor
22 * sync up talking head fixes from FS2_Open code base (still not 100%)
24 * Revision 1.5 2004/09/20 01:31:44 theoddone33
27 * Revision 1.4 2004/06/11 00:27:06 tigital
28 * byte-swapping changes for bigendian systems
30 * Revision 1.3 2002/06/09 04:41:15 relnev
31 * added copyright header
33 * Revision 1.2 2002/05/07 03:16:43 theoddone33
34 * The Great Newline Fix
36 * Revision 1.1.1.1 2002/05/03 03:28:08 root
40 * 9 9/13/99 11:26p Andsager
41 * Add debug code to check for poorly sized anis
43 * 8 8/26/99 9:45a Dave
44 * First pass at easter eggs and cheats.
46 * 7 7/16/99 1:49p Dave
47 * 8 bit aabitmaps. yay.
49 * 6 7/13/99 1:15p Dave
50 * 32 bit support. Whee!
52 * 5 6/10/99 10:34a Dave
53 * Removed unnecessary assert.
55 * 4 11/30/98 1:07p Dave
56 * 16 bit conversion, first run.
58 * 3 10/22/98 6:14p Dave
59 * Optimized some #includes in Anim folder. Put in the beginnings of
60 * parse/localization support for externalized strings and tstrings.tbl
62 * 2 10/07/98 10:52a Dave
65 * 1 10/07/98 10:48a Dave
67 * 38 6/23/98 4:18p Hoffoss
68 * Fixed some bugs with AC release build.
70 * 37 5/18/98 5:59p Hoffoss
71 * Made command briefing advanced now once the speech stops and animation
72 * has fully played once, whichever is longer.
74 * 36 5/14/98 6:29p Hoffoss
75 * Fixed some warnings a release rebuild all turned up.
77 * 35 5/07/98 3:11a Lawrance
78 * Implement custom streaming code
80 * 34 4/27/98 3:36p Dave
82 * 33 3/25/98 8:43p Hoffoss
83 * Changed anim_play() to not be so damn complex when you try and call it.
85 * 32 2/05/98 9:21p John
86 * Some new Direct3D code. Added code to monitor a ton of stuff in the
89 * 31 1/19/98 11:37p Lawrance
90 * Fixing Optimization build warnings
92 * 30 1/19/98 3:09p Hoffoss
93 * Only free an anim instance if it is actually playing.
95 * 29 1/14/98 6:43p Lawrance
96 * Add ref_count to anim struct, so we don't free multiple times
98 * 28 12/30/97 6:44p John
99 * Made g3_Draw_bitmap functions account for aspect of bitmap.
101 * 27 12/27/97 2:35p John
102 * Restructed some code so that if the memory-mapped file open fails, it
103 * will still read it the old-fashioned, non-memory mapped way.
105 * 26 12/24/97 9:10p Lawrance
106 * take out some debugging statements
108 * 25 12/24/97 8:57p Lawrance
109 * Added anim_ignore_next_frametime()
111 * 24 12/07/97 2:06p Dave
112 * Removed some debug frame-checking code.
114 * 23 12/06/97 2:55p Dave
116 * 22 11/29/97 2:05p John
117 * made g3_draw_bitmap and g3_draw_rotated bitmap take w&h, not w/2 & h/2,
118 * like they used to incorrectly assume. Added code to model to read in
121 * 21 11/20/97 5:36p Dave
122 * Hooked in a bunch of main hall changes (including sound). Made it
123 * possible to reposition (rewind/ffwd)
124 * sound buffer pointers. Fixed animation direction change framerate
127 * 20 11/20/97 4:33p Sandeep
128 * ALAN: ensure instance->paused gets initialized
130 * 19 11/20/97 4:12p Lawrance
131 * when paused, don't increment time_elapsed
133 * 18 11/19/97 8:28p Dave
134 * Hooked in Main Hall screen. Put in Anim support for ping ponging
135 * animations as well as general reversal of anim direction.
137 * 17 9/11/97 4:17p Allender
138 * use _MAX_PATH instead of MAX_FILENAME_LENGTH for anim_play since it is
139 * called from MoviePlayer with full path
141 * 16 9/11/97 10:49a Lawrance
142 * improve anim_show_next_frame()
144 * 15 9/10/97 4:59p Lawrance
145 * improve comments to anim_play() function
147 * 14 9/03/97 4:19p John
148 * changed bmpman to only accept ani and pcx's. made passing .pcx or .ani
149 * to bm_load functions not needed. Made bmpman keep track of palettes
150 * for bitmaps not mapped into game palettes.
152 * 13 8/30/97 2:11p Lawrance
153 * allow animations to loop
155 * 12 8/25/97 11:13p Lawrance
156 * support framerate independent playback with the option of now advancing
157 * more than one frame at a time
159 * 11 8/22/97 8:20a Lawrance
160 * display scrolling text properly, with indicator that text exitsts to be
163 * 10 8/21/97 5:11p Lawrance
164 * frame numbering for ANI's now is from 0 -> total_frames-1.
166 * 9 8/19/97 9:30a Lawrance
167 * print out reason if file doesn't open right
169 * 8 7/28/97 10:42p Lawrance
170 * re-did interface to unpack_frame() to make more general
172 * 7 7/21/97 11:41a Lawrance
173 * make playback time of .ani files keyed of frametime
175 * 6 7/20/97 6:57p Lawrance
176 * supporting new RLE format
178 * 5 7/11/97 11:54a John
179 * added rotated 3d bitmaps.
181 * 4 6/27/97 4:36p Lawrance
182 * update pal translation table when gr_screen.signature changes
184 * 3 6/26/97 3:00p Lawrance
185 * fix bug with playing 3d anims
187 * 2 6/26/97 12:12a Lawrance
188 * supporting anti-aliased bitmap animations
190 * 1 6/23/97 5:09p Lawrance
192 * 24 6/03/97 5:53p Lawrance
193 * don't unload bitmap after bm_create
198 #include "animplay.h"
199 #include "linklist.h"
204 #include "grinternal.h"
205 #include "pcxutils.h"
206 #include "packunpack.h"
209 static color Color_xparent;
211 anim *first_anim = NULL;
212 anim_instance anim_free_list;
213 anim_instance anim_render_list;
215 #define MAX_ANIM_INSTANCES 25
216 anim_instance anim_render_instance[MAX_ANIM_INSTANCES];
218 int Anim_paused; // global variable to pause the playing back of anims
219 int Anim_inited = FALSE;
223 int Anim_ignore_frametime=0; // flag used to ignore frametime... useful when need to avoid saturated frametimes
225 // -------------------------------------------------------------------------------------------------
226 // anim_init() will queue all the anim_render_instance[] elements onto the anim_free_list
232 if ( Anim_inited == TRUE )
235 list_init( &anim_free_list );
236 list_init( &anim_render_list );
238 // Link all anim render slots into the free list
239 for (i=1; i < MAX_ANIM_INSTANCES; i++) {
240 list_append(&anim_free_list, &anim_render_instance[i]);
247 // -------------------------------------------------------------------------------------------------
248 // anim_render_all() will display the frames for the currently playing anims
250 void anim_render_all(int screen_id, float frametime)
255 A = GET_FIRST(&anim_render_list);
256 while( A !=END_OF_LIST(&anim_render_list) ) {
258 if ( A->screen_id == screen_id ) {
259 if ( Anim_ignore_frametime ) {
261 Anim_ignore_frametime=0;
263 if ( anim_show_next_frame(A, frametime) == -1 ) {
265 anim_release_render_instance(A);
272 // -------------------------------------------------------------------------------------------------
273 // anim_render_one() will display the frames for the passed animation, it will ignore animations which
274 // do not have the same id as the passed screen_id
276 void anim_render_one(int screen_id, anim_instance *ani, float frametime)
278 // make sure this guy's screen id matches the passed one
279 if(screen_id != ani->screen_id){
283 // otherwise render it
284 if ( Anim_ignore_frametime ) {
286 Anim_ignore_frametime=0;
288 if ( anim_show_next_frame(ani, frametime) == -1 ) {
290 anim_release_render_instance(ani);
294 MONITOR(NumANIPlayed);
296 // Setup an anim_play_struct for passing into anim_play(). Will fill in default values, which you
297 // can then change before calling anim_play().
299 void anim_play_init(anim_play_struct *aps, anim *a_info, int x, int y)
301 aps->anim_info = a_info;
305 aps->stop_at = a_info->total_frames - 1;
307 aps->world_pos = NULL;
309 aps->framerate_independent = 0;
311 aps->skip_frames = 1;
316 // -------------------------------------------------------------------------------------------------
317 // anim_play() will add an anim instance to the anim_render_list. This will cause the
318 // anim to be played at the x,y position specified in the parameter list.
322 // anim_info => the compressed animation that we should make an instance from
323 // x => x position of animation to play at (top left corner)
324 // y => y position of animation to play at ( top left corner)
325 // start_at => frame number to start at (note: numbering is from 0->num_frames-1)
326 // stop_at => frame number to stop at (note: numbering is from 0->num_frames-1)
327 // screen_id => OPTIONAL (default value 0): screen signature so animation only plays when
328 // anim_render_all() called with that same signature
329 // world_pos => OPTIONAL (default value NULL): only give a world pos when you want to
330 // play the animation at a 3D location. You must specify radius when
332 // radius => OPTIONAL (default value 0): only needed when the animation is playing
333 // as a 3D animation (this is only when world_pos in not NULL).
334 // fi => OPTIONAL (default value 0): framerate indepentdent flag, when set TRUE
335 // the animation will skip frames if necessary to maintain the fps value
336 // associated with the animation
337 // color => OPTIONAL (default value NULL) address of an alpha color struct. Only
338 // required when the animation should be drawn with an alpha color.
339 // skip_frames => OPTIONAL (default value 1) should anim skip frames when doing framerate
340 // independent playback
341 // looped => OPTIONAL (default value 0) should anim play looped (ie forever)
345 // pointer to instance => success
346 // NULL => if anim anim could not be played
348 anim_instance *anim_play(anim_play_struct *aps)
350 SDL_assert( aps->anim_info != NULL );
351 SDL_assert( aps->start_at >= 0 );
352 SDL_assert( aps->stop_at < aps->anim_info->total_frames );
353 // SDL_assert( aps->stop_at >= aps->start_at );
354 SDL_assert( !(aps->looped && aps->ping_pong) ); // shouldn't have these both set at once
356 MONITOR_INC(NumANIPlayed, 1);
358 // if (aps->ping_pong && !(aps->anim_info->flags & ANF_ALL_KEYFRAMES));
360 anim_instance *instance;
362 // Find next free anim instance slot on queue
363 instance = GET_FIRST(&anim_free_list);
364 SDL_assert( instance != &anim_free_list ); // shouldn't have the dummy element
366 // remove instance from the free list
367 list_remove( &anim_free_list, instance );
369 // insert instance onto the end of anim_render_list
370 list_append( &anim_render_list, instance );
372 aps->anim_info->instance_count++;
373 instance->frame_num = -1;
374 instance->last_frame_num = -99;
375 instance->parent = aps->anim_info;
376 instance->data = aps->anim_info->data;
377 if ( anim_instance_is_streamed(instance) ) {
378 instance->file_offset = instance->parent->file_offset;
380 instance->frame = (ubyte *) malloc(instance->parent->width * instance->parent->height * 2);
381 SDL_assert( instance->frame != NULL );
382 instance->time_elapsed = 0.0f;
383 instance->stop_at = aps->stop_at;
384 instance->x = aps->x;
385 instance->y = aps->y;
386 instance->world_pos = aps->world_pos;
387 instance->radius = aps->radius;
388 instance->framerate_independent = aps->framerate_independent;
389 instance->last_bitmap = -1;
390 instance->stop_now = FALSE;
391 instance->screen_id = aps->screen_id;
392 instance->aa_color = aps->color;
393 instance->skip_frames = aps->skip_frames;
394 instance->looped = aps->looped;
395 instance->ping_pong = aps->ping_pong;
396 instance->direction = ANIM_DIRECT_FORWARD;
397 instance->paused = 0;
398 instance->loop_count = 0;
399 if ( aps->color == NULL ){
400 instance->xlate_pal = 1;
402 instance->xlate_pal = 0;
405 // determining the start_at frame is more complicated, since it must be a key-frame.
406 // Futhermore, need to subtract 1 from key-frame number, since frame number is always
407 // incremented the first time anim_show_next_frame() is called
409 instance->start_at = aps->start_at;
411 if ( aps->start_at > 0 ) {
416 int frame_num = aps->start_at;
418 keyp = instance->parent->keys;
420 while (idx < instance->parent->num_keys) {
421 if (key == frame_num)
424 key = keyp[idx].frame_num - 1;
425 offset = keyp[idx].offset;
430 if (( (keyp->frame_num-1) <= frame_num) && ( (keyp->frame_num-1) > key)) { // find closest key
431 key = keyp->frame_num-1;
432 offset = keyp->offset;
433 if ( key == frame_num )
440 if (key > instance->frame_num) { // best key is closer than current position
441 instance->frame_num = key;
442 if ( anim_instance_is_streamed(instance) ) {
443 instance->file_offset = instance->parent->file_offset + offset;
445 instance->data = instance->parent->data + offset;
450 instance->frame_num--; // required
456 // -----------------------------------------------------------------------------
457 // anim_show_next_frame()
459 // This function is called to blit the next frame of an anim instance to the
460 // screen. This is normally called by the anim_render_all() function.
462 // input: instance => pointer to animation instance
463 // frametime => time elapsed since last call, in seconds
465 int anim_show_next_frame(anim_instance *instance, float frametime)
467 int bitmap_id, bitmap_flags=0, new_frame_num, frame_diff=0, i, n_frames=0,frame_save;
468 float percent_through, decompress_time, render_time, time;
473 SDL_assert( instance != NULL );
475 instance->time_elapsed += frametime;
477 // Advance to the next frame, if we determine enough time has elapsed.
478 if(instance->direction == ANIM_DIRECT_FORWARD)
479 n_frames = instance->stop_at - instance->start_at + 1;
480 else if(instance->direction == ANIM_DIRECT_REVERSE)
481 n_frames = instance->start_at - instance->stop_at + 1;
482 time = n_frames / i2fl(instance->parent->fps);
484 percent_through = instance->time_elapsed / time;
486 if(instance->direction == ANIM_DIRECT_FORWARD)
487 new_frame_num = instance->start_at - 1 + fl2i(percent_through * n_frames + 0.5f);
489 new_frame_num = instance->start_at - 1 - fl2i(percent_through * n_frames + 0.5f);
491 frame_save = instance->frame_num;
493 // If framerate independent, use the new_frame_num... unless instance->skip_frames is
494 // FALSE, then only advance a maximum of one frame (this is needed since some big animations
495 // should just play slower rather than taking the hit of decompressing multiple frames and
496 // creating an even greater slowdown
497 if (instance->framerate_independent) {
498 if(instance->direction == ANIM_DIRECT_FORWARD){
499 if ( new_frame_num > instance->last_frame_num) {
500 if ( instance->skip_frames )
501 instance->frame_num = new_frame_num;
503 instance->frame_num++;
505 } else if(instance->direction == ANIM_DIRECT_REVERSE){
506 if( new_frame_num < instance->last_frame_num) {
507 if ( instance->skip_frames )
508 instance->frame_num = new_frame_num;
510 instance->frame_num--;
515 if(instance->direction == ANIM_DIRECT_FORWARD){
516 if ( new_frame_num > instance->last_frame_num) {
517 instance->frame_num++;
519 } else if(instance->direction == ANIM_DIRECT_REVERSE){
520 if ( new_frame_num < instance->last_frame_num) {
521 instance->frame_num--;
526 if(instance->direction == ANIM_DIRECT_FORWARD){
527 if ( instance->frame_num < instance->start_at ) {
528 instance->frame_num = instance->start_at;
530 } else if(instance->direction == ANIM_DIRECT_REVERSE){
531 if ( instance->frame_num > instance->start_at ) {
532 instance->frame_num = instance->start_at;
536 if ( instance->stop_now == TRUE ) {
540 // If past the last frame, clamp to the last frame and then set the stop_now flag in the
541 // anim instance. The next iteration, the animation will stop.
542 if(instance->direction == ANIM_DIRECT_FORWARD){
543 if (instance->frame_num >= instance->stop_at ) {
544 if (instance->looped) { // looped animations
545 instance->frame_num = instance->stop_at;
546 instance->time_elapsed = 0.0f;
547 } else if(instance->ping_pong) { // pingponged animations
548 instance->frame_num = instance->stop_at;
549 // instance->time_elapsed = 0.0f;
550 anim_reverse_direction(instance);
551 } else { // one-shot animations
552 instance->frame_num = instance->stop_at;
553 instance->last_frame_num = instance->frame_num;
554 instance->stop_now = TRUE;
557 } else if(instance->direction == ANIM_DIRECT_REVERSE){
558 if (instance->frame_num <= instance->stop_at ) {
559 if (instance->looped) { // looped animations
560 instance->frame_num = instance->stop_at;
561 instance->time_elapsed = 0.0f;
562 } else if(instance->ping_pong) { // pingponged animations
563 instance->frame_num = instance->stop_at;
564 // instance->time_elapsed = 0.0f;
565 anim_reverse_direction(instance);
566 } else { // one-shot animations
567 instance->frame_num = instance->stop_at+1;
568 instance->last_frame_num = instance->frame_num;
569 instance->stop_now = TRUE;
574 if(instance->direction == ANIM_DIRECT_FORWARD){
575 if( instance->last_frame_num >= instance->start_at ) {
576 frame_diff = instance->frame_num - instance->last_frame_num;
580 } else if(instance->direction == ANIM_DIRECT_REVERSE){
581 if( instance->last_frame_num <= instance->start_at ) {
582 frame_diff = instance->last_frame_num - instance->frame_num;
587 SDL_assert(frame_diff >= 0);
588 // nprintf(("Alan","FRAME DIFF: %d\n",frame_diff));
589 SDL_assert( instance->frame_num >= 0 && instance->frame_num < instance->parent->total_frames );
591 // if the anim is paused, ignore all the above changes and still display this frame
592 if(instance->paused || Anim_paused){
593 instance->frame_num = frame_save;
594 instance->time_elapsed -= frametime;
598 if (instance->parent->flags & ANF_XPARENT){
599 // bitmap_flags = BMP_XPARENT;
603 if(instance->aa_color != NULL){
604 bitmap_flags |= BMP_AABITMAP;
609 if ( frame_diff > 0 ) {
610 instance->last_frame_num = instance->frame_num;
612 t1 = timer_get_fixed_seconds();
613 for ( i = 0; i < frame_diff; i++ ) {
614 anim_check_for_palette_change(instance);
616 // if we're playing backwards, every frame must be a keyframe and we set the data ptr here
617 if(instance->direction == ANIM_DIRECT_REVERSE){
618 if ( anim_instance_is_streamed(instance) ) {
619 instance->file_offset = instance->parent->file_offset + instance->parent->keys[instance->frame_num-1].offset;
621 instance->data = instance->parent->data + instance->parent->keys[instance->frame_num-1].offset;
626 int temp_file_offset = 0;
628 BM_SELECT_TEX_FORMAT();
630 if ( anim_instance_is_streamed(instance) ) {
631 if ( instance->xlate_pal ){
632 temp_file_offset = unpack_frame_from_file(instance, instance->frame, instance->parent->width*instance->parent->height, instance->parent->palette_translation, aabitmap, bpp);
634 temp_file_offset = unpack_frame_from_file(instance, instance->frame, instance->parent->width*instance->parent->height, NULL, aabitmap, bpp);
637 if ( instance->xlate_pal ){
638 temp = unpack_frame(instance, instance->data, instance->frame, instance->parent->width*instance->parent->height, instance->parent->palette_translation, aabitmap, bpp);
640 temp = unpack_frame(instance, instance->data, instance->frame, instance->parent->width*instance->parent->height, NULL, aabitmap, bpp);
644 // always go back to screen format
645 BM_SELECT_SCREEN_FORMAT();
647 if(instance->direction == ANIM_DIRECT_FORWARD){
648 if ( anim_instance_is_streamed(instance) ) {
649 instance->file_offset = temp_file_offset;
651 instance->data = temp;
655 t2 = timer_get_fixed_seconds();
661 // this only happens when the anim is being looped, we need to reset the last_frame_num
662 if ( (instance->time_elapsed == 0) && (instance->looped) ) {
663 instance->last_frame_num = -1;
664 instance->frame_num = -1;
665 instance->data = instance->parent->data;
666 instance->file_offset = instance->parent->file_offset;
667 instance->loop_count++;
670 decompress_time = f2fl(t2-t1);
672 t1 = timer_get_fixed_seconds();
673 if ( frame_diff == 0 && instance->last_bitmap != -1 ) {
674 bitmap_id = instance->last_bitmap;
677 if ( instance->last_bitmap != -1 ){
678 bm_release(instance->last_bitmap);
680 bitmap_id = bm_create(16, instance->parent->width, instance->parent->height, instance->frame, bitmap_flags);
683 if ( bitmap_id == -1 ) {
684 // anim has finsished playing, free the instance frame data
685 anim_release_render_instance(instance);
688 // NOTE: there is no need to free the instance, since it was pre-allocated as
689 // part of the anim_free_list
692 gr_set_bitmap(bitmap_id, GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
694 // determine x,y to display the bitmap at
695 if ( instance->world_pos == NULL ) {
696 if ( instance->aa_color == NULL ) {
697 gr_bitmap(instance->x, instance->y);
700 gr_set_color_fast( (color*)instance->aa_color );
701 gr_aabitmap(instance->x, instance->y);
705 g3_rotate_vertex(&image_vertex,instance->world_pos);
706 SDL_assert(instance->radius != 0.0f);
707 g3_draw_bitmap(&image_vertex, 0, instance->radius*1.5f, TMAP_FLAG_TEXTURED );
710 //bm_release(bitmap_id);
711 instance->last_bitmap = bitmap_id;
714 t2 = timer_get_fixed_seconds();
715 render_time = f2fl(t2-t1);
717 // nprintf(("Alan","DECOMPRESS: %.3fms RENDER: %.3fms\n", decompress_time*1000, render_time*1000));
722 // -----------------------------------------------------------------------------
723 // anim_stop_playing()
725 // Stop an anim instance that is on the anim_render_list from playing
727 int anim_stop_playing(anim_instance* instance)
729 SDL_assert(instance != NULL);
731 if ( anim_playing(instance) ) {
732 anim_release_render_instance(instance);
737 // -----------------------------------------------------------------------------
738 // anim_release_render_instance()
740 // Free a particular animation instance that is on the anim_render_list. Do
741 // not call this function to free an animation instance in general (use
742 // free_anim_instance() for that), only when you want to free an instance
743 // that is on the anim_render_list
745 void anim_release_render_instance(anim_instance* instance)
747 SDL_assert( instance != NULL );
748 SDL_assert(instance->frame);
749 free(instance->frame);
750 instance->frame = NULL;
751 instance->parent->instance_count--;
753 if ( instance->last_bitmap != -1 ) {
754 bm_release(instance->last_bitmap);
755 instance->last_bitmap = -1;
758 // remove instance from anim_render_list
759 list_remove( &anim_render_list, instance );
761 // insert instance into the anim_free_list
762 list_append( &anim_free_list, instance );
765 // -----------------------------------------------------------------------------
766 // anim_release_all_instances()
768 // Free all anim instances that are on the anim_render_list.
770 // input: screen_id => optional parameter that lets you only free a subset
771 // of the anim instances. A screen_id of 0 is the default
772 // value, and this is used for animations that always play
773 // when they are placed on the aim_render_list.
775 void anim_release_all_instances(int screen_id)
780 if ( Anim_inited == FALSE )
783 A = GET_FIRST(&anim_render_list);
784 while( A !=END_OF_LIST(&anim_render_list) ) {
786 if ( A->screen_id == screen_id || screen_id == 0 ) {
787 anim_release_render_instance(A);
793 // -----------------------------------------------------------------------------
794 // anim_read_header()
796 // Read the header of a .ani file. Below is the format of a .ani header
798 // #bytes | description
799 // 2 | obsolete, kept for compatibility with old versions
800 // 2 | version number
802 // 1 | transparent red value
803 // 1 | transparent green value
804 // 1 | transparent blue value
807 // 2 | number of frames
810 // 2 | number of key frames
811 // 2 | key frame number } repeats
812 // 4 | key frame offset } repeats
813 // 4 | compressed data length
815 void anim_read_header(anim *ptr, CFILE *fp)
817 ptr->width = cfread_short(fp);
818 // If first 2 bytes are zero, this means we are using a new format, which includes
819 // a version, and fps values. This is only done since a version number was not included
820 // in the original header.
823 Color_xparent.red = 0;
824 Color_xparent.green = 255;
825 Color_xparent.blue = 0;
827 if ( ptr->width == 0 ) {
828 ptr->version = cfread_short(fp);
829 ptr->fps = cfread_short(fp);
831 // version 2 added a custom transparency color
832 if ( ptr->version >= 2 ) {
833 cfread(&Color_xparent.red, 1, 1, fp);
834 cfread(&Color_xparent.green, 1, 1, fp);
835 cfread(&Color_xparent.blue, 1, 1, fp);
838 ptr->width = cfread_short(fp);
845 ptr->height = cfread_short(fp);
848 // get size of ani compared to power of 2
858 int floor_size = (int) pow(2, floor_pow);
859 int diff = ptr->height - floor_size;
860 float waste = 100.0f * float((floor_size - diff))/(2.0f *(float)floor_size);
863 if (ptr->height > 16) {
864 mprintf(("ANI %s with size %dx%d (%.1f%% wasted)\n", ptr->name, ptr->height, ptr->height, waste));
869 ptr->total_frames = cfread_short(fp);
870 ptr->packer_code = cfread_ubyte(fp);
871 cfread(&ptr->palette, 256, 3, fp);
872 ptr->num_keys = cfread_short(fp);
874 // store xparent colors
875 ptr->xparent_r = Color_xparent.red;
876 ptr->xparent_g = Color_xparent.green;
877 ptr->xparent_b = Color_xparent.blue;
879 if(ptr->total_frames == ptr->num_keys){
880 ptr->flags |= ANF_ALL_KEYFRAMES;
884 // -----------------------------------------------------------------------------
887 // Load an animation. This stores the compressed data, which instances
888 // of the animation can reference. Must be free'ed later with anim_free()
890 // input: name => filename of animation
891 // file_mapped => boolean, whether to use memory-mapped file or not.
892 // Memory-mapped files will page in the animation from disk
893 // as it is needed, but performance is not as good
895 // returns: pointer to anim that is loaded => sucess
898 anim *anim_load(const char *real_filename, int file_mapped)
903 char name[_MAX_PATH];
907 SDL_assert ( real_filename != NULL );
909 strcpy( name, real_filename );
910 char *p = strchr( name, '.' );
914 strcat( name, ".ani" );
918 if (!SDL_strcasecmp(name, ptr->name))
925 fp = cfopen(name, "rb");
929 ptr = (anim *) malloc(sizeof(anim));
933 ptr->next = first_anim;
935 SDL_assert(strlen(name) < _MAX_PATH - 1);
936 strcpy(ptr->name, name);
937 ptr->instance_count = 0;
940 ptr->total_frames = 0;
944 anim_read_header(ptr, fp);
946 if(ptr->num_keys > 0){
947 ptr->keys = (key_frame*)malloc(sizeof(key_frame) * ptr->num_keys);
948 SDL_assert(ptr->keys != NULL);
951 // store how long the anim should take on playback (in seconds)
952 ptr->time = i2fl(ptr->total_frames)/ptr->fps;
954 for(idx=0;idx<ptr->num_keys;idx++){
955 ptr->keys[idx].frame_num = 0;
956 cfread(&ptr->keys[idx].frame_num, 2, 1, fp);
957 cfread(&ptr->keys[idx].offset, 4, 1, fp);
958 ptr->keys[idx].frame_num = INTEL_INT( ptr->keys[idx].frame_num );
959 ptr->keys[idx].offset = INTEL_INT( ptr->keys[idx].offset );
962 /*prev_keyp = &ptr->keys;
963 count = ptr->num_keys;
965 keyp = (key_frame *) malloc(sizeof(key_frame));
966 keyp->next = *prev_keyp;
968 prev_keyp = &keyp->next;
971 cfread(&keyp->frame_num, 2, 1, fp);
972 cfread(&keyp->offset, 4, 1, fp);
974 cfread(&count, 4, 1, fp); // size of compressed data
975 count = INTEL_INT( count );
977 ptr->cfile_ptr = NULL;
980 // Try mapping the file to memory
981 ptr->flags |= ANF_MEM_MAPPED;
982 ptr->cfile_ptr = cfopen(name, "rb", CFILE_MEMORY_MAPPED);
985 // couldn't memory-map file... must be in a packfile, so stream manually
986 if ( file_mapped && !ptr->cfile_ptr ) {
987 ptr->flags &= ~ANF_MEM_MAPPED;
988 ptr->flags |= ANF_STREAMED;
989 ptr->cfile_ptr = cfopen(name, "rb");
994 // If it opened properly as mem-mapped (or streamed)
995 if (ptr->cfile_ptr != NULL) {
996 // VERY IMPORTANT STEP
997 // Set the data pointer to the compressed data (which is not at the start of the
998 // file). Use ftell() to find out how far we've already parsed into the file
1001 offset = cftell(fp);
1002 ptr->file_offset = offset;
1003 if ( ptr->flags & ANF_STREAMED ) {
1005 ptr->cache_file_offset = ptr->file_offset;
1006 ptr->cache = (ubyte*)malloc(ANI_STREAM_CACHE_SIZE+2);
1007 SDL_assert(ptr->cache);
1008 cfseek(ptr->cfile_ptr, offset, CF_SEEK_SET);
1009 cfread(ptr->cache, ANI_STREAM_CACHE_SIZE, 1, ptr->cfile_ptr);
1011 ptr->data = (ubyte*)cf_returndata(ptr->cfile_ptr) + offset;
1014 // Not a memory mapped file (or streamed)
1015 ptr->flags &= ~ANF_MEM_MAPPED;
1016 ptr->flags &= ~ANF_STREAMED;
1017 ptr->data = (ubyte *) malloc(count);
1018 ptr->file_offset = -1;
1019 cfread(ptr->data, count, 1, fp);
1024 // store screen signature, so we can tell if palette changes
1025 ptr->screen_sig = gr_screen.signature;
1027 anim_set_palette(ptr);
1034 // ---------------------------------------------------
1037 // Free an animation that was loaded with anim_load(). All instances
1038 // referencing this animation must be free'ed or get an assert.
1040 int anim_free(anim *ptr)
1042 SDL_assert ( ptr != NULL );
1043 anim *list, **prev_anim;
1046 prev_anim = &first_anim;
1047 while (list && (list != ptr)) {
1048 prev_anim = &list->next;
1055 // only free when ref_count is 0
1057 if ( ptr->ref_count > 0 )
1060 // only free if there are no playing instances
1061 if ( ptr->instance_count > 0 )
1064 if(ptr->keys != NULL){
1069 if ( ptr->flags & (ANF_MEM_MAPPED|ANF_STREAMED) ) {
1070 cfclose(ptr->cfile_ptr);
1076 SDL_assert(ptr->data);
1080 *prev_anim = ptr->next;
1086 // ---------------------------------------------------------------------
1089 // Return if an anim is playing or not.
1091 int anim_playing(anim_instance *ai)
1093 SDL_assert(ai != NULL);
1094 if ( ai->frame == NULL )
1101 // ---------------------------------------------------------------------
1102 // anim_level_init()
1104 // Called at the beginning of a mission to initialize any mission dependent
1107 void anim_level_init()
1111 // ---------------------------------------------------------------------
1112 // anim_level_close()
1114 // Called after the end of a mission to clean up any mission dependent
1117 void anim_level_close()
1119 anim_release_all_instances();
1122 // ---------------------------------------------------
1123 // anim_write_frames_out()
1125 // Write the frames of a .ani file out to disk as .pcx files.
1126 // Use naming convention: filename0000.pcx, filename0001.pcx etc.
1128 // return: 0 => success
1131 int anim_write_frames_out(const char *filename)
1135 char root_name[256], pcxname[256];
1140 strcpy(root_name, filename);
1141 root_name[strlen(filename)-4] = 0;
1143 source_anim = anim_load(filename);
1144 if ( source_anim == NULL )
1147 ai = init_anim_instance(source_anim, 16);
1149 row_data = (ubyte**)malloc((source_anim->height+1) * 4);
1151 for ( i = 0; i < source_anim->total_frames; i++ ) {
1152 anim_get_next_raw_buffer(ai, 0, 0, 16);
1153 strcpy(pcxname, root_name);
1154 sprintf(buf,"%04d",i);
1155 strcat(pcxname, buf);
1157 for ( j = 0; j < source_anim->height; j++ ) {
1158 row_data[j] = &ai->frame[j*source_anim->width];
1162 pcx_write_bitmap( pcxname,
1164 source_anim->height,
1166 source_anim->palette);
1176 // ---------------------------------------------------
1177 // anim_display_info()
1179 // Display information and statistics about a .ani file.
1180 // This is called when -i switch is on when running ac.exe
1182 void anim_display_info(const char *real_filename)
1187 int i, uncompressed, compressed, *key_frame_nums=NULL, tmp;
1188 char filename[MAX_FILENAME_LEN];
1190 strcpy( filename, real_filename );
1191 char *p = strchr( filename, '.' );
1195 strcat( filename, ".ani" );
1197 fp = cfopen(filename, "rb");
1199 printf("Fatal error opening %s", filename);
1203 anim_read_header(&A, fp);
1204 // read the keyframe frame nums and offsets
1205 key_frame_nums = (int*)malloc(sizeof(int)*A.num_keys);
1206 SDL_assert(key_frame_nums != NULL);
1207 for ( i = 0; i < A.num_keys; i++ ) {
1208 key_frame_nums[i] = 0;
1209 cfread(&key_frame_nums[i], 2, 1, fp);
1210 cfread(&tmp, 4, 1, fp);
1211 //printf("key frame num: %d,%d\n", key_frame_nums[i],tmp);
1214 cfread(&compressed, 4, 1, fp);
1216 uncompressed = A.width * A.height * A.total_frames; // 8 bits per pixel
1217 percent = i2fl(compressed) / uncompressed * 100.0f;
1219 printf("%% of uncompressed size: %.0f%%\n", percent);
1220 printf("Width: %d\n", A.width);
1221 printf("Height: %d\n", A.height);
1222 printf("Total Frames: %d\n", A.total_frames);
1225 printf("Key Frames: %d\n", A.num_keys);
1226 if ( A.num_keys > 1 && (A.total_frames != A.num_keys) ) {
1227 printf("key list: (");
1228 for ( i = 0; i < A.num_keys; i++ ) {
1229 if ( i < A.num_keys-1 )
1230 printf("%d, ", key_frame_nums[i]);
1232 printf("%d)\n", key_frame_nums[i]);
1237 printf("FPS: %d\n", A.fps);
1240 printf("Transparent RGB: (%d,%d,%d)\n", A.xparent_r, A.xparent_g, A.xparent_b);
1243 printf("ac version: %d\n", A.version);
1245 if ( key_frame_nums != NULL ) {
1246 free(key_frame_nums);
1252 void anim_reverse_direction(anim_instance *ai)
1256 if(!(ai->parent->flags & ANF_ALL_KEYFRAMES)){
1257 // you're not allowed to call anim_reverse_direction(...) unless every frame is a keyframe!!!!
1258 // The God of Delta-RLE demands it be thus.
1262 // flip the animation direction
1263 if(ai->direction == ANIM_DIRECT_FORWARD){
1264 ai->direction = ANIM_DIRECT_REVERSE;
1265 } else if(ai->direction == ANIM_DIRECT_REVERSE){
1266 ai->direction = ANIM_DIRECT_FORWARD;
1269 // flip frame_num and last_frame_num
1270 temp = ai->frame_num;
1271 ai->frame_num = ai->last_frame_num;
1272 ai->last_frame_num = temp;
1274 // flip the start and stop at frames
1276 ai->stop_at = ai->start_at;
1277 ai->start_at = temp;
1279 // make sure to sync up the time correctly
1280 if(ai->direction == ANIM_DIRECT_FORWARD){
1281 ai->time_elapsed = ((float)ai->frame_num - (float)ai->start_at - 1.0f) / (float)ai->parent->fps;
1282 } else if(ai->direction == ANIM_DIRECT_REVERSE) {
1283 ai->time_elapsed = ((float)ai->start_at - (float)ai->frame_num - 1.0f) / (float)ai->parent->fps;
1287 void anim_pause(anim_instance *ai)
1292 void anim_unpause(anim_instance *ai)
1297 void anim_ignore_next_frametime()
1299 Anim_ignore_frametime=1;
1302 int anim_instance_is_streamed(anim_instance *ai)
1305 return ( ai->parent->flags & ANF_STREAMED );
1308 unsigned char anim_instance_get_byte(anim_instance *ai, int offset)
1310 int absolute_offset;
1314 SDL_assert(ai->parent->cfile_ptr);
1315 SDL_assert(ai->parent->flags & ANF_STREAMED);
1317 parent = ai->parent;
1318 absolute_offset = ai->file_offset + offset;
1322 cache_offset = absolute_offset - parent->cache_file_offset;
1323 if ( (cache_offset >= 0) && (cache_offset < ANI_STREAM_CACHE_SIZE) ) {
1324 return parent->cache[cache_offset];
1327 cfseek(parent->cfile_ptr, absolute_offset, CF_SEEK_SET);
1328 cfread(parent->cache, ANI_STREAM_CACHE_SIZE, 1, parent->cfile_ptr);
1329 parent->cache_file_offset = absolute_offset;
1330 return parent->cache[0];