1 #include "anim/packunpack.h"
2 #include "globalincs/globals.h"
3 #include "graphics/2d.h"
4 #include "graphics/generic.h"
5 #define BMPMAN_INTERNAL
6 #include "bmpman/bm_internal.h"
7 #ifdef _WIN32
8 #include <windows.h> // for MAX_PATH
9 #else
10 #define MAX_PATH 255
11 #endif
12 //#define TIMER
13 #ifdef TIMER
14 #include "io/timer.h"
15 #endif
16
17 //we check background type to avoid messed up colours for ANI
18 #define ANI_BPP_CHECK (ga->ani.bg_type == BM_TYPE_PCX) ? 16 : 32
19
20 // These two functions find if a bitmap or animation exists by filename, no extension needed.
generic_bitmap_exists(const char * filename)21 bool generic_bitmap_exists(const char *filename)
22 {
23 return cf_exists_full_ext(filename, CF_TYPE_ANY, BM_NUM_TYPES, bm_ext_list) != 0;
24 }
25
generic_anim_exists(const char * filename)26 bool generic_anim_exists(const char *filename)
27 {
28 return cf_exists_full_ext(filename, CF_TYPE_ANY, BM_ANI_NUM_TYPES, bm_ani_ext_list) != 0;
29 }
30
31 // Goober5000
generic_anim_init_and_stream(generic_anim * ga,const char * anim_filename,BM_TYPE bg_type,bool attempt_hi_res)32 int generic_anim_init_and_stream(generic_anim *ga, const char *anim_filename, BM_TYPE bg_type, bool attempt_hi_res)
33 {
34 int stream_result = -1;
35 char filename[NAME_LENGTH];
36 char *p;
37
38 Assert(ga != NULL);
39 Assert(anim_filename != NULL);
40
41 // hi-res support
42 if (attempt_hi_res && (gr_screen.res == GR_1024)) {
43 // attempt to load a hi-res animation
44 memset(filename, 0, NAME_LENGTH);
45 strcpy_s(filename, "2_");
46 strncat(filename, anim_filename, NAME_LENGTH - 3);
47
48 // remove extension
49 p = strchr(filename, '.');
50 if(p) {
51 *p = '\0';
52 }
53
54 // attempt to stream the hi-res ani
55 generic_anim_init(ga, filename);
56 ga->ani.bg_type = bg_type;
57 stream_result = generic_anim_stream(ga);
58 }
59
60 // we failed to stream hi-res, or we aren't running in hi-res, so try low-res
61 if (stream_result < 0) {
62 strcpy_s(filename, anim_filename);
63
64 // remove extension
65 p = strchr(filename, '.');
66 if(p) {
67 *p = '\0';
68 }
69
70 // attempt to stream the low-res ani
71 generic_anim_init(ga, filename);
72 ga->ani.bg_type = bg_type;
73 stream_result = generic_anim_stream(ga);
74 }
75
76 return stream_result;
77 }
78
79 // Goober5000
generic_anim_init(generic_anim * ga)80 void generic_anim_init(generic_anim *ga)
81 {
82 generic_anim_init(ga, NULL);
83 }
84
85 // Goober5000
generic_anim_init(generic_anim * ga,const char * filename)86 void generic_anim_init(generic_anim *ga, const char *filename)
87 {
88 if (filename != NULL)
89 strcpy_s(ga->filename, filename);
90 else
91 memset(ga->filename, 0, MAX_FILENAME_LEN);
92 ga->first_frame = -1;
93 ga->num_frames = 0;
94 ga->keyframe = 0;
95 ga->keyoffset = 0;
96 ga->current_frame = 0;
97 ga->previous_frame = -1;
98 ga->direction = GENERIC_ANIM_DIRECTION_FORWARDS;
99 ga->done_playing = 0;
100 ga->total_time = 0.0f;
101 ga->anim_time = 0.0f;
102
103 //we only care about the stuff below if we're streaming
104 ga->ani.animation = NULL;
105 ga->ani.instance = NULL;
106 ga->ani.bg_type = BM_TYPE_NONE;
107 ga->type = BM_TYPE_NONE;
108 ga->streaming = 0;
109 ga->buffer = NULL;
110 ga->height = 0;
111 ga->width = 0;
112 ga->bitmap_id = -1;
113 ga->use_hud_color = false;
114 }
115
116 // CommanderDJ - same as generic_anim_init, just with an SCP_string
generic_anim_init(generic_anim * ga,const SCP_string & filename)117 void generic_anim_init(generic_anim *ga, const SCP_string& filename)
118 {
119 generic_anim_init(ga);
120 filename.copy(ga->filename, MAX_FILENAME_LEN - 1);
121 }
122
123 // Goober5000
generic_bitmap_init(generic_bitmap * gb,const char * filename)124 void generic_bitmap_init(generic_bitmap *gb, const char *filename)
125 {
126 if (filename == NULL) {
127 gb->filename[0] = '\0';
128 } else {
129 strncpy(gb->filename, filename, MAX_FILENAME_LEN - 1);
130 }
131
132 gb->bitmap_id = -1;
133 }
134
135 // Goober5000
136 // load a generic_anim
137 // return 0 is successful, otherwise return -1
generic_anim_load(generic_anim * ga)138 int generic_anim_load(generic_anim *ga)
139 {
140 int fps;
141
142 if ( !VALID_FNAME(ga->filename) )
143 return -1;
144
145 ga->first_frame = bm_load_animation(ga->filename, &ga->num_frames, &fps, &ga->keyframe, &ga->total_time);
146 //mprintf(("generic_anim_load: %s - keyframe = %d\n", ga->filename, ga->keyframe));
147
148 if (ga->first_frame < 0)
149 return -1;
150
151 ga->done_playing = 0;
152 ga->anim_time = 0.0f;
153
154 return 0;
155 }
156
generic_anim_stream(generic_anim * ga,const bool cache)157 int generic_anim_stream(generic_anim *ga, const bool cache)
158 {
159 CFILE *img_cfp = NULL;
160 int anim_fps = 0;
161 int bpp;
162
163 ga->type = BM_TYPE_NONE;
164
165 auto res = cf_find_file_location_ext(ga->filename, BM_ANI_NUM_TYPES, bm_ani_ext_list, CF_TYPE_ANY, false);
166
167 // could not be found, or is invalid for some reason
168 if ( !res.found )
169 return -1;
170
171 //make sure we can open it
172 img_cfp = cfopen_special(res, "rb", CF_TYPE_ANY);
173
174 if (img_cfp == NULL) {
175 return -1;
176 }
177
178 strcat_s(ga->filename, bm_ani_ext_list[res.extension_index]);
179 ga->type = bm_ani_type_list[res.extension_index];
180 //seek to the end
181 cfseek(img_cfp, 0, CF_SEEK_END);
182
183 cfclose(img_cfp);
184
185 if(ga->type == BM_TYPE_ANI) {
186 bpp = ANI_BPP_CHECK;
187 if(ga->use_hud_color)
188 bpp = 8;
189 if (ga->ani.animation == nullptr) {
190 ga->ani.animation = anim_load(ga->filename, CF_TYPE_ANY, 0);
191 }
192 if (ga->ani.instance == nullptr) {
193 ga->ani.instance = init_anim_instance(ga->ani.animation, bpp);
194 }
195
196 #ifndef NDEBUG
197 // for debug of ANI sizes
198 strcpy_s(ga->ani.animation->name, ga->filename);
199 #endif
200
201 ga->num_frames = ga->ani.animation->total_frames;
202 anim_fps = ga->ani.animation->fps;
203 ga->height = ga->ani.animation->height;
204 ga->width = ga->ani.animation->width;
205 ga->buffer = ga->ani.instance->frame;
206 ga->bitmap_id = bm_create(bpp, ga->width, ga->height, ga->buffer, (bpp==8)?BMP_AABITMAP:0);
207 ga->ani.instance->last_bitmap = -1;
208
209 ga->ani.instance->file_offset = ga->ani.animation->file_offset;
210 ga->ani.instance->data = ga->ani.animation->data;
211
212 ga->previous_frame = -1;
213 }
214 else if (ga->type == BM_TYPE_PNG) {
215 if (ga->png.anim == nullptr) {
216 try {
217 ga->png.anim = new apng::apng_ani(ga->filename, cache);
218 }
219 catch (const apng::ApngException& e) {
220 mprintf(("Failed to load apng: %s\n", e.what() ));
221 delete ga->png.anim;
222 ga->png.anim = nullptr;
223 ga->type = BM_TYPE_NONE;
224 return -1;
225 }
226 nprintf(("apng", "apng read OK (%ix%i@%i) duration (%f)\n", ga->png.anim->w, ga->png.anim->h,
227 ga->png.anim->bpp, ga->png.anim->anim_time));
228 }
229 ga->png.anim->goto_start();
230 ga->current_frame = 0;
231 ga->png.previous_frame_time = 0.0f;
232 ga->num_frames = ga->png.anim->nframes;
233 ga->height = ga->png.anim->h;
234 ga->width = ga->png.anim->w;
235 ga->previous_frame = -1;
236 ga->buffer = ga->png.anim->frame.data.data();
237 ga->bitmap_id = bm_create(ga->png.anim->bpp, ga->width, ga->height, ga->buffer, 0);
238 }
239 else {
240 bpp = 32;
241 if(ga->use_hud_color)
242 bpp = 8;
243 bm_load_and_parse_eff(ga->filename, CF_TYPE_ANY, &ga->num_frames, &anim_fps, &ga->keyframe, 0);
244 char *p = strrchr( ga->filename, '.' );
245 if ( p )
246 *p = 0;
247 char frame_name[MAX_FILENAME_LEN];
248 if (snprintf(frame_name, MAX_FILENAME_LEN, "%s_0000", ga->filename) >= MAX_FILENAME_LEN) {
249 // Make sure the string is null terminated
250 frame_name[MAX_FILENAME_LEN - 1] = '\0';
251 }
252 ga->bitmap_id = bm_load(frame_name);
253 if(ga->bitmap_id < 0) {
254 mprintf(("Cannot find first frame for eff streaming. eff Filename: %s\n", ga->filename));
255 return -1;
256 }
257 if (snprintf(frame_name, MAX_FILENAME_LEN, "%s_0001", ga->filename) >= MAX_FILENAME_LEN) {
258 // Make sure the string is null terminated
259 frame_name[MAX_FILENAME_LEN - 1] = '\0';
260 }
261 ga->eff.next_frame = bm_load(frame_name);
262 bm_get_info(ga->bitmap_id, &ga->width, &ga->height);
263 ga->previous_frame = 0;
264 }
265
266 // keyframe info
267 if (ga->type == BM_TYPE_ANI) {
268 //we only care if there are 2 keyframes - first frame, other frame to jump to for ship/weapons
269 //mainhall door anis hav every frame as keyframe, so we don't care
270 //other anis only have the first frame
271 if(ga->ani.animation->num_keys == 2) {
272 int key1 = ga->ani.animation->keys[0].frame_num;
273 int key2 = ga->ani.animation->keys[1].frame_num;
274
275 if (key1 < 0 || key1 >= ga->num_frames) key1 = -1;
276 if (key2 < 0 || key2 >= ga->num_frames) key2 = -1;
277
278 // some retail anis have their keyframes reversed
279 // and some have their keyframes out of bounds
280 if (key1 >= 0 && key1 >= key2) {
281 ga->keyframe = ga->ani.animation->keys[0].frame_num;
282 ga->keyoffset = ga->ani.animation->keys[0].offset;
283 }
284 else if (key2 >= 0 && key2 >= key1) {
285 ga->keyframe = ga->ani.animation->keys[1].frame_num;
286 ga->keyoffset = ga->ani.animation->keys[1].offset;
287 }
288 }
289 }
290
291 ga->streaming = 1;
292
293 if (ga->type == BM_TYPE_PNG) {
294 ga->total_time = ga->png.anim->anim_time;
295 }
296 else {
297 if (anim_fps == 0) {
298 Error(LOCATION, "animation (%s) has invalid fps of zero, fix this!", ga->filename);
299 }
300 ga->total_time = ga->num_frames / (float) anim_fps;
301 }
302 ga->done_playing = 0;
303 ga->anim_time = 0.0f;
304
305 return 0;
306 }
307
generic_bitmap_load(generic_bitmap * gb)308 int generic_bitmap_load(generic_bitmap *gb)
309 {
310 if ( !VALID_FNAME(gb->filename) )
311 return -1;
312
313 gb->bitmap_id = bm_load(gb->filename);
314
315 if (gb->bitmap_id < 0)
316 return -1;
317
318 return 0;
319 }
320
generic_anim_unload(generic_anim * ga)321 void generic_anim_unload(generic_anim *ga)
322 {
323 if(ga->num_frames > 0) {
324 if(ga->streaming) {
325 if(ga->type == BM_TYPE_ANI) {
326 free_anim_instance(ga->ani.instance);
327 anim_free(ga->ani.animation);
328 }
329 if(ga->type == BM_TYPE_EFF) {
330 if(ga->eff.next_frame >= 0)
331 bm_release(ga->eff.next_frame);
332 if(ga->bitmap_id >= 0)
333 bm_release(ga->bitmap_id);
334 }
335 if(ga->type == BM_TYPE_PNG) {
336 if(ga->bitmap_id >= 0)
337 bm_release(ga->bitmap_id);
338 if (ga->png.anim != nullptr) {
339 delete ga->png.anim;
340 ga->png.anim = nullptr;
341 }
342 }
343 }
344 else {
345 //trying to release the first frame will release ALL frames
346 bm_release(ga->first_frame);
347 }
348 if(ga->buffer) {
349 bm_release(ga->bitmap_id);
350 }
351 }
352 generic_anim_init(ga, NULL);
353 }
354
355 //for timer debug, #define TIMER
generic_render_eff_stream(generic_anim * ga)356 void generic_render_eff_stream(generic_anim *ga)
357 {
358 if(ga->current_frame == ga->previous_frame)
359 return;
360 ubyte bpp = 32;
361 if(ga->use_hud_color)
362 bpp = 8;
363 #ifdef TIMER
364 int start_time = timer_get_fixed_seconds();
365 #endif
366
367 #ifdef TIMER
368 mprintf(("=========================\n"));
369 mprintf(("frame: %d\n", ga->current_frame));
370 #endif
371 char frame_name[MAX_FILENAME_LEN];
372 if (snprintf(frame_name, MAX_FILENAME_LEN, "%s_%.4d", ga->filename, ga->current_frame) >= MAX_FILENAME_LEN) {
373 // Make sure the string is null terminated
374 frame_name[MAX_FILENAME_LEN - 1] = '\0';
375 }
376 if(bm_reload(ga->eff.next_frame, frame_name) == ga->eff.next_frame)
377 {
378 bitmap* next_frame_bmp = bm_lock(ga->eff.next_frame, bpp, (bpp==8)?BMP_AABITMAP:BMP_TEX_NONCOMP, true);
379 if(next_frame_bmp->data)
380 gr_update_texture(ga->bitmap_id, bpp, (ubyte*)next_frame_bmp->data, ga->width, ga->height);
381 bm_unlock(ga->eff.next_frame);
382 bm_unload(ga->eff.next_frame, 0, true);
383 if (ga->current_frame == ga->num_frames-1)
384 {
385 if (snprintf(frame_name, MAX_FILENAME_LEN, "%s_0001", ga->filename) >= MAX_FILENAME_LEN) {
386 // Make sure the string is null terminated
387 frame_name[MAX_FILENAME_LEN - 1] = '\0';
388 }
389 bm_reload(ga->eff.next_frame, frame_name);
390 }
391 }
392 #ifdef TIMER
393 mprintf(("end: %d\n", timer_get_fixed_seconds() - start_time));
394 mprintf(("=========================\n"));
395 #endif
396 }
397
generic_render_ani_stream(generic_anim * ga)398 void generic_render_ani_stream(generic_anim *ga)
399 {
400 int i;
401 int bpp = ANI_BPP_CHECK;
402 if(ga->use_hud_color)
403 bpp = 8;
404 #ifdef TIMER
405 int start_time = timer_get_fixed_seconds();
406 #endif
407
408 if(ga->current_frame == ga->previous_frame)
409 return;
410
411 #ifdef TIMER
412 mprintf(("=========================\n"));
413 mprintf(("frame: %d\n", ga->current_frame));
414 #endif
415
416 anim_check_for_palette_change(ga->ani.instance);
417 // if we're using bitmap polys
418 BM_SELECT_TEX_FORMAT();
419 if(ga->direction & GENERIC_ANIM_DIRECTION_BACKWARDS) {
420 //grab the keyframe - every frame is a keyframe for ANI
421 if(ga->ani.animation->flags & ANF_STREAMED) {
422 ga->ani.instance->file_offset = ga->ani.animation->file_offset + ga->ani.animation->keys[ga->current_frame].offset;
423 } else {
424 ga->ani.instance->data = ga->ani.animation->data + ga->ani.animation->keys[ga->current_frame].offset;
425 }
426 if(ga->ani.animation->flags & ANF_STREAMED) {
427 ga->ani.instance->file_offset = unpack_frame_from_file(ga->ani.instance, ga->buffer, ga->width * ga->height, (ga->ani.instance->xlate_pal) ? ga->ani.animation->palette_translation : NULL, (bpp==8)?1:0, bpp);
428 }
429 else {
430 ga->ani.instance->data = unpack_frame(ga->ani.instance, ga->ani.instance->data, ga->buffer, ga->width * ga->height, (ga->ani.instance->xlate_pal) ? ga->ani.animation->palette_translation : NULL, (bpp==8)?1:0, bpp);
431 }
432 }
433 else {
434 //looping back
435 if((ga->current_frame == 0) || (ga->current_frame < ga->previous_frame)) {
436 //go back to keyframe if there is one
437 if(ga->keyframe && (ga->current_frame > 0)) {
438 if(ga->ani.animation->flags & ANF_STREAMED) {
439 ga->ani.instance->file_offset = ga->ani.animation->file_offset + ga->keyoffset;
440 } else {
441 ga->ani.instance->data = ga->ani.animation->data + ga->keyoffset;
442 }
443 ga->previous_frame = ga->keyframe - 1;
444 }
445 //go back to the start
446 else {
447 ga->ani.instance->file_offset = ga->ani.animation->file_offset;
448 ga->ani.instance->data = ga->ani.animation->data;
449 ga->previous_frame = -1;
450 }
451 }
452 #ifdef TIMER
453 mprintf(("proc: %d\n", timer_get_fixed_seconds() - start_time));
454 mprintf(("previous frame: %d\n", ga->previous_frame));
455 #endif
456 for(i = ga->previous_frame + 1; i <= ga->current_frame; i++) {
457 if(ga->ani.animation->flags & ANF_STREAMED) {
458 ga->ani.instance->file_offset = unpack_frame_from_file(ga->ani.instance, ga->buffer, ga->width * ga->height, (ga->ani.instance->xlate_pal) ? ga->ani.animation->palette_translation : NULL, (bpp==8)?1:0, bpp);
459 }
460 else {
461 ga->ani.instance->data = unpack_frame(ga->ani.instance, ga->ani.instance->data, ga->buffer, ga->width * ga->height, (ga->ani.instance->xlate_pal) ? ga->ani.animation->palette_translation : NULL, (bpp==8)?1:0, bpp);
462 }
463 }
464 }
465 // always go back to screen format
466 BM_SELECT_SCREEN_FORMAT();
467 //we need to use this because performance is worse if we flush the gfx card buffer
468
469 gr_update_texture(ga->bitmap_id, bpp, ga->buffer, ga->width, ga->height);
470
471 //in case we want to check that the frame is actually changing
472 //mprintf(("frame crc = %08X\n", cf_add_chksum_long(0, ga->buffer, ga->width * ga->height * (bpp >> 3))));
473 ga->ani.instance->last_bitmap = ga->bitmap_id;
474
475 #ifdef TIMER
476 mprintf(("end: %d\n", timer_get_fixed_seconds() - start_time));
477 mprintf(("=========================\n"));
478 #endif
479 }
480
481 /*
482 * @brief apng specific animation rendering
483 *
484 * @param [in] ga pointer to generic_anim struct
485 */
generic_render_png_stream(generic_anim * ga)486 void generic_render_png_stream(generic_anim* ga)
487 {
488 if(ga->current_frame == ga->previous_frame) {
489 return;
490 }
491
492 try {
493 if ((ga->direction & GENERIC_ANIM_DIRECTION_BACKWARDS) && (ga->previous_frame != -1)) {
494 // mainhall door anims start backwards to ensure they stay shut
495 // in that case (i.e. previous_frame is -1) we actually want to call
496 // next_frame, in order to retrieve the 1st frame of the animation
497 ga->png.anim->prev_frame();
498 }
499 else {
500 ga->png.anim->next_frame();
501 }
502 }
503 catch (const apng::ApngException& e) {
504 nprintf(("apng", "Unable to get next/prev apng frame: %s\n", e.what()));
505 return;
506 }
507
508 bm_lock(ga->bitmap_id, ga->png.anim->bpp, BMP_TEX_NONCOMP, true); // lock in 32 bpp for png
509 int bpp = ga->png.anim->bpp;
510 if (ga->use_hud_color) {
511 bpp = 8;
512 }
513 gr_update_texture(ga->bitmap_id, bpp, ga->buffer, ga->width, ga->height); // this will convert to 8 bpp if required
514 bm_unlock(ga->bitmap_id);
515 }
516
517 /*
518 * @brief calculate current frame for fixed frame delay animation formats (ani & eff)
519 *
520 * @param [in] *ga animation data
521 * @param [in] frametime how long this frame took
522 */
generic_anim_render_fixed_frame_delay(generic_anim * ga,float frametime)523 void generic_anim_render_fixed_frame_delay(generic_anim* ga, float frametime)
524 {
525 float keytime = 0.0;
526
527 if(ga->keyframe)
528 keytime = (ga->total_time * ((float)ga->keyframe / (float)ga->num_frames));
529 //don't mess with the frame time if we're paused
530 if((ga->direction & GENERIC_ANIM_DIRECTION_PAUSED) == 0) {
531 if(ga->direction & GENERIC_ANIM_DIRECTION_BACKWARDS) {
532 //keep going forwards if we're in a keyframe loop
533 if(ga->keyframe && (ga->anim_time >= keytime)) {
534 ga->anim_time += frametime;
535 if(ga->anim_time >= ga->total_time) {
536 ga->anim_time = keytime - 0.001f;
537 ga->done_playing = 0;
538 }
539 }
540 else {
541 //playing backwards
542 ga->anim_time -= frametime;
543 if((ga->direction & GENERIC_ANIM_DIRECTION_NOLOOP) && ga->anim_time <= 0.0) {
544 ga->anim_time = 0; //stop on first frame when playing in reverse
545 }
546 else {
547 while(ga->anim_time <= 0.0)
548 ga->anim_time += ga->total_time; //make sure we're always positive, so we can go back to the end
549 }
550 }
551 }
552 else {
553 ga->anim_time += frametime;
554 if(ga->anim_time >= ga->total_time) {
555 if(ga->direction & GENERIC_ANIM_DIRECTION_NOLOOP) {
556 ga->anim_time = ga->total_time - 0.001f; //stop on last frame when playing - if it's equal we jump to the first frame
557 }
558 if(!ga->done_playing){
559 //we've played this at least once
560 ga->done_playing = 1;
561 }
562 }
563 }
564 }
565 if(ga->num_frames > 0)
566 {
567 ga->current_frame = 0;
568 if(ga->done_playing && ga->keyframe) {
569 ga->anim_time = fmod(ga->anim_time - keytime, ga->total_time - keytime) + keytime;
570 }
571 else {
572 ga->anim_time = fmod(ga->anim_time, ga->total_time);
573 }
574 ga->current_frame += fl2i(ga->anim_time * ga->num_frames / ga->total_time);
575 //sanity check
576 CLAMP(ga->current_frame, 0, ga->num_frames - 1);
577 if(ga->streaming) {
578 //handle streaming - render one frame
579 if(ga->type == BM_TYPE_ANI) {
580 generic_render_ani_stream(ga);
581 } else {
582 generic_render_eff_stream(ga);
583 }
584 gr_set_bitmap(ga->bitmap_id);
585 }
586 else {
587 gr_set_bitmap(ga->first_frame + ga->current_frame);
588 }
589 }
590 }
591
592 /*
593 * @brief calculate current frame for variable frame delay animation formats (e.g. apng)
594 *
595 * @param [in] *ga animation data
596 * @param [in] frametime how long this frame took
597 * @param [in] alpha transparency to draw frame with (0.0 - 1.0)
598 *
599 * @note
600 * uses both time & frame counts to determine end state; so that if the anims playing
601 * can't be processed fast enough, all frames will still play, rather than the end
602 * frames being skipped
603 */
generic_anim_render_variable_frame_delay(generic_anim * ga,float frametime,float alpha)604 void generic_anim_render_variable_frame_delay(generic_anim* ga, float frametime, float alpha)
605 {
606 Assertion(ga->type == BM_TYPE_PNG, "only valid for apngs (currently); get a coder!");
607 if (ga->keyframe != 0) {
608 Warning(LOCATION, "apngs don't support keyframes");
609 return;
610 }
611
612 // don't change the frame time if we're paused
613 if((ga->direction & GENERIC_ANIM_DIRECTION_PAUSED) == 0) {
614 if(ga->direction & GENERIC_ANIM_DIRECTION_BACKWARDS) {
615 // playing backwards
616 ga->anim_time -= frametime;
617 if (ga->anim_time <= 0.0 && ga->png.anim->current_frame <= 0) {
618 if(ga->direction & GENERIC_ANIM_DIRECTION_NOLOOP) {
619 ga->anim_time = 0; //stop on first frame when playing in reverse
620 }
621 else {
622 // loop back to end
623 ga->anim_time = ga->total_time;
624 ga->png.previous_frame_time = ga->total_time;
625 ga->png.anim->current_frame = ga->num_frames-1;
626 ga->current_frame = ga->num_frames-1;
627 }
628 }
629 }
630 else {
631 // playing forwards
632 ga->anim_time += frametime;
633 if(ga->anim_time >= ga->total_time && ga->png.anim->current_frame >= ga->png.anim->nframes) {
634 if(ga->direction & GENERIC_ANIM_DIRECTION_NOLOOP) {
635 ga->anim_time = ga->total_time; // stop on last frame when playing
636 }
637 else {
638 // loop back to start
639 ga->anim_time = 0.0f;
640 ga->png.previous_frame_time = 0.0f;
641 ga->current_frame = 0;
642 ga->png.anim->goto_start();
643 }
644 ga->done_playing = 1;
645 }
646 }
647 }
648
649 if (ga->num_frames > 0) {
650
651 // just increment or decrement the frame by one
652 // jumping forwards multiple frames will just exacerbate slowdowns as multiple frames
653 // would need to be composed
654 if (ga->direction & GENERIC_ANIM_DIRECTION_BACKWARDS) {
655 if (ga->anim_time <= ga->png.previous_frame_time - ga->png.anim->frame.delay &&
656 ga->png.anim->current_frame > 0) {
657 ga->png.previous_frame_time -= ga->png.anim->frame.delay;
658 ga->current_frame--;
659 }
660 }
661 else {
662 if (ga->anim_time >= ga->png.previous_frame_time + ga->png.anim->frame.delay &&
663 ga->png.anim->current_frame < ga->png.anim->nframes) {
664 ga->png.previous_frame_time += ga->png.anim->frame.delay;
665 ga->current_frame++;
666 }
667 }
668
669 // verbose debug; but quite useful
670 nprintf(("apng", "apng generic render timings/frames: %04f %04f %04f %04f | %03i %03i %03i\n",
671 frametime, ga->anim_time, ga->png.anim->frame.delay, ga->png.previous_frame_time,
672 ga->previous_frame, ga->current_frame, ga->png.anim->current_frame));
673
674 Assertion(ga->streaming != 0, "non-streaming apngs not implemented yet");
675 // note: generic anims are not currently ever non-streaming in FSO
676 // I'm not even sure that the existing ani/eff code would allow non-streaming generic anims
677 generic_render_png_stream(ga);
678 gr_set_bitmap(ga->bitmap_id, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha);
679 }
680 }
681
682
683 /*
684 * @brief render animations
685 *
686 * @param [in] *ga animation data
687 * @param [in] frametime how long this frame took
688 * @param [in] x 2D screen x co-ordinate to render at
689 * @param [in] y 2D screen y co-ordinate to render at
690 * @param [in] menu select if this is rendered in menu screen, or fullscreen
691 */
generic_anim_render(generic_anim * ga,float frametime,int x,int y,bool menu,const generic_extras * ge)692 void generic_anim_render(generic_anim *ga, float frametime, int x, int y, bool menu, const generic_extras *ge)
693 {
694 if ((ge != nullptr) && (ga->use_hud_color == true)) {
695 Warning(LOCATION, "Monochrome generic anims can't use extra info (yet)");
696 return;
697 }
698
699 float a = 1.0f;
700 if (ge != nullptr) {
701 a = ge->alpha;
702 }
703 if (ga->type == BM_TYPE_PNG) {
704 generic_anim_render_variable_frame_delay(ga, frametime, a);
705 }
706 else {
707 generic_anim_render_fixed_frame_delay(ga, frametime);
708 }
709
710 if(ga->num_frames > 0) {
711 ga->previous_frame = ga->current_frame;
712
713 if(ga->use_hud_color) {
714 gr_aabitmap(x, y, (menu ? GR_RESIZE_MENU : GR_RESIZE_FULL));
715 }
716 else {
717 if (ge == nullptr) {
718 gr_bitmap(x, y, (menu ? GR_RESIZE_MENU : GR_RESIZE_FULL));
719 }
720 else if (ge->draw == true) {
721 // currently only for lua streaminganim objects
722 // and don't draw them unless requested...
723 gr_bitmap_uv(x, y, ge->width, ge->height, ge->u0, ge->v0, ge->u1, ge->v1, GR_RESIZE_NONE);
724 }
725 }
726 }
727 }
728
729 /*
730 * @brief reset an animation back to the start
731 *
732 * @param [in] *ga animation data
733 */
generic_anim_reset(generic_anim * ga)734 void generic_anim_reset(generic_anim *ga) {
735 ga->anim_time = 0.0f;
736 ga->current_frame = 0;
737 if (ga->type == BM_TYPE_PNG) {
738 ga->png.previous_frame_time = 0.0f;
739 ga->png.anim->goto_start();
740 }
741 }
742