Files
fs2open.github.com/code/anim/animplay.cpp
T
niffiwan 027f218f7f Allow any resolution for mainhalls (From Yarn)
Also includes support for more than two mainhall resolutions, zooming
so one mainhall can adapt to multiple resolutions, setting new mainhall
fonts, help overlays for all resolutions and adjustable tooltip padding.
Also allows preload screens to be any resolution & aspect ratio.

git-svn-id: svn://svn.icculus.org/fs2open/trunk/fs2_open@10844 387891d4-d844-0410-90c0-e4c51a9137d3
2014-07-05 10:38:07 +10:00

1101 lines
29 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "anim/animplay.h"
#include "globalincs/linklist.h"
#include "io/timer.h"
#include "bmpman/bmpman.h"
#include "graphics/2d.h"
#include "render/3d.h"
#include "pcxutils/pcxutils.h"
#include "anim/packunpack.h"
#include "cfile/cfile.h"
#include "cmdline/cmdline.h"
static color Color_xparent;
anim *first_anim = NULL;
anim_instance anim_free_list;
anim_instance anim_render_list;
#define MAX_ANIM_INSTANCES 25
anim_instance anim_render_instance[MAX_ANIM_INSTANCES];
int Anim_paused; /// Global variable to pause the playing back of anims
int Anim_inited = FALSE;
fix t1,t2;
int Anim_ignore_frametime=0; // flag used to ignore frametime... useful when need to avoid saturated frametimes
/**
* @brief Initialise animation
* @details Queue all the ::anim_render_instance[] elements onto the ::anim_free_list
*/
void anim_init()
{
int i;
if ( Anim_inited == TRUE )
return;
list_init( &anim_free_list );
list_init( &anim_render_list );
// Link all anim render slots into the free list
for (i=1; i < MAX_ANIM_INSTANCES; i++) {
list_append(&anim_free_list, &anim_render_instance[i]);
}
Anim_paused = 0;
Anim_inited = TRUE;
}
/**
* @brief Display the frames for the currently playing anims
*/
void anim_render_all(int screen_id, float frametime)
{
anim_instance* A;
anim_instance* temp;
A = GET_FIRST(&anim_render_list);
while( A !=END_OF_LIST(&anim_render_list) ) {
temp = GET_NEXT(A);
if ( A->screen_id == screen_id ) {
if ( Anim_ignore_frametime ) {
frametime = 0.0f;
Anim_ignore_frametime=0;
}
if ( anim_show_next_frame(A, frametime) == -1 ) {
A->data = NULL;
anim_release_render_instance(A);
}
}
A = temp;
}
}
/**
* @brief Display the frames for the passed animation
* @details It will ignore animations which do not have the same id as the passed screen_id
*/
void anim_render_one(int screen_id, anim_instance *ani, float frametime)
{
// make sure this guy's screen id matches the passed one
if(screen_id != ani->screen_id){
return;
}
// otherwise render it
if ( Anim_ignore_frametime ) {
frametime = 0.0f;
Anim_ignore_frametime=0;
}
if ( anim_show_next_frame(ani, frametime) == -1 ) {
ani->data = NULL;
anim_release_render_instance(ani);
}
}
MONITOR(NumANIPlayed)
/**
* @brief Setup an anim_play_struct for passing into ::anim_play().
* @details Will fill in default values, which you can then change before calling ::anim_play().
*/
void anim_play_init(anim_play_struct *aps, anim *a_info, int x, int y, int base_w, int base_h)
{
aps->anim_info = a_info;
aps->x = x;
aps->y = y;
aps->base_w = base_w;
aps->base_h = base_h;
aps->start_at = 0;
aps->stop_at = a_info->total_frames - 1;
aps->screen_id = 0;
aps->world_pos = NULL;
aps->radius = 0.0f;
aps->framerate_independent = 0;
aps->color = NULL;
aps->skip_frames = 1;
aps->looped = 0;
aps->ping_pong = 0;
}
/**
* @brief Will add an anim instance to the anim_render_list.
* This will cause the anim to be played at the x,y position specified in the parameter list.
*
* @param aps Compressed animation that we should make an instance from
* @return If success pointer to instance, NULL if anim anim could not be played
*/
anim_instance *anim_play(anim_play_struct *aps)
{
Assert( aps->anim_info != NULL );
Assert( aps->start_at >= 0 );
Assert( aps->stop_at < aps->anim_info->total_frames );
Assert( !(aps->looped && aps->ping_pong) ); // shouldn't have these both set at once
MONITOR_INC(NumANIPlayed, 1);
anim_instance *instance;
// Find next free anim instance slot on queue
instance = GET_FIRST(&anim_free_list);
Assert( instance != &anim_free_list ); // shouldn't have the dummy element
// remove instance from the free list
list_remove( &anim_free_list, instance );
// insert instance onto the end of anim_render_list
list_append( &anim_render_list, instance );
aps->anim_info->instance_count++;
instance->frame_num = -1;
instance->last_frame_num = -99;
instance->parent = aps->anim_info;
instance->data = aps->anim_info->data;
if ( anim_instance_is_streamed(instance) ) {
instance->file_offset = instance->parent->file_offset;
}
instance->frame = (ubyte *) vm_malloc(instance->parent->width * instance->parent->height * 2);
Assert( instance->frame != NULL );
memset( instance->frame, 0, instance->parent->width * instance->parent->height * 2 );
instance->time_elapsed = 0.0f;
instance->stop_at = aps->stop_at;
instance->x = aps->x;
instance->y = aps->y;
instance->world_pos = aps->world_pos;
instance->radius = aps->radius;
instance->framerate_independent = aps->framerate_independent;
instance->last_bitmap = -1;
instance->stop_now = FALSE;
instance->screen_id = aps->screen_id;
instance->aa_color = aps->color;
instance->skip_frames = aps->skip_frames;
instance->looped = aps->looped;
instance->ping_pong = aps->ping_pong;
instance->direction = ANIM_DIRECT_FORWARD;
instance->paused = 0;
instance->loop_count = 0;
if ( aps->color == NULL ){
instance->xlate_pal = 1;
} else {
instance->xlate_pal = 0;
}
if(aps->base_w < 0 || aps->base_h < 0) {
instance->base_w = gr_screen.max_w_unscaled_zoomed;
instance->base_h = gr_screen.max_h_unscaled_zoomed;
} else {
instance->base_w = aps->base_w;
instance->base_h = aps->base_h;
}
// determining the start_at frame is more complicated, since it must be a key-frame.
// Futhermore, need to subtract 1 from key-frame number, since frame number is always
// incremented the first time anim_show_next_frame() is called
instance->start_at = aps->start_at;
if ( aps->start_at > 0 ) {
key_frame *keyp;
int idx;
int key = 0;
int offset = 0;
int frame_num = aps->start_at;
keyp = instance->parent->keys;
idx = 0;
while (idx < instance->parent->num_keys) {
if (key == frame_num)
break;
key = keyp[idx].frame_num - 1;
offset = keyp[idx].offset;
idx++;
}
if (key > instance->frame_num) { // best key is closer than current position
instance->frame_num = key;
if ( anim_instance_is_streamed(instance) ) {
instance->file_offset = instance->parent->file_offset + offset;
} else {
instance->data = instance->parent->data + offset;
}
}
instance->frame_num--; // required
}
return instance;
}
/**
* @brief This function is called to blit the next frame of an anim instance to the screen.
* This is normally called by the anim_render_all() function.
*
* @param instance Pointer to animation instance
* @param frametime Time elapsed since last call, in seconds
*/
int anim_show_next_frame(anim_instance *instance, float frametime)
{
int bitmap_id, bitmap_flags=0, new_frame_num, frame_diff=0, i, n_frames=0,frame_save;
float percent_through, decompress_time, render_time, time;
vertex image_vertex;
int aabitmap = 0;
int bpp = 16;
Assert( instance != NULL );
instance->time_elapsed += frametime;
// Advance to the next frame, if we determine enough time has elapsed.
if(instance->direction == ANIM_DIRECT_FORWARD)
n_frames = instance->stop_at - instance->start_at + 1;
else if(instance->direction == ANIM_DIRECT_REVERSE)
n_frames = instance->start_at - instance->stop_at + 1;
time = n_frames / i2fl(instance->parent->fps);
percent_through = instance->time_elapsed / time;
if(instance->direction == ANIM_DIRECT_FORWARD)
new_frame_num = instance->start_at - 1 + fl2i(percent_through * n_frames + 0.5f);
else
new_frame_num = instance->start_at - 1 - fl2i(percent_through * n_frames + 0.5f);
frame_save = instance->frame_num;
// If framerate independent, use the new_frame_num... unless instance->skip_frames is
// FALSE, then only advance a maximum of one frame (this is needed since some big animations
// should just play slower rather than taking the hit of decompressing multiple frames and
// creating an even greater slowdown
if (instance->framerate_independent) {
if(instance->direction == ANIM_DIRECT_FORWARD){
if ( new_frame_num > instance->last_frame_num) {
if ( instance->skip_frames )
instance->frame_num = new_frame_num;
else
instance->frame_num++;
}
} else if(instance->direction == ANIM_DIRECT_REVERSE){
if( new_frame_num < instance->last_frame_num) {
if ( instance->skip_frames )
instance->frame_num = new_frame_num;
else
instance->frame_num--;
}
}
}
else {
if(instance->direction == ANIM_DIRECT_FORWARD){
if ( new_frame_num > instance->last_frame_num) {
instance->frame_num++;
}
} else if(instance->direction == ANIM_DIRECT_REVERSE){
if ( new_frame_num < instance->last_frame_num) {
instance->frame_num--;
}
}
}
if(instance->direction == ANIM_DIRECT_FORWARD){
if ( instance->frame_num < instance->start_at ) {
instance->frame_num = instance->start_at;
}
} else if(instance->direction == ANIM_DIRECT_REVERSE){
if ( instance->frame_num > instance->start_at ) {
instance->frame_num = instance->start_at;
}
}
if ( instance->stop_now == TRUE ) {
return -1;
}
// If past the last frame, clamp to the last frame and then set the stop_now flag in the
// anim instance. The next iteration, the animation will stop.
if(instance->direction == ANIM_DIRECT_FORWARD){
if (instance->frame_num >= instance->stop_at ) {
if (instance->looped) { // looped animations
instance->frame_num = instance->stop_at;
instance->time_elapsed = 0.0f;
} else if(instance->ping_pong) { // pingponged animations
instance->frame_num = instance->stop_at;
anim_reverse_direction(instance);
} else { // one-shot animations
instance->frame_num = instance->stop_at;
instance->last_frame_num = instance->frame_num;
instance->stop_now = TRUE;
}
}
} else if(instance->direction == ANIM_DIRECT_REVERSE){
if (instance->frame_num <= instance->stop_at ) {
if (instance->looped) { // looped animations
instance->frame_num = instance->stop_at;
instance->time_elapsed = 0.0f;
} else if(instance->ping_pong) { // pingponged animations
instance->frame_num = instance->stop_at;
anim_reverse_direction(instance);
} else { // one-shot animations
instance->frame_num = instance->stop_at+1;
instance->last_frame_num = instance->frame_num;
instance->stop_now = TRUE;
}
}
}
if(instance->direction == ANIM_DIRECT_FORWARD){
if( instance->last_frame_num >= instance->start_at ) {
frame_diff = instance->frame_num - instance->last_frame_num;
} else {
frame_diff = 1;
}
} else if(instance->direction == ANIM_DIRECT_REVERSE){
if( instance->last_frame_num <= instance->start_at ) {
frame_diff = instance->last_frame_num - instance->frame_num;
} else {
frame_diff = 1;
}
}
Assert(frame_diff >= 0);
Assert( instance->frame_num >= 0 && instance->frame_num < instance->parent->total_frames );
// if the anim is paused, ignore all the above changes and still display this frame
if(instance->paused || Anim_paused){
instance->frame_num = frame_save;
instance->time_elapsed -= frametime;
frame_diff = 0;
}
if (instance->parent->flags & ANF_XPARENT){
bitmap_flags = 0;
}
bpp = 16;
if(instance->aa_color != NULL){
bitmap_flags |= BMP_AABITMAP;
aabitmap = 1;
bpp = 8;
}
if ( frame_diff > 0 ) {
instance->last_frame_num = instance->frame_num;
t1 = timer_get_fixed_seconds();
for ( i = 0; i < frame_diff; i++ ) {
anim_check_for_palette_change(instance);
// if we're playing backwards, every frame must be a keyframe and we set the data ptr here
if(instance->direction == ANIM_DIRECT_REVERSE){
if ( anim_instance_is_streamed(instance) ) {
instance->file_offset = instance->parent->file_offset + instance->parent->keys[instance->frame_num-1].offset;
} else {
instance->data = instance->parent->data + instance->parent->keys[instance->frame_num-1].offset;
}
}
ubyte *temp = NULL;
int temp_file_offset = -1;
// if we're using bitmap polys
BM_SELECT_TEX_FORMAT();
if ( anim_instance_is_streamed(instance) ) {
if ( instance->xlate_pal ){
temp_file_offset = unpack_frame_from_file(instance, instance->frame, instance->parent->width*instance->parent->height, instance->parent->palette_translation, aabitmap, bpp);
} else {
temp_file_offset = unpack_frame_from_file(instance, instance->frame, instance->parent->width*instance->parent->height, NULL, aabitmap, bpp);
}
} else {
if ( instance->xlate_pal ){
temp = unpack_frame(instance, instance->data, instance->frame, instance->parent->width*instance->parent->height, instance->parent->palette_translation, aabitmap, bpp);
} else {
temp = unpack_frame(instance, instance->data, instance->frame, instance->parent->width*instance->parent->height, NULL, aabitmap, bpp);
}
}
// always go back to screen format
BM_SELECT_SCREEN_FORMAT();
// see if we had an error during decode (corrupted anim stream)
if ( (temp == NULL) && (temp_file_offset < 0) ) {
mprintf(("ANI: Fatal ERROR at frame %i!! Aborting playback of \"%s\"...\n", instance->frame_num, instance->parent->name));
// return -1 to end all playing of this anim instanc
return -1;
}
if(instance->direction == ANIM_DIRECT_FORWARD){
if ( anim_instance_is_streamed(instance) ) {
instance->file_offset = temp_file_offset;
} else {
instance->data = temp;
}
}
}
t2 = timer_get_fixed_seconds();
}
else {
t2=t1=0;
}
// this only happens when the anim is being looped, we need to reset the last_frame_num
if ( (instance->time_elapsed == 0) && (instance->looped) ) {
instance->last_frame_num = -1;
instance->frame_num = -1;
instance->data = instance->parent->data;
instance->file_offset = instance->parent->file_offset;
instance->loop_count++;
}
decompress_time = f2fl(t2-t1);
t1 = timer_get_fixed_seconds();
if ( frame_diff == 0 && instance->last_bitmap != -1 ) {
bitmap_id = instance->last_bitmap;
}
else {
if ( instance->last_bitmap != -1 ){
bm_release(instance->last_bitmap);
}
bitmap_id = bm_create(bpp, instance->parent->width, instance->parent->height, instance->frame, bitmap_flags);
}
if ( bitmap_id == -1 ) {
// anim has finsished playing, free the instance frame data
anim_release_render_instance(instance);
return -1;
// NOTE: there is no need to free the instance, since it was pre-allocated as
// part of the anim_free_list
}
else {
gr_set_bitmap(bitmap_id);
// determine x,y to display the bitmap at
if ( instance->world_pos == NULL ) {
int old_max_w_unscaled = gr_screen.max_w_unscaled;
int old_max_h_unscaled = gr_screen.max_h_unscaled;
int old_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed;
int old_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;
gr_set_screen_scale(instance->base_w, instance->base_h);
gr_set_clip(0, 0, instance->base_w, instance->base_h, GR_RESIZE_MENU);
if ( instance->aa_color == NULL ) {
gr_bitmap(instance->x, instance->y, GR_RESIZE_MENU_NO_OFFSET);
}
else {
gr_set_color_fast( (color*)instance->aa_color );
gr_aabitmap(instance->x, instance->y, GR_RESIZE_MENU_NO_OFFSET);
}
gr_set_screen_scale(old_max_w_unscaled, old_max_h_unscaled, old_max_w_unscaled_zoomed, old_max_h_unscaled_zoomed);
gr_reset_clip();
}
else {
g3_rotate_vertex(&image_vertex,instance->world_pos);
Assert(instance->radius != 0.0f);
g3_draw_bitmap(&image_vertex, 0, instance->radius*1.5f, TMAP_FLAG_TEXTURED | TMAP_HTL_2D);
}
instance->last_bitmap = bitmap_id;
}
t2 = timer_get_fixed_seconds();
render_time = f2fl(t2-t1);
return 0;
}
/**
* @brief Stop an anim instance that is on the anim_render_list from playing
*/
int anim_stop_playing(anim_instance* instance)
{
Assert(instance != NULL);
if ( anim_playing(instance) ) {
anim_release_render_instance(instance);
}
return 0;
}
/**
* @brief Free a particular animation instance that is on the anim_render_list.
* Do not call this function to free an animation instance in general (use
* free_anim_instance() for that), only when you want to free an instance
* that is on the anim_render_list
*/
void anim_release_render_instance(anim_instance* instance)
{
Assert( instance != NULL );
if (instance->frame != NULL)
vm_free(instance->frame);
instance->frame = NULL;
instance->parent->instance_count--;
if ( instance->last_bitmap != -1 ) {
bm_release(instance->last_bitmap);
instance->last_bitmap = -1;
}
// remove instance from anim_render_list
list_remove( &anim_render_list, instance );
// insert instance into the anim_free_list
list_append( &anim_free_list, instance );
}
/**
* @brief Free all anim instances that are on the anim_render_list.
*
* @param screen_id Optional parameter that lets you only free a subset of the anim instances.
* A screen_id of 0 is the default value, and this is used for animations that always play when
* they are placed on the aim_render_list.
*/
void anim_release_all_instances(int screen_id)
{
anim_instance* A;
anim_instance* temp;
if ( Anim_inited == FALSE )
return;
A = GET_FIRST(&anim_render_list);
while( A !=END_OF_LIST(&anim_render_list) ) {
temp = GET_NEXT(A);
if ( A->screen_id == screen_id || screen_id == 0 ) {
anim_release_render_instance(A);
}
A = temp;
}
}
// -----------------------------------------------------------------------------
// anim_read_header()
//
// Read the header of a .ani file. Below is the format of a .ani header
//
// #bytes | description
// 2 | obsolete, kept for compatibility with old versions
// 2 | version number
// 2 | fps
// 1 | transparent red value
// 1 | transparent green value
// 1 | transparent blue value
// 2 | width
// 2 | height
// 2 | number of frames
// 1 | packer code
// 763 | palette
// 2 | number of key frames
// 2 | key frame number } repeats
// 4 | key frame offset } repeats
// 4 | compressed data length
//
void anim_read_header(anim *ptr, CFILE *fp)
{
ptr->width = cfread_short(fp);
// If first 2 bytes are zero, this means we are using a new format, which includes
// a version, and fps values. This is only done since a version number was not included
// in the original header.
// default
Color_xparent.red = 0;
Color_xparent.green = 255;
Color_xparent.blue = 0;
if ( ptr->width == 0 ) {
ptr->version = cfread_short(fp);
ptr->fps = cfread_short(fp);
// version 2 added a custom transparency color
if ( ptr->version >= 2 ) {
cfread(&Color_xparent.red, 1, 1, fp);
cfread(&Color_xparent.green, 1, 1, fp);
cfread(&Color_xparent.blue, 1, 1, fp);
}
ptr->width = cfread_short(fp);
}
else {
ptr->version = 0;
ptr->fps = 30;
}
ptr->height = cfread_short(fp);
#ifndef NDEBUG
// get size of ani compared to power of 2
int r, floor_pow;
r = ptr->height;
floor_pow = 0;
while(r >= 2) {
r /= 2;
floor_pow++;
}
int floor_size = (int) pow(2.0, floor_pow);
int diff = ptr->height - floor_size;
float waste = 100.0f * float((floor_size - diff))/(2.0f *(float)floor_size);
if (diff != 0) {
if (ptr->height > 16) {
mprintf(("ANI %s with size %dx%d (%.1f%% wasted)\n", ptr->name, ptr->width, ptr->height, waste));
}
}
#endif
ptr->total_frames = cfread_short(fp);
ptr->packer_code = cfread_ubyte(fp);
cfread(&ptr->palette, 256, 3, fp);
ptr->num_keys = cfread_short(fp);
// store xparent colors
ptr->xparent_r = Color_xparent.red;
ptr->xparent_g = Color_xparent.green;
ptr->xparent_b = Color_xparent.blue;
if(ptr->total_frames == ptr->num_keys){
ptr->flags |= ANF_ALL_KEYFRAMES;
}
}
/**
* @brief Load an animation. This stores the compressed data, which instances of the animation can reference.
* Must be free'ed later with anim_free().
*
* @param real_filename Filename of animation
* @param cf_dir_type
* @param file_mapped Whether to use memory-mapped file or not.
*
* @details Memory-mapped files will page in the animation from disk as it is needed, but performance is not as good.
* @return Pointer to anim that is loaded if sucess, NULL if failure.
*/
anim *anim_load(char *real_filename, int cf_dir_type, int file_mapped)
{
anim *ptr;
CFILE *fp;
int count,idx;
char name[_MAX_PATH];
Assert( real_filename != NULL );
strcpy_s( name, real_filename );
char *p = strchr( name, '.' );
if ( p ) {
*p = 0;
}
strcat_s( name, ".ani" );
ptr = first_anim;
while (ptr) {
if (!stricmp(name, ptr->name))
break;
ptr = ptr->next;
}
if (!ptr) {
fp = cfopen(name, "rb", CFILE_NORMAL, cf_dir_type);
if ( !fp )
return NULL;
ptr = (anim *) vm_malloc(sizeof(anim));
Assert(ptr);
ptr->flags = 0;
ptr->next = first_anim;
first_anim = ptr;
Assert(strlen(name) < _MAX_PATH - 1);
strcpy_s(ptr->name, name);
ptr->instance_count = 0;
ptr->width = 0;
ptr->height = 0;
ptr->total_frames = 0;
ptr->keys = NULL;
ptr->ref_count=0;
anim_read_header(ptr, fp);
if(ptr->num_keys > 0){
ptr->keys = (key_frame*)vm_malloc(sizeof(key_frame) * ptr->num_keys);
Assert(ptr->keys != NULL);
}
// store how long the anim should take on playback (in seconds)
ptr->time = i2fl(ptr->total_frames)/ptr->fps;
for(idx=0;idx<ptr->num_keys;idx++){
ptr->keys[idx].frame_num = 0;
cfread(&ptr->keys[idx].frame_num, 2, 1, fp);
cfread(&ptr->keys[idx].offset, 4, 1, fp);
ptr->keys[idx].frame_num = INTEL_INT( ptr->keys[idx].frame_num ); //-V570
ptr->keys[idx].offset = INTEL_INT( ptr->keys[idx].offset ); //-V570
}
cfread(&count, 4, 1, fp); // size of compressed data
count = INTEL_INT( count );
ptr->cfile_ptr = NULL;
if ( file_mapped == PAGE_FROM_MEM) {
// Try mapping the file to memory
ptr->flags |= ANF_MEM_MAPPED;
ptr->cfile_ptr = cfopen(name, "rb", CFILE_MEMORY_MAPPED, cf_dir_type);
}
// couldn't memory-map file... must be in a packfile, so stream manually
if ( file_mapped && !ptr->cfile_ptr ) {
ptr->flags &= ~ANF_MEM_MAPPED;
ptr->flags |= ANF_STREAMED;
ptr->cfile_ptr = cfopen(name, "rb", CFILE_NORMAL, cf_dir_type);
}
ptr->cache = NULL;
// If it opened properly as mem-mapped (or streamed)
if (ptr->cfile_ptr != NULL) {
// VERY IMPORTANT STEP
// Set the data pointer to the compressed data (which is not at the start of the
// file). Use ftell() to find out how far we've already parsed into the file
//
int offset;
offset = cftell(fp);
ptr->file_offset = offset;
if ( ptr->flags & ANF_STREAMED ) {
ptr->data = NULL;
ptr->cache_file_offset = ptr->file_offset;
ptr->cache = (ubyte*)vm_malloc(ANI_STREAM_CACHE_SIZE+2);
Assert(ptr->cache);
cfseek(ptr->cfile_ptr, offset, CF_SEEK_SET);
cfread(ptr->cache, ANI_STREAM_CACHE_SIZE, 1, ptr->cfile_ptr);
} else {
ptr->data = (ubyte*)cf_returndata(ptr->cfile_ptr) + offset;
}
} else {
// Not a memory mapped file (or streamed)
ptr->flags &= ~ANF_MEM_MAPPED;
ptr->flags &= ~ANF_STREAMED;
ptr->data = (ubyte *) vm_malloc(count);
ptr->file_offset = -1;
cfread(ptr->data, count, 1, fp);
}
cfclose(fp);
// store screen signature, so we can tell if palette changes
ptr->screen_sig = gr_screen.signature;
anim_set_palette(ptr);
}
ptr->ref_count++;
return ptr;
}
/**
* @brief Free an animation that was loaded with anim_load().
* @details All instances referencing this animation must be free'ed or get an assert.
*/
int anim_free(anim *ptr)
{
Assert ( ptr != NULL );
anim *list, **prev_anim;
list = first_anim;
prev_anim = &first_anim;
while (list && (list != ptr)) {
prev_anim = &list->next;
list = list->next;
}
if ( !list )
return -2;
// only free when ref_count is 0
ptr->ref_count--;
if ( ptr->ref_count > 0 )
return -1;
// only free if there are no playing instances
if ( ptr->instance_count > 0 )
return -1;
if(ptr->keys != NULL){
vm_free(ptr->keys);
ptr->keys = NULL;
}
if ( ptr->flags & (ANF_MEM_MAPPED|ANF_STREAMED) ) {
cfclose(ptr->cfile_ptr);
if (ptr->cache != NULL) {
vm_free(ptr->cache);
ptr->cache = NULL;
}
}
else {
Assert(ptr->data);
if (ptr->data != NULL) {
vm_free(ptr->data);
ptr->data = NULL;
}
}
*prev_anim = ptr->next;
vm_free(ptr);
return 0;
}
/**
* @brief Return if an anim is playing or not.
*/
int anim_playing(anim_instance *ai)
{
Assert(ai != NULL);
if ( ai->frame == NULL )
return 0;
else
return 1;
}
/**
* @brief Called at the beginning of a mission to initialize any mission dependent anim data.
* @todo Redundant?
*/
void anim_level_init()
{
}
/**
* @brief Called after the end of a mission to clean up any mission dependent anim data.
*/
void anim_level_close()
{
anim_release_all_instances();
}
/**
* @brief Write the frames of a .ani file out to disk as .pcx files.
* @details Use naming convention: filename0000.pcx, filename0001.pcx etc.
*
* @return If success 0, or if failed -1
*/
int anim_write_frames_out(char *filename)
{
anim *source_anim;
anim_instance *ai;
char root_name[256], pcxname[256];
char buf[64];
int i,j;
ubyte **row_data;
strcpy_s(root_name, filename);
root_name[strlen(filename)-4] = 0;
source_anim = anim_load(filename);
if ( source_anim == NULL )
return -1;
ai = init_anim_instance(source_anim, 16);
row_data = (ubyte**)vm_malloc((source_anim->height+1) * 4);
for ( i = 0; i < source_anim->total_frames; i++ ) {
anim_get_next_raw_buffer(ai, 0, 0, 16);
strcpy_s(pcxname, root_name);
sprintf(buf,"%04d",i);
strcat_s(pcxname, buf);
for ( j = 0; j < source_anim->height; j++ ) {
row_data[j] = &ai->frame[j*source_anim->width];
}
pcx_write_bitmap( pcxname,
source_anim->width,
source_anim->height,
row_data,
source_anim->palette);
printf(".");
}
printf("\n");
vm_free(row_data);
return 0;
}
/**
* @brief Display information and statistics about a .ani file.
* @details This is called when -i switch is on when running ac.exe
*/
void anim_display_info(char *real_filename)
{
CFILE *fp;
anim A;
float percent;
int i, uncompressed, compressed, *key_frame_nums=NULL, tmp;
char filename[MAX_FILENAME_LEN];
strcpy_s( filename, real_filename );
char *p = strchr( filename, '.' );
if ( p ) {
*p = 0;
}
strcat_s( filename, ".ani" );
fp = cfopen(filename, "rb");
if ( !fp ) {
printf("Fatal error opening %s", filename);
return;
}
anim_read_header(&A, fp);
// read the keyframe frame nums and offsets
key_frame_nums = (int*)vm_malloc(sizeof(int)*A.num_keys);
Assert(key_frame_nums != NULL);
if (key_frame_nums == NULL)
return;
for ( i = 0; i < A.num_keys; i++ ) {
key_frame_nums[i] = 0;
cfread(&key_frame_nums[i], 2, 1, fp);
cfread(&tmp, 4, 1, fp);
}
cfread(&compressed, 4, 1, fp);
uncompressed = A.width * A.height * A.total_frames; // 8 bits per pixel
percent = i2fl(compressed) / uncompressed * 100.0f;
printf("%% of uncompressed size: %.0f%%\n", percent);
printf("Width: %d\n", A.width);
printf("Height: %d\n", A.height);
printf("Total Frames: %d\n", A.total_frames);
#ifndef NDEBUG
printf("Key Frames: %d\n", A.num_keys);
if ( A.num_keys > 1 && (A.total_frames != A.num_keys) ) {
printf("key list: (");
for ( i = 0; i < A.num_keys; i++ ) {
if ( i < A.num_keys-1 )
printf("%d, ", key_frame_nums[i]);
else
printf("%d)\n", key_frame_nums[i]);
}
}
#endif
printf("FPS: %d\n", A.fps);
#ifndef NDEBUG
printf("Transparent RGB: (%u,%u,%u)\n", A.xparent_r, A.xparent_g, A.xparent_b);
#endif
printf("ac version: %d\n", A.version);
if ( key_frame_nums != NULL ) {
vm_free(key_frame_nums);
}
cfclose(fp);
}
void anim_reverse_direction(anim_instance *ai)
{
int temp;
// you're not allowed to call anim_reverse_direction(...) unless every frame is a keyframe!!!!
// The God of Delta-RLE demands it be thus.
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.");
// flip the animation direction
if(ai->direction == ANIM_DIRECT_FORWARD){
ai->direction = ANIM_DIRECT_REVERSE;
} else if(ai->direction == ANIM_DIRECT_REVERSE){
ai->direction = ANIM_DIRECT_FORWARD;
}
// flip frame_num and last_frame_num
temp = ai->frame_num;
ai->frame_num = ai->last_frame_num;
ai->last_frame_num = temp;
// flip the start and stop at frames
temp = ai->stop_at;
ai->stop_at = ai->start_at;
ai->start_at = temp;
// make sure to sync up the time correctly
if(ai->direction == ANIM_DIRECT_FORWARD){
ai->time_elapsed = ((float)ai->frame_num - (float)ai->start_at - 1.0f) / (float)ai->parent->fps;
} else if(ai->direction == ANIM_DIRECT_REVERSE) {
ai->time_elapsed = ((float)ai->start_at - (float)ai->frame_num - 1.0f) / (float)ai->parent->fps;
}
}
void anim_pause(anim_instance *ai)
{
ai->paused = 1;
}
void anim_unpause(anim_instance *ai)
{
ai->paused = 0;
}
void anim_ignore_next_frametime()
{
Anim_ignore_frametime=1;
}
int anim_instance_is_streamed(anim_instance *ai)
{
Assert(ai);
return ( ai->parent->flags & ANF_STREAMED );
}
unsigned char anim_instance_get_byte(anim_instance *ai, int offset)
{
int absolute_offset;
anim *parent;
Assert(ai);
Assert(ai->parent->cfile_ptr);
Assert(ai->parent->flags & ANF_STREAMED);
parent = ai->parent;
absolute_offset = ai->file_offset + offset;
// maybe in cache?
int cache_offset;
cache_offset = absolute_offset - parent->cache_file_offset;
if ( (cache_offset >= 0) && (cache_offset < ANI_STREAM_CACHE_SIZE) ) {
return parent->cache[cache_offset];
} else {
// fill cache
cfseek(parent->cfile_ptr, absolute_offset, CF_SEEK_SET);
cfread(parent->cache, ANI_STREAM_CACHE_SIZE, 1, parent->cfile_ptr);
parent->cache_file_offset = absolute_offset;
return parent->cache[0];
}
}