added delayed init call after scenegraph/meshes are done loading
[laserbrain_demo] / src / exman.cc
1 #include <float.h>
2 #include <algorithm>
3 #include "exman.h"
4 #include "exhibit.h"
5 #include "blob_exhibit.h"
6 #include "treestore.h"
7 #include "app.h"
8 #include "geomdraw.h"
9
10 static Exhibit *create_exhibit(const char *type);
11 static void clean_desc_text(char *dest, const char *src);
12
13
14 ExhibitSlot::ExhibitSlot(Exhibit *ex)
15 {
16         this->ex = 0;
17
18         init(ex);
19 }
20
21 ExhibitSlot::~ExhibitSlot()
22 {
23         detach_exhibit();
24
25         SceneNode *par = node.get_parent();
26         if(par) {
27                 par->remove_child(&node);
28
29                 while(node.get_num_children()) {
30                         par->add_child(node.get_child(0));
31                 }
32         }
33 }
34
35
36 void ExhibitSlot::init(Exhibit *ex)
37 {
38         std::string node_name = "ExhibitSlot";
39         if(ex) {
40                 if(ex->get_name()) {
41                         node_name += std::string(":") + std::string(ex->get_name());
42                 }
43
44                 if(ex->node) {
45                         if(ex->node->get_parent()) {
46                                 ex->node->get_parent()->add_child(&node);
47                         }
48                         node.set_position(ex->node->get_node_position());
49                         node.set_rotation(ex->node->get_node_rotation());
50                         ex->node->set_position(Vec3(0, 0, 0));
51                         ex->node->set_rotation(Quat::identity);
52                 }
53                 attach_exhibit(ex);
54         } else {
55                 this->ex = 0;
56         }
57
58         node.set_name(node_name.c_str());
59 }
60
61 bool ExhibitSlot::empty() const
62 {
63         return ex == 0;
64 }
65
66 Exhibit *ExhibitSlot::get_exhibit() const
67 {
68         return ex;
69 }
70
71 /* In the process of attaching the exhibit, we also steal the exhibit's node
72  * from its previous parent, and reparent it to the slot's node. As the slot's
73  * node itself should have been made a child of the original parent of the
74  * exhibit during init(), the initial state is we're interjecting a null node in
75  * the scene graph between the exhibit and its original parent.
76  *
77  * Attaching to a slot, implicitly detaches from the previous slot.
78  */
79 bool ExhibitSlot::attach_exhibit(Exhibit *ex, ExSlotAttachMode mode)
80 {
81         if(!ex || this->ex) return false;
82
83         if(ex->prev_slot && ex->prev_slot->ex == ex) {
84                 ex->prev_slot->detach_exhibit();
85         }
86
87         if(mode != EXSLOT_ATTACH_TRANSIENT) {
88                 ex->prev_slot = this;
89         }
90
91         node.add_child(ex->node);
92         this->ex = ex;
93         return true;
94 }
95
96 bool ExhibitSlot::detach_exhibit()
97 {
98         if(!ex) return false;
99
100         node.remove_child(ex->node);
101         ex = 0;
102         return true;
103 }
104
105
106 // ---- exhibit manager implementation ----
107
108 ExhibitManager::ExhibitManager()
109 {
110         own_scn = 0;
111 }
112
113 ExhibitManager::~ExhibitManager()
114 {
115         clear();
116 }
117
118 void ExhibitManager::clear()
119 {
120         // not deleting exhibit objects, as they will be deleted the own_scn destructor
121         items.clear();
122
123         int num = (int)exslots.size();
124         for(int i=0; i<num; i++) {
125                 delete exslots[i];
126         }
127         exslots.clear();
128
129         delete own_scn; // this must be the last thing to destroy
130 }
131
132 void ExhibitManager::add(Exhibit *ex)
133 {
134         std::vector<Exhibit*>::iterator it = std::find(items.begin(), items.end(), ex);
135         if(it == items.end()) {
136                 items.push_back(ex);
137                 own_scn->add_object(ex);
138                 if(ex->node) own_scn->add_node(ex->node);
139         }
140 }
141
142 bool ExhibitManager::remove(Exhibit *ex)
143 {
144         std::vector<Exhibit*>::iterator it = std::find(items.begin(), items.end(), ex);
145         if(it != items.end()) {
146                 items.erase(it);
147                 own_scn->remove_object(ex);
148                 if(ex->node) own_scn->remove_node(ex->node);
149                 return true;
150         }
151         return false;
152 }
153
154 bool ExhibitManager::load(MetaScene *mscn, const char *fname)
155 {
156         info_log("ExhibitManager::load(%s)\n", fname);
157
158         struct ts_node *root = ts_load(fname);
159         if(!root || strcmp(root->name, "exhibits") != 0) {
160                 ts_free_tree(root);
161                 error_log("failed to load exhibits\n");
162                 return false;
163         }
164
165         /* create our own scene to manage all exhibits not already in an existing scene
166          * and add it to the metascene.
167          * Also exhibit drawing happens due to the renderer drawing the metascene
168          */
169         if(!own_scn) {
170                 own_scn = new Scene;
171                 own_scn->name = "ad-hoc exhibits";
172                 mscn->scenes.push_back(own_scn);
173         }
174
175         struct ts_node *iter = root->child_list;
176         while(iter) {
177                 struct ts_node *node = iter;
178                 iter = iter->next;
179
180                 SceneNode *snode = 0;
181
182                 if(strcmp(node->name, "item") == 0) {
183                         Exhibit *ex;
184                         const char *atype = ts_get_attr_str(node, "type");
185                         if(!atype || !(ex = create_exhibit(atype))) {
186                                 error_log("failed to create exhibit of type: %s\n", atype);
187                                 continue;
188                         }
189                         const char *alabel = ts_get_attr_str(node, "label");
190                         if(alabel) {
191                                 ex->set_name(alabel);
192                         }
193
194                         const char *amatch = ts_get_attr_str(node, "match_node");
195                         if(amatch) {
196                                 if(!(snode = mscn->match_node(amatch))) {
197                                         error_log("ExhibitManager::load: regexp \"%s\" didn't match any nodes\n",
198                                                         amatch ? amatch : "");
199                                         continue;
200                                 }
201                         }
202
203                         // add everything to our data structures
204                         // equivalent to add_exhibit(ex), but without all the searching
205                         own_scn->add_object(ex);
206                         if(!snode) {
207                                 snode = new SceneNode;
208                                 snode->set_name(ex->get_name());
209                                 own_scn->add_node(snode);
210                         }
211                         ex->set_node(snode);
212                         items.push_back(ex);
213
214                         float *apos = ts_get_attr_vec(node, "pos");
215                         if(apos) {
216                                 snode->set_position(Vec3(apos[0], apos[1], apos[2]));
217                         }
218                         float *arot_axis = ts_get_attr_vec(node, "rotaxis");
219                         if(arot_axis) {
220                                 float arot_angle = ts_get_attr_num(node, "rotangle", 0.0f);
221                                 Vec3 axis = Vec3(arot_axis[0], arot_axis[1], arot_axis[2]);
222                                 Quat q;
223                                 q.set_rotation(axis, deg_to_rad(arot_angle));
224                                 snode->set_rotation(q);
225                         }
226                         struct ts_attr *ascale = ts_get_attr(node, "scale");
227                         if(ascale) {
228                                 switch(ascale->val.type) {
229                                 case TS_NUMBER:
230                                         snode->set_scaling(Vec3(ascale->val.fnum, ascale->val.fnum, ascale->val.fnum));
231                                         break;
232                                 case TS_VECTOR:
233                                         snode->set_scaling(Vec3(ascale->val.vec[0], ascale->val.vec[1], ascale->val.vec[2]));
234                                 default:
235                                         break;
236                                 }
237                         }
238
239                         // create a new slot and attach the exhibit to it
240                         ExhibitSlot *slot = new ExhibitSlot(ex);
241                         exslots.push_back(slot);
242
243                         // grab any extra exhibit data
244                         const char *desc = ts_get_attr_str(node, "description");
245                         const char *voice = ts_get_attr_str(node, "voiceover");
246                         if(desc || voice) {
247                                 ExData exd;
248                                 exd.type = EXDATA_INFO;
249
250                                 if(desc) {
251                                         char *fixed_desc = new char[strlen(desc) + 1];
252                                         clean_desc_text(fixed_desc, desc);
253                                         exd.text = std::string(fixed_desc);
254                                         delete [] fixed_desc;
255                                 }
256                                 if(voice) {
257                                         exd.voice = new OggVorbisStream;
258                                         if(!exd.voice->open(voice)) {
259                                                 error_log("failed to open voiceover: %s\n", voice);
260                                                 delete exd.voice;
261                                                 exd.voice = 0;
262                                         }
263                                 }
264                                 ex->data.push_back(exd);
265                         }
266                 }
267         }
268
269         ts_free_tree(root);
270         return true;
271 }
272
273 ExSelection ExhibitManager::select(const Ray &ray) const
274 {
275         ExSelection nearest;
276         nearest.dist = FLT_MAX;
277
278         int nitems = items.size();
279         for(int i=0; i<nitems; i++) {
280                 ExSelection sel = items[i]->select(ray);
281                 if(sel && sel.dist < nearest.dist) {
282                         nearest = sel;
283                 }
284         }
285
286         return nearest;
287 }
288
289 ExSelection ExhibitManager::select(const Sphere &sph) const
290 {
291         int nitems = items.size();
292         for(int i=0; i<nitems; i++) {
293                 ExSelection sel = items[i]->select(sph);
294                 if(sel) {
295                         return sel;
296                 }
297         }
298         return ExSelection();
299 }
300
301 // TODO optimize
302 ExhibitSlot *ExhibitManager::nearest_empty_slot(const Vec3 &pos, float max_dist) const
303 {
304         ExhibitSlot *nearest = 0;
305         float nearest_sqdist = max_dist * max_dist;
306
307         int nslots = exslots.size();
308         for(int i=0; i<nslots; i++) {
309                 ExhibitSlot *slot = exslots[i];
310                 if(!slot->empty()) continue;
311
312                 Vec3 slotpos = slot->node.get_position();
313                 float dsq = length_sq(pos - slotpos);
314                 if(dsq < nearest_sqdist) {
315                         nearest = slot;
316                         nearest_sqdist = dsq;
317                 }
318         }
319
320         return nearest;
321 }
322
323 void ExhibitManager::stash_exhibit(Exhibit *ex)
324 {
325         // make sure it's not already stashed
326         if(std::find(stashed.begin(), stashed.end(), ex) != stashed.end()) {
327                 return;
328         }
329         stashed.push_back(ex);
330
331         ex->prev_slot = 0;
332         if(ex->node->get_parent()) {
333                 ex->node->get_parent()->remove_child(ex->node);
334         }
335 }
336
337 void ExhibitManager::update(float dt)
338 {
339         int num = items.size();
340         for(int i=0; i<num; i++) {
341                 // if the exhibit is not part of a scene graph, first call its
342                 // node's update function (otherwise it would have been called recursively earlier)
343                 if(items[i]->node && !items[i]->node->get_parent()) {
344                         items[i]->node->update(dt);
345                 }
346                 items[i]->update(dt);
347         }
348 }
349
350 void ExhibitManager::draw() const
351 {
352         int num = items.size();
353         for(int i=0; i<num; i++) {
354                 if(exsel_hover.ex == items[i]) {
355                         const AABox &bvol = items[i]->get_aabox();
356                         draw_geom_object(&bvol);
357                 }
358         }
359 }
360
361 static Exhibit *create_exhibit(const char *type)
362 {
363         if(strcmp(type, "static") == 0) {
364                 debug_log("creating static exhibit\n");
365                 return new Exhibit;
366         } else if(strcmp(type, "blobs") == 0) {
367                 debug_log("creating blobs exhibit\n");
368                 BlobExhibit *b = new BlobExhibit;
369                 if(!b->init()) {
370                         delete b;
371                         error_log("failed to initialize blobs exhibit\n");
372                         return 0;
373                 }
374                 return b;
375         }
376         error_log("unknown exhibit type: %s\n", type);
377         return 0;
378 }
379
380 /* clean up description text to be more easily layed out later.
381  * more specifically:
382  *  - remove redundant spaces
383  *  - remove all newlines except as paragraph separators
384  *  - remove all other whitespace chars
385  * destination buffer must be as large as the source buffer
386  */
387 static void clean_desc_text(char *dest, const char *src)
388 {
389         while(*src) {
390                 if(isspace(*src)) {
391                         if(*src == '\n' && *(src + 1) == '\n') {
392                                 *dest++ = '\n';
393                         } else {
394                                 *dest++ = ' ';
395                         }
396                         while(*src && isspace(*src)) ++src;
397                 } else {
398                         *dest++ = *src++;
399                 }
400         }
401         *dest = 0;
402 }