1
by Jasper van de Gronde
Initial commit |
1 |
/**
|
2 |
* \brief Shapes are special paths on which boolops can be performed
|
|
3 |
*
|
|
4 |
* Authors:
|
|
5 |
* Michael G. Sloan <mgsloan@gmail.com>
|
|
6 |
* Nathan Hurst <njh@mail.csse.monash.edu.au>
|
|
7 |
* MenTaLguY <mental@rydia.net>
|
|
8 |
*
|
|
9 |
* Copyright 2007-2009 Authors
|
|
10 |
*
|
|
11 |
* This library is free software; you can redistribute it and/or
|
|
12 |
* modify it either under the terms of the GNU Lesser General Public
|
|
13 |
* License version 2.1 as published by the Free Software Foundation
|
|
14 |
* (the "LGPL") or, at your option, under the terms of the Mozilla
|
|
15 |
* Public License Version 1.1 (the "MPL"). If you do not alter this
|
|
16 |
* notice, a recipient may use your version of this file under either
|
|
17 |
* the MPL or the LGPL.
|
|
18 |
*
|
|
19 |
* You should have received a copy of the LGPL along with this library
|
|
20 |
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
|
|
21 |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
22 |
* You should have received a copy of the MPL along with this library
|
|
23 |
* in the file COPYING-MPL-1.1
|
|
24 |
*
|
|
25 |
* The contents of this file are subject to the Mozilla Public License
|
|
26 |
* Version 1.1 (the "License"); you may not use this file except in
|
|
27 |
* compliance with the License. You may obtain a copy of the License at
|
|
28 |
* http://www.mozilla.org/MPL/
|
|
29 |
*
|
|
30 |
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
|
|
31 |
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
|
|
32 |
* the specific language governing rights and limitations.
|
|
33 |
*
|
|
34 |
*/
|
|
35 |
||
36 |
#include <2geom/shape.h> |
|
37 |
||
38 |
#include <2geom/utils.h> |
|
39 |
#include <2geom/sweep.h> |
|
40 |
#include <2geom/ord.h> |
|
41 |
||
42 |
#include <iostream> |
|
43 |
#include <algorithm> |
|
44 |
#include <cstdlib> |
|
45 |
||
46 |
//#define SHAPE_DEBUG // turns on debug outputting to cout.
|
|
47 |
||
48 |
namespace Geom { |
|
49 |
||
50 |
// A little sugar for appending a list to another
|
|
51 |
template<typename T> |
|
52 |
void append(T &a, T const &b) { |
|
53 |
a.insert(a.end(), b.begin(), b.end()); |
|
54 |
}
|
|
55 |
||
56 |
//Orders a list of indices according to their containment within eachother.
|
|
57 |
struct ContainmentOrder { |
|
58 |
std::vector<Region> const *rs; |
|
59 |
explicit ContainmentOrder(std::vector<Region> const *r) : rs(r) {} |
|
60 |
bool operator()(unsigned a, unsigned b) const { return (*rs)[b].contains((*rs)[a]); } |
|
61 |
};
|
|
62 |
||
63 |
//Returns the list of regions containing a particular point. Useful in tandem with ContainmentOrder
|
|
64 |
std::vector<unsigned> Shape::containment_list(Point p) const { |
|
65 |
std::vector<Rect> pnt; |
|
66 |
pnt.push_back(Rect(p, p)); |
|
67 |
std::vector<std::vector<unsigned> > cull = sweep_bounds(pnt, bounds(*this)); |
|
68 |
std::vector<unsigned> containers; |
|
69 |
if(cull[0].size() == 0) return containers; |
|
70 |
for(unsigned i = 0; i < cull[0].size(); i++) |
|
71 |
if(content[cull[0][i]].contains(p)) containers.push_back(cull[0][i]); |
|
72 |
return containers; |
|
73 |
}
|
|
74 |
||
75 |
/* Used within shape_boolean and related functions, as the name describes, finds the
|
|
76 |
* first false within the list of lists of booleans.
|
|
77 |
*/
|
|
78 |
void first_false(std::vector<std::vector<bool> > visited, unsigned &i, unsigned &j) { |
|
79 |
for(i = 0, j = 0; i < visited.size(); i++) { |
|
80 |
std::vector<bool>::iterator unvisited = std::find(visited[i].begin(), visited[i].end(), false); |
|
81 |
if(unvisited != visited[i].end()) { |
|
82 |
j = unvisited - visited[i].begin(); |
|
83 |
break; |
|
84 |
}
|
|
85 |
}
|
|
86 |
}
|
|
87 |
||
88 |
// Finds a crossing in a list of them, given the sorting index.
|
|
89 |
unsigned find_crossing(Crossings const &cr, Crossing x, unsigned i) { |
|
90 |
return std::lower_bound(cr.begin(), cr.end(), x, CrossingOrder(i)) - cr.begin(); |
|
91 |
}
|
|
92 |
||
93 |
/* This function handles boolean ops on shapes. The first parameter is a bool
|
|
94 |
* which determines its behavior in each combination of cases. For proper
|
|
95 |
* fill information and noncrossing behavior, the fill data of the regions
|
|
96 |
* must be correct. The boolean parameter determines whether the operation
|
|
97 |
* is a union or a subtraction. Reversed paths represent inverse regions,
|
|
98 |
* where everything is included in the fill except for the insides.
|
|
99 |
*
|
|
100 |
* Here is a chart of the behavior under various circumstances:
|
|
101 |
*
|
|
102 |
* rev = false (union)
|
|
103 |
* A
|
|
104 |
* F H
|
|
105 |
* F A+B -> F A-B -> H
|
|
106 |
*B
|
|
107 |
* H B-A -> H AxB -> H
|
|
108 |
*
|
|
109 |
* rev = true (intersect)
|
|
110 |
* A
|
|
111 |
* F H
|
|
112 |
* F AxB -> F B-A -> F
|
|
113 |
*B
|
|
114 |
* H A-B -> F A+B -> H
|
|
115 |
*
|
|
116 |
* F/H = Fill outer / Hole outer
|
|
117 |
* A/B specify operands
|
|
118 |
* + = union, - = subtraction, x = intersection
|
|
119 |
* -> read as "produces"
|
|
120 |
*
|
|
121 |
* This is the main function of boolops, yet its operation isn't very complicated.
|
|
122 |
* It traverses the crossings, and uses the crossing direction to decide whether
|
|
123 |
* the next segment should be taken from A or from B. The second half of the
|
|
124 |
* function deals with figuring out what to do with bits that have no intersection.
|
|
125 |
*/
|
|
126 |
Shape shape_boolean(bool rev, Shape const & a, Shape const & b, CrossingSet const & crs) { |
|
127 |
const Regions ac = a.content, bc = b.content; |
|
128 |
||
129 |
//Keep track of which crossings we've hit.
|
|
130 |
std::vector<std::vector<bool> > visited; |
|
131 |
for(unsigned i = 0; i < crs.size(); i++) |
|
132 |
visited.push_back(std::vector<bool>(crs[i].size(), false)); |
|
133 |
||
134 |
//Traverse the crossings, creating chunks
|
|
135 |
Regions chunks; |
|
136 |
while(true) { |
|
137 |
unsigned i, j; |
|
138 |
first_false(visited, i, j); |
|
139 |
if(i == visited.size()) break; |
|
140 |
||
141 |
Path res; |
|
142 |
do { |
|
143 |
Crossing cur = crs[i][j]; |
|
144 |
visited[i][j] = true; |
|
145 |
||
146 |
//get indices of the dual:
|
|
147 |
unsigned io = cur.getOther(i), jo = find_crossing(crs[io], cur, io); |
|
148 |
if(jo < visited[io].size()) visited[io][jo] = true; |
|
149 |
||
150 |
//main driving logic
|
|
151 |
if(logical_xor(cur.dir, rev)) { |
|
152 |
if(i >= ac.size()) { i = io; j = jo; } |
|
153 |
j++; |
|
154 |
if(j >= crs[i].size()) j = 0; |
|
155 |
Crossing next = crs[i][j]; |
|
156 |
ac[next.a].boundary.appendPortionTo(res, cur.ta, next.ta); |
|
157 |
} else { |
|
158 |
if(i < ac.size()) { i = io; j = jo; } |
|
159 |
j++; |
|
160 |
if(j >= crs[i].size()) j = 0; |
|
161 |
Crossing next = crs[i][j]; |
|
162 |
bc[next.b - ac.size()].boundary.appendPortionTo(res, cur.tb, next.tb); |
|
163 |
}
|
|
164 |
} while (!visited[i][j]); |
|
165 |
if(res.size() > 0) chunks.push_back(Region(res)); |
|
166 |
}
|
|
167 |
||
168 |
//If true, then we are on the 'subtraction diagonal'
|
|
169 |
bool const on_sub = logical_xor(a.fill, b.fill); |
|
170 |
//If true, outer paths are filled
|
|
171 |
bool const res_fill = rev ? (on_sub || (a.fill && b.fill)) : (a.fill && b.fill); |
|
172 |
||
173 |
//Handle unintersecting portions
|
|
174 |
for(unsigned i = 0; i < crs.size(); i++) { |
|
175 |
if(crs[i].size() == 0) { |
|
176 |
bool env; |
|
177 |
bool on_a = i < ac.size(); |
|
178 |
Region const & r(on_a ? ac[i] : bc[i - ac.size()]); |
|
179 |
Shape const & other(on_a ? b : a); |
|
180 |
||
181 |
std::vector<unsigned> containers = other.containment_list(r.boundary.initialPoint()); |
|
182 |
if(containers.empty()) { |
|
183 |
//not included in any container, the environment fill is the opposite of the outer fill
|
|
184 |
env = !res_fill; |
|
185 |
if(on_sub && logical_xor(other.fill, res_fill)) env = !env; //If on the subtractor, invert the environment fill |
|
186 |
} else { |
|
187 |
//environment fill is the same as the inner-most container
|
|
188 |
std::vector<unsigned>::iterator cit = std::min_element(containers.begin(), containers.end(), ContainmentOrder(&other.content)); |
|
189 |
env = other[*cit].isFill(); |
|
190 |
}
|
|
191 |
if(!logical_xor(rev, env)) chunks.push_back(r); //When unioning, environment must be hole for inclusion, when intersecting, it must be filled |
|
192 |
}
|
|
193 |
}
|
|
194 |
||
195 |
return Shape(chunks, res_fill); |
|
196 |
}
|
|
197 |
||
198 |
// Just a convenience wrapper for shape_boolean, which handles the crossings
|
|
199 |
Shape shape_boolean(bool rev, Shape const & a, Shape const & b) { |
|
200 |
CrossingSet crs = crossings_between(a, b); |
|
201 |
||
202 |
return shape_boolean(rev, a, b, crs); |
|
203 |
}
|
|
204 |
||
205 |
||
206 |
// Some utility functions for boolop:
|
|
207 |
||
208 |
std::vector<double> region_sizes(Shape const &a) { |
|
209 |
std::vector<double> ret; |
|
210 |
for(unsigned i = 0; i < a.size(); i++) { |
|
211 |
ret.push_back(double(a[i].size())); |
|
212 |
}
|
|
213 |
return ret; |
|
214 |
}
|
|
215 |
||
216 |
Shape shape_boolean_ra(bool rev, Shape const &a, Shape const &b, CrossingSet const &crs) { |
|
217 |
return shape_boolean(rev, a.inverse(), b, reverse_ta(crs, a.size(), region_sizes(a))); |
|
218 |
}
|
|
219 |
||
220 |
Shape shape_boolean_rb(bool rev, Shape const &a, Shape const &b, CrossingSet const &crs) { |
|
221 |
return shape_boolean(rev, a, b.inverse(), reverse_tb(crs, a.size(), region_sizes(b))); |
|
222 |
}
|
|
223 |
||
224 |
/* This is a function based on shape_boolean which allows boolean operations
|
|
225 |
* to be specified as a logic table. This logic table is 4 bit-flags, which
|
|
226 |
* correspond to the elements of the 'truth table' for a particular operation.
|
|
227 |
* These flags are defined with the enums starting with BOOLOP_ .
|
|
228 |
*
|
|
229 |
* NOTE: currently doesn't work, as the CrossingSet reversal functions crash
|
|
230 |
*/
|
|
231 |
Shape boolop(Shape const &a, Shape const &b, unsigned flags, CrossingSet const &crs) { |
|
232 |
THROW_NOTIMPLEMENTED(); |
|
233 |
flags &= 15; |
|
234 |
if(flags <= BOOLOP_UNION) { |
|
235 |
switch(flags) { |
|
236 |
case BOOLOP_INTERSECT: return shape_boolean(true, a, b, crs); |
|
237 |
case BOOLOP_SUBTRACT_A_B: return shape_boolean_rb(true, a, b, crs); |
|
238 |
case BOOLOP_IDENTITY_A: return a; |
|
239 |
case BOOLOP_SUBTRACT_B_A: return shape_boolean_ra(true, a, b, crs); |
|
240 |
case BOOLOP_IDENTITY_B: return b; |
|
241 |
case BOOLOP_EXCLUSION: { |
|
242 |
Shape res = shape_boolean_rb(true, a, b, crs); |
|
243 |
append(res.content, shape_boolean_ra(true, a, b, crs).content); |
|
244 |
return res; |
|
245 |
}
|
|
246 |
case BOOLOP_UNION: return shape_boolean(false, a, b); |
|
247 |
}
|
|
248 |
} else { |
|
249 |
flags = ~flags & 15; |
|
250 |
switch(flags - BOOLOP_NEITHER) { |
|
251 |
case BOOLOP_SUBTRACT_A_B: return shape_boolean_ra(false, a, b, crs); |
|
252 |
case BOOLOP_SUBTRACT_B_A: return shape_boolean_rb(false, a, b, crs); |
|
253 |
case BOOLOP_EXCLUSION: { |
|
254 |
Shape res = shape_boolean_ra(false, a, b, CrossingSet(crs)); |
|
255 |
append(res.content, shape_boolean_rb(false, a, b, CrossingSet(crs)).content); |
|
256 |
return res; |
|
257 |
}
|
|
258 |
}
|
|
259 |
return boolop(a, b, flags, crs).inverse(); |
|
260 |
}
|
|
261 |
return Shape(); |
|
262 |
}
|
|
263 |
||
264 |
/* This version of the boolop function doesn't require a set of crossings, as
|
|
265 |
* it computes them for you. This is more efficient in some cases, as the
|
|
266 |
* shape can be inverted before finding crossings. In the special case of
|
|
267 |
* exclusion it uses the other version of boolop.
|
|
268 |
*/
|
|
269 |
Shape boolop(Shape const &a, Shape const &b, unsigned flags) { |
|
270 |
flags &= 15; |
|
271 |
if(flags <= BOOLOP_UNION) { |
|
272 |
switch(flags) { |
|
273 |
case BOOLOP_INTERSECT: return shape_boolean(true, a, b); |
|
274 |
case BOOLOP_SUBTRACT_A_B: return shape_boolean(true, a, b.inverse()); |
|
275 |
case BOOLOP_IDENTITY_A: return a; |
|
276 |
case BOOLOP_SUBTRACT_B_A: return shape_boolean(true, b, a.inverse()); |
|
277 |
case BOOLOP_IDENTITY_B: return b; |
|
278 |
case BOOLOP_EXCLUSION: { |
|
279 |
Shape res = shape_boolean(true, a, b.inverse()); |
|
280 |
append(res.content, shape_boolean(true, b, a.inverse()).content); |
|
281 |
return res; |
|
282 |
} //return boolop(a, b, flags, crossings_between(a, b)); |
|
283 |
case BOOLOP_UNION: return shape_boolean(false, a, b); |
|
284 |
}
|
|
285 |
} else { |
|
286 |
flags = ~flags & 15; |
|
287 |
switch(flags) { |
|
288 |
case BOOLOP_SUBTRACT_A_B: return shape_boolean(false, b, a.inverse()); |
|
289 |
case BOOLOP_SUBTRACT_B_A: return shape_boolean(false, a, b.inverse()); |
|
290 |
case BOOLOP_EXCLUSION: { |
|
291 |
Shape res = shape_boolean(false, a, b.inverse()); |
|
292 |
append(res.content, shape_boolean(false, b, a.inverse()).content); |
|
293 |
return res; |
|
294 |
} //return boolop(a, b, flags, crossings_between(a, b)); |
|
295 |
}
|
|
296 |
return boolop(a, b, flags).inverse(); |
|
297 |
}
|
|
298 |
return Shape(); |
|
299 |
}
|
|
300 |
||
301 |
int paths_winding(std::vector<Path> const &ps, Point p) { |
|
302 |
int ret = 0; |
|
303 |
for(unsigned i = 0; i < ps.size(); i++) |
|
304 |
ret += winding(ps[i], p); |
|
305 |
return ret; |
|
306 |
}
|
|
307 |
||
308 |
void add_to_shape(Shape &s, Path const &p, bool fill) { |
|
309 |
if(fill) |
|
310 |
s.content.push_back(Region(p).asFill()); |
|
311 |
else
|
|
312 |
s.content.push_back(Region(p).asHole()); |
|
313 |
}
|
|
314 |
||
315 |
int inner_winding(Path const & p, std::vector<Path> const &ps) { |
|
316 |
Point pnt = p.initialPoint(); |
|
317 |
return paths_winding(ps, pnt) - winding(p, pnt) + 1; |
|
318 |
}
|
|
319 |
||
320 |
double fudgerize(double d, bool rev) { |
|
321 |
double ret = rev ? d - 0.01 : d + 0.01; |
|
322 |
if(ret < 0) ret = 0; |
|
323 |
return ret; |
|
324 |
}
|
|
325 |
||
326 |
unsigned pick_coincident(unsigned ix, unsigned jx, bool &rev, std::vector<Path> const &ps, CrossingSet const &crs) { |
|
327 |
unsigned ex_jx = jx; |
|
328 |
unsigned oix = crs[ix][jx].getOther(ix); |
|
329 |
double otime = crs[ix][jx].getTime(oix); |
|
330 |
Point cross_point = ps[oix].pointAt(otime), |
|
331 |
along = ps[oix].pointAt(fudgerize(otime, rev)) - cross_point, |
|
332 |
prev = -along; |
|
333 |
bool ex_dir = rev; |
|
334 |
for(unsigned k = jx; k < crs[ix].size(); k++) { |
|
335 |
unsigned koix = crs[ix][k].getOther(ix); |
|
336 |
if(koix == oix) { |
|
337 |
if(!are_near(otime, crs[ix][k].getTime(oix))) break; |
|
338 |
for(unsigned dir = 0; dir < 2; dir++) { |
|
339 |
Point val = ps[ix].pointAt(fudgerize(crs[ix][k].getTime(ix), dir)) - cross_point; |
|
340 |
Cmp to_prev = cmp(cross(val, prev), 0); |
|
341 |
Cmp from_along = cmp(cross(along, val), 0); |
|
342 |
Cmp c = cmp(from_along, to_prev); |
|
343 |
if(c == EQUAL_TO && from_along == LESS_THAN) { |
|
344 |
ex_jx = k; |
|
345 |
prev = val; |
|
346 |
ex_dir = dir; |
|
347 |
}
|
|
348 |
}
|
|
349 |
}
|
|
350 |
}
|
|
351 |
rev = ex_dir; |
|
352 |
return ex_jx; |
|
353 |
}
|
|
354 |
||
355 |
unsigned crossing_along(double t, unsigned ix, unsigned jx, bool dir, Crossings const & crs) { |
|
356 |
Crossing cur = Crossing(t, t, ix, ix, false); |
|
357 |
if(jx < crs.size()) { |
|
358 |
double ct = crs[jx].getTime(ix); |
|
359 |
if(t == ct) { |
|
360 |
cur = crs[jx]; |
|
361 |
if(cur.a == cur.b) { |
|
362 |
if(jx+1 <= crs.size() && crs[jx+1].getOther(ix) == ix) return jx+1; |
|
363 |
if(jx > 0 && crs[jx-1].getOther(ix) == ix) return jx-1; |
|
364 |
}
|
|
365 |
}
|
|
366 |
}
|
|
367 |
if(!dir) { |
|
368 |
jx = std::upper_bound(crs.begin(), crs.end(), cur, CrossingOrder(ix)) - crs.begin(); |
|
369 |
} else { |
|
370 |
jx = std::lower_bound(crs.begin(), crs.end(), cur, CrossingOrder(ix)) - crs.begin(); |
|
371 |
if(jx == 0) jx = crs.size() - 1; else jx--; |
|
372 |
jx = std::lower_bound(crs.begin(), crs.end(), crs[jx], CrossingOrder(ix)) - crs.begin(); |
|
373 |
}
|
|
374 |
if(jx >= crs.size()) jx = 0; |
|
375 |
return jx; |
|
376 |
}
|
|
377 |
||
378 |
void crossing_dual(unsigned &i, unsigned &j, CrossingSet const & crs) { |
|
379 |
Crossing cur = crs[i][j]; |
|
380 |
i = cur.getOther(i); |
|
381 |
#ifdef SHAPE_DEBUG
|
|
382 |
std::cout << i << "\n"; |
|
383 |
#endif
|
|
384 |
if(crs[i].empty()) |
|
385 |
j = 0; |
|
386 |
else
|
|
387 |
j = std::lower_bound(crs[i].begin(), crs[i].end(), cur, CrossingOrder(i)) - crs[i].begin(); |
|
388 |
}
|
|
389 |
||
390 |
//locate a crossing on the outside, by casting a ray through the middle of the bbox
|
|
391 |
void outer_crossing(unsigned &ix, unsigned &jx, bool & dir, std::vector<Path> const & ps, CrossingSet const & crs) { |
|
392 |
Rect bounds = *(ps[ix].boundsFast()); |
|
393 |
double ry = bounds[Y].middle(); |
|
394 |
double max_val = bounds.left(), max_t = 0; |
|
395 |
ix = ps.size(); |
|
396 |
for(unsigned i = 0; i < ps.size(); i++) { |
|
397 |
if(!crs[i].empty()) { |
|
398 |
std::vector<double> rts = ps[i].roots(ry, Y); |
|
399 |
for(unsigned j = 0; j < rts.size(); j++) { |
|
400 |
double val = ps[i].valueAt(rts[j], X); |
|
401 |
if(val > max_val) { |
|
402 |
ix = i; |
|
403 |
max_val = val; |
|
404 |
max_t = rts[j]; |
|
405 |
}
|
|
406 |
}
|
|
407 |
}
|
|
408 |
}
|
|
409 |
if(ix != ps.size()) { |
|
410 |
dir = ps[ix].valueAt(max_t + 0.01, Y) > |
|
411 |
ps[ix].valueAt(max_t - 0.01, Y); |
|
412 |
jx = crossing_along(max_t, ix, jx, dir, crs[ix]); |
|
413 |
}
|
|
414 |
}
|
|
415 |
||
416 |
std::vector<Path> inner_sanitize(std::vector<Path> const & ps) { |
|
417 |
CrossingSet crs(crossings_among(ps)); |
|
418 |
||
419 |
Regions chunks; |
|
420 |
||
421 |
std::vector<bool> used_path(ps.size(), false); |
|
422 |
std::vector<std::vector<bool> > visited; |
|
423 |
for(unsigned i = 0; i < crs.size(); i++) |
|
424 |
visited.push_back(std::vector<bool>(crs[i].size(), false)); |
|
425 |
||
426 |
std::vector<Path> result_paths; |
|
427 |
||
428 |
while(true) { |
|
429 |
unsigned ix = 0, jx = 0; |
|
430 |
bool dir = false; |
|
431 |
||
432 |
//find an outer crossing by trying various paths and checking if the crossings are used
|
|
433 |
for(; ix < crs.size(); ix++) { |
|
434 |
//TODO: optimize so it doesn't unecessarily check stuff
|
|
435 |
bool cont = true; |
|
436 |
for(unsigned j = 0; j < crs[ix].size(); j++) { |
|
437 |
if(!visited[ix][j]) { cont = false; break; } |
|
438 |
}
|
|
439 |
if(cont) continue; |
|
440 |
unsigned rix = ix, rjx = jx; |
|
441 |
outer_crossing(rix, rjx, dir, ps, crs); |
|
442 |
if(rix >= crs.size() || visited[rix][rjx]) continue; |
|
443 |
ix = rix; jx = rjx; |
|
444 |
break; |
|
445 |
}
|
|
446 |
if(ix == crs.size()) break; |
|
447 |
crossing_dual(ix, jx, crs); |
|
448 |
||
449 |
dir = !dir; |
|
450 |
||
451 |
Path res; |
|
452 |
do { |
|
453 |
visited[ix][jx] = true; |
|
454 |
//unsigned nix = ix, njx = jx;
|
|
455 |
//crossing_dual(nix, njx, crs);
|
|
456 |
//visited[nix][njx] = true;
|
|
457 |
unsigned fix = ix, fjx = jx; |
|
458 |
||
459 |
bool new_dir = dir; |
|
460 |
||
461 |
jx = crossing_along(crs[ix][jx].getTime(ix), ix, jx, dir, crs[ix]); |
|
462 |
if(crs[ix][jx].a != crs[ix][jx].b) crossing_dual(ix, jx, crs); else new_dir = !new_dir; |
|
463 |
jx = pick_coincident(ix, jx, new_dir, ps, crs); |
|
464 |
||
465 |
//unsigned nix = ix, njx = jx;
|
|
466 |
//crossing_dual(nix, njx, crs);
|
|
467 |
||
468 |
Crossing from = crs[fix][fjx], |
|
469 |
to = crs[ix][jx]; |
|
470 |
if(dir) { |
|
471 |
// backwards
|
|
472 |
#ifdef SHAPE_DEBUG
|
|
473 |
std::cout << "r" << ix << "[" << from.getTime(ix) << ", " << to.getTime(ix) << "]\n"; |
|
474 |
#endif
|
|
475 |
Path p = ps[ix].portion(from.getTime(ix), to.getTime(ix)).reverse(); |
|
476 |
for(unsigned i = 0; i < p.size(); i++) |
|
477 |
res.append(p[i], Path::STITCH_DISCONTINUOUS); |
|
478 |
} else { |
|
479 |
// forwards
|
|
480 |
#ifdef SHAPE_DEBUG
|
|
481 |
std::cout << "f" << ix << "[" << from.getTime(ix) << ", " << to.getTime(ix) << "]\n"; |
|
482 |
#endif
|
|
483 |
ps[ix].appendPortionTo(res, from.getTime(ix), to.getTime(ix)); |
|
484 |
}
|
|
485 |
dir = new_dir; |
|
486 |
} while(!visited[ix][jx]); |
|
487 |
#ifdef SHAPE_DEBUG
|
|
488 |
std::cout << "added " << res.size() << "\n"; |
|
489 |
#endif
|
|
490 |
result_paths.push_back(res); |
|
491 |
}
|
|
492 |
for(unsigned i = 0; i < crs.size(); i++) { |
|
493 |
if(crs[i].empty() && !used_path[i]) |
|
494 |
result_paths.push_back(ps[i]); |
|
495 |
}
|
|
496 |
return result_paths; |
|
497 |
}
|
|
498 |
||
499 |
Shape sanitize(std::vector<Path> const & ps) { |
|
500 |
std::vector<Path> res; |
|
501 |
for(unsigned i = 0; i < ps.size(); i++) { |
|
502 |
append(res, inner_sanitize(std::vector<Path>(1, ps[i]))); |
|
503 |
}
|
|
504 |
return stopgap_cleaner(res); |
|
505 |
}
|
|
506 |
||
507 |
/* WIP sanitizer:
|
|
508 |
unsigned pick_coincident(unsigned ix, unsigned jx, bool pref, bool &rev, std::vector<Path> const &ps, CrossingSet const &crs) {
|
|
509 |
unsigned ex_jx = jx;
|
|
510 |
unsigned oix = crs[ix][jx].getOther(ix);
|
|
511 |
double otime = crs[ix][jx].getTime(oix);
|
|
512 |
Point cross_point = ps[oix].pointAt(otime),
|
|
513 |
along = ps[oix].pointAt(otime + (rev ? -0.01 : 0.01)) - cross_point,
|
|
514 |
prev = -along;
|
|
515 |
bool ex_dir = rev;
|
|
516 |
for(unsigned k = jx; k < crs[ix].size(); k++) {
|
|
517 |
unsigned koix = crs[ix][k].getOther(ix);
|
|
518 |
if(koix == oix) {
|
|
519 |
if(!are_near(otime, crs[ix][k].getTime(oix))) break;
|
|
520 |
for(unsigned dir = 0; dir < 2; dir++) {
|
|
521 |
Point val = ps[ix].pointAt(crs[ix][k].getTime(ix) + (dir ? -0.01 : 0.01)) - cross_point;
|
|
522 |
Cmp to_prev = cmp(cross(val, prev), 0);
|
|
523 |
Cmp from_along = cmp(cross(along, val), 0);
|
|
524 |
Cmp c = cmp(from_along, to_prev);
|
|
525 |
if(c == EQUAL_TO && (from_along == LESS_THAN) == pref) {
|
|
526 |
ex_jx = k;
|
|
527 |
prev = val;
|
|
528 |
ex_dir = dir;
|
|
529 |
}
|
|
530 |
}
|
|
531 |
}
|
|
532 |
}
|
|
533 |
rev = ex_dir;
|
|
534 |
return ex_jx;
|
|
535 |
}
|
|
536 |
||
537 |
unsigned corner_index(unsigned &i) {
|
|
538 |
div_t div_res = div(i, 4);
|
|
539 |
i = div_res.quot;
|
|
540 |
return div_res.rem;
|
|
541 |
}
|
|
542 |
||
543 |
bool corner_direction(unsigned ix, unsigned jc, unsigned corner, CrossingSet const &crs) {
|
|
544 |
if(crs[ix][jc].a == ix) return corner > 1; else return corner %2 == 1;
|
|
545 |
}
|
|
546 |
||
547 |
Shape sanitize(std::vector<Path> const & ps) {
|
|
548 |
CrossingSet crs = crossings_among(ps);
|
|
549 |
|
|
550 |
//Keep track of which CORNERS we've hit.
|
|
551 |
// FF FR RF RR, first is A dir, second B dir
|
|
552 |
std::vector<std::vector<bool> > visited;
|
|
553 |
for(unsigned i = 0; i < crs.size(); i++)
|
|
554 |
visited.push_back(std::vector<bool>(crs[i].size()*4, false));
|
|
555 |
|
|
556 |
Regions chunks;
|
|
557 |
while(true) {
|
|
558 |
unsigned i, j;
|
|
559 |
first_false(visited, i, j);
|
|
560 |
unsigned corner = corner_index(j);
|
|
561 |
|
|
562 |
if(i == visited.size()) break;
|
|
563 |
|
|
564 |
bool dir = corner_direction(i, j, corner, crs);
|
|
565 |
|
|
566 |
//Figure out whether we hug the path cw or ccw, based on the orientation of the initial corner:
|
|
567 |
unsigned oix = crs[i][j].getOther(i);
|
|
568 |
double otime = crs[i][j].getTime(oix);
|
|
569 |
bool odir = (oix == crs[i][j].a) ? corner > 1 : corner % 2 == 1;
|
|
570 |
Point cross_point = ps[oix].pointAt(otime),
|
|
571 |
along = ps[oix].pointAt(otime + (odir ? -0.01 : 0.01)) - cross_point,
|
|
572 |
val = ps[i].pointAt(crs[i][j].getTime(i) + (dir ? -0.01 : 0.01)) - cross_point;
|
|
573 |
|
|
574 |
Cmp from_along = cmp(cross(along, val), 0);
|
|
575 |
bool cw = from_along == LESS_THAN;
|
|
576 |
std::cout << "cw = " << cw << "\n";
|
|
577 |
Path res;
|
|
578 |
do {
|
|
579 |
Crossing cur = crs[i][j];
|
|
580 |
visited[i][j*4+corner] = true;
|
|
581 |
|
|
582 |
unsigned fix = i, fjx = j;
|
|
583 |
crossing_dual(i, j, crs);
|
|
584 |
visited[i][j*4+corner] = true;
|
|
585 |
i = fix; j = fjx;
|
|
586 |
|
|
587 |
j = crossing_along(crs[i][j].getTime(i), i, j, dir, crs[i]);
|
|
588 |
|
|
589 |
crossing_dual(i, j, crs);
|
|
590 |
|
|
591 |
bool new_dir = dir;
|
|
592 |
pick_coincident(i, j, cw, new_dir, ps, crs);
|
|
593 |
|
|
594 |
Crossing from = crs[fix][fjx],
|
|
595 |
to = crs[i][j];
|
|
596 |
if(dir) {
|
|
597 |
// backwards
|
|
598 |
std::cout << "r" << i << "[" << to.getTime(i) << ", " << from.getTime(i) << "]\n";
|
|
599 |
Path p = ps[i].portion(to.getTime(i) + 0.001, from.getTime(i)).reverse();
|
|
600 |
for(unsigned k = 0; k < p.size(); k++)
|
|
601 |
res.append(p[k]);
|
|
602 |
} else {
|
|
603 |
// forwards
|
|
604 |
std::cout << "f" << i << "[" << from.getTime(i) << ", " << to.getTime(i) << "]\n";
|
|
605 |
ps[i].appendPortionTo(res, from.getTime(i) + 0.001, to.getTime(i));
|
|
606 |
}
|
|
607 |
if(i == to.a)
|
|
608 |
corner = (new_dir ? 2 : 0) + (dir ? 1 : 0);
|
|
609 |
else
|
|
610 |
corner = (new_dir ? 1 : 0) + (dir ? 2 : 0);
|
|
611 |
dir = new_dir;
|
|
612 |
} while(!visited[i][j*4+corner]);
|
|
613 |
chunks.push_back(Region(res));
|
|
614 |
// if(use) {
|
|
615 |
// chunks.push_back(Region(res, true));
|
|
616 |
// }
|
|
617 |
}
|
|
618 |
return Shape(chunks);
|
|
619 |
// return ret;
|
|
620 |
} */
|
|
621 |
||
622 |
/* This transforms a shape by a matrix. In the case that the matrix flips
|
|
623 |
* the shape, it reverses the paths in order to preserve the fill.
|
|
624 |
*/
|
|
625 |
Shape Shape::operator*(Matrix const &m) const { |
|
626 |
Shape ret; |
|
627 |
for(unsigned i = 0; i < size(); i++) |
|
628 |
ret.content.push_back(content[i] * m); |
|
629 |
ret.fill = fill; |
|
630 |
return ret; |
|
631 |
}
|
|
632 |
||
633 |
// Inverse is a boolean not, and simply reverses all the paths & fill flags
|
|
634 |
Shape Shape::inverse() const { |
|
635 |
Shape ret; |
|
636 |
for(unsigned i = 0; i < size(); i++) |
|
637 |
ret.content.push_back(content[i].inverse()); |
|
638 |
ret.fill = !fill; |
|
639 |
return ret; |
|
640 |
}
|
|
641 |
||
642 |
bool Shape::contains(Point const &p) const { |
|
643 |
std::vector<unsigned> containers = containment_list(p); |
|
644 |
if(containers.empty()) return !isFill(); |
|
645 |
unsigned ix = *min_element(containers.begin(), containers.end(), ContainmentOrder(&content)); |
|
646 |
return content[ix].isFill(); |
|
647 |
}
|
|
648 |
||
649 |
Shape stopgap_cleaner(std::vector<Path> const &ps) { |
|
650 |
if(ps.empty()) return Shape(false); |
|
651 |
Shape ret; |
|
652 |
for(unsigned i = 0; i < ps.size(); i++) |
|
653 |
add_to_shape(ret, ps[i], inner_winding(ps[i], ps) % 2 != 0); |
|
654 |
return ret; |
|
655 |
}
|
|
656 |
||
657 |
bool Shape::inside_invariants() const { //semi-slow & easy to violate |
|
658 |
for(unsigned i = 0; i < size(); i++) |
|
659 |
if( logical_xor(content[i].isFill(), contains(content[i].boundary.initialPoint())) ) return false; |
|
660 |
return true; |
|
661 |
}
|
|
662 |
bool Shape::region_invariants() const { //semi-slow |
|
663 |
for(unsigned i = 0; i < size(); i++) |
|
664 |
if(!content[i].invariants()) return false; |
|
665 |
return true; |
|
666 |
}
|
|
667 |
bool Shape::cross_invariants() const { //slow |
|
668 |
CrossingSet crs; // = crossings_among(paths_from_regions(content)); |
|
669 |
for(unsigned i = 0; i < crs.size(); i++) |
|
670 |
if(!crs[i].empty()) return false; |
|
671 |
return true; |
|
672 |
}
|
|
673 |
||
674 |
bool Shape::invariants() const { |
|
675 |
return inside_invariants() && region_invariants() && cross_invariants(); |
|
676 |
}
|
|
677 |
||
678 |
}
|
|
679 |
||
680 |
/*
|
|
681 |
Local Variables:
|
|
682 |
mode:c++
|
|
683 |
c-file-style:"stroustrup"
|
|
684 |
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
|
|
685 |
indent-tabs-mode:nil
|
|
686 |
fill-column:99
|
|
687 |
End:
|
|
688 |
*/
|
|
689 |
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
|