added scr_lvled, a bunch of libraries, and improved framework code
[raydungeon] / libs / goat3d / src / write.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 <stdlib.h>
19 #include <string.h>
20 #include "g3dscn.h"
21 #include "log.h"
22 #include "dynarr.h"
23 #include "treestor.h"
24
25 /* type passed to namegen */
26 enum { MTL, MESH, LIGHT, CAMERA, NODE, ANIM, TRACK };
27
28 static struct ts_node *create_mtltree(struct goat3d *g, const struct goat3d_material *mtl);
29 static struct ts_node *create_meshtree(struct goat3d *g, const struct goat3d_mesh *mesh);
30 static struct ts_node *create_lighttree(struct goat3d *g, const struct goat3d_light *light);
31 static struct ts_node *create_camtree(struct goat3d *g, const struct goat3d_camera *cam);
32 static struct ts_node *create_nodetree(struct goat3d *g, const struct goat3d_node *node);
33 static struct ts_node *create_animtree(struct goat3d *g, const struct goat3d_anim *anim);
34 static struct ts_node *create_tracktree(struct goat3d *g, const struct goat3d_track *trk);
35
36 static void init_namegen(struct goat3d *g);
37 static const char *namegen(struct goat3d *g, const char *name, int type);
38
39 GOAT3DAPI char *goat3d_b64encode(const void *data, int size, char *buf, int *bufsz);
40 #define b64encode goat3d_b64encode
41
42 #define create_tsnode(n, p, nstr) \
43         do { \
44                 int len = strlen(nstr); \
45                 if(!((n) = ts_alloc_node())) { \
46                         goat3d_logmsg(LOG_ERROR, "failed to create treestore node\n"); \
47                         goto err; \
48                 } \
49                 if(!((n)->name = malloc(len + 1))) { \
50                         goat3d_logmsg(LOG_ERROR, "failed to allocate node name string\n"); \
51                         ts_free_node(n); \
52                         goto err; \
53                 } \
54                 memcpy((n)->name, (nstr), len + 1); \
55                 if(p) { \
56                         ts_add_child((p), (n)); \
57                 } \
58         } while(0)
59
60 #define create_tsattr(a, n, nstr, atype) \
61         do { \
62                 if(!((a) = ts_alloc_attr())) { \
63                         goat3d_logmsg(LOG_ERROR, "failed to create treestore attribute\n"); \
64                         goto err; \
65                 } \
66                 if(ts_set_attr_name(a, nstr) == -1) { \
67                         goat3d_logmsg(LOG_ERROR, "failed to allocate attrib name string\n"); \
68                         ts_free_attr(a); \
69                         goto err; \
70                 } \
71                 (a)->val.type = (atype); \
72                 if(n) { \
73                         ts_add_attr((n), (a)); \
74                 } \
75         } while(0)
76
77
78
79 int g3dimpl_scnsave(const struct goat3d *g, struct goat3d_io *io)
80 {
81         int i, num;
82         struct ts_io tsio;
83         struct ts_node *tsroot = 0, *tsn, *tsenv;
84         struct ts_attr *tsa;
85
86         tsio.data = io->cls;
87         tsio.read = io->read;
88         tsio.write = io->write;
89
90         init_namegen((struct goat3d*)g);
91
92         create_tsnode(tsroot, 0, "scene");
93
94         /* environment */
95         create_tsnode(tsenv, tsroot, "env");
96         create_tsattr(tsa, tsenv, "ambient", TS_VECTOR);
97         ts_set_valuefv(&tsa->val, 3, g->ambient.x, g->ambient.y, g->ambient.z);
98         /* TODO: fog */
99
100         num = dynarr_size(g->materials);
101         for(i=0; i<num; i++) {
102                 if(!(tsn = create_mtltree((struct goat3d*)g, g->materials[i]))) {
103                         goto err;
104                 }
105                 ts_add_child(tsroot, tsn);
106         }
107
108         num = dynarr_size(g->meshes);
109         for(i=0; i<num; i++) {
110                 if(!(tsn = create_meshtree((struct goat3d*)g, g->meshes[i]))) {
111                         goto err;
112                 }
113                 ts_add_child(tsroot, tsn);
114         }
115
116         num = dynarr_size(g->lights);
117         for(i=0; i<num; i++) {
118                 if(!(tsn = create_lighttree((struct goat3d*)g, g->lights[i]))) {
119                         goto err;
120                 }
121                 ts_add_child(tsroot, tsn);
122         }
123
124         num = dynarr_size(g->cameras);
125         for(i=0; i<num; i++) {
126                 if(!(tsn = create_camtree((struct goat3d*)g, g->cameras[i]))) {
127                         goto err;
128                 }
129                 ts_add_child(tsroot, tsn);
130         }
131
132         num = dynarr_size(g->nodes);
133         for(i=0; i<num; i++) {
134                 if(!(tsn = create_nodetree((struct goat3d*)g, g->nodes[i]))) {
135                         goto err;
136                 }
137                 ts_add_child(tsroot, tsn);
138         }
139
140         num = dynarr_size(g->anims);
141         for(i=0; i<num; i++) {
142                 if(!(tsn = create_animtree((struct goat3d*)g, g->anims[i]))) {
143                         goto err;
144                 }
145                 ts_add_child(tsroot, tsn);
146         }
147
148         if(ts_save_io(tsroot, &tsio) == -1) {
149                 goat3d_logmsg(LOG_ERROR, "g3dimpl_scnsave: failed\n");
150                 goto err;
151         }
152         return 0;
153
154 err:
155         ts_free_tree(tsroot);
156         return -1;
157 }
158
159 int g3dimpl_anmsave(const struct goat3d *g, struct goat3d_io *io)
160 {
161         return -1;
162 }
163
164 static struct ts_node *create_mtltree(struct goat3d *g, const struct goat3d_material *mtl)
165 {
166         int i, num_attr;
167         struct ts_node *tsn, *tsmtl = 0;
168         struct ts_attr *tsa;
169
170         create_tsnode(tsmtl, 0, "mtl");
171         create_tsattr(tsa, tsmtl, "name", TS_STRING);
172         if(ts_set_value_str(&tsa->val, namegen(g, mtl->name, MTL)) == -1) {
173                 goto err;
174         }
175
176         num_attr = dynarr_size(mtl->attrib);
177         for(i=0; i<num_attr; i++) {
178                 struct material_attrib *attr = mtl->attrib + i;
179
180                 create_tsnode(tsn, tsmtl, "attr");
181                 create_tsattr(tsa, tsn, "name", TS_STRING);
182                 if(ts_set_value_str(&tsa->val, attr->name) == -1) {
183                         goto err;
184                 }
185                 create_tsattr(tsa, tsn, "val", TS_VECTOR);
186                 ts_set_valuefv(&tsa->val, 4, attr->value.x, attr->value.y, attr->value.z, attr->value.w);
187                 if(attr->map) {
188                         create_tsattr(tsa, tsn, "map", TS_STRING);
189                         if(ts_set_value_str(&tsa->val, attr->map) == -1) {
190                                 goto err;
191                         }
192                 }
193         }
194         return tsmtl;
195
196 err:
197         ts_free_tree(tsmtl);
198         return 0;
199 }
200
201 static struct ts_node *create_meshtree(struct goat3d *g, const struct goat3d_mesh *mesh)
202 {
203         int i, num;
204         struct ts_node *tsmesh = 0, *tslist, *tsitem;
205         struct ts_attr *tsa;
206
207         create_tsnode(tsmesh, 0, "mesh");
208         create_tsattr(tsa, tsmesh, "name", TS_STRING);
209         if(ts_set_value_str(&tsa->val, namegen(g, mesh->name, MESH)) == -1) {
210                 goto err;
211         }
212
213         if(mesh->mtl) {
214                 if(mesh->mtl->name) {
215                         create_tsattr(tsa, tsmesh, "material", TS_STRING);
216                         if(ts_set_value_str(&tsa->val, mesh->mtl->name) == -1) {
217                                 goto err;
218                         }
219                 } else {
220                         create_tsattr(tsa, tsmesh, "material", TS_NUMBER);
221                         ts_set_valuei(&tsa->val, mesh->mtl->idx);
222                 }
223         }
224
225         /* TODO option of saving separate mesh files */
226
227         if((num = dynarr_size(mesh->vertices))) {
228                 create_tsnode(tslist, tsmesh, "vertex_list");
229                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
230                 ts_set_valuei(&tsa->val, num);
231
232                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
233                         create_tsattr(tsa, tslist, "base64", TS_STRING);
234                         if(!(tsa->val.str = b64encode(mesh->vertices, num * 3 * sizeof(float), 0, 0))) {
235                                 goto err;
236                         }
237                 } else {
238                         for(i=0; i<num; i++) {
239                                 cgm_vec3 *vptr = mesh->vertices + i;
240                                 create_tsnode(tsitem, tslist, "vertex");
241                                 create_tsattr(tsa, tsitem, "pos", TS_VECTOR);
242                                 ts_set_valuefv(&tsa->val, 3, vptr->x, vptr->y, vptr->z);
243                         }
244                 }
245         }
246
247         if((num = dynarr_size(mesh->normals))) {
248                 create_tsnode(tslist, tsmesh, "normal_list");
249                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
250                 ts_set_valuei(&tsa->val, num);
251
252                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
253                         create_tsattr(tsa, tslist, "base64", TS_STRING);
254                         if(!(tsa->val.str = b64encode(mesh->normals, num * 3 * sizeof(float), 0, 0))) {
255                                 goto err;
256                         }
257                 } else {
258                         for(i=0; i<num; i++) {
259                                 cgm_vec3 *nptr = mesh->normals + i;
260                                 create_tsnode(tsitem, tslist, "normal");
261                                 create_tsattr(tsa, tsitem, "dir", TS_VECTOR);
262                                 ts_set_valuefv(&tsa->val, 3, nptr->x, nptr->y, nptr->z);
263                         }
264                 }
265         }
266
267         if((num = dynarr_size(mesh->tangents))) {
268                 create_tsnode(tslist, tsmesh, "tangent_list");
269                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
270                 ts_set_valuei(&tsa->val, num);
271
272                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
273                         create_tsattr(tsa, tslist, "base64", TS_STRING);
274                         if(!(tsa->val.str = b64encode(mesh->tangents, num * 3 * sizeof(float), 0, 0))) {
275                                 goto err;
276                         }
277                 } else {
278                         for(i=0; i<num; i++) {
279                                 cgm_vec3 *tptr = mesh->tangents + i;
280                                 create_tsnode(tsitem, tslist, "tangent");
281                                 create_tsattr(tsa, tsitem, "dir", TS_VECTOR);
282                                 ts_set_valuefv(&tsa->val, 3, tptr->x, tptr->y, tptr->z);
283                         }
284                 }
285         }
286
287         if((num = dynarr_size(mesh->texcoords))) {
288                 create_tsnode(tslist, tsmesh, "texcoord_list");
289                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
290                 ts_set_valuei(&tsa->val, num);
291
292                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
293                         create_tsattr(tsa, tslist, "base64", TS_STRING);
294                         if(!(tsa->val.str = b64encode(mesh->texcoords, num * 3 * sizeof(float), 0, 0))) {
295                                 goto err;
296                         }
297                 } else {
298                         for(i=0; i<num; i++) {
299                                 cgm_vec2 *uvptr = mesh->texcoords + i;
300                                 create_tsnode(tsitem, tslist, "texcoord");
301                                 create_tsattr(tsa, tsitem, "uv", TS_VECTOR);
302                                 ts_set_valuefv(&tsa->val, 3, uvptr->x, uvptr->y, 0.0f);
303                         }
304                 }
305         }
306
307         if((num = dynarr_size(mesh->skin_weights))) {
308                 create_tsnode(tslist, tsmesh, "skinweight_list");
309                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
310                 ts_set_valuei(&tsa->val, num);
311
312                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
313                         create_tsattr(tsa, tslist, "base64", TS_STRING);
314                         if(!(tsa->val.str = b64encode(mesh->skin_weights, num * 4 * sizeof(float), 0, 0))) {
315                                 goto err;
316                         }
317                 } else {
318                         for(i=0; i<num; i++) {
319                                 cgm_vec4 *wptr = mesh->skin_weights + i;
320                                 create_tsnode(tsitem, tslist, "skinweight");
321                                 create_tsattr(tsa, tsitem, "weights", TS_VECTOR);
322                                 ts_set_valuefv(&tsa->val, 4, wptr->x, wptr->y, wptr->z, wptr->w);
323                         }
324                 }
325         }
326
327         if((num = dynarr_size(mesh->skin_matrices))) {
328                 create_tsnode(tslist, tsmesh, "skinmatrix_list");
329                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
330                 ts_set_valuei(&tsa->val, num);
331
332                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
333                         create_tsattr(tsa, tslist, "base64", TS_STRING);
334                         if(!(tsa->val.str = b64encode(mesh->skin_matrices, num * 4 * sizeof(int), 0, 0))) {
335                                 goto err;
336                         }
337                 } else {
338                         for(i=0; i<num; i++) {
339                                 int4 *iptr = mesh->skin_matrices + i;
340                                 create_tsnode(tsitem, tslist, "skinmatrix");
341                                 create_tsattr(tsa, tsitem, "idx", TS_VECTOR);
342                                 ts_set_valueiv(&tsa->val, 4, iptr->x, iptr->y, iptr->z, iptr->w);
343                         }
344                 }
345         }
346
347         if((num = dynarr_size(mesh->colors))) {
348                 create_tsnode(tslist, tsmesh, "color_list");
349                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
350                 ts_set_valuei(&tsa->val, num);
351
352                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
353                         create_tsattr(tsa, tslist, "base64", TS_STRING);
354                         if(!(tsa->val.str = b64encode(mesh->colors, num * 4 * sizeof(float), 0, 0))) {
355                                 goto err;
356                         }
357                 } else {
358                         for(i=0; i<num; i++) {
359                                 cgm_vec4 *cptr = mesh->colors + i;
360                                 create_tsnode(tsitem, tslist, "color");
361                                 create_tsattr(tsa, tsitem, "color", TS_VECTOR);
362                                 ts_set_valuefv(&tsa->val, 4, cptr->x, cptr->y, cptr->z, cptr->w);
363                         }
364                 }
365         }
366
367         if((num = dynarr_size(mesh->bones))) {
368                 create_tsnode(tslist, tsmesh, "bone_list");
369                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
370                 ts_set_valuei(&tsa->val, num);
371
372                 /* TODO: base64 option */
373                 for(i=0; i<num; i++) {
374                         create_tsnode(tsitem, tslist, "bone");
375                         create_tsattr(tsa, tsitem, "name", TS_STRING);
376                         if(ts_set_value_str(&tsa->val, mesh->bones[i]->name) == -1) {
377                                 goto err;
378                         }
379                 }
380         }
381
382         if((num = dynarr_size(mesh->faces))) {
383                 create_tsnode(tslist, tsmesh, "face_list");
384                 create_tsattr(tsa, tslist, "list_size", TS_NUMBER);
385                 ts_set_valuei(&tsa->val, num);
386
387                 if(goat3d_getopt(g, GOAT3D_OPT_SAVEBINDATA)) {
388                         create_tsattr(tsa, tslist, "base64", TS_STRING);
389                         if(!(tsa->val.str = b64encode(mesh->faces, num * 3 * sizeof(int), 0, 0))) {
390                                 goto err;
391                         }
392                 } else {
393                         for(i=0; i<num; i++) {
394                                 struct face *fptr = mesh->faces + i;
395                                 create_tsnode(tsitem, tslist, "face");
396                                 create_tsattr(tsa, tsitem, "idx", TS_VECTOR);
397                                 ts_set_valueiv(&tsa->val, 3, fptr->v[0], fptr->v[1], fptr->v[2]);
398                         }
399                 }
400         }
401
402         return tsmesh;
403
404 err:
405         ts_free_tree(tsmesh);
406         return 0;
407 }
408
409 static struct ts_node *create_lighttree(struct goat3d *g, const struct goat3d_light *light)
410 {
411         struct ts_node *tslight = 0;
412         struct ts_attr *tsa;
413
414         create_tsnode(tslight, 0, "light");
415         create_tsattr(tsa, tslight, "name", TS_STRING);
416         if(ts_set_value_str(&tsa->val, namegen(g, light->name, LIGHT)) == -1) {
417                 goto err;
418         }
419
420         if(light->ltype != LTYPE_DIR) {
421                 create_tsattr(tsa, tslight, "pos", TS_VECTOR);
422                 ts_set_valuefv(&tsa->val, 3, light->pos.x, light->pos.y, light->pos.z);
423         }
424
425         if(light->ltype != LTYPE_POINT) {
426                 create_tsattr(tsa, tslight, "dir", TS_VECTOR);
427                 ts_set_valuefv(&tsa->val, 3, light->dir.x, light->dir.y, light->dir.z);
428         }
429
430         if(light->ltype == LTYPE_SPOT) {
431                 create_tsattr(tsa, tslight, "cone_inner", TS_NUMBER);
432                 ts_set_valuef(&tsa->val, light->inner_cone);
433                 create_tsattr(tsa, tslight, "cone_outer", TS_NUMBER);
434                 ts_set_valuef(&tsa->val, light->outer_cone);
435         }
436
437         create_tsattr(tsa, tslight, "color", TS_VECTOR);
438         ts_set_valuefv(&tsa->val, 3, light->color.x, light->color.y, light->color.z);
439
440         create_tsattr(tsa, tslight, "atten", TS_VECTOR);
441         ts_set_valuefv(&tsa->val, 3, light->attenuation.x, light->attenuation.y, light->attenuation.z);
442
443         create_tsattr(tsa, tslight, "distance", TS_NUMBER);
444         ts_set_valuef(&tsa->val, light->max_dist);
445
446         return tslight;
447
448 err:
449         ts_free_tree(tslight);
450         return 0;
451 }
452
453 static struct ts_node *create_camtree(struct goat3d *g, const struct goat3d_camera *cam)
454 {
455         struct ts_node *tscam = 0;
456         struct ts_attr *tsa;
457
458         create_tsnode(tscam, 0, "camera");
459         create_tsattr(tsa, tscam, "name", TS_STRING);
460         if(ts_set_value_str(&tsa->val, namegen(g, cam->name, CAMERA)) == -1) {
461                 goto err;
462         }
463
464         create_tsattr(tsa, tscam, "pos", TS_VECTOR);
465         ts_set_valuefv(&tsa->val, 3, cam->pos.x, cam->pos.y, cam->pos.z);
466
467         if(cam->camtype == CAMTYPE_TARGET) {
468                 create_tsattr(tsa, tscam, "target", TS_VECTOR);
469                 ts_set_valuefv(&tsa->val, 3, cam->target.x, cam->target.y, cam->target.z);
470         }
471
472         create_tsattr(tsa, tscam, "fov", TS_NUMBER);
473         ts_set_valuef(&tsa->val, cam->fov);
474
475         create_tsattr(tsa, tscam, "nearclip", TS_NUMBER);
476         ts_set_valuef(&tsa->val, cam->near_clip);
477
478         create_tsattr(tsa, tscam, "farclip", TS_NUMBER);
479         ts_set_valuef(&tsa->val, cam->far_clip);
480
481         return tscam;
482
483 err:
484         ts_free_tree(tscam);
485         return 0;
486 }
487
488 static struct ts_node *create_nodetree(struct goat3d *g, const struct goat3d_node *node)
489 {
490         struct ts_node *tsnode = 0;
491         struct ts_attr *tsa;
492         struct goat3d_node *par;
493         static const char *objtypestr[] = {"null", "mesh", "light", "camera"};
494         float vec[4];
495         float xform[16];
496
497         create_tsnode(tsnode, 0, "node");
498         create_tsattr(tsa, tsnode, "name", TS_STRING);
499         if(ts_set_value_str(&tsa->val, namegen(g, node->name, NODE)) == -1) {
500                 goto err;
501         }
502
503         if((par = goat3d_get_node_parent(node))) {
504                 create_tsattr(tsa, tsnode, "parent", TS_STRING);
505                 if(ts_set_value_str(&tsa->val, goat3d_get_node_name(par)) == -1) {
506                         goto err;
507                 }
508         }
509
510         if(node->obj && node->type != GOAT3D_NODE_NULL) {
511                 create_tsattr(tsa, tsnode, objtypestr[node->type], TS_STRING);
512                 if(ts_set_value_str(&tsa->val, ((struct object*)node->obj)->name) == -1) {
513                         goto err;
514                 }
515         }
516
517         goat3d_get_node_position(node, vec, vec + 1, vec + 2);
518         create_tsattr(tsa, tsnode, "pos", TS_VECTOR);
519         ts_set_valuef_arr(&tsa->val, 3, vec);
520
521         goat3d_get_node_rotation(node, vec, vec + 1, vec + 2, vec + 3);
522         create_tsattr(tsa, tsnode, "rot", TS_VECTOR);
523         ts_set_valuef_arr(&tsa->val, 4, vec);
524
525         goat3d_get_node_scaling(node, vec, vec + 1, vec + 2);
526         create_tsattr(tsa, tsnode, "scale", TS_VECTOR);
527         ts_set_valuef_arr(&tsa->val, 3, vec);
528
529         goat3d_get_node_pivot(node, vec, vec + 1, vec + 2);
530         create_tsattr(tsa, tsnode, "pivot", TS_VECTOR);
531         ts_set_valuef_arr(&tsa->val, 3, vec);
532
533         goat3d_get_node_matrix(node, xform);
534         cgm_mtranspose(xform);
535         create_tsattr(tsa, tsnode, "matrix0", TS_VECTOR);
536         ts_set_valuef_arr(&tsa->val, 4, xform);
537         create_tsattr(tsa, tsnode, "matrix1", TS_VECTOR);
538         ts_set_valuef_arr(&tsa->val, 4, xform + 4);
539         create_tsattr(tsa, tsnode, "matrix2", TS_VECTOR);
540         ts_set_valuef_arr(&tsa->val, 4, xform + 8);
541
542         return tsnode;
543
544 err:
545         ts_free_tree(tsnode);
546         return 0;
547 }
548
549 static struct ts_node *create_animtree(struct goat3d *g, const struct goat3d_anim *anim)
550 {
551         int i, num_trk;
552         struct ts_node *tsanim, *tstrk;
553         struct ts_attr *tsa;
554
555         create_tsnode(tsanim, 0, "anim");
556         create_tsattr(tsa, tsanim, "name", TS_STRING);
557         if(ts_set_value_str(&tsa->val, namegen(g, anim->name, ANIM)) == -1) {
558                 goto err;
559         }
560
561
562         num_trk = goat3d_get_anim_track_count(anim);
563         for(i=0; i<num_trk; i++) {
564                 if((tstrk = create_tracktree(g, goat3d_get_anim_track(anim, i)))) {
565                         ts_add_child(tsanim, tstrk);
566                 }
567         }
568
569         return tsanim;
570
571 err:
572         ts_free_tree(tsanim);
573         return 0;
574 }
575
576 static const char *instr[] = {"step", "linear", "cubic"};
577 static const char *exstr[] = {"extend", "clamp", "repeat", "pingpong"};
578
579 static struct ts_node *create_tracktree(struct goat3d *g, const struct goat3d_track *trk)
580 {
581         int i, num_keys;
582         struct ts_node *tstrk, *tskey;
583         struct ts_attr *tsa;
584         struct goat3d_key key;
585         enum goat3d_track_type basetype;
586
587         create_tsnode(tstrk, 0, "track");
588         create_tsattr(tsa, tstrk, "name", TS_STRING);
589         if(ts_set_value_str(&tsa->val, namegen(g, trk->name, TRACK)) == -1) {
590                 goto err;
591         }
592
593         create_tsattr(tsa, tstrk, "type", TS_STRING);
594         if(ts_set_value_str(&tsa->val, g3dimpl_trktypestr(trk->type)) == -1) {
595                 goto err;
596         }
597         basetype = trk->type & 0xff;
598
599         create_tsattr(tsa, tstrk, "interp", TS_STRING);
600         if(ts_set_value_str(&tsa->val, instr[trk->trk[0].interp]) == -1) {
601                 goto err;
602         }
603         create_tsattr(tsa, tstrk, "extrap", TS_STRING);
604         if(ts_set_value_str(&tsa->val, exstr[trk->trk[0].extrap]) == -1) {
605                 goto err;
606         }
607
608         if(trk->node) {
609                 create_tsattr(tsa, tstrk, "node", TS_STRING);
610                 if(ts_set_value_str(&tsa->val, trk->node->name) == -1) {
611                         goto err;
612                 }
613         }
614
615         num_keys = goat3d_get_track_key_count(trk);
616         for(i=0; i<num_keys; i++) {
617                 goat3d_get_track_key(trk, i, &key);
618
619                 create_tsnode(tskey, tstrk, "key");
620                 create_tsattr(tsa, tskey, "time", TS_NUMBER);
621                 ts_set_valuei(&tsa->val, key.tm);
622
623                 if(basetype == GOAT3D_TRACK_VAL) {
624                         create_tsattr(tsa, tskey, "value", TS_NUMBER);
625                         ts_set_valuef(&tsa->val, key.val[0]);
626                 } else {
627                         static const int typecount[] = {1, 3, 4, 4};
628                         create_tsattr(tsa, tskey, "value", TS_VECTOR);
629                         ts_set_valuef_arr(&tsa->val, typecount[basetype], key.val);
630                 }
631         }
632
633         return tstrk;
634
635 err:
636         ts_free_tree(tstrk);
637         return 0;
638 }
639
640
641
642 static void init_namegen(struct goat3d *g)
643 {
644         memset(g->namecnt, 0, sizeof g->namecnt);
645 }
646
647 static const char *namegen(struct goat3d *g, const char *name, int type)
648 {
649         static const char *fmt[] = {"material%03u", "mesh%03u", "light%03u",
650                 "camera%03u", "node%03u", "animation%03u", "track%03u"};
651
652         if(name) {
653                 /* if an actual name happens to match our pattern, make sure to skip it
654                  * for the auto-generated names
655                  */
656                 int n;
657                 if(sscanf(name, fmt[type], &n)) {
658                         g->namecnt[type] = n;
659                 }
660                 return name;
661         }
662
663         sprintf(g->namebuf, fmt[type], g->namecnt[type]++);
664         return g->namebuf;
665 }
666
667 static const char *enctab =
668         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
669         "abcdefghijklmnopqrstuvwxyz"
670         "0123456789+/";
671
672 GOAT3DAPI char *goat3d_b64encode(const void *data, int size, char *buf, int *bufsz)
673 {
674         const unsigned char *src = data;
675         char *dest;
676         int i;
677         int outsz = size * 4 / 3 + 1;
678         int bitgrp[4];
679         int leftover;
680
681         if(buf) {
682                 if(*bufsz < outsz) {
683                         *bufsz = outsz;
684                         return 0;
685                 }
686         } else {
687                 if(bufsz) *bufsz = outsz;
688
689                 /* reserve more space for up to two padding bytes */
690                 if(!(buf = malloc(outsz + 2))) {
691                         return 0;
692                 }
693         }
694         dest = buf;
695
696         leftover = 0;
697         while(size > 0) {
698                 bitgrp[0] = (src[0] & 0xfc) >> 2;
699                 bitgrp[1] = (src[0] & 3) << 4;
700                 if(--size <= 0) {
701                         leftover = 2;
702                         break;
703                 }
704                 bitgrp[1] |= src[1] >> 4;
705                 bitgrp[2] = (src[1] & 0xf) << 2;
706                 if(--size <= 0) {
707                         leftover = 3;
708                         break;
709                 }
710                 bitgrp[2] |= src[2] >> 6;
711                 bitgrp[3] = src[2] & 0x3f;
712
713                 dest[0] = enctab[bitgrp[0]];
714                 dest[1] = enctab[bitgrp[1]];
715                 dest[2] = enctab[bitgrp[2]];
716                 dest[3] = enctab[bitgrp[3]];
717
718                 dest += 4;
719                 src += 3;
720                 leftover = 0;
721                 size--;
722         }
723
724         if(leftover) {
725                 for(i=0; i<4; i++) {
726                         if(i < leftover) {
727                                 *dest++ = enctab[bitgrp[i]];
728                         } else {
729                                 *dest++ = '=';
730                         }
731                 }
732         }
733
734         *dest = 0;
735         return buf;
736 }