added libanim, and moved gph-cmath into libs/cgmath
[dosdemo] / libs / anim / src / anim.c
diff --git a/libs/anim/src/anim.c b/libs/anim/src/anim.c
new file mode 100644 (file)
index 0000000..a2f1944
--- /dev/null
@@ -0,0 +1,1020 @@
+/*
+libanim - hierarchical keyframe animation library
+Copyright (C) 2012-2018 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+#include "anim.h"
+#include "dynarr.h"
+
+#include "cgmath/cgmath.h"
+
+#define ROT_USE_SLERP
+
+static void invalidate_cache(struct anm_node *node);
+
+int anm_init_animation(struct anm_animation *anim)
+{
+       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 */
+       };
+
+       anim->name = 0;
+
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               if(anm_init_track(anim->tracks + i) == -1) {
+                       for(j=0; j<i; j++) {
+                               anm_destroy_track(anim->tracks + i);
+                       }
+               }
+               anm_set_track_default(anim->tracks + i, defaults[i]);
+       }
+       return 0;
+}
+
+void anm_destroy_animation(struct anm_animation *anim)
+{
+       int i;
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               anm_destroy_track(anim->tracks + i);
+       }
+       free(anim->name);
+}
+
+void anm_set_animation_name(struct anm_animation *anim, const char *name)
+{
+       char *newname = malloc(strlen(name) + 1);
+       if(!newname) return;
+
+       strcpy(newname, name);
+
+       free(anim->name);
+       anim->name = newname;
+}
+
+/* ---- node implementation ----- */
+
+int anm_init_node(struct anm_node *node)
+{
+       memset(node, 0, sizeof *node);
+
+       node->cur_anim[1] = -1;
+
+       if(!(node->animations = dynarr_alloc(1, sizeof *node->animations))) {
+               return -1;
+       }
+       if(anm_init_animation(node->animations) == -1) {
+               dynarr_free(node->animations);
+               return -1;
+       }
+
+#ifdef ANIM_THREAD_SAFE
+       /* initialize thread-local matrix cache */
+       pthread_key_create(&node->cache_key, 0);
+       pthread_mutex_init(&node->cache_list_lock, 0);
+#endif
+
+       return 0;
+}
+
+void anm_destroy_node(struct anm_node *node)
+{
+       int i, num_anim;
+       free(node->name);
+
+       num_anim = anm_get_animation_count(node);
+       for(i=0; i<num_anim; i++) {
+               anm_destroy_animation(node->animations + i);
+       }
+       dynarr_free(node->animations);
+
+#ifdef ANIM_THREAD_SAFE
+       /* 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);
+       }
+#endif
+}
+
+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_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_pivot(struct anm_node *node, float x, float y, float z)
+{
+       node->pivot[0] = x;
+       node->pivot[1] = y;
+       node->pivot[2] = z;
+}
+
+void anm_get_pivot(struct anm_node *node, float *x, float *y, float *z)
+{
+       *x = node->pivot[0];
+       *y = node->pivot[1];
+       *z = node->pivot[2];
+}
+
+
+/* animation management */
+
+int anm_use_node_animation(struct anm_node *node, int aidx)
+{
+       if(aidx == node->cur_anim[0] && node->cur_anim[1] == -1) {
+               return 0;       /* no change, no invalidation */
+       }
+
+       if(aidx < 0 || aidx >= anm_get_animation_count(node)) {
+               return -1;
+       }
+
+       node->cur_anim[0] = aidx;
+       node->cur_anim[1] = -1;
+       node->cur_mix = 0;
+       node->blend_dur = -1;
+
+       invalidate_cache(node);
+       return 0;
+}
+
+int anm_use_node_animations(struct anm_node *node, int aidx, int bidx, float t)
+{
+       int num_anim;
+
+       if(node->cur_anim[0] == aidx && node->cur_anim[1] == bidx &&
+                       fabs(t - node->cur_mix) < 1e-6) {
+               return 0;       /* no change, no invalidation */
+       }
+
+       num_anim = anm_get_animation_count(node);
+       if(aidx < 0 || aidx >= num_anim) {
+               return anm_use_animation(node, bidx);
+       }
+       if(bidx < 0 || bidx >= num_anim) {
+               return anm_use_animation(node, aidx);
+       }
+       node->cur_anim[0] = aidx;
+       node->cur_anim[1] = bidx;
+       node->cur_mix = t;
+
+       invalidate_cache(node);
+       return 0;
+}
+
+int anm_use_animation(struct anm_node *node, int aidx)
+{
+       struct anm_node *child;
+
+       if(anm_use_node_animation(node, aidx) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_use_animation(child, aidx) == -1) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+}
+
+int anm_use_animations(struct anm_node *node, int aidx, int bidx, float t)
+{
+       struct anm_node *child;
+
+       if(anm_use_node_animations(node, aidx, bidx, t) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_use_animations(child, aidx, bidx, t) == -1) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+
+}
+
+void anm_set_node_animation_offset(struct anm_node *node, anm_time_t offs, int which)
+{
+       if(which < 0 || which >= 2) {
+               return;
+       }
+       node->cur_anim_offset[which] = offs;
+}
+
+anm_time_t anm_get_animation_offset(const struct anm_node *node, int which)
+{
+       if(which < 0 || which >= 2) {
+               return 0;
+       }
+       return node->cur_anim_offset[which];
+}
+
+void anm_set_animation_offset(struct anm_node *node, anm_time_t offs, int which)
+{
+       struct anm_node *c = node->child;
+       while(c) {
+               anm_set_animation_offset(c, offs, which);
+               c = c->next;
+       }
+
+       anm_set_node_animation_offset(node, offs, which);
+}
+
+int anm_get_active_animation_index(const struct anm_node *node, int which)
+{
+       if(which < 0 || which >= 2) return -1;
+       return node->cur_anim[which];
+}
+
+struct anm_animation *anm_get_active_animation(const struct anm_node *node, int which)
+{
+       int idx = anm_get_active_animation_index(node, which);
+       if(idx < 0 || idx >= anm_get_animation_count(node)) {
+               return 0;
+       }
+       return node->animations + idx;
+}
+
+float anm_get_active_animation_mix(const struct anm_node *node)
+{
+       return node->cur_mix;
+}
+
+int anm_get_animation_count(const struct anm_node *node)
+{
+       return dynarr_size(node->animations);
+}
+
+int anm_add_node_animation(struct anm_node *node)
+{
+       struct anm_animation newanim;
+       anm_init_animation(&newanim);
+
+       node->animations = dynarr_push(node->animations, &newanim);
+       return 0;
+}
+
+int anm_remove_node_animation(struct anm_node *node, int idx)
+{
+       fprintf(stderr, "anm_remove_animation: unimplemented!");
+       abort();
+       return 0;
+}
+
+int anm_add_animation(struct anm_node *node)
+{
+       struct anm_node *child;
+
+       if(anm_add_node_animation(node) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_add_animation(child)) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+}
+
+int anm_remove_animation(struct anm_node *node, int idx)
+{
+       struct anm_node *child;
+
+       if(anm_remove_node_animation(node, idx) == -1) {
+               return -1;
+       }
+
+       child = node->child;
+       while(child) {
+               if(anm_remove_animation(child, idx) == -1) {
+                       return -1;
+               }
+               child = child->next;
+       }
+       return 0;
+}
+
+struct anm_animation *anm_get_animation(struct anm_node *node, int idx)
+{
+       if(idx < 0 || idx > anm_get_animation_count(node)) {
+               return 0;
+       }
+       return node->animations + idx;
+}
+
+struct anm_animation *anm_get_animation_by_name(struct anm_node *node, const char *name)
+{
+       return anm_get_animation(node, anm_find_animation(node, name));
+}
+
+int anm_find_animation(struct anm_node *node, const char *name)
+{
+       int i, count = anm_get_animation_count(node);
+       for(i=0; i<count; i++) {
+               if(strcmp(node->animations[i].name, name) == 0) {
+                       return i;
+               }
+       }
+       return -1;
+}
+
+/* all the rest act on the current animation(s) */
+
+void anm_set_interpolator(struct anm_node *node, enum anm_interpolator in)
+{
+       int i;
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               anm_set_track_interpolator(anim->tracks + i, in);
+       }
+       invalidate_cache(node);
+}
+
+void anm_set_extrapolator(struct anm_node *node, enum anm_extrapolator ex)
+{
+       int i;
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       for(i=0; i<ANM_NUM_TRACKS; i++) {
+               anm_set_track_extrapolator(anim->tracks + i, ex);
+       }
+       invalidate_cache(node);
+}
+
+void anm_set_node_active_animation_name(struct anm_node *node, const char *name)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_animation_name(anim, name);
+}
+
+void anm_set_active_animation_name(struct anm_node *node, const char *name)
+{
+       struct anm_node *child;
+
+       anm_set_node_active_animation_name(node, name);
+
+       child = node->child;
+       while(child) {
+               anm_set_active_animation_name(child, name);
+               child = child->next;
+       }
+}
+
+const char *anm_get_active_animation_name(struct anm_node *node)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(anim) {
+               return anim->name;
+       }
+       return 0;
+}
+
+/* ---- high level animation blending ---- */
+void anm_transition(struct anm_node *node, int anmidx, anm_time_t start, anm_time_t dur)
+{
+       struct anm_node *c = node->child;
+
+       if(anmidx == node->cur_anim[0]) {
+               return;
+       }
+
+       while(c) {
+               anm_transition(c, anmidx, start, dur);
+               c = c->next;
+       }
+
+       anm_node_transition(node, anmidx, start, dur);
+}
+
+void anm_node_transition(struct anm_node *node, int anmidx, anm_time_t start, anm_time_t dur)
+{
+       if(anmidx == node->cur_anim[0]) {
+               return;
+       }
+
+       node->cur_anim[1] = anmidx;
+       node->cur_anim_offset[1] = start;
+       node->blend_dur = dur;
+}
+
+
+#define BLEND_START_TM node->cur_anim_offset[1]
+
+static anm_time_t animation_time(struct anm_node *node, anm_time_t tm, int which)
+{
+       float t;
+
+       if(node->blend_dur >= 0) {
+               /* we're in transition... */
+               t = (float)(tm - BLEND_START_TM) / (float)node->blend_dur;
+               if(t < 0.0) t = 0.0;
+
+               node->cur_mix = t;
+
+               if(t > 1.0) {
+                       /* switch completely over to the target animation and stop blending */
+                       anm_use_node_animation(node, node->cur_anim[1]);
+                       node->cur_anim_offset[0] = node->cur_anim_offset[1];
+               }
+       }
+
+       return tm - node->cur_anim_offset[which];
+}
+
+
+void anm_set_position(struct anm_node *node, const float *pos, anm_time_t tm)
+{
+       anm_set_position3f(node, pos[0], pos[1], pos[2], tm);
+}
+
+void anm_set_position3f(struct anm_node *node, float x, float y, float z, anm_time_t tm)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_value(anim->tracks + ANM_TRACK_POS_X, tm, x);
+       anm_set_value(anim->tracks + ANM_TRACK_POS_Y, tm, y);
+       anm_set_value(anim->tracks + ANM_TRACK_POS_Z, tm, z);
+       invalidate_cache(node);
+}
+
+void anm_get_node_position(struct anm_node *node, float *pos, anm_time_t tm)
+{
+       anm_time_t tm0 = animation_time(node, tm, 0);
+       struct anm_animation *anim0 = anm_get_active_animation(node, 0);
+       struct anm_animation *anim1 = anm_get_active_animation(node, 1);
+
+       if(!anim0) {
+               pos[0] = pos[1] = pos[2] = 0.0f;
+               return;
+       }
+
+       pos[0] = anm_get_value(anim0->tracks + ANM_TRACK_POS_X, tm0);
+       pos[1] = anm_get_value(anim0->tracks + ANM_TRACK_POS_Y, tm0);
+       pos[2] = anm_get_value(anim0->tracks + ANM_TRACK_POS_Z, tm0);
+
+       if(anim1) {
+               anm_time_t tm1 = animation_time(node, tm, 1);
+               float x1 = anm_get_value(anim1->tracks + ANM_TRACK_POS_X, tm1);
+               float y1 = anm_get_value(anim1->tracks + ANM_TRACK_POS_Y, tm1);
+               float z1 = anm_get_value(anim1->tracks + ANM_TRACK_POS_Z, tm1);
+
+               pos[0] = pos[0] + (x1 - pos[0]) * node->cur_mix;
+               pos[1] = pos[1] + (y1 - pos[1]) * node->cur_mix;
+               pos[2] = pos[2] + (z1 - pos[2]) * node->cur_mix;
+       }
+}
+
+void anm_set_rotation(struct anm_node *node, const float *qrot, anm_time_t tm)
+{
+       anm_set_rotation4f(node, qrot[0], qrot[1], qrot[2], qrot[3], tm);
+}
+
+void anm_set_rotation4f(struct anm_node *node, float x, float y, float z, float w, anm_time_t tm)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_X, tm, x);
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_Y, tm, y);
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_Z, tm, z);
+       anm_set_value(anim->tracks + ANM_TRACK_ROT_W, tm, w);
+       invalidate_cache(node);
+}
+
+void anm_set_rotation_axis(struct anm_node *node, float angle, float x, float y, float z, anm_time_t tm)
+{
+       cgm_quat q;
+       cgm_vec3 axis;
+
+       cgm_vcons(&axis, x, y, z);
+       cgm_qrotation(&q, &axis, angle);
+
+       anm_set_rotation(node, (float*)&q, tm);
+}
+
+static void get_node_rotation(cgm_quat *qres, struct anm_node *node, anm_time_t tm, struct anm_animation *anim)
+{
+#ifndef ROT_USE_SLERP
+       qres->x = anm_get_value(anim->tracks + ANM_TRACK_ROT_X, tm);
+       qres->y = anm_get_value(anim->tracks + ANM_TRACK_ROT_Y, tm);
+       qres->z = anm_get_value(anim->tracks + ANM_TRACK_ROT_Z, tm);
+       qres->w = anm_get_value(anim->tracks + ANM_TRACK_ROT_W, tm);
+#else
+       int idx0, idx1, last_idx;
+       anm_time_t tstart, tend;
+       float t, dt;
+       struct anm_track *track_x, *track_y, *track_z, *track_w;
+       cgm_quat q1, q2;
+
+       track_x = anim->tracks + ANM_TRACK_ROT_X;
+       track_y = anim->tracks + ANM_TRACK_ROT_Y;
+       track_z = anim->tracks + ANM_TRACK_ROT_Z;
+       track_w = anim->tracks + ANM_TRACK_ROT_W;
+
+       if(!track_x->count) {
+               qres->x = track_x->def_val;
+               qres->y = track_y->def_val;
+               qres->z = track_z->def_val;
+               qres->w = track_w->def_val;
+               return;
+       }
+
+       last_idx = track_x->count - 1;
+
+       tstart = track_x->keys[0].time;
+       tend = track_x->keys[last_idx].time;
+
+       if(tstart == tend) {
+               qres->x = track_x->keys[0].val;
+               qres->y = track_y->keys[0].val;
+               qres->z = track_z->keys[0].val;
+               qres->w = track_w->keys[0].val;
+               return;
+       }
+
+       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;
+
+       if(idx0 == last_idx) {
+               qres->x = track_x->keys[idx0].val;
+               qres->y = track_y->keys[idx0].val;
+               qres->z = track_z->keys[idx0].val;
+               qres->w = track_w->keys[idx0].val;
+               return;
+       }
+
+       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;
+
+       cgm_qslerp(qres, &q1, &q2, t);
+#endif
+}
+
+//get_node_rotation(cgm_quat *qres, struct anm_node *node, anm_time_t tm, struct anm_animation *anim)
+void anm_get_node_rotation(struct anm_node *node, float *qrot, anm_time_t tm)
+{
+       anm_time_t tm0 = animation_time(node, tm, 0);
+       struct anm_animation *anim0 = anm_get_active_animation(node, 0);
+       struct anm_animation *anim1 = anm_get_active_animation(node, 1);
+
+       if(!anim0) {
+               qrot[0] = qrot[1] = qrot[2] = 0.0f;
+               qrot[3] = 1.0f;
+               return;
+       }
+
+
+       if(anim1) {
+               cgm_quat q0, q1;
+               anm_time_t tm1 = animation_time(node, tm, 1);
+
+               get_node_rotation(&q0, node, tm0, anim0);
+               get_node_rotation(&q1, node, tm1, anim1);
+
+               cgm_qslerp((cgm_quat*)qrot, &q0, &q1, node->cur_mix);
+       } else {
+               get_node_rotation((cgm_quat*)qrot, node, tm0, anim0);
+       }
+}
+
+void anm_set_scaling(struct anm_node *node, const float *scale, anm_time_t tm)
+{
+       anm_set_scaling3f(node, scale[0], scale[1], scale[2], tm);
+}
+
+void anm_set_scaling3f(struct anm_node *node, float x, float y, float z, anm_time_t tm)
+{
+       struct anm_animation *anim = anm_get_active_animation(node, 0);
+       if(!anim) return;
+
+       anm_set_value(anim->tracks + ANM_TRACK_SCL_X, tm, x);
+       anm_set_value(anim->tracks + ANM_TRACK_SCL_Y, tm, y);
+       anm_set_value(anim->tracks + ANM_TRACK_SCL_Z, tm, z);
+       invalidate_cache(node);
+}
+
+void anm_get_node_scaling(struct anm_node *node, float *scale, anm_time_t tm)
+{
+       anm_time_t tm0 = animation_time(node, tm, 0);
+       struct anm_animation *anim0 = anm_get_active_animation(node, 0);
+       struct anm_animation *anim1 = anm_get_active_animation(node, 1);
+
+       if(!anim0) {
+               scale[0] = scale[1] = scale[2] = 1.0f;
+               return;
+       }
+
+       scale[0] = anm_get_value(anim0->tracks + ANM_TRACK_SCL_X, tm0);
+       scale[1] = anm_get_value(anim0->tracks + ANM_TRACK_SCL_Y, tm0);
+       scale[2] = anm_get_value(anim0->tracks + ANM_TRACK_SCL_Z, tm0);
+
+       if(anim1) {
+               anm_time_t tm1 = animation_time(node, tm, 1);
+               float x1 = anm_get_value(anim1->tracks + ANM_TRACK_SCL_X, tm1);
+               float y1 = anm_get_value(anim1->tracks + ANM_TRACK_SCL_Y, tm1);
+               float z1 = anm_get_value(anim1->tracks + ANM_TRACK_SCL_Z, tm1);
+
+               scale[0] = scale[0] + (x1 - scale[0]) * node->cur_mix;
+               scale[1] = scale[1] + (y1 - scale[1]) * node->cur_mix;
+               scale[2] = scale[2] + (z1 - scale[2]) * node->cur_mix;
+       }
+}
+
+void anm_get_position(struct anm_node *node, float *pos, anm_time_t tm)
+{
+       if(!node->parent) {
+               anm_get_node_position(node, pos, tm);
+       } else {
+               float xform[16];
+               anm_get_matrix(node, xform, tm);
+               cgm_mget_translation(xform, (cgm_vec3*)pos);
+       }
+}
+
+void anm_get_rotation(struct anm_node *node, float *qrot, anm_time_t tm)
+{
+       if(!node->parent) {
+               anm_get_node_rotation(node, qrot, tm);
+       } else {
+               cgm_quat rot;
+               anm_get_node_rotation(node, &rot.x, tm);
+               anm_get_rotation(node->parent, qrot, tm);
+               cgm_qmul((cgm_quat*)qrot, &rot);
+       }
+}
+
+void anm_get_scaling(struct anm_node *node, float *scale, anm_time_t tm)
+{
+       anm_get_node_scaling(node, scale, tm);
+       if(node->parent) {
+               cgm_vec3 ps;
+               anm_get_scaling(node->parent, &ps.x, tm);
+               cgm_vmul((cgm_vec3*)scale, &ps);
+       }
+}
+
+void anm_get_node_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+       int i;
+       float rmat[16];
+       cgm_vec3 pos, scale;
+       cgm_quat rot;
+
+       anm_get_node_position(node, &pos.x, tm);
+       anm_get_node_rotation(node, &rot.x, tm);
+       anm_get_node_scaling(node, &scale.x, tm);
+
+       cgm_mtranslation(mat, node->pivot[0], node->pivot[1], node->pivot[2]);
+       cgm_mrotation_quat(rmat, &rot);
+
+       /*
+       for(i=0; i<3; i++) {
+               mat[i * 4] = rmat[i * 4];
+               mat[i * 4 + 1] = rmat[i * 4 + 1];
+               mat[i * 4 + 2] = rmat[i * 4 + 2];
+       }
+       */
+       for(i=0; i<3; i++) {
+               mat[i] = rmat[i];
+               mat[4 + i] = rmat[4 + i];
+               mat[8 + i] = rmat[8 + i];
+       }
+
+       mat[0] *= scale.x; mat[4] *= scale.y; mat[8] *= scale.z; mat[12] += pos.x;
+       mat[1] *= scale.x; mat[5] *= scale.y; mat[9] *= scale.z; mat[13] += pos.y;
+       mat[2] *= scale.x; mat[6] *= scale.y; mat[10] *= scale.z; mat[14] += pos.z;
+
+       cgm_mpretranslate(mat, -node->pivot[0], -node->pivot[1], -node->pivot[2]);
+
+       /* that's basically: pivot * rotation * translation * scaling * -pivot */
+}
+
+void anm_get_node_inv_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+       anm_get_node_matrix(node, mat, tm);
+       cgm_minverse(mat);
+}
+
+void anm_eval_node(struct anm_node *node, anm_time_t tm)
+{
+       anm_get_node_matrix(node, node->matrix, tm);
+}
+
+void anm_eval(struct anm_node *node, anm_time_t tm)
+{
+       struct anm_node *c;
+
+       anm_eval_node(node, tm);
+
+       if(node->parent) {
+               /* due to post-order traversal, the parent matrix is already evaluated */
+               cgm_mmul(node->matrix, node->parent->matrix);
+       }
+
+       /* recersively evaluate all children */
+       c = node->child;
+       while(c) {
+               anm_eval(c, tm);
+               c = c->next;
+       }
+}
+
+float *anm_get_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+#ifdef ANIM_THREAD_SAFE
+       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;
+               cache->inv_time = ANM_TIME_INVAL;
+               pthread_setspecific(node->cache_key, cache);
+       }
+#else
+       struct mat_cache *cache = &node->cache;
+#endif
+
+       if(cache->time != tm) {
+               anm_get_node_matrix(node, cache->matrix, tm);
+
+               if(node->parent) {
+                       float parent_mat[16];
+
+                       anm_get_matrix(node->parent, parent_mat, tm);
+                       cgm_mmul(cache->matrix, parent_mat);
+               }
+               cache->time = tm;
+       }
+
+       if(mat) {
+               cgm_mcopy(mat, cache->matrix);
+       }
+       return cache->matrix;
+}
+
+float *anm_get_inv_matrix(struct anm_node *node, float *mat, anm_time_t tm)
+{
+#ifdef ANIM_THREAD_SAFE
+       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;
+               cache->inv_time = ANM_TIME_INVAL;
+               pthread_setspecific(node->cache_key, cache);
+       }
+#else
+       struct mat_cache *cache = &node->cache;
+#endif
+
+       if(cache->inv_time != tm) {
+               anm_get_matrix(node, cache->inv_matrix, tm);
+               cgm_minverse(cache->inv_matrix);
+               cache->inv_time = tm;
+       }
+
+       if(mat) {
+               cgm_mcopy(mat, cache->inv_matrix);
+       }
+       return cache->inv_matrix;
+}
+
+anm_time_t anm_get_start_time(struct anm_node *node)
+{
+       int i, j;
+       struct anm_node *c;
+       anm_time_t res = LONG_MAX;
+
+       for(j=0; j<2; j++) {
+               struct anm_animation *anim = anm_get_active_animation(node, j);
+               if(!anim) break;
+
+               for(i=0; i<ANM_NUM_TRACKS; i++) {
+                       if(anim->tracks[i].count) {
+                               anm_time_t tm = anim->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, j;
+       struct anm_node *c;
+       anm_time_t res = LONG_MIN;
+
+       for(j=0; j<2; j++) {
+               struct anm_animation *anim = anm_get_active_animation(node, j);
+               if(!anim) break;
+
+               for(i=0; i<ANM_NUM_TRACKS; i++) {
+                       if(anim->tracks[i].count) {
+                               anm_time_t tm = anim->tracks[i].keys[anim->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 anm_node *c;
+
+#ifdef ANIM_THREAD_SAFE
+       struct mat_cache *cache = pthread_getspecific(node->cache_key);
+       if(cache) {
+          cache->time = cache->inv_time = ANM_TIME_INVAL;
+       }
+#else
+       node->cache.time = node->cache.inv_time = ANM_TIME_INVAL;
+#endif
+
+       c = node->child;
+       while(c) {
+               invalidate_cache(c);
+               c = c->next;
+       }
+}