changeset 232:527894c2ad39

Add functions for collision test. - mb_obj_pos_is_in() test if two coords and their descendants are overlaid. - mb_objs_is_overlay() test if a point is covered by another mb_obj_t.
author Thinker K.F. Li <thinker@branda.to>
date Sun, 21 Dec 2008 23:30:00 +0800
parents 2637519e2bd7
children ec62453bbb2b
files include/mb_redraw_man.h include/mb_types.h src/event.c
diffstat 3 files changed, 291 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/include/mb_redraw_man.h	Thu Dec 18 22:37:15 2008 +0800
+++ b/include/mb_redraw_man.h	Sun Dec 21 23:30:00 2008 +0800
@@ -165,6 +165,10 @@
 
 extern shape_t *find_shape_at_pos(redraw_man_t *rdman,
 				  co_aix x, co_aix y, int *in_stroke);
+extern int mb_obj_pos_is_in(redraw_man_t *rdman, mb_obj_t *obj,
+			    co_aix x, co_aix y, int *in_stroke);
+extern int mb_objs_is_overlay(redraw_man_t *rdman,
+			      mb_obj_t *obj1, mb_obj_t *obj2);
 #define rdman_get_ob_factory(rdman) (&(rdman)->ob_factory)
 #define rdman_get_redraw_subject(rdman) ((rdman)->redraw)
 #define rdman_get_root(rdman) ((rdman)->root_coord)
@@ -182,4 +186,5 @@
  */
 void sprite_set_search_path(char *path);
 
+
 #endif /* __REDRAW_MAN_H_ */
--- a/include/mb_types.h	Thu Dec 18 22:37:15 2008 +0800
+++ b/include/mb_types.h	Sun Dec 21 23:30:00 2008 +0800
@@ -55,6 +55,7 @@
 #define mb_obj_destroy(obj)
 #define mb_obj_prop_store(obj) (&(obj)->props)
 
+
 /* @} */
 
 /*! \brief Base of paint types.
@@ -103,6 +104,7 @@
 #define GEF_DIRTY 0x1
 #define GEF_HIDDEN 0x2		/*!< The geo is hidden. */
 #define GEF_FREE 0x4
+#define GEF_OV_DRAW 0x8		/*!< To flag drawed for a overlay testing. */
 
 extern int is_overlay(area_t *r1, area_t *r2);
 extern void area_init(area_t *area, int n_pos, co_aix pos[][2]);
@@ -116,6 +118,7 @@
 #define geo_pos_is_in(g, _x, _y)				\
     (_geo_is_in(_x, (g)->cur_area->x, (g)->cur_area->w) &&	\
      _geo_is_in(_y, (g)->cur_area->y, (g)->cur_area->h))
+#define geo_get_area(g) ((g)->cur_area)
 
 
 /*! \brief A coordination system.
@@ -181,7 +184,6 @@
 extern void update_aggr_matrix(coord_t *start);
 extern coord_t *preorder_coord_subtree(coord_t *root, coord_t *last);
 extern coord_t *postorder_coord_subtree(coord_t *root, coord_t *last);
-extern void preorder_coord_skip_subtree(coord_t *subroot);
 #define preorder_coord_skip_subtree(sub)		\
     do { (sub)->flags |= COF_SKIP_TRIVAL; } while(0)
 #define coord_hide(co)		      \
@@ -190,6 +192,14 @@
     } while(0)
 #define coord_show(co) do { co->flags &= ~COF_HIDDEN; } while(0)
 #define coord_get_mouse_event(coord) ((coord)->mouse_event)
+#define FOR_COORDS_POSTORDER(coord, cur)			\
+    for((cur) = postorder_coord_subtree((coord), NULL);		\
+	(cur) != NULL;						\
+	(cur) = postorder_coord_subtree((coord), (cur)))
+#define FOR_COORDS_PREORDER(coord, cur)			\
+    for((cur) = (coord);				\
+	(cur) != NULL;					\
+	(cur) = preorder_coord_subtree((coord), (cur)))
 
 /*! \brief Coord operation function
  * These functions are used to move and scale the coord_t. Programmers should use these functions instead of using the matrix directly.
@@ -203,6 +213,11 @@
 #define coord_scaley(ci) ((co)->matrix[3])
 #define coord_x(ci) ((co)->matrix[2])
 #define coord_y(ci) ((co)->matrix[5])
+#define FOR_COORD_MEMBERS(coord, geo)			\
+    for(geo = STAILQ_HEAD((coord)->members);		\
+	geo != NULL;					\
+	geo = STAILQ_NEXT(geo_t, coord_next, geo))
+#define coord_get_area(coord) ((coord)->cur_area)
 
 /*! \brief A grahpic shape.
  *
@@ -238,6 +253,7 @@
     do {						\
 	(sh)->geo->flags &= ~GEF_HIDDEN;		\
     } while(0)
+#define sh_get_geo(sh) ((sh)->geo)
 
 
 /*! \brief A sprite is a set of graphics that being an object in animation.
--- a/src/event.c	Thu Dec 18 22:37:15 2008 +0800
+++ b/src/event.c	Sun Dec 21 23:30:00 2008 +0800
@@ -1,3 +1,6 @@
+/*! \file
+ * \brief Convenience functions for event relative work.
+ */
 #include <stdio.h>
 #include <stdlib.h>
 #include <cairo.h>
@@ -7,6 +10,8 @@
 
 #define OK 0
 #define ERR -1
+#define FALSE 0
+#define TRUE 1
 
 #define ARRAY_EXT_SZ 64
 
@@ -20,14 +25,14 @@
  * different modules that need to select part of geo_t objects
  * from a redraw manager.
  */
-static int add_gen_geo(redraw_man_t *rdman, geo_t *geo) {
+static int _add_gen_geo(redraw_man_t *rdman, geo_t *geo) {
     int r;
 
     r = geos_add(&rdman->gen_geos, geo);
     return r == 0? OK: ERR;
 }
 
-static int collect_shapes_at_point(redraw_man_t *rdman,
+static int _collect_geos_at_point(redraw_man_t *rdman,
 				   co_aix x, co_aix y) {
     geo_t *geo;
     int r;
@@ -42,7 +47,7 @@
 	geo != NULL;
 	geo = rdman_geos(rdman, geo)) {
 	if(geo_pos_is_in(geo, x, y)) {
-	    r = add_gen_geo(rdman, geo);
+	    r = _add_gen_geo(rdman, geo);
 	    if(r != OK)
 		return ERR;
 	}
@@ -65,13 +70,31 @@
     }
 }
 
+static int _shape_pos_is_in_cairo(shape_t *shape, co_aix x, co_aix y,
+				  int *in_stroke, cairo_t *cr) {
+    draw_shape_path(shape, cr);
+    if(shape->fill) {
+	if(cairo_in_fill(cr, x, y)) {
+	    *in_stroke = 0;
+	    return TRUE;
+	}
+    }
+    if(shape->stroke) {
+	if(cairo_in_stroke(cr, x, y)) {
+	    *in_stroke = 1;
+	    return TRUE;
+	}
+    }
+    return FALSE;
+}
+
 static geo_t *find_geo_in_pos(redraw_man_t *rdman,
 			      co_aix x, co_aix y, int *in_stroke) {
     geo_t *geo;
     geo_t **geos;
     shape_t *shape;
     cairo_t *cr;
-    int i;
+    int i, r;
 
     geos = rdman->gen_geos.ds;
     cr = rdman->cr;
@@ -79,23 +102,11 @@
 	geo = geos[i];
 	if(geo->flags & GEF_HIDDEN)
 	    continue;
-	shape = geo->shape;
-	draw_shape_path(shape, cr);
-	if(shape->fill) {
-	    if(cairo_in_fill(cr, x, y)) {
-		*in_stroke = 0;
-		cairo_new_path(rdman->cr);
-		return geo;
-	    }
-	}
-	if(shape->stroke) {
-	    if(cairo_in_stroke(cr, x, y)) {
-		*in_stroke = 1;
-		cairo_new_path(rdman->cr);
-		return geo;
-	    }
-	}
-	cairo_new_path(rdman->cr);
+	shape = geo_get_shape(geo);
+	r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, cr);
+	cairo_new_path(cr);
+	if(r)
+	    return geo;
     }
 
     return NULL;
@@ -106,7 +117,7 @@
     geo_t *geo;
     int r;
 
-    r = collect_shapes_at_point(rdman, x, y);
+    r = _collect_geos_at_point(rdman, x, y);
     if(r != OK)
 	return NULL;
 
@@ -114,5 +125,240 @@
     if(geo == NULL)
 	return NULL;
 
-    return geo->shape;
+    return geo_get_shape(geo);
+}
+
+static
+int _shape_pos_is_in(redraw_man_t *rdman, shape_t *shape,
+		     co_aix x, co_aix y, int *in_stroke) {
+    geo_t *geo;
+    int r;
+
+    geo = sh_get_geo(shape);
+    r = geo_pos_is_in(geo, x, y);
+    if(!r)
+	return FALSE;
+
+    r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, rdman->cr);
+    if(!r)
+	return FALSE;
+
+    return TRUE;
+}
+
+/*! \brief Test if an object and descendants cover the position
+ *	specified by x,y.
+ *
+ * \param in_stroke is x, y is on a stroke.
+ */
+int mb_obj_pos_is_in(redraw_man_t *rdman, mb_obj_t *obj,
+		     co_aix x, co_aix y, int *in_stroke) {
+    coord_t *cur_coord, *root;
+    shape_t *shape;
+    geo_t *geo;
+    int r;
+
+    if(IS_MBO_SHAPES(obj)) {
+	shape = (shape_t *)obj;
+	r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, rdman->cr);
+	return r;
+    }
+    root = (coord_t *)obj;
+    for(cur_coord = postorder_coord_subtree(root, NULL);
+	cur_coord != NULL;
+	cur_coord = postorder_coord_subtree(root, cur_coord)) {
+	FOR_COORD_MEMBERS(cur_coord, geo) {
+	    shape = geo_get_shape(geo);
+	    r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, rdman->cr);
+	    if(r)
+		return TRUE;
+	}
+    }
+    return FALSE;
+}
+
+static
+cairo_t * _prepare_cairo_for_testing(redraw_man_t *rdman) {
+    cairo_surface_t *surface, *rdman_surface;
+    cairo_t *cr;
+    int w, h;
+
+    rdman_surface = cairo_get_target(rdman->cr);
+    w = cairo_image_surface_get_width(rdman_surface);
+    h = cairo_image_surface_get_height(rdman_surface);
+    
+    surface = cairo_image_surface_create(CAIRO_FORMAT_A1, w, h);
+    if(surface == NULL)
+	return NULL;
+
+    cr = cairo_create(surface);
+    if(cr == NULL)
+	cairo_surface_destroy(surface);
+    
+    return cr;
+}
+
+static
+void _release_cairo_for_testing(cairo_t *cr) {
+    cairo_destroy(cr);
+}
+
+static _draw_to_mask(shape_t *shape, cairo_t *cr) {
+    geo_t *geo;
+
+    geo = sh_get_geo(shape);
+    if(geo->flags & GEF_OV_DRAW)
+	return;
+    
+    draw_shape_path(shape, cr);
+    cairo_clip(cr);
+    
+    geo->flags |= GEF_OV_DRAW;
+}
+
+static
+int _fill_and_check(shape_t *shape, cairo_t *cr) {
+    int h, stride;
+    cairo_surface_t *surface;
+    unsigned char *data;
+    int i, sz;
+
+    draw_shape_path(shape, cr);
+    cairo_fill(cr);
+
+    surface = cairo_get_target(cr);
+    data = cairo_image_surface_get_data(surface);
+
+    h = cairo_image_surface_get_height(surface);
+    stride = cairo_image_surface_get_stride(surface);
+
+    sz = stride * h;
+    for(i = 0; i < sz; i++) {
+	if(data[i])
+	    return TRUE;
+    }
+
+    return FALSE;
 }
+
+/*! \brief Is a mb_obj_t overlaid with another mb_object_t and
+ *	descendants.
+ *
+ * coord is relative less than shapes.  Check areas of coord can
+ * havily avoid useless computation.  For shapes, it not only check
+ * overlay of area.  It also check overlay by actually drawing on a
+ * cairo surface.
+ */
+static
+int _is_obj_objs_overlay(mb_obj_t *obj, mb_obj_t *others_root,
+			 cairo_t *cr) {
+    area_t *area, *candi_area;
+    coord_t *coord, *candi_coord, *root;
+    shape_t *shape, *candi_shape;
+    geo_t *geo, *candi_geo;
+    int obj_is_shape;
+    int r;
+    /*
+     */
+    
+    obj_is_shape = IS_MBO_SHAPES(obj);
+    
+    if(obj_is_shape) {
+	shape = (shape_t *)obj;
+	geo = sh_get_geo(shape);
+	area = geo_get_area(geo);
+    } else {
+	coord = (coord_t *)obj;
+	area = coord_get_area(coord);
+    }
+	
+    if(IS_MBO_SHAPES(others_root)) {
+	candi_shape = (shape_t *)others_root;
+	candi_geo = sh_get_geo(candi_shape);
+	candi_area =  geo_get_area(candi_geo);
+	
+	r = is_overlay(area, candi_area);
+	if(!r)
+	    return FALSE;
+	
+	if(!obj_is_shape)
+	    return TRUE;
+	
+	_draw_to_mask(candi_shape, cr);
+	r = _fill_and_check(shape, cr);
+	
+	return r;
+    }
+    
+    root = (coord_t *)others_root;
+    FOR_COORDS_PREORDER(root, candi_coord) {
+	candi_area = coord_get_area(candi_coord);
+	r = is_overlay(area, candi_area);
+	if(!r) {
+	    preorder_coord_skip_subtree(coord);
+	    continue;
+	}
+	
+	FOR_COORD_MEMBERS(coord, candi_geo) {
+	    candi_area = geo_get_area(candi_geo);
+	    r = is_overlay(area, candi_area);
+	    if(!r)
+		continue;
+	    
+	    if(!obj_is_shape)
+		return TRUE;
+	    
+	    _draw_to_mask(candi_shape, cr);
+	    r = _fill_and_check(shape, cr);
+	    if(r)
+		return TRUE;
+	}
+    }
+    
+    return FALSE;
+}
+
+/*! \brief Test if two objects are overlaid.
+ *
+ * \todo Detect overlay in better way with cairo.
+ * \note This function cost heavy on CPU power.
+ */
+int mb_objs_is_overlay(redraw_man_t *rdman,
+		       mb_obj_t *obj1, mb_obj_t *obj2) {
+    cairo_t *cr;
+    area_t *area;
+    shape_t *shape;
+    geo_t *geo;
+    coord_t *coord, *root;
+    int r;
+
+    cr = _prepare_cairo_for_testing(rdman);
+
+    if(IS_MBO_SHAPES(obj1)) {
+	shape = (shape_t *)obj1;
+	r = _is_obj_objs_overlay(obj1, obj2, cr);
+	goto out;
+    }
+    
+    root = (coord_t *)obj1;
+    FOR_COORDS_PREORDER(root, coord) {
+	area = coord_get_area(coord);
+	r = _is_obj_objs_overlay((mb_obj_t *)coord, obj2, cr);
+	if(!r) {
+	    preorder_coord_skip_subtree(coord);
+	    continue;
+	}
+
+	FOR_COORD_MEMBERS(coord, geo) {
+	    shape = geo_get_shape(geo);
+	    r = _is_obj_objs_overlay((mb_obj_t *)shape, obj2, cr);
+	    if(r)
+		goto out;
+	}
+    }
+    r = FALSE;
+    
+ out:
+    _release_cairo_for_testing(cr);
+    return r;
+}