new files for pathops geometric intersection

There's no gyp references to these new files,
so this should only have the effect of reducing
the size of the commit that turns this code on.

TBR=

Review URL: https://codereview.chromium.org/853223002
This commit is contained in:
caryclark 2015-01-16 07:04:10 -08:00 committed by Commit bot
parent 40248f365b
commit 45fa447460
14 changed files with 5043 additions and 0 deletions

View File

@ -0,0 +1,86 @@
/*
* 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 "SkMatrix.h"
#include "SkPath.h"
#include "SkPathOps.h"
void SkOpBuilder::add(const SkPath& path, SkPathOp op) {
if (0 == fOps.count() && op != kUnion_PathOp) {
fPathRefs.push_back() = SkPath();
*fOps.append() = kUnion_PathOp;
}
fPathRefs.push_back() = path;
*fOps.append() = op;
}
void SkOpBuilder::reset() {
fPathRefs.reset();
fOps.reset();
}
/* OPTIMIZATION: Union doesn't need to be all-or-nothing. A run of three or more convex
paths with union ops could be locally resolved and still improve over doing the
ops one at a time. */
bool SkOpBuilder::resolve(SkPath* result) {
int count = fOps.count();
bool allUnion = true;
SkPath::Direction firstDir;
for (int index = 0; index < count; ++index) {
SkPath* test = &fPathRefs[index];
if (kUnion_PathOp != fOps[index] || test->isInverseFillType()) {
allUnion = false;
break;
}
// If all paths are convex, track direction, reversing as needed.
if (test->isConvex()) {
SkPath::Direction dir;
if (!test->cheapComputeDirection(&dir)) {
allUnion = false;
break;
}
if (index == 0) {
firstDir = dir;
} else if (firstDir != dir) {
SkPath temp;
temp.reverseAddPath(*test);
*test = temp;
}
continue;
}
// If the path is not convex but its bounds do not intersect the others, simplify is enough.
const SkRect& testBounds = test->getBounds();
for (int inner = 0; inner < index; ++inner) {
// OPTIMIZE: check to see if the contour bounds do not intersect other contour bounds?
if (SkRect::Intersects(fPathRefs[inner].getBounds(), testBounds)) {
allUnion = false;
break;
}
}
}
if (!allUnion) {
*result = fPathRefs[0];
for (int index = 1; index < count; ++index) {
if (!Op(*result, fPathRefs[index], fOps[index], result)) {
reset();
return false;
}
}
reset();
return true;
}
SkPath sum;
for (int index = 0; index < count; ++index) {
if (!Simplify(fPathRefs[index], &fPathRefs[index])) {
reset();
return false;
}
sum.addPath(fPathRefs[index]);
}
reset();
return Simplify(sum, result);
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkOpCoincidence_DEFINED
#define SkOpCoincidence_DEFINED
#include "SkOpTAllocator.h"
#include "SkOpSpan.h"
class SkOpPtT;
struct SkCoincidentSpans {
SkCoincidentSpans* fNext;
SkOpPtT* fCoinPtTStart;
SkOpPtT* fCoinPtTEnd;
SkOpPtT* fOppPtTStart;
SkOpPtT* fOppPtTEnd;
bool fFlipped;
};
class SkOpCoincidence {
public:
SkOpCoincidence()
: fHead(NULL) {
}
void add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart,
SkOpPtT* oppPtTEnd, bool flipped, SkChunkAlloc* allocator);
void apply();
bool contains(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart,
SkOpPtT* oppPtTEnd, bool flipped);
void dump() const;
void mark();
SkCoincidentSpans* fHead;
};
#endif

View File

@ -0,0 +1,126 @@
/*
* 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 "SkPathOpsCubic.h"
static bool rotate(const SkDCubic& cubic, int zero, int index, SkDCubic& rotPath) {
double dy = cubic[index].fY - cubic[zero].fY;
double dx = cubic[index].fX - cubic[zero].fX;
if (approximately_zero(dy)) {
if (approximately_zero(dx)) {
return false;
}
rotPath = cubic;
return true;
}
for (int index = 0; index < 4; ++index) {
rotPath[index].fX = cubic[index].fX * dx + cubic[index].fY * dy;
rotPath[index].fY = cubic[index].fY * dx - cubic[index].fX * dy;
}
return true;
}
// Returns 0 if negative, 1 if zero, 2 if positive
static int side(double x) {
return (x > 0) + (x >= 0);
}
/* Given a cubic, find the convex hull described by the end and control points.
The hull may have 3 or 4 points. Cubics that degenerate into a point or line
are not considered.
The hull is computed by assuming that three points, if unique and non-linear,
form a triangle. The fourth point may replace one of the first three, may be
discarded if in the triangle or on an edge, or may be inserted between any of
the three to form a convex quadralateral.
The indices returned in order describe the convex hull.
*/
int SkDCubic::convexHull(char order[4]) const {
size_t index;
// find top point
size_t yMin = 0;
for (index = 1; index < 4; ++index) {
if (fPts[yMin].fY > fPts[index].fY || (fPts[yMin].fY == fPts[index].fY
&& fPts[yMin].fX > fPts[index].fX)) {
yMin = index;
}
}
order[0] = yMin;
int midX = -1;
int backupYMin = -1;
for (int pass = 0; pass < 2; ++pass) {
for (index = 0; index < 4; ++index) {
if (index == yMin) {
continue;
}
// rotate line from (yMin, index) to axis
// see if remaining two points are both above or below
// use this to find mid
int mask = other_two(yMin, index);
int side1 = yMin ^ mask;
int side2 = index ^ mask;
SkDCubic rotPath;
if (!rotate(*this, yMin, index, rotPath)) { // ! if cbc[yMin]==cbc[idx]
order[1] = side1;
order[2] = side2;
return 3;
}
int sides = side(rotPath[side1].fY - rotPath[yMin].fY);
sides ^= side(rotPath[side2].fY - rotPath[yMin].fY);
if (sides == 2) { // '2' means one remaining point <0, one >0
if (midX >= 0) {
// one of the control points is equal to an end point
order[0] = 0;
order[1] = 3;
if (fPts[1] == fPts[0] || fPts[1] == fPts[3]) {
order[2] = 2;
return 3;
}
SkASSERT(fPts[2] == fPts[0] || fPts[2] == fPts[3]);
order[2] = 1;
return 3;
}
midX = index;
} else if (sides == 0) { // '0' means both to one side or the other
backupYMin = index;
}
}
if (midX >= 0) {
break;
}
if (backupYMin < 0) {
break;
}
yMin = backupYMin;
backupYMin = -1;
}
if (midX < 0) {
midX = yMin ^ 3; // choose any other point
}
int mask = other_two(yMin, midX);
int least = yMin ^ mask;
int most = midX ^ mask;
order[0] = yMin;
order[1] = least;
// see if mid value is on same side of line (least, most) as yMin
SkDCubic midPath;
if (!rotate(*this, least, most, midPath)) { // ! if cbc[least]==cbc[most]
order[2] = midX;
return 3;
}
int midSides = side(midPath[yMin].fY - midPath[least].fY);
midSides ^= side(midPath[midX].fY - midPath[least].fY);
if (midSides != 2) { // if mid point is not between
order[2] = most;
return 3; // result is a triangle
}
order[2] = midX;
order[3] = most;
return 4; // result is a quadralateral
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkOpTAllocator_DEFINED
#define SkOpTAllocator_DEFINED
#include "SkChunkAlloc.h"
// T is SkOpAngle2, SkOpSpan2, or SkOpSegment2
template<typename T>
class SkOpTAllocator {
public:
static T* Allocate(SkChunkAlloc* allocator) {
void* ptr = allocator->allocThrow(sizeof(T));
T* record = (T*) ptr;
return record;
}
static T* New(SkChunkAlloc* allocator) {
return new (Allocate(allocator)) T();
}
};
#endif

View File

@ -0,0 +1,175 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkCubicSpan_DEFINE
#define SkCubicSpan_DEFINE
#include "SkChunkAlloc.h"
#include "SkPathOpsRect.h"
#include "SkPathOpsCubic.h"
#include "SkTArray.h"
class SkIntersections;
class SkCubicCoincident {
public:
bool isCoincident() const {
return fCoincident;
}
void init() {
fCoincident = false;
SkDEBUGCODE(fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN);
SkDEBUGCODE(fPerpT = SK_ScalarNaN);
}
void markCoincident() {
if (!fCoincident) {
fPerpT = -1;
}
fCoincident = true;
}
const SkDPoint& perpPt() const {
return fPerpPt;
}
double perpT() const {
return fPerpT;
}
void setPerp(const SkDCubic& cubic1, double t, const SkDPoint& qPt, const SkDCubic& cubic2);
private:
SkDPoint fPerpPt;
double fPerpT; // perpendicular intersection on opposite Cubic
bool fCoincident;
};
class SkCubicSect; // used only by debug id
class SkCubicSpan {
public:
void init(const SkDCubic& Cubic);
void initBounds(const SkDCubic& Cubic);
bool contains(double t) const {
return !! const_cast<SkCubicSpan*>(this)->innerFind(t);
}
bool contains(const SkCubicSpan* span) const;
SkCubicSpan* find(double t) {
SkCubicSpan* result = innerFind(t);
SkASSERT(result);
return result;
}
bool intersects(const SkCubicSpan* span) const;
const SkCubicSpan* next() const {
return fNext;
}
void reset() {
fBounded.reset();
}
bool split(SkCubicSpan* work) {
return splitAt(work, (work->fStartT + work->fEndT) * 0.5);
}
bool splitAt(SkCubicSpan* work, double t);
bool tightBoundsIntersects(const SkCubicSpan* span) const;
// implementation is for testing only
void dump() const;
private:
bool hullIntersects(const SkDCubic& ) const;
SkCubicSpan* innerFind(double t);
bool linearIntersects(const SkDCubic& ) const;
// implementation is for testing only
#if DEBUG_BINARY_CUBIC
int debugID(const SkCubicSect* ) const { return fDebugID; }
#else
int debugID(const SkCubicSect* ) const;
#endif
void dump(const SkCubicSect* ) const;
void dumpID(const SkCubicSect* ) const;
#if DEBUG_BINARY_CUBIC
void validate() const;
#endif
SkDCubic fPart;
SkCubicCoincident fCoinStart;
SkCubicCoincident fCoinEnd;
SkSTArray<4, SkCubicSpan*, true> fBounded;
SkCubicSpan* fPrev;
SkCubicSpan* fNext;
SkDRect fBounds;
double fStartT;
double fEndT;
double fBoundsMax;
bool fCollapsed;
bool fHasPerp;
mutable bool fIsLinear;
#if DEBUG_BINARY_CUBIC
int fDebugID;
bool fDebugDeleted;
#endif
friend class SkCubicSect;
};
class SkCubicSect {
public:
SkCubicSect(const SkDCubic& Cubic PATH_OPS_DEBUG_PARAMS(int id));
static void BinarySearch(SkCubicSect* sect1, SkCubicSect* sect2, SkIntersections* intersections);
// for testing only
void dumpCubics() const;
private:
SkCubicSpan* addOne();
bool binarySearchCoin(const SkCubicSect& , double tStart, double tStep, double* t,
double* oppT);
SkCubicSpan* boundsMax() const;
void coincidentCheck(SkCubicSect* sect2);
bool intersects(const SkCubicSpan* span, const SkCubicSect* opp, const SkCubicSpan* oppSpan) const;
void onCurveCheck(SkCubicSect* sect2, SkCubicSpan* first, SkCubicSpan* last);
void recoverCollapsed();
void removeSpan(SkCubicSpan* span);
void removeOne(const SkCubicSpan* test, SkCubicSpan* span);
void removeSpans(SkCubicSpan* span, SkCubicSect* opp);
void setPerp(const SkDCubic& opp, SkCubicSpan* first, SkCubicSpan* last);
void trim(SkCubicSpan* span, SkCubicSect* opp);
// for testing only
void dump() const;
void dumpBoth(const SkCubicSect& opp) const;
void dumpBoth(const SkCubicSect* opp) const;
#if DEBUG_BINARY_CUBIC
int debugID() const { return fDebugID; }
void validate() const;
#else
int debugID() const { return 0; }
#endif
const SkDCubic& fCubic;
SkChunkAlloc fHeap;
SkCubicSpan* fHead;
SkCubicSpan* fDeleted;
int fActiveCount;
#if DEBUG_BINARY_CUBIC
int fDebugID;
int fDebugCount;
int fDebugAllocatedCount;
#endif
friend class SkCubicSpan; // only used by debug id
};
#endif

View File

@ -0,0 +1,502 @@
/*
* 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();
}
SkOpDebugState* SkOpPtT::debugState() const {
return PATH_OPS_DEBUG_RELEASE(contour()->debugState(), 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->debugState()->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();
fDeleted = true;
return prev;
}
prev = next;
} while (prev != this);
SkASSERT(0);
return NULL;
}
void SkOpPtT::removeNext() {
SkASSERT(this->fNext);
SkOpPtT* next = this->fNext;
this->fNext = next->fNext;
SkOpSpanBase* span = next->span();
next->setDeleted();
if (span->ptT() == next) {
span->upCast()->detach();
}
}
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();
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();
}
SkOpDebugState* SkOpSpanBase::debugState() const {
return PATH_OPS_DEBUG_RELEASE(contour()->debugState(), 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 = ++debugState()->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();
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() {
SkASSERT(!final());
SkOpSpan* prev = this->prev();
SkASSERT(prev);
SkOpSpanBase* next = this->next();
SkASSERT(next);
prev->setNext(next);
next->setPrev(prev);
this->segment()->detach(this);
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));
}

View File

@ -0,0 +1,175 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkQuadSpan_DEFINE
#define SkQuadSpan_DEFINE
#include "SkChunkAlloc.h"
#include "SkPathOpsRect.h"
#include "SkPathOpsQuad.h"
#include "SkTArray.h"
class SkIntersections;
class SkQuadCoincident {
public:
bool isCoincident() const {
return fCoincident;
}
void init() {
fCoincident = false;
SkDEBUGCODE(fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN);
SkDEBUGCODE(fPerpT = SK_ScalarNaN);
}
void markCoincident() {
if (!fCoincident) {
fPerpT = -1;
}
fCoincident = true;
}
const SkDPoint& perpPt() const {
return fPerpPt;
}
double perpT() const {
return fPerpT;
}
void setPerp(const SkDQuad& quad1, double t, const SkDPoint& qPt, const SkDQuad& quad2);
private:
SkDPoint fPerpPt;
double fPerpT; // perpendicular intersection on opposite quad
bool fCoincident;
};
class SkQuadSect; // used only by debug id
class SkQuadSpan {
public:
void init(const SkDQuad& quad);
void initBounds(const SkDQuad& quad);
bool contains(double t) const {
return !! const_cast<SkQuadSpan*>(this)->innerFind(t);
}
bool contains(const SkQuadSpan* span) const;
SkQuadSpan* find(double t) {
SkQuadSpan* result = innerFind(t);
SkASSERT(result);
return result;
}
bool intersects(const SkQuadSpan* span) const;
const SkQuadSpan* next() const {
return fNext;
}
void reset() {
fBounded.reset();
}
bool split(SkQuadSpan* work) {
return splitAt(work, (work->fStartT + work->fEndT) * 0.5);
}
bool splitAt(SkQuadSpan* work, double t);
bool tightBoundsIntersects(const SkQuadSpan* span) const;
// implementation is for testing only
void dump() const;
private:
bool hullIntersects(const SkDQuad& q2) const;
SkQuadSpan* innerFind(double t);
bool linearIntersects(const SkDQuad& q2) const;
// implementation is for testing only
#if DEBUG_BINARY_QUAD
int debugID(const SkQuadSect* ) const { return fDebugID; }
#else
int debugID(const SkQuadSect* ) const;
#endif
void dump(const SkQuadSect* ) const;
void dumpID(const SkQuadSect* ) const;
#if DEBUG_BINARY_QUAD
void validate() const;
#endif
SkDQuad fPart;
SkQuadCoincident fCoinStart;
SkQuadCoincident fCoinEnd;
SkSTArray<4, SkQuadSpan*, true> fBounded;
SkQuadSpan* fPrev;
SkQuadSpan* fNext;
SkDRect fBounds;
double fStartT;
double fEndT;
double fBoundsMax;
bool fCollapsed;
bool fHasPerp;
mutable bool fIsLinear;
#if DEBUG_BINARY_QUAD
int fDebugID;
bool fDebugDeleted;
#endif
friend class SkQuadSect;
};
class SkQuadSect {
public:
SkQuadSect(const SkDQuad& quad PATH_OPS_DEBUG_PARAMS(int id));
static void BinarySearch(SkQuadSect* sect1, SkQuadSect* sect2, SkIntersections* intersections);
// for testing only
void dumpQuads() const;
private:
SkQuadSpan* addOne();
bool binarySearchCoin(const SkQuadSect& , double tStart, double tStep, double* t, double* oppT);
SkQuadSpan* boundsMax() const;
void coincidentCheck(SkQuadSect* sect2);
bool intersects(const SkQuadSpan* span, const SkQuadSect* opp, const SkQuadSpan* oppSpan) const;
void onCurveCheck(SkQuadSect* sect2, SkQuadSpan* first, SkQuadSpan* last);
void recoverCollapsed();
void removeSpan(SkQuadSpan* span);
void removeOne(const SkQuadSpan* test, SkQuadSpan* span);
void removeSpans(SkQuadSpan* span, SkQuadSect* opp);
void setPerp(const SkDQuad& opp, SkQuadSpan* first, SkQuadSpan* last);
const SkQuadSpan* tail() const;
void trim(SkQuadSpan* span, SkQuadSect* opp);
// for testing only
void dump() const;
void dumpBoth(const SkQuadSect& opp) const;
void dumpBoth(const SkQuadSect* opp) const;
#if DEBUG_BINARY_QUAD
int debugID() const { return fDebugID; }
void validate() const;
#else
int debugID() const { return 0; }
#endif
const SkDQuad& fQuad;
SkChunkAlloc fHeap;
SkQuadSpan* fHead;
SkQuadSpan* fDeleted;
int fActiveCount;
#if DEBUG_BINARY_QUAD
int fDebugID;
int fDebugCount;
int fDebugAllocatedCount;
#endif
friend class SkQuadSpan; // only used by debug id
};
#endif

View File

@ -0,0 +1,15 @@
/*
* 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 "SkPathOpsTSect.h"
int SkIntersections::intersectB(const SkDCubic& cubic1, const SkDCubic& cubic2) {
SkTSect<SkDCubic> sect1(cubic1 PATH_OPS_DEBUG_PARAMS(1));
SkTSect<SkDCubic> sect2(cubic2 PATH_OPS_DEBUG_PARAMS(2));
SkTSect<SkDCubic>::BinarySearch(&sect1, &sect2, this);
return used();
}

View File

@ -0,0 +1,15 @@
/*
* 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 "SkPathOpsTSect.h"
int SkIntersections::intersectB(const SkDQuad& quad1, const SkDQuad& quad2) {
SkTSect<SkDQuad> sect1(quad1 PATH_OPS_DEBUG_PARAMS(1));
SkTSect<SkDQuad> sect2(quad2 PATH_OPS_DEBUG_PARAMS(2));
SkTSect<SkDQuad>::BinarySearch(&sect1, &sect2, this);
return used();
}

1211
src/pathops/SkPathOpsTSect.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
/*
* 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 "PathOpsTestCommon.h"
#include "Test.h"
DEF_TEST(PathOpsBuilder, reporter) {
SkOpBuilder builder;
SkPath result;
REPORTER_ASSERT(reporter, builder.resolve(&result));
REPORTER_ASSERT(reporter, result.isEmpty());
builder.add(result, kDifference_PathOp);
REPORTER_ASSERT(reporter, builder.resolve(&result));
REPORTER_ASSERT(reporter, result.isEmpty());
builder.add(result, kUnion_PathOp);
REPORTER_ASSERT(reporter, builder.resolve(&result));
REPORTER_ASSERT(reporter, result.isEmpty());
SkPath rectPath;
rectPath.addRect(0, 1, 2, 3, SkPath::kCW_Direction);
builder.add(rectPath, kUnion_PathOp);
REPORTER_ASSERT(reporter, builder.resolve(&result));
bool closed;
SkPath::Direction dir;
REPORTER_ASSERT(reporter, result.isRect(NULL, &closed, &dir));
REPORTER_ASSERT(reporter, closed);
REPORTER_ASSERT(reporter, dir == SkPath::kCW_Direction);
REPORTER_ASSERT(reporter, rectPath == result);
rectPath.reset();
rectPath.addRect(0, 1, 2, 3, SkPath::kCCW_Direction);
builder.add(rectPath, kUnion_PathOp);
REPORTER_ASSERT(reporter, builder.resolve(&result));
REPORTER_ASSERT(reporter, result.isRect(NULL, &closed, &dir));
REPORTER_ASSERT(reporter, closed);
REPORTER_ASSERT(reporter, dir == SkPath::kCCW_Direction);
REPORTER_ASSERT(reporter, rectPath == result);
builder.add(rectPath, kDifference_PathOp);
REPORTER_ASSERT(reporter, builder.resolve(&result));
REPORTER_ASSERT(reporter, result.isEmpty());
SkPath rect2, rect3;
rect2.addRect(2, 1, 4, 3, SkPath::kCW_Direction);
rect3.addRect(4, 1, 5, 3, SkPath::kCCW_Direction);
builder.add(rectPath, kUnion_PathOp);
builder.add(rect2, kUnion_PathOp);
builder.add(rect3, kUnion_PathOp);
REPORTER_ASSERT(reporter, builder.resolve(&result));
REPORTER_ASSERT(reporter, result.isRect(NULL, &closed, &dir));
REPORTER_ASSERT(reporter, closed);
SkRect expected;
expected.set(0, 1, 5, 3);
REPORTER_ASSERT(reporter, result.getBounds() == expected);
SkPath circle1, circle2, circle3;
circle1.addCircle(5, 6, 4, SkPath::kCW_Direction);
circle2.addCircle(7, 4, 8, SkPath::kCCW_Direction);
circle3.addCircle(6, 5, 6, SkPath::kCW_Direction);
SkPath opCompare;
Op(circle1, circle2, kUnion_PathOp, &opCompare);
Op(opCompare, circle3, kDifference_PathOp, &opCompare);
builder.add(circle1, kUnion_PathOp);
builder.add(circle2, kUnion_PathOp);
builder.add(circle3, kDifference_PathOp);
REPORTER_ASSERT(reporter, builder.resolve(&result));
REPORTER_ASSERT(reporter, opCompare == result);
}

84
tests/PathOpsTSectDebug.h Normal file
View File

@ -0,0 +1,84 @@
/*
* 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 "SkPathOpsTSect.h"
template<typename TCurve>
void SkTSect<TCurve>::dump() const {
SkDebugf("id=%d", debugID());
const SkTSpan<TCurve>* test = fHead;
if (!test) {
SkDebugf(" (empty)");
return;
}
do {
SkDebugf(" ");
test->dump(this);
} while ((test = test->next()));
}
template<typename TCurve>
void SkTSect<TCurve>::dumpBoth(const SkTSect& opp) const {
dump();
SkDebugf(" ");
opp.dump();
SkDebugf("\n");
}
template<typename TCurve>
void SkTSect<TCurve>::dumpBoth(const SkTSect* opp) const {
dumpBoth(*opp);
}
template<typename TCurve>
void SkTSect<TCurve>::dumpCurves() const {
const SkTSpan<TCurve>* test = fHead;
do {
test->fPart.dump();
} while ((test = test->next()));
}
#if !DEBUG_T_SECT
template<typename TCurve>
int SkTSpan<TCurve>::debugID(const SkTSect<TCurve>* sect) const {
if (!sect) {
return -1;
}
int id = 1;
const SkTSpan* test = sect->fHead;
while (test && test != this) {
++id;
test = test->fNext;
}
return id;
}
#endif
template<typename TCurve>
void SkTSpan<TCurve>::dumpID(const SkTSect<TCurve>* sect) const {
if (fCoinStart.isCoincident()) {
SkDebugf("%c", '*');
}
SkDebugf("%d", debugID(sect));
if (fCoinEnd.isCoincident()) {
SkDebugf("%c", '*');
}
}
template<typename TCurve>
void SkTSpan<TCurve>::dump(const SkTSect<TCurve>* sect) const {
dumpID(sect);
SkDebugf("=(%g,%g) [", fStartT, fEndT);
for (int index = 0; index < fBounded.count(); ++index) {
SkTSpan* span = fBounded[index];
span->dumpID(sect);
if (index < fBounded.count() - 1) {
SkDebugf(",");
}
}
SkDebugf("]");
}

View File

@ -0,0 +1,78 @@
/*
* 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 "SkIntersections.h"
#include "SkTDArray.h"
#include "Test.h"
// check intersections for consistency
struct Curve {
int ptCount;
SkDCubic curve; // largest can hold lines / quads/ cubics
};
static const Curve testSet0[] = { // extracted from skpClip2
{4, {{{134,11414}, {131.990234,11414}, {130.32666,11415.4824}, {130.042755,11417.4131}}} },
{4, {{{130.042755,11417.4131}, {130.233124,11418.3193}, {131.037079,11419}, {132,11419}}} },
{4, {{{132,11419}, {130.895432,11419}, {130,11418.1045}, {130,11417}}} },
};
static const Curve testSet1[] = { // extracted from cubicOp85i
{4, {{{3,4}, {1,5}, {4,3}, {6,4}}} },
{1, {{{6,4}, {3,4}}} },
{4, {{{3,4}, {4,6}, {4,3}, {5,1}}} },
{1, {{{5,1}, {3,4}}} },
};
static const struct TestSet {
const Curve* tests;
int testCount;
} testSets[] = {
{ testSet0, (int) SK_ARRAY_COUNT(testSet0) },
{ testSet1, (int) SK_ARRAY_COUNT(testSet1) },
};
static const int testSetsCount = (int) SK_ARRAY_COUNT(testSets);
static void testSetTest(skiatest::Reporter* reporter, int index) {
const TestSet& testSet = testSets[index];
int testCount = testSet.testCount;
SkASSERT(testCount > 1);
SkTDArray<SkIntersections> combos;
for (int outer = 0; outer < testCount - 1; ++outer) {
const Curve& oTest = testSet.tests[outer];
for (int inner = outer + 1; inner < testCount; ++inner) {
const Curve& iTest = testSet.tests[inner];
SkIntersections* i = combos.append();
sk_bzero(i, sizeof(SkIntersections));
if (oTest.ptCount == 1 && iTest.ptCount == 1) {
i->intersect(*(const SkDLine*) &oTest.curve, *(const SkDLine*) &iTest.curve);
} else if (oTest.ptCount == 1 && iTest.ptCount == 4) {
i->intersect(iTest.curve, *(const SkDLine*) &oTest.curve);
} else if (oTest.ptCount == 4 && iTest.ptCount == 1) {
i->intersect(oTest.curve, *(const SkDLine*) &oTest.curve);
} else if (oTest.ptCount == 4 && iTest.ptCount == 4) {
i->intersectB(oTest.curve, iTest.curve);
} else {
SkASSERT(0);
}
// i->dump();
}
}
}
DEF_TEST(PathOpsThreeWay, reporter) {
for (int index = 0; index < testSetsCount; ++index) {
testSetTest(reporter, index);
reporter->bumpTestCount();
}
}
DEF_TEST(PathOpsThreeWayOneOff, reporter) {
int index = 1;
testSetTest(reporter, index);
}