/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkOpCoincidence.h"
#include "SkOpContour.h"
#include "SkOpSegment.h"
#include "SkPathWriter.h"
bool SkOpPtT::alias() const {
return this->span()->ptT() != this;
}
SkOpContour* SkOpPtT::contour() const {
return segment()->contour();
}
SkOpGlobalState* SkOpPtT::globalState() const {
return PATH_OPS_DEBUG_RELEASE(contour()->globalState(), NULL);
}
void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplicate) {
fT = t;
fPt = pt;
fSpan = span;
fNext = this;
fDuplicatePt = duplicate;
fDeleted = false;
PATH_OPS_DEBUG_CODE(fID = ++span->globalState()->fPtTID);
}
bool SkOpPtT::onEnd() const {
const SkOpSpanBase* span = this->span();
if (span->ptT() != this) {
return false;
}
const SkOpSegment* segment = this->segment();
return span == segment->head() || span == segment->tail();
}
SkOpPtT* SkOpPtT::remove() {
SkOpPtT* prev = this;
do {
SkOpPtT* next = prev->fNext;
if (next == this) {
prev->removeNext(this);
fDeleted = true;
return prev;
}
prev = next;
} while (prev != this);
SkASSERT(0);
return NULL;
}
void SkOpPtT::removeNext(SkOpPtT* kept) {
SkASSERT(this->fNext);
SkOpPtT* next = this->fNext;
this->fNext = next->fNext;
SkOpSpanBase* span = next->span();
next->setDeleted();
if (span->ptT() == next) {
span->upCast()->detach(kept);
}
}
const SkOpSegment* SkOpPtT::segment() const {
return span()->segment();
}
SkOpSegment* SkOpPtT::segment() {
return span()->segment();
}
// find the starting or ending span with an existing loop of angles
// OPTIMIZE? remove the spans pointing to windValue==0 here or earlier?
// FIXME? assert that only one other span has a valid windValue or oppValue
void SkOpSpanBase::addSimpleAngle(bool checkFrom, SkChunkAlloc* allocator) {
SkOpAngle* angle;
if (checkFrom) {
SkASSERT(this->final());
if (this->fromAngle()) {
SkASSERT(this->fromAngle()->loopCount() == 2);
return;
}
angle = this->segment()->addEndSpan(allocator);
} else {
SkASSERT(this->t() == 0);
SkOpSpan* span = this->upCast();
if (span->toAngle()) {
SkASSERT(span->toAngle()->loopCount() == 2);
SkASSERT(!span->fromAngle());
span->setFromAngle(span->toAngle()->next());
return;
}
angle = this->segment()->addStartSpan(allocator);
}
SkOpPtT* ptT = this->ptT();
SkOpSpanBase* oSpanBase;
SkOpSpan* oSpan;
SkOpSegment* other;
do {
ptT = ptT->next();
oSpanBase = ptT->span();
oSpan = oSpanBase->upCastable();
other = oSpanBase->segment();
if (oSpan && oSpan->windValue()) {
break;
}
if (oSpanBase->t() == 0) {
continue;
}
SkOpSpan* oFromSpan = oSpanBase->prev();
SkASSERT(oFromSpan->t() < 1);
if (oFromSpan->windValue()) {
break;
}
} while (ptT != this->ptT());
SkOpAngle* oAngle;
if (checkFrom) {
oAngle = other->addStartSpan(allocator);
SkASSERT(oSpan && !oSpan->final());
SkASSERT(oAngle == oSpan->toAngle());
} else {
oAngle = other->addEndSpan(allocator);
SkASSERT(oAngle == oSpanBase->fromAngle());
}
angle->insert(oAngle);
}
void SkOpSpanBase::align() {
if (this->fAligned) {
return;
}
SkASSERT(!zero_or_one(this->fPtT.fT));
SkASSERT(this->fPtT.next());
// if a linked pt/t pair has a t of zero or one, use it as the base for alignment
SkOpPtT* ptT = &this->fPtT, * stopPtT = ptT;
while ((ptT = ptT->next()) != stopPtT) {
if (zero_or_one(ptT->fT)) {
SkOpSegment* segment = ptT->segment();
SkASSERT(this->segment() != segment);
SkASSERT(segment->head()->ptT() == ptT || segment->tail()->ptT() == ptT);
if (ptT->fT) {
segment->tail()->alignEnd(1, segment->lastPt());
} else {
segment->head()->alignEnd(0, segment->pts()[0]);
}
return;
}
}
alignInner();
this->fAligned = true;
}
// FIXME: delete spans that collapse
// delete segments that collapse
// delete contours that collapse
void SkOpSpanBase::alignEnd(double t, const SkPoint& pt) {
SkASSERT(zero_or_one(t));
SkOpSegment* segment = this->segment();
SkASSERT(t ? segment->lastPt() == pt : segment->pts()[0] == pt);
alignInner();
*segment->writablePt(!!t) = pt;
SkOpPtT* ptT = &this->fPtT;
SkASSERT(t == ptT->fT);
SkASSERT(pt == ptT->fPt);
SkOpPtT* test = ptT, * stopPtT = ptT;
while ((test = test->next()) != stopPtT) {
SkOpSegment* other = test->segment();
if (other == this->segment()) {
continue;
}
if (!zero_or_one(test->fT)) {
continue;
}
*other->writablePt(!!test->fT) = pt;
}
this->fAligned = true;
}
void SkOpSpanBase::alignInner() {
// force the spans to share points and t values
SkOpPtT* ptT = &this->fPtT, * stopPtT = ptT;
const SkPoint& pt = ptT->fPt;
do {
ptT->fPt = pt;
const SkOpSpanBase* span = ptT->span();
SkOpPtT* test = ptT;
do {
SkOpPtT* prev = test;
if ((test = test->next()) == stopPtT) {
break;
}
if (span == test->span() && !span->segment()->ptsDisjoint(*ptT, *test)) {
// omit aliases that alignment makes redundant
if ((!ptT->alias() || test->alias()) && (ptT->onEnd() || !test->onEnd())) {
SkASSERT(test->alias());
prev->removeNext(ptT);
test = prev;
} else {
SkASSERT(ptT->alias());
stopPtT = ptT = ptT->remove();
break;
}
}
} while (true);
} while ((ptT = ptT->next()) != stopPtT);
}
bool SkOpSpanBase::contains(const SkOpSpanBase* span) const {
const SkOpPtT* start = &fPtT;
const SkOpPtT* check = &span->fPtT;
SkASSERT(start != check);
const SkOpPtT* walk = start;
while ((walk = walk->next()) != start) {
if (walk == check) {
return true;
}
}
return false;
}
bool SkOpSpanBase::containsCoinEnd(const SkOpSegment* segment) const {
SkASSERT(this->segment() != segment);
const SkOpSpanBase* next = this;
while ((next = next->fCoinEnd) != this) {
if (next->segment() == segment) {
return true;
}
}
return false;
}
SkOpContour* SkOpSpanBase::contour() const {
return segment()->contour();
}
SkOpGlobalState* SkOpSpanBase::globalState() const {
return PATH_OPS_DEBUG_RELEASE(contour()->globalState(), NULL);
}
void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) {
fSegment = segment;
fPtT.init(this, t, pt, false);
fCoinEnd = this;
fFromAngle = NULL;
fPrev = prev;
fAligned = true;
fChased = false;
PATH_OPS_DEBUG_CODE(fCount = 1);
PATH_OPS_DEBUG_CODE(fID = ++globalState()->fSpanID);
}
// this pair of spans share a common t value or point; merge them and eliminate duplicates
// this does not compute the best t or pt value; this merely moves all data into a single list
void SkOpSpanBase::merge(SkOpSpan* span) {
SkOpPtT* spanPtT = span->ptT();
SkASSERT(this->t() != spanPtT->fT);
SkASSERT(!zero_or_one(spanPtT->fT));
span->detach(this->ptT());
SkOpPtT* remainder = spanPtT->next();
ptT()->insert(spanPtT);
while (remainder != spanPtT) {
SkOpPtT* next = remainder->next();
SkOpPtT* compare = spanPtT->next();
while (compare != spanPtT) {
SkOpPtT* nextC = compare->next();
if (nextC->span() == remainder->span() && nextC->fT == remainder->fT) {
goto tryNextRemainder;
}
compare = nextC;
}
spanPtT->insert(remainder);
tryNextRemainder:
remainder = next;
}
}
void SkOpSpanBase::mergeBaseAttributes(SkOpSpanBase* span) {
SkASSERT(!span->fChased);
SkASSERT(!span->fFromAngle);
if (this->upCastable() && span->upCastable()) {
this->upCast()->mergeAttributes(span->upCast());
}
}
void SkOpSpan::applyCoincidence(SkOpSpan* opp) {
SkASSERT(!final());
SkASSERT(0); // incomplete
}
bool SkOpSpan::containsCoincidence(const SkOpSegment* segment) const {
SkASSERT(this->segment() != segment);
const SkOpSpan* next = this;
while ((next = next->fCoincident) != this) {
if (next->segment() == segment) {
return true;
}
}
return false;
}
void SkOpSpan::detach(SkOpPtT* kept) {
SkASSERT(!final());
SkOpSpan* prev = this->prev();
SkASSERT(prev);
SkOpSpanBase* next = this->next();
SkASSERT(next);
prev->setNext(next);
next->setPrev(prev);
this->segment()->detach(this);
if (this->coincident()) {
this->globalState()->fCoincidence->fixUp(this->ptT(), kept);
}
this->ptT()->setDeleted();
}
void SkOpSpan::init(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) {
SkASSERT(t != 1);
initBase(segment, prev, t, pt);
fCoincident = this;
fToAngle = NULL;
fWindSum = fOppSum = SK_MinS32;
fWindValue = 1;
fOppValue = 0;
fChased = fDone = false;
segment->bumpCount();
}
void SkOpSpan::mergeAttributes(SkOpSpan* span) {
SkASSERT(!span->fToAngle);
if (span->fCoincident) {
this->insertCoincidence(span);
}
}
void SkOpCoincidence::add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart,
SkOpPtT* oppPtTEnd, bool flipped, SkChunkAlloc* allocator) {
SkCoincidentSpans* coinRec = SkOpTAllocator<SkCoincidentSpans>::Allocate(allocator);
SkOpSpanBase* coinEnd = coinPtTEnd->span();
SkOpSpanBase* oppEnd = oppPtTEnd->span();
SkOpSpan* coinStart = coinPtTStart->span()->upCast();
SkASSERT(coinStart == coinStart->starter(coinEnd));
SkOpSpan* oppStart = (flipped ? oppPtTEnd : oppPtTStart)->span()->upCast();
SkASSERT(oppStart == oppStart->starter(oppEnd));
coinStart->insertCoincidence(oppStart);
coinEnd->insertCoinEnd(oppEnd);
coinRec->fNext = this->fHead;
coinRec->fCoinPtTStart = coinPtTStart;
coinRec->fCoinPtTEnd = coinPtTEnd;
coinRec->fOppPtTStart = oppPtTStart;
coinRec->fOppPtTEnd = oppPtTEnd;
coinRec->fFlipped = flipped;
this->fHead = coinRec;
}
bool SkOpCoincidence::contains(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart,
SkOpPtT* oppPtTEnd, bool flipped) {
SkCoincidentSpans* coin = fHead;
if (!coin) {
return false;
}
do {
if (coin->fCoinPtTStart == coinPtTStart && coin->fCoinPtTEnd == coinPtTEnd
&& coin->fOppPtTStart == oppPtTStart && coin->fOppPtTEnd == oppPtTEnd
&& coin->fFlipped == flipped) {
return true;
}
} while ((coin = coin->fNext));
return false;
}
// walk span sets in parallel, moving winding from one to the other
void SkOpCoincidence::apply() {
SkCoincidentSpans* coin = fHead;
if (!coin) {
return;
}
do {
SkOpSpanBase* end = coin->fCoinPtTEnd->span();
SkOpSpan* start = coin->fCoinPtTStart->span()->upCast();
SkASSERT(start == start->starter(end));
bool flipped = coin->fFlipped;
SkOpSpanBase* oEnd = (flipped ? coin->fOppPtTStart : coin->fOppPtTEnd)->span();
SkOpSpan* oStart = (flipped ? coin->fOppPtTEnd : coin->fOppPtTStart)->span()->upCast();
SkASSERT(oStart == oStart->starter(oEnd));
SkOpSegment* segment = start->segment();
SkOpSegment* oSegment = oStart->segment();
bool operandSwap = segment->operand() != oSegment->operand();
if (flipped) {
do {
SkOpSpanBase* oNext = oStart->next();
if (oNext == oEnd) {
break;
}
oStart = oNext->upCast();
} while (true);
}
bool isXor = segment->isXor();
bool oppXor = oSegment->isXor();
do {
int windValue = start->windValue();
int oWindValue = oStart->windValue();
int oppValue = start->oppValue();
int oOppValue = oStart->oppValue();
// winding values are added or subtracted depending on direction and wind type
// same or opposite values are summed depending on the operand value
if (windValue >= oWindValue) {
if (operandSwap) {
SkTSwap(oWindValue, oOppValue);
}
if (flipped) {
windValue -= oWindValue;
oppValue -= oOppValue;
} else {
windValue += oWindValue;
oppValue += oOppValue;
}
if (isXor) {
windValue &= 1;
}
if (oppXor) {
oppValue &= 1;
}
oWindValue = oOppValue = 0;
} else {
if (operandSwap) {
SkTSwap(windValue, oppValue);
}
if (flipped) {
oWindValue -= windValue;
oOppValue -= oppValue;
} else {
oWindValue += windValue;
oOppValue += oppValue;
}
if (isXor) {
oOppValue &= 1;
}
if (oppXor) {
oWindValue &= 1;
}
windValue = oppValue = 0;
}
start->setWindValue(windValue);
start->setOppValue(oppValue);
oStart->setWindValue(oWindValue);
oStart->setOppValue(oOppValue);
if (!windValue && !oppValue) {
segment->markDone(start);
}
if (!oWindValue && !oOppValue) {
oSegment->markDone(oStart);
}
SkOpSpanBase* next = start->next();
SkOpSpanBase* oNext = flipped ? oStart->prev() : oStart->next();
if (next == end) {
break;
}
start = next->upCast();
oStart = oNext->upCast();
} while (true);
} while ((coin = coin->fNext));
}
void SkOpCoincidence::mark() {
SkCoincidentSpans* coin = fHead;
if (!coin) {
return;
}
do {
SkOpSpanBase* end = coin->fCoinPtTEnd->span();
SkOpSpanBase* oldEnd = end;
SkOpSpan* start = coin->fCoinPtTStart->span()->starter(&end);
SkOpSpanBase* oEnd = coin->fOppPtTEnd->span();
SkOpSpanBase* oOldEnd = oEnd;
SkOpSpanBase* oStart = coin->fOppPtTStart->span()->starter(&oEnd);
bool flipped = (end == oldEnd) != (oEnd == oOldEnd);
if (flipped) {
SkTSwap(oStart, oEnd);
}
SkOpSpanBase* next = start;
SkOpSpanBase* oNext = oStart;
do {
next = next->upCast()->next();
oNext = flipped ? oNext->prev() : oNext->upCast()->next();
if (next == end) {
SkASSERT(oNext == oEnd);
break;
}
if (!next->containsCoinEnd(oNext)) {
next->insertCoinEnd(oNext);
}
SkOpSpan* nextSpan = next->upCast();
SkOpSpan* oNextSpan = oNext->upCast();
if (!nextSpan->containsCoincidence(oNextSpan)) {
nextSpan->insertCoincidence(oNextSpan);
}
} while (true);
} while ((coin = coin->fNext));
}