cad023d35c490233cbe00d08727306d2262481bd
[deeprace] / libs / goat3d / src / read.c
1 /*
2 goat3d - 3D scene, and animation file format library.
3 Copyright (C) 2013-2023  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <assert.h>
19 #include "treestor.h"
20 #include "goat3d.h"
21 #include "g3dscn.h"
22 #include "log.h"
23 #include "dynarr.h"
24 #include "track.h"
25 #include "util.h"
26
27 #if defined(__WATCOMC__) || defined(_WIN32) || defined(__DJGPP__)
28 #include <malloc.h>
29 #else
30 #include <alloca.h>
31 #endif
32
33
34 struct key {
35         long tm;
36         cgm_vec4 val;
37 };
38
39 static struct goat3d_material *read_material(struct goat3d *g, struct ts_node *tsmtl);
40 static char *read_material_attrib(struct material_attrib *attr, struct ts_node *tsmattr);
41 static struct goat3d_mesh *read_mesh(struct goat3d *g, struct ts_node *tsmesh);
42 static void *read_veclist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tsnode);
43 static void *read_intlist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tsnode);
44 static void *read_bonelist(struct goat3d *g, struct goat3d_node **arr, struct ts_node *tsnode);
45 static int read_node(struct goat3d *g, struct goat3d_node *node, struct ts_node *tsnode);
46 static int read_anim(struct goat3d *g, struct ts_node *tsanim);
47 static struct goat3d_track *read_track(struct goat3d *g, struct ts_node *tstrk);
48
49
50 int g3dimpl_scnload(struct goat3d *g, struct goat3d_io *io)
51 {
52         int idx;
53         struct ts_io tsio;
54         struct ts_node *tsroot, *c;
55         const char *str;
56
57         tsio.data = io->cls;
58         tsio.read = io->read;
59         tsio.write = io->write;
60
61         if(!(tsroot = ts_load_io(&tsio))) {
62                 goat3d_logmsg(LOG_ERROR, "failed to load scene\n");
63                 return -1;
64         }
65         if(strcmp(tsroot->name, "scene") != 0) {
66                 goat3d_logmsg(LOG_ERROR, "invalid scene file, root node is not \"scene\"\n");
67                 ts_free_tree(tsroot);
68                 return -1;
69         }
70
71         /* read all materials */
72         c = tsroot->child_list;
73         while(c) {
74                 if(strcmp(c->name, "mtl") == 0) {
75                         struct goat3d_material *mtl = read_material(g, c);
76                         if(mtl) {
77                                 goat3d_add_mtl(g, mtl);
78                         }
79                 }
80                 c = c->next;
81         }
82
83         /* create placeholder nodes, only populating the name field, so that bone
84          * references in meshes can be resolved. We'll read the rest of the node
85          * info, including their mesh/light/camera references at the end.
86          */
87         c = tsroot->child_list;
88         while(c) {
89                 if(strcmp(c->name, "node") == 0) {
90                         struct goat3d_node *node;
91
92                         if(!(node = goat3d_create_node())) {
93                                 goat3d_logmsg(LOG_ERROR, "failed to allocate node\n");
94                                 c = c->next;
95                                 continue;
96                         }
97                         if((str = ts_get_attr_str(c, "name", 0))) {
98                                 goat3d_set_node_name(node, str);
99                         }
100                         goat3d_add_node(g, node);
101                 }
102                 c = c->next;
103         }
104
105         /* read all meshes, cameras, lights, animations */
106         c = tsroot->child_list;
107         while(c) {
108                 if(strcmp(c->name, "mesh") == 0) {
109                         struct goat3d_mesh *mesh = read_mesh(g, c);
110                         if(mesh) {
111                                 goat3d_add_mesh(g, mesh);
112                         }
113                 } else if(strcmp(c->name, "anim") == 0) {
114                         read_anim(g, c);
115                 }
116
117                 c = c->next;
118         }
119
120         /* now load the nodes properly */
121         idx = 0;
122         c = tsroot->child_list;
123         while(c) {
124                 if(strcmp(c->name, "node") == 0) {
125                         struct goat3d_node *node = goat3d_get_node(g, idx++);
126                         assert(node);
127                         read_node(g, node, c);
128                 }
129                 c = c->next;
130         }
131
132         ts_free_tree(tsroot);
133         return 0;
134 }
135
136 int g3dimpl_anmload(struct goat3d *g, struct goat3d_io *io)
137 {
138         /*
139         struct ts_io tsio;
140         tsio.data = io->cls;
141         tsio.read = io->read;
142         tsio.write = io->write;
143         */
144         return -1;
145 }
146
147 static struct goat3d_material *read_material(struct goat3d *g, struct ts_node *tsmtl)
148 {
149         struct goat3d_material *mtl;
150         struct material_attrib mattr, *arr;
151         struct ts_node *c;
152         const char *str;
153
154         if(!(mtl = malloc(sizeof *mtl)) || g3dimpl_mtl_init(mtl) == -1) {
155                 free(mtl);
156                 goat3d_logmsg(LOG_ERROR, "read_material: failed to allocate material\n");
157                 return 0;
158         }
159
160         if(!(str = ts_get_attr_str(tsmtl, "name", 0)) || !*str) {
161                 /* XXX wait, we can refer to materials by index, why is the name important? */
162                 goat3d_logmsg(LOG_WARNING, "read_material: ignoring material without a name\n");
163                 g3dimpl_mtl_destroy(mtl);
164                 return 0;
165         }
166         goat3d_set_mtl_name(mtl, str);
167
168         /* read all material attributes */
169         c = tsmtl->child_list;
170         while(c) {
171                 if(strcmp(c->name, "attr") == 0) {
172                         if(read_material_attrib(&mattr, c)) {
173                                 if(!(arr = dynarr_push(mtl->attrib, &mattr))) {
174                                         goat3d_logmsg(LOG_ERROR, "read_material: failed to resize material attribute array\n");
175                                         g3dimpl_mtl_destroy(mtl);
176                                         return 0;
177                                 }
178                                 mtl->attrib = arr;
179                         }
180                 }
181                 c = c->next;
182         }
183
184         /*if(dynarr_empty(mtl->attrib)) {
185                 goat3d_logmsg(LOG_WARNING, "read_material: ignoring empty material: %s\n", mtl->name);
186                 g3dimpl_mtl_destroy(mtl);
187                 return 0;
188         }*/
189         return mtl;
190 }
191
192 static char *read_material_attrib(struct material_attrib *attr, struct ts_node *tsnode)
193 {
194         int i;
195         struct ts_attr *tsattr;
196         const char *name, *map;
197
198         memset(attr, 0, sizeof *attr);
199
200         if((tsattr = ts_get_attr(tsnode, "val"))) {
201                 attr->value.w = 1.0f;   /* default W to 1 if we get less than a float4 */
202
203                 switch(tsattr->val.type) {
204                 case TS_NUMBER:
205                         attr->value.x = tsattr->val.fnum;
206                         break;
207                 case TS_VECTOR:
208                         assert(tsattr->val.vec_size <= 4);
209                         for(i=0; i<tsattr->val.vec_size; i++) {
210                                 (&attr->value.x)[i] = tsattr->val.vec[i];
211                         }
212                         break;
213                 default: /* no valid val attribute found */
214                         return 0;
215                 }
216         }
217
218         if(!(name = ts_get_attr_str(tsnode, "name", 0)) || !*name) {
219                 return 0;
220         }
221         if(!(attr->name = malloc(strlen(name) + 1))) {
222                 goat3d_logmsg(LOG_ERROR, "read_material_attrib: failed to allocate name\n");
223                 return 0;
224         }
225         strcpy(attr->name, name);
226
227         if((map = ts_get_attr_str(tsnode, "map", 0)) && *map) {
228                 if(!(attr->map = malloc(strlen(map) + 1))) {
229                         goat3d_logmsg(LOG_ERROR, "read_material_attrib: failed to allocate map name\n");
230                         free(attr->name);
231                         return 0;
232                 }
233                 strcpy(attr->map, map);
234         }
235         return attr->name;
236 }
237
238 static struct goat3d_mesh *read_mesh(struct goat3d *g, struct ts_node *tsmesh)
239 {
240         struct goat3d_mesh *mesh;
241         struct goat3d_material *mtl;
242         struct ts_node *c;
243         const char *str;
244         int num;
245         void *tmp;
246
247         if(!(mesh = malloc(sizeof *mesh)) || g3dimpl_obj_init((struct object*)mesh, OBJTYPE_MESH)) {
248                 goat3d_logmsg(LOG_ERROR, "read_mesh: failed to allocate mesh\n");
249                 goto fail;
250         }
251
252         if((str = ts_get_attr_str(tsmesh, "name", 0))) {
253                 goat3d_set_mesh_name(mesh, str);
254         }
255
256         /* material reference */
257         if((num = ts_get_attr_num(tsmesh, "material", -1)) >= 0) {
258                 if((mtl = goat3d_get_mtl(g, num))) {
259                         goat3d_set_mesh_mtl(mesh, mtl);
260                 } else {
261                         goat3d_logmsg(LOG_WARNING, "read_mesh: mesh %s refers to invalid material: %d\n",
262                                         mesh->name, num);
263                 }
264         } else if((str = ts_get_attr_str(tsmesh, "material", 0))) {
265                 if((mtl = goat3d_get_mtl_by_name(g, str))) {
266                         goat3d_set_mesh_mtl(mesh, mtl);
267                 } else {
268                         goat3d_logmsg(LOG_WARNING, "read_mesh: mesh %s refers to invalid material: %s\n",
269                                         mesh->name, str);
270                 }
271         }
272
273         /* external mesh data */
274         if((str = ts_get_attr_str(tsmesh, "file", 0))) {
275                 const char *fname = str;
276                 char *pathbuf;
277
278                 if(g->search_path) {
279                         pathbuf = alloca(strlen(str) + strlen(g->search_path) + 2);
280                         sprintf(pathbuf, "%s/%s", g->search_path, str);
281                         fname = pathbuf;
282                 }
283
284                 if(g3dimpl_loadmesh(mesh, fname) == -1) {
285                         goat3d_logmsg(LOG_ERROR, "read_mesh: failed to load external mesh: %s\n", fname);
286                         goto fail;
287                 }
288
289                 /* done loading, we can't have both an external mesh AND internal data,
290                  * if there's anything else hanging under this tsnode, ignore it.
291                  */
292                 if(tsmesh->child_list) {
293                         goat3d_logmsg(LOG_WARNING, "read_mesh: external mesh node also has unexpected children; ignoring.\n");
294                 }
295                 return mesh;
296         }
297
298         c = tsmesh->child_list;
299         while(c) {
300                 if(strcmp(c->name, "vertex_list") == 0) {
301                         if(!(tmp = read_veclist(mesh->vertices, 3, "vertex", "pos", c))) {
302                                 goat3d_logmsg(LOG_ERROR, "read_mesh: failed to read vertex array for mesh %s\n",
303                                                 mesh->name);
304                                 goto fail;
305                         }
306                         mesh->vertices = tmp;
307
308                 } else if(strcmp(c->name, "normal_list") == 0) {
309                         if(!(tmp = read_veclist(mesh->normals, 3, "normal", "dir", c))) {
310                                 goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read normals array for mesh %s\n",
311                                                 mesh->name);
312                         } else {
313                                 mesh->normals = tmp;
314                         }
315
316                 } else if(strcmp(c->name, "tangent_list") == 0) {
317                         if(!(tmp = read_veclist(mesh->tangents, 3, "tangent", "dir", c))) {
318                                 goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read tangents array for mesh %s\n",
319                                                 mesh->name);
320                         } else {
321                                 mesh->tangents = tmp;
322                         }
323
324                 } else if(strcmp(c->name, "texcoord_list") == 0) {
325                         if(!(tmp = read_veclist(mesh->texcoords, 2, "texcoord", "uv", c))) {
326                                 goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read texcoord array for mesh %s\n",
327                                                 mesh->name);
328                         } else {
329                                 mesh->texcoords = tmp;
330                         }
331
332                 } else if(strcmp(c->name, "skinweight_list") == 0) {
333                         if(!(tmp = read_veclist(mesh->skin_weights, 4, "skinweight", "weights", c))) {
334                                 goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read skin weights array for mesh %s\n",
335                                                 mesh->name);
336                         } else {
337                                 mesh->skin_weights = tmp;
338                         }
339
340                 } else if(strcmp(c->name, "skinmatrix_list") == 0) {
341                         if(!(tmp = read_intlist(mesh->skin_matrices, 4, "skinmatrix", "idx", c))) {
342                                 goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read skin matrix index array for mesh %s\n",
343                                                 mesh->name);
344                         } else {
345                                 mesh->skin_matrices = tmp;
346                         }
347
348                 } else if(strcmp(c->name, "color_list") == 0) {
349                         if(!(tmp = read_veclist(mesh->colors, 4, "color", "color", c))) {
350                                 goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read color array for mesh %s\n",
351                                                 mesh->name);
352                         } else {
353                                 mesh->colors = tmp;
354                         }
355
356                 } else if(strcmp(c->name, "bone_list") == 0) {
357                         if(!(tmp = read_bonelist(g, mesh->bones, c))) {
358                                 goat3d_logmsg(LOG_WARNING, "read_mesh: failed to read bones array for mesh %s\n",
359                                                 mesh->name);
360                         } else {
361                                 mesh->bones = tmp;
362                         }
363
364                 } else if(strcmp(c->name, "face_list") == 0) {
365                         if(!(tmp = read_intlist(mesh->faces, 3, "face", "idx", c))) {
366                                 goat3d_logmsg(LOG_ERROR, "read_mesh: failed to read faces array for mesh %s\n",
367                                                 mesh->name);
368                                 goto fail;
369                         }
370                         mesh->faces = tmp;
371                 }
372                 c = c->next;
373         }
374         return mesh;
375
376 fail:
377         g3dimpl_obj_destroy((struct object*)mesh);
378         return 0;
379 }
380
381 static void *read_veclist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tslist)
382 {
383         int i, size, bufsz;
384         struct ts_node *c;
385         struct ts_attr *attr;
386         float vec[4];
387         const char *str;
388
389         arr = dynarr_clear(arr);
390         assert(arr);
391
392         if((size = ts_get_attr_int(tslist, "list_size", -1)) <= 0) {
393                 goat3d_logmsg(LOG_WARNING, "read_veclist: list_size attribute missing or invalid\n");
394                 size = -1;
395         }
396
397         if((str = ts_get_attr_str(tslist, "base64", 0))) {
398                 if(size == -1) size = calc_b64_size(str);
399                 if(!(arr = dynarr_resize(arr, size))) {
400                         goat3d_logmsg(LOG_ERROR, "read_veclist: failed to resize %s array\n",
401                                         nodename);
402                         return 0;
403                 }
404
405                 bufsz = size * dim * sizeof(float);
406                 b64decode(str, arr, &bufsz);
407 #ifdef GOAT3D_BIGEND
408                 goat3d_bswap32(arr, size * dim);
409 #endif
410         }
411
412         c = tslist->child_list;
413         while(c) {
414                 if(strcmp(c->name, nodename) != 0) {
415                         c = c->next;
416                         continue;
417                 }
418
419                 if((attr = ts_get_attr(c, attrname)) && attr->val.type == TS_VECTOR) {
420                         for(i=0; i<dim; i++) {
421                                 if(i >= attr->val.vec_size) {
422                                         vec[i] = 0;
423                                 } else {
424                                         vec[i] = attr->val.vec[i];
425                                 }
426                         }
427
428                         if(!(arr = dynarr_push(arr, vec))) {
429                                 goat3d_logmsg(LOG_ERROR, "read_veclist: failed to resize %s array\n",
430                                                 nodename);
431                                 return 0;
432                         }
433                 }
434                 c = c->next;
435         }
436
437         if(size > 0 && dynarr_size(arr) != size) {
438                 goat3d_logmsg(LOG_WARNING, "read_veclist: expected %d items, read %d\n", size, dynarr_size(arr));
439         }
440         return arr;
441 }
442
443 static void *read_intlist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tslist)
444 {
445         int i, size, bufsz;
446         struct ts_node *c;
447         struct ts_attr *attr;
448         int ivec[4];
449         const char *str;
450
451         arr = dynarr_clear(arr);
452         assert(arr);
453
454         if((size = ts_get_attr_int(tslist, "list_size", -1)) <= 0) {
455                 goat3d_logmsg(LOG_WARNING, "read_intlist: list_size attribute missing or invalid\n");
456                 size = -1;
457         }
458
459         if((str = ts_get_attr_str(tslist, "base64", 0))) {
460                 if(size == -1) size = calc_b64_size(str);
461                 if(!(arr = dynarr_resize(arr, size))) {
462                         goat3d_logmsg(LOG_ERROR, "read_intlist: failed to resize %s array\n",
463                                         nodename);
464                         return 0;
465                 }
466
467                 bufsz = size * dim * sizeof(int);
468                 b64decode(str, arr, &bufsz);
469 #ifdef GOAT3D_BIGEND
470                 goat3d_bswap32(arr, size * dim);
471 #endif
472         }
473
474         c = tslist->child_list;
475         while(c) {
476                 if(strcmp(c->name, nodename) != 0) {
477                         c = c->next;
478                         continue;
479                 }
480
481                 if((attr = ts_get_attr(c, attrname)) && attr->val.type == TS_VECTOR) {
482                         for(i=0; i<dim; i++) {
483                                 if(i >= attr->val.vec_size) {
484                                         ivec[i] = 0;
485                                 } else {
486                                         ivec[i] = attr->val.vec[i];
487                                 }
488                         }
489
490                         if(!(arr = dynarr_push(arr, ivec))) {
491                                 goat3d_logmsg(LOG_ERROR, "read_intlist: failed to resize %s array\n",
492                                                 nodename);
493                                 return 0;
494                         }
495                 }
496                 c = c->next;
497         }
498
499         if(size > 0 && dynarr_size(arr) != size) {
500                 goat3d_logmsg(LOG_WARNING, "read_intlist: expected %d items, read %d\n", size, dynarr_size(arr));
501         }
502         return arr;
503 }
504
505 static void *read_bonelist(struct goat3d *g, struct goat3d_node **arr, struct ts_node *tslist)
506 {
507         int size, idx;
508         struct ts_node *c;
509         struct goat3d_node *bone;
510         const char *str;
511
512         arr = dynarr_clear(arr);
513         assert(arr);
514
515         if((size = ts_get_attr_int(tslist, "list_size", -1)) <= 0) {
516                 goat3d_logmsg(LOG_WARNING, "read_bonelist: list_size attribute missing or invalid\n");
517                 size = -1;
518         }
519
520         /* TODO base64 data support */
521
522         c = tslist->child_list;
523         while(c) {
524                 if(strcmp(c->name, "bone") != 0) {
525                         c = c->next;
526                         continue;
527                 }
528
529                 bone = 0;
530
531                 if((idx = ts_get_attr_int(c, "bone", -1)) >= 0) {
532                         if(!(bone = goat3d_get_node(g, idx))) {
533                                 goat3d_logmsg(LOG_ERROR, "read_bonelist: reference to invalid bone: %d\n", idx);
534                                 return 0;
535                         }
536
537                 } else if((str = ts_get_attr_str(c, "bone", 0))) {
538                         if(!(bone = goat3d_get_node_by_name(g, str))) {
539                                 goat3d_logmsg(LOG_ERROR, "read_bonelist: reference to invalid bone: %s\n", str);
540                                 return 0;
541                         }
542                 }
543
544                 if(bone && !(arr = dynarr_push(arr, &bone))) {
545                         goat3d_logmsg(LOG_ERROR, "read_bonelist: failed to resize bone array\n");
546                         return 0;
547                 }
548                 c = c->next;
549         }
550
551         if(size > 0 && dynarr_size(arr) != size) {
552                 goat3d_logmsg(LOG_WARNING, "read_bonelist: expected %d items, read %d\n", size, dynarr_size(arr));
553         }
554         return arr;
555 }
556
557 #define GETREF(ptr, typestr, getname) \
558         do { \
559                 ptr = 0; \
560                 if((idx = ts_get_attr_int(tsnode, typestr, -1)) >= 0) { \
561                         if(!(ptr = goat3d_get_##getname(g, idx))) { \
562                                 goat3d_logmsg(LOG_ERROR, "read_node: ignoring reference to invalid " typestr ": %d\n", idx); \
563                         } \
564                 } else if((str = ts_get_attr_str(tsnode, typestr, 0))) { \
565                         if(!(ptr = goat3d_get_##getname##_by_name(g, str))) { \
566                                 goat3d_logmsg(LOG_ERROR, "read_node: ignoring reference to invalid " typestr ": %s\n", str); \
567                         } \
568                 } \
569         } while(0)
570
571 static int read_node(struct goat3d *g, struct goat3d_node *node, struct ts_node *tsnode)
572 {
573         int idx;
574         const char *str;
575         struct goat3d_node *parent;
576         float *vec;
577
578         GETREF(parent, "parent", node);
579         if(parent) {
580                 goat3d_add_node_child(parent, node);
581         }
582
583         node->type = GOAT3D_NODE_MESH;
584         GETREF(node->obj, "mesh", mesh);
585         if(!node->obj) {
586                 node->type = GOAT3D_NODE_LIGHT;
587                 GETREF(node->obj, "light", light);
588         }
589         if(!node->obj) {
590                 node->type = GOAT3D_NODE_CAMERA;
591                 GETREF(node->obj, "camera", camera);
592         }
593         if(!node->obj) {
594                 node->type = GOAT3D_NODE_NULL;
595         }
596
597         if((vec = ts_get_attr_vec(tsnode, "pos", 0))) {
598                 goat3d_set_node_position(node, vec[0], vec[1], vec[2]);
599         }
600         if((vec = ts_get_attr_vec(tsnode, "rot", 0))) {
601                 goat3d_set_node_rotation(node, vec[0], vec[1], vec[2], vec[3]);
602         }
603         if((vec = ts_get_attr_vec(tsnode, "scale", 0))) {
604                 goat3d_set_node_scaling(node, vec[0], vec[1], vec[2]);
605         }
606         if((vec = ts_get_attr_vec(tsnode, "pivot", 0))) {
607                 goat3d_set_node_pivot(node, vec[0], vec[1], vec[2]);
608         }
609
610         return 0;
611 }
612
613 static int read_anim(struct goat3d *g, struct ts_node *tsanim)
614 {
615         struct ts_node *c;
616         const char *str;
617         struct goat3d_anim *anim;
618         struct goat3d_track *trk;
619
620         if(!(str = ts_get_attr_str(tsanim, "name", 0))) {
621                 goat3d_logmsg(LOG_WARNING, "read_anim: ignoring animation without a name\n");
622                 return -1;
623         }
624
625         if(!(anim = goat3d_create_anim())) {
626                 goat3d_logmsg(LOG_ERROR, "read_anim: failed to initialize animation: %s\n", str);
627                 return -1;
628         }
629         goat3d_set_anim_name(anim, str);
630
631         c = tsanim->child_list;
632         while(c) {
633                 if(strcmp(c->name, "track") == 0) {
634                         if(!(trk = read_track(g, c))) {
635                                 c = c->next;
636                                 continue;
637                         }
638                         goat3d_add_anim_track(anim, trk);
639                 }
640                 c = c->next;
641         }
642
643         goat3d_add_anim(g, anim);
644         return 0;
645 }
646
647 static int parsetype(const char *str)
648 {
649         if(strcmp(str, "pos") == 0) return GOAT3D_TRACK_POS;
650         if(strcmp(str, "rot") == 0) return GOAT3D_TRACK_ROT;
651         if(strcmp(str, "scale") == 0) return GOAT3D_TRACK_SCALE;
652         if(strcmp(str, "val") == 0) return GOAT3D_TRACK_VAL;
653         if(strcmp(str, "vec3") == 0) return GOAT3D_TRACK_VEC3;
654         if(strcmp(str, "vec4") == 0) return GOAT3D_TRACK_VEC4;
655         if(strcmp(str, "quat") == 0) return GOAT3D_TRACK_QUAT;
656         return -1;
657 }
658
659 static int parseinterp(const char *str)
660 {
661         if(strcmp(str, "step") == 0) return GOAT3D_INTERP_STEP;
662         if(strcmp(str, "linear") == 0) return GOAT3D_INTERP_LINEAR;
663         if(strcmp(str, "cubic") == 0) return GOAT3D_INTERP_CUBIC;
664         return -1;
665 }
666
667 static int parseextrap(const char *str)
668 {
669         if(strcmp(str, "extend") == 0) return GOAT3D_EXTRAP_EXTEND;
670         if(strcmp(str, "clamp") == 0) return GOAT3D_EXTRAP_CLAMP;
671         if(strcmp(str, "repeat") == 0) return GOAT3D_EXTRAP_REPEAT;
672         if(strcmp(str, "pingpong") == 0) return GOAT3D_EXTRAP_PINGPONG;
673         return -1;
674 }
675
676 static struct goat3d_track *read_track(struct goat3d *g, struct ts_node *tstrk)
677 {
678         int i, idx;
679         const char *str;
680         struct goat3d_track *trk;
681         struct goat3d_node *node;
682         struct goat3d_key key;
683         int type, in, ex;
684         struct ts_node *c;
685         struct ts_attr *tsattr;
686
687         if(!(str = ts_get_attr_str(tstrk, "type", 0)) || (type = parsetype(str)) == -1) {
688                 goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with missing or invalid type attribute\n");
689                 return 0;
690         }
691
692         if((idx = ts_get_attr_int(tstrk, "node", -1)) >= 0) {
693                 if(!(node = goat3d_get_node(g, idx))) {
694                         goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with invalid node reference (%d)\n", idx);
695                         return 0;
696                 }
697         } else if((str = ts_get_attr_str(tstrk, "node", 0))) {
698                 if(!(node = goat3d_get_node_by_name(g, str))) {
699                         goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with invalid node reference (%s)\n", str);
700                         return 0;
701                 }
702         } else {
703                 goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with missing node reference\n");
704                 return 0;
705         }
706
707         if(!(trk = goat3d_create_track())) {
708                 goat3d_logmsg(LOG_ERROR, "read_track: failed to create new keyframe track\n");
709                 return 0;
710         }
711         goat3d_set_track_node(trk, node);
712         goat3d_set_track_type(trk, type);
713
714         if((str = ts_get_attr_str(tstrk, "name", 0))) {
715                 goat3d_set_track_name(trk, str);
716         }
717
718         if((str = ts_get_attr_str(tstrk, "interp", 0))) {
719                 if((in = parseinterp(str)) == -1) {
720                         goat3d_logmsg(LOG_WARNING, "read_track: ignoring invalid interpolation mode: %s\n", str);
721                 } else {
722                         goat3d_set_track_interp(trk, in);
723                 }
724         }
725         if((str = ts_get_attr_str(tstrk, "extrap", 0))) {
726                 if((ex = parseextrap(str)) == -1) {
727                         goat3d_logmsg(LOG_WARNING, "read_track: ignoring invalid extrapolation mode: %s\n", str);
728                 } else {
729                         goat3d_set_track_extrap(trk, ex);
730                 }
731         }
732
733         c = tstrk->child_list;
734         while(c) {
735                 if(strcmp(c->name, "key") == 0) {
736                         if((key.tm = ts_get_attr_int(c, "time", INT_MIN)) == INT_MIN) {
737                                 goat3d_logmsg(LOG_WARNING, "read_track: ignoring keyframe with missing or invalid time (%s)\n",
738                                                 ts_get_attr_str(c, "time", ""));
739                                 c = c->next;
740                                 continue;
741                         }
742                         if(!(tsattr = ts_get_attr(c, "value")) || (tsattr->val.type != TS_NUMBER &&
743                                                 tsattr->val.type != TS_VECTOR)) {
744                                 goat3d_logmsg(LOG_WARNING, "read_track: ignoring keyframe with missing or invalid value (%s)\n",
745                                                 ts_get_attr_str(c, "value", ""));
746                                 c = c->next;
747                                 continue;
748                         }
749
750                         if(tsattr->val.type == TS_NUMBER) {
751                                 key.val[0] = tsattr->val.fnum;
752                         } else {
753                                 for(i=0; i<4; i++) {
754                                         if(i < tsattr->val.vec_size) {
755                                                 key.val[i] = tsattr->val.vec[i];
756                                         } else {
757                                                 key.val[i] = 0;
758                                         }
759                                 }
760                         }
761                         goat3d_set_track_key(trk, &key);
762                 }
763                 c = c->next;
764         }
765
766         /* force lazy re-sorting of keyframes if necessary */
767         goat3d_get_track_key(trk, 0, &key);
768
769         return trk;
770 }