Mercurial > MadButterfly
diff src/event.c @ 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 | c234ee745ceb |
children | 65cabbdd5284 |
line wrap: on
line diff
--- 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; +}