e16/src/animation.c

488 lines
13 KiB
C

/*
* Copyright (C) 2012 Daniel Manjarres
* Copyright (C) 2013-2015 Kim Woelders
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies of the Software, its documentation and marketing & publicity
* materials, and acknowledgment shall be given in the documentation, materials
* and software packages that this Software was used.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <math.h>
#include "E.h"
#include "animation.h"
#include "eobj.h"
#include "timers.h"
#include "util.h"
#define ENABLE_DEBUG 1
#if ENABLE_DEBUG
#define Dprintf(fmt...) if(EDebug(EDBUG_TYPE_ANIM))Eprintf(fmt)
#define D2printf(fmt...) if(EDebug(EDBUG_TYPE_ANIM)>1)Eprintf(fmt)
#define D3printf(fmt...)
#define EOW(eo) (eo ? EobjGetXwin(eo) : NoXID)
#else
#define Dprintf(fmt...)
#define D2printf(fmt...)
#define D3printf(fmt...)
#endif /* ENABLE_DEBUG */
#define FPS Mode.screen.fps
static int timing_engine(void);
/*
* State
*/
static struct {
Timer *timer;
Idler *idler;
unsigned int time_ms; /* Just use Mode.events.time_ms? */
unsigned int seqn;
} Mode_anim = {
NULL, NULL, 0, 0
};
static int
_AnimatorsTimer(void *timer_call)
{
int frame_skip;
int dt;
/* Remember current event run sequence number */
Mode_anim.seqn = Mode.events.seqn;
/* Remember current event time */
Mode_anim.time_ms = Mode.events.time_ms;
frame_skip = timing_engine();
/* time = partial_frames * usecs_per_partial_frame */
if (frame_skip < 1000000)
dt = (1000 * (frame_skip + 1)) / FPS;
else
dt = 1000000000; /* Some arbitrary high value */
if (timer_call)
{
TimerSetInterval(Mode_anim.timer, dt);
}
else
{
TIMER_DEL(Mode_anim.timer);
TIMER_ADD(Mode_anim.timer, dt, _AnimatorsTimer, (void *)1L);
}
D2printf("%s (%s) frame_skip=%d dt=%d\n", __func__,
timer_call ? "TIMER" : "IDLER", frame_skip, dt);
return 1;
}
static void
_AnimatorsIdler(void *data)
{
/* Don't run idler if we have just run timer */
if (Mode_anim.seqn == Mode.events.seqn)
return;
_AnimatorsTimer(data);
}
static void
_AnimatorsInit(void)
{
TIMER_ADD(Mode_anim.timer, 100, _AnimatorsTimer, (void *)1L);
Mode_anim.idler = IdlerAdd(_AnimatorsIdler, (void *)0L);
}
/*
* The animation engine
*/
#define LATER(frame, than) ((int)(frame - than) > 0)
#define LATER_EQ(frame, than) ((int)(frame - than) >= 0)
struct _animator {
struct _animator *next;
AnimCbFunc *func;
AnimDoneFunc *done;
animation_category category;
int duration;
esound_e start_sound;
esound_e end_sound;
char initialized;
char serialize;
char cancelled;
unsigned int start_frame;
unsigned int end_frame;
unsigned int next_frame;
unsigned int last_tms;
EObj *eo;
};
static Animator *global_animators;
/* This is the frame we THINk we are currently displaying.
* The next frame to render is this + 1. */
static unsigned int current_frame_num = 1;
/* This is the number of the next frame we need to render for a pending
* animation */
static unsigned int skip_to_frame_num = 0;
static char anim_recheck = 0;
Animator *
AnimatorAdd(EObj *eo, animation_category category, AnimCbFunc *func,
int duration, int serialize, size_t data_size, void *data)
{
Animator *an, **insert;
an = (Animator *) calloc(1, sizeof(Animator) + data_size);
if (!an)
return NULL;
Dprintf("%s: %u/%u: %#x %p C%d\n", __func__,
current_frame_num, skip_to_frame_num, EOW(eo), an, category);
if (!Mode_anim.timer)
_AnimatorsInit();
insert = eo ? &eo->animations : &global_animators;
while (*insert)
insert = &((*insert)->next);
*insert = an;
an->func = func;
if (duration >= 0)
{
an->duration = (duration * FPS) / 1000; /* ms -> frames */
if (an->duration == 0)
an->duration = 1; /* At least one frame */
}
else
an->duration = -1; /* Forever */
an->category = category;
an->serialize = serialize;
an->eo = eo;
an->start_sound = an->end_sound = SOUND_NONE;
if (data_size)
memcpy(an + 1, data, data_size);
anim_recheck = 1;
return an;
}
void
AnimatorSetSound(Animator *an, esound_e start_sound, esound_e end_sound)
{
if (!an)
return;
an->start_sound = start_sound;
an->end_sound = end_sound;
}
void
AnimatorSetDoneFunc(Animator *an, AnimDoneFunc *done)
{
an->done = done;
}
static void
_AnimatorDel(Animator *an)
{
Dprintf("%s: %u/%u: %#x %p C%d\n", __func__,
current_frame_num, skip_to_frame_num, EOW(an->eo), an,
an->category);
Efree(an);
}
void
AnimatorsFree(EObj *eo)
{
Animator *an, *next;
for (an = eo->animations; an; an = next)
{
next = an->next;
_AnimatorDel(an);
}
}
void *
AnimatorGetData(Animator *an)
{
return (an) ? an + 1 : NULL;
}
/* Quarter period sinusoidal used in time limited animations */
#define M_PI_F ((float)(M_PI))
#define REMAINING(elapsed, duration) \
(int)(1024 * (1.f - cosf(((M_PI_F / 2 * (elapsed)) / (duration)))))
static unsigned int
_AnimatorsRun(Animator **head, unsigned int frame_num, unsigned int next_frame)
{
Animator *an, *next, **pprev;
int res;
int first;
int remaining;
int delta_t;
for (first = 1, pprev = head, an = *head; an; an = next)
{
D3printf("%s: %#x %p\n", __func__, EOW(an->eo), an);
next = an->next;
if (an->cancelled)
{
res = ANIM_RET_CANCEL_ANIM;
goto check_res;
}
if (!an->initialized)
{
/* Just added - calculate first/last frame */
/* NB! New animations start one frame into the future */
if (an->serialize)
{
/* Start when other non-forever animations have run */
if (!first)
goto do_next;
Dprintf("%s: %#x %p C%d: De-serialize\n", __func__,
EOW(an->eo), an, an->category);
}
an->initialized = 1;
an->start_frame = frame_num + 1;
an->end_frame = an->start_frame + an->duration - 1;
an->next_frame = an->start_frame;
an->last_tms = Mode_anim.time_ms;
}
/* Don't serialize animations that follow an inf loop with the inf loop */
if (an->duration > 0)
first = 0;
if (an->category >= 0 && LATER(an->next_frame, frame_num))
goto check_next_frame;
/*{ start of old _AnimatorRun() */
if (an->start_sound)
{
SoundPlay(an->start_sound);
an->start_sound = SOUND_NONE;
}
delta_t = Mode_anim.time_ms - an->last_tms;
an->last_tms = Mode_anim.time_ms;
if (an->duration > 0)
{
remaining = 0;
if (frame_num < an->end_frame)
remaining = REMAINING(an->end_frame - frame_num, an->duration);
}
else
{
remaining = delta_t;
}
D2printf("%s: eo=%p an=%p cat=%d rem=%4d dur=%4d dt=%4d\n", __func__,
an->eo, an, an->category, remaining, an->duration, delta_t);
res = an->func(an->eo, remaining, an + 1);
Dprintf("%s: res=%4d num=%u end=%u\n", __func__, res, frame_num,
an->end_frame);
if (res >= 0)
{
if (an->duration > 0 && remaining <= 0)
{
Dprintf("%s: %#x %p C%d: autocancelling\n", __func__,
EOW(an->eo), an, an->category);
res = ANIM_RET_CANCEL_ANIM;
}
}
else
{
Dprintf("%s: %#x %p C%d: self cancelling\n", __func__,
EOW(an->eo), an, an->category);
}
/*} end of old _AnimatorRun() */
check_res:
if (res >= 0)
{
/* animator will run again */
an->next_frame = frame_num + 1 + res;
}
else
{
if (an->done)
an->done(an->eo, an + 1);
if (an->end_sound)
SoundPlay(an->end_sound);
_AnimatorDel(an);
*pprev = next;
continue; /* Skip pprev update */
}
check_next_frame:
if (an->category >= 0 && LATER(next_frame, an->next_frame))
next_frame = an->next_frame;
do_next:
pprev = &an->next;
}
return next_frame;
}
static unsigned int
_AnimatorsRunAll(unsigned int frame_num)
{
EObj **lst;
EObj *const *lst2;
unsigned int next_frame;
int num, i;
lst2 = EobjListStackGet(&num);
lst = EMALLOC(EObj *, num);
memcpy(lst, lst2, num * sizeof(EObj *));
next_frame = frame_num + 0x7fffffff;
D3printf("%s: %u/%u\n", __func__, current_frame_num, skip_to_frame_num);
for (i = 0; i < num; i++)
next_frame = _AnimatorsRun(&lst[i]->animations, frame_num, next_frame);
Efree(lst);
next_frame = _AnimatorsRun(&global_animators, frame_num, next_frame);
return next_frame;
}
int
AnimatorDel(EObj *eo, Animator *anx)
{
Animator *an;
for (an = (eo) ? eo->animations : global_animators; an; an = an->next)
{
if (an != anx)
continue;
Dprintf("%s: %u/%u: %#x %p C%d\n", __func__,
current_frame_num, skip_to_frame_num, EOW(an->eo), an,
an->category);
an->cancelled = 1;
return 1;
}
return 0;
}
static unsigned int
_FrameNum(void)
{
static char init = 0;
static unsigned int tp = 0;
static unsigned int fp = 0;
unsigned int t, frame, dx;
t = GetTimeMs();
if (!init)
{
init = 1;
tp = t;
}
dx = t - tp;
frame = fp + (dx * FPS) / 1000;
if (dx > 1000000)
{
dx /= 1000;
tp += dx * 1000;
fp += dx * FPS;
}
return frame;
}
static unsigned int
get_check_frame_count(unsigned int last_frame __UNUSED__,
unsigned int skip_to_frame,
unsigned int *good_framesp,
unsigned int *last_skipped_framep, const char *msg)
{
unsigned int frame_num;
frame_num = _FrameNum();
if (frame_num > skip_to_frame)
{
if (EDebug(1))
Eprintf("@%u %s missed %u frames after %u [%u] good frames\n",
frame_num, msg, frame_num - skip_to_frame, *good_framesp,
skip_to_frame - 1 - *last_skipped_framep);
*good_framesp = 0;
*last_skipped_framep = frame_num - 1;
}
return frame_num;
}
static int
timing_engine(void)
{
static unsigned int last_frame_num;
static unsigned int good_frames;
static unsigned int last_skipped_frame;
int frameskip;
current_frame_num = get_check_frame_count(last_frame_num, skip_to_frame_num,
&good_frames, &last_skipped_frame,
"before render");
D2printf("%s: cur/last=%u/%u next=%u good=%u last-skipped=%u\n",
__func__, current_frame_num, last_frame_num, skip_to_frame_num,
good_frames, last_skipped_frame);
if (current_frame_num == last_frame_num && !anim_recheck)
goto done;
last_frame_num = current_frame_num;
anim_recheck = 0;
skip_to_frame_num = _AnimatorsRunAll(current_frame_num);
done:
frameskip = skip_to_frame_num - current_frame_num - 1;
return frameskip;
}