1 | /* |
2 | * Copyright 2015 Google Inc. |
3 | * |
4 | * Use of this source code is governed by a BSD-style license that can be |
5 | * found in the LICENSE file. |
6 | */ |
7 | |
8 | // given a prospective edge, compute its initial winding by projecting a ray |
9 | // if the ray hits another edge |
10 | // if the edge doesn't have a winding yet, hop up to that edge and start over |
11 | // concern : check for hops forming a loop |
12 | // if the edge is unsortable, or |
13 | // the intersection is nearly at the ends, or |
14 | // the tangent at the intersection is nearly coincident to the ray, |
15 | // choose a different ray and try again |
16 | // concern : if it is unable to succeed after N tries, try another edge? direction? |
17 | // if no edge is hit, compute the winding directly |
18 | |
19 | // given the top span, project the most perpendicular ray and look for intersections |
20 | // let's try up and then down. What the hey |
21 | |
22 | // bestXY is initialized by caller with basePt |
23 | |
24 | #include "src/pathops/SkOpContour.h" |
25 | #include "src/pathops/SkOpSegment.h" |
26 | #include "src/pathops/SkPathOpsCurve.h" |
27 | |
28 | #include <utility> |
29 | |
30 | enum class SkOpRayDir { |
31 | kLeft, |
32 | kTop, |
33 | kRight, |
34 | kBottom, |
35 | }; |
36 | |
37 | #if DEBUG_WINDING |
38 | const char* gDebugRayDirName[] = { |
39 | "kLeft" , |
40 | "kTop" , |
41 | "kRight" , |
42 | "kBottom" |
43 | }; |
44 | #endif |
45 | |
46 | static int xy_index(SkOpRayDir dir) { |
47 | return static_cast<int>(dir) & 1; |
48 | } |
49 | |
50 | static SkScalar pt_xy(const SkPoint& pt, SkOpRayDir dir) { |
51 | return (&pt.fX)[xy_index(dir)]; |
52 | } |
53 | |
54 | static SkScalar pt_yx(const SkPoint& pt, SkOpRayDir dir) { |
55 | return (&pt.fX)[!xy_index(dir)]; |
56 | } |
57 | |
58 | static double pt_dxdy(const SkDVector& v, SkOpRayDir dir) { |
59 | return (&v.fX)[xy_index(dir)]; |
60 | } |
61 | |
62 | static double pt_dydx(const SkDVector& v, SkOpRayDir dir) { |
63 | return (&v.fX)[!xy_index(dir)]; |
64 | } |
65 | |
66 | static SkScalar rect_side(const SkRect& r, SkOpRayDir dir) { |
67 | return (&r.fLeft)[static_cast<int>(dir)]; |
68 | } |
69 | |
70 | static bool sideways_overlap(const SkRect& rect, const SkPoint& pt, SkOpRayDir dir) { |
71 | int i = !xy_index(dir); |
72 | return approximately_between((&rect.fLeft)[i], (&pt.fX)[i], (&rect.fRight)[i]); |
73 | } |
74 | |
75 | static bool less_than(SkOpRayDir dir) { |
76 | return static_cast<bool>((static_cast<int>(dir) & 2) == 0); |
77 | } |
78 | |
79 | static bool ccw_dxdy(const SkDVector& v, SkOpRayDir dir) { |
80 | bool vPartPos = pt_dydx(v, dir) > 0; |
81 | bool leftBottom = ((static_cast<int>(dir) + 1) & 2) != 0; |
82 | return vPartPos == leftBottom; |
83 | } |
84 | |
85 | struct SkOpRayHit { |
86 | SkOpRayDir makeTestBase(SkOpSpan* span, double t) { |
87 | fNext = nullptr; |
88 | fSpan = span; |
89 | fT = span->t() * (1 - t) + span->next()->t() * t; |
90 | SkOpSegment* segment = span->segment(); |
91 | fSlope = segment->dSlopeAtT(fT); |
92 | fPt = segment->ptAtT(fT); |
93 | fValid = true; |
94 | return fabs(fSlope.fX) < fabs(fSlope.fY) ? SkOpRayDir::kLeft : SkOpRayDir::kTop; |
95 | } |
96 | |
97 | SkOpRayHit* fNext; |
98 | SkOpSpan* fSpan; |
99 | SkPoint fPt; |
100 | double fT; |
101 | SkDVector fSlope; |
102 | bool fValid; |
103 | }; |
104 | |
105 | void SkOpContour::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, |
106 | SkArenaAlloc* allocator) { |
107 | // if the bounds extreme is outside the best, we're done |
108 | SkScalar baseXY = pt_xy(base.fPt, dir); |
109 | SkScalar boundsXY = rect_side(fBounds, dir); |
110 | bool checkLessThan = less_than(dir); |
111 | if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { |
112 | return; |
113 | } |
114 | SkOpSegment* testSegment = &fHead; |
115 | do { |
116 | testSegment->rayCheck(base, dir, hits, allocator); |
117 | } while ((testSegment = testSegment->next())); |
118 | } |
119 | |
120 | void SkOpSegment::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, |
121 | SkArenaAlloc* allocator) { |
122 | if (!sideways_overlap(fBounds, base.fPt, dir)) { |
123 | return; |
124 | } |
125 | SkScalar baseXY = pt_xy(base.fPt, dir); |
126 | SkScalar boundsXY = rect_side(fBounds, dir); |
127 | bool checkLessThan = less_than(dir); |
128 | if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { |
129 | return; |
130 | } |
131 | double tVals[3]; |
132 | SkScalar baseYX = pt_yx(base.fPt, dir); |
133 | int roots = (*CurveIntercept[fVerb * 2 + xy_index(dir)])(fPts, fWeight, baseYX, tVals); |
134 | for (int index = 0; index < roots; ++index) { |
135 | double t = tVals[index]; |
136 | if (base.fSpan->segment() == this && approximately_equal(base.fT, t)) { |
137 | continue; |
138 | } |
139 | SkDVector slope; |
140 | SkPoint pt; |
141 | SkDEBUGCODE(sk_bzero(&slope, sizeof(slope))); |
142 | bool valid = false; |
143 | if (approximately_zero(t)) { |
144 | pt = fPts[0]; |
145 | } else if (approximately_equal(t, 1)) { |
146 | pt = fPts[SkPathOpsVerbToPoints(fVerb)]; |
147 | } else { |
148 | SkASSERT(between(0, t, 1)); |
149 | pt = this->ptAtT(t); |
150 | if (SkDPoint::ApproximatelyEqual(pt, base.fPt)) { |
151 | if (base.fSpan->segment() == this) { |
152 | continue; |
153 | } |
154 | } else { |
155 | SkScalar ptXY = pt_xy(pt, dir); |
156 | if (!approximately_equal(baseXY, ptXY) && (baseXY < ptXY) == checkLessThan) { |
157 | continue; |
158 | } |
159 | slope = this->dSlopeAtT(t); |
160 | if (fVerb == SkPath::kCubic_Verb && base.fSpan->segment() == this |
161 | && roughly_equal(base.fT, t) |
162 | && SkDPoint::RoughlyEqual(pt, base.fPt)) { |
163 | #if DEBUG_WINDING |
164 | SkDebugf("%s (rarely expect this)\n" , __FUNCTION__); |
165 | #endif |
166 | continue; |
167 | } |
168 | if (fabs(pt_dydx(slope, dir) * 10000) > fabs(pt_dxdy(slope, dir))) { |
169 | valid = true; |
170 | } |
171 | } |
172 | } |
173 | SkOpSpan* span = this->windingSpanAtT(t); |
174 | if (!span) { |
175 | valid = false; |
176 | } else if (!span->windValue() && !span->oppValue()) { |
177 | continue; |
178 | } |
179 | SkOpRayHit* newHit = allocator->make<SkOpRayHit>(); |
180 | newHit->fNext = *hits; |
181 | newHit->fPt = pt; |
182 | newHit->fSlope = slope; |
183 | newHit->fSpan = span; |
184 | newHit->fT = t; |
185 | newHit->fValid = valid; |
186 | *hits = newHit; |
187 | } |
188 | } |
189 | |
190 | SkOpSpan* SkOpSegment::windingSpanAtT(double tHit) { |
191 | SkOpSpan* span = &fHead; |
192 | SkOpSpanBase* next; |
193 | do { |
194 | next = span->next(); |
195 | if (approximately_equal(tHit, next->t())) { |
196 | return nullptr; |
197 | } |
198 | if (tHit < next->t()) { |
199 | return span; |
200 | } |
201 | } while (!next->final() && (span = next->upCast())); |
202 | return nullptr; |
203 | } |
204 | |
205 | static bool hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { |
206 | return a->fPt.fX < b->fPt.fX; |
207 | } |
208 | |
209 | static bool reverse_hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { |
210 | return b->fPt.fX < a->fPt.fX; |
211 | } |
212 | |
213 | static bool hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { |
214 | return a->fPt.fY < b->fPt.fY; |
215 | } |
216 | |
217 | static bool reverse_hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { |
218 | return b->fPt.fY < a->fPt.fY; |
219 | } |
220 | |
221 | static double get_t_guess(int tTry, int* dirOffset) { |
222 | double t = 0.5; |
223 | *dirOffset = tTry & 1; |
224 | int tBase = tTry >> 1; |
225 | int tBits = 0; |
226 | while (tTry >>= 1) { |
227 | t /= 2; |
228 | ++tBits; |
229 | } |
230 | if (tBits) { |
231 | int tIndex = (tBase - 1) & ((1 << tBits) - 1); |
232 | t += t * 2 * tIndex; |
233 | } |
234 | return t; |
235 | } |
236 | |
237 | bool SkOpSpan::sortableTop(SkOpContour* contourHead) { |
238 | SkSTArenaAlloc<1024> allocator; |
239 | int dirOffset; |
240 | double t = get_t_guess(fTopTTry++, &dirOffset); |
241 | SkOpRayHit hitBase; |
242 | SkOpRayDir dir = hitBase.makeTestBase(this, t); |
243 | if (hitBase.fSlope.fX == 0 && hitBase.fSlope.fY == 0) { |
244 | return false; |
245 | } |
246 | SkOpRayHit* hitHead = &hitBase; |
247 | dir = static_cast<SkOpRayDir>(static_cast<int>(dir) + dirOffset); |
248 | if (hitBase.fSpan && hitBase.fSpan->segment()->verb() > SkPath::kLine_Verb |
249 | && !pt_dydx(hitBase.fSlope, dir)) { |
250 | return false; |
251 | } |
252 | SkOpContour* contour = contourHead; |
253 | do { |
254 | if (!contour->count()) { |
255 | continue; |
256 | } |
257 | contour->rayCheck(hitBase, dir, &hitHead, &allocator); |
258 | } while ((contour = contour->next())); |
259 | // sort hits |
260 | SkSTArray<1, SkOpRayHit*> sorted; |
261 | SkOpRayHit* hit = hitHead; |
262 | while (hit) { |
263 | sorted.push_back(hit); |
264 | hit = hit->fNext; |
265 | } |
266 | int count = sorted.count(); |
267 | SkTQSort(sorted.begin(), sorted.end() - 1, xy_index(dir) |
268 | ? less_than(dir) ? hit_compare_y : reverse_hit_compare_y |
269 | : less_than(dir) ? hit_compare_x : reverse_hit_compare_x); |
270 | // verify windings |
271 | #if DEBUG_WINDING |
272 | SkDebugf("%s dir=%s seg=%d t=%1.9g pt=(%1.9g,%1.9g)\n" , __FUNCTION__, |
273 | gDebugRayDirName[static_cast<int>(dir)], hitBase.fSpan->segment()->debugID(), |
274 | hitBase.fT, hitBase.fPt.fX, hitBase.fPt.fY); |
275 | for (int index = 0; index < count; ++index) { |
276 | hit = sorted[index]; |
277 | SkOpSpan* span = hit->fSpan; |
278 | SkOpSegment* hitSegment = span ? span->segment() : nullptr; |
279 | bool operand = span ? hitSegment->operand() : false; |
280 | bool ccw = ccw_dxdy(hit->fSlope, dir); |
281 | SkDebugf("%s [%d] valid=%d operand=%d span=%d ccw=%d " , __FUNCTION__, index, |
282 | hit->fValid, operand, span ? span->debugID() : -1, ccw); |
283 | if (span) { |
284 | hitSegment->dumpPtsInner(); |
285 | } |
286 | SkDebugf(" t=%1.9g pt=(%1.9g,%1.9g) slope=(%1.9g,%1.9g)\n" , hit->fT, |
287 | hit->fPt.fX, hit->fPt.fY, hit->fSlope.fX, hit->fSlope.fY); |
288 | } |
289 | #endif |
290 | const SkPoint* last = nullptr; |
291 | int wind = 0; |
292 | int oppWind = 0; |
293 | for (int index = 0; index < count; ++index) { |
294 | hit = sorted[index]; |
295 | if (!hit->fValid) { |
296 | return false; |
297 | } |
298 | bool ccw = ccw_dxdy(hit->fSlope, dir); |
299 | // SkASSERT(!approximately_zero(hit->fT) || !hit->fValid); |
300 | SkOpSpan* span = hit->fSpan; |
301 | if (!span) { |
302 | return false; |
303 | } |
304 | SkOpSegment* hitSegment = span->segment(); |
305 | if (span->windValue() == 0 && span->oppValue() == 0) { |
306 | continue; |
307 | } |
308 | if (last && SkDPoint::ApproximatelyEqual(*last, hit->fPt)) { |
309 | return false; |
310 | } |
311 | if (index < count - 1) { |
312 | const SkPoint& next = sorted[index + 1]->fPt; |
313 | if (SkDPoint::ApproximatelyEqual(next, hit->fPt)) { |
314 | return false; |
315 | } |
316 | } |
317 | bool operand = hitSegment->operand(); |
318 | if (operand) { |
319 | using std::swap; |
320 | swap(wind, oppWind); |
321 | } |
322 | int lastWind = wind; |
323 | int lastOpp = oppWind; |
324 | int windValue = ccw ? -span->windValue() : span->windValue(); |
325 | int oppValue = ccw ? -span->oppValue() : span->oppValue(); |
326 | wind += windValue; |
327 | oppWind += oppValue; |
328 | bool sumSet = false; |
329 | int spanSum = span->windSum(); |
330 | int windSum = SkOpSegment::UseInnerWinding(lastWind, wind) ? wind : lastWind; |
331 | if (spanSum == SK_MinS32) { |
332 | span->setWindSum(windSum); |
333 | sumSet = true; |
334 | } else { |
335 | // the need for this condition suggests that UseInnerWinding is flawed |
336 | // happened when last = 1 wind = -1 |
337 | #if 0 |
338 | SkASSERT((hitSegment->isXor() ? (windSum & 1) == (spanSum & 1) : windSum == spanSum) |
339 | || (abs(wind) == abs(lastWind) |
340 | && (windSum ^ wind ^ lastWind) == spanSum)); |
341 | #endif |
342 | } |
343 | int oSpanSum = span->oppSum(); |
344 | int oppSum = SkOpSegment::UseInnerWinding(lastOpp, oppWind) ? oppWind : lastOpp; |
345 | if (oSpanSum == SK_MinS32) { |
346 | span->setOppSum(oppSum); |
347 | } else { |
348 | #if 0 |
349 | SkASSERT(hitSegment->oppXor() ? (oppSum & 1) == (oSpanSum & 1) : oppSum == oSpanSum |
350 | || (abs(oppWind) == abs(lastOpp) |
351 | && (oppSum ^ oppWind ^ lastOpp) == oSpanSum)); |
352 | #endif |
353 | } |
354 | if (sumSet) { |
355 | if (this->globalState()->phase() == SkOpPhase::kFixWinding) { |
356 | hitSegment->contour()->setCcw(ccw); |
357 | } else { |
358 | (void) hitSegment->markAndChaseWinding(span, span->next(), windSum, oppSum, nullptr); |
359 | (void) hitSegment->markAndChaseWinding(span->next(), span, windSum, oppSum, nullptr); |
360 | } |
361 | } |
362 | if (operand) { |
363 | using std::swap; |
364 | swap(wind, oppWind); |
365 | } |
366 | last = &hit->fPt; |
367 | this->globalState()->bumpNested(); |
368 | } |
369 | return true; |
370 | } |
371 | |
372 | SkOpSpan* SkOpSegment::findSortableTop(SkOpContour* contourHead) { |
373 | SkOpSpan* span = &fHead; |
374 | SkOpSpanBase* next; |
375 | do { |
376 | next = span->next(); |
377 | if (span->done()) { |
378 | continue; |
379 | } |
380 | if (span->windSum() != SK_MinS32) { |
381 | return span; |
382 | } |
383 | if (span->sortableTop(contourHead)) { |
384 | return span; |
385 | } |
386 | } while (!next->final() && (span = next->upCast())); |
387 | return nullptr; |
388 | } |
389 | |
390 | SkOpSpan* SkOpContour::findSortableTop(SkOpContour* contourHead) { |
391 | bool allDone = true; |
392 | if (fCount) { |
393 | SkOpSegment* testSegment = &fHead; |
394 | do { |
395 | if (testSegment->done()) { |
396 | continue; |
397 | } |
398 | allDone = false; |
399 | SkOpSpan* result = testSegment->findSortableTop(contourHead); |
400 | if (result) { |
401 | return result; |
402 | } |
403 | } while ((testSegment = testSegment->next())); |
404 | } |
405 | if (allDone) { |
406 | fDone = true; |
407 | } |
408 | return nullptr; |
409 | } |
410 | |
411 | SkOpSpan* FindSortableTop(SkOpContourHead* contourHead) { |
412 | for (int index = 0; index < SkOpGlobalState::kMaxWindingTries; ++index) { |
413 | SkOpContour* contour = contourHead; |
414 | do { |
415 | if (contour->done()) { |
416 | continue; |
417 | } |
418 | SkOpSpan* result = contour->findSortableTop(contourHead); |
419 | if (result) { |
420 | return result; |
421 | } |
422 | } while ((contour = contour->next())); |
423 | } |
424 | return nullptr; |
425 | } |
426 | |