From 1d0635977583ad84faaa1978f1fd78fa3ec83052 Mon Sep 17 00:00:00 2001 From: Andy Spencer Date: Sat, 15 Oct 2011 06:13:39 +0000 Subject: [PATCH] Add mouse enter/leave signals to objects 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 | 83 ++++++++++++++++++++++++--- src/objects/grits-object.c | 112 ++++++++++++++++++++++++++++++++----- src/objects/grits-object.h | 14 +++++ src/objects/grits-poly.c | 13 +++++ 4 files changed, 201 insertions(+), 21 deletions(-) diff --git a/src/grits-opengl.c b/src/grits-opengl.c index 9c3af08..423c849 100644 --- a/src/grits-opengl.c +++ b/src/grits-opengl.c @@ -46,6 +46,14 @@ /* 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); diff --git a/src/objects/grits-object.c b/src/objects/grits-object.c index 3cfd802..6abc4eb 100644 --- a/src/objects/grits-object.c +++ b/src/objects/grits-object.c @@ -34,18 +34,15 @@ #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); } diff --git a/src/objects/grits-object.h b/src/objects/grits-object.h index de77ac5..d7cd2b0 100644 --- a/src/objects/grits-object.h +++ b/src/objects/grits-object.h @@ -36,6 +36,12 @@ #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 diff --git a/src/objects/grits-poly.c b/src/objects/grits-poly.c index 55d492a..da35b36 100644 --- a/src/objects/grits-poly.c +++ b/src/objects/grits-poly.c @@ -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; } -- 2.41.0