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 "anim/packunpack.h"
14 #include "bmpman/bmpman.h"
15 #include "cfile/cfile.h"
16 #include "cmdline/cmdline.h"
17 #include "globalincs/linklist.h"
18 #include "graphics/2d.h"
19 #include "io/timer.h"
20 #include "pcxutils/pcxutils.h"
21 #include "render/3d.h"
22 #include "graphics/material.h"
23 #include "tracing/Monitor.h"
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_zoomed;
200 		instance->base_h = gr_screen.max_h_unscaled_zoomed;
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, 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 + (int) lround(percent_through * n_frames);
278 	else
279 		new_frame_num = instance->start_at - 1 - (int) lround(percent_through * n_frames);
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 	t1 = timer_get_fixed_seconds();
466 	if ( frame_diff == 0 && instance->last_bitmap != -1 ) {
467 		bitmap_id = instance->last_bitmap;
468 	}
469 	else {
470 		if ( instance->last_bitmap != -1 ){
471 			bm_release(instance->last_bitmap);
472 		}
473 		bitmap_id = bm_create(bpp, instance->parent->width, instance->parent->height, instance->frame, bitmap_flags);
474 	}
475 
476 	if ( bitmap_id == -1 ) {
477 		// anim has finsished playing, free the instance frame data
478 		anim_release_render_instance(instance);
479 		return -1;
480 
481 		// NOTE: there is no need to free the instance, since it was pre-allocated as
482 		//       part of the anim_free_list
483 	}
484 	else {
485 		gr_set_bitmap(bitmap_id);
486 
487 		// determine x,y to display the bitmap at
488 		if ( instance->world_pos == NULL ) {
489 			int old_max_w_unscaled = gr_screen.max_w_unscaled;
490 			int old_max_h_unscaled = gr_screen.max_h_unscaled;
491 			int old_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed;
492 			int old_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;
493 			gr_set_screen_scale(instance->base_w, instance->base_h);
494 			gr_set_clip(0, 0, instance->base_w, instance->base_h, GR_RESIZE_MENU);
495 			if ( instance->aa_color == NULL ) {
496 				gr_bitmap(instance->x, instance->y, GR_RESIZE_MENU_NO_OFFSET);
497 			}
498 			else {
499 				gr_set_color_fast( (color*)instance->aa_color );
500 				gr_aabitmap(instance->x, instance->y, GR_RESIZE_MENU_NO_OFFSET);
501 			}
502 			gr_set_screen_scale(old_max_w_unscaled, old_max_h_unscaled, old_max_w_unscaled_zoomed, old_max_h_unscaled_zoomed);
503 			gr_reset_clip();
504 		}
505 		else {
506 			g3_rotate_vertex(&image_vertex,instance->world_pos);
507 			Assert(instance->radius != 0.0f);
508 			//g3_draw_bitmap(&image_vertex, 0, instance->radius*1.5f, TMAP_FLAG_TEXTURED | TMAP_HTL_2D);
509 			material mat_params;
510 			material_set_unlit(&mat_params, bitmap_id, 1.0f, false, false);
511 			g3_render_rect_screen_aligned_2d(&mat_params, &image_vertex, 0, instance->radius*1.5f);
512 		}
513 
514 		instance->last_bitmap = bitmap_id;
515 	}
516 
517 	t2 = timer_get_fixed_seconds();
518 
519 	return 0;
520 }
521 
522 /**
523  * @brief Free a particular animation instance that is on the anim_render_list.
524  * Do not call this function to free an animation instance in general (use
525  * free_anim_instance() for that), only when you want to free an instance
526  * that is on the anim_render_list
527  */
anim_release_render_instance(anim_instance * instance)528 void anim_release_render_instance(anim_instance* instance)
529 {
530 	Assert( instance != NULL );
531 
532 	if (instance->frame != NULL)
533 		vm_free(instance->frame);
534 
535 	instance->frame = NULL;
536 	instance->parent->instance_count--;
537 
538 	if ( instance->last_bitmap != -1 ) {
539 		bm_release(instance->last_bitmap);
540 		instance->last_bitmap = -1;
541 	}
542 
543 	// remove instance from anim_render_list
544 	list_remove( &anim_render_list, instance );
545 
546 	// insert instance into the anim_free_list
547 	list_append( &anim_free_list, instance );
548 }
549 
550 /**
551  * @brief Free all anim instances that are on the anim_render_list.
552  *
553  * @param screen_id	Optional parameter that lets you only free a subset of the anim instances.
554  * A screen_id of 0 is the default value, and this is used for animations that always play when
555  * they are placed on the aim_render_list.
556  */
anim_release_all_instances(int screen_id)557 void anim_release_all_instances(int screen_id)
558 {
559 	anim_instance* A;
560 	anim_instance* temp;
561 
562 	if ( Anim_inited == FALSE )
563 		return;
564 
565 	A = GET_FIRST(&anim_render_list);
566 	while( A !=END_OF_LIST(&anim_render_list) )	{
567 		temp = GET_NEXT(A);
568 		if ( A->screen_id == screen_id || screen_id == 0 ) {
569 			anim_release_render_instance(A);
570 		}
571 		A = temp;
572 	}
573 }
574 
575 // -----------------------------------------------------------------------------
576 //	anim_read_header()
577 //
578 // Read the header of a .ani file.  Below is the format of a .ani header
579 //
580 //	#bytes		|	description
581 //	2			|	obsolete, kept for compatibility with old versions
582 //	2			|	version number
583 //	2			|	fps
584 //	1			|	transparent red value
585 //  1			|	transparent green value
586 //	1			|	transparent blue value
587 //	2			|	width
588 //	2			|	height
589 //	2			|	number of frames
590 //	1			|	packer code
591 //	763			|	palette
592 //	2			|	number of key frames
593 //	2			|	key frame number	}		repeats
594 //	4			|	key frame offset	}		repeats
595 //	4			|	compressed data length
596 //
anim_read_header(anim * ptr,CFILE * fp)597 void anim_read_header(anim *ptr, CFILE *fp)
598 {
599 	ptr->width = cfread_short(fp);
600 	// If first 2 bytes are zero, this means we are using a new format, which includes
601 	// a version, and fps values. This is only done since a version number was not included
602 	// in the original header.
603 
604 	// default
605 	Color_xparent.red = 0;
606 	Color_xparent.green = 255;
607 	Color_xparent.blue = 0;
608 
609 	if ( ptr->width == 0 ) {
610 		ptr->version = cfread_short(fp);
611 		ptr->fps = cfread_short(fp);
612 
613 		// version 2 added a custom transparency color
614 		if ( ptr->version >= 2 ) {
615 			cfread(&Color_xparent.red, 1, 1, fp);
616 			cfread(&Color_xparent.green, 1, 1, fp);
617 			cfread(&Color_xparent.blue, 1, 1, fp);
618 		}
619 
620 		ptr->width = cfread_short(fp);
621 	}
622 	else {
623 		ptr->version = 0;
624 		ptr->fps = 30;
625 	}
626 
627 	ptr->height = cfread_short(fp);
628 
629 #ifndef NDEBUG
630 	// get size of ani compared to power of 2
631 	int r, floor_pow;
632 	r = ptr->height;
633 	floor_pow = 0;
634 
635 	while(r >= 2) {
636 		r /= 2;
637 		floor_pow++;
638 	}
639 
640 	int floor_size = (int) pow(2.0, floor_pow);
641 	int diff = ptr->height - floor_size;
642 	float waste = 100.0f * float((floor_size - diff))/(2.0f *(float)floor_size);
643 
644 	if (diff != 0) {
645 		if (ptr->height > 16) {
646 			mprintf(("ANI %s with size %dx%d (%.1f%% wasted)\n", ptr->name, ptr->width, ptr->height, waste));
647 		}
648 	}
649 #endif
650 
651 	ptr->total_frames = cfread_short(fp);
652 	ptr->packer_code = cfread_ubyte(fp);
653 	cfread(&ptr->palette, 256, 3, fp);
654 	ptr->num_keys = cfread_short(fp);
655 
656 	// store xparent colors
657 	ptr->xparent_r = Color_xparent.red;
658 	ptr->xparent_g = Color_xparent.green;
659 	ptr->xparent_b = Color_xparent.blue;
660 
661 	if(ptr->total_frames == ptr->num_keys){
662 		ptr->flags |= ANF_ALL_KEYFRAMES;
663 	}
664 }
665 
666 /**
667  * @brief Load an animation.  This stores the compressed data, which instances of the animation can reference.
668  * Must be free'ed later with anim_free().
669  *
670  * @param real_filename Filename of animation
671  * @param cf_dir_type
672  * @param file_mapped Whether to use memory-mapped file or not.
673  *
674  * @details Memory-mapped files will page in the animation from disk as it is needed, but performance is not as good.
675  * @return Pointer to anim that is loaded if sucess, NULL if failure.
676  */
anim_load(const char * real_filename,int cf_dir_type,int file_mapped)677 anim *anim_load(const char *real_filename, int cf_dir_type, int file_mapped)
678 {
679 	anim			*ptr;
680 	CFILE			*fp;
681 	int			count,idx;
682 	char name[_MAX_PATH];
683 
684 	Assert( real_filename != NULL );
685 
686 	strcpy_s( name, real_filename );
687 	char *p = strchr( name, '.' );
688 	if ( p ) {
689 		*p = 0;
690 	}
691 	strcat_s( name, ".ani" );
692 
693 	ptr = first_anim;
694 	while (ptr) {
695 		if (!stricmp(name, ptr->name))
696 			break;
697 
698 		ptr = ptr->next;
699 	}
700 
701 	if (!ptr) {
702 		fp = cfopen(name, "rb", CFILE_NORMAL, cf_dir_type);
703 		if ( !fp )
704 			return NULL;
705 
706 		ptr = (anim *) vm_malloc(sizeof(anim));
707 		Assert(ptr);
708 
709 		ptr->flags = 0;
710 		ptr->next = first_anim;
711 		first_anim = ptr;
712 		Assert(strlen(name) < _MAX_PATH - 1);
713 		strcpy_s(ptr->name, name);
714 		ptr->instance_count = 0;
715 		ptr->width = 0;
716 		ptr->height = 0;
717 		ptr->total_frames = 0;
718 		ptr->keys = NULL;
719 		ptr->ref_count=0;
720 
721 		anim_read_header(ptr, fp);
722 
723 		if (ptr->width < 0 || ptr->height < 0) {
724 			Error(LOCATION, "Ani file %s has a faulty header and cannot be loaded.", name);
725 		}
726 
727 		if(ptr->num_keys > 0){
728 			ptr->keys = (key_frame*)vm_malloc(sizeof(key_frame) * ptr->num_keys);
729 			Assert(ptr->keys != NULL);
730 		}
731 
732 		// store how long the anim should take on playback (in seconds)
733 		ptr->time = i2fl(ptr->total_frames)/ptr->fps;
734 
735 		for(idx=0;idx<ptr->num_keys;idx++){
736 			ptr->keys[idx].frame_num = 0;
737 			cfread(&ptr->keys[idx].frame_num, 2, 1, fp);
738 			cfread(&ptr->keys[idx].offset, 4, 1, fp);
739 			ptr->keys[idx].frame_num = INTEL_INT( ptr->keys[idx].frame_num ); //-V570
740 			ptr->keys[idx].offset = INTEL_INT( ptr->keys[idx].offset ); //-V570
741 		}
742 
743 		cfread(&count, 4, 1, fp);	// size of compressed data
744 		count = INTEL_INT( count );
745 
746 		ptr->cfile_ptr = NULL;
747 
748 		if ( file_mapped == PAGE_FROM_MEM) {
749 			// Try mapping the file to memory
750 			ptr->flags |= ANF_MEM_MAPPED;
751 			ptr->cfile_ptr = cfopen(name, "rb", CFILE_MEMORY_MAPPED, cf_dir_type);
752 		}
753 
754 		// couldn't memory-map file... must be in a packfile, so stream manually
755 		if ( file_mapped == PAGE_FROM_MEM && !ptr->cfile_ptr ) {
756 			ptr->flags &= ~ANF_MEM_MAPPED;
757 			ptr->flags |= ANF_STREAMED;
758 			ptr->cfile_ptr = cfopen(name, "rb", CFILE_NORMAL, cf_dir_type);
759 		}
760 
761 		ptr->cache = NULL;
762 
763 		// If it opened properly as mem-mapped (or streamed)
764 		if (ptr->cfile_ptr != NULL)	{
765 			// VERY IMPORTANT STEP
766 			// Set the data pointer to the compressed data (which is not at the start of the
767 			// file).  Use ftell() to find out how far we've already parsed into the file
768 			//
769 			int offset;
770 			offset = cftell(fp);
771 			ptr->file_offset = offset;
772 			if ( ptr->flags & ANF_STREAMED ) {
773 				ptr->data = NULL;
774 				ptr->cache_file_offset = ptr->file_offset;
775 				ptr->cache = (ubyte*)vm_malloc(ANI_STREAM_CACHE_SIZE+2);
776 				Assert(ptr->cache);
777 				cfseek(ptr->cfile_ptr, offset, CF_SEEK_SET);
778 				cfread(ptr->cache, ANI_STREAM_CACHE_SIZE, 1, ptr->cfile_ptr);
779 			} else {
780 				ptr->data = (ubyte*)cf_returndata(ptr->cfile_ptr) + offset;
781 			}
782 		} else {
783 			// Not a memory mapped file (or streamed)
784 			ptr->flags &= ~ANF_MEM_MAPPED;
785 			ptr->flags &= ~ANF_STREAMED;
786 			ptr->data = (ubyte *) vm_malloc(count);
787 			ptr->file_offset = -1;
788 			cfread(ptr->data, count, 1, fp);
789 		}
790 
791 		cfclose(fp);
792 
793 		// store screen signature, so we can tell if palette changes
794 		ptr->screen_sig = gr_screen.signature;
795 
796 		anim_set_palette(ptr);
797 	}
798 
799 	ptr->ref_count++;
800 	return ptr;
801 }
802 
803 /**
804  * @brief Free an animation that was loaded with anim_load().
805  * @details All instances referencing this animation must be free'ed or get an assert.
806  */
anim_free(anim * ptr)807 int anim_free(anim *ptr)
808 {
809 	Assert ( ptr != NULL );
810 	anim *list, **prev_anim;
811 
812 	list = first_anim;
813 	prev_anim = &first_anim;
814 	while (list && (list != ptr)) {
815 		prev_anim = &list->next;
816 		list = list->next;
817 	}
818 
819 	if ( !list )
820 		return -2;
821 
822 	// only free when ref_count is 0
823 	ptr->ref_count--;
824 	if ( ptr->ref_count > 0 )
825 		return -1;
826 
827 	// only free if there are no playing instances
828 	if ( ptr->instance_count > 0 )
829 		return -1;
830 
831 	if(ptr->keys != NULL){
832 		vm_free(ptr->keys);
833 		ptr->keys = NULL;
834 	}
835 
836 	if ( ptr->flags & (ANF_MEM_MAPPED|ANF_STREAMED) ) {
837 		cfclose(ptr->cfile_ptr);
838 		if (ptr->cache != NULL) {
839 			vm_free(ptr->cache);
840 			ptr->cache = NULL;
841 		}
842 	}
843 	else {
844 		Assert(ptr->data);
845 		if (ptr->data != NULL) {
846 			vm_free(ptr->data);
847 			ptr->data = NULL;
848 		}
849 	}
850 
851 	*prev_anim = ptr->next;
852 	vm_free(ptr);
853 	return 0;
854 }
855 
856 /**
857  * @brief Called at the beginning of a mission to initialize any mission dependent anim data.
858  * @todo Redundant?
859  */
anim_level_init()860 void anim_level_init()
861 {
862 }
863 
864 /**
865  * @brief Called after the end of a mission to clean up any mission dependent anim data.
866  */
anim_level_close()867 void anim_level_close()
868 {
869 	anim_release_all_instances();
870 }
871 
anim_reverse_direction(anim_instance * ai)872 void anim_reverse_direction(anim_instance *ai)
873 {
874 	int temp;
875 
876 	// you're not allowed to call anim_reverse_direction(...) unless every frame is a keyframe!!!!
877 	// The God of Delta-RLE demands it be thus.
878 	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.");
879 
880 	// flip the animation direction
881 	if(ai->direction == ANIM_DIRECT_FORWARD){
882 		ai->direction = ANIM_DIRECT_REVERSE;
883 	} else if(ai->direction == ANIM_DIRECT_REVERSE){
884 		ai->direction = ANIM_DIRECT_FORWARD;
885 	}
886 
887 	// flip frame_num and last_frame_num
888 	temp = ai->frame_num;
889 	ai->frame_num = ai->last_frame_num;
890 	ai->last_frame_num = temp;
891 
892 	// flip the start and stop at frames
893 	temp = ai->stop_at;
894 	ai->stop_at = ai->start_at;
895 	ai->start_at = temp;
896 
897 	// make sure to sync up the time correctly
898 	if(ai->direction == ANIM_DIRECT_FORWARD){
899 		ai->time_elapsed = ((float)ai->frame_num - (float)ai->start_at - 1.0f) / (float)ai->parent->fps;
900 	} else if(ai->direction == ANIM_DIRECT_REVERSE) {
901 		ai->time_elapsed = ((float)ai->start_at - (float)ai->frame_num - 1.0f) / (float)ai->parent->fps;
902 	}
903 }
904 
anim_ignore_next_frametime()905 void anim_ignore_next_frametime()
906 {
907 	Anim_ignore_frametime=1;
908 }
909 
anim_instance_is_streamed(anim_instance * ai)910 int anim_instance_is_streamed(anim_instance *ai)
911 {
912 	Assert(ai);
913 	return ( ai->parent->flags & ANF_STREAMED );
914 }
915 
anim_instance_get_byte(anim_instance * ai,int offset)916 unsigned char anim_instance_get_byte(anim_instance *ai, int offset)
917 {
918 	int absolute_offset;
919 	anim *parent;
920 
921 	Assert(ai);
922 	Assert(ai->parent->cfile_ptr);
923 	Assert(ai->parent->flags & ANF_STREAMED);
924 
925 	parent = ai->parent;
926 	absolute_offset = ai->file_offset + offset;
927 
928 	// maybe in cache?
929 	int cache_offset;
930 	cache_offset = absolute_offset - parent->cache_file_offset;
931 	if ( (cache_offset >= 0) && (cache_offset < ANI_STREAM_CACHE_SIZE) ) {
932 		return parent->cache[cache_offset];
933 	} else {
934 		// fill cache
935 		cfseek(parent->cfile_ptr, absolute_offset, CF_SEEK_SET);
936 		cfread(parent->cache, ANI_STREAM_CACHE_SIZE, 1, parent->cfile_ptr);
937 		parent->cache_file_offset = absolute_offset;
938 		return parent->cache[0];
939 	}
940 }
941