1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
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 the
6 * source.
7 *
8 */
9
10
11
12 #include "anim/animplay.h"
13 #include "globalincs/linklist.h"
14 #include "io/timer.h"
15 #include "bmpman/bmpman.h"
16 #include "graphics/2d.h"
17 #include "render/3d.h"
18 #include "pcxutils/pcxutils.h"
19 #include "anim/packunpack.h"
20 #include "cfile/cfile.h"
21 #include "cmdline/cmdline.h"
22
23
24
25 static color Color_xparent;
26
27 anim *first_anim = NULL;
28 anim_instance anim_free_list;
29 anim_instance anim_render_list;
30
31 #define MAX_ANIM_INSTANCES 25
32 anim_instance anim_render_instance[MAX_ANIM_INSTANCES];
33
34 int Anim_paused; /// Global variable to pause the playing back of anims
35 int Anim_inited = FALSE;
36
37 fix t1,t2;
38
39 int Anim_ignore_frametime=0; // flag used to ignore frametime... useful when need to avoid saturated frametimes
40
41 /**
42 * @brief Initialise animation
43 * @details Queue all the ::anim_render_instance[] elements onto the ::anim_free_list
44 */
anim_init()45 void anim_init()
46 {
47 int i;
48
49 if ( Anim_inited == TRUE )
50 return;
51
52 list_init( &anim_free_list );
53 list_init( &anim_render_list );
54
55 // Link all anim render slots into the free list
56 for (i=1; i < MAX_ANIM_INSTANCES; i++) {
57 list_append(&anim_free_list, &anim_render_instance[i]);
58 }
59
60 Anim_paused = 0;
61 Anim_inited = TRUE;
62 }
63
64 /**
65 * @brief Display the frames for the currently playing anims
66 */
anim_render_all(int screen_id,float frametime)67 void anim_render_all(int screen_id, float frametime)
68 {
69 anim_instance* A;
70 anim_instance* temp;
71
72 A = GET_FIRST(&anim_render_list);
73 while( A !=END_OF_LIST(&anim_render_list) ) {
74 temp = GET_NEXT(A);
75 if ( A->screen_id == screen_id ) {
76 if ( Anim_ignore_frametime ) {
77 frametime = 0.0f;
78 Anim_ignore_frametime=0;
79 }
80 if ( anim_show_next_frame(A, frametime) == -1 ) {
81 A->data = NULL;
82 anim_release_render_instance(A);
83 }
84 }
85 A = temp;
86 }
87 }
88
89 /**
90 * @brief Display the frames for the passed animation
91 * @details It will ignore animations which do not have the same id as the passed screen_id
92 */
anim_render_one(int screen_id,anim_instance * ani,float frametime)93 void anim_render_one(int screen_id, anim_instance *ani, float frametime)
94 {
95 // make sure this guy's screen id matches the passed one
96 if(screen_id != ani->screen_id){
97 return;
98 }
99
100 // otherwise render it
101 if ( Anim_ignore_frametime ) {
102 frametime = 0.0f;
103 Anim_ignore_frametime=0;
104 }
105 if ( anim_show_next_frame(ani, frametime) == -1 ) {
106 ani->data = NULL;
107 anim_release_render_instance(ani);
108 }
109 }
110
MONITOR(NumANIPlayed)111 MONITOR(NumANIPlayed)
112
113 /**
114 * @brief Setup an anim_play_struct for passing into ::anim_play().
115 * @details Will fill in default values, which you can then change before calling ::anim_play().
116 */
117 void anim_play_init(anim_play_struct *aps, anim *a_info, int x, int y, int base_w, int base_h)
118 {
119 aps->anim_info = a_info;
120 aps->x = x;
121 aps->y = y;
122 aps->base_w = base_w;
123 aps->base_h = base_h;
124 aps->start_at = 0;
125 aps->stop_at = a_info->total_frames - 1;
126 aps->screen_id = 0;
127 aps->world_pos = NULL;
128 aps->radius = 0.0f;
129 aps->framerate_independent = 0;
130 aps->color = NULL;
131 aps->skip_frames = 1;
132 aps->looped = 0;
133 aps->ping_pong = 0;
134 }
135
136 /**
137 * @brief Will add an anim instance to the anim_render_list.
138 * This will cause the anim to be played at the x,y position specified in the parameter list.
139 *
140 * @param aps Compressed animation that we should make an instance from
141 * @return If success pointer to instance, NULL if anim anim could not be played
142 */
anim_play(anim_play_struct * aps)143 anim_instance *anim_play(anim_play_struct *aps)
144 {
145 Assert( aps->anim_info != NULL );
146 Assert( aps->start_at >= 0 );
147 Assert( aps->stop_at < aps->anim_info->total_frames );
148 Assert( !(aps->looped && aps->ping_pong) ); // shouldn't have these both set at once
149
150 MONITOR_INC(NumANIPlayed, 1);
151
152 anim_instance *instance;
153
154 // Find next free anim instance slot on queue
155 instance = GET_FIRST(&anim_free_list);
156 Assert( instance != &anim_free_list ); // shouldn't have the dummy element
157
158 // remove instance from the free list
159 list_remove( &anim_free_list, instance );
160
161 // insert instance onto the end of anim_render_list
162 list_append( &anim_render_list, instance );
163
164 aps->anim_info->instance_count++;
165 instance->frame_num = -1;
166 instance->last_frame_num = -99;
167 instance->parent = aps->anim_info;
168 instance->data = aps->anim_info->data;
169 if ( anim_instance_is_streamed(instance) ) {
170 instance->file_offset = instance->parent->file_offset;
171 }
172 instance->frame = (ubyte *) vm_malloc(instance->parent->width * instance->parent->height * 2);
173 Assert( instance->frame != NULL );
174 memset( instance->frame, 0, instance->parent->width * instance->parent->height * 2 );
175 instance->time_elapsed = 0.0f;
176 instance->stop_at = aps->stop_at;
177 instance->x = aps->x;
178 instance->y = aps->y;
179 instance->world_pos = aps->world_pos;
180 instance->radius = aps->radius;
181 instance->framerate_independent = aps->framerate_independent;
182 instance->last_bitmap = -1;
183 instance->stop_now = FALSE;
184 instance->screen_id = aps->screen_id;
185 instance->aa_color = aps->color;
186 instance->skip_frames = aps->skip_frames;
187 instance->looped = aps->looped;
188 instance->ping_pong = aps->ping_pong;
189 instance->direction = ANIM_DIRECT_FORWARD;
190 instance->paused = 0;
191 instance->loop_count = 0;
192 if ( aps->color == NULL ){
193 instance->xlate_pal = 1;
194 } else {
195 instance->xlate_pal = 0;
196 }
197
198 if(aps->base_w < 0 || aps->base_h < 0) {
199 instance->base_w = gr_screen.max_w_unscaled;
200 instance->base_h = gr_screen.max_h_unscaled;
201 } else {
202 instance->base_w = aps->base_w;
203 instance->base_h = aps->base_h;
204 }
205
206 // determining the start_at frame is more complicated, since it must be a key-frame.
207 // Futhermore, need to subtract 1 from key-frame number, since frame number is always
208 // incremented the first time anim_show_next_frame() is called
209
210 instance->start_at = aps->start_at;
211
212 if ( aps->start_at > 0 ) {
213 key_frame *keyp;
214 int idx;
215 int key = 0;
216 int offset = 0;
217 int frame_num = aps->start_at;
218
219 keyp = instance->parent->keys;
220 idx = 0;
221 while (idx < instance->parent->num_keys) {
222 if (key == frame_num)
223 break;
224
225 key = keyp[idx].frame_num - 1;
226 offset = keyp[idx].offset;
227
228 idx++;
229 }
230
231 if (key > instance->frame_num) { // best key is closer than current position
232 instance->frame_num = key;
233 if ( anim_instance_is_streamed(instance) ) {
234 instance->file_offset = instance->parent->file_offset + offset;
235 } else {
236 instance->data = instance->parent->data + offset;
237 }
238
239 }
240
241 instance->frame_num--; // required
242 }
243
244 return instance;
245 }
246
247 /**
248 * @brief This function is called to blit the next frame of an anim instance to the screen.
249 * This is normally called by the anim_render_all() function.
250 *
251 * @param instance Pointer to animation instance
252 * @param frametime Time elapsed since last call, in seconds
253 */
anim_show_next_frame(anim_instance * instance,float frametime)254 int anim_show_next_frame(anim_instance *instance, float frametime)
255 {
256 int bitmap_id, bitmap_flags=0, new_frame_num, frame_diff=0, i, n_frames=0,frame_save;
257 float percent_through, decompress_time, render_time, time;
258 vertex image_vertex;
259 int aabitmap = 0;
260 int bpp = 16;
261
262 Assert( instance != NULL );
263
264 instance->time_elapsed += frametime;
265
266 // Advance to the next frame, if we determine enough time has elapsed.
267 if(instance->direction == ANIM_DIRECT_FORWARD)
268 n_frames = instance->stop_at - instance->start_at + 1;
269 else if(instance->direction == ANIM_DIRECT_REVERSE)
270 n_frames = instance->start_at - instance->stop_at + 1;
271
272 time = n_frames / i2fl(instance->parent->fps);
273
274 percent_through = instance->time_elapsed / time;
275
276 if(instance->direction == ANIM_DIRECT_FORWARD)
277 new_frame_num = instance->start_at - 1 + fl2i(percent_through * n_frames + 0.5f);
278 else
279 new_frame_num = instance->start_at - 1 - fl2i(percent_through * n_frames + 0.5f);
280
281 frame_save = instance->frame_num;
282
283 // If framerate independent, use the new_frame_num... unless instance->skip_frames is
284 // FALSE, then only advance a maximum of one frame (this is needed since some big animations
285 // should just play slower rather than taking the hit of decompressing multiple frames and
286 // creating an even greater slowdown
287 if (instance->framerate_independent) {
288 if(instance->direction == ANIM_DIRECT_FORWARD){
289 if ( new_frame_num > instance->last_frame_num) {
290 if ( instance->skip_frames )
291 instance->frame_num = new_frame_num;
292 else
293 instance->frame_num++;
294 }
295 } else if(instance->direction == ANIM_DIRECT_REVERSE){
296 if( new_frame_num < instance->last_frame_num) {
297 if ( instance->skip_frames )
298 instance->frame_num = new_frame_num;
299 else
300 instance->frame_num--;
301 }
302 }
303 }
304 else {
305 if(instance->direction == ANIM_DIRECT_FORWARD){
306 if ( new_frame_num > instance->last_frame_num) {
307 instance->frame_num++;
308 }
309 } else if(instance->direction == ANIM_DIRECT_REVERSE){
310 if ( new_frame_num < instance->last_frame_num) {
311 instance->frame_num--;
312 }
313 }
314 }
315
316 if(instance->direction == ANIM_DIRECT_FORWARD){
317 if ( instance->frame_num < instance->start_at ) {
318 instance->frame_num = instance->start_at;
319 }
320 } else if(instance->direction == ANIM_DIRECT_REVERSE){
321 if ( instance->frame_num > instance->start_at ) {
322 instance->frame_num = instance->start_at;
323 }
324 }
325
326 if ( instance->stop_now == TRUE ) {
327 return -1;
328 }
329
330 // If past the last frame, clamp to the last frame and then set the stop_now flag in the
331 // anim instance. The next iteration, the animation will stop.
332 if(instance->direction == ANIM_DIRECT_FORWARD){
333 if (instance->frame_num >= instance->stop_at ) {
334 if (instance->looped) { // looped animations
335 instance->frame_num = instance->stop_at;
336 instance->time_elapsed = 0.0f;
337 } else if(instance->ping_pong) { // pingponged animations
338 instance->frame_num = instance->stop_at;
339 anim_reverse_direction(instance);
340 } else { // one-shot animations
341 instance->frame_num = instance->stop_at;
342 instance->last_frame_num = instance->frame_num;
343 instance->stop_now = TRUE;
344 }
345 }
346 } else if(instance->direction == ANIM_DIRECT_REVERSE){
347 if (instance->frame_num <= instance->stop_at ) {
348 if (instance->looped) { // looped animations
349 instance->frame_num = instance->stop_at;
350 instance->time_elapsed = 0.0f;
351 } else if(instance->ping_pong) { // pingponged animations
352 instance->frame_num = instance->stop_at;
353 anim_reverse_direction(instance);
354 } else { // one-shot animations
355 instance->frame_num = instance->stop_at+1;
356 instance->last_frame_num = instance->frame_num;
357 instance->stop_now = TRUE;
358 }
359 }
360 }
361
362 if(instance->direction == ANIM_DIRECT_FORWARD){
363 if( instance->last_frame_num >= instance->start_at ) {
364 frame_diff = instance->frame_num - instance->last_frame_num;
365 } else {
366 frame_diff = 1;
367 }
368 } else if(instance->direction == ANIM_DIRECT_REVERSE){
369 if( instance->last_frame_num <= instance->start_at ) {
370 frame_diff = instance->last_frame_num - instance->frame_num;
371 } else {
372 frame_diff = 1;
373 }
374 }
375 Assert(frame_diff >= 0);
376 Assert( instance->frame_num >= 0 && instance->frame_num < instance->parent->total_frames );
377
378 // if the anim is paused, ignore all the above changes and still display this frame
379 if(instance->paused || Anim_paused){
380 instance->frame_num = frame_save;
381 instance->time_elapsed -= frametime;
382 frame_diff = 0;
383 }
384
385 if (instance->parent->flags & ANF_XPARENT){
386 bitmap_flags = 0;
387 }
388 bpp = 16;
389 if(instance->aa_color != NULL){
390 bitmap_flags |= BMP_AABITMAP;
391 aabitmap = 1;
392 bpp = 8;
393 }
394
395 if ( frame_diff > 0 ) {
396 instance->last_frame_num = instance->frame_num;
397
398 t1 = timer_get_fixed_seconds();
399 for ( i = 0; i < frame_diff; i++ ) {
400 anim_check_for_palette_change(instance);
401
402 // if we're playing backwards, every frame must be a keyframe and we set the data ptr here
403 if(instance->direction == ANIM_DIRECT_REVERSE){
404 if ( anim_instance_is_streamed(instance) ) {
405 instance->file_offset = instance->parent->file_offset + instance->parent->keys[instance->frame_num-1].offset;
406 } else {
407 instance->data = instance->parent->data + instance->parent->keys[instance->frame_num-1].offset;
408 }
409 }
410
411 ubyte *temp = NULL;
412 int temp_file_offset = -1;
413
414 // if we're using bitmap polys
415 BM_SELECT_TEX_FORMAT();
416
417 if ( anim_instance_is_streamed(instance) ) {
418 if ( instance->xlate_pal ){
419 temp_file_offset = unpack_frame_from_file(instance, instance->frame, instance->parent->width*instance->parent->height, instance->parent->palette_translation, aabitmap, bpp);
420 } else {
421 temp_file_offset = unpack_frame_from_file(instance, instance->frame, instance->parent->width*instance->parent->height, NULL, aabitmap, bpp);
422 }
423 } else {
424 if ( instance->xlate_pal ){
425 temp = unpack_frame(instance, instance->data, instance->frame, instance->parent->width*instance->parent->height, instance->parent->palette_translation, aabitmap, bpp);
426 } else {
427 temp = unpack_frame(instance, instance->data, instance->frame, instance->parent->width*instance->parent->height, NULL, aabitmap, bpp);
428 }
429 }
430
431 // always go back to screen format
432 BM_SELECT_SCREEN_FORMAT();
433
434 // see if we had an error during decode (corrupted anim stream)
435 if ( (temp == NULL) && (temp_file_offset < 0) ) {
436 mprintf(("ANI: Fatal ERROR at frame %i!! Aborting playback of \"%s\"...\n", instance->frame_num, instance->parent->name));
437
438 // return -1 to end all playing of this anim instanc
439 return -1;
440 }
441
442 if(instance->direction == ANIM_DIRECT_FORWARD){
443 if ( anim_instance_is_streamed(instance) ) {
444 instance->file_offset = temp_file_offset;
445 } else {
446 instance->data = temp;
447 }
448 }
449 }
450 t2 = timer_get_fixed_seconds();
451 }
452 else {
453 t2=t1=0;
454 }
455
456 // this only happens when the anim is being looped, we need to reset the last_frame_num
457 if ( (instance->time_elapsed == 0) && (instance->looped) ) {
458 instance->last_frame_num = -1;
459 instance->frame_num = -1;
460 instance->data = instance->parent->data;
461 instance->file_offset = instance->parent->file_offset;
462 instance->loop_count++;
463 }
464
465 decompress_time = f2fl(t2-t1);
466
467 t1 = timer_get_fixed_seconds();
468 if ( frame_diff == 0 && instance->last_bitmap != -1 ) {
469 bitmap_id = instance->last_bitmap;
470 }
471 else {
472 if ( instance->last_bitmap != -1 ){
473 bm_release(instance->last_bitmap);
474 }
475 bitmap_id = bm_create(bpp, instance->parent->width, instance->parent->height, instance->frame, bitmap_flags);
476 }
477
478 if ( bitmap_id == -1 ) {
479 // anim has finsished playing, free the instance frame data
480 anim_release_render_instance(instance);
481 return -1;
482
483 // NOTE: there is no need to free the instance, since it was pre-allocated as
484 // part of the anim_free_list
485 }
486 else {
487 gr_set_bitmap(bitmap_id);
488
489 // determine x,y to display the bitmap at
490 if ( instance->world_pos == NULL ) {
491 gr_set_screen_scale(instance->base_w, instance->base_h);
492 gr_set_clip(0, 0, instance->base_w, instance->base_h, GR_RESIZE_MENU);
493 if ( instance->aa_color == NULL ) {
494 gr_bitmap(instance->x, instance->y, GR_RESIZE_MENU_NO_OFFSET);
495 }
496 else {
497 gr_set_color_fast( (color*)instance->aa_color );
498 gr_aabitmap(instance->x, instance->y, GR_RESIZE_MENU_NO_OFFSET);
499 }
500 gr_reset_screen_scale();
501 gr_reset_clip();
502 }
503 else {
504 g3_rotate_vertex(&image_vertex,instance->world_pos);
505 Assert(instance->radius != 0.0f);
506 g3_draw_bitmap(&image_vertex, 0, instance->radius*1.5f, TMAP_FLAG_TEXTURED | TMAP_HTL_2D);
507 }
508
509 instance->last_bitmap = bitmap_id;
510 }
511
512 t2 = timer_get_fixed_seconds();
513 render_time = f2fl(t2-t1);
514
515 return 0;
516 }
517
518 /**
519 * @brief Stop an anim instance that is on the anim_render_list from playing
520 */
anim_stop_playing(anim_instance * instance)521 int anim_stop_playing(anim_instance* instance)
522 {
523 Assert(instance != NULL);
524
525 if ( anim_playing(instance) ) {
526 anim_release_render_instance(instance);
527 }
528 return 0;
529 }
530
531 /**
532 * @brief Free a particular animation instance that is on the anim_render_list.
533 * Do not call this function to free an animation instance in general (use
534 * free_anim_instance() for that), only when you want to free an instance
535 * that is on the anim_render_list
536 */
anim_release_render_instance(anim_instance * instance)537 void anim_release_render_instance(anim_instance* instance)
538 {
539 Assert( instance != NULL );
540
541 if (instance->frame != NULL)
542 vm_free(instance->frame);
543
544 instance->frame = NULL;
545 instance->parent->instance_count--;
546
547 if ( instance->last_bitmap != -1 ) {
548 bm_release(instance->last_bitmap);
549 instance->last_bitmap = -1;
550 }
551
552 // remove instance from anim_render_list
553 list_remove( &anim_render_list, instance );
554
555 // insert instance into the anim_free_list
556 list_append( &anim_free_list, instance );
557 }
558
559 /**
560 * @brief Free all anim instances that are on the anim_render_list.
561 *
562 * @param screen_id Optional parameter that lets you only free a subset of the anim instances.
563 * A screen_id of 0 is the default value, and this is used for animations that always play when
564 * they are placed on the aim_render_list.
565 */
anim_release_all_instances(int screen_id)566 void anim_release_all_instances(int screen_id)
567 {
568 anim_instance* A;
569 anim_instance* temp;
570
571 if ( Anim_inited == FALSE )
572 return;
573
574 A = GET_FIRST(&anim_render_list);
575 while( A !=END_OF_LIST(&anim_render_list) ) {
576 temp = GET_NEXT(A);
577 if ( A->screen_id == screen_id || screen_id == 0 ) {
578 anim_release_render_instance(A);
579 }
580 A = temp;
581 }
582 }
583
584 // -----------------------------------------------------------------------------
585 // anim_read_header()
586 //
587 // Read the header of a .ani file. Below is the format of a .ani header
588 //
589 // #bytes | description
590 // 2 | obsolete, kept for compatibility with old versions
591 // 2 | version number
592 // 2 | fps
593 // 1 | transparent red value
594 // 1 | transparent green value
595 // 1 | transparent blue value
596 // 2 | width
597 // 2 | height
598 // 2 | number of frames
599 // 1 | packer code
600 // 763 | palette
601 // 2 | number of key frames
602 // 2 | key frame number } repeats
603 // 4 | key frame offset } repeats
604 // 4 | compressed data length
605 //
anim_read_header(anim * ptr,CFILE * fp)606 void anim_read_header(anim *ptr, CFILE *fp)
607 {
608 ptr->width = cfread_short(fp);
609 // If first 2 bytes are zero, this means we are using a new format, which includes
610 // a version, and fps values. This is only done since a version number was not included
611 // in the original header.
612
613 // default
614 Color_xparent.red = 0;
615 Color_xparent.green = 255;
616 Color_xparent.blue = 0;
617
618 if ( ptr->width == 0 ) {
619 ptr->version = cfread_short(fp);
620 ptr->fps = cfread_short(fp);
621
622 // version 2 added a custom transparency color
623 if ( ptr->version >= 2 ) {
624 cfread(&Color_xparent.red, 1, 1, fp);
625 cfread(&Color_xparent.green, 1, 1, fp);
626 cfread(&Color_xparent.blue, 1, 1, fp);
627 }
628
629 ptr->width = cfread_short(fp);
630 }
631 else {
632 ptr->version = 0;
633 ptr->fps = 30;
634 }
635
636 ptr->height = cfread_short(fp);
637
638 #ifndef NDEBUG
639 // get size of ani compared to power of 2
640 int r, floor_pow;
641 r = ptr->height;
642 floor_pow = 0;
643
644 while(r >= 2) {
645 r /= 2;
646 floor_pow++;
647 }
648
649 int floor_size = (int) pow(2.0, floor_pow);
650 int diff = ptr->height - floor_size;
651 float waste = 100.0f * float((floor_size - diff))/(2.0f *(float)floor_size);
652
653 if (diff != 0) {
654 if (ptr->height > 16) {
655 mprintf(("ANI %s with size %dx%d (%.1f%% wasted)\n", ptr->name, ptr->width, ptr->height, waste));
656 }
657 }
658 #endif
659
660 ptr->total_frames = cfread_short(fp);
661 ptr->packer_code = cfread_ubyte(fp);
662 cfread(&ptr->palette, 256, 3, fp);
663 ptr->num_keys = cfread_short(fp);
664
665 // store xparent colors
666 ptr->xparent_r = Color_xparent.red;
667 ptr->xparent_g = Color_xparent.green;
668 ptr->xparent_b = Color_xparent.blue;
669
670 if(ptr->total_frames == ptr->num_keys){
671 ptr->flags |= ANF_ALL_KEYFRAMES;
672 }
673 }
674
675 /**
676 * @brief Load an animation. This stores the compressed data, which instances of the animation can reference.
677 * Must be free'ed later with anim_free().
678 *
679 * @param real_filename Filename of animation
680 * @param cf_dir_type
681 * @param file_mapped Whether to use memory-mapped file or not.
682 *
683 * @details Memory-mapped files will page in the animation from disk as it is needed, but performance is not as good.
684 * @return Pointer to anim that is loaded if sucess, NULL if failure.
685 */
anim_load(char * real_filename,int cf_dir_type,int file_mapped)686 anim *anim_load(char *real_filename, int cf_dir_type, int file_mapped)
687 {
688 anim *ptr;
689 CFILE *fp;
690 int count,idx;
691 char name[_MAX_PATH];
692
693 Assert( real_filename != NULL );
694
695 strcpy_s( name, real_filename );
696 char *p = strchr( name, '.' );
697 if ( p ) {
698 *p = 0;
699 }
700 strcat_s( name, ".ani" );
701
702 ptr = first_anim;
703 while (ptr) {
704 if (!stricmp(name, ptr->name))
705 break;
706
707 ptr = ptr->next;
708 }
709
710 if (!ptr) {
711 fp = cfopen(name, "rb", CFILE_NORMAL, cf_dir_type);
712 if ( !fp )
713 return NULL;
714
715 ptr = (anim *) vm_malloc(sizeof(anim));
716 Assert(ptr);
717
718 ptr->flags = 0;
719 ptr->next = first_anim;
720 first_anim = ptr;
721 Assert(strlen(name) < _MAX_PATH - 1);
722 strcpy_s(ptr->name, name);
723 ptr->instance_count = 0;
724 ptr->width = 0;
725 ptr->height = 0;
726 ptr->total_frames = 0;
727 ptr->keys = NULL;
728 ptr->ref_count=0;
729
730 anim_read_header(ptr, fp);
731
732 if(ptr->num_keys > 0){
733 ptr->keys = (key_frame*)vm_malloc(sizeof(key_frame) * ptr->num_keys);
734 Assert(ptr->keys != NULL);
735 }
736
737 // store how long the anim should take on playback (in seconds)
738 ptr->time = i2fl(ptr->total_frames)/ptr->fps;
739
740 for(idx=0;idx<ptr->num_keys;idx++){
741 ptr->keys[idx].frame_num = 0;
742 cfread(&ptr->keys[idx].frame_num, 2, 1, fp);
743 cfread(&ptr->keys[idx].offset, 4, 1, fp);
744 ptr->keys[idx].frame_num = INTEL_INT( ptr->keys[idx].frame_num ); //-V570
745 ptr->keys[idx].offset = INTEL_INT( ptr->keys[idx].offset ); //-V570
746 }
747
748 cfread(&count, 4, 1, fp); // size of compressed data
749 count = INTEL_INT( count );
750
751 ptr->cfile_ptr = NULL;
752
753 if ( file_mapped == PAGE_FROM_MEM) {
754 // Try mapping the file to memory
755 ptr->flags |= ANF_MEM_MAPPED;
756 ptr->cfile_ptr = cfopen(name, "rb", CFILE_MEMORY_MAPPED, cf_dir_type);
757 }
758
759 // couldn't memory-map file... must be in a packfile, so stream manually
760 if ( file_mapped && !ptr->cfile_ptr ) {
761 ptr->flags &= ~ANF_MEM_MAPPED;
762 ptr->flags |= ANF_STREAMED;
763 ptr->cfile_ptr = cfopen(name, "rb", CFILE_NORMAL, cf_dir_type);
764 }
765
766 ptr->cache = NULL;
767
768 // If it opened properly as mem-mapped (or streamed)
769 if (ptr->cfile_ptr != NULL) {
770 // VERY IMPORTANT STEP
771 // Set the data pointer to the compressed data (which is not at the start of the
772 // file). Use ftell() to find out how far we've already parsed into the file
773 //
774 int offset;
775 offset = cftell(fp);
776 ptr->file_offset = offset;
777 if ( ptr->flags & ANF_STREAMED ) {
778 ptr->data = NULL;
779 ptr->cache_file_offset = ptr->file_offset;
780 ptr->cache = (ubyte*)vm_malloc(ANI_STREAM_CACHE_SIZE+2);
781 Assert(ptr->cache);
782 cfseek(ptr->cfile_ptr, offset, CF_SEEK_SET);
783 cfread(ptr->cache, ANI_STREAM_CACHE_SIZE, 1, ptr->cfile_ptr);
784 } else {
785 ptr->data = (ubyte*)cf_returndata(ptr->cfile_ptr) + offset;
786 }
787 } else {
788 // Not a memory mapped file (or streamed)
789 ptr->flags &= ~ANF_MEM_MAPPED;
790 ptr->flags &= ~ANF_STREAMED;
791 ptr->data = (ubyte *) vm_malloc(count);
792 ptr->file_offset = -1;
793 cfread(ptr->data, count, 1, fp);
794 }
795
796 cfclose(fp);
797
798 // store screen signature, so we can tell if palette changes
799 ptr->screen_sig = gr_screen.signature;
800
801 anim_set_palette(ptr);
802 }
803
804 ptr->ref_count++;
805 return ptr;
806 }
807
808 /**
809 * @brief Free an animation that was loaded with anim_load().
810 * @details All instances referencing this animation must be free'ed or get an assert.
811 */
anim_free(anim * ptr)812 int anim_free(anim *ptr)
813 {
814 Assert ( ptr != NULL );
815 anim *list, **prev_anim;
816
817 list = first_anim;
818 prev_anim = &first_anim;
819 while (list && (list != ptr)) {
820 prev_anim = &list->next;
821 list = list->next;
822 }
823
824 if ( !list )
825 return -2;
826
827 // only free when ref_count is 0
828 ptr->ref_count--;
829 if ( ptr->ref_count > 0 )
830 return -1;
831
832 // only free if there are no playing instances
833 if ( ptr->instance_count > 0 )
834 return -1;
835
836 if(ptr->keys != NULL){
837 vm_free(ptr->keys);
838 ptr->keys = NULL;
839 }
840
841 if ( ptr->flags & (ANF_MEM_MAPPED|ANF_STREAMED) ) {
842 cfclose(ptr->cfile_ptr);
843 if (ptr->cache != NULL) {
844 vm_free(ptr->cache);
845 ptr->cache = NULL;
846 }
847 }
848 else {
849 Assert(ptr->data);
850 if (ptr->data != NULL) {
851 vm_free(ptr->data);
852 ptr->data = NULL;
853 }
854 }
855
856 *prev_anim = ptr->next;
857 vm_free(ptr);
858 return 0;
859 }
860
861
862 /**
863 * @brief Return if an anim is playing or not.
864 */
anim_playing(anim_instance * ai)865 int anim_playing(anim_instance *ai)
866 {
867 Assert(ai != NULL);
868 if ( ai->frame == NULL )
869 return 0;
870 else
871 return 1;
872 }
873
874
875 /**
876 * @brief Called at the beginning of a mission to initialize any mission dependent anim data.
877 * @todo Redundant?
878 */
anim_level_init()879 void anim_level_init()
880 {
881 }
882
883 /**
884 * @brief Called after the end of a mission to clean up any mission dependent anim data.
885 */
anim_level_close()886 void anim_level_close()
887 {
888 anim_release_all_instances();
889 }
890
891 /**
892 * @brief Write the frames of a .ani file out to disk as .pcx files.
893 * @details Use naming convention: filename0000.pcx, filename0001.pcx etc.
894 *
895 * @return If success 0, or if failed -1
896 */
anim_write_frames_out(char * filename)897 int anim_write_frames_out(char *filename)
898 {
899 anim *source_anim;
900 anim_instance *ai;
901 char root_name[256], pcxname[256];
902 char buf[64];
903 int i,j;
904 ubyte **row_data;
905
906 strcpy_s(root_name, filename);
907 root_name[strlen(filename)-4] = 0;
908
909 source_anim = anim_load(filename);
910 if ( source_anim == NULL )
911 return -1;
912
913 ai = init_anim_instance(source_anim, 16);
914
915 row_data = (ubyte**)vm_malloc((source_anim->height+1) * 4);
916
917 for ( i = 0; i < source_anim->total_frames; i++ ) {
918 anim_get_next_raw_buffer(ai, 0, 0, 16);
919 strcpy_s(pcxname, root_name);
920 sprintf(buf,"%04d",i);
921 strcat_s(pcxname, buf);
922
923 for ( j = 0; j < source_anim->height; j++ ) {
924 row_data[j] = &ai->frame[j*source_anim->width];
925 }
926
927
928 pcx_write_bitmap( pcxname,
929 source_anim->width,
930 source_anim->height,
931 row_data,
932 source_anim->palette);
933
934 printf(".");
935
936 }
937 printf("\n");
938 vm_free(row_data);
939 return 0;
940 }
941
942 /**
943 * @brief Display information and statistics about a .ani file.
944 * @details This is called when -i switch is on when running ac.exe
945 */
anim_display_info(char * real_filename)946 void anim_display_info(char *real_filename)
947 {
948 CFILE *fp;
949 anim A;
950 float percent;
951 int i, uncompressed, compressed, *key_frame_nums=NULL, tmp;
952 char filename[MAX_FILENAME_LEN];
953
954 strcpy_s( filename, real_filename );
955 char *p = strchr( filename, '.' );
956 if ( p ) {
957 *p = 0;
958 }
959 strcat_s( filename, ".ani" );
960
961 fp = cfopen(filename, "rb");
962 if ( !fp ) {
963 printf("Fatal error opening %s", filename);
964 return;
965 }
966
967 anim_read_header(&A, fp);
968 // read the keyframe frame nums and offsets
969 key_frame_nums = (int*)vm_malloc(sizeof(int)*A.num_keys);
970 Assert(key_frame_nums != NULL);
971 if (key_frame_nums == NULL)
972 return;
973
974 for ( i = 0; i < A.num_keys; i++ ) {
975 key_frame_nums[i] = 0;
976 cfread(&key_frame_nums[i], 2, 1, fp);
977 cfread(&tmp, 4, 1, fp);
978 }
979
980 cfread(&compressed, 4, 1, fp);
981
982 uncompressed = A.width * A.height * A.total_frames; // 8 bits per pixel
983 percent = i2fl(compressed) / uncompressed * 100.0f;
984
985 printf("%% of uncompressed size: %.0f%%\n", percent);
986 printf("Width: %d\n", A.width);
987 printf("Height: %d\n", A.height);
988 printf("Total Frames: %d\n", A.total_frames);
989
990 #ifndef NDEBUG
991 printf("Key Frames: %d\n", A.num_keys);
992 if ( A.num_keys > 1 && (A.total_frames != A.num_keys) ) {
993 printf("key list: (");
994 for ( i = 0; i < A.num_keys; i++ ) {
995 if ( i < A.num_keys-1 )
996 printf("%d, ", key_frame_nums[i]);
997 else
998 printf("%d)\n", key_frame_nums[i]);
999 }
1000 }
1001 #endif
1002
1003 printf("FPS: %d\n", A.fps);
1004
1005 #ifndef NDEBUG
1006 printf("Transparent RGB: (%u,%u,%u)\n", A.xparent_r, A.xparent_g, A.xparent_b);
1007 #endif
1008
1009 printf("ac version: %d\n", A.version);
1010
1011 if ( key_frame_nums != NULL ) {
1012 vm_free(key_frame_nums);
1013 }
1014
1015 cfclose(fp);
1016 }
1017
anim_reverse_direction(anim_instance * ai)1018 void anim_reverse_direction(anim_instance *ai)
1019 {
1020 int temp;
1021
1022 // you're not allowed to call anim_reverse_direction(...) unless every frame is a keyframe!!!!
1023 // The God of Delta-RLE demands it be thus.
1024 Assertion( ai->parent->flags & ANF_ALL_KEYFRAMES, "Ani was set to play backwards. In order to enable this, all frames of the animation MUST be keyframes.");
1025
1026 // flip the animation direction
1027 if(ai->direction == ANIM_DIRECT_FORWARD){
1028 ai->direction = ANIM_DIRECT_REVERSE;
1029 } else if(ai->direction == ANIM_DIRECT_REVERSE){
1030 ai->direction = ANIM_DIRECT_FORWARD;
1031 }
1032
1033 // flip frame_num and last_frame_num
1034 temp = ai->frame_num;
1035 ai->frame_num = ai->last_frame_num;
1036 ai->last_frame_num = temp;
1037
1038 // flip the start and stop at frames
1039 temp = ai->stop_at;
1040 ai->stop_at = ai->start_at;
1041 ai->start_at = temp;
1042
1043 // make sure to sync up the time correctly
1044 if(ai->direction == ANIM_DIRECT_FORWARD){
1045 ai->time_elapsed = ((float)ai->frame_num - (float)ai->start_at - 1.0f) / (float)ai->parent->fps;
1046 } else if(ai->direction == ANIM_DIRECT_REVERSE) {
1047 ai->time_elapsed = ((float)ai->start_at - (float)ai->frame_num - 1.0f) / (float)ai->parent->fps;
1048 }
1049 }
1050
anim_pause(anim_instance * ai)1051 void anim_pause(anim_instance *ai)
1052 {
1053 ai->paused = 1;
1054 }
1055
anim_unpause(anim_instance * ai)1056 void anim_unpause(anim_instance *ai)
1057 {
1058 ai->paused = 0;
1059 }
1060
anim_ignore_next_frametime()1061 void anim_ignore_next_frametime()
1062 {
1063 Anim_ignore_frametime=1;
1064 }
1065
anim_instance_is_streamed(anim_instance * ai)1066 int anim_instance_is_streamed(anim_instance *ai)
1067 {
1068 Assert(ai);
1069 return ( ai->parent->flags & ANF_STREAMED );
1070 }
1071
anim_instance_get_byte(anim_instance * ai,int offset)1072 unsigned char anim_instance_get_byte(anim_instance *ai, int offset)
1073 {
1074 int absolute_offset;
1075 anim *parent;
1076
1077 Assert(ai);
1078 Assert(ai->parent->cfile_ptr);
1079 Assert(ai->parent->flags & ANF_STREAMED);
1080
1081 parent = ai->parent;
1082 absolute_offset = ai->file_offset + offset;
1083
1084 // maybe in cache?
1085 int cache_offset;
1086 cache_offset = absolute_offset - parent->cache_file_offset;
1087 if ( (cache_offset >= 0) && (cache_offset < ANI_STREAM_CACHE_SIZE) ) {
1088 return parent->cache[cache_offset];
1089 } else {
1090 // fill cache
1091 cfseek(parent->cfile_ptr, absolute_offset, CF_SEEK_SET);
1092 cfread(parent->cache, ANI_STREAM_CACHE_SIZE, 1, parent->cfile_ptr);
1093 parent->cache_file_offset = absolute_offset;
1094 return parent->cache[0];
1095 }
1096 }
1097