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/>.
26 #if defined(__WATCOMC__) || defined(_WIN32) || defined(__DJGPP__)
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);
48 GOAT3DAPI void *goat3d_b64decode(const char *str, void *buf, int *bufsz);
49 #define b64decode goat3d_b64decode
52 int g3dimpl_scnload(struct goat3d *g, struct goat3d_io *io)
56 struct ts_node *tsroot, *c;
61 tsio.write = io->write;
63 if(!(tsroot = ts_load_io(&tsio))) {
64 goat3d_logmsg(LOG_ERROR, "failed to load scene\n");
67 if(strcmp(tsroot->name, "scene") != 0) {
68 goat3d_logmsg(LOG_ERROR, "invalid scene file, root node is not \"scene\"\n");
73 /* read all materials */
74 c = tsroot->child_list;
76 if(strcmp(c->name, "mtl") == 0) {
77 struct goat3d_material *mtl = read_material(g, c);
79 goat3d_add_mtl(g, mtl);
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.
89 c = tsroot->child_list;
91 if(strcmp(c->name, "node") == 0) {
92 struct goat3d_node *node;
94 if(!(node = goat3d_create_node())) {
95 goat3d_logmsg(LOG_ERROR, "failed to allocate node\n");
99 if((str = ts_get_attr_str(c, "name", 0))) {
100 goat3d_set_node_name(node, str);
102 goat3d_add_node(g, node);
107 /* read all meshes, cameras, lights, animations */
108 c = tsroot->child_list;
110 if(strcmp(c->name, "mesh") == 0) {
111 struct goat3d_mesh *mesh = read_mesh(g, c);
113 goat3d_add_mesh(g, mesh);
115 } else if(strcmp(c->name, "anim") == 0) {
122 /* now load the nodes properly */
124 c = tsroot->child_list;
126 if(strcmp(c->name, "node") == 0) {
127 struct goat3d_node *node = goat3d_get_node(g, idx++);
129 read_node(g, node, c);
134 ts_free_tree(tsroot);
138 int g3dimpl_anmload(struct goat3d *g, struct goat3d_io *io)
143 tsio.read = io->read;
144 tsio.write = io->write;
149 static struct goat3d_material *read_material(struct goat3d *g, struct ts_node *tsmtl)
151 struct goat3d_material *mtl;
152 struct material_attrib mattr, *arr;
156 if(!(mtl = malloc(sizeof *mtl)) || g3dimpl_mtl_init(mtl) == -1) {
158 goat3d_logmsg(LOG_ERROR, "read_material: failed to allocate material\n");
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);
168 goat3d_set_mtl_name(mtl, str);
170 /* read all material attributes */
171 c = tsmtl->child_list;
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);
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);
194 static char *read_material_attrib(struct material_attrib *attr, struct ts_node *tsnode)
197 struct ts_attr *tsattr;
198 const char *name, *map;
200 memset(attr, 0, sizeof *attr);
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 */
205 switch(tsattr->val.type) {
207 attr->value.x = tsattr->val.fnum;
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];
215 default: /* no valid val attribute found */
220 if(!(name = ts_get_attr_str(tsnode, "name", 0)) || !*name) {
223 if(!(attr->name = malloc(strlen(name) + 1))) {
224 goat3d_logmsg(LOG_ERROR, "read_material_attrib: failed to allocate name\n");
227 strcpy(attr->name, name);
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");
235 strcpy(attr->map, map);
240 static struct goat3d_mesh *read_mesh(struct goat3d *g, struct ts_node *tsmesh)
242 struct goat3d_mesh *mesh;
243 struct goat3d_material *mtl;
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");
254 if((str = ts_get_attr_str(tsmesh, "name", 0))) {
255 goat3d_set_mesh_name(mesh, str);
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);
263 goat3d_logmsg(LOG_WARNING, "read_mesh: mesh %s refers to invalid material: %d\n",
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);
270 goat3d_logmsg(LOG_WARNING, "read_mesh: mesh %s refers to invalid material: %s\n",
275 /* external mesh data */
276 if((str = ts_get_attr_str(tsmesh, "file", 0))) {
277 const char *fname = str;
281 pathbuf = alloca(strlen(str) + strlen(g->search_path) + 2);
282 sprintf(pathbuf, "%s/%s", g->search_path, str);
286 if(g3dimpl_loadmesh(mesh, fname) == -1) {
287 goat3d_logmsg(LOG_ERROR, "read_mesh: failed to load external mesh: %s\n", fname);
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.
294 if(tsmesh->child_list) {
295 goat3d_logmsg(LOG_WARNING, "read_mesh: external mesh node also has unexpected children; ignoring.\n");
300 c = tsmesh->child_list;
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",
308 mesh->vertices = tmp;
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",
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",
323 mesh->tangents = tmp;
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",
331 mesh->texcoords = tmp;
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",
339 mesh->skin_weights = tmp;
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",
347 mesh->skin_matrices = tmp;
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",
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",
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",
379 g3dimpl_obj_destroy((struct object*)mesh);
383 static int calc_b64_size(const char *s)
386 const char *end = s + len;
387 while(end > s && *--end == '=') len--;
391 static void *read_veclist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tslist)
395 struct ts_attr *attr;
399 arr = dynarr_clear(arr);
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");
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",
415 bufsz = size * dim * sizeof(float);
416 b64decode(str, arr, &bufsz);
419 c = tslist->child_list;
421 if(strcmp(c->name, nodename) != 0) {
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) {
431 vec[i] = attr->val.vec[i];
435 if(!(arr = dynarr_push(arr, vec))) {
436 goat3d_logmsg(LOG_ERROR, "read_veclist: failed to resize %s array\n",
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));
450 static void *read_intlist(void *arr, int dim, const char *nodename, const char *attrname, struct ts_node *tslist)
454 struct ts_attr *attr;
458 arr = dynarr_clear(arr);
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");
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",
474 bufsz = size * dim * sizeof(int);
475 b64decode(str, arr, &bufsz);
478 c = tslist->child_list;
480 if(strcmp(c->name, nodename) != 0) {
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) {
490 ivec[i] = attr->val.vec[i];
494 if(!(arr = dynarr_push(arr, ivec))) {
495 goat3d_logmsg(LOG_ERROR, "read_intlist: failed to resize %s array\n",
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));
509 static void *read_bonelist(struct goat3d *g, struct goat3d_node **arr, struct ts_node *tslist)
513 struct goat3d_node *bone;
516 arr = dynarr_clear(arr);
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");
524 /* TODO base64 data support */
526 c = tslist->child_list;
528 if(strcmp(c->name, "bone") != 0) {
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);
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);
548 if(bone && !(arr = dynarr_push(arr, &bone))) {
549 goat3d_logmsg(LOG_ERROR, "read_bonelist: failed to resize bone array\n");
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));
561 #define GETREF(ptr, typestr, getname) \
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); \
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); \
575 static int read_node(struct goat3d *g, struct goat3d_node *node, struct ts_node *tsnode)
579 struct goat3d_node *parent;
582 GETREF(parent, "parent", node);
584 goat3d_add_node_child(parent, node);
587 node->type = GOAT3D_NODE_MESH;
588 GETREF(node->obj, "mesh", mesh);
590 node->type = GOAT3D_NODE_LIGHT;
591 GETREF(node->obj, "light", light);
594 node->type = GOAT3D_NODE_CAMERA;
595 GETREF(node->obj, "camera", camera);
598 node->type = GOAT3D_NODE_NULL;
601 if((vec = ts_get_attr_vec(tsnode, "pos", 0))) {
602 goat3d_set_node_position(node, vec[0], vec[1], vec[2]);
604 if((vec = ts_get_attr_vec(tsnode, "rot", 0))) {
605 goat3d_set_node_rotation(node, vec[0], vec[1], vec[2], vec[3]);
607 if((vec = ts_get_attr_vec(tsnode, "scale", 0))) {
608 goat3d_set_node_scaling(node, vec[0], vec[1], vec[2]);
610 if((vec = ts_get_attr_vec(tsnode, "pivot", 0))) {
611 goat3d_set_node_pivot(node, vec[0], vec[1], vec[2]);
617 static int read_anim(struct goat3d *g, struct ts_node *tsanim)
621 struct goat3d_anim *anim;
622 struct goat3d_track *trk;
624 if(!(str = ts_get_attr_str(tsanim, "name", 0))) {
625 goat3d_logmsg(LOG_WARNING, "read_anim: ignoring animation without a name\n");
629 if(!(anim = goat3d_create_anim())) {
630 goat3d_logmsg(LOG_ERROR, "read_anim: failed to initialize animation: %s\n", str);
633 goat3d_set_anim_name(anim, str);
635 c = tsanim->child_list;
637 if(strcmp(c->name, "track") == 0) {
638 if(!(trk = read_track(g, c))) {
642 goat3d_add_anim_track(anim, trk);
647 goat3d_add_anim(g, anim);
651 static int parsetype(const char *str)
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;
663 static int parseinterp(const char *str)
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;
671 static int parseextrap(const char *str)
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;
680 static struct goat3d_track *read_track(struct goat3d *g, struct ts_node *tstrk)
684 struct goat3d_track *trk;
685 struct goat3d_node *node;
686 struct goat3d_key key;
689 struct ts_attr *tsattr;
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");
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);
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);
707 goat3d_logmsg(LOG_WARNING, "read_track: ignoring track with missing node reference\n");
711 if(!(trk = goat3d_create_track())) {
712 goat3d_logmsg(LOG_ERROR, "read_track: failed to create new keyframe track\n");
715 goat3d_set_track_node(trk, node);
716 goat3d_set_track_type(trk, type);
718 if((str = ts_get_attr_str(tstrk, "name", 0))) {
719 goat3d_set_track_name(trk, str);
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);
726 goat3d_set_track_interp(trk, in);
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);
733 goat3d_set_track_extrap(trk, ex);
737 c = tstrk->child_list;
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", ""));
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", ""));
754 if(tsattr->val.type == TS_NUMBER) {
755 key.val[0] = tsattr->val.fnum;
758 if(i < tsattr->val.vec_size) {
759 key.val[i] = tsattr->val.vec[i];
765 goat3d_set_track_key(trk, &key);
770 /* force lazy re-sorting of keyframes if necessary */
771 goat3d_get_track_key(trk, 0, &key);
776 static int b64bits(int c)
778 if(c >= 'A' && c <= 'Z') {
781 if(c >= 'a' && c <= 'z') {
784 if(c >= '0' && c <= '9') {
787 if(c == '+') return 62;
788 if(c == '/') return 63;
793 GOAT3DAPI void *goat3d_b64decode(const char *str, void *buf, int *bufsz)
795 unsigned char *dest, *end;
803 sz = calc_b64_size(str);
804 if(!(buf = malloc(sz))) {
807 if(bufsz) *bufsz = sz;
810 end = (unsigned char*)buf + sz;
816 if((bits = b64bits(*str++)) == -1) {
825 if(dest < end) *dest = acc | (bits >> 4);
830 if(dest < end) *dest = acc | (bits >> 2);
835 if(dest < end) *dest = acc | bits;
843 if(dest < end) *dest = acc;
847 if(bufsz) *bufsz = dest - (unsigned char*)buf;