X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=dosdemo;a=blobdiff_plain;f=libs%2Fanim%2Fsrc%2Fanim.c;fp=libs%2Fanim%2Fsrc%2Fanim.c;h=a2f19444f127f732c672f9b53c382e8f4bc44640;hp=0000000000000000000000000000000000000000;hb=3c8a42c780f4fb7817badbc136f7312aa20dd5ef;hpb=b6c89d9ffbb08c5286448de4773290f0924a420b diff --git a/libs/anim/src/anim.c b/libs/anim/src/anim.c new file mode 100644 index 0000000..a2f1944 --- /dev/null +++ b/libs/anim/src/anim.c @@ -0,0 +1,1020 @@ +/* +libanim - hierarchical keyframe animation library +Copyright (C) 2012-2018 John Tsiombikas + +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 . +*/ +#include +#include +#include +#include +#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; itracks + i) == -1) { + for(j=0; jtracks + 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; itracks + 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; ianimations + 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; ianimations[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; itracks + 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; itracks + 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; itracks[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; itracks[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; + } +}