/* * Copyright 2012 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file.
*/ #include"src/pathops/SkOpSegment.h"
/* After computing raw intersections, post process all segments to: - find small collections of points that can be collapsed to a single point - find missing intersections to resolve differences caused by different algorithms
Consider segments containing tiny or small intervals. Consider coincident segments because coincidence finds intersections through distance measurement that non-coincident intersection tests cannot.
*/
#define F (false) // discard the edge #define T (true) // keep the edge
const SkOpPtT* SkOpSegment::existing(double t, const SkOpSegment* opp) const { const SkOpSpanBase* test = &fHead; const SkOpPtT* testPtT;
SkPoint pt = this->ptAtT(t); do {
testPtT = test->ptT(); if (testPtT->fT == t) { break;
} if (!this->match(testPtT, this, t, pt)) { if (t < testPtT->fT) { return nullptr;
} continue;
} if (!opp) { return testPtT;
} const SkOpPtT* loop = testPtT->next(); while (loop != testPtT) { if (loop->segment() == this && loop->fT == t && loop->fPt == pt) { goto foundMatch;
}
loop = loop->next();
} return nullptr;
} while ((test = test->upCast()->next()));
foundMatch: return opp && !test->contains(opp) ? nullptr : testPtT;
}
// break the span so that the coincident part does not change the angle of the remainder bool SkOpSegment::addExpanded(double newT, const SkOpSpanBase* test, bool* startOver) { if (this->contains(newT)) { returntrue;
}
this->globalState()->resetAllocatedOpSpan();
FAIL_IF(!between(0, newT, 1));
SkOpPtT* newPtT = this->addT(newT);
*startOver |= this->globalState()->allocatedOpSpan(); if (!newPtT) { returnfalse;
}
newPtT->fPt = this->ptAtT(newT);
SkOpPtT* oppPrev = test->ptT()->oppPrev(newPtT); if (oppPrev) { // const cast away to change linked list; pt/t values stays unchanged
SkOpSpanBase* writableTest = const_cast<SkOpSpanBase*>(test);
writableTest->mergeMatches(newPtT->span());
writableTest->ptT()->addOpp(newPtT, oppPrev);
writableTest->checkForCollapsedCoincidence();
} returntrue;
}
// Please keep this in sync with debugAddT()
SkOpPtT* SkOpSegment::addT(double t, const SkPoint& pt) {
debugValidate();
SkOpSpanBase* spanBase = &fHead; do {
SkOpPtT* result = spanBase->ptT(); if (t == result->fT || (!zero_or_one(t) && this->match(result, this, t, pt))) {
spanBase->bumpSpanAdds(); return result;
} if (t < result->fT) {
SkOpSpan* prev = result->span()->prev();
FAIL_WITH_NULL_IF(!prev); // marks in global state that new op span has been allocated
SkOpSpan* span = this->insert(prev);
span->init(this, prev, t, pt);
this->debugValidate(); #if DEBUG_ADD_T
SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t,
span->segment()->debugID(), span->debugID()); #endif
span->bumpSpanAdds(); return span->ptT();
}
FAIL_WITH_NULL_IF(spanBase == &fTail);
} while ((spanBase = spanBase->upCast()->next()));
SkASSERT(0); return nullptr; // we never get here, but need this to satisfy compiler
}
// Please keep this in sync with debugClearAll() void SkOpSegment::clearAll() {
SkOpSpan* span = &fHead; do {
this->clearOne(span);
} while ((span = span->next()->upCastable()));
this->globalState()->coincidence()->release(this);
}
// Please keep this in sync with debugClearOne() void SkOpSegment::clearOne(SkOpSpan* span) {
span->setWindValue(0);
span->setOppValue(0);
this->markDone(span);
}
SkOpSpanBase::Collapsed SkOpSegment::collapsed(double s, double e) const { const SkOpSpanBase* span = &fHead; do {
SkOpSpanBase::Collapsed result = span->collapsed(s, e); if (SkOpSpanBase::Collapsed::kNo != result) { return result;
}
} while (span->upCastable() && (span = span->upCast()->next())); return SkOpSpanBase::Collapsed::kNo;
}
bool SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle,
SkOpAngle::IncludeType includeType) {
SkOpSegment* baseSegment = baseAngle->segment(); int sumMiWinding = baseSegment->updateWindingReverse(baseAngle); int sumSuWinding; bool binary = includeType >= SkOpAngle::kBinarySingle; if (binary) {
sumSuWinding = baseSegment->updateOppWindingReverse(baseAngle); if (baseSegment->operand()) { using std::swap;
swap(sumMiWinding, sumSuWinding);
}
}
SkOpSegment* nextSegment = nextAngle->segment(); int maxWinding, sumWinding;
SkOpSpanBase* last = nullptr; if (binary) { int oppMaxWinding, oppSumWinding;
nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding,
&sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding,
nextAngle, &last)) { returnfalse;
}
} else {
nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding,
&maxWinding, &sumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, nextAngle, &last)) { returnfalse;
}
}
nextAngle->setLastMarked(last); returntrue;
}
bool SkOpSegment::ComputeOneSumReverse(SkOpAngle* baseAngle, SkOpAngle* nextAngle,
SkOpAngle::IncludeType includeType) {
SkOpSegment* baseSegment = baseAngle->segment(); int sumMiWinding = baseSegment->updateWinding(baseAngle); int sumSuWinding; bool binary = includeType >= SkOpAngle::kBinarySingle; if (binary) {
sumSuWinding = baseSegment->updateOppWinding(baseAngle); if (baseSegment->operand()) { using std::swap;
swap(sumMiWinding, sumSuWinding);
}
}
SkOpSegment* nextSegment = nextAngle->segment(); int maxWinding, sumWinding;
SkOpSpanBase* last = nullptr; if (binary) { int oppMaxWinding, oppSumWinding;
nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding,
&sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding,
nextAngle, &last)) { returnfalse;
}
} else {
nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding,
&maxWinding, &sumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, nextAngle, &last)) { returnfalse;
}
}
nextAngle->setLastMarked(last); returntrue;
}
// at this point, the span is already ordered, or unorderable int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end,
SkOpAngle::IncludeType includeType) {
SkASSERT(includeType != SkOpAngle::kUnaryXor);
SkOpAngle* firstAngle = this->spanToAngle(end, start); if (nullptr == firstAngle || nullptr == firstAngle->next()) { return SK_NaN32;
} // if all angles have a computed winding, // or if no adjacent angles are orderable, // or if adjacent orderable angles have no computed winding, // there's nothing to do // if two orderable angles are adjacent, and both are next to orderable angles, // and one has winding computed, transfer to the other
SkOpAngle* baseAngle = nullptr; bool tryReverse = false; // look for counterclockwise transfers
SkOpAngle* angle = firstAngle->previous();
SkOpAngle* next = angle->next();
firstAngle = next; do {
SkOpAngle* prior = angle;
angle = next;
next = angle->next();
SkASSERT(prior->next() == angle);
SkASSERT(angle->next() == next); if (prior->unorderable() || angle->unorderable() || next->unorderable()) {
baseAngle = nullptr; continue;
} int testWinding = angle->starter()->windSum(); if (SK_MinS32 != testWinding) {
baseAngle = angle;
tryReverse = true; continue;
} if (baseAngle) {
ComputeOneSum(baseAngle, angle, includeType);
baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : nullptr;
}
} while (next != firstAngle); if (baseAngle && SK_MinS32 == firstAngle->starter()->windSum()) {
firstAngle = baseAngle;
tryReverse = true;
} if (tryReverse) {
baseAngle = nullptr;
SkOpAngle* prior = firstAngle; do {
angle = prior;
prior = angle->previous();
SkASSERT(prior->next() == angle);
next = angle->next(); if (prior->unorderable() || angle->unorderable() || next->unorderable()) {
baseAngle = nullptr; continue;
} int testWinding = angle->starter()->windSum(); if (SK_MinS32 != testWinding) {
baseAngle = angle; continue;
} if (baseAngle) {
ComputeOneSumReverse(baseAngle, angle, includeType);
baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : nullptr;
}
} while (prior != firstAngle);
} return start->starter(end)->windSum();
}
bool SkOpSegment::contains(double newT) const { const SkOpSpanBase* spanBase = &fHead; do { if (spanBase->ptT()->contains(this, newT)) { returntrue;
} if (spanBase == &fTail) { break;
}
spanBase = spanBase->upCast()->next();
} while (true); returnfalse;
}
// Please keep this in sync with DebugClearVisited() void SkOpSegment::ClearVisited(SkOpSpanBase* span) { // reset visited flag back to false do {
SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) {
SkOpSegment* opp = ptT->segment();
opp->resetVisited();
}
} while (!span->final() && (span = span->upCast()->next()));
}
// Please keep this in sync with debugMissingCoincidence() // look for pairs of undetected coincident curves // assumes that segments going in have visited flag clear // Even though pairs of curves correct detect coincident runs, a run may be missed // if the coincidence is a product of multiple intersections. For instance, given // curves A, B, and C: // A-B intersect at a point 1; A-C and B-C intersect at point 2, so near // the end of C that the intersection is replaced with the end of C. // Even though A-B correctly do not detect an intersection at point 2, // the resulting run from point 1 to point 2 is coincident on A and B. bool SkOpSegment::missingCoincidence() { if (this->done()) { returnfalse;
}
SkOpSpan* prior = nullptr;
SkOpSpanBase* spanBase = &fHead; bool result = false; int safetyNet = 100000; do {
SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT;
SkOPASSERT(ptT->span() == spanBase); while ((ptT = ptT->next()) != spanStopPtT) { if (!--safetyNet) { returnfalse;
} if (ptT->deleted()) { continue;
}
SkOpSegment* opp = ptT->span()->segment(); if (opp->done()) { continue;
} // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence if (!opp->visited()) { continue;
} if (spanBase == &fHead) { continue;
} if (ptT->segment() == this) { continue;
}
SkOpSpan* span = spanBase->upCastable(); // FIXME?: this assumes that if the opposite segment is coincident then no more // coincidence needs to be detected. This may not be true. if (span && span->containsCoincidence(opp)) { continue;
} if (spanBase->containsCoinEnd(opp)) { continue;
}
SkOpPtT* priorPtT = nullptr, * priorStopPtT; // find prior span containing opp segment
SkOpSegment* priorOpp = nullptr;
SkOpSpan* priorTest = spanBase->prev(); while (!priorOpp && priorTest) {
priorStopPtT = priorPtT = priorTest->ptT(); while ((priorPtT = priorPtT->next()) != priorStopPtT) { if (priorPtT->deleted()) { continue;
}
SkOpSegment* segment = priorPtT->span()->segment(); if (segment == opp) {
prior = priorTest;
priorOpp = opp; break;
}
}
priorTest = priorTest->prev();
} if (!priorOpp) { continue;
} if (priorPtT == ptT) { continue;
}
SkOpPtT* oppStart = prior->ptT();
SkOpPtT* oppEnd = spanBase->ptT(); bool swapped = priorPtT->fT > ptT->fT; if (swapped) { using std::swap;
swap(priorPtT, ptT);
swap(oppStart, oppEnd);
}
SkOpCoincidence* coincidences = this->globalState()->coincidence();
SkOpPtT* rootPriorPtT = priorPtT->span()->ptT();
SkOpPtT* rootPtT = ptT->span()->ptT();
SkOpPtT* rootOppStart = oppStart->span()->ptT();
SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); if (coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { goto swapBack;
} if (this->testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { // mark coincidence #if DEBUG_COINCIDENCE_VERBOSE
SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__,
rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(),
rootOppEnd->debugID()); #endif if (!coincidences->extend(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) {
coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd);
} #if DEBUG_COINCIDENCE
SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)); #endif
result = true;
}
swapBack: if (swapped) { using std::swap;
swap(priorPtT, ptT);
}
}
} while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next()));
ClearVisited(&fHead); return result;
}
// please keep this in sync with debugMoveMultiples() // if a span has more than one intersection, merge the other segments' span as needed bool SkOpSegment::moveMultiples() {
debugValidate();
SkOpSpanBase* test = &fHead; do { int addCount = test->spanAddsCount(); // FAIL_IF(addCount < 1); if (addCount <= 1) { continue;
}
SkOpPtT* startPtT = test->ptT();
SkOpPtT* testPtT = startPtT; int safetyHatch = 1000000; do { // iterate through all spans associated with start if (!--safetyHatch) { returnfalse;
}
SkOpSpanBase* oppSpan = testPtT->span(); if (oppSpan->spanAddsCount() == addCount) { continue;
} if (oppSpan->deleted()) { continue;
}
SkOpSegment* oppSegment = oppSpan->segment(); if (oppSegment == this) { continue;
} // find range of spans to consider merging
SkOpSpanBase* oppPrev = oppSpan;
SkOpSpanBase* oppFirst = oppSpan; while ((oppPrev = oppPrev->prev())) { if (!roughly_equal(oppPrev->t(), oppSpan->t())) { break;
} if (oppPrev->spanAddsCount() == addCount) { continue;
} if (oppPrev->deleted()) { continue;
}
oppFirst = oppPrev;
}
SkOpSpanBase* oppNext = oppSpan;
SkOpSpanBase* oppLast = oppSpan; while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { if (!roughly_equal(oppNext->t(), oppSpan->t())) { break;
} if (oppNext->spanAddsCount() == addCount) { continue;
} if (oppNext->deleted()) { continue;
}
oppLast = oppNext;
} if (oppFirst == oppLast) { continue;
}
SkOpSpanBase* oppTest = oppFirst; do { if (oppTest == oppSpan) { continue;
} // check to see if the candidate meets specific criteria: // it contains spans of segments in test's loop but not including 'this'
SkOpPtT* oppStartPtT = oppTest->ptT();
SkOpPtT* oppPtT = oppStartPtT; while ((oppPtT = oppPtT->next()) != oppStartPtT) {
SkOpSegment* oppPtTSegment = oppPtT->segment(); if (oppPtTSegment == this) { goto tryNextSpan;
}
SkOpPtT* matchPtT = startPtT; do { if (matchPtT->segment() == oppPtTSegment) { goto foundMatch;
}
} while ((matchPtT = matchPtT->next()) != startPtT); goto tryNextSpan;
foundMatch: // merge oppTest and oppSpan
oppSegment->debugValidate();
oppTest->mergeMatches(oppSpan);
oppTest->addOpp(oppSpan);
oppSegment->debugValidate(); goto checkNextSpan;
}
tryNextSpan:
;
} while (oppTest != oppLast && (oppTest = oppTest->upCast()->next()));
} while ((testPtT = testPtT->next()) != startPtT);
checkNextSpan:
;
} while ((test = test->final() ? nullptr : test->upCast()->next()));
debugValidate(); returntrue;
}
// adjacent spans may have points close by bool SkOpSegment::spansNearby(const SkOpSpanBase* refSpan, const SkOpSpanBase* checkSpan, bool* found) const { const SkOpPtT* refHead = refSpan->ptT(); const SkOpPtT* checkHead = checkSpan->ptT(); // if the first pt pair from adjacent spans are far apart, assume that all are far enough apart if (!SkDPoint::WayRoughlyEqual(refHead->fPt, checkHead->fPt)) { #if DEBUG_COINCIDENCE // verify that no combination of points are close const SkOpPtT* dBugRef = refHead; do { const SkOpPtT* dBugCheck = checkHead; do {
SkOPASSERT(!SkDPoint::ApproximatelyEqual(dBugRef->fPt, dBugCheck->fPt));
dBugCheck = dBugCheck->next();
} while (dBugCheck != checkHead);
dBugRef = dBugRef->next();
} while (dBugRef != refHead); #endif
*found = false; returntrue;
} // check only unique points
SkScalar distSqBest = SK_ScalarMax; const SkOpPtT* refBest = nullptr; const SkOpPtT* checkBest = nullptr; const SkOpPtT* ref = refHead; do { if (ref->deleted()) { continue;
} while (ref->ptAlreadySeen(refHead)) {
ref = ref->next(); if (ref == refHead) { goto doneCheckingDistance;
}
} const SkOpPtT* check = checkHead; const SkOpSegment* refSeg = ref->segment(); int escapeHatch = 100000; // defend against infinite loops do { if (check->deleted()) { continue;
} while (check->ptAlreadySeen(checkHead)) {
check = check->next(); if (check == checkHead) { goto nextRef;
}
}
SkScalar distSq = SkPointPriv::DistanceToSqd(ref->fPt, check->fPt); if (distSqBest > distSq && (refSeg != check->segment()
|| !refSeg->ptsDisjoint(*ref, *check))) {
distSqBest = distSq;
refBest = ref;
checkBest = check;
} if (--escapeHatch <= 0) { returnfalse;
}
} while ((check = check->next()) != checkHead);
nextRef:
;
} while ((ref = ref->next()) != refHead);
doneCheckingDistance:
*found = checkBest && refBest->segment()->match(refBest, checkBest->segment(), checkBest->fT,
checkBest->fPt); returntrue;
}
// Please keep this function in sync with debugMoveNearby() // Move nearby t values and pts so they all hang off the same span. Alignment happens later. bool SkOpSegment::moveNearby() {
debugValidate(); // release undeleted spans pointing to this seg that are linked to the primary span
SkOpSpanBase* spanBase = &fHead; int escapeHatch = 9999; // the largest count for a regular test is 50; for a fuzzer, 500 do {
SkOpPtT* ptT = spanBase->ptT(); const SkOpPtT* headPtT = ptT; while ((ptT = ptT->next()) != headPtT) { if (!--escapeHatch) { returnfalse;
}
SkOpSpanBase* test = ptT->span(); if (ptT->segment() == this && !ptT->deleted() && test != spanBase
&& test->ptT() == ptT) { if (test->final()) { if (spanBase == &fHead) {
this->clearAll(); returntrue;
}
spanBase->upCast()->release(ptT);
} elseif (test->prev()) {
test->upCast()->release(headPtT);
} break;
}
}
spanBase = spanBase->upCast()->next();
} while (!spanBase->final()); // This loop looks for adjacent spans which are near by
spanBase = &fHead; do { // iterate through all spans associated with start
SkOpSpanBase* test = spanBase->upCast()->next(); bool found; if (!this->spansNearby(spanBase, test, &found)) { returnfalse;
} if (found) { if (test->final()) { if (spanBase->prev()) {
test->merge(spanBase->upCast());
} else {
this->clearAll(); returntrue;
}
} else {
spanBase->merge(test->upCast());
}
}
spanBase = test;
} while (!spanBase->final());
debugValidate(); returntrue;
}
bool SkOpSegment::ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const { if (fVerb == SkPath::kLine_Verb) { returnfalse;
} // quads (and cubics) can loop back to nearly a line so that an opposite curve // hits in two places with very different t values. // OPTIMIZATION: curves could be preflighted so that, for example, something like // 'controls contained by ends' could avoid this check for common curves // 'ends are extremes in x or y' is cheaper to compute and real-world common // on the other hand, the below check is relatively inexpensive double midT = (t1 + t2) / 2;
SkPoint midPt = this->ptAtT(midT); double seDistSq = std::max(SkPointPriv::DistanceToSqd(pt1, pt2) * 2, FLT_EPSILON * 2); return SkPointPriv::DistanceToSqd(midPt, pt1) > seDistSq ||
SkPointPriv::DistanceToSqd(midPt, pt2) > seDistSq;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.