ExhibitSlots and exhibit placement (initial)
authorJohn Tsiombikas <nuclear@member.fsf.org>
Wed, 17 Jan 2018 06:37:34 +0000 (08:37 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Wed, 17 Jan 2018 06:37:34 +0000 (08:37 +0200)
src/app.cc
src/app.h
src/avatar.h
src/exhibit.cc
src/exhibit.h
src/exman.cc
src/exman.h
src/ui_exhibit.cc [new file with mode: 0644]
src/ui_exhibit.h [new file with mode: 0644]

index 59d713c..8865012 100644 (file)
@@ -1,4 +1,5 @@
 #include <stdio.h>
+#include <limits.h>
 #include <assert.h>
 #include <goatvr.h>
 #include "app.h"
@@ -72,7 +73,11 @@ static ExhibitManager *exman;
 static BlobExhibit *blobs;
 static bool show_blobs;
 
-ExSelection exsel_grab, exsel_hover;
+ExSelection exsel_active, exsel_hover;
+ExSelection exsel_grab_left, exsel_grab_right;
+#define exsel_grab_mouse exsel_grab_right
+static ExhibitSlot exslot_left, exslot_right;
+#define exslot_mouse exslot_right
 
 static Renderer *rend;
 
@@ -221,6 +226,9 @@ void app_cleanup()
 
        delete rend;
 
+       /* this must be destroyed before the scene graph to detach exhibit nodes
+        * before the scene tries to delete them recursively
+        */
        delete exman;
 
        texman.clear();
@@ -333,7 +341,7 @@ static void update(float dt)
                floor_y = avatar.pos.y - user_eye_height;
        }
 
-       // TODO move to avatar
+       // TODO move to the avatar system
        // calculate mouselook view matrix
        mouse_view_matrix = Mat4::identity;
        mouse_view_matrix.pre_translate(0, 0, -cam_dist);
@@ -343,12 +351,17 @@ static void update(float dt)
        mouse_view_matrix.pre_rotate_y(deg_to_rad(avatar.body_rot));
        mouse_view_matrix.pre_translate(-avatar.pos.x, -avatar.pos.y, -avatar.pos.z);
 
-       // check if an exhibit is hovered-over by mouse or 6dof
-       // XXX note: using previous view/proj matrix lattency shouldn't be an issue but
-       //           make sure state-creep doesn't get us
-       // XXX also this mouse-picking probably should only be active in non-VR mode
-       Ray ray = calc_pick_ray(prev_mx, prev_my);
-       exsel_hover = exman->select(ray);
+       // check if an exhibit is hovered-over by mouse or 6dof (only if we don't have one grabbed)
+       if(!exsel_grab_mouse) {
+               // XXX note: using previous view/proj matrix lattency shouldn't be an issue but
+               //           make sure state-creep doesn't get us
+               // XXX also this mouse-picking probably should only be active in non-VR mode
+               Ray ray = calc_pick_ray(prev_mx, prev_my);
+               exsel_hover = exman->select(ray);
+       }
+
+       if(!exslot_left.empty()) exslot_left.node.update(dt);
+       if(!exslot_right.empty()) exslot_right.node.update(dt);
 
        // update hand-tracking
        if(have_handtracking) {
@@ -601,6 +614,8 @@ void app_keyboard(int key, bool pressed)
 
 void app_mouse_button(int bn, bool pressed, int x, int y)
 {
+       static int press_x, press_y;
+
        if(debug_gui) {
                debug_gui_mbutton(bn, pressed, x, y);
                return; // ignore mouse events while GUI is visible
@@ -611,13 +626,64 @@ void app_mouse_button(int bn, bool pressed, int x, int y)
        bnstate[bn] = pressed;
 
        if(bn == 0) {
-               if(!pressed) {
-                       if(exsel_grab.ex) {
-                               exsel_grab.ex = 0;
-                       } else {
-                               Ray ray = calc_pick_ray(x, y);
-                               exsel_grab = exman->select(ray);
+               ExSelection sel;
+               Ray ray = calc_pick_ray(x, y);
+               sel = exman->select(ray);
+
+               if(pressed) {
+                       if(sel && (app_get_modifiers() & MOD_CTRL)) {
+                               exsel_grab_mouse = sel;
+                               Vec3 pos = sel.ex->node->get_position();
+                               debug_log("grabbing... (%g %g %g)\n", pos.x, pos.y, pos.z);
+                               exslot_mouse.node.set_position(pos);
+                               exslot_mouse.node.set_rotation(sel.ex->node->get_rotation());
+                               exslot_mouse.attach_exhibit(sel.ex, EXSLOT_ATTACH_TRANSIENT);
+                               if(exsel_active) {
+                                       exsel_active = ExSelection::null;       // cancel active on grab
+                               }
+                       }
+                       press_x = x;
+                       press_y = y;
+
+               } else {
+                       if(exsel_grab_mouse) {
+                               // cancel grab on mouse release
+                               debug_log("releasing...\n");
+                               Exhibit *ex = exsel_grab_mouse.ex;
+                               Vec3 pos = exslot_mouse.node.get_position();
+                               debug_log("release location: %g %g %g\n", pos.x, pos.y, pos.z);
+
+                               ExhibitSlot *slot = exman->nearest_empty_slot(pos);
+                               if(!slot) {
+                                       debug_log("no nearby slot\n");
+                                       if(ex->prev_slot && ex->prev_slot->empty()) {
+                                               slot = ex->prev_slot;
+                                               debug_log("previous slot available though\n");
+                                       }
+                               }
+
+                               if(slot) {
+                                       slot->attach_exhibit(ex);
+                               } else {
+                                       // nowhere to put it, so stash it for later
+                                       exslot_mouse.detach_exhibit();
+                                       exman->stash_exhibit(ex);
+                                       debug_log("no slots available, stashing\n");
+                               }
+
+                               exsel_grab_mouse = ExSelection::null;
                        }
+
+                       if(abs(press_x - x) < 5 && abs(press_y - y) < 5) {
+                               exsel_active = sel;     // select or deselect active exhibit
+                               if(sel) {
+                                       debug_log("selecting...\n");
+                               } else {
+                                       debug_log("deselecting...\n");
+                               }
+                       }
+
+                       press_x = press_y = INT_MIN;
                }
        }
 }
@@ -652,11 +718,11 @@ void app_mouse_motion(int x, int y)
 
        if(!dx && !dy) return;
 
-       if(exsel_grab.ex) {
-               Vec3 pos = exsel_grab.ex->node->get_node_position();
+       if(exsel_grab_mouse) {
+               Vec3 pos = exslot_mouse.node.get_node_position();
                Vec3 dir = transpose(view_matrix.upper3x3()) * Vec3(dx * 1.0, dy * -1.0, 0);
 
-               exsel_grab.ex->node->set_position(pos + dir);
+               exslot_mouse.node.set_position(pos + dir);
        }
 
        if(bnstate[2]) {
index 1eed2fe..686c7e0 100644 (file)
--- a/src/app.h
+++ b/src/app.h
@@ -16,7 +16,9 @@ extern SceneSet sceneman;
 
 extern unsigned int sdr_ltmap, sdr_ltmap_notex;
 
-extern ExSelection exsel_grab, exsel_hover;
+extern ExSelection exsel_active;       // active (info/interact) exhibit
+extern ExSelection exsel_grab_left, exsel_grab_right; // grabbed on each hand
+extern ExSelection exsel_hover;                // hover
 
 extern int fpexcept_enabled;   // int so that C modules may fwd-delcare and use it
 
index ca3ea8c..6c3aa41 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef AVATAR_H_
 #define AVATAR_H_
 
+// TODO incomplete
+
 #include <gmath/gmath.h>
 
 /* when head-tracking is available, head_tilt is ignored, and the
index 266a583..8403550 100644 (file)
@@ -4,14 +4,7 @@
 #include "geomdraw.h"
 #include "app.h"
 
-class ExhibitPriv {
-public:
-       Vec3 orig_pos;
-       Quat orig_rot;
-       SceneNode *orig_node;
-
-       ExhibitPriv();
-};
+ExSelection ExSelection::null;
 
 
 // selection
@@ -38,28 +31,21 @@ ExData::~ExData()
        delete voice;
 }
 
-// private data for each exhibit type
-ExhibitPriv::ExhibitPriv()
-{
-       orig_node = 0;
-}
-
 // exhibit class
 Exhibit::Exhibit()
 {
-       priv = new ExhibitPriv;
+       orig_parent = 0;
+       prev_slot = 0;
 }
 
 Exhibit::~Exhibit()
 {
-       delete priv;
 }
 
 void Exhibit::set_node(SceneNode *node)
 {
-       this->node = priv->orig_node = node;
-       priv->orig_pos = node->get_position();
-       priv->orig_rot = node->get_rotation();
+       this->node = node;
+       orig_parent = node->get_parent();
 }
 
 ExSelection Exhibit::select(const Ray &ray) const
index f331d99..fd1d77b 100644 (file)
@@ -8,7 +8,7 @@
 #include "audio/stream.h"
 
 class Exhibit;
-class ExhibitPriv;
+class ExhibitSlot;
 class Scene;
 
 enum {
@@ -18,6 +18,8 @@ enum {
 
 class ExSelection {
 public:
+       static ExSelection null;        // null selection
+
        Exhibit *ex;
        void *obj;
        void *data;
@@ -56,9 +58,10 @@ public:
  */
 class Exhibit : public Object {
 private:
-       ExhibitPriv *priv;
+       SceneNode *orig_parent;
 
 public:
+       ExhibitSlot *prev_slot;
        std::vector<ExData> data;
 
        Exhibit();
index 21e0074..02f4bc5 100644 (file)
@@ -7,6 +7,101 @@
 
 static Exhibit *create_exhibit(const char *type);
 
+
+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()
 {
 }
@@ -23,6 +118,12 @@ void ExhibitManager::clear()
                delete items[i];
        }
        items.clear();
+
+       num = (int)exslots.size();
+       for(int i=0; i<num; i++) {
+               delete exslots[i];
+       }
+       exslots.clear();
 }
 
 void ExhibitManager::add(Exhibit *ex)
@@ -73,7 +174,19 @@ bool ExhibitManager::load(MetaScene *mscn, const char *fname)
                                error_log("failed to create exhibit of type: %s\n", atype);
                                continue;
                        }
+                       ex->set_node(snode);
+                       items.push_back(ex);
 
+                       const char *alabel = ts_get_attr_str(node, "label");
+                       if(alabel) {
+                               ex->set_name(alabel);
+                       }
+
+                       // 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) {
@@ -92,9 +205,6 @@ bool ExhibitManager::load(MetaScene *mscn, const char *fname)
                                }
                                ex->data.push_back(exd);
                        }
-
-                       ex->set_node(snode);
-                       items.push_back(ex);
                }
        }
 
@@ -129,6 +239,42 @@ ExSelection ExhibitManager::select(const Sphere &sph) const
        return sel;     // TODO
 }
 
+// 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();
index 4a72993..bbd72fd 100644 (file)
@@ -5,9 +5,38 @@
 #include "exhibit.h"
 #include "metascene.h"
 
+//! argument to ExhibitSlot::attach
+enum ExSlotAttachMode {
+       EXSLOT_ATTACH_PERMANENT,
+       EXSLOT_ATTACH_TRANSIENT
+};
+
+//! slot which can hold a single exhibit
+class ExhibitSlot {
+private:
+       Exhibit *ex;
+
+public:
+       SceneNode node;
+
+       ExhibitSlot(Exhibit *ex = 0);
+       ~ExhibitSlot();
+
+       void init(Exhibit *ex);
+
+       bool empty() const;
+       Exhibit *get_exhibit() const;
+
+       bool attach_exhibit(Exhibit *ex, ExSlotAttachMode mode = EXSLOT_ATTACH_PERMANENT);
+       bool detach_exhibit();
+};
+
+
 class ExhibitManager {
 private:
-       std::vector<Exhibit*> items;
+       std::vector<Exhibit*> items, stashed;
+       std::vector<ExhibitSlot*> exslots;
+       // TODO kdtree of slots for quick nearest queries
 
 public:
        ExhibitManager();
@@ -23,6 +52,10 @@ public:
        ExSelection select(const Ray &ray) const;
        ExSelection select(const Sphere &sph) const;
 
+       ExhibitSlot *nearest_empty_slot(const Vec3 &pos, float max_dist = 10) const;
+
+       void stash_exhibit(Exhibit *ex);
+
        void update(float dt = 0.0f);
        void draw() const;
 };
diff --git a/src/ui_exhibit.cc b/src/ui_exhibit.cc
new file mode 100644 (file)
index 0000000..409bd94
--- /dev/null
@@ -0,0 +1,21 @@
+#include "ui_exhibit.h"
+#include "app.h"
+
+bool exui_init()
+{
+       return true;
+}
+
+void exui_shutdown()
+{
+}
+
+void exui_update(float dt)
+{
+}
+
+void exui_draw()
+{
+       if(!exsel_active) return;
+
+}
diff --git a/src/ui_exhibit.h b/src/ui_exhibit.h
new file mode 100644 (file)
index 0000000..6fdb6ca
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef UI_EXHIBIT_H_
+#define UI_EXHIBIT_H_
+
+bool exui_init();
+void exui_shutdown();
+
+void exui_update(float dt);
+void exui_draw();
+
+#endif // UI_EXHIBIT_H_