2 * \brief Metascene implementation
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).
14 #include "metascene.h"
16 #include "treestore.h"
21 #if defined(WIN32) || defined(__WIN32__)
27 enum MaterialEditType {
32 struct MaterialEditTexture {
37 struct MaterialEditMirror {
43 MaterialEditType type;
45 MaterialEditTexture tex;
46 MaterialEditMirror mirror;
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);
57 static void print_scene_graph(SceneNode *n, int level);
60 MetaScene::MetaScene()
66 MetaScene::~MetaScene()
72 bool MetaScene::load(const char *fname)
74 struct ts_node *root = ts_load(fname);
75 if(!root || strcmp(root->name, "scene") != 0) {
77 error_log("failed to load scene metadata: %s\n", fname);
81 bool res = proc_node(this, root);
84 /*info_log("loaded scene: %s\n", fname);
85 info_log("scene graph:\n");
86 print_scene_graph(scn->nodes, 0);
91 void MetaScene::update(float dt)
94 static char text[256];
96 ImGui::Begin("MetaScene nodes", 0, 0);
101 float x = ImGui::GetColumnOffset(1);
102 ImGui::SetColumnOffset(1, x * 1.7);
107 int nscn = scenes.size();
108 for(int i=0; i<nscn; i++) {
111 if(scenes[i]->name.empty()) {
112 sprintf(text, "scene %3d", i);
114 sprintf(text, "scene %3d: %s", i, scenes[i]->name.c_str());
116 expanded = parent_expanded = ImGui::TreeNode(text);
121 scenes[i]->update(dt);
123 if(debug_gui && expanded) {
134 // XXX not used, renderer draws
135 void MetaScene::draw() const
137 int nscn = scenes.size();
138 for(int i=0; i<nscn; i++) {
143 SceneNode *MetaScene::find_node(const char *name) const
145 int num = scenes.size();
146 for(int i=0; i<num; i++) {
147 SceneNode *n = scenes[i]->find_node(name);
153 SceneNode *MetaScene::match_node(const char *qstr) const
155 int num = scenes.size();
156 for(int i=0; i<num; i++) {
157 SceneNode *n = scenes[i]->match_node(qstr);
163 std::list<SceneNode*> MetaScene::match_nodes(const char *qstr) const
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);
170 res.splice(res.end(), tmp);
173 return std::move(res);
176 Scene *MetaScene::extract_nodes(const char *qstr)
179 int nscn = scenes.size();
180 for(int i=0; i<nscn; i++) {
181 Scene *tmp = scenes[i]->extract_nodes(qstr);
194 static bool proc_node(MetaScene *mscn, struct ts_node *node)
196 struct ts_node *c = node->child_list;
198 if(!proc_node(mscn, c)) {
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);
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);
215 } else if(strcmp(node->name, "music") == 0) {
216 return proc_music(mscn, node);
226 std::string walkmesh_regexp, spawn_regexp;
227 std::vector<MaterialEdit> mtledit;
230 /*! Processes a `scenefile` node. And kicks off scene loading (if necessary) by
231 * calling `SceneSet::get`.
233 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node)
235 const char *fname = ts_get_attr_str(node, "file");
237 SceneData *sdat = new SceneData;
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);
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);
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);
259 struct ts_attr *awspawn = attr_inscope(node, "spawn");
261 switch(awspawn->val.type) {
263 mscn->start_pos = Vec3(awspawn->val.vec[0], awspawn->val.vec[1],
264 awspawn->val.vec[2]);
269 sdat->spawn_regexp = std::string(awspawn->val.str);
272 if((awspawn = attr_inscope(node, "spawn_rot")) && awspawn->val.type == TS_VECTOR) {
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;
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)) {
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;
290 if(proc_mtledit(mscn, &medit, child)) {
291 sdat->mtledit.push_back(medit);
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.
301 newscn->datamap = mscn->datamap;
302 mscn->datamap.clear();
304 newscn->metascn = mscn;
305 mscn->scndata[newscn] = sdat;
310 bool MetaScene::scene_loaded(Scene *newscn)
312 SceneData *sdat = (SceneData*)scndata[newscn];
314 error_log("MetaScene::scene_loaded called, but corresponding SceneData not found\n");
318 // extract the walk mesh if necessary
320 if(!sdat->walkmesh_regexp.empty() && (wscn = newscn->extract_nodes(sdat->walkmesh_regexp.c_str()))) {
321 // apply all transformations to the meshes
324 int nmeshes = wscn->meshes.size();
325 for(int i=0; i<nmeshes; i++) {
326 Mesh *m = wscn->meshes[i];
329 walk_mesh->append(*m);
332 wscn->remove_mesh(m); // to save it from destruction
339 // extract the spawn node
340 if(!sdat->spawn_regexp.empty() && (wscn = newscn->extract_nodes(sdat->spawn_regexp.c_str()))) {
342 int nmeshes = wscn->meshes.size();
343 int nnodes = wscn->nodes ? wscn->nodes->get_num_children() : 0;
347 for(int i=0; i<nmeshes; i++) {
348 const Sphere &bsph = wscn->meshes[i]->get_bsphere();
351 pos /= (float)nmeshes;
352 sdat->meta->start_pos = pos;
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();
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]);
369 scenes.push_back(newscn);
373 static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node)
375 if(strcmp(node->name, "mtledit") != 0) {
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;
384 med->name_re = std::regex(restr);
386 node = node->child_list;
388 struct ts_node *cn = node;
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");
396 int textype = MTL_TEX_DIFFUSE;
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);
407 error_log("unexpected texture type in mtledit: %s\n", atype->val.str);
412 med->tex.attr = textype;
414 if(!afile || !afile->val.str || !*afile->val.str) {
418 med->tex.tex = texman.get_texture(afile->val.str, TEX_2D, &mscn->datamap);
421 med->type = MTL_EDIT_TEXTURE;
425 if(strcmp(cn->name, "mirror") == 0) {
426 // make this object a flat mirror (hopefully the object is flat otherwise this won't work)
429 struct ts_attr *arefl = ts_get_attr(cn, "reflect");
431 if(arefl->val.type == TS_NUMBER) {
432 refl = arefl->val.fnum;
434 error_log("invalid reflect attribute in mirror mtledit: %s\n", arefl->val.str);
439 med->type = MTL_EDIT_MIRROR;
440 med->mirror.reflectivity = refl;
448 static void apply_mtledit(Scene *scn, const MaterialEdit &med)
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);
460 static bool proc_music(MetaScene *mscn, struct ts_node *node)
462 const char *fname = ts_get_attr_str(node, "file");
464 SceneData *sdat = new SceneData;
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);
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)) {
479 OggVorbisStream *ovstream = new OggVorbisStream;
480 if(!ovstream->open(fname)) {
486 mscn->music = ovstream;
491 static void apply_mtledit(Material *mtl, const MaterialEdit &med)
493 // TODO more edit modes...
495 case MTL_EDIT_TEXTURE:
497 mtl->add_texture(med.tex.tex, med.tex.attr);
499 Texture *tex = mtl->stdtex[med.tex.attr];
501 mtl->remove_texture(tex);
506 case MTL_EDIT_MIRROR:
507 mtl->flat_mirror = med.mirror.reflectivity > 1e-6;
508 mtl->reflect = med.mirror.reflectivity;
513 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name)
515 struct ts_attr *attr = 0;
517 while(node && !(attr = ts_get_attr(node, name))) {
523 static void print_scene_graph(SceneNode *n, int level)
527 for(int i=0; i<level; i++) {
531 int nobj = n->get_num_objects();
533 info_log("%s - %d obj\n", n->get_name(), n->get_num_objects());
535 info_log("%s\n", n->get_name());
538 for(int i=0; i<n->get_num_children(); i++) {
539 print_scene_graph(n->get_child(i), level + 1);