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