added scr_lvled, a bunch of libraries, and improved framework code
[raydungeon] / libs / goat3d / src / readgltf.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 <ctype.h>
20 #include "goat3d.h"
21 #include "g3dscn.h"
22 #include "log.h"
23 #include "json.h"
24
25 static struct goat3d_material *read_material(struct goat3d *g, struct json_obj *jmtl);
26
27 int g3dimpl_loadgltf(struct goat3d *g, struct goat3d_io *io)
28 {
29         long i, filesz;
30         char *filebuf;
31         struct json_obj root;
32         struct json_value *jval;
33         struct json_item *jitem;
34         struct goat3d_material *mtl;
35
36         if(!(filebuf = malloc(4096))) {
37                 goat3d_logmsg(LOG_ERROR, "goat3d_load: failed to allocate file buffer\n");
38                 return -1;
39         }
40         filesz = io->read(filebuf, 4096, io->cls);
41         if(filesz < 2) {
42                 free(filebuf);
43                 return -1;
44         }
45         for(i=0; i<filesz; i++) {
46                 if(!isspace(filebuf[i])) {
47                         if(filebuf[i] != '{') {
48                                 free(filebuf);
49                                 return -1;              /* not json */
50                         }
51                         break;
52                 }
53         }
54         free(filebuf);
55
56         /* alright, it looks like json, load into memory and parse it to continue */
57         filesz = io->seek(0, SEEK_END, io->cls);
58         io->seek(0, SEEK_SET, io->cls);
59         if(!(filebuf = malloc(filesz + 1))) {
60                 goat3d_logmsg(LOG_ERROR, "goat3d_load: failed to load file into memory\n");
61                 return -1;
62         }
63         if(io->read(filebuf, filesz, io->cls) != filesz) {
64                 goat3d_logmsg(LOG_ERROR, "goat3d_load: EOF while reading file\n");
65                 free(filebuf);
66                 return -1;
67         }
68         filebuf[filesz] = 0;
69
70         json_init_obj(&root);
71         if(json_parse(&root, filebuf) == -1) {
72                 free(filebuf);
73                 return -1;
74         }
75         free(filebuf);
76
77         /* a valid gltf file needs to have an "asset" node with a version number */
78         if(!(jval = json_lookup(&root, "asset.version"))) {
79                 json_destroy_obj(&root);
80                 return -1;
81         }
82
83         /* read all materials */
84         if((jitem = json_find_item(&root, "materials"))) {
85                 if(jitem->val.type != JSON_ARR) {
86                         goat3d_logmsg(LOG_ERROR, "goat3d_load: gltf materials value is not an array!\n");
87                         goto skipmtl;
88                 }
89
90                 for(i=0; i<jitem->val.arr.size; i++) {
91                         jval = jitem->val.arr.val + i;
92
93                         if(jval->type != JSON_OBJ) {
94                                 goat3d_logmsg(LOG_ERROR, "goat3d_load: gltf material is not a json object!\n");
95                                 continue;
96                         }
97
98                         if((mtl = read_material(g, &jval->obj))) {
99                                 goat3d_add_mtl(g, mtl);
100                         }
101                 }
102         }
103 skipmtl:
104
105         /* ... */
106
107         json_destroy_obj(&root);
108         return 0;
109 }
110
111 static int jarr_to_vec(struct json_arr *jarr, float *vec)
112 {
113         int i;
114
115         if(jarr->size < 3 || jarr->size > 4) {
116                 return -1;
117         }
118
119         for(i=0; i<4; i++) {
120                 if(i >= jarr->size) {
121                         vec[i] = 0;
122                         continue;
123                 }
124                 if(jarr->val[i].type != JSON_NUM) {
125                         return -1;
126                 }
127                 vec[i] = jarr->val[i].num;
128         }
129         return jarr->size;
130 }
131
132 static int jval_to_vec(struct json_value *jval, float *vec)
133 {
134         if(jval->type != JSON_ARR) return -1;
135         return jarr_to_vec(&jval->arr, vec);
136 }
137
138 static struct goat3d_material *read_material(struct goat3d *g, struct json_obj *jmtl)
139 {
140         struct goat3d_material *mtl;
141         const char *str;
142         struct json_value *jval;
143         float color[4], specular[4], roughness, metal, ior;
144         int nelem;
145
146         if(!(mtl = malloc(sizeof *mtl)) || g3dimpl_mtl_init(mtl) == -1) {
147                 free(mtl);
148                 goat3d_logmsg(LOG_ERROR, "read_material: failed to allocate material\n");
149                 return 0;
150         }
151
152         if((str = json_lookup_str(jmtl, "name", 0))) {
153                 goat3d_set_mtl_name(mtl, str);
154         }
155
156         if((jval = json_lookup(jmtl, "pbrMetallicRoughness.baseColorFactor")) &&
157                         (nelem = jval_to_vec(jval, color)) != -1) {
158                 goat3d_set_mtl_attrib(mtl, "diffuse", color);
159         }
160         /* TODO textures */
161
162         if((roughness = json_lookup_num(jmtl, "pbrMetallicRoughness.roughnessFactor", -1.0)) >= 0) {
163                 goat3d_set_mtl_attrib1f(mtl, "roughness", roughness);
164                 goat3d_set_mtl_attrib1f(mtl, GOAT3D_MAT_ATTR_SHININESS, (1.0f - roughness) * 100.0f + 1.0f);
165         }
166         if((metal = json_lookup_num(jmtl, "pbrMetallicRoughness.metallicFactor", -1.0)) >= 0) {
167                 goat3d_set_mtl_attrib1f(mtl, "metal", metal);
168         }
169         if((jval = json_lookup(jmtl, "extensions.KHR_materials_specular.specularColorFactor")) &&
170                         (nelem = jval_to_vec(jval, specular)) != -1) {
171                 goat3d_set_mtl_attrib(mtl, GOAT3D_MAT_ATTR_SPECULAR, specular);
172         }
173         if((ior = json_lookup_num(jmtl, "extensions.KHR_materials_ior.ior", -1.0)) >= 0) {
174                 goat3d_set_mtl_attrib1f(mtl, GOAT3D_MAT_ATTR_IOR, ior);
175         }
176         /* TODO more attributes */
177
178         return mtl;
179 }