added delayed init call after scenegraph/meshes are done loading
[laserbrain_demo] / src / metascene.cc
1 /*! \file
2  * \brief Metascene implementation
3  *
4  * Loading starts at `MetaScene::load`, which calls `ts_load` (libtreestore)
5  * to load the metascene description tree into memory. Then `proc_node` is
6  * called at the root to recursively process the tree. `scenefile` nodes are
7  * handed over to `proc_scenefile`, which will trigger scene loading for any
8  * nodes with a `file` attribute. Scene loading is handled by requesting the
9  * filename from the scene resource manager, which is of type [SceneSet](\ref SceneSet).
10  */
11 #include <assert.h>
12 #include <string>
13 #include <regex>
14 #include "metascene.h"
15 #include "scene.h"
16 #include "objmesh.h"
17 #include "treestore.h"
18 #include "logger.h"
19 #include "app.h"
20 #include "dbg_gui.h"
21
22 #if defined(WIN32) || defined(__WIN32__)
23 #include <malloc.h>
24 #else
25 #include <alloca.h>
26 #endif
27
28 enum MaterialEditType {
29         MTL_EDIT_TEXTURE,
30         MTL_EDIT_MIRROR
31 };
32
33 struct MaterialEditTexture {
34         int attr;
35         Texture *tex;
36 };
37
38 struct MaterialEditMirror {
39         float reflectivity;
40 };
41
42 struct MaterialEdit {
43         std::regex name_re;
44         MaterialEditType type;
45
46         MaterialEditTexture tex;
47         MaterialEditMirror mirror;
48 };
49
50 static bool proc_node(MetaScene *mscn, struct ts_node *node);
51 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node);
52 static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node);
53 static bool proc_music(MetaScene *mscn, struct ts_node *node);
54 static void apply_mtledit(Scene *scn, const MaterialEdit &med);
55 static void apply_mtledit(Material *mtl, const MaterialEdit &med);
56 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name);
57
58 static void print_scene_graph(SceneNode *n, int level);
59
60
61 MetaScene::MetaScene()
62 {
63         walk_mesh = 0;
64         music = 0;
65         mirrors = 0;
66 }
67
68 MetaScene::~MetaScene()
69 {
70         delete walk_mesh;
71         delete music;
72
73         while(mirrors) {
74                 FlatMirror *m = mirrors;
75                 mirrors = mirrors->next;
76                 delete m;
77         }
78 }
79
80 bool MetaScene::load(const char *fname)
81 {
82         struct ts_node *root = ts_load(fname);
83         if(!root || strcmp(root->name, "scene") != 0) {
84                 ts_free_tree(root);
85                 error_log("failed to load scene metadata: %s\n", fname);
86                 return false;
87         }
88
89         bool res = proc_node(this, root);
90         ts_free_tree(root);
91
92         /*info_log("loaded scene: %s\n", fname);
93         info_log("scene graph:\n");
94         print_scene_graph(scn->nodes, 0);
95         */
96         return res;
97 }
98
99 void MetaScene::update(float dt)
100 {
101         bool expanded;
102         static char text[256];
103         if(debug_gui) {
104                 ImGui::Begin("MetaScene nodes", 0, 0);
105                 ImGui::Columns(2);
106
107                 static bool once;
108                 if(!once) {
109                         float x = ImGui::GetColumnOffset(1);
110                         ImGui::SetColumnOffset(1, x * 1.7);
111                         once = true;
112                 }
113         }
114
115         int nscn = scenes.size();
116         for(int i=0; i<nscn; i++) {
117
118                 if(debug_gui) {
119                         if(scenes[i]->name.empty()) {
120                                 sprintf(text, "scene %3d", i);
121                         } else {
122                                 sprintf(text, "scene %3d: %s", i, scenes[i]->name.c_str());
123                         }
124                         expanded = parent_expanded = ImGui::TreeNode(text);
125                         ImGui::NextColumn();
126                         ImGui::NextColumn();
127                 }
128
129                 scenes[i]->update(dt);
130
131                 if(debug_gui && expanded) {
132                         ImGui::TreePop();
133                 }
134         }
135
136         if(debug_gui) {
137                 ImGui::Columns(1);
138                 ImGui::End();
139         }
140 }
141
142 // XXX not used, renderer draws
143 void MetaScene::draw() const
144 {
145         error_log("Don't call MetaScene::draw, use the Renderer\n");
146         int nscn = scenes.size();
147         for(int i=0; i<nscn; i++) {
148                 scenes[i]->draw();
149         }
150 }
151
152 SceneNode *MetaScene::find_node(const char *name) const
153 {
154         int num = scenes.size();
155         for(int i=0; i<num; i++) {
156                 SceneNode *n = scenes[i]->find_node(name);
157                 if(n) return n;
158         }
159         return 0;
160 }
161
162 SceneNode *MetaScene::match_node(const char *qstr) const
163 {
164         int num = scenes.size();
165         for(int i=0; i<num; i++) {
166                 SceneNode *n = scenes[i]->match_node(qstr);
167                 if(n) return n;
168         }
169         return 0;
170 }
171
172 std::list<SceneNode*> MetaScene::match_nodes(const char *qstr) const
173 {
174         std::list<SceneNode*> res;
175         int num = scenes.size();
176         for(int i=0; i<num; i++) {
177                 std::list<SceneNode*> tmp = scenes[i]->match_nodes(qstr);
178                 if(!tmp.empty()) {
179                         res.splice(res.end(), tmp);
180                 }
181         }
182         return std::move(res);
183 }
184
185 Scene *MetaScene::extract_nodes(const char *qstr)
186 {
187         Scene *scn = 0;
188         int nscn = scenes.size();
189         for(int i=0; i<nscn; i++) {
190                 Scene *tmp = scenes[i]->extract_nodes(qstr);
191                 if(tmp) {
192                         if(!scn) {
193                                 scn = tmp;
194                         } else {
195                                 scn->merge(tmp);
196                                 delete tmp;
197                         }
198                 }
199         }
200         return scn;
201 }
202
203 int MetaScene::calc_mirror_planes()
204 {
205         int num_mirrors = 0;
206         while(mirrors) {
207                 FlatMirror *m = mirrors;
208                 mirrors = mirrors->next;
209                 delete m;
210         }
211         mirrors = 0;
212         objmirror.clear();
213
214         int numscn = scenes.size();
215         for(int i=0; i<numscn; i++) {
216                 Scene *scn = scenes[i];
217
218                 int numobj = scn->objects.size();
219                 for(int j=0; j<numobj; j++) {
220                         Object *obj = scn->objects[j];
221
222                         if(obj->mtl.flat_mirror && obj->get_type() == OBJ_MESH) {
223                                 const Mesh *mesh = ((ObjMesh*)obj)->mesh;
224                                 if(!mesh) continue;
225
226                                 // assume the object is actually flat, so grab the first triangle and make a plane
227                                 Triangle face = Triangle(0, (const Vec3*)mesh->get_attrib_data(MESH_ATTR_VERTEX),
228                                                 mesh->get_index_data());
229                                 face.calc_normal();
230
231                                 FlatMirror *mir = new FlatMirror;
232                                 mir->plane.pt = face.v[0];
233                                 mir->plane.normal = face.normal;
234                                 mir->reflect = obj->mtl.reflect;
235
236                                 // check to see if we have found this mirror plane already
237                                 bool found = false;
238                                 FlatMirror *node = mirrors;
239                                 while(node) {
240                                         if(fabs(dot(mir->plane.normal, node->plane.normal)) < 1e-4 &&
241                                                         fabs(dot(mir->plane.normal, normalize(mir->plane.pt - node->plane.pt))) < 1e-4) {
242                                                 found = true;
243                                                 break;
244                                         }
245                                 }
246
247                                 if(!found) {
248                                         mir->next = mirrors;
249                                         mirrors = mir;
250
251                                         objmirror[obj] = mir;   // associate with object
252                                         ++num_mirrors;
253                                 } else {
254                                         delete mir;
255                                 }
256                         }
257                 }
258         }
259
260         return num_mirrors;
261 }
262
263 static bool proc_node(MetaScene *mscn, struct ts_node *node)
264 {
265         struct ts_node *c = node->child_list;
266         while(c) {
267                 if(!proc_node(mscn, c)) {
268                         return false;
269                 }
270                 c = c->next;
271         }
272
273         // do this last to allow other contents of the node to do their thing
274         if(strcmp(node->name, "scenefile") == 0) {
275                 return proc_scenefile(mscn, node);
276
277         } else if(strcmp(node->name, "remap") == 0) {
278                 const char *match = ts_get_attr_str(node, "match");
279                 const char *replace = ts_get_attr_str(node, "replace");
280                 if(match && replace) {
281                         mscn->datamap.map(match, replace);
282                 }
283
284         } else if(strcmp(node->name, "music") == 0) {
285                 return proc_music(mscn, node);
286         }
287
288         return true;
289 }
290
291
292
293 struct SceneData {
294         MetaScene *meta;
295         std::string walkmesh_regexp, spawn_regexp;
296         std::vector<MaterialEdit> mtledit;
297 };
298
299 /*! Processes a `scenefile` node. And kicks off scene loading (if necessary) by
300  * calling `SceneSet::get`.
301  */
302 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node)
303 {
304         const char *fname = ts_get_attr_str(node, "file");
305         if(fname) {
306                 SceneData *sdat = new SceneData;
307                 sdat->meta = mscn;
308
309                 // datapath
310                 struct ts_attr *adpath = attr_inscope(node, "datapath");
311                 if(adpath && adpath->val.type == TS_STRING) {
312                         mscn->datamap.set_path(adpath->val.str);
313                 }
314
315                 // strip path
316                 struct ts_attr *aspath = attr_inscope(node, "strip_path");
317                 if(aspath && aspath->val.type == TS_NUMBER) {
318                         mscn->datamap.set_strip(aspath->val.inum);
319                 }
320
321                 // walkmesh
322                 struct ts_attr *awmesh = attr_inscope(node, "walkmesh");
323                 if(awmesh && awmesh->val.type == TS_STRING) {
324                         sdat->walkmesh_regexp = std::string(awmesh->val.str);
325                 }
326
327                 // spawn node
328                 struct ts_attr *awspawn = attr_inscope(node, "spawn");
329                 if(awspawn) {
330                         switch(awspawn->val.type) {
331                         case TS_VECTOR:
332                                 mscn->start_pos = Vec3(awspawn->val.vec[0], awspawn->val.vec[1],
333                                                 awspawn->val.vec[2]);
334                                 break;
335
336                         case TS_STRING:
337                         default:
338                                 sdat->spawn_regexp = std::string(awspawn->val.str);
339                         }
340                 }
341                 if((awspawn = attr_inscope(node, "spawn_rot")) && awspawn->val.type == TS_VECTOR) {
342                         Quat rot;
343                         rot.rotate(Vec3(1, 0, 0), deg_to_rad(awspawn->val.vec[0]));
344                         rot.rotate(Vec3(0, 1, 0), deg_to_rad(awspawn->val.vec[1]));
345                         rot.rotate(Vec3(0, 0, 1), deg_to_rad(awspawn->val.vec[2]));
346                         mscn->start_rot = rot;
347                 }
348
349                 int namesz = mscn->datamap.lookup(fname, 0, 0);
350                 char *namebuf = (char*)alloca(namesz + 1);
351                 if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
352                         fname = namebuf;
353                 }
354
355                 // material edits are kept in a list to be applied when the scene has been loaded
356                 struct ts_node *child = node->child_list;
357                 while(child) {
358                         MaterialEdit medit;
359                         if(proc_mtledit(mscn, &medit, child)) {
360                                 sdat->mtledit.push_back(medit);
361                         }
362                         child = child->next;
363                 }
364
365                 Scene *newscn = sceneman.get(fname);
366                 /* NOTE: setting all these after get() is not a race condition, because
367                  * scene_loaded() which uses this, will only run in our main loop during
368                  * SceneSet::update() on the main thread.
369                  */
370                 newscn->datamap = mscn->datamap;
371                 mscn->datamap.clear();
372
373                 newscn->metascn = mscn;
374                 mscn->scndata[newscn] = sdat;
375         }
376         return true;
377 }
378
379 bool MetaScene::scene_loaded(Scene *newscn)
380 {
381         SceneData *sdat = (SceneData*)scndata[newscn];
382         if(!sdat) {
383                 error_log("MetaScene::scene_loaded called, but corresponding SceneData not found\n");
384                 return false;
385         }
386
387         // extract the walk mesh if necessary
388         Scene *wscn;
389         if(!sdat->walkmesh_regexp.empty() && (wscn = newscn->extract_nodes(sdat->walkmesh_regexp.c_str()))) {
390                 // apply all transformations to the meshes
391                 wscn->apply_xform();
392
393                 int nmeshes = wscn->meshes.size();
394                 for(int i=0; i<nmeshes; i++) {
395                         Mesh *m = wscn->meshes[i];
396
397                         if(walk_mesh) {
398                                 walk_mesh->append(*m);
399                         } else {
400                                 walk_mesh = m;
401                                 wscn->remove_mesh(m);   // to save it from destruction
402                         }
403                 }
404
405                 delete wscn;
406         }
407
408         // extract the spawn node
409         if(!sdat->spawn_regexp.empty() && (wscn = newscn->extract_nodes(sdat->spawn_regexp.c_str()))) {
410
411                 int nmeshes = wscn->meshes.size();
412                 int nnodes = wscn->nodes ? wscn->nodes->get_num_children() : 0;
413
414                 if(nmeshes) {
415                         Vec3 pos;
416                         for(int i=0; i<nmeshes; i++) {
417                                 const Sphere &bsph = wscn->meshes[i]->get_bsphere();
418                                 pos += bsph.center;
419                         }
420                         pos /= (float)nmeshes;
421                         sdat->meta->start_pos = pos;
422
423                 } else if(nnodes) {
424                         // just use the first one
425                         SceneNode *first = wscn->nodes->get_child(0);
426                         sdat->meta->start_pos = first->get_position();
427                         sdat->meta->start_rot = first->get_rotation();
428                 }
429                 delete wscn;
430         }
431
432         int num_medits = sdat->mtledit.size();
433         for(int i=0; i<num_medits; i++) {
434                 // perform material edits
435                 apply_mtledit(newscn, sdat->mtledit[i]);
436         }
437
438         scenes.push_back(newscn);
439         return true;
440 }
441
442 static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node)
443 {
444         if(strcmp(node->name, "mtledit") != 0) {
445                 return false;
446         }
447
448         const char *restr = ".*";
449         struct ts_attr *amtl = ts_get_attr(node, "material");
450         if(amtl && amtl->val.type == TS_STRING) {
451                 restr = amtl->val.str;
452         }
453         med->name_re = std::regex(restr);
454
455         node = node->child_list;
456         while(node) {
457                 struct ts_node *cn = node;
458                 node = node->next;
459
460                 if(strcmp(cn->name, "texture") == 0) {
461                         // add/change/remove a texture
462                         struct ts_attr *atype = ts_get_attr(cn, "type");
463                         struct ts_attr *afile = ts_get_attr(cn, "file");
464
465                         int textype = MTL_TEX_DIFFUSE;
466                         if(atype) {
467                                 if(atype->val.type == TS_STRING) {
468                                         textype = mtl_parse_type(atype->val.str);
469                                 } else if(atype->val.type == TS_NUMBER) {
470                                         textype = atype->val.inum;
471                                         if(textype < 0 || textype >= NUM_MTL_TEXTURES) {
472                                                 error_log("invalid texture in mtledit: %d\n", textype);
473                                                 continue;
474                                         }
475                                 } else {
476                                         error_log("unexpected texture type in mtledit: %s\n", atype->val.str);
477                                         continue;
478                                 }
479                         }
480
481                         med->tex.attr = textype;
482
483                         if(!afile || !afile->val.str || !*afile->val.str) {
484                                 // remove
485                                 med->tex.tex = 0;
486                         } else {
487                                 med->tex.tex = texman.get_texture(afile->val.str, TEX_2D, &mscn->datamap);
488                         }
489
490                         med->type = MTL_EDIT_TEXTURE;
491                         break;
492                 }
493
494                 if(strcmp(cn->name, "mirror") == 0) {
495                         // make this object a flat mirror (hopefully the object is flat otherwise this won't work)
496                         float refl = 1.0f;
497
498                         struct ts_attr *arefl = ts_get_attr(cn, "reflect");
499                         if(arefl) {
500                                 if(arefl->val.type == TS_NUMBER) {
501                                         refl = arefl->val.fnum;
502                                 } else {
503                                         error_log("invalid reflect attribute in mirror mtledit: %s\n", arefl->val.str);
504                                         continue;
505                                 }
506                         }
507
508                         med->type = MTL_EDIT_MIRROR;
509                         med->mirror.reflectivity = refl;
510                         break;
511                 }
512         }
513
514         return true;
515 }
516
517 static void apply_mtledit(Scene *scn, const MaterialEdit &med)
518 {
519         // search all the objects to find matching material names
520         int nobj = scn->objects.size();
521         for(int i=0; i<nobj; i++) {
522                 Object *obj = scn->objects[i];
523                 if(std::regex_match(obj->mtl.name, med.name_re)) {
524                         apply_mtledit(&obj->mtl, med);
525                 }
526         }
527 }
528
529 static bool proc_music(MetaScene *mscn, struct ts_node *node)
530 {
531         const char *fname = ts_get_attr_str(node, "file");
532         if(fname) {
533                 SceneData *sdat = new SceneData;
534                 sdat->meta = mscn;
535
536                 // datapath
537                 struct ts_attr *adpath = attr_inscope(node, "datapath");
538                 if(adpath && adpath->val.type == TS_STRING) {
539                         mscn->datamap.set_path(adpath->val.str);
540                 }
541
542                 int namesz = mscn->datamap.lookup(fname, 0, 0);
543                 char *namebuf = (char*)alloca(namesz + 1);
544                 if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
545                         fname = namebuf;
546                 }
547
548                 OggVorbisStream *ovstream = new OggVorbisStream;
549                 if(!ovstream->open(fname)) {
550                         delete ovstream;
551                         return false;
552                 }
553
554                 delete mscn->music;
555                 mscn->music = ovstream;
556         }
557         return true;
558 }
559
560 static void apply_mtledit(Material *mtl, const MaterialEdit &med)
561 {
562         // TODO more edit modes...
563         switch(med.type) {
564         case MTL_EDIT_TEXTURE:
565                 if(med.tex.tex) {
566                         mtl->add_texture(med.tex.tex, med.tex.attr);
567                 } else {
568                         Texture *tex = mtl->stdtex[med.tex.attr];
569                         if(tex) {
570                                 mtl->remove_texture(tex);
571                         }
572                 }
573                 break;
574
575         case MTL_EDIT_MIRROR:
576                 mtl->flat_mirror = med.mirror.reflectivity > 1e-6;
577                 mtl->reflect = med.mirror.reflectivity;
578                 break;
579         }
580 }
581
582 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name)
583 {
584         struct ts_attr *attr = 0;
585
586         while(node && !(attr = ts_get_attr(node, name))) {
587                 node = node->parent;
588         }
589         return attr;
590 }
591
592 static void print_scene_graph(SceneNode *n, int level)
593 {
594         if(!n) return;
595
596         for(int i=0; i<level; i++) {
597                 info_log("  ");
598         }
599
600         int nobj = n->get_num_objects();
601         if(nobj) {
602                 info_log("%s - %d obj\n", n->get_name(), n->get_num_objects());
603         } else {
604                 info_log("%s\n", n->get_name());
605         }
606
607         for(int i=0; i<n->get_num_children(); i++) {
608                 print_scene_graph(n->get_child(i), level + 1);
609         }
610 }