15 |
16 | Polygon representation
17 |
18 |
19 | Polygons in gfxpoly are represented as a set of line segments.
20 | The internal representation doesn't have a way to represent curves (splines),
21 | however it's possible to convert splines to a gfxpoly structure
22 | by approximating them with lines (See creating polygons.)
23 |
24 |
25 |
26 | Polygon endpoints, furthermore, are rounded to a grid (i.e., represented as integer coordinates.)
27 | During polygon intersection, the resulting polygons are again rounded- also, new intersection
28 | points will cause adjacent lines (i.e. lines passing through that grid point)
29 | to be "routed" through the intersection point, a concept known as "snap rounding to hot pixels":
30 |
31 |
32 |
33 |
34 |
35 | This approach ensures robustness of intersection, since there's always a unique representation of
36 | the output polygon (as opposed to doing polygon intersection on floating point numbers where
37 | due to numerical precision, intersection points might now have an actual representation as floating
38 | point number, leading to errors when they are "rounded" to a float.)
39 |
40 |
41 |
42 | There are a lot of academic papers about why snap rounding is preferrable to numeric approaches.
43 | gfxpoly uses the algorithm from Hobby
44 | with a few additional ideas
45 | from Hershberger and
46 | Bhattacharya et al.
47 |
48 |
49 | (In the above figure, you might have noticed that gfxpoly snaps to the lower right corner
50 | of grid points, instead of the center- this is an optimization that makes snapping faster)
51 |
52 |
53 | Creating polygons
54 |
55 |
56 | The easiest way to create a gfxpoly polygon is to first represent
57 | the shape you want to process as an outline of lines and splines:
58 |
59 |
60 |
61 | double gridsize = 0.01;
62 |
63 | gfxline_t*outline = NULL;
64 | outline = gfx_moveTo(outline, 0,0);
65 | outline = gfx_lineTo(outline, 100,0);
66 | outline = gfx_lineTo(outline, 100,100);
67 | outline = gfx_splineTo(outline, 50,50, 0,100);
68 | outline = gfx_lineTo(outline, 0,0);
69 |
70 | gfxpoly_t*poly = gfxpoly_from_fill(outline, gridsize);
71 |
72 |
73 |
74 | Here, gridsize is the spacing of grid points- i.e., the numerical
75 | precision of your coordinate system. So a coordinate like 7.392 will
76 | be truncated to 7.39 during conversion to gfxpoly_t (and since gfxpoly_t
77 | represents coordinates as integers, be internally stored as 739.)
78 |
79 |
80 |
81 | You can also create polygons from a stroke:
82 |
83 |
84 |
85 | gfxpoly_t*poly = gfxpoly_from_stroke(outline, /*line width*/10.0, gfx_capRound, gfx_joinRound, 0.0, gridsize);
86 |
87 |
88 |
89 | The parameters are defined as following:
90 |
91 |
92 |
93 |
94 | Boolean operations between polygons
95 |
96 | Once you have a representation of your polygon as gfxpoly you
97 | can intersect polygons:
98 |
99 |
100 | gfxpoly_t*poly12 = gfxpoly_intersect(poly1, poly2);
101 |
102 |
103 | You can also build the union of two polygons:
104 |
105 |
106 | gfxpoly_t*poly12 = gfxpoly_union(poly1, poly2);
107 |
108 |
109 | It also sometimes makes sense to intersect a polygon with itself (for example,
110 | to determine which parts of it are filled.)
111 | There are two convenience functions that apply the corresponding fillstyle
112 | and generate a self-intersected polygon:
113 |
114 |
115 |
116 | gfxpoly_t* new_polygon = gfxpoly_selfintersect_evenodd(poly);
117 |
118 |
119 |
120 | gfxpoly_t* new_polygon = gfxpoly_selfintersect_circular(poly);
121 |
122 |
123 |
124 |
125 | Notice: gfxpoly_intersect, gfxpoly_union and gfxpoly_selfintersect_* are really just convenience
126 | wrappers for gfxpoly_process. See gfxpoly_process.
127 | By using gfxpoly_process directly, you can implement other polygon operations, like symmetric
128 | difference, overlay, transparency etc.
129 |
130 |
131 | Polygon area and moments
132 |
133 |
134 | You can also compute the area of a polygon:
135 |
136 |
137 |
138 | double area = gfxpoly_area(poly);
139 |
140 |
141 |
142 | If you're interested in the degree of overlap between two polygons, there's
143 | a shortcut that does gfxpoly_intersect and gfxpoly_area in
144 | a single step:
145 |
146 |
147 |
148 | double area = gfxpoly_intersection_area(poly1, poly2);
149 |
150 |
151 | Advanced
152 | Creating gfxpoly through gfxcanvas
153 |
154 | Representing polygons as a gfxline_t list first has the disadvantage
155 | of having to allocate that structure first. A faster way is to use the
156 | gfxcanvas_t abstraction to convert a shape to a gfxpoly on the fly:
157 |
158 |
159 |
160 | gfxcanvas_t*canvas = gfxcanvas_new(gridsize);
161 | canvas->moveTo(canvas, 0,0);
162 | canvas->lineTo(canvas, 100,0);
163 | canvas->lineTo(canvas, 100,100);
164 | canvas->splineTo(canvas, 50,50, 0,100);
165 | canvas->lineTo(canvas, 0,0);
166 | canvas->close(canvas);
167 | gfxpoly_t* poly = (gfxpoly_t*)canvas->result(canvas);
168 |
169 |
170 |
171 | This also has the advantage that you can overlay multiple input polygons using
172 | the same gfxcanvas object, and then later process them in one go.
173 | Use canvas->setUserData(canvas, data) to store polygon-specific data. This
174 | data will later be passed back to you by windrule.add().
175 |
176 |
177 | gfxpoly_process
178 |
179 |
180 | Internally, for every polygon operation, a scanline pass is run over all
181 | the line segments using gfxpoly_process. It has the following prototype:
182 |
183 |
184 |
185 | gfxpoly_t* gfxpoly_process(gfxpoly_t*poly1, gfxpoly_t*poly2,
186 | windrule_t*windrule, windcontext_t*context,
187 | moments_t*moments);
188 |
189 |
190 | To implement custom polygon operations, you would create your own windcontext.
191 | To understand how this works, we need to talk about scanline algorithms first.
192 | A scanline algorithm processes a set of line segments from top to bottom (or left to right,
193 | depending on the implementation), and during every scanline, shoots an imaginary ray from negative
194 | infinity to positive infinity, looking at which polygon segments it hits, and in which order:
195 |
196 |
197 |
198 |
199 |
200 |
201 | The state of the ray is represented as a windstate_t structure- it e.g. stores
202 | whether the current polygon area we're in is filled or not, or how many polygons are currently
203 | on top of each other.
204 |
205 |
206 | A windrule_t structure specifies what should happen to the windstate_t every
207 | time we pass through an edge, it's initial value (at negative infinity) and also, when generating
208 | the output polygon, what kind of edge to place between two different windstate_t areas.
209 |
210 |
211 | An edgestyle_t structure is attached to every edge, and can store user data that applies
212 | to that edge (e.g. for polygon overlays, the color of the polygon belonging to that edge.)
213 |
214 |
215 | Hence, for creating your own windrule, you have to implement the following functions:
216 |
217 |
218 |
219 | windstate_t start(windcontext_t*context);
220 | windstate_t add(windcontext_t*context, windstate_t left, edgestyle_t*edge, segment_dir_t dir, int polygon_nr);
221 | edgestyle_t* diff(windstate_t*left, windstate_t*right);
222 |
223 |
224 |
225 |
226 |
227 |
228 | winrules / fillstyles
229 |
230 | gfxpoly already contains windrule (a.k.a. fillstyle) implementations for:
231 |
232 | - Even/Odd self-intersection (windrule_evenodd)
233 | - Circular self-intersection (windrule_circular)
234 | - Two polygon intersection (windrule_intersect)
235 | - Two polygon union (windrule_union)
236 |
237 |
238 |
239 |