libanim

view src/anim.c @ 58:fe017bde08bc

implemented multiple animations per node, and blending between two animations
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 27 Dec 2013 06:28:43 +0200
parents 3c2428cb38f7
children 9758004136f8
line source
1 #include <stdlib.h>
2 #include <limits.h>
3 #include <assert.h>
4 #include "anim.h"
5 #include "dynarr.h"
7 #define ROT_USE_SLERP
9 static void invalidate_cache(struct anm_node *node);
11 int anm_init_animation(struct anm_animation *anim)
12 {
13 int i, j;
14 static const float defaults[] = {
15 0.0f, 0.0f, 0.0f, /* default position */
16 0.0f, 0.0f, 0.0f, 1.0f, /* default rotation quat */
17 1.0f, 1.0f, 1.0f /* default scale factor */
18 };
20 anim->name = 0;
22 for(i=0; i<ANM_NUM_TRACKS; i++) {
23 if(anm_init_track(anim->tracks + i) == -1) {
24 for(j=0; j<i; j++) {
25 anm_destroy_track(anim->tracks + i);
26 }
27 }
28 anm_set_track_default(anim->tracks + i, defaults[i]);
29 }
30 return 0;
31 }
33 void anm_destroy_animation(struct anm_animation *anim)
34 {
35 int i;
36 for(i=0; i<ANM_NUM_TRACKS; i++) {
37 anm_destroy_track(anim->tracks + i);
38 }
39 free(anim->name);
40 }
42 /* ---- node implementation ----- */
44 int anm_init_node(struct anm_node *node)
45 {
46 memset(node, 0, sizeof *node);
48 node->cur_anim[1] = -1;
49 node->cur_mix = 0;
51 if(!(node->animations = dynarr_alloc(1, sizeof *node->animations))) {
52 return -1;
53 }
54 if(anm_init_animation(node->animations) == -1) {
55 dynarr_free(node->animations);
56 return -1;
57 }
59 /* initialize thread-local matrix cache */
60 pthread_key_create(&node->cache_key, 0);
61 pthread_mutex_init(&node->cache_list_lock, 0);
63 return 0;
64 }
66 void anm_destroy_node(struct anm_node *node)
67 {
68 int i;
69 free(node->name);
71 for(i=0; i<ANM_NUM_TRACKS; i++) {
72 anm_destroy_animation(node->animations + i);
73 }
74 dynarr_free(node->animations);
76 /* destroy thread-specific cache */
77 pthread_key_delete(node->cache_key);
79 while(node->cache_list) {
80 struct mat_cache *tmp = node->cache_list;
81 node->cache_list = tmp->next;
82 free(tmp);
83 }
84 }
86 void anm_destroy_node_tree(struct anm_node *tree)
87 {
88 struct anm_node *c, *tmp;
90 if(!tree) return;
92 c = tree->child;
93 while(c) {
94 tmp = c;
95 c = c->next;
97 anm_destroy_node_tree(tmp);
98 }
99 anm_destroy_node(tree);
100 }
102 struct anm_node *anm_create_node(void)
103 {
104 struct anm_node *n;
106 if((n = malloc(sizeof *n))) {
107 if(anm_init_node(n) == -1) {
108 free(n);
109 return 0;
110 }
111 }
112 return n;
113 }
115 void anm_free_node(struct anm_node *node)
116 {
117 anm_destroy_node(node);
118 free(node);
119 }
121 void anm_free_node_tree(struct anm_node *tree)
122 {
123 struct anm_node *c, *tmp;
125 if(!tree) return;
127 c = tree->child;
128 while(c) {
129 tmp = c;
130 c = c->next;
132 anm_free_node_tree(tmp);
133 }
135 anm_free_node(tree);
136 }
138 int anm_set_node_name(struct anm_node *node, const char *name)
139 {
140 char *str;
142 if(!(str = malloc(strlen(name) + 1))) {
143 return -1;
144 }
145 strcpy(str, name);
146 free(node->name);
147 node->name = str;
148 return 0;
149 }
151 const char *anm_get_node_name(struct anm_node *node)
152 {
153 return node->name ? node->name : "";
154 }
156 void anm_link_node(struct anm_node *p, struct anm_node *c)
157 {
158 c->next = p->child;
159 p->child = c;
161 c->parent = p;
162 invalidate_cache(c);
163 }
165 int anm_unlink_node(struct anm_node *p, struct anm_node *c)
166 {
167 struct anm_node *iter;
169 if(p->child == c) {
170 p->child = c->next;
171 c->next = 0;
172 invalidate_cache(c);
173 return 0;
174 }
176 iter = p->child;
177 while(iter->next) {
178 if(iter->next == c) {
179 iter->next = c->next;
180 c->next = 0;
181 invalidate_cache(c);
182 return 0;
183 }
184 }
185 return -1;
186 }
188 void anm_set_pivot(struct anm_node *node, vec3_t piv)
189 {
190 node->pivot = piv;
191 }
193 vec3_t anm_get_pivot(struct anm_node *node)
194 {
195 return node->pivot;
196 }
199 /* animation management */
201 int anm_use_node_animation(struct anm_node *node, int aidx)
202 {
203 if(aidx == node->cur_anim[0] && node->cur_anim[1] == -1) {
204 return 0; /* no change, no invalidation */
205 }
207 if(aidx < 0 || aidx >= anm_get_animation_count(node)) {
208 return -1;
209 }
211 node->cur_anim[0] = aidx;
212 node->cur_anim[1] = -1;
213 node->cur_mix = 0;
215 invalidate_cache(node);
216 return 0;
217 }
219 int anm_use_node_animations(struct anm_node *node, int aidx, int bidx, float t)
220 {
221 int num_anim;
223 if(node->cur_anim[0] == aidx && node->cur_anim[1] == bidx &&
224 fabs(t - node->cur_mix) < 1e-6) {
225 return 0; /* no change, no invalidation */
226 }
228 num_anim = anm_get_animation_count(node);
229 if(aidx < 0 || aidx >= num_anim) {
230 return anm_use_animation(node, bidx);
231 }
232 if(bidx < 0 || bidx >= num_anim) {
233 return anm_use_animation(node, aidx);
234 }
235 node->cur_anim[0] = aidx;
236 node->cur_anim[1] = bidx;
237 node->cur_mix = t;
239 invalidate_cache(node);
240 return 0;
241 }
243 int anm_use_animation(struct anm_node *node, int aidx)
244 {
245 struct anm_node *child;
247 if(anm_use_node_animation(node, aidx) == -1) {
248 return -1;
249 }
251 child = node->child;
252 while(child) {
253 if(anm_use_animation(child, aidx) == -1) {
254 return -1;
255 }
256 child = child->next;
257 }
258 return 0;
259 }
261 int anm_use_animations(struct anm_node *node, int aidx, int bidx, float t)
262 {
263 struct anm_node *child;
265 if(anm_use_node_animations(node, aidx, bidx, t) == -1) {
266 return -1;
267 }
269 child = node->child;
270 while(child) {
271 if(anm_use_animations(child, aidx, bidx, t) == -1) {
272 return -1;
273 }
274 child = child->next;
275 }
276 return 0;
278 }
280 int anm_get_active_animation_index(struct anm_node *node, int which)
281 {
282 if(which < 0 || which >= 2) return -1;
283 return node->cur_anim[which];
284 }
286 struct anm_animation *anm_get_active_animation(struct anm_node *node, int which)
287 {
288 int idx = anm_get_active_animation_index(node, which);
289 if(idx < 0 || idx >= anm_get_animation_count(node)) {
290 return 0;
291 }
292 return node->animations + idx;
293 }
295 float anm_get_active_animation_mix(struct anm_node *node)
296 {
297 return node->cur_mix;
298 }
300 int anm_get_animation_count(struct anm_node *node)
301 {
302 return dynarr_size(node->animations);
303 }
305 int anm_add_node_animation(struct anm_node *node)
306 {
307 struct anm_animation newanim;
308 anm_init_animation(&newanim);
310 node->animations = dynarr_push(node->animations, &newanim);
311 return 0;
312 }
314 int anm_remove_node_animation(struct anm_node *node, int idx)
315 {
316 fprintf(stderr, "anm_remove_animation: unimplemented!");
317 abort();
318 return 0;
319 }
321 int anm_add_animation(struct anm_node *node)
322 {
323 struct anm_node *child;
325 if(anm_add_node_animation(node) == -1) {
326 return -1;
327 }
329 child = node->child;
330 while(child) {
331 if(anm_add_animation(child)) {
332 return -1;
333 }
334 child = child->next;
335 }
336 return 0;
337 }
339 int anm_remove_animation(struct anm_node *node, int idx)
340 {
341 struct anm_node *child;
343 if(anm_remove_node_animation(node, idx) == -1) {
344 return -1;
345 }
347 child = node->child;
348 while(child) {
349 if(anm_remove_animation(child, idx) == -1) {
350 return -1;
351 }
352 child = child->next;
353 }
354 return 0;
355 }
357 struct anm_animation *anm_get_animation(struct anm_node *node, int idx)
358 {
359 if(idx < 0 || idx > anm_get_animation_count(node)) {
360 return 0;
361 }
362 return node->animations + idx;
363 }
365 struct anm_animation *anm_get_animation_by_name(struct anm_node *node, const char *name)
366 {
367 return anm_get_animation(node, anm_find_animation(node, name));
368 }
370 int anm_find_animation(struct anm_node *node, const char *name)
371 {
372 int i, count = anm_get_animation_count(node);
373 for(i=0; i<count; i++) {
374 if(strcmp(node->animations[i].name, name) == 0) {
375 return i;
376 }
377 }
378 return -1;
379 }
381 /* all the rest act on the current animation(s) */
383 void anm_set_interpolator(struct anm_node *node, enum anm_interpolator in)
384 {
385 int i;
386 struct anm_animation *anim = anm_get_active_animation(node, 0);
387 if(!anim) return;
389 for(i=0; i<ANM_NUM_TRACKS; i++) {
390 anm_set_track_interpolator(anim->tracks + i, in);
391 }
392 invalidate_cache(node);
393 }
395 void anm_set_extrapolator(struct anm_node *node, enum anm_extrapolator ex)
396 {
397 int i;
398 struct anm_animation *anim = anm_get_active_animation(node, 0);
399 if(!anim) return;
401 for(i=0; i<ANM_NUM_TRACKS; i++) {
402 anm_set_track_extrapolator(anim->tracks + i, ex);
403 }
404 invalidate_cache(node);
405 }
407 void anm_set_position(struct anm_node *node, vec3_t pos, anm_time_t tm)
408 {
409 struct anm_animation *anim = anm_get_active_animation(node, 0);
410 if(!anim) return;
412 anm_set_value(anim->tracks + ANM_TRACK_POS_X, tm, pos.x);
413 anm_set_value(anim->tracks + ANM_TRACK_POS_Y, tm, pos.y);
414 anm_set_value(anim->tracks + ANM_TRACK_POS_Z, tm, pos.z);
415 invalidate_cache(node);
416 }
418 vec3_t anm_get_node_position(struct anm_node *node, anm_time_t tm)
419 {
420 vec3_t v;
421 struct anm_animation *anim0 = anm_get_active_animation(node, 0);
422 struct anm_animation *anim1 = anm_get_active_animation(node, 1);
424 if(!anim0) {
425 return v3_cons(0, 0, 0);
426 }
428 v.x = anm_get_value(anim0->tracks + ANM_TRACK_POS_X, tm);
429 v.y = anm_get_value(anim0->tracks + ANM_TRACK_POS_Y, tm);
430 v.z = anm_get_value(anim0->tracks + ANM_TRACK_POS_Z, tm);
432 if(anim1) {
433 vec3_t v1;
434 v1.x = anm_get_value(anim1->tracks + ANM_TRACK_POS_X, tm);
435 v1.y = anm_get_value(anim1->tracks + ANM_TRACK_POS_Y, tm);
436 v1.z = anm_get_value(anim1->tracks + ANM_TRACK_POS_Z, tm);
438 v.x = v.x + (v1.x - v.x) * node->cur_mix;
439 v.y = v.y + (v1.y - v.y) * node->cur_mix;
440 v.z = v.z + (v1.z - v.z) * node->cur_mix;
441 }
443 return v;
444 }
446 void anm_set_rotation(struct anm_node *node, quat_t rot, anm_time_t tm)
447 {
448 struct anm_animation *anim = anm_get_active_animation(node, 0);
449 if(!anim) return;
451 anm_set_value(anim->tracks + ANM_TRACK_ROT_X, tm, rot.x);
452 anm_set_value(anim->tracks + ANM_TRACK_ROT_Y, tm, rot.y);
453 anm_set_value(anim->tracks + ANM_TRACK_ROT_Z, tm, rot.z);
454 anm_set_value(anim->tracks + ANM_TRACK_ROT_W, tm, rot.w);
455 invalidate_cache(node);
456 }
458 static quat_t get_node_rotation(struct anm_node *node, anm_time_t tm, struct anm_animation *anim)
459 {
460 #ifndef ROT_USE_SLERP
461 quat_t q;
462 q.x = anm_get_value(anim->tracks + ANM_TRACK_ROT_X, tm);
463 q.y = anm_get_value(anim->tracks + ANM_TRACK_ROT_Y, tm);
464 q.z = anm_get_value(anim->tracks + ANM_TRACK_ROT_Z, tm);
465 q.w = anm_get_value(anim->tracks + ANM_TRACK_ROT_W, tm);
466 return q;
467 #else
468 int idx0, idx1, last_idx;
469 anm_time_t tstart, tend;
470 float t, dt;
471 struct anm_track *track_x, *track_y, *track_z, *track_w;
472 quat_t q, q1, q2;
474 track_x = anim->tracks + ANM_TRACK_ROT_X;
475 track_y = anim->tracks + ANM_TRACK_ROT_Y;
476 track_z = anim->tracks + ANM_TRACK_ROT_Z;
477 track_w = anim->tracks + ANM_TRACK_ROT_W;
479 if(!track_x->count) {
480 q.x = track_x->def_val;
481 q.y = track_y->def_val;
482 q.z = track_z->def_val;
483 q.w = track_w->def_val;
484 return q;
485 }
487 last_idx = track_x->count - 1;
489 tstart = track_x->keys[0].time;
490 tend = track_x->keys[last_idx].time;
492 if(tstart == tend) {
493 q.x = track_x->keys[0].val;
494 q.y = track_y->keys[0].val;
495 q.z = track_z->keys[0].val;
496 q.w = track_w->keys[0].val;
497 return q;
498 }
500 tm = anm_remap_time(track_x, tm, tstart, tend);
502 idx0 = anm_get_key_interval(track_x, tm);
503 assert(idx0 >= 0 && idx0 < track_x->count);
504 idx1 = idx0 + 1;
506 if(idx0 == last_idx) {
507 q.x = track_x->keys[idx0].val;
508 q.y = track_y->keys[idx0].val;
509 q.z = track_z->keys[idx0].val;
510 q.w = track_w->keys[idx0].val;
511 return q;
512 }
514 dt = (float)(track_x->keys[idx1].time - track_x->keys[idx0].time);
515 t = (float)(tm - track_x->keys[idx0].time) / dt;
517 q1.x = track_x->keys[idx0].val;
518 q1.y = track_y->keys[idx0].val;
519 q1.z = track_z->keys[idx0].val;
520 q1.w = track_w->keys[idx0].val;
522 q2.x = track_x->keys[idx1].val;
523 q2.y = track_y->keys[idx1].val;
524 q2.z = track_z->keys[idx1].val;
525 q2.w = track_w->keys[idx1].val;
527 /*q1 = quat_normalize(q1);
528 q2 = quat_normalize(q2);*/
530 return quat_slerp(q1, q2, t);
531 #endif
532 }
534 quat_t anm_get_node_rotation(struct anm_node *node, anm_time_t tm)
535 {
536 quat_t q;
537 struct anm_animation *anim0 = anm_get_active_animation(node, 0);
538 struct anm_animation *anim1 = anm_get_active_animation(node, 1);
540 if(!anim0) {
541 return quat_identity();
542 }
544 q = get_node_rotation(node, tm, anim0);
546 if(anim1) {
547 quat_t q1 = get_node_rotation(node, tm, anim1);
549 q = quat_slerp(q, q1, node->cur_mix);
550 }
551 return q;
552 }
554 void anm_set_scaling(struct anm_node *node, vec3_t scl, anm_time_t tm)
555 {
556 struct anm_animation *anim = anm_get_active_animation(node, 0);
557 if(!anim) return;
559 anm_set_value(anim->tracks + ANM_TRACK_SCL_X, tm, scl.x);
560 anm_set_value(anim->tracks + ANM_TRACK_SCL_Y, tm, scl.y);
561 anm_set_value(anim->tracks + ANM_TRACK_SCL_Z, tm, scl.z);
562 invalidate_cache(node);
563 }
565 vec3_t anm_get_node_scaling(struct anm_node *node, anm_time_t tm)
566 {
567 vec3_t v;
568 struct anm_animation *anim0 = anm_get_active_animation(node, 0);
569 struct anm_animation *anim1 = anm_get_active_animation(node, 1);
571 if(!anim0) {
572 return v3_cons(1, 1, 1);
573 }
575 v.x = anm_get_value(anim0->tracks + ANM_TRACK_SCL_X, tm);
576 v.y = anm_get_value(anim0->tracks + ANM_TRACK_SCL_Y, tm);
577 v.z = anm_get_value(anim0->tracks + ANM_TRACK_SCL_Z, tm);
579 if(anim1) {
580 vec3_t v1;
581 v1.x = anm_get_value(anim1->tracks + ANM_TRACK_SCL_X, tm);
582 v1.y = anm_get_value(anim1->tracks + ANM_TRACK_SCL_Y, tm);
583 v1.z = anm_get_value(anim1->tracks + ANM_TRACK_SCL_Z, tm);
585 v.x = v.x + (v1.x - v.x) * node->cur_mix;
586 v.y = v.y + (v1.y - v.y) * node->cur_mix;
587 v.z = v.z + (v1.z - v.z) * node->cur_mix;
588 }
590 return v;
591 }
594 vec3_t anm_get_position(struct anm_node *node, anm_time_t tm)
595 {
596 mat4_t xform;
597 vec3_t pos = {0.0, 0.0, 0.0};
599 if(!node->parent) {
600 return anm_get_node_position(node, tm);
601 }
603 anm_get_matrix(node, xform, tm);
604 return v3_transform(pos, xform);
605 }
607 quat_t anm_get_rotation(struct anm_node *node, anm_time_t tm)
608 {
609 quat_t rot, prot;
610 rot = anm_get_node_rotation(node, tm);
612 if(!node->parent) {
613 return rot;
614 }
616 prot = anm_get_rotation(node->parent, tm);
617 return quat_mul(prot, rot);
618 }
620 vec3_t anm_get_scaling(struct anm_node *node, anm_time_t tm)
621 {
622 vec3_t s, ps;
623 s = anm_get_node_scaling(node, tm);
625 if(!node->parent) {
626 return s;
627 }
629 ps = anm_get_scaling(node->parent, tm);
630 return v3_mul(s, ps);
631 }
633 void anm_get_node_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm)
634 {
635 int i;
636 mat4_t rmat;
637 vec3_t pos, scale;
638 quat_t rot;
640 pos = anm_get_node_position(node, tm);
641 rot = anm_get_node_rotation(node, tm);
642 scale = anm_get_node_scaling(node, tm);
644 m4_set_translation(mat, node->pivot.x, node->pivot.y, node->pivot.z);
646 quat_to_mat4(rmat, rot);
647 for(i=0; i<3; i++) {
648 mat[i][0] = rmat[i][0];
649 mat[i][1] = rmat[i][1];
650 mat[i][2] = rmat[i][2];
651 }
652 /* this loop is equivalent to: m4_mult(mat, mat, rmat); */
654 mat[0][0] *= scale.x; mat[0][1] *= scale.y; mat[0][2] *= scale.z; mat[0][3] += pos.x;
655 mat[1][0] *= scale.x; mat[1][1] *= scale.y; mat[1][2] *= scale.z; mat[1][3] += pos.y;
656 mat[2][0] *= scale.x; mat[2][1] *= scale.y; mat[2][2] *= scale.z; mat[2][3] += pos.z;
658 m4_translate(mat, -node->pivot.x, -node->pivot.y, -node->pivot.z);
660 /* that's basically: pivot * rotation * translation * scaling * -pivot */
661 }
663 void anm_get_node_inv_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm)
664 {
665 mat4_t tmp;
666 anm_get_node_matrix(node, tmp, tm);
667 m4_inverse(mat, tmp);
668 }
670 void anm_eval_node(struct anm_node *node, anm_time_t tm)
671 {
672 anm_get_node_matrix(node, node->matrix, tm);
673 }
675 void anm_eval(struct anm_node *node, anm_time_t tm)
676 {
677 struct anm_node *c;
679 anm_eval_node(node, tm);
681 if(node->parent) {
682 /* due to post-order traversal, the parent matrix is already evaluated */
683 m4_mult(node->matrix, node->parent->matrix, node->matrix);
684 }
686 /* recersively evaluate all children */
687 c = node->child;
688 while(c) {
689 anm_eval(c, tm);
690 c = c->next;
691 }
692 }
694 void anm_get_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm)
695 {
696 struct mat_cache *cache = pthread_getspecific(node->cache_key);
697 if(!cache) {
698 cache = malloc(sizeof *cache);
699 assert(cache);
701 pthread_mutex_lock(&node->cache_list_lock);
702 cache->next = node->cache_list;
703 node->cache_list = cache;
704 pthread_mutex_unlock(&node->cache_list_lock);
706 cache->time = ANM_TIME_INVAL;
707 cache->inv_time = ANM_TIME_INVAL;
708 pthread_setspecific(node->cache_key, cache);
709 }
711 if(cache->time != tm) {
712 anm_get_node_matrix(node, cache->matrix, tm);
714 if(node->parent) {
715 mat4_t parent_mat;
717 anm_get_matrix(node->parent, parent_mat, tm);
718 m4_mult(cache->matrix, parent_mat, cache->matrix);
719 }
720 cache->time = tm;
721 }
722 m4_copy(mat, cache->matrix);
723 }
725 void anm_get_inv_matrix(struct anm_node *node, mat4_t mat, anm_time_t tm)
726 {
727 struct mat_cache *cache = pthread_getspecific(node->cache_key);
728 if(!cache) {
729 cache = malloc(sizeof *cache);
730 assert(cache);
732 pthread_mutex_lock(&node->cache_list_lock);
733 cache->next = node->cache_list;
734 node->cache_list = cache;
735 pthread_mutex_unlock(&node->cache_list_lock);
737 cache->inv_time = ANM_TIME_INVAL;
738 cache->inv_time = ANM_TIME_INVAL;
739 pthread_setspecific(node->cache_key, cache);
740 }
742 if(cache->inv_time != tm) {
743 anm_get_matrix(node, mat, tm);
744 m4_inverse(cache->inv_matrix, mat);
745 cache->inv_time = tm;
746 }
747 m4_copy(mat, cache->inv_matrix);
748 }
750 anm_time_t anm_get_start_time(struct anm_node *node)
751 {
752 int i, j;
753 struct anm_node *c;
754 anm_time_t res = LONG_MAX;
756 for(j=0; j<2; j++) {
757 struct anm_animation *anim = anm_get_active_animation(node, j);
758 if(!anim) break;
760 for(i=0; i<ANM_NUM_TRACKS; i++) {
761 if(anim->tracks[i].count) {
762 anm_time_t tm = anim->tracks[i].keys[0].time;
763 if(tm < res) {
764 res = tm;
765 }
766 }
767 }
768 }
770 c = node->child;
771 while(c) {
772 anm_time_t tm = anm_get_start_time(c);
773 if(tm < res) {
774 res = tm;
775 }
776 c = c->next;
777 }
778 return res;
779 }
781 anm_time_t anm_get_end_time(struct anm_node *node)
782 {
783 int i, j;
784 struct anm_node *c;
785 anm_time_t res = LONG_MIN;
787 for(j=0; j<2; j++) {
788 struct anm_animation *anim = anm_get_active_animation(node, j);
789 if(!anim) break;
791 for(i=0; i<ANM_NUM_TRACKS; i++) {
792 if(anim->tracks[i].count) {
793 anm_time_t tm = anim->tracks[i].keys[anim->tracks[i].count - 1].time;
794 if(tm > res) {
795 res = tm;
796 }
797 }
798 }
799 }
801 c = node->child;
802 while(c) {
803 anm_time_t tm = anm_get_end_time(c);
804 if(tm > res) {
805 res = tm;
806 }
807 c = c->next;
808 }
809 return res;
810 }
812 static void invalidate_cache(struct anm_node *node)
813 {
814 struct mat_cache *cache = pthread_getspecific(node->cache_key);
815 if(cache) {
816 cache->time = cache->inv_time = ANM_TIME_INVAL;
817 }
818 }