added audio
[laserbrain_demo] / src / metascene.cc
1 #include <assert.h>
2 #include <string>
3 #include <regex>
4 #include "metascene.h"
5 #include "scene.h"
6 #include "treestore.h"
7 #include "logger.h"
8 #include "app.h"
9
10 #if defined(WIN32) || defined(__WIN32__)
11 #include <malloc.h>
12 #else
13 #include <alloca.h>
14 #endif
15
16 struct MaterialEdit {
17         std::regex name_re;
18         int attr;
19         Texture *tex;
20 };
21
22 static bool proc_node(MetaScene *mscn, struct ts_node *node);
23 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node);
24 static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node);
25 static bool proc_music(MetaScene *mscn, struct ts_node *node);
26 static void apply_mtledit(Scene *scn, const MaterialEdit &med);
27 static void apply_mtledit(Material *mtl, const MaterialEdit &med);
28 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name);
29
30 static void print_scene_graph(SceneNode *n, int level);
31
32
33 MetaScene::MetaScene()
34 {
35         walk_mesh = 0;
36         music = 0;
37 }
38
39 MetaScene::~MetaScene()
40 {
41         delete walk_mesh;
42         delete music;
43 }
44
45
46 bool MetaScene::load(const char *fname)
47 {
48         struct ts_node *root = ts_load(fname);
49         if(!root || strcmp(root->name, "scene") != 0) {
50                 ts_free_tree(root);
51                 error_log("failed to load scene metadata: %s\n", fname);
52                 return false;
53         }
54
55         bool res = proc_node(this, root);
56         ts_free_tree(root);
57
58         /*info_log("loaded scene: %s\n", fname);
59         info_log("scene graph:\n");
60         print_scene_graph(scn->nodes, 0);
61         */
62         return res;
63 }
64
65 void MetaScene::update(float dt)
66 {
67         int nscn = scenes.size();
68         for(int i=0; i<nscn; i++) {
69                 scenes[i]->update(dt);
70         }
71 }
72
73 void MetaScene::draw() const
74 {
75         int nscn = scenes.size();
76         for(int i=0; i<nscn; i++) {
77                 scenes[i]->draw();
78         }
79 }
80
81 SceneNode *MetaScene::find_node(const char *name) const
82 {
83         int num = scenes.size();
84         for(int i=0; i<num; i++) {
85                 SceneNode *n = scenes[i]->find_node(name);
86                 if(n) return n;
87         }
88         return 0;
89 }
90
91 SceneNode *MetaScene::match_node(const char *qstr) const
92 {
93         int num = scenes.size();
94         for(int i=0; i<num; i++) {
95                 SceneNode *n = scenes[i]->match_node(qstr);
96                 if(n) return n;
97         }
98         return 0;
99 }
100
101 std::list<SceneNode*> MetaScene::match_nodes(const char *qstr) const
102 {
103         std::list<SceneNode*> res;
104         int num = scenes.size();
105         for(int i=0; i<num; i++) {
106                 std::list<SceneNode*> tmp = scenes[i]->match_nodes(qstr);
107                 if(!tmp.empty()) {
108                         res.splice(res.end(), tmp);
109                 }
110         }
111         return std::move(res);
112 }
113
114 Scene *MetaScene::extract_nodes(const char *qstr)
115 {
116         Scene *scn = 0;
117         int nscn = scenes.size();
118         for(int i=0; i<nscn; i++) {
119                 Scene *tmp = scenes[i]->extract_nodes(qstr);
120                 if(tmp) {
121                         if(!scn) {
122                                 scn = tmp;
123                         } else {
124                                 scn->merge(tmp);
125                                 delete tmp;
126                         }
127                 }
128         }
129         return scn;
130 }
131
132 static bool proc_node(MetaScene *mscn, struct ts_node *node)
133 {
134         struct ts_node *c = node->child_list;
135         while(c) {
136                 if(!proc_node(mscn, c)) {
137                         return false;
138                 }
139                 c = c->next;
140         }
141
142         // do this last to allow other contents of the node to do their thing
143         if(strcmp(node->name, "scenefile") == 0) {
144                 return proc_scenefile(mscn, node);
145
146         } else if(strcmp(node->name, "remap") == 0) {
147                 const char *match = ts_get_attr_str(node, "match");
148                 const char *replace = ts_get_attr_str(node, "replace");
149                 if(match && replace) {
150                         mscn->datamap.map(match, replace);
151                 }
152
153         } else if(strcmp(node->name, "music") == 0) {
154                 return proc_music(mscn, node);
155         }
156
157         return true;
158 }
159
160
161
162 struct SceneData {
163         MetaScene *meta;
164         std::string walkmesh_regexp, spawn_regexp;
165         std::vector<MaterialEdit> mtledit;
166 };
167
168 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node)
169 {
170         const char *fname = ts_get_attr_str(node, "file");
171         if(fname) {
172                 SceneData *sdat = new SceneData;
173                 sdat->meta = mscn;
174
175                 // datapath
176                 struct ts_attr *adpath = attr_inscope(node, "datapath");
177                 if(adpath && adpath->val.type == TS_STRING) {
178                         mscn->datamap.set_path(adpath->val.str);
179                 }
180
181                 // strip path
182                 struct ts_attr *aspath = attr_inscope(node, "strip_path");
183                 if(aspath && aspath->val.type == TS_NUMBER) {
184                         mscn->datamap.set_strip(aspath->val.inum);
185                 }
186
187                 // walkmesh
188                 struct ts_attr *awmesh = attr_inscope(node, "walkmesh");
189                 if(awmesh && awmesh->val.type == TS_STRING) {
190                         sdat->walkmesh_regexp = std::string(awmesh->val.str);
191                 }
192
193                 // spawn node
194                 struct ts_attr *awspawn = attr_inscope(node, "spawn");
195                 if(awspawn) {
196                         switch(awspawn->val.type) {
197                         case TS_VECTOR:
198                                 mscn->start_pos = Vec3(awspawn->val.vec[0], awspawn->val.vec[1],
199                                                 awspawn->val.vec[2]);
200                                 break;
201
202                         case TS_STRING:
203                         default:
204                                 sdat->spawn_regexp = std::string(awspawn->val.str);
205                         }
206                 }
207                 if((awspawn = attr_inscope(node, "spawn_rot")) && awspawn->val.type == TS_VECTOR) {
208                         Quat rot;
209                         rot.rotate(Vec3(1, 0, 0), deg_to_rad(awspawn->val.vec[0]));
210                         rot.rotate(Vec3(0, 1, 0), deg_to_rad(awspawn->val.vec[1]));
211                         rot.rotate(Vec3(0, 0, 1), deg_to_rad(awspawn->val.vec[2]));
212                         mscn->start_rot = rot;
213                 }
214
215                 int namesz = mscn->datamap.lookup(fname, 0, 0);
216                 char *namebuf = (char*)alloca(namesz + 1);
217                 if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
218                         fname = namebuf;
219                 }
220
221                 // material edits
222                 struct ts_node *child = node->child_list;
223                 while(child) {
224                         MaterialEdit medit;
225                         if(proc_mtledit(mscn, &medit, child)) {
226                                 sdat->mtledit.push_back(medit);
227                         }
228                         child = child->next;
229                 }
230
231                 Scene *newscn = sceneman.get(fname);
232                 /* NOTE: setting all these after get() is not a race condition, because
233                  * scene_loaded() which uses this, will only run in our main loop during
234                  * SceneSet::update() on the main thread.
235                  */
236                 newscn->datamap = mscn->datamap;
237                 mscn->datamap.clear();
238
239                 newscn->metascn = mscn;
240                 mscn->scndata[newscn] = sdat;
241         }
242         return true;
243 }
244
245 bool MetaScene::scene_loaded(Scene *newscn)
246 {
247         SceneData *sdat = (SceneData*)scndata[newscn];
248         if(!sdat) {
249                 error_log("MetaScene::scene_loaded called, but corresponding SceneData not found\n");
250                 return false;
251         }
252
253         // extract the walk mesh if necessary
254         Scene *wscn;
255         if(!sdat->walkmesh_regexp.empty() && (wscn = newscn->extract_nodes(sdat->walkmesh_regexp.c_str()))) {
256                 // apply all transformations to the meshes
257                 wscn->apply_xform();
258
259                 int nmeshes = wscn->meshes.size();
260                 for(int i=0; i<nmeshes; i++) {
261                         Mesh *m = wscn->meshes[i];
262
263                         if(walk_mesh) {
264                                 walk_mesh->append(*m);
265                         } else {
266                                 walk_mesh = m;
267                                 wscn->remove_mesh(m);   // to save it from destruction
268                         }
269                 }
270
271                 delete wscn;
272         }
273
274         // extract the spawn node
275         if(!sdat->spawn_regexp.empty() && (wscn = newscn->extract_nodes(sdat->spawn_regexp.c_str()))) {
276
277                 int nmeshes = wscn->meshes.size();
278                 int nnodes = wscn->nodes ? wscn->nodes->get_num_children() : 0;
279
280                 if(nmeshes) {
281                         Vec3 pos;
282                         for(int i=0; i<nmeshes; i++) {
283                                 const Sphere &bsph = wscn->meshes[i]->get_bsphere();
284                                 pos += bsph.center;
285                         }
286                         pos /= (float)nmeshes;
287                         sdat->meta->start_pos = pos;
288
289                 } else if(nnodes) {
290                         // just use the first one
291                         SceneNode *first = wscn->nodes->get_child(0);
292                         sdat->meta->start_pos = first->get_position();
293                         sdat->meta->start_rot = first->get_rotation();
294                 }
295                 delete wscn;
296         }
297
298         int num_medits = sdat->mtledit.size();
299         for(int i=0; i<num_medits; i++) {
300                 // perform material edits
301                 apply_mtledit(newscn, sdat->mtledit[i]);
302         }
303
304         scenes.push_back(newscn);
305         return true;
306 }
307
308 static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node)
309 {
310         if(strcmp(node->name, "mtledit") != 0) {
311                 return false;
312         }
313
314         const char *restr = ".*";
315         struct ts_attr *amtl = ts_get_attr(node, "material");
316         if(amtl && amtl->val.type == TS_STRING) {
317                 restr = amtl->val.str;
318         }
319
320         med->name_re = std::regex(restr);
321
322         node = node->child_list;
323         while(node) {
324                 struct ts_node *cn = node;
325                 node = node->next;
326
327                 if(strcmp(cn->name, "texture") == 0) {
328                         // add/change/remove a texture
329                         struct ts_attr *atype = ts_get_attr(cn, "type");
330                         struct ts_attr *afile = ts_get_attr(cn, "file");
331
332                         int textype = MTL_TEX_DIFFUSE;
333                         if(atype) {
334                                 if(atype->val.type == TS_STRING) {
335                                         textype = mtl_parse_type(atype->val.str);
336                                 } else if(atype->val.type == TS_NUMBER) {
337                                         textype = atype->val.inum;
338                                         if(textype < 0 || textype >= NUM_MTL_TEXTURES) {
339                                                 error_log("invalid texture in mtledit: %d\n", textype);
340                                                 continue;
341                                         }
342                                 } else {
343                                         error_log("unexpected texture type in mtledit: %s\n", atype->val.str);
344                                         continue;
345                                 }
346                         }
347
348                         med->attr = textype;
349
350                         if(!afile || !afile->val.str || !*afile->val.str) {
351                                 // remove
352                                 med->tex = 0;
353                         } else {
354                                 med->tex = texman.get_texture(afile->val.str, TEX_2D, &mscn->datamap);
355                         }
356                 }
357                 // TODO add more edit modes
358         }
359
360         return true;
361 }
362
363 static void apply_mtledit(Scene *scn, const MaterialEdit &med)
364 {
365         // search all the objects to find matching material names
366         int nobj = scn->objects.size();
367         for(int i=0; i<nobj; i++) {
368                 Object *obj = scn->objects[i];
369                 if(std::regex_match(obj->mtl.name, med.name_re)) {
370                         apply_mtledit(&obj->mtl, med);
371                 }
372         }
373 }
374
375 static bool proc_music(MetaScene *mscn, struct ts_node *node)
376 {
377         const char *fname = ts_get_attr_str(node, "file");
378         if(fname) {
379                 SceneData *sdat = new SceneData;
380                 sdat->meta = mscn;
381
382                 // datapath
383                 struct ts_attr *adpath = attr_inscope(node, "datapath");
384                 if(adpath && adpath->val.type == TS_STRING) {
385                         mscn->datamap.set_path(adpath->val.str);
386                 }
387
388                 int namesz = mscn->datamap.lookup(fname, 0, 0);
389                 char *namebuf = (char*)alloca(namesz + 1);
390                 if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
391                         fname = namebuf;
392                 }
393
394                 OggVorbisStream *ovstream = new OggVorbisStream;
395                 if(!ovstream->open(fname)) {
396                         delete ovstream;
397                         return false;
398                 }
399
400                 delete mscn->music;
401                 mscn->music = ovstream;
402         }
403         return true;
404 }
405
406 static void apply_mtledit(Material *mtl, const MaterialEdit &med)
407 {
408         // TODO more edit modes...
409         if(med.tex) {
410                 mtl->add_texture(med.tex, med.attr);
411         } else {
412                 Texture *tex = mtl->stdtex[med.attr];
413                 if(tex) {
414                         mtl->remove_texture(tex);
415                 }
416         }
417 }
418
419 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name)
420 {
421         struct ts_attr *attr = 0;
422
423         while(node && !(attr = ts_get_attr(node, name))) {
424                 node = node->parent;
425         }
426         return attr;
427 }
428
429 static void print_scene_graph(SceneNode *n, int level)
430 {
431         if(!n) return;
432
433         for(int i=0; i<level; i++) {
434                 info_log("  ");
435         }
436
437         int nobj = n->get_num_objects();
438         if(nobj) {
439                 info_log("%s - %d obj\n", n->get_name(), n->get_num_objects());
440         } else {
441                 info_log("%s\n", n->get_name());
442         }
443
444         for(int i=0; i<n->get_num_children(); i++) {
445                 print_scene_graph(n->get_child(i), level + 1);
446         }
447 }