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