Mercurial > MadButterfly
view 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 source
/*! \file * \brief Convenience functions for event relative work. */ #include <stdio.h> #include <stdlib.h> #include <cairo.h> #include "mb_types.h" #include "mb_redraw_man.h" #include "mb_shapes.h" #define OK 0 #define ERR -1 #define FALSE 0 #define TRUE 1 #define ARRAY_EXT_SZ 64 DARRAY_DEFINE(geos, geo_t *); /*! \brief Add a geo_t object to general geo list. * * General geo list can use to temporary keep a list of geo_t * objects for any purpose. It supposed to be reused by * 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) { int r; r = geos_add(&rdman->gen_geos, geo); return r == 0? OK: ERR; } static int _collect_geos_at_point(redraw_man_t *rdman, co_aix x, co_aix y) { geo_t *geo; int r; r = rdman_force_clean(rdman); if(r != OK) return ERR; rdman->gen_geos.num = 0; for(geo = rdman_geos(rdman, NULL); geo != NULL; geo = rdman_geos(rdman, geo)) { if(geo_pos_is_in(geo, x, y)) { r = _add_gen_geo(rdman, geo); if(r != OK) return ERR; } } return OK; } static void draw_shape_path(shape_t *shape, cairo_t *cr) { switch(MBO_TYPE(shape)) { case MBO_PATH: sh_path_draw(shape, cr); break; case MBO_TEXT: sh_text_draw(shape, cr); break; case MBO_RECT: sh_rect_draw(shape, cr); break; } } 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, r; geos = rdman->gen_geos.ds; cr = rdman->cr; for(i = rdman->gen_geos.num - 1; i >= 0; i--) { geo = geos[i]; if(geo->flags & GEF_HIDDEN) continue; 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; } shape_t *find_shape_at_pos(redraw_man_t *rdman, co_aix x, co_aix y, int *in_stroke) { geo_t *geo; int r; r = _collect_geos_at_point(rdman, x, y); if(r != OK) return NULL; geo = find_geo_in_pos(rdman, x, y, in_stroke); if(geo == NULL) return NULL; 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; }