]> Pileus Git - grits/commitdiff
Add mouse enter/leave signals to objects
authorAndy Spencer <andy753421@gmail.com>
Sat, 15 Oct 2011 06:13:39 +0000 (06:13 +0000)
committerAndy Spencer <andy753421@gmail.com>
Sat, 15 Oct 2011 06:13:39 +0000 (06:13 +0000)
This uses the OpenGL Selection render mode to determine which objects
the mouse is over. It requires fairly tight integration between
GritsOpenGL and GritsObject.

The signal code is handled internally by GritsObject. However, most of
the actual selection matching work is done by GritsOpenGL.

Object types that wish to improve performance can implement the pick()
function in addition to the draw() function. The pick function is used
when performing selection matching. It works similar to draw, but does
not need to do textures/lighting/opacity/etc.

src/grits-opengl.c
src/objects/grits-object.c
src/objects/grits-object.h
src/objects/grits-poly.c

index 9c3af0886ada378819d812e9b0e910c904009ffe..423c849dba2fa53f568c0672db4c6213add4a86a 100644 (file)
 /* http://research.microsoft.com/pubs/70307/tr-2006-81.pdf */
 /* http://www.opengl.org/wiki/Alpha_Blending */
 
+/* The unsorted/sroted GLists are blank head nodes,
+ * This way us we can remove objects from the level just by fixing up links
+ * I.e. we don't need to do a lookup to remove an object if we have its GList */
+struct RenderLevel {
+       GList unsorted;
+       GList sorted;
+};
+
 /***********
  * Helpers *
  ***********/
@@ -116,18 +124,27 @@ static void _set_visuals(GritsOpenGL *opengl)
        g_mutex_unlock(opengl->sphere_lock);
 }
 
+static gboolean _foreach_object_cb(gpointer key, gpointer value, gpointer pointers)
+{
+       struct RenderLevel *level = value;
+       GFunc    user_func = ((gpointer*)pointers)[0];
+       gpointer user_data = ((gpointer*)pointers)[1];
+       for (GList *cur = level->unsorted.next; cur; cur = cur->next)
+               user_func(cur->data, user_data);
+       for (GList *cur = level->sorted.next;   cur; cur = cur->next)
+               user_func(cur->data, user_data);
+       return FALSE;
+}
+
+static void _foreach_object(GritsOpenGL *opengl, GFunc func, gpointer user_data)
+{
+       gpointer pointers[2] = {func, user_data};
+       g_tree_foreach(opengl->objects, _foreach_object_cb, pointers);
+}
 
 /*************
  * Callbacks *
  *************/
-/* The unsorted/sroted GLists are blank head nodes,
- * This way us we can remove objects from the level just by fixing up links
- * I.e. we don't need to do a lookup to remove an object if we have its GList */
-struct RenderLevel {
-       GList unsorted;
-       GList sorted;
-};
-
 static gboolean on_configure(GritsOpenGL *opengl, GdkEventConfigure *event, gpointer _)
 {
        g_debug("GritsOpenGL: on_configure");
@@ -212,6 +229,55 @@ static gboolean on_expose(GritsOpenGL *opengl, GdkEventExpose *event, gpointer _
        return FALSE;
 }
 
+static gboolean on_motion_notify(GritsOpenGL *opengl, GdkEventMotion *event, gpointer _)
+{
+       gdouble height = GTK_WIDGET(opengl)->allocation.height;
+       gdouble gl_x   = event->x;
+       gdouble gl_y   = height - event->y;
+
+       /* Configure view */
+       gint viewport[4];
+       gdouble projection[16];
+       glGetIntegerv(GL_VIEWPORT, viewport);
+       glGetDoublev(GL_PROJECTION_MATRIX, projection);
+
+       glMatrixMode(GL_PROJECTION);
+       glPushMatrix();
+       glLoadIdentity();
+       gluPickMatrix(gl_x, gl_y, 2, 2, viewport);
+       glMultMatrixd(projection);
+
+       /* Prepare for picking */
+       guint buffer[100][4] = {};
+       glSelectBuffer(G_N_ELEMENTS(buffer), (guint*)buffer);
+       glRenderMode(GL_SELECT);
+       glInitNames();
+
+       /* Run picking */
+       g_mutex_lock(opengl->objects_lock);
+       _foreach_object(opengl, (GFunc)grits_object_pick_begin, opengl);
+       int hits = glRenderMode(GL_RENDER);
+       g_debug("GritsOpenGL: on_motion_notify - hits=%d ev=%.0lf,%.0lf",
+                       hits, gl_x, gl_y);
+       for (int i = 0; i < hits; i++) {
+               //g_debug("\tHit: %d",     i);
+               //g_debug("\t\tcount: %d", buffer[i][0]);
+               //g_debug("\t\tz1:    %f", (float)buffer[i][1]/0x7fffffff);
+               //g_debug("\t\tz2:    %f", (float)buffer[i][2]/0x7fffffff);
+               //g_debug("\t\tname:  %p", (gpointer)buffer[i][3]);
+               GritsObject *object = GRITS_OBJECT(buffer[i][3]);
+               grits_object_pick_pointer(object, gl_x, gl_y);
+       }
+       _foreach_object(opengl, (GFunc)grits_object_pick_end, NULL);
+       g_mutex_unlock(opengl->objects_lock);
+
+       /* Cleanup */
+       glMatrixMode(GL_PROJECTION);
+       glPopMatrix();
+
+       return FALSE;
+}
+
 static gboolean on_key_press(GritsOpenGL *opengl, GdkEventKey *event, gpointer _)
 {
        g_debug("GritsOpenGL: on_key_press - key=%x, state=%x, plus=%x",
@@ -282,6 +348,7 @@ static void on_realize(GritsOpenGL *opengl, gpointer _)
        g_signal_connect(opengl, "location-changed", G_CALLBACK(on_view_changed), NULL);
        g_signal_connect(opengl, "rotation-changed", G_CALLBACK(on_view_changed), NULL);
 
+       g_signal_connect(opengl, "motion-notify-event", G_CALLBACK(on_motion_notify), NULL);
 #ifndef ROAM_DEBUG
        opengl->sm_source[0] = g_timeout_add_full(G_PRIORITY_HIGH_IDLE+30, 33,  (GSourceFunc)on_idle, opengl, NULL);
        opengl->sm_source[1] = g_timeout_add_full(G_PRIORITY_HIGH_IDLE+10, 500, (GSourceFunc)on_idle, opengl, NULL);
index 3cfd8026c6558d1ae172f1278ab4e7d25317a490..6abc4eb26f3ba70b3ff25a945f2af5918cd38d44 100644 (file)
 
 #include "grits-object.h"
 
-
-/**
- * grits_object_draw:
- * @object: the object
- * @opengl: the viewer the object is being displayed in
- *
- * Perform any OpenGL commands necessasairy to draw the object.
- *
- * The GL_PROJECTION and GL_MODELVIEW matricies and GL_ALL_ATTRIB_BITS will be
- * restored to the default state after the call to draw.
- */
-void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
+/* Constants */
+enum {
+       SIG_ENTER,
+       SIG_LEAVE,
+       NUM_SIGNALS,
+};
+static guint signals[NUM_SIGNALS];
+
+void grits_object_pickdraw(GritsObject *object, GritsOpenGL *opengl, gboolean pick)
 {
        GritsObjectClass *klass = GRITS_OBJECT_GET_CLASS(object);
        if (!klass->draw) {
@@ -111,7 +108,10 @@ void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
                                object->center.lon,
                                object->center.elev);
 
-       klass->draw(object, opengl);
+       if (pick && klass->pick)
+               klass->pick(object, opengl);
+       else
+               klass->draw(object, opengl);
 
        if (!(object->skip & GRITS_SKIP_STATE)) {
                glPopAttrib();
@@ -121,6 +121,21 @@ void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
        g_mutex_unlock(opengl->sphere_lock);
 }
 
+/**
+ * grits_object_draw:
+ * @object: the object
+ * @opengl: the viewer the object is being displayed in
+ *
+ * Perform any OpenGL commands necessasairy to draw the object.
+ *
+ * The GL_PROJECTION and GL_MODELVIEW matricies and GL_ALL_ATTRIB_BITS will be
+ * restored to the default state after the call to draw.
+ */
+void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
+{
+       grits_object_pickdraw(object, opengl, FALSE);
+}
+
 void grits_object_hide(GritsObject *object, gboolean hidden)
 {
        GritsObjectClass *klass = GRITS_OBJECT_GET_CLASS(object);
@@ -135,6 +150,41 @@ void grits_object_queue_draw(GritsObject *object)
                gtk_widget_queue_draw(GTK_WIDGET(object->viewer));
 }
 
+/* Event handling */
+void grits_object_pick_begin(GritsObject *object, GritsOpenGL *opengl)
+{
+       object->state.picked = FALSE;
+
+       /* Check for connected signals */
+       for (int i = 0; i < NUM_SIGNALS; i++) {
+               if (g_signal_has_handler_pending(object, signals[i], 0, FALSE)) {
+                       /* Someone is watching, render the object _once_ */
+                       glPushName((guint)object);
+                       grits_object_pickdraw(object, opengl, TRUE);
+                       glPopName();
+                       return;
+               }
+       }
+}
+
+void grits_object_pick_pointer(GritsObject *object, double x, double y)
+{
+       object->state.picked = TRUE;
+}
+
+void grits_object_pick_end(GritsObject *object)
+{
+       if (object->state.picked) {
+               if (!object->state.selected)
+                       g_signal_emit(object, signals[SIG_ENTER], 0);
+               object->state.selected = TRUE;
+       } else {
+               if (object->state.selected)
+                       g_signal_emit(object, signals[SIG_LEAVE], 0);
+               object->state.selected = FALSE;
+       }
+}
+
 /* GObject stuff */
 G_DEFINE_ABSTRACT_TYPE(GritsObject, grits_object, G_TYPE_OBJECT);
 static void grits_object_init(GritsObject *object)
@@ -146,4 +196,40 @@ static void grits_object_init(GritsObject *object)
 
 static void grits_object_class_init(GritsObjectClass *klass)
 {
+       GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+       /**
+        * GritsObject::enter:
+        * @object: the object.
+        *
+        * The ::enter signal is emitted when the pointer moves over the object
+        */
+       signals[SIG_ENTER] = g_signal_new(
+                       "enter",
+                       G_TYPE_FROM_CLASS(gobject_class),
+                       G_SIGNAL_RUN_LAST,
+                       0,
+                       NULL,
+                       NULL,
+                       g_cclosure_marshal_VOID__VOID,
+                       G_TYPE_NONE,
+                       0);
+
+       /**
+        * GritsViewer::leave:
+        * @object: the object.
+        *
+        * The ::leave signal is emitted when the pointer moves away from the
+        * object
+        */
+       signals[SIG_LEAVE] = g_signal_new(
+                       "leave",
+                       G_TYPE_FROM_CLASS(gobject_class),
+                       G_SIGNAL_RUN_LAST,
+                       0,
+                       NULL,
+                       NULL,
+                       g_cclosure_marshal_VOID__VOID,
+                       G_TYPE_NONE,
+                       0);
 }
index de77ac5da4e0d75286c1c435f37484ddd7219997..d7cd2b0491277c9dac19d99947ec052536e821d6 100644 (file)
 #define GRITS_SKIP_CENTER  (1<<2)
 #define GRITS_SKIP_STATE   (1<<3)
 
+/* Picking states */
+typedef struct {
+       guint picked   : 1;
+       guint selected : 1;
+} GritsState;
+
 typedef struct _GritsObject      GritsObject;
 typedef struct _GritsObjectClass GritsObjectClass;
 
@@ -48,6 +54,8 @@ struct _GritsObject {
        gboolean     hidden; // If true, the object will not be drawn
        gdouble      lod;    // Level of detail, used to hide small objects
        guint32      skip;   // Bit mask of safe operations
+
+       GritsState   state;  // Internal, used for picking
 };
 
 struct _GritsObjectClass {
@@ -55,6 +63,7 @@ struct _GritsObjectClass {
 
        /* Move some of these to GObject? */
        void (*draw) (GritsObject *object, GritsOpenGL *opengl);
+       void (*pick) (GritsObject *object, GritsOpenGL *opengl);
        void (*hide) (GritsObject *object, gboolean hidden);
 };
 
@@ -65,6 +74,11 @@ void grits_object_draw(GritsObject *object, GritsOpenGL *opengl);
 
 void grits_object_hide(GritsObject *object, gboolean hidden);
 
+/* Interal, used by grits_opengl */
+void grits_object_pick_begin(GritsObject *object, GritsOpenGL *opengl);
+void grits_object_pick_pointer(GritsObject *object, double x, double y);
+void grits_object_pick_end(GritsObject *object);
+
 /**
  * grits_object_queue_draw:
  * @object: The #GritsObject that needs drawing
index 55d492a8f11a6892d4a1de4ab1e11a61c006eb29..da35b36c792333b7d043a174253f8be137997c3a 100644 (file)
@@ -110,6 +110,18 @@ static void grits_poly_draw(GritsObject *_poly, GritsOpenGL *opengl)
        glPopAttrib();
 }
 
+static void grits_poly_pick(GritsObject *_poly, GritsOpenGL *opengl)
+{
+       //g_debug("GritsPoly: pick");
+       GritsPoly *poly = GRITS_POLY(_poly);
+       if (!poly->list)
+               return;
+       glPushAttrib(GL_ENABLE_BIT);
+       glDisable(GL_CULL_FACE);
+       glCallList(poly->list+0);
+       glPopAttrib();
+}
+
 /**
  * grits_poly_new:
  * @points:  TODO
@@ -198,4 +210,5 @@ static void grits_poly_class_init(GritsPolyClass *klass)
 
        GritsObjectClass *object_class = GRITS_OBJECT_CLASS(klass);
        object_class->draw = grits_poly_draw;
+       object_class->pick = grits_poly_pick;
 }