488 lines
13 KiB
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;
|
|
}
|