Skip to content

Instantly share code, notes, and snippets.

@idiotWu
Last active June 4, 2019 16:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save idiotWu/fb6b084fab6db0d6c4ab60b27e6ddfa6 to your computer and use it in GitHub Desktop.
Save idiotWu/fb6b084fab6db0d6c4ab60b27e6ddfa6 to your computer and use it in GitHub Desktop.
Simple timeline-based animation engine
/**
* Timeline based animation engine
*
* t
* ------+------------------------------------------> timeline
* +-----|-+
* | A | | <- animation{}
* +-----|-+
* +--|------+
* | | A |
* +--|------+
* +----|+-----+-----+-----+-----+
* | A || A | A | A | A | <- repeating animation
* +----|+-----+-----+-----+-----+
* |
* render(t)
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#include "engine.h"
#include "list.h"
defNode(TimelineNode, Animation);
// typedef struct TimelineNode {
// Animation* data;
// struct TimelineNode* prev;
// struct TimelineNode* next;
// } TimelineNode;
defList(Timeline, TimelineNode);
// typedef struct Timeline {
// size_t count;
// TimelineNode* head;
// TimelineNode* tail;
// } Timeline;
static Timeline gameTL;
// reset state to the beginning of animaiton
static void resetFrame(Animation* animation) {
animation->currentFrame = 1;
animation->elapsed = 0;
}
/**
* Right-continuous animation (repeat > 1):
* - jumps to the first frame when the animation ends;
* - renders one more frame per sencond.
*
* 3 frames non-repeat: (3 FPS)
* [-------|-------]
* 1 2 3
*
* 3 frames * 2 repeats: (3+1 FPS)
* [----|----|----][----|----|----]
* 1 2 3 1(4) 2 3 1(4)
*/
Animation* createAnimation(uint16_t frameCount,
double duration,
uint16_t repeat) {
assert(frameCount > 0);
assert(repeat > 0);
Animation* animation = malloc(sizeof(Animation));
resetFrame(animation);
animation->frameCount = frameCount;
animation->framesPerSecond = repeat == 1 ? frameCount : frameCount + 1;
animation->interval = round(duration / ANIMATION_60_FPS / animation->framesPerSecond);
animation->nth = 1;
animation->repeat = repeat;
animation->from = NULL;
animation->to = NULL;
animation->render = NULL;
animation->complete = NULL;
TimelineNode* node = (TimelineNode*)createNode();
node->data = animation;
listAppend((List*)&gameTL, (Node*)node);
return animation;
}
Animation* createAnimation60FPS(double duration, uint16_t repeat) {
return createAnimation(round(duration / ANIMATION_60_FPS), duration, repeat);
}
bool cancelAnimation(Animation* animation) {
TimelineNode* node = gameTL.head;
while (node) {
if (node->data == animation) {
listDelete((List*)&gameTL, (Node*)node);
return true;
}
node = node->next;
}
return false;
}
// render next frame
void engineNext(void) {
if (!gameTL.count) {
return;
}
TimelineNode* node = gameTL.head;
TimelineNode* next;
do {
// record `next` in case that node is deleted
next = node->next;
Animation* animation = node->data;
animation->render(animation);
animation->elapsed++;
// elapsed < interval
// -> waiting
if (animation->elapsed < animation->interval) {
continue;
}
// elapsed == interval
// -> next frame
animation->currentFrame++;
animation->elapsed = 0;
// currentFrame < framesPerSecond
// -> running
if (animation->currentFrame < animation->framesPerSecond) {
continue;
}
// currentFrame == framesPerSecond
// -> repeat or finish
// repeating mode: jump to the first frame
if (animation->repeat > 1) {
resetFrame(animation);
}
// should repeat?
if (animation->repeat == ANIMATION_INFINITY ||
animation->nth < animation->repeat) {
animation->nth++;
} else {
// finished
// render the last frame
animation->render(animation);
if (animation->complete) {
animation->complete(animation);
}
free(animation->from);
free(animation->to);
listDelete((List*)&gameTL, (Node*)node);
}
} while ((node = next) != NULL);
}
#ifndef ENGINE_H
#define ENGINE_H
#include <stdint.h>
#include <stdbool.h>
#define ANIMATION_60_FPS (1000.0 / 60.0)
#define ANIMATION_ONCE 1
#define ANIMATION_INFINITY UINT16_MAX
typedef struct Animation {
void* from;
void* to;
uint16_t currentFrame;
uint16_t frameCount;
uint16_t framesPerSecond;
uint16_t elapsed;
uint16_t interval;
uint16_t nth;
uint16_t repeat;
void (*render)(struct Animation*); // render function
void (*complete)(struct Animation*); // on complete callback function
} Animation;
Animation* createAnimation(uint16_t frames,
double duration,
uint16_t repeat);
Animation* createAnimation60FPS(double duration, uint16_t repeat);
bool cancelAnimation(Animation* animation);
// render next frame
void engineNext(void);
#endif
#include <stdlib.h>
#include <assert.h>
#include "list.h"
void listAppend(List* list, Node* node) {
if (list->count == 0) {
list->head = list->tail = node;
} else {
list->tail->next = node;
node->prev = list->tail;
list->tail = node;
}
list->count++;
}
void listPrepend(List* list, Node* node) {
if (list->count == 0) {
list->head = list->tail = node;
} else {
list->head->prev = node;
node->next = list->head;
list->head = node;
}
list->count++;
}
void listInsertAfter(List* list, Node* prev, Node* node) {
Node* next = prev->next;
prev->next = node;
node->prev = prev;
next->prev = node;
node->next = next;
list->count++;
}
void listDelete(List* list, Node* node) {
assert(list->count > 0);
Node* prev = node->prev;
Node* next = node->next;
if (list->head == node) {
list->head = next;
}
if (list->tail == node) {
list->tail = prev;
}
if (prev != NULL) {
prev->next = next;
}
if (next != NULL) {
next->prev = prev;
}
node->next = node->prev = NULL;
listFreeNode(node);
list->count--;
}
void listFreeNode(Node* node) {
free(node->data);
free(node);
}
void listDestory(List* list) {
Node* node = list->head;
while (node) {
listFreeNode(node);
node = node->next;
}
free(list);
}
List* createList(void) {
List* list = malloc(sizeof(List));
list->count = 0;
list->head = list->tail = NULL;
return list;
}
Node* createNode(void) {
Node* node = malloc(sizeof(Node));
node->data = NULL;
node->prev = node->next = NULL;
return node;
}
/**
* Generic Doubly-linked List
*/
#ifndef LIST_H
#define LIST_H
#include <stddef.h>
#include <assert.h>
#define __HAS_ELEMENT(TYPE_A, TYPE_B, ELEMENT) \
assert(offsetof(TYPE_A, ELEMENT) == offsetof(TYPE_B, ELEMENT))
// duck typing check
#define SAFE_DUCK_LIST(DuckList) do {\
__HAS_ELEMENT(DuckList, List, count);\
__HAS_ELEMENT(DuckList, List, head);\
__HAS_ELEMENT(DuckList, List, tail);\
} while(0)
#define SAFE_DUCK_NODE(DuckNode) do {\
__HAS_ELEMENT(DuckNode, Node, data);\
__HAS_ELEMENT(DuckNode, Node, prev);\
__HAS_ELEMENT(DuckNode, Node, next);\
} while(0)
#define defNode(NAME, DATA_TYPE) \
typedef struct NAME {\
DATA_TYPE* data;\
struct NAME* prev;\
struct NAME* next;\
} NAME
#define defList(NAME, NODE_TYPE) \
typedef struct NAME {\
size_t count;\
NODE_TYPE* head;\
NODE_TYPE* tail;\
} NAME
defNode(Node, void);
//typedef struct Node {
// void* data;
// struct Node* prev;
// struct Node* next;
//} Node;
defList(List, Node);
//typedef struct List {
// size_t count;
// Node* head;
// Node* tail;
//} List;
void listAppend(List* list, Node* node);
void listPrepend(List* list, Node* node);
void listInsertAfter(List* list, Node* prev, Node* node);
void listDelete(List* list, Node* node);
void listFreeNode(Node* node);
void listDestory(List* list);
List* createList(void);
Node* createNode(void);
#endif
#define _USE_MATH_DEFINES
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "glut.h"
#include "engine.h"
#define RENDER_WAVE
#define LISTEN_MOUSE_STAR
#define deg2rad(x) (x / 180.0 * M_PI)
#define UNUSED(x) (void)(x)
#define RECT_COUNT 60
#define RECT_WIDTH (2.0 / RECT_COUNT)
typedef struct Rectangle {
double x1;
double y1;
double x2;
double y2;
double color[3];
} Rectangle;
typedef struct Star {
double x;
double y;
double r;
double rot;
double color[4];
} Star;
static int randInitFlag = 0;
double randomBetween(double lower, double upper) {
// init once
if (!randInitFlag) {
randInitFlag = 1;
srand((unsigned int)time(NULL));
}
return (double)rand() / (double)RAND_MAX * (upper - lower) + lower;
}
double easeInOutBack(double p) {
if (p < 0.5) {
double f = 2 * p;
return 0.5 * (f * f * f - f * sin(f * M_PI));
} else {
double f = (1 - (2*p - 1));
return 0.5 * (1 - (f * f * f - f * sin(f * M_PI))) + 0.5;
}
}
// render sin wave
void renderWave(Animation* animation) {
Rectangle* rect = animation->from;
double percent = (double)animation->currentFrame / animation->frameCount;
double pFix = percent < 0.5 ? (percent * 2) : (1 - (percent - 0.5) * 2);
double pos = easeInOutBack(pFix);
if (animation->currentFrame == 1) {
rect->color[0] = randomBetween(0.5, 1);
rect->color[1] = randomBetween(0.5, 1);
rect->color[2] = randomBetween(0.5, 1);
}
glColor3dv(rect->color);
glRectd(
rect->x1,
rect->y1,
rect->x2,
rect->y1 + (rect->y2 - rect->y1) * pos);
}
// create sin wave
static int lastOffset = -1;
void createWave(Animation* animation) {
double interval = (double)(animation->frameCount) / RECT_COUNT;
int offset = animation->currentFrame / interval;
if (offset > lastOffset) {
lastOffset = offset;
Rectangle* rect = malloc(sizeof(Rectangle));
double offset = RECT_WIDTH * (int)((animation->currentFrame - 1) / interval);
double height = sin((double)animation->currentFrame / (animation->frameCount) * M_PI) * 1.0 + 0.25;
rect->x1 = -1.0 + offset;
rect->y1 = -1.0;
rect->x2 = rect->x1 + RECT_WIDTH;
rect->y2 = -1.0 + height;
Animation* a = createAnimation60FPS(1500, ANIMATION_INFINITY);
a->from = rect;
a->render = renderWave;
}
}
// render falling star
const double starVertexes[5] = { 90.0, 234.0, 18.0, 162.0, 306.0 };
void renderStar(Animation* animation) {
Star* star = animation->from;
star->r *= 0.95;
star->y -= 1.0/60.0;
star->rot += 10.0;
star->color[3] = 1.0 - (double)animation->currentFrame / animation->frameCount;
double scaleX = (double)glutGet(GLUT_WINDOW_WIDTH) / glutGet(GLUT_WINDOW_HEIGHT);
glColor4dv(star->color);
glBegin(GL_TRIANGLE_FAN);
glScaled(10.0, 1.0, 1.0);
glVertex2d(star->x, star->y);
for (size_t i = 0; i <= 5; i++) {
double rad = deg2rad(star->rot +starVertexes[i % 5]);
glVertex2d(star->x + star->r / scaleX * cos(rad), star->y + star->r * sin(rad));
}
glEnd();
}
// create star on mouse move
void mouseListenerStar(int x, int y) {
double u = (double)x / glutGet(GLUT_WINDOW_WIDTH) * 2.0 - 1.0;
double v = -(double)y / glutGet(GLUT_WINDOW_HEIGHT) * 2.0 + 1.0;
Star* star = malloc(sizeof(Star));
star->x = u;
star->y = v;
star->r = randomBetween(0.05, 0.1);
star->rot = randomBetween(0.0, 360.0);
star->color[0] = randomBetween(0.5, 1);
star->color[1] = randomBetween(0.5, 1);
star->color[2] = randomBetween(0.5, 1);
Animation* animation = createAnimation60FPS(2000, 1);
animation->from = star;
animation->render = renderStar;
}
void init(void) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glClearColor(0.0, 0.0, 0.0, 0.0);
#ifdef RENDER_WAVE
Animation* animation = createAnimation60FPS(1000, 1);
animation->render = createWave;
#endif
#ifdef LISTEN_MOUSE_STAR
glutMotionFunc(mouseListenerStar);
glutPassiveMotionFunc(mouseListenerStar);
#endif
}
void update(int _) {
glutPostRedisplay();
UNUSED(_);
glutTimerFunc(ANIMATION_60_FPS, update, 0);
}
void display(void) {
glClear(GL_COLOR_BUFFER_BIT);
engineNext();
glutSwapBuffers();
}
void reshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
}
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 800);
glutCreateWindow("Test");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
init();
update(0);
glutMainLoop();
return EXIT_SUCCESS;
}
@idiotWu
Copy link
Author

idiotWu commented Jun 4, 2019

@idiotWu
Copy link
Author

idiotWu commented Jun 4, 2019

Usage Example:

void renderRect(Animation* animation) {
  double percent = (double)animation->currentFrame / animation->frameCount;

  double c = percent < 0.5 ? (percent * 2) : (1 - (percent - 0.5) * 2);

  printf("%2d/%2d\t%.2lf\n", animation->currentFrame, animation->frameCount, c);

  glColor3d(0.75 + c * 0.25, c, 1 - c);
  glRectd(-1.0 + c, -0.5, c, 0.5);
}

void display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  engineNext();
  glutSwapBuffers();
}

int main(void) {
  // ...
  glutDisplayFunc(display);
  Animation* animation = createAnimation60FPS(1000, ANIMATION_INFINITY);
  animation->render = renderRect;
  // ...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment