material editing in scene metafile
[laserbrain_demo] / src / metascene.cc
1 #include <regex>
2 #include "metascene.h"
3 #include "scene.h"
4 #include "datamap.h"
5 #include "treestore.h"
6 #include "logger.h"
7
8 #ifdef WIN32
9 #include <malloc.h>
10 #else
11 #include <alloca.h>
12 #endif
13
14 static bool proc_node(Scene *scn, struct ts_node *node);
15 static bool proc_scenefile(Scene *scn, struct ts_node *node);
16 static bool proc_mtledit(Scene *scn, struct ts_node *node);
17 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name);
18
19 static void print_scene_graph(SceneNode *n, int level);
20
21 bool load_scene(Scene *scn, const char *fname)
22 {
23         struct ts_node *root = ts_load(fname);
24         if(!root || strcmp(root->name, "scene") != 0) {
25                 ts_free_tree(root);
26                 error_log("failed to load scene metadata: %s\n", fname);
27                 return false;
28         }
29
30         bool res = proc_node(scn, root);
31         ts_free_tree(root);
32
33         info_log("loaded scene: %s\n", fname);
34         info_log("scene graph:\n");
35         print_scene_graph(scn->nodes, 0);
36         return res;
37 }
38
39 static bool proc_node(Scene *scn, struct ts_node *node)
40 {
41         struct ts_node *c = node->child_list;
42         while(c) {
43                 if(!proc_node(scn, c)) {
44                         return false;
45                 }
46                 c = c->next;
47         }
48
49         // do this last to allow other contents of the node to do their thing
50         if(strcmp(node->name, "scenefile") == 0) {
51                 return proc_scenefile(scn, node);
52
53         } else if(strcmp(node->name, "remap") == 0) {
54                 const char *match = ts_get_attr_str(node, "match");
55                 const char *replace = ts_get_attr_str(node, "replace");
56                 if(match && replace) {
57                         datamap_map(match, replace);
58                 }
59         }
60
61         return true;
62 }
63
64 static bool proc_scenefile(Scene *scn, struct ts_node *node)
65 {
66         const char *fname = ts_get_attr_str(node, "file");
67         if(fname) {
68                 // datapath
69                 struct ts_attr *adpath = attr_inscope(node, "datapath");
70                 if(adpath && adpath->val.type == TS_STRING) {
71                         info_log("adding data path: %s\n", adpath->val.str);
72                         datamap_set_path(adpath->val.str);
73                 }
74
75                 // walkmesh
76                 char *walkmesh_regexp = 0;
77                 struct ts_attr *awmesh = attr_inscope(node, "walkmesh");
78                 if(awmesh && awmesh->val.type == TS_STRING) {
79                         walkmesh_regexp = awmesh->val.str;
80                 }
81
82                 int namesz = datamap_lookup(fname, 0, 0);
83                 char *namebuf = (char*)alloca(namesz + 1);
84                 if(datamap_lookup(fname, namebuf, namesz + 1)) {
85                         fname = namebuf;
86                 }
87
88                 Scene *newscn = new Scene(scn->texset);
89                 if(!(newscn->load(fname, SCNLOAD_FLIPTEX))) {   // TODO unhardcode FLIPTEX
90                         return false;
91                 }
92
93                 // extract the walk mesh if necessary
94                 Scene *wscn;
95                 if(walkmesh_regexp && (wscn = newscn->extract_nodes(walkmesh_regexp))) {
96                         // apply all transformations to the meshes
97                         wscn->apply_xform();
98
99                         int nmeshes = wscn->meshes.size();
100                         for(int i=0; i<nmeshes; i++) {
101                                 Mesh *m = wscn->meshes[i];
102
103                                 if(newscn->walk_mesh) {
104                                         newscn->walk_mesh->append(*m);
105                                 } else {
106                                         newscn->walk_mesh = m;
107                                         wscn->remove_mesh(m);   // to save it from destruction
108                                 }
109                         }
110
111                         delete wscn;
112                 }
113
114                 // perform material edits
115                 struct ts_node *child = node->child_list;
116                 while(child) {
117                         proc_mtledit(newscn, child); // ignores any non-mtledit nodes, no need to check
118                         child = child->next;
119                 }
120
121                 scn->merge(newscn);
122                 delete newscn;
123         }
124         //datamap_reset();      // TODO this should be push/pop instead of hard reset
125
126         return true;
127 }
128
129 static void mtledit(Material *mtl, TextureSet *texset, struct ts_node *node)
130 {
131         node = node->child_list;
132         while(node) {
133                 struct ts_node *cn = node;
134                 node = node->next;
135
136                 if(strcmp(cn->name, "texture") == 0) {
137                         // add/change/remove a texture
138                         struct ts_attr *atype = ts_get_attr(cn, "type");
139                         struct ts_attr *afile = ts_get_attr(cn, "file");
140
141                         int textype = MTL_TEX_DIFFUSE;
142                         if(atype) {
143                                 if(atype->val.type == TS_STRING) {
144                                         textype = mtl_parse_type(atype->val.str);
145                                 } else if(atype->val.type == TS_NUMBER) {
146                                         textype = atype->val.inum;
147                                         if(textype < 0 || textype >= NUM_MTL_TEXTURES) {
148                                                 error_log("invalid texture in mtledit: %d\n", textype);
149                                                 continue;
150                                         }
151                                 } else {
152                                         error_log("unexpected texture type in mtledit: %s\n", atype->val.str);
153                                         continue;
154                                 }
155                         }
156
157                         if(!afile || !afile->val.str || !*afile->val.str) {
158                                 // remove
159                                 mtl->textures[textype] = 0;
160                         } else {
161                                 Texture *tex = texset->get_texture(afile->val.str, TEX_2D);
162                                 mtl->add_texture(tex, textype);
163                         }
164                 }
165                 // TODO add more edit modes
166         }
167 }
168
169 static bool proc_mtledit(Scene *scn, struct ts_node *node)
170 {
171         if(strcmp(node->name, "mtledit") != 0) {
172                 return false;
173         }
174
175         const char *mtl_regexp = ".*";
176         struct ts_attr *amtl = ts_get_attr(node, "material");
177         if(amtl && amtl->val.type == TS_STRING) {
178                 mtl_regexp = amtl->val.str;
179         }
180
181         // search all the objects to find matching material names
182         std::regex re{mtl_regexp};
183
184         int nobj = scn->objects.size();
185         for(int i=0; i<nobj; i++) {
186                 Object *obj = scn->objects[i];
187                 if(std::regex_match(obj->get_name(), re)) {
188                         mtledit(&obj->mtl, scn->texset, node);
189                 }
190         }
191
192         return true;
193 }
194
195 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name)
196 {
197         struct ts_attr *attr = 0;
198
199         while(node && !(attr = ts_get_attr(node, name))) {
200                 node = node->parent;
201         }
202         return attr;
203 }
204
205 static void print_scene_graph(SceneNode *n, int level)
206 {
207         if(!n) return;
208
209         for(int i=0; i<level; i++) {
210                 info_log("  ");
211         }
212
213         int nobj = n->get_num_objects();
214         if(nobj) {
215                 info_log("%s - %d obj\n", n->get_name(), n->get_num_objects());
216         } else {
217                 info_log("%s\n", n->get_name());
218         }
219
220         for(int i=0; i<n->get_num_children(); i++) {
221                 print_scene_graph(n->get_child(i), level + 1);
222         }
223 }