comparison 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
comparison
equal deleted inserted replaced
231:2637519e2bd7 232:527894c2ad39
1 /*! \file
2 * \brief Convenience functions for event relative work.
3 */
1 #include <stdio.h> 4 #include <stdio.h>
2 #include <stdlib.h> 5 #include <stdlib.h>
3 #include <cairo.h> 6 #include <cairo.h>
4 #include "mb_types.h" 7 #include "mb_types.h"
5 #include "mb_redraw_man.h" 8 #include "mb_redraw_man.h"
6 #include "mb_shapes.h" 9 #include "mb_shapes.h"
7 10
8 #define OK 0 11 #define OK 0
9 #define ERR -1 12 #define ERR -1
13 #define FALSE 0
14 #define TRUE 1
10 15
11 #define ARRAY_EXT_SZ 64 16 #define ARRAY_EXT_SZ 64
12 17
13 18
14 DARRAY_DEFINE(geos, geo_t *); 19 DARRAY_DEFINE(geos, geo_t *);
18 * General geo list can use to temporary keep a list of geo_t 23 * General geo list can use to temporary keep a list of geo_t
19 * objects for any purpose. It supposed to be reused by 24 * objects for any purpose. It supposed to be reused by
20 * different modules that need to select part of geo_t objects 25 * different modules that need to select part of geo_t objects
21 * from a redraw manager. 26 * from a redraw manager.
22 */ 27 */
23 static int add_gen_geo(redraw_man_t *rdman, geo_t *geo) { 28 static int _add_gen_geo(redraw_man_t *rdman, geo_t *geo) {
24 int r; 29 int r;
25 30
26 r = geos_add(&rdman->gen_geos, geo); 31 r = geos_add(&rdman->gen_geos, geo);
27 return r == 0? OK: ERR; 32 return r == 0? OK: ERR;
28 } 33 }
29 34
30 static int collect_shapes_at_point(redraw_man_t *rdman, 35 static int _collect_geos_at_point(redraw_man_t *rdman,
31 co_aix x, co_aix y) { 36 co_aix x, co_aix y) {
32 geo_t *geo; 37 geo_t *geo;
33 int r; 38 int r;
34 39
35 r = rdman_force_clean(rdman); 40 r = rdman_force_clean(rdman);
40 45
41 for(geo = rdman_geos(rdman, NULL); 46 for(geo = rdman_geos(rdman, NULL);
42 geo != NULL; 47 geo != NULL;
43 geo = rdman_geos(rdman, geo)) { 48 geo = rdman_geos(rdman, geo)) {
44 if(geo_pos_is_in(geo, x, y)) { 49 if(geo_pos_is_in(geo, x, y)) {
45 r = add_gen_geo(rdman, geo); 50 r = _add_gen_geo(rdman, geo);
46 if(r != OK) 51 if(r != OK)
47 return ERR; 52 return ERR;
48 } 53 }
49 } 54 }
50 55
63 sh_rect_draw(shape, cr); 68 sh_rect_draw(shape, cr);
64 break; 69 break;
65 } 70 }
66 } 71 }
67 72
73 static int _shape_pos_is_in_cairo(shape_t *shape, co_aix x, co_aix y,
74 int *in_stroke, cairo_t *cr) {
75 draw_shape_path(shape, cr);
76 if(shape->fill) {
77 if(cairo_in_fill(cr, x, y)) {
78 *in_stroke = 0;
79 return TRUE;
80 }
81 }
82 if(shape->stroke) {
83 if(cairo_in_stroke(cr, x, y)) {
84 *in_stroke = 1;
85 return TRUE;
86 }
87 }
88 return FALSE;
89 }
90
68 static geo_t *find_geo_in_pos(redraw_man_t *rdman, 91 static geo_t *find_geo_in_pos(redraw_man_t *rdman,
69 co_aix x, co_aix y, int *in_stroke) { 92 co_aix x, co_aix y, int *in_stroke) {
70 geo_t *geo; 93 geo_t *geo;
71 geo_t **geos; 94 geo_t **geos;
72 shape_t *shape; 95 shape_t *shape;
73 cairo_t *cr; 96 cairo_t *cr;
74 int i; 97 int i, r;
75 98
76 geos = rdman->gen_geos.ds; 99 geos = rdman->gen_geos.ds;
77 cr = rdman->cr; 100 cr = rdman->cr;
78 for(i = rdman->gen_geos.num - 1; i >= 0; i--) { 101 for(i = rdman->gen_geos.num - 1; i >= 0; i--) {
79 geo = geos[i]; 102 geo = geos[i];
80 if(geo->flags & GEF_HIDDEN) 103 if(geo->flags & GEF_HIDDEN)
81 continue; 104 continue;
82 shape = geo->shape; 105 shape = geo_get_shape(geo);
83 draw_shape_path(shape, cr); 106 r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, cr);
84 if(shape->fill) { 107 cairo_new_path(cr);
85 if(cairo_in_fill(cr, x, y)) { 108 if(r)
86 *in_stroke = 0; 109 return geo;
87 cairo_new_path(rdman->cr);
88 return geo;
89 }
90 }
91 if(shape->stroke) {
92 if(cairo_in_stroke(cr, x, y)) {
93 *in_stroke = 1;
94 cairo_new_path(rdman->cr);
95 return geo;
96 }
97 }
98 cairo_new_path(rdman->cr);
99 } 110 }
100 111
101 return NULL; 112 return NULL;
102 } 113 }
103 114
104 shape_t *find_shape_at_pos(redraw_man_t *rdman, 115 shape_t *find_shape_at_pos(redraw_man_t *rdman,
105 co_aix x, co_aix y, int *in_stroke) { 116 co_aix x, co_aix y, int *in_stroke) {
106 geo_t *geo; 117 geo_t *geo;
107 int r; 118 int r;
108 119
109 r = collect_shapes_at_point(rdman, x, y); 120 r = _collect_geos_at_point(rdman, x, y);
110 if(r != OK) 121 if(r != OK)
111 return NULL; 122 return NULL;
112 123
113 geo = find_geo_in_pos(rdman, x, y, in_stroke); 124 geo = find_geo_in_pos(rdman, x, y, in_stroke);
114 if(geo == NULL) 125 if(geo == NULL)
115 return NULL; 126 return NULL;
116 127
117 return geo->shape; 128 return geo_get_shape(geo);
118 } 129 }
130
131 static
132 int _shape_pos_is_in(redraw_man_t *rdman, shape_t *shape,
133 co_aix x, co_aix y, int *in_stroke) {
134 geo_t *geo;
135 int r;
136
137 geo = sh_get_geo(shape);
138 r = geo_pos_is_in(geo, x, y);
139 if(!r)
140 return FALSE;
141
142 r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, rdman->cr);
143 if(!r)
144 return FALSE;
145
146 return TRUE;
147 }
148
149 /*! \brief Test if an object and descendants cover the position
150 * specified by x,y.
151 *
152 * \param in_stroke is x, y is on a stroke.
153 */
154 int mb_obj_pos_is_in(redraw_man_t *rdman, mb_obj_t *obj,
155 co_aix x, co_aix y, int *in_stroke) {
156 coord_t *cur_coord, *root;
157 shape_t *shape;
158 geo_t *geo;
159 int r;
160
161 if(IS_MBO_SHAPES(obj)) {
162 shape = (shape_t *)obj;
163 r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, rdman->cr);
164 return r;
165 }
166 root = (coord_t *)obj;
167 for(cur_coord = postorder_coord_subtree(root, NULL);
168 cur_coord != NULL;
169 cur_coord = postorder_coord_subtree(root, cur_coord)) {
170 FOR_COORD_MEMBERS(cur_coord, geo) {
171 shape = geo_get_shape(geo);
172 r = _shape_pos_is_in_cairo(shape, x, y, in_stroke, rdman->cr);
173 if(r)
174 return TRUE;
175 }
176 }
177 return FALSE;
178 }
179
180 static
181 cairo_t * _prepare_cairo_for_testing(redraw_man_t *rdman) {
182 cairo_surface_t *surface, *rdman_surface;
183 cairo_t *cr;
184 int w, h;
185
186 rdman_surface = cairo_get_target(rdman->cr);
187 w = cairo_image_surface_get_width(rdman_surface);
188 h = cairo_image_surface_get_height(rdman_surface);
189
190 surface = cairo_image_surface_create(CAIRO_FORMAT_A1, w, h);
191 if(surface == NULL)
192 return NULL;
193
194 cr = cairo_create(surface);
195 if(cr == NULL)
196 cairo_surface_destroy(surface);
197
198 return cr;
199 }
200
201 static
202 void _release_cairo_for_testing(cairo_t *cr) {
203 cairo_destroy(cr);
204 }
205
206 static _draw_to_mask(shape_t *shape, cairo_t *cr) {
207 geo_t *geo;
208
209 geo = sh_get_geo(shape);
210 if(geo->flags & GEF_OV_DRAW)
211 return;
212
213 draw_shape_path(shape, cr);
214 cairo_clip(cr);
215
216 geo->flags |= GEF_OV_DRAW;
217 }
218
219 static
220 int _fill_and_check(shape_t *shape, cairo_t *cr) {
221 int h, stride;
222 cairo_surface_t *surface;
223 unsigned char *data;
224 int i, sz;
225
226 draw_shape_path(shape, cr);
227 cairo_fill(cr);
228
229 surface = cairo_get_target(cr);
230 data = cairo_image_surface_get_data(surface);
231
232 h = cairo_image_surface_get_height(surface);
233 stride = cairo_image_surface_get_stride(surface);
234
235 sz = stride * h;
236 for(i = 0; i < sz; i++) {
237 if(data[i])
238 return TRUE;
239 }
240
241 return FALSE;
242 }
243
244 /*! \brief Is a mb_obj_t overlaid with another mb_object_t and
245 * descendants.
246 *
247 * coord is relative less than shapes. Check areas of coord can
248 * havily avoid useless computation. For shapes, it not only check
249 * overlay of area. It also check overlay by actually drawing on a
250 * cairo surface.
251 */
252 static
253 int _is_obj_objs_overlay(mb_obj_t *obj, mb_obj_t *others_root,
254 cairo_t *cr) {
255 area_t *area, *candi_area;
256 coord_t *coord, *candi_coord, *root;
257 shape_t *shape, *candi_shape;
258 geo_t *geo, *candi_geo;
259 int obj_is_shape;
260 int r;
261 /*
262 */
263
264 obj_is_shape = IS_MBO_SHAPES(obj);
265
266 if(obj_is_shape) {
267 shape = (shape_t *)obj;
268 geo = sh_get_geo(shape);
269 area = geo_get_area(geo);
270 } else {
271 coord = (coord_t *)obj;
272 area = coord_get_area(coord);
273 }
274
275 if(IS_MBO_SHAPES(others_root)) {
276 candi_shape = (shape_t *)others_root;
277 candi_geo = sh_get_geo(candi_shape);
278 candi_area = geo_get_area(candi_geo);
279
280 r = is_overlay(area, candi_area);
281 if(!r)
282 return FALSE;
283
284 if(!obj_is_shape)
285 return TRUE;
286
287 _draw_to_mask(candi_shape, cr);
288 r = _fill_and_check(shape, cr);
289
290 return r;
291 }
292
293 root = (coord_t *)others_root;
294 FOR_COORDS_PREORDER(root, candi_coord) {
295 candi_area = coord_get_area(candi_coord);
296 r = is_overlay(area, candi_area);
297 if(!r) {
298 preorder_coord_skip_subtree(coord);
299 continue;
300 }
301
302 FOR_COORD_MEMBERS(coord, candi_geo) {
303 candi_area = geo_get_area(candi_geo);
304 r = is_overlay(area, candi_area);
305 if(!r)
306 continue;
307
308 if(!obj_is_shape)
309 return TRUE;
310
311 _draw_to_mask(candi_shape, cr);
312 r = _fill_and_check(shape, cr);
313 if(r)
314 return TRUE;
315 }
316 }
317
318 return FALSE;
319 }
320
321 /*! \brief Test if two objects are overlaid.
322 *
323 * \todo Detect overlay in better way with cairo.
324 * \note This function cost heavy on CPU power.
325 */
326 int mb_objs_is_overlay(redraw_man_t *rdman,
327 mb_obj_t *obj1, mb_obj_t *obj2) {
328 cairo_t *cr;
329 area_t *area;
330 shape_t *shape;
331 geo_t *geo;
332 coord_t *coord, *root;
333 int r;
334
335 cr = _prepare_cairo_for_testing(rdman);
336
337 if(IS_MBO_SHAPES(obj1)) {
338 shape = (shape_t *)obj1;
339 r = _is_obj_objs_overlay(obj1, obj2, cr);
340 goto out;
341 }
342
343 root = (coord_t *)obj1;
344 FOR_COORDS_PREORDER(root, coord) {
345 area = coord_get_area(coord);
346 r = _is_obj_objs_overlay((mb_obj_t *)coord, obj2, cr);
347 if(!r) {
348 preorder_coord_skip_subtree(coord);
349 continue;
350 }
351
352 FOR_COORD_MEMBERS(coord, geo) {
353 shape = geo_get_shape(geo);
354 r = _is_obj_objs_overlay((mb_obj_t *)shape, obj2, cr);
355 if(r)
356 goto out;
357 }
358 }
359 r = FALSE;
360
361 out:
362 _release_cairo_for_testing(cr);
363 return r;
364 }