adding mirror plane options other than auto
[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                                 FlatMirror *mir = new FlatMirror;
227                                 mir->reflect = obj->mtl.reflect;
228
229                                 if(obj->mtl.flat_mirror == MTL_MIRROR_AUTO) {
230                                         // assume the object is actually flat, so grab the first triangle and make a plane
231                                         Triangle face = Triangle(0, (const Vec3*)mesh->get_attrib_data(MESH_ATTR_VERTEX),
232                                                         mesh->get_index_data());
233                                         face.calc_normal();
234
235                                         mir->plane.pt = face.v[0];
236                                         mir->plane.normal = face.normal;
237                                 } else {
238                                 }
239
240                                 // check to see if we have found this mirror plane already
241                                 bool found = false;
242                                 FlatMirror *node = mirrors;
243                                 while(node) {
244                                         if(1.0f - dot(mir->plane.normal, node->plane.normal) < 1e-4f &&
245                                                         fabs(dot(mir->plane.normal, normalize(mir->plane.pt - node->plane.pt))) < 1e-4) {
246                                                 found = true;
247                                                 break;
248                                         }
249                                         node = node->next;
250                                 }
251
252                                 if(!found) {
253                                         mir->next = mirrors;
254                                         mirrors = mir;
255
256                                         objmirror[obj] = mir;   // associate with object
257                                         ++num_mirrors;
258                                 } else {
259                                         delete mir;
260                                 }
261                         }
262                 }
263         }
264
265         return num_mirrors;
266 }
267
268 static bool proc_node(MetaScene *mscn, struct ts_node *node)
269 {
270         struct ts_node *c = node->child_list;
271         while(c) {
272                 if(!proc_node(mscn, c)) {
273                         return false;
274                 }
275                 c = c->next;
276         }
277
278         // do this last to allow other contents of the node to do their thing
279         if(strcmp(node->name, "scenefile") == 0) {
280                 return proc_scenefile(mscn, node);
281
282         } else if(strcmp(node->name, "remap") == 0) {
283                 const char *match = ts_get_attr_str(node, "match");
284                 const char *replace = ts_get_attr_str(node, "replace");
285                 if(match && replace) {
286                         mscn->datamap.map(match, replace);
287                 }
288
289         } else if(strcmp(node->name, "music") == 0) {
290                 return proc_music(mscn, node);
291         }
292
293         return true;
294 }
295
296
297
298 struct SceneData {
299         MetaScene *meta;
300         std::string walkmesh_regexp, spawn_regexp;
301         std::vector<MaterialEdit> mtledit;
302 };
303
304 /*! Processes a `scenefile` node. And kicks off scene loading (if necessary) by
305  * calling `SceneSet::get`.
306  */
307 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node)
308 {
309         const char *fname = ts_get_attr_str(node, "file");
310         if(fname) {
311                 SceneData *sdat = new SceneData;
312                 sdat->meta = mscn;
313
314                 // datapath
315                 struct ts_attr *adpath = attr_inscope(node, "datapath");
316                 if(adpath && adpath->val.type == TS_STRING) {
317                         mscn->datamap.set_path(adpath->val.str);
318                 }
319
320                 // strip path
321                 struct ts_attr *aspath = attr_inscope(node, "strip_path");
322                 if(aspath && aspath->val.type == TS_NUMBER) {
323                         mscn->datamap.set_strip(aspath->val.inum);
324                 }
325
326                 // walkmesh
327                 struct ts_attr *awmesh = attr_inscope(node, "walkmesh");
328                 if(awmesh && awmesh->val.type == TS_STRING) {
329                         sdat->walkmesh_regexp = std::string(awmesh->val.str);
330                 }
331
332                 // spawn node
333                 struct ts_attr *awspawn = attr_inscope(node, "spawn");
334                 if(awspawn) {
335                         switch(awspawn->val.type) {
336                         case TS_VECTOR:
337                                 mscn->start_pos = Vec3(awspawn->val.vec[0], awspawn->val.vec[1],
338                                                 awspawn->val.vec[2]);
339                                 break;
340
341                         case TS_STRING:
342                         default:
343                                 sdat->spawn_regexp = std::string(awspawn->val.str);
344                         }
345                 }
346                 if((awspawn = attr_inscope(node, "spawn_rot")) && awspawn->val.type == TS_VECTOR) {
347                         Quat rot;
348                         rot.rotate(Vec3(1, 0, 0), deg_to_rad(awspawn->val.vec[0]));
349                         rot.rotate(Vec3(0, 1, 0), deg_to_rad(awspawn->val.vec[1]));
350                         rot.rotate(Vec3(0, 0, 1), deg_to_rad(awspawn->val.vec[2]));
351                         mscn->start_rot = rot;
352                 }
353
354                 int namesz = mscn->datamap.lookup(fname, 0, 0);
355                 char *namebuf = (char*)alloca(namesz + 1);
356                 if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
357                         fname = namebuf;
358                 }
359
360                 // material edits are kept in a list to be applied when the scene has been loaded
361                 struct ts_node *child = node->child_list;
362                 while(child) {
363                         MaterialEdit medit;
364                         if(proc_mtledit(mscn, &medit, child)) {
365                                 sdat->mtledit.push_back(medit);
366                         }
367                         child = child->next;
368                 }
369
370                 Scene *newscn = sceneman.get(fname);
371                 /* NOTE: setting all these after get() is not a race condition, because
372                  * scene_loaded() which uses this, will only run in our main loop during
373                  * SceneSet::update() on the main thread.
374                  */
375                 newscn->datamap = mscn->datamap;
376                 mscn->datamap.clear();
377
378                 newscn->metascn = mscn;
379                 mscn->scndata[newscn] = sdat;
380         }
381         return true;
382 }
383
384 bool MetaScene::scene_loaded(Scene *newscn)
385 {
386         SceneData *sdat = (SceneData*)scndata[newscn];
387         if(!sdat) {
388                 error_log("MetaScene::scene_loaded called, but corresponding SceneData not found\n");
389                 return false;
390         }
391
392         // extract the walk mesh if necessary
393         Scene *wscn;
394         if(!sdat->walkmesh_regexp.empty() && (wscn = newscn->extract_nodes(sdat->walkmesh_regexp.c_str()))) {
395                 // apply all transformations to the meshes
396                 wscn->apply_xform();
397
398                 int nmeshes = wscn->meshes.size();
399                 for(int i=0; i<nmeshes; i++) {
400                         Mesh *m = wscn->meshes[i];
401
402                         if(walk_mesh) {
403                                 walk_mesh->append(*m);
404                         } else {
405                                 walk_mesh = m;
406                                 wscn->remove_mesh(m);   // to save it from destruction
407                         }
408                 }
409
410                 delete wscn;
411         }
412
413         // extract the spawn node
414         if(!sdat->spawn_regexp.empty() && (wscn = newscn->extract_nodes(sdat->spawn_regexp.c_str()))) {
415
416                 int nmeshes = wscn->meshes.size();
417                 int nnodes = wscn->nodes ? wscn->nodes->get_num_children() : 0;
418
419                 if(nmeshes) {
420                         Vec3 pos;
421                         for(int i=0; i<nmeshes; i++) {
422                                 const Sphere &bsph = wscn->meshes[i]->get_bsphere();
423                                 pos += bsph.center;
424                         }
425                         pos /= (float)nmeshes;
426                         sdat->meta->start_pos = pos;
427
428                 } else if(nnodes) {
429                         // just use the first one
430                         SceneNode *first = wscn->nodes->get_child(0);
431                         sdat->meta->start_pos = first->get_position();
432                         sdat->meta->start_rot = first->get_rotation();
433                 }
434                 delete wscn;
435         }
436
437         int num_medits = sdat->mtledit.size();
438         for(int i=0; i<num_medits; i++) {
439                 // perform material edits
440                 apply_mtledit(newscn, sdat->mtledit[i]);
441         }
442
443         scenes.push_back(newscn);
444         return true;
445 }
446
447 static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node)
448 {
449         if(strcmp(node->name, "mtledit") != 0) {
450                 return false;
451         }
452
453         const char *restr = ".*";
454         struct ts_attr *amtl = ts_get_attr(node, "material");
455         if(amtl && amtl->val.type == TS_STRING) {
456                 restr = amtl->val.str;
457         }
458         med->name_re = std::regex(restr);
459
460         node = node->child_list;
461         while(node) {
462                 struct ts_node *cn = node;
463                 node = node->next;
464
465                 if(strcmp(cn->name, "texture") == 0) {
466                         // add/change/remove a texture
467                         struct ts_attr *atype = ts_get_attr(cn, "type");
468                         struct ts_attr *afile = ts_get_attr(cn, "file");
469
470                         int textype = MTL_TEX_DIFFUSE;
471                         if(atype) {
472                                 if(atype->val.type == TS_STRING) {
473                                         textype = mtl_parse_type(atype->val.str);
474                                 } else if(atype->val.type == TS_NUMBER) {
475                                         textype = atype->val.inum;
476                                         if(textype < 0 || textype >= NUM_MTL_TEXTURES) {
477                                                 error_log("invalid texture in mtledit: %d\n", textype);
478                                                 continue;
479                                         }
480                                 } else {
481                                         error_log("unexpected texture type in mtledit: %s\n", atype->val.str);
482                                         continue;
483                                 }
484                         }
485
486                         med->tex.attr = textype;
487
488                         if(!afile || !afile->val.str || !*afile->val.str) {
489                                 // remove
490                                 med->tex.tex = 0;
491                         } else {
492                                 med->tex.tex = texman.get_texture(afile->val.str, TEX_2D, &mscn->datamap);
493                         }
494
495                         med->type = MTL_EDIT_TEXTURE;
496                         break;
497                 }
498
499                 if(strcmp(cn->name, "mirror") == 0) {
500                         // make this object a flat mirror (hopefully the object is flat otherwise this won't work)
501                         float refl = 1.0f;
502
503                         struct ts_attr *arefl = ts_get_attr(cn, "reflect");
504                         if(arefl) {
505                                 if(arefl->val.type == TS_NUMBER) {
506                                         refl = arefl->val.fnum;
507                                 } else {
508                                         error_log("invalid reflect attribute in mirror mtledit: %s\n", arefl->val.str);
509                                         continue;
510                                 }
511                         }
512
513                         med->type = MTL_EDIT_MIRROR;
514                         med->mirror.reflectivity = refl;
515                         break;
516                 }
517         }
518
519         return true;
520 }
521
522 static void apply_mtledit(Scene *scn, const MaterialEdit &med)
523 {
524         // search all the objects to find matching material names
525         int nobj = scn->objects.size();
526         for(int i=0; i<nobj; i++) {
527                 Object *obj = scn->objects[i];
528                 if(std::regex_match(obj->mtl.name, med.name_re)) {
529                         apply_mtledit(&obj->mtl, med);
530                 }
531         }
532 }
533
534 static bool proc_music(MetaScene *mscn, struct ts_node *node)
535 {
536         const char *fname = ts_get_attr_str(node, "file");
537         if(fname) {
538                 SceneData *sdat = new SceneData;
539                 sdat->meta = mscn;
540
541                 // datapath
542                 struct ts_attr *adpath = attr_inscope(node, "datapath");
543                 if(adpath && adpath->val.type == TS_STRING) {
544                         mscn->datamap.set_path(adpath->val.str);
545                 }
546
547                 int namesz = mscn->datamap.lookup(fname, 0, 0);
548                 char *namebuf = (char*)alloca(namesz + 1);
549                 if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
550                         fname = namebuf;
551                 }
552
553                 OggVorbisStream *ovstream = new OggVorbisStream;
554                 if(!ovstream->open(fname)) {
555                         delete ovstream;
556                         return false;
557                 }
558
559                 delete mscn->music;
560                 mscn->music = ovstream;
561         }
562         return true;
563 }
564
565 static void apply_mtledit(Material *mtl, const MaterialEdit &med)
566 {
567         // TODO more edit modes...
568         switch(med.type) {
569         case MTL_EDIT_TEXTURE:
570                 if(med.tex.tex) {
571                         mtl->add_texture(med.tex.tex, med.tex.attr);
572                 } else {
573                         Texture *tex = mtl->stdtex[med.tex.attr];
574                         if(tex) {
575                                 mtl->remove_texture(tex);
576                         }
577                 }
578                 break;
579
580         case MTL_EDIT_MIRROR:
581                 mtl->flat_mirror = med.mirror.reflectivity > 1e-6;
582                 mtl->reflect = med.mirror.reflectivity;
583                 break;
584         }
585 }
586
587 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name)
588 {
589         struct ts_attr *attr = 0;
590
591         while(node && !(attr = ts_get_attr(node, name))) {
592                 node = node->parent;
593         }
594         return attr;
595 }
596
597 static void print_scene_graph(SceneNode *n, int level)
598 {
599         if(!n) return;
600
601         for(int i=0; i<level; i++) {
602                 info_log("  ");
603         }
604
605         int nobj = n->get_num_objects();
606         if(nobj) {
607                 info_log("%s - %d obj\n", n->get_name(), n->get_num_objects());
608         } else {
609                 info_log("%s\n", n->get_name());
610         }
611
612         for(int i=0; i<n->get_num_children(); i++) {
613                 print_scene_graph(n->get_child(i), level + 1);
614         }
615 }