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