# HG changeset patch # User John Tsiombikas # Date 1325992393 -7200 # Node ID c97151c60302dc568c2c538de968738712cce430 libanim mercurial repo diff -r 000000000000 -r c97151c60302 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,7 @@ +\.d$ +\.o$ +\.swp$ +^libanim.a$ +^libanim.so +^libanim.dylib +^Makefile$ diff -r 000000000000 -r c97151c60302 Makefile.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.in Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,63 @@ +src = $(wildcard src/*.c) +hdr = src/track.h src/anim.h src/config.h +obj = $(src:.c=.o) +dep = $(obj:.o=.d) +lib_a = libanim.a + +ifeq ($(shell uname -s), Darwin) + lib_so = anim.dylib + shared = -dynamiclib +else + somajor = 0 + sominor = 1 + soname = libanim.so.$(somajor) + lib_so = $(soname).$(sominor) + solink = libanim.so + shared = -shared -Wl,-soname,$(soname) +endif + + +CC = gcc +AR = ar +CFLAGS = $(opt) $(dbg) -pedantic -Wall -fPIC -I$(PREFIX)/include +LDFLAGS = -L$(PREFIX)/lib -lvmath -lm -lpthread + +.PHONY: all +all: $(lib_a) $(lib_so) + +$(lib_a): $(obj) + $(AR) rcs $@ $(obj) + +$(lib_so): $(obj) + $(CC) $(shared) -o $@ $(obj) $(LDFLAGS) + +-include $(dep) + +%.d: %.c + @$(CPP) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@ + +.PHONY: install +install: $(lib_a) $(lib_so) + mkdir -p $(PREFIX)/lib $(PREFIX)/include/anim + cp $(lib_a) $(PREFIX)/lib/$(lib_a) + cp $(lib_so) $(PREFIX)/lib/$(lib_so) + [ -n "$(solink)" ] && rm -f $(PREFIX)/lib/$(soname) $(PREFIX)/lib/$(solink) \ + && ln -s $(PREFIX)/lib/$(lib_so) $(PREFIX)/lib/$(soname) \ + && ln -s $(PREFIX)/lib/$(soname) $(PREFIX)/lib/$(solink) \ + || true + cp $(hdr) $(PREFIX)/include/anim/ + +.PHONY: uninstall +uninstall: + rm -f $(PREFIX)/lib/$(lib_a) + rm -f $(PREFIX)/lib/$(lib_so) + rm -f $(PREFIX)/include/anim/*.h + rmdir $(PREFIX)/include/anim + +.PHONY: clean +clean: + rm -f $(obj) $(lib_so) $(lib_a) + +.PHONY: distclean +distclean: clean + rm -f Makefile diff -r 000000000000 -r c97151c60302 configure --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/configure Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,85 @@ +#!/bin/sh + +PREFIX=/usr/local +OPT=yes +DBG=yes +PTHREAD=no + +config_h=src/config.h + +#echo "configuring libanim $VERSION ..." + +for arg; do + case "$arg" in + --prefix=*) + value=`echo $arg | sed 's/--prefix=//'` + PREFIX=${value:-$prefix} + ;; + + --enable-opt) + OPT=yes;; + --disable-opt) + OPT=no;; + + --enable-debug) + DBG=yes;; + --disable-debug) + DBG=no;; + + --thread-safe) + PTHREAD=yes;; + --thread-unsafe) + PTHREAD=no;; + + --help) + echo 'usage: ./configure [options]' + echo 'options:' + echo ' --prefix=: installation path (default: /usr/local)' + echo ' --enable-opt: enable speed optimizations (default)' + echo ' --disable-opt: disable speed optimizations' + echo ' --enable-debug: include debugging symbols (default)' + echo ' --disable-debug: do not include debugging symbols' + echo ' --thread-safe: protect concurrent access to matrix cache' + echo ' --thread-unsafe: assume only single-threaded operation (default)' + echo 'all invalid options are silently ignored' + exit 0 + ;; + esac +done + +echo "prefix: $PREFIX" +echo "optimize for speed: $OPT" +echo "include debugging symbols: $DBG" + +echo 'creating makefile ...' +echo "PREFIX = $PREFIX" >Makefile +if [ "$DBG" = 'yes' ]; then + echo 'dbg = -g' >>Makefile +fi +if [ "$OPT" = 'yes' ]; then + echo 'opt = -O3' >>Makefile +fi +if [ "$PTHREAD" = yes ]; then + echo 'pthr = -lpthread' >>Makefile +fi + +cat Makefile.in >>Makefile + +echo 'creating config.h ...' +echo '#ifndef ANIM_CONFIG_H_' >src/config.h +echo '#define ANIM_CONFIG_H_' >>src/config.h +echo >>src/config.h +if [ "$PTHREAD" = yes ]; then + echo '#define ANIM_THREAD_SAFE' >>src/config.h +else + echo '#undef ANIM_THREAD_SAFE' >>src/config.h +fi +echo >>src/config.h +echo '#endif /* ANIM_CONFIG_H_ */'>>src/config.h + +#echo 'creating pkg-config file ...' +#echo "prefix=$PREFIX" >vmath.pc +#echo "ver=$VERSION" >>vmath.pc +#cat vmath.pc.in >>vmath.pc + +echo 'configuration completed, type make (or gmake) to build.' diff -r 000000000000 -r c97151c60302 src/anim.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/anim.c Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,451 @@ +#include +#include +#include "anim.h" +#include "dynarr.h" + +static void invalidate_cache(struct anm_node *node); + +int anm_init_node(struct anm_node *node) +{ + int i, j; + static const float defaults[] = { + 0.0f, 0.0f, 0.0f, /* default position */ + 0.0f, 0.0f, 0.0f, 1.0f, /* default rotation quat */ + 1.0f, 1.0f, 1.0f /* default scale factor */ + }; + + memset(node, 0, sizeof *node); + + /* initialize thread-local matrix cache */ + pthread_key_create(&node->cache_key, 0); + + for(i=0; itracks + i) == -1) { + for(j=0; jtracks + i); + } + } + anm_set_track_default(node->tracks + i, defaults[i]); + } + return 0; +} + +void anm_destroy_node(struct anm_node *node) +{ + int i; + free(node->name); + + for(i=0; itracks + i); + } + + /* destroy thread-specific cache */ + pthread_key_delete(node->cache_key); + + while(node->cache_list) { + struct mat_cache *tmp = node->cache_list; + node->cache_list = tmp->next; + free(tmp); + } +} + +void anm_destroy_node_tree(struct anm_node *tree) +{ + struct anm_node *c, *tmp; + + if(!tree) return; + + c = tree->child; + while(c) { + tmp = c; + c = c->next; + + anm_destroy_node_tree(tmp); + } + anm_destroy_node(tree); +} + +struct anm_node *anm_create_node(void) +{ + struct anm_node *n; + + if((n = malloc(sizeof *n))) { + if(anm_init_node(n) == -1) { + free(n); + return 0; + } + } + return n; +} + +void anm_free_node(struct anm_node *node) +{ + anm_destroy_node(node); + free(node); +} + +void anm_free_node_tree(struct anm_node *tree) +{ + struct anm_node *c, *tmp; + + if(!tree) return; + + c = tree->child; + while(c) { + tmp = c; + c = c->next; + + anm_free_node_tree(tmp); + } + + anm_free_node(tree); +} + +int anm_set_node_name(struct anm_node *node, const char *name) +{ + char *str; + + if(!(str = malloc(strlen(name) + 1))) { + return -1; + } + strcpy(str, name); + free(node->name); + node->name = str; + return 0; +} + +const char *anm_get_node_name(struct anm_node *node) +{ + return node->name ? node->name : ""; +} + +void anm_set_interpolator(struct anm_node *node, enum anm_interpolator in) +{ + int i; + + for(i=0; itracks + i, in); + } + invalidate_cache(node); +} + +void anm_set_extrapolator(struct anm_node *node, enum anm_extrapolator ex) +{ + int i; + + for(i=0; itracks + i, ex); + } + invalidate_cache(node); +} + +void anm_link_node(struct anm_node *p, struct anm_node *c) +{ + c->next = p->child; + p->child = c; + + c->parent = p; + invalidate_cache(c); +} + +int anm_unlink_node(struct anm_node *p, struct anm_node *c) +{ + struct anm_node *iter; + + if(p->child == c) { + p->child = c->next; + c->next = 0; + invalidate_cache(c); + return 0; + } + + iter = p->child; + while(iter->next) { + if(iter->next == c) { + iter->next = c->next; + c->next = 0; + invalidate_cache(c); + return 0; + } + } + return -1; +} + +void anm_set_position(struct anm_node *node, vec3_t pos, anm_time_t tm) +{ + anm_set_value(node->tracks + ANM_TRACK_POS_X, tm, pos.x); + anm_set_value(node->tracks + ANM_TRACK_POS_Y, tm, pos.y); + anm_set_value(node->tracks + ANM_TRACK_POS_Z, tm, pos.z); + invalidate_cache(node); +} + +vec3_t anm_get_node_position(struct anm_node *node, anm_time_t tm) +{ + vec3_t v; + v.x = anm_get_value(node->tracks + ANM_TRACK_POS_X, tm); + v.y = anm_get_value(node->tracks + ANM_TRACK_POS_Y, tm); + v.z = anm_get_value(node->tracks + ANM_TRACK_POS_Z, tm); + return v; +} + +void anm_set_rotation(struct anm_node *node, quat_t rot, anm_time_t tm) +{ + anm_set_value(node->tracks + ANM_TRACK_ROT_X, tm, rot.x); + anm_set_value(node->tracks + ANM_TRACK_ROT_Y, tm, rot.y); + anm_set_value(node->tracks + ANM_TRACK_ROT_Z, tm, rot.z); + anm_set_value(node->tracks + ANM_TRACK_ROT_W, tm, rot.w); + invalidate_cache(node); +} + +quat_t anm_get_node_rotation(struct anm_node *node, anm_time_t tm) +{ + int idx0, idx1, last_idx; + anm_time_t tstart, tend; + float t, dt; + struct anm_track *track_x, *track_y, *track_z, *track_w; + quat_t q, q1, q2; + + track_x = node->tracks + ANM_TRACK_ROT_X; + track_y = node->tracks + ANM_TRACK_ROT_Y; + track_z = node->tracks + ANM_TRACK_ROT_Z; + track_w = node->tracks + ANM_TRACK_ROT_W; + + if(!track_x->count) { + q.x = track_x->def_val; + q.y = track_y->def_val; + q.z = track_z->def_val; + q.w = track_w->def_val; + return q; + } + + last_idx = track_x->count - 1; + + tstart = track_x->keys[0].time; + tend = track_x->keys[last_idx].time; + tm = anm_remap_time(track_x, tm, tstart, tend); + + idx0 = anm_get_key_interval(track_x, tm); + assert(idx0 >= 0 && idx0 < track_x->count); + idx1 = idx0 + 1; + + dt = (float)(track_x->keys[idx1].time - track_x->keys[idx0].time); + t = (float)(tm - track_x->keys[idx0].time) / dt; + + q1.x = track_x->keys[idx0].val; + q1.y = track_y->keys[idx0].val; + q1.z = track_z->keys[idx0].val; + q1.w = track_w->keys[idx0].val; + + q2.x = track_x->keys[idx1].val; + q2.y = track_y->keys[idx1].val; + q2.z = track_z->keys[idx1].val; + q2.w = track_w->keys[idx1].val; + + return quat_slerp(q1, q2, t); +} + +void anm_set_scaling(struct anm_node *node, vec3_t scl, anm_time_t tm) +{ + anm_set_value(node->tracks + ANM_TRACK_SCL_X, tm, scl.x); + anm_set_value(node->tracks + ANM_TRACK_SCL_Y, tm, scl.y); + anm_set_value(node->tracks + ANM_TRACK_SCL_Z, tm, scl.z); + invalidate_cache(node); +} + +vec3_t anm_get_node_scaling(struct anm_node *node, anm_time_t tm) +{ + vec3_t v; + v.x = anm_get_value(node->tracks + ANM_TRACK_SCL_X, tm); + v.y = anm_get_value(node->tracks + ANM_TRACK_SCL_Y, tm); + v.z = anm_get_value(node->tracks + ANM_TRACK_SCL_Z, tm); + return v; +} + + +vec3_t anm_get_position(struct anm_node *node, anm_time_t tm) +{ + mat4_t xform; + vec3_t pos = {0.0, 0.0, 0.0}; + + if(!node->parent) { + return anm_get_node_position(node, tm); + } + + anm_get_matrix(node, xform, tm); + return v3_transform(pos, xform); +} + +quat_t anm_get_rotation(struct anm_node *node, anm_time_t tm) +{ + quat_t rot, prot; + rot = anm_get_node_rotation(node, tm); + + if(!node->parent) { + return rot; + } + + prot = anm_get_rotation(node->parent, tm); + return quat_mul(prot, rot); +} + +vec3_t anm_get_scaling(struct anm_node *node, anm_time_t tm) +{ + vec3_t s, ps; + s = anm_get_node_scaling(node, tm); + + if(!node->parent) { + return s; + } + + ps = anm_get_scaling(node->parent, tm); + return v3_mul(s, ps); +} + +void anm_set_pivot(struct anm_node *node, vec3_t piv) +{ + node->pivot = piv; +} + +vec3_t anm_get_pivot(struct anm_node *node) +{ + return node->pivot; +} + +void anm_get_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm) +{ + struct mat_cache *cache = pthread_getspecific(node->cache_key); + if(!cache) { + cache = malloc(sizeof *cache); + assert(cache); + + pthread_mutex_lock(&node->cache_list_lock); + cache->next = node->cache_list; + node->cache_list = cache; + pthread_mutex_unlock(&node->cache_list_lock); + + cache->time = ANM_TIME_INVAL; + pthread_setspecific(node->cache_key, cache); + } + + if(cache->time != tm) { + mat4_t tmat, rmat, smat, pivmat, neg_pivmat; + vec3_t pos, scale; + quat_t rot; + + m4_identity(tmat); + /*no need to m4_identity(rmat); quat_to_mat4 sets this properly */ + m4_identity(smat); + m4_identity(pivmat); + m4_identity(neg_pivmat); + + pos = anm_get_node_position(node, tm); + rot = anm_get_node_rotation(node, tm); + scale = anm_get_node_scaling(node, tm); + + m4_translate(pivmat, node->pivot.x, node->pivot.y, node->pivot.z); + m4_translate(neg_pivmat, -node->pivot.x, -node->pivot.y, -node->pivot.z); + + m4_translate(tmat, pos.x, pos.y, pos.z); + quat_to_mat4(rmat, rot); + m4_translate(smat, scale.x, scale.y, scale.z); + + /* ok this would look nicer in C++ */ + m4_mult(cache->matrix, pivmat, tmat); + m4_mult(cache->matrix, cache->matrix, rmat); + m4_mult(cache->matrix, cache->matrix, smat); + m4_mult(cache->matrix, cache->matrix, neg_pivmat); + + if(node->parent) { + mat4_t parent_mat; + + anm_get_matrix(node->parent, mat, tm); + m4_mult(cache->matrix, parent_mat, cache->matrix); + } + cache->time = tm; + } + m4_copy(mat, cache->matrix); +} + +void anm_get_inv_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm) +{ + struct mat_cache *cache = pthread_getspecific(node->cache_key); + if(!cache) { + cache = malloc(sizeof *cache); + assert(cache); + + pthread_mutex_lock(&node->cache_list_lock); + cache->next = node->cache_list; + node->cache_list = cache; + pthread_mutex_unlock(&node->cache_list_lock); + + cache->inv_time = ANM_TIME_INVAL; + pthread_setspecific(node->cache_key, cache); + } + + if(cache->inv_time != tm) { + anm_get_matrix(node, mat, tm); + m4_inverse(cache->inv_matrix, mat); + cache->inv_time = tm; + } + m4_copy(mat, cache->inv_matrix); +} + +anm_time_t anm_get_start_time(struct anm_node *node) +{ + int i; + struct anm_node *c; + anm_time_t res = LONG_MAX; + + for(i=0; itracks[i].count) { + anm_time_t tm = node->tracks[i].keys[0].time; + if(tm < res) { + res = tm; + } + } + } + + c = node->child; + while(c) { + anm_time_t tm = anm_get_start_time(c); + if(tm < res) { + res = tm; + } + c = c->next; + } + return res; +} + +anm_time_t anm_get_end_time(struct anm_node *node) +{ + int i; + struct anm_node *c; + anm_time_t res = LONG_MIN; + + for(i=0; itracks[i].count) { + anm_time_t tm = node->tracks[i].keys[node->tracks[i].count - 1].time; + if(tm > res) { + res = tm; + } + } + } + + c = node->child; + while(c) { + anm_time_t tm = anm_get_end_time(c); + if(tm > res) { + res = tm; + } + c = c->next; + } + return res; +} + +static void invalidate_cache(struct anm_node *node) +{ + struct mat_cache *cache = pthread_getspecific(node->cache_key); + if(cache) { + cache->time = ANM_TIME_INVAL; + } +} diff -r 000000000000 -r c97151c60302 src/anim.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/anim.h Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,107 @@ +#ifndef LIBANIM_H_ +#define LIBANIM_H_ + +#include "config.h" + +#include + +#include +#include "track.h" + +enum { + ANM_TRACK_POS_X, + ANM_TRACK_POS_Y, + ANM_TRACK_POS_Z, + + ANM_TRACK_ROT_X, + ANM_TRACK_ROT_Y, + ANM_TRACK_ROT_Z, + ANM_TRACK_ROT_W, + + ANM_TRACK_SCL_X, + ANM_TRACK_SCL_Y, + ANM_TRACK_SCL_Z, + + ANM_NUM_TRACKS +}; + +struct anm_node { + char *name; + + struct anm_track tracks[ANM_NUM_TRACKS]; + vec3_t pivot; + + /* matrix cache */ + struct mat_cache { + mat4_t matrix, inv_matrix; + anm_time_t time, inv_time; + struct mat_cache *next; + } *cache_list; + pthread_key_t cache_key; + pthread_mutex_t cache_list_lock; + + struct anm_node *parent; + struct anm_node *child; + struct anm_node *next; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* node constructor and destructor */ +int anm_init_node(struct anm_node *node); +void anm_destroy_node(struct anm_node *node); + +/* recursively destroy an animation node tree */ +void anm_destroy_node_tree(struct anm_node *tree); + +/* helper functions to allocate/construct and destroy/free with + * a single call. They call anm_init_node and anm_destroy_node + * internally. + */ +struct anm_node *anm_create_node(void); +void anm_free_node(struct anm_node *node); + +/* recursively destroy and free the nodes of a node tree */ +void anm_free_node_tree(struct anm_node *tree); + +int anm_set_node_name(struct anm_node *node, const char *name); +const char *anm_get_node_name(struct anm_node *node); + +void anm_set_interpolator(struct anm_node *node, enum anm_interpolator in); +void anm_set_extrapolator(struct anm_node *node, enum anm_extrapolator ex); + +/* link and unlink nodes with parent/child relations */ +void anm_link_node(struct anm_node *parent, struct anm_node *child); +int anm_unlink_node(struct anm_node *parent, struct anm_node *child); + +void anm_set_position(struct anm_node *node, vec3_t pos, anm_time_t tm); +vec3_t anm_get_node_position(struct anm_node *node, anm_time_t tm); + +void anm_set_rotation(struct anm_node *node, quat_t rot, anm_time_t tm); +quat_t anm_get_node_rotation(struct anm_node *node, anm_time_t tm); + +void anm_set_scaling(struct anm_node *node, vec3_t scl, anm_time_t tm); +vec3_t anm_get_node_scaling(struct anm_node *node, anm_time_t tm); + +/* these three return the full p/r/s taking hierarchy into account */ +vec3_t anm_get_position(struct anm_node *node, anm_time_t tm); +quat_t anm_get_rotation(struct anm_node *node, anm_time_t tm); +vec3_t anm_get_scaling(struct anm_node *node, anm_time_t tm); + +void anm_set_pivot(struct anm_node *node, vec3_t pivot); +vec3_t anm_get_pivot(struct anm_node *node); + +void anm_get_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm); +void anm_get_inv_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm); + +/* those return the start and end times of the whole tree */ +anm_time_t anm_get_start_time(struct anm_node *node); +anm_time_t anm_get_end_time(struct anm_node *node); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBANIM_H_ */ diff -r 000000000000 -r c97151c60302 src/config.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/config.h Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,6 @@ +#ifndef ANIM_CONFIG_H_ +#define ANIM_CONFIG_H_ + +#undef ANIM_THREAD_SAFE + +#endif /* ANIM_CONFIG_H_ */ diff -r 000000000000 -r c97151c60302 src/dynarr.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dynarr.c Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,122 @@ +#include +#include +#include +#include "dynarr.h" + +/* The array descriptor keeps auxilliary information needed to manipulate + * the dynamic array. It's allocated adjacent to the array buffer. + */ +struct arrdesc { + int nelem, szelem; + int max_elem; + int bufsz; /* not including the descriptor */ +}; + +#define DESC(x) ((struct arrdesc*)((char*)(x) - sizeof(struct arrdesc))) + +void *dynarr_alloc(int elem, int szelem) +{ + struct arrdesc *desc; + + if(!(desc = malloc(elem * szelem + sizeof *desc))) { + return 0; + } + desc->nelem = desc->max_elem = elem; + desc->szelem = szelem; + desc->bufsz = elem * szelem; + return (char*)desc + sizeof *desc; +} + +void dynarr_free(void *da) +{ + if(da) { + free(DESC(da)); + } +} + +void *dynarr_resize(void *da, int elem) +{ + int newsz; + void *tmp; + struct arrdesc *desc; + + if(!da) return 0; + desc = DESC(da); + + newsz = desc->szelem * elem; + + if(!(tmp = realloc(desc, newsz + sizeof *desc))) { + return 0; + } + desc = tmp; + + desc->nelem = desc->max_elem = elem; + desc->bufsz = newsz; + return (char*)desc + sizeof *desc; +} + +int dynarr_empty(void *da) +{ + return DESC(da)->nelem ? 0 : 1; +} + +int dynarr_size(void *da) +{ + return DESC(da)->nelem; +} + + +/* stack semantics */ +void *dynarr_push(void *da, void *item) +{ + struct arrdesc *desc; + int nelem; + + desc = DESC(da); + nelem = desc->nelem; + + if(nelem >= desc->max_elem) { + /* need to resize */ + struct arrdesc *tmp; + int newsz = desc->max_elem ? desc->max_elem * 2 : 1; + + if(!(tmp = dynarr_resize(da, newsz))) { + fprintf(stderr, "failed to resize\n"); + return da; + } + da = tmp; + desc = DESC(da); + desc->nelem = nelem; + } + + memcpy((char*)da + desc->nelem++ * desc->szelem, item, desc->szelem); + return da; +} + +void *dynarr_pop(void *da) +{ + struct arrdesc *desc; + int nelem; + + desc = DESC(da); + nelem = desc->nelem; + + if(!nelem) return da; + + if(nelem <= desc->max_elem / 3) { + /* reclaim space */ + struct arrdesc *tmp; + int newsz = desc->max_elem / 2; + + if(!(tmp = dynarr_resize(da, newsz))) { + fprintf(stderr, "failed to resize\n"); + return da; + } + da = tmp; + desc = DESC(da); + desc->nelem = nelem; + } + desc->nelem--; + + return da; +} diff -r 000000000000 -r c97151c60302 src/dynarr.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dynarr.h Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,16 @@ +#ifndef DYNARR_H_ +#define DYNARR_H_ + +void *dynarr_alloc(int elem, int szelem); +void dynarr_free(void *da); +void *dynarr_resize(void *da, int elem); + +int dynarr_empty(void *da); +int dynarr_size(void *da); + +/* stack semantics */ +void *dynarr_push(void *da, void *item); +void *dynarr_pop(void *da); + + +#endif /* DYNARR_H_ */ diff -r 000000000000 -r c97151c60302 src/track.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/track.c Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,272 @@ +#include +#include +#include +#include "track.h" +#include "dynarr.h" + +static int keycmp(const void *a, const void *b); +static int find_prev_key(struct anm_keyframe *arr, int start, int end, anm_time_t tm); + +static float interp_step(float v0, float v1, float v2, float v3, float t); +static float interp_linear(float v0, float v1, float v2, float v3, float t); +static float interp_cubic(float v0, float v1, float v2, float v3, float t); + +static anm_time_t remap_extend(anm_time_t tm, anm_time_t start, anm_time_t end); +static anm_time_t remap_clamp(anm_time_t tm, anm_time_t start, anm_time_t end); +static anm_time_t remap_repeat(anm_time_t tm, anm_time_t start, anm_time_t end); + +/* XXX keep this in sync with enum anm_interpolator at track.h */ +static float (*interp[])(float, float, float, float, float) = { + interp_step, + interp_linear, + interp_cubic, + 0 +}; + +/* XXX keep this in sync with enum anm_extrapolator at track.h */ +static anm_time_t (*remap_time[])(anm_time_t, anm_time_t, anm_time_t) = { + remap_extend, + remap_clamp, + remap_repeat, + 0 +}; + +int anm_init_track(struct anm_track *track) +{ + memset(track, 0, sizeof *track); + + if(!(track->keys = dynarr_alloc(0, sizeof *track->keys))) { + return -1; + } + track->interp = ANM_INTERP_LINEAR; + track->extrap = ANM_EXTRAP_CLAMP; + return 0; +} + +void anm_destroy_track(struct anm_track *track) +{ + dynarr_free(track->keys); +} + +struct anm_track *anm_create_track(void) +{ + struct anm_track *track; + + if((track = malloc(sizeof *track))) { + if(anm_init_track(track) == -1) { + free(track); + return 0; + } + } + return track; +} + +void anm_free_track(struct anm_track *track) +{ + anm_destroy_track(track); + free(track); +} + +int anm_set_track_name(struct anm_track *track, const char *name) +{ + char *tmp; + + if(!(tmp = malloc(strlen(name) + 1))) { + return -1; + } + free(track->name); + track->name = tmp; + return 0; +} + +const char *anm_get_track_name(struct anm_track *track) +{ + return track->name; +} + +void anm_set_track_interpolator(struct anm_track *track, enum anm_interpolator in) +{ + track->interp = in; +} + +void anm_set_track_extrapolator(struct anm_track *track, enum anm_extrapolator ex) +{ + track->extrap = ex; +} + +anm_time_t anm_remap_time(struct anm_track *track, anm_time_t tm, anm_time_t start, anm_time_t end) +{ + return remap_time[track->extrap](tm, start, end); +} + +void anm_set_track_default(struct anm_track *track, float def) +{ + track->def_val = def; +} + +int anm_set_keyframe(struct anm_track *track, struct anm_keyframe *key) +{ + int idx = anm_get_key_interval(track, key->time); + + /* if we got a valid keyframe index, compare them... */ + if(idx >= 0 && idx < track->count && keycmp(key, track->keys + idx) == 0) { + /* ... it's the same key, just update the value */ + track->keys[idx].val = key->val; + } else { + /* ... it's a new key, add it and re-sort them */ + void *tmp; + if(!(tmp = dynarr_push(track->keys, key))) { + return -1; + } + track->keys = tmp; + /* TODO lazy qsort */ + qsort(track->keys, ++track->count, sizeof *track->keys, keycmp); + } + return 0; +} + +static int keycmp(const void *a, const void *b) +{ + return ((struct anm_keyframe*)a)->time - ((struct anm_keyframe*)b)->time; +} + +struct anm_keyframe *anm_get_keyframe(struct anm_track *track, int idx) +{ + if(idx < 0 || idx >= track->count) { + return 0; + } + return track->keys + idx; +} + +int anm_get_key_interval(struct anm_track *track, anm_time_t tm) +{ + int last; + + if(!track->count || tm < track->keys[0].time) { + return -1; + } + + last = track->count - 1; + if(tm > track->keys[last].time) { + return last; + } + + return find_prev_key(track->keys, 0, last, tm); +} + +static int find_prev_key(struct anm_keyframe *arr, int start, int end, anm_time_t tm) +{ + int mid; + + if(end - start <= 1) { + return start; + } + + mid = (start + end) / 2; + if(tm < arr[mid].time) { + return find_prev_key(arr, start, mid, tm); + } + if(tm > arr[mid].time) { + return find_prev_key(arr, mid, end, tm); + } + return mid; +} + +int anm_set_value(struct anm_track *track, anm_time_t tm, float val) +{ + struct anm_keyframe key; + key.time = tm; + key.val = val; + + return anm_set_keyframe(track, &key); +} + +float anm_get_value(struct anm_track *track, anm_time_t tm) +{ + int idx0, idx1, last_idx; + anm_time_t tstart, tend; + float t, dt; + float v0, v1, v2, v3; + + if(!track->count) { + return track->def_val; + } + + last_idx = track->count - 1; + + tstart = track->keys[0].time; + tend = track->keys[last_idx].time; + + if(tstart == tend) { + return track->keys[0].val; + } + + tm = remap_time[track->extrap](tm, tstart, tend); + + idx0 = anm_get_key_interval(track, tm); + assert(idx0 >= 0 && idx0 < track->count); + idx1 = idx0 + 1; + + if(idx0 == last_idx) { + return track->keys[idx0].val; + } + + dt = (float)(track->keys[idx1].time - track->keys[idx0].time); + t = (float)(tm - track->keys[idx0].time) / dt; + + v1 = track->keys[idx0].val; + v2 = track->keys[idx1].val; + + /* get the neigboring values to allow for cubic interpolation */ + v0 = idx0 > 0 ? track->keys[idx0 - 1].val : v1; + v3 = idx1 < last_idx ? track->keys[idx1 + 1].val : v2; + + return interp[track->interp](v0, v1, v2, v3, t); +} + + +static float interp_step(float v0, float v1, float v2, float v3, float t) +{ + return v1; +} + +static float interp_linear(float v0, float v1, float v2, float v3, float t) +{ + return v1 + (v2 - v1) * t; +} + +static float interp_cubic(float a, float b, float c, float d, float t) +{ + float x, y, z, w; + float tsq = t * t; + + x = -a + 3.0 * b - 3.0 * c + d; + y = 2.0 * a - 5.0 * b + 4.0 * c - d; + z = c - a; + w = 2.0 * b; + + return 0.5 * (x * tsq * t + y * tsq + z * t + w); +} + +static anm_time_t remap_extend(anm_time_t tm, anm_time_t start, anm_time_t end) +{ + return remap_repeat(tm, start, end); +} + +static anm_time_t remap_clamp(anm_time_t tm, anm_time_t start, anm_time_t end) +{ + return tm < start ? start : (tm >= end ? end - 1 : tm); +} + +static anm_time_t remap_repeat(anm_time_t tm, anm_time_t start, anm_time_t end) +{ + anm_time_t interv = end - start; + + if(tm < start) { + while(tm < start) { + tm += interv; + } + return tm; + } + return (tm - start) % interv + start; +} diff -r 000000000000 -r c97151c60302 src/track.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/track.h Sun Jan 08 05:13:13 2012 +0200 @@ -0,0 +1,95 @@ +/* An animation track defines the values of a single scalar over time + * and supports various interpolation and extrapolation modes. + */ +#ifndef LIBANIM_TRACK_H_ +#define LIBANIM_TRACK_H_ + +#include +#include "config.h" + +enum anm_interpolator { + ANM_INTERP_STEP, + ANM_INTERP_LINEAR, + ANM_INTERP_CUBIC +}; + +enum anm_extrapolator { + ANM_EXTRAP_EXTEND, /* extend to infinity */ + ANM_EXTRAP_CLAMP, /* clamp to last value */ + ANM_EXTRAP_REPEAT /* repeat motion */ +}; + +typedef long anm_time_t; +#define ANM_TIME_INVAL LONG_MIN + +#define ANM_SEC2TM(x) ((anm_time_t)((x) * 1000)) +#define ANM_MSEC2TM(x) ((anm_time_t)(x)) +#define ANM_TM2SEC(x) ((x) / 1000.0) +#define ANM_TM2MSEC(x) (x) + +struct anm_keyframe { + anm_time_t time; + float val; +}; + +struct anm_track { + char *name; + int count; + struct anm_keyframe *keys; + + float def_val; + + enum anm_interpolator interp; + enum anm_extrapolator extrap; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* track constructor and destructor */ +int anm_init_track(struct anm_track *track); +void anm_destroy_track(struct anm_track *track); + +/* helper functions that use anm_init_track and anm_destroy_track internally */ +struct anm_track *anm_create_track(void); +void anm_free_track(struct anm_track *track); + +int anm_set_track_name(struct anm_track *track, const char *name); +const char *anm_get_track_name(struct anm_track *track); + +void anm_set_track_interpolator(struct anm_track *track, enum anm_interpolator in); +void anm_set_track_extrapolator(struct anm_track *track, enum anm_extrapolator ex); + +anm_time_t anm_remap_time(struct anm_track *track, anm_time_t tm, anm_time_t start, anm_time_t end); + +void anm_set_track_default(struct anm_track *track, float def); + +/* set or update a keyframe */ +int anm_set_keyframe(struct anm_track *track, struct anm_keyframe *key); + +/* get the idx-th keyframe, returns null if it doesn't exist */ +struct anm_keyframe *anm_get_keyframe(struct anm_track *track, int idx); + +/* Finds the 0-based index of the intra-keyframe interval which corresponds + * to the specified time. If the time falls exactly onto the N-th keyframe + * the function returns N. + * + * Special cases: + * - if the time is before the first keyframe -1 is returned. + * - if the time is after the last keyframe, the index of the last keyframe + * is returned. + */ +int anm_get_key_interval(struct anm_track *track, anm_time_t tm); + +int anm_set_value(struct anm_track *track, anm_time_t tm, float val); + +/* evaluates and returns the value of the track for a particular time */ +float anm_get_value(struct anm_track *track, anm_time_t tm); + +#ifdef __cplusplus +} +#endif + + +#endif /* LIBANIM_TRACK_H_ */