2 goat3d - 3D scene, and animation file format library.
3 Copyright (C) 2013-2023 John Tsiombikas <nuclear@member.fsf.org>
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.
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.
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/>.
25 /* type passed to namegen */
26 enum { MTL, MESH, LIGHT, CAMERA, NODE, ANIM, TRACK };
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);
36 static void init_namegen(struct goat3d *g);
37 static const char *namegen(struct goat3d *g, const char *name, int type);
39 GOAT3DAPI char *goat3d_b64encode(const void *data, int size, char *buf, int *bufsz);
40 #define b64encode goat3d_b64encode
42 #define create_tsnode(n, p, nstr) \
44 int len = strlen(nstr); \
45 if(!((n) = ts_alloc_node())) { \
46 goat3d_logmsg(LOG_ERROR, "failed to create treestore node\n"); \
49 if(!((n)->name = malloc(len + 1))) { \
50 goat3d_logmsg(LOG_ERROR, "failed to allocate node name string\n"); \
54 memcpy((n)->name, (nstr), len + 1); \
56 ts_add_child((p), (n)); \
60 #define create_tsattr(a, n, nstr, atype) \
62 if(!((a) = ts_alloc_attr())) { \
63 goat3d_logmsg(LOG_ERROR, "failed to create treestore attribute\n"); \
66 if(ts_set_attr_name(a, nstr) == -1) { \
67 goat3d_logmsg(LOG_ERROR, "failed to allocate attrib name string\n"); \
71 (a)->val.type = (atype); \
73 ts_add_attr((n), (a)); \
79 int g3dimpl_scnsave(const struct goat3d *g, struct goat3d_io *io)
83 struct ts_node *tsroot = 0, *tsn, *tsenv;
88 tsio.write = io->write;
90 init_namegen((struct goat3d*)g);
92 create_tsnode(tsroot, 0, "scene");
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);
100 num = dynarr_size(g->materials);
101 for(i=0; i<num; i++) {
102 if(!(tsn = create_mtltree((struct goat3d*)g, g->materials[i]))) {
105 ts_add_child(tsroot, tsn);
108 num = dynarr_size(g->meshes);
109 for(i=0; i<num; i++) {
110 if(!(tsn = create_meshtree((struct goat3d*)g, g->meshes[i]))) {
113 ts_add_child(tsroot, tsn);
116 num = dynarr_size(g->lights);
117 for(i=0; i<num; i++) {
118 if(!(tsn = create_lighttree((struct goat3d*)g, g->lights[i]))) {
121 ts_add_child(tsroot, tsn);
124 num = dynarr_size(g->cameras);
125 for(i=0; i<num; i++) {
126 if(!(tsn = create_camtree((struct goat3d*)g, g->cameras[i]))) {
129 ts_add_child(tsroot, tsn);
132 num = dynarr_size(g->nodes);
133 for(i=0; i<num; i++) {
134 if(!(tsn = create_nodetree((struct goat3d*)g, g->nodes[i]))) {
137 ts_add_child(tsroot, tsn);
140 num = dynarr_size(g->anims);
141 for(i=0; i<num; i++) {
142 if(!(tsn = create_animtree((struct goat3d*)g, g->anims[i]))) {
145 ts_add_child(tsroot, tsn);
148 if(ts_save_io(tsroot, &tsio) == -1) {
149 goat3d_logmsg(LOG_ERROR, "g3dimpl_scnsave: failed\n");
155 ts_free_tree(tsroot);
159 int g3dimpl_anmsave(const struct goat3d *g, struct goat3d_io *io)
164 static struct ts_node *create_mtltree(struct goat3d *g, const struct goat3d_material *mtl)
167 struct ts_node *tsn, *tsmtl = 0;
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) {
176 num_attr = dynarr_size(mtl->attrib);
177 for(i=0; i<num_attr; i++) {
178 struct material_attrib *attr = mtl->attrib + i;
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) {
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);
188 create_tsattr(tsa, tsn, "map", TS_STRING);
189 if(ts_set_value_str(&tsa->val, attr->map) == -1) {
201 static struct ts_node *create_meshtree(struct goat3d *g, const struct goat3d_mesh *mesh)
204 struct ts_node *tsmesh = 0, *tslist, *tsitem;
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) {
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) {
220 create_tsattr(tsa, tsmesh, "material", TS_NUMBER);
221 ts_set_valuei(&tsa->val, mesh->mtl->idx);
225 /* TODO option of saving separate mesh files */
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);
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))) {
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);
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);
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))) {
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);
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);
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))) {
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);
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);
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))) {
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);
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);
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))) {
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);
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);
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))) {
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);
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);
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))) {
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);
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);
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) {
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);
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))) {
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]);
405 ts_free_tree(tsmesh);
409 static struct ts_node *create_lighttree(struct goat3d *g, const struct goat3d_light *light)
411 struct ts_node *tslight = 0;
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) {
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);
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);
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);
437 create_tsattr(tsa, tslight, "color", TS_VECTOR);
438 ts_set_valuefv(&tsa->val, 3, light->color.x, light->color.y, light->color.z);
440 create_tsattr(tsa, tslight, "atten", TS_VECTOR);
441 ts_set_valuefv(&tsa->val, 3, light->attenuation.x, light->attenuation.y, light->attenuation.z);
443 create_tsattr(tsa, tslight, "distance", TS_NUMBER);
444 ts_set_valuef(&tsa->val, light->max_dist);
449 ts_free_tree(tslight);
453 static struct ts_node *create_camtree(struct goat3d *g, const struct goat3d_camera *cam)
455 struct ts_node *tscam = 0;
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) {
464 create_tsattr(tsa, tscam, "pos", TS_VECTOR);
465 ts_set_valuefv(&tsa->val, 3, cam->pos.x, cam->pos.y, cam->pos.z);
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);
472 create_tsattr(tsa, tscam, "fov", TS_NUMBER);
473 ts_set_valuef(&tsa->val, cam->fov);
475 create_tsattr(tsa, tscam, "nearclip", TS_NUMBER);
476 ts_set_valuef(&tsa->val, cam->near_clip);
478 create_tsattr(tsa, tscam, "farclip", TS_NUMBER);
479 ts_set_valuef(&tsa->val, cam->far_clip);
488 static struct ts_node *create_nodetree(struct goat3d *g, const struct goat3d_node *node)
490 struct ts_node *tsnode = 0;
492 struct goat3d_node *par;
493 static const char *objtypestr[] = {"null", "mesh", "light", "camera"};
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) {
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) {
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) {
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);
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);
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);
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);
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);
545 ts_free_tree(tsnode);
549 static struct ts_node *create_animtree(struct goat3d *g, const struct goat3d_anim *anim)
552 struct ts_node *tsanim, *tstrk;
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) {
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);
572 ts_free_tree(tsanim);
576 static const char *instr[] = {"step", "linear", "cubic"};
577 static const char *exstr[] = {"extend", "clamp", "repeat", "pingpong"};
579 static struct ts_node *create_tracktree(struct goat3d *g, const struct goat3d_track *trk)
582 struct ts_node *tstrk, *tskey;
584 struct goat3d_key key;
585 enum goat3d_track_type basetype;
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) {
593 create_tsattr(tsa, tstrk, "type", TS_STRING);
594 if(ts_set_value_str(&tsa->val, g3dimpl_trktypestr(trk->type)) == -1) {
597 basetype = trk->type & 0xff;
599 create_tsattr(tsa, tstrk, "interp", TS_STRING);
600 if(ts_set_value_str(&tsa->val, instr[trk->trk[0].interp]) == -1) {
603 create_tsattr(tsa, tstrk, "extrap", TS_STRING);
604 if(ts_set_value_str(&tsa->val, exstr[trk->trk[0].extrap]) == -1) {
609 create_tsattr(tsa, tstrk, "node", TS_STRING);
610 if(ts_set_value_str(&tsa->val, trk->node->name) == -1) {
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);
619 create_tsnode(tskey, tstrk, "key");
620 create_tsattr(tsa, tskey, "time", TS_NUMBER);
621 ts_set_valuei(&tsa->val, key.tm);
623 if(basetype == GOAT3D_TRACK_VAL) {
624 create_tsattr(tsa, tskey, "value", TS_NUMBER);
625 ts_set_valuef(&tsa->val, key.val[0]);
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);
642 static void init_namegen(struct goat3d *g)
644 memset(g->namecnt, 0, sizeof g->namecnt);
647 static const char *namegen(struct goat3d *g, const char *name, int type)
649 static const char *fmt[] = {"material%03u", "mesh%03u", "light%03u",
650 "camera%03u", "node%03u", "animation%03u", "track%03u"};
653 /* if an actual name happens to match our pattern, make sure to skip it
654 * for the auto-generated names
657 if(sscanf(name, fmt[type], &n)) {
658 g->namecnt[type] = n;
663 sprintf(g->namebuf, fmt[type], g->namecnt[type]++);
667 static const char *enctab =
668 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
669 "abcdefghijklmnopqrstuvwxyz"
672 GOAT3DAPI char *goat3d_b64encode(const void *data, int size, char *buf, int *bufsz)
674 const unsigned char *src = data;
677 int outsz = size * 4 / 3 + 1;
687 if(bufsz) *bufsz = outsz;
689 /* reserve more space for up to two padding bytes */
690 if(!(buf = malloc(outsz + 2))) {
698 bitgrp[0] = (src[0] & 0xfc) >> 2;
699 bitgrp[1] = (src[0] & 3) << 4;
704 bitgrp[1] |= src[1] >> 4;
705 bitgrp[2] = (src[1] & 0xf) << 2;
710 bitgrp[2] |= src[2] >> 6;
711 bitgrp[3] = src[2] & 0x3f;
713 dest[0] = enctab[bitgrp[0]];
714 dest[1] = enctab[bitgrp[1]];
715 dest[2] = enctab[bitgrp[2]];
716 dest[3] = enctab[bitgrp[3]];
727 *dest++ = enctab[bitgrp[i]];