background scene file loading introduces race condition with datamaps
[laserbrain_demo] / src / metascene.cc
1 #include <string>
2 #include <regex>
3 #include "metascene.h"
4 #include "scene.h"
5 #include "datamap.h"
6 #include "treestore.h"
7 #include "logger.h"
8
9 #ifdef WIN32
10 #include <malloc.h>
11 #else
12 #include <alloca.h>
13 #endif
14
15 struct MaterialEdit {
16         std::regex name_re;
17         int attr;
18         Texture *tex;
19 };
20
21 static bool proc_node(MetaScene *mscn, struct ts_node *node);
22 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node);
23 static bool proc_mtledit(MaterialEdit *med, struct ts_node *node, TextureSet *texset);
24 static void apply_mtledit(Scene *scn, const MaterialEdit &med);
25 static void apply_mtledit(Material *mtl, const MaterialEdit &med);
26 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name);
27
28 static void print_scene_graph(SceneNode *n, int level);
29
30
31 MetaScene::MetaScene(SceneSet *sman, TextureSet *tman)
32 {
33         sceneman = sman;
34         texman = tman;
35         walk_mesh = 0;
36 }
37
38 MetaScene::~MetaScene()
39 {
40         delete walk_mesh;
41 }
42
43
44 bool MetaScene::load(const char *fname)
45 {
46         struct ts_node *root = ts_load(fname);
47         if(!root || strcmp(root->name, "scene") != 0) {
48                 ts_free_tree(root);
49                 error_log("failed to load scene metadata: %s\n", fname);
50                 return false;
51         }
52
53         bool res = proc_node(this, root);
54         ts_free_tree(root);
55
56         /*info_log("loaded scene: %s\n", fname);
57         info_log("scene graph:\n");
58         print_scene_graph(scn->nodes, 0);
59         */
60         return res;
61 }
62
63 void MetaScene::update(float dt)
64 {
65         int nscn = scenes.size();
66         for(int i=0; i<nscn; i++) {
67                 scenes[i]->update(dt);
68         }
69 }
70
71 void MetaScene::draw() const
72 {
73         int nscn = scenes.size();
74         for(int i=0; i<nscn; i++) {
75                 scenes[i]->draw();
76         }
77 }
78
79 static bool proc_node(MetaScene *mscn, struct ts_node *node)
80 {
81         struct ts_node *c = node->child_list;
82         while(c) {
83                 if(!proc_node(mscn, c)) {
84                         return false;
85                 }
86                 c = c->next;
87         }
88
89         // do this last to allow other contents of the node to do their thing
90         if(strcmp(node->name, "scenefile") == 0) {
91                 return proc_scenefile(mscn, node);
92
93         } else if(strcmp(node->name, "remap") == 0) {
94                 const char *match = ts_get_attr_str(node, "match");
95                 const char *replace = ts_get_attr_str(node, "replace");
96                 if(match && replace) {
97                         datamap_map(match, replace);
98                 }
99         }
100
101         return true;
102 }
103
104
105
106 struct SceneData {
107         std::string walkmesh_regexp;
108         std::vector<MaterialEdit> mtledit;
109 };
110
111 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node)
112 {
113         const char *fname = ts_get_attr_str(node, "file");
114         if(fname) {
115                 SceneData *sdat = new SceneData;
116
117                 // datapath
118                 struct ts_attr *adpath = attr_inscope(node, "datapath");
119                 if(adpath && adpath->val.type == TS_STRING) {
120                         info_log("adding data path: %s\n", adpath->val.str);
121                         datamap_set_path(adpath->val.str);
122                 }
123
124                 // walkmesh
125                 struct ts_attr *awmesh = attr_inscope(node, "walkmesh");
126                 if(awmesh && awmesh->val.type == TS_STRING) {
127                         sdat->walkmesh_regexp = std::string(awmesh->val.str);
128                 }
129
130                 int namesz = datamap_lookup(fname, 0, 0);
131                 char *namebuf = (char*)alloca(namesz + 1);
132                 if(datamap_lookup(fname, namebuf, namesz + 1)) {
133                         fname = namebuf;
134                 }
135
136                 // material edits
137                 struct ts_node *child = node->child_list;
138                 while(child) {
139                         MaterialEdit medit;
140                         if(proc_mtledit(&medit, child, mscn->texman)) {
141                                 sdat->mtledit.push_back(medit);
142                         }
143                         child = child->next;
144                 }
145
146                 Scene *newscn = mscn->sceneman->get(fname);
147                 /* NOTE: setting this after get() is not a race condition, because
148                  * scene_loaded() which uses this, will only run in our main loop during
149                  * SceneSet::update() on the main thread.
150                  */
151                 newscn->metascn = mscn;
152                 mscn->scndata[newscn] = sdat;
153         }
154         return true;
155 }
156
157 bool MetaScene::scene_loaded(Scene *newscn)
158 {
159         SceneData *sdat = (SceneData*)scndata[newscn];
160         if(!sdat) {
161                 error_log("MetaScene::scene_loaded called, but corresponding SceneData not found\n");
162                 return false;
163         }
164
165         // extract the walk mesh if necessary
166         Scene *wscn;
167         if(!sdat->walkmesh_regexp.empty() && (wscn = newscn->extract_nodes(sdat->walkmesh_regexp.c_str()))) {
168                 // apply all transformations to the meshes
169                 wscn->apply_xform();
170
171                 int nmeshes = wscn->meshes.size();
172                 for(int i=0; i<nmeshes; i++) {
173                         Mesh *m = wscn->meshes[i];
174
175                         if(walk_mesh) {
176                                 walk_mesh->append(*m);
177                         } else {
178                                 walk_mesh = m;
179                                 wscn->remove_mesh(m);   // to save it from destruction
180                         }
181                 }
182
183                 delete wscn;
184         }
185
186         int num_medits = sdat->mtledit.size();
187         for(int i=0; i<num_medits; i++) {
188                 // perform material edits
189                 apply_mtledit(newscn, sdat->mtledit[i]);
190         }
191
192         scenes.push_back(newscn);
193         return true;
194 }
195
196 static bool proc_mtledit(MaterialEdit *med, struct ts_node *node, TextureSet *texset)
197 {
198         if(strcmp(node->name, "mtledit") != 0) {
199                 return false;
200         }
201
202         const char *restr = ".*";
203         struct ts_attr *amtl = ts_get_attr(node, "material");
204         if(amtl && amtl->val.type == TS_STRING) {
205                 restr = amtl->val.str;
206         }
207
208         med->name_re = std::regex(restr);
209
210         node = node->child_list;
211         while(node) {
212                 struct ts_node *cn = node;
213                 node = node->next;
214
215                 if(strcmp(cn->name, "texture") == 0) {
216                         // add/change/remove a texture
217                         struct ts_attr *atype = ts_get_attr(cn, "type");
218                         struct ts_attr *afile = ts_get_attr(cn, "file");
219
220                         int textype = MTL_TEX_DIFFUSE;
221                         if(atype) {
222                                 if(atype->val.type == TS_STRING) {
223                                         textype = mtl_parse_type(atype->val.str);
224                                 } else if(atype->val.type == TS_NUMBER) {
225                                         textype = atype->val.inum;
226                                         if(textype < 0 || textype >= NUM_MTL_TEXTURES) {
227                                                 error_log("invalid texture in mtledit: %d\n", textype);
228                                                 continue;
229                                         }
230                                 } else {
231                                         error_log("unexpected texture type in mtledit: %s\n", atype->val.str);
232                                         continue;
233                                 }
234                         }
235
236                         med->attr = textype;
237
238                         if(!afile || !afile->val.str || !*afile->val.str) {
239                                 // remove
240                                 med->tex = 0;
241                         } else {
242                                 med->tex = texset->get_texture(afile->val.str, TEX_2D);
243                         }
244                 }
245                 // TODO add more edit modes
246         }
247
248         return true;
249 }
250
251 static void apply_mtledit(Scene *scn, const MaterialEdit &med)
252 {
253         // search all the objects to find matching material names
254         int nobj = scn->objects.size();
255         for(int i=0; i<nobj; i++) {
256                 Object *obj = scn->objects[i];
257                 if(std::regex_match(obj->get_name(), med.name_re)) {
258                         apply_mtledit(&obj->mtl, med);
259                 }
260         }
261 }
262
263 static void apply_mtledit(Material *mtl, const MaterialEdit &med)
264 {
265         // TODO more edit modes...
266         if(med.tex) {
267                 mtl->add_texture(med.tex, med.attr);
268         } else {
269                 Texture *tex = mtl->stdtex[med.attr];
270                 if(tex) {
271                         mtl->remove_texture(tex);
272                 }
273         }
274 }
275
276 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name)
277 {
278         struct ts_attr *attr = 0;
279
280         while(node && !(attr = ts_get_attr(node, name))) {
281                 node = node->parent;
282         }
283         return attr;
284 }
285
286 static void print_scene_graph(SceneNode *n, int level)
287 {
288         if(!n) return;
289
290         for(int i=0; i<level; i++) {
291                 info_log("  ");
292         }
293
294         int nobj = n->get_num_objects();
295         if(nobj) {
296                 info_log("%s - %d obj\n", n->get_name(), n->get_num_objects());
297         } else {
298                 info_log("%s\n", n->get_name());
299         }
300
301         for(int i=0; i<n->get_num_children(); i++) {
302                 print_scene_graph(n->get_child(i), level + 1);
303         }
304 }