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