+#include <float.h>
#include <algorithm>
#include "exman.h"
#include "exhibit.h"
#include "blob_exhibit.h"
#include "treestore.h"
+#include "app.h"
+#include "geomdraw.h"
static Exhibit *create_exhibit(const char *type);
+static void clean_desc_text(char *dest, const char *src);
+
+
+ExhibitSlot::ExhibitSlot(Exhibit *ex)
+{
+ this->ex = 0;
+
+ init(ex);
+}
+
+ExhibitSlot::~ExhibitSlot()
+{
+ detach_exhibit();
+
+ SceneNode *par = node.get_parent();
+ if(par) {
+ par->remove_child(&node);
+
+ while(node.get_num_children()) {
+ par->add_child(node.get_child(0));
+ }
+ }
+}
+
+
+void ExhibitSlot::init(Exhibit *ex)
+{
+ std::string node_name = "ExhibitSlot";
+ if(ex) {
+ if(ex->get_name()) {
+ node_name += std::string(":") + std::string(ex->get_name());
+ }
+
+ if(ex->node) {
+ if(ex->node->get_parent()) {
+ ex->node->get_parent()->add_child(&node);
+ }
+ node.set_position(ex->node->get_node_position());
+ node.set_rotation(ex->node->get_node_rotation());
+ ex->node->set_position(Vec3(0, 0, 0));
+ ex->node->set_rotation(Quat::identity);
+ }
+ attach_exhibit(ex);
+ } else {
+ this->ex = 0;
+ }
+
+ node.set_name(node_name.c_str());
+}
+
+bool ExhibitSlot::empty() const
+{
+ return ex == 0;
+}
+
+Exhibit *ExhibitSlot::get_exhibit() const
+{
+ return ex;
+}
+
+/* In the process of attaching the exhibit, we also steal the exhibit's node
+ * from its previous parent, and reparent it to the slot's node. As the slot's
+ * node itself should have been made a child of the original parent of the
+ * exhibit during init(), the initial state is we're interjecting a null node in
+ * the scene graph between the exhibit and its original parent.
+ *
+ * Attaching to a slot, implicitly detaches from the previous slot.
+ */
+bool ExhibitSlot::attach_exhibit(Exhibit *ex, ExSlotAttachMode mode)
+{
+ if(!ex || this->ex) return false;
+
+ if(ex->prev_slot && ex->prev_slot->ex == ex) {
+ ex->prev_slot->detach_exhibit();
+ }
+
+ if(mode != EXSLOT_ATTACH_TRANSIENT) {
+ ex->prev_slot = this;
+ }
+
+ node.add_child(ex->node);
+ this->ex = ex;
+ return true;
+}
+
+bool ExhibitSlot::detach_exhibit()
+{
+ if(!ex) return false;
+
+ node.remove_child(ex->node);
+ ex = 0;
+ return true;
+}
+
+
+// ---- exhibit manager implementation ----
ExhibitManager::ExhibitManager()
{
+ own_scn = 0;
}
ExhibitManager::~ExhibitManager()
{
- int num = (int)items.size();
+ clear();
+}
+
+void ExhibitManager::clear()
+{
+ // not deleting exhibit objects, as they will be deleted the own_scn destructor
+ items.clear();
+
+ int num = (int)exslots.size();
for(int i=0; i<num; i++) {
- delete items[i];
+ delete exslots[i];
}
- items.clear();
+ exslots.clear();
+
+ delete own_scn; // this must be the last thing to destroy
}
void ExhibitManager::add(Exhibit *ex)
std::vector<Exhibit*>::iterator it = std::find(items.begin(), items.end(), ex);
if(it == items.end()) {
items.push_back(ex);
+ own_scn->add_object(ex);
+ if(ex->node) own_scn->add_node(ex->node);
}
}
std::vector<Exhibit*>::iterator it = std::find(items.begin(), items.end(), ex);
if(it != items.end()) {
items.erase(it);
+ own_scn->remove_object(ex);
+ if(ex->node) own_scn->remove_node(ex->node);
return true;
}
return false;
bool ExhibitManager::load(MetaScene *mscn, const char *fname)
{
+ info_log("ExhibitManager::load(%s)\n", fname);
+
struct ts_node *root = ts_load(fname);
if(!root || strcmp(root->name, "exhibits") != 0) {
ts_free_tree(root);
return false;
}
+ /* create our own scene to manage all exhibits not already in an existing scene
+ * and add it to the metascene.
+ * Also exhibit drawing happens due to the renderer drawing the metascene
+ */
+ if(!own_scn) {
+ own_scn = new Scene;
+ own_scn->name = "ad-hoc exhibits";
+ mscn->scenes.push_back(own_scn);
+ }
+
struct ts_node *iter = root->child_list;
while(iter) {
struct ts_node *node = iter;
iter = iter->next;
- if(strcmp(node->name, "item") == 0) {
- SceneNode *snode;
-
- const char *amatch = ts_get_attr_str(node, "match_node");
- if(!amatch || !(snode = mscn->match_node(amatch))) {
- error_log("regexp \"%s\" didn't match any nodes\n", amatch ? amatch : "");
- continue;
- }
+ SceneNode *snode = 0;
+ if(strcmp(node->name, "item") == 0) {
Exhibit *ex;
const char *atype = ts_get_attr_str(node, "type");
if(!atype || !(ex = create_exhibit(atype))) {
error_log("failed to create exhibit of type: %s\n", atype);
continue;
}
+ const char *alabel = ts_get_attr_str(node, "label");
+ if(alabel) {
+ ex->set_name(alabel);
+ }
+ const char *amatch = ts_get_attr_str(node, "match_node");
+ if(amatch) {
+ if(!(snode = mscn->match_node(amatch))) {
+ error_log("ExhibitManager::load: regexp \"%s\" didn't match any nodes\n",
+ amatch ? amatch : "");
+ continue;
+ }
+ }
+
+ // add everything to our data structures
+ // equivalent to add_exhibit(ex), but without all the searching
+ own_scn->add_object(ex);
+ if(!snode) {
+ snode = new SceneNode;
+ snode->set_name(ex->get_name());
+ own_scn->add_node(snode);
+ }
ex->set_node(snode);
items.push_back(ex);
+
+ float *apos = ts_get_attr_vec(node, "pos");
+ if(apos) {
+ snode->set_position(Vec3(apos[0], apos[1], apos[2]));
+ }
+ float *arot_axis = ts_get_attr_vec(node, "rotaxis");
+ if(arot_axis) {
+ float arot_angle = ts_get_attr_num(node, "rotangle", 0.0f);
+ Vec3 axis = Vec3(arot_axis[0], arot_axis[1], arot_axis[2]);
+ Quat q;
+ q.set_rotation(axis, deg_to_rad(arot_angle));
+ snode->set_rotation(q);
+ }
+ struct ts_attr *ascale = ts_get_attr(node, "scale");
+ if(ascale) {
+ switch(ascale->val.type) {
+ case TS_NUMBER:
+ snode->set_scaling(Vec3(ascale->val.fnum, ascale->val.fnum, ascale->val.fnum));
+ break;
+ case TS_VECTOR:
+ snode->set_scaling(Vec3(ascale->val.vec[0], ascale->val.vec[1], ascale->val.vec[2]));
+ default:
+ break;
+ }
+ }
+
+ // create a new slot and attach the exhibit to it
+ ExhibitSlot *slot = new ExhibitSlot(ex);
+ exslots.push_back(slot);
+
+ // grab any extra exhibit data
+ const char *desc = ts_get_attr_str(node, "description");
+ const char *voice = ts_get_attr_str(node, "voiceover");
+ if(desc || voice) {
+ ExData exd;
+ exd.type = EXDATA_INFO;
+
+ if(desc) {
+ char *fixed_desc = new char[strlen(desc) + 1];
+ clean_desc_text(fixed_desc, desc);
+ exd.text = std::string(fixed_desc);
+ delete [] fixed_desc;
+ }
+ if(voice) {
+ exd.voice = new OggVorbisStream;
+ if(!exd.voice->open(voice)) {
+ error_log("failed to open voiceover: %s\n", voice);
+ delete exd.voice;
+ exd.voice = 0;
+ }
+ }
+ ex->data.push_back(exd);
+ }
}
}
return true;
}
+ExSelection ExhibitManager::select(const Ray &ray) const
+{
+ ExSelection nearest;
+ nearest.dist = FLT_MAX;
+
+ int nitems = items.size();
+ for(int i=0; i<nitems; i++) {
+ ExSelection sel = items[i]->select(ray);
+ if(sel && sel.dist < nearest.dist) {
+ nearest = sel;
+ }
+ }
+
+ return nearest;
+}
+
+ExSelection ExhibitManager::select(const Sphere &sph) const
+{
+ int nitems = items.size();
+ for(int i=0; i<nitems; i++) {
+ ExSelection sel = items[i]->select(sph);
+ if(sel) {
+ return sel;
+ }
+ }
+ return ExSelection();
+}
+
+// TODO optimize
+ExhibitSlot *ExhibitManager::nearest_empty_slot(const Vec3 &pos, float max_dist) const
+{
+ ExhibitSlot *nearest = 0;
+ float nearest_sqdist = max_dist * max_dist;
+
+ int nslots = exslots.size();
+ for(int i=0; i<nslots; i++) {
+ ExhibitSlot *slot = exslots[i];
+ if(!slot->empty()) continue;
+
+ Vec3 slotpos = slot->node.get_position();
+ float dsq = length_sq(pos - slotpos);
+ if(dsq < nearest_sqdist) {
+ nearest = slot;
+ nearest_sqdist = dsq;
+ }
+ }
+
+ return nearest;
+}
+
+void ExhibitManager::stash_exhibit(Exhibit *ex)
+{
+ // make sure it's not already stashed
+ if(std::find(stashed.begin(), stashed.end(), ex) != stashed.end()) {
+ return;
+ }
+ stashed.push_back(ex);
+
+ ex->prev_slot = 0;
+ if(ex->node->get_parent()) {
+ ex->node->get_parent()->remove_child(ex->node);
+ }
+}
+
void ExhibitManager::update(float dt)
{
int num = items.size();
for(int i=0; i<num; i++) {
+ // if the exhibit is not part of a scene graph, first call its
+ // node's update function (otherwise it would have been called recursively earlier)
+ if(items[i]->node && !items[i]->node->get_parent()) {
+ items[i]->node->update(dt);
+ }
items[i]->update(dt);
}
}
+void ExhibitManager::draw() const
+{
+ int num = items.size();
+ for(int i=0; i<num; i++) {
+ if(exsel_hover.ex == items[i]) {
+ const AABox &bvol = items[i]->get_aabox();
+ draw_geom_object(&bvol);
+ }
+ }
+}
+
static Exhibit *create_exhibit(const char *type)
{
if(strcmp(type, "static") == 0) {
+ debug_log("creating static exhibit\n");
return new Exhibit;
} else if(strcmp(type, "blobs") == 0) {
- return new BlobExhibit;
+ debug_log("creating blobs exhibit\n");
+ BlobExhibit *b = new BlobExhibit;
+ if(!b->init()) {
+ delete b;
+ error_log("failed to initialize blobs exhibit\n");
+ return 0;
+ }
+ return b;
}
error_log("unknown exhibit type: %s\n", type);
return 0;
}
+
+/* clean up description text to be more easily layed out later.
+ * more specifically:
+ * - remove redundant spaces
+ * - remove all newlines except as paragraph separators
+ * - remove all other whitespace chars
+ * destination buffer must be as large as the source buffer
+ */
+static void clean_desc_text(char *dest, const char *src)
+{
+ while(*src) {
+ if(isspace(*src)) {
+ if(*src == '\n' && *(src + 1) == '\n') {
+ *dest++ = '\n';
+ } else {
+ *dest++ = ' ';
+ }
+ while(*src && isspace(*src)) ++src;
+ } else {
+ *dest++ = *src++;
+ }
+ }
+ *dest = 0;
+}