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