mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-22 07:19:57 -05:00
Implement correct CurveLocation#isCrossing() check.
And improve curve caching. Still needs work.
This commit is contained in:
parent
3ce7d88347
commit
60a725b527
1 changed files with 108 additions and 33 deletions
|
@ -57,19 +57,24 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
||||||
// since this is only required to be unique at runtime among other
|
// since this is only required to be unique at runtime among other
|
||||||
// CurveLocation objects.
|
// CurveLocation objects.
|
||||||
this._id = UID.get(CurveLocation);
|
this._id = UID.get(CurveLocation);
|
||||||
var path = curve._path;
|
this._setCurve(curve);
|
||||||
this._version = path ? path._version : 0;
|
|
||||||
this._curve = curve;
|
|
||||||
this._parameter = parameter;
|
this._parameter = parameter;
|
||||||
this._point = point || curve.getPointAt(parameter, true);
|
this._point = point || curve.getPointAt(parameter, true);
|
||||||
this._distance = _distance;
|
this._distance = _distance;
|
||||||
this._overlap = _overlap;
|
this._overlap = _overlap;
|
||||||
|
this._crossing = null;
|
||||||
this._intersection = _intersection;
|
this._intersection = _intersection;
|
||||||
if (_intersection) {
|
if (_intersection) {
|
||||||
_intersection._intersection = this;
|
_intersection._intersection = this;
|
||||||
// TODO: Remove this once debug logging is removed.
|
// TODO: Remove this once debug logging is removed.
|
||||||
_intersection._other = true;
|
_intersection._other = true;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setCurve: function(curve) {
|
||||||
|
var path = curve._path;
|
||||||
|
this._version = path ? path._version : 0;
|
||||||
|
this._curve = curve;
|
||||||
this._segment = null; // To be determined, see #getSegment()
|
this._segment = null; // To be determined, see #getSegment()
|
||||||
// Also store references to segment1 and segment2, in case path
|
// Also store references to segment1 and segment2, in case path
|
||||||
// splitting / dividing is going to happen, in which case the segments
|
// splitting / dividing is going to happen, in which case the segments
|
||||||
|
@ -84,25 +89,26 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
||||||
* @type Segment
|
* @type Segment
|
||||||
* @bean
|
* @bean
|
||||||
*/
|
*/
|
||||||
getSegment: function(_preferFirst) {
|
getSegment: function() {
|
||||||
if (!this._segment) {
|
// Request curve first, so _segment gets invalidated if it's out of sync
|
||||||
var curve = this.getCurve(),
|
var curve = this.getCurve(),
|
||||||
parameter = this.getParameter();
|
segment = this._segment;
|
||||||
if (parameter === 1) {
|
if (!segment) {
|
||||||
this._segment = curve._segment2;
|
var parameter = this.getParameter();
|
||||||
} else if (parameter === 0 || _preferFirst) {
|
if (parameter === 0) {
|
||||||
this._segment = curve._segment1;
|
segment = curve._segment1;
|
||||||
} else if (parameter == null) {
|
} else if (parameter === 1) {
|
||||||
return null;
|
segment = curve._segment2;
|
||||||
} else {
|
} else if (parameter != null) {
|
||||||
// Determine the closest segment by comparing curve lengths
|
// Determine the closest segment by comparing curve lengths
|
||||||
this._segment = curve.getPartLength(0, parameter)
|
segment = curve.getPartLength(0, parameter)
|
||||||
< curve.getPartLength(parameter, 1)
|
< curve.getPartLength(parameter, 1)
|
||||||
? curve._segment1
|
? curve._segment1
|
||||||
: curve._segment2;
|
: curve._segment2;
|
||||||
}
|
}
|
||||||
|
this._segment = segment;
|
||||||
}
|
}
|
||||||
return this._segment;
|
return segment;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,29 +119,34 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
||||||
*/
|
*/
|
||||||
getCurve: function() {
|
getCurve: function() {
|
||||||
var curve = this._curve,
|
var curve = this._curve,
|
||||||
path = curve && curve._path;
|
path = curve && curve._path,
|
||||||
|
that = this;
|
||||||
if (path && path._version !== this._version) {
|
if (path && path._version !== this._version) {
|
||||||
// If the path's segments have changed in the meantime, clear the
|
// If the path's segments have changed in the meantime, clear the
|
||||||
// internal _parameter value and force refetching of the correct
|
// internal _parameter value and force refetching of the correct
|
||||||
// curve again here.
|
// curve again here.
|
||||||
curve = null;
|
curve = this._parameter = this._curve = null;
|
||||||
this._parameter = null;
|
|
||||||
}
|
}
|
||||||
if (!curve) {
|
|
||||||
// If we're asked to get the curve uncached, access current curve
|
// If path is out of sync, access current curve objects through segment1
|
||||||
// objects through segment1 / segment2. Since path splitting or
|
// / segment2. Since path splitting or dividing might have happened in
|
||||||
// dividing might have happened in the meantime, try segment1's
|
// the meantime, try segment1's curve, and see if _point lies on it
|
||||||
// curve, and see if _point lies on it still, otherwise assume it's
|
// still, otherwise assume it's the curve before segment2.
|
||||||
// the curve before segment2.
|
function trySegment(segment) {
|
||||||
curve = this._segment1.getCurve();
|
var curve = segment && segment.getCurve();
|
||||||
if (curve.getParameterOf(this._point) == null)
|
if (curve && (that._parameter = curve.getParameterOf(that._point))
|
||||||
curve = this._segment2.getPrevious().getCurve();
|
!= null) {
|
||||||
this._curve = curve;
|
|
||||||
// Fetch path again as it could be on a new one through split()
|
// Fetch path again as it could be on a new one through split()
|
||||||
path = curve._path;
|
that._setCurve(curve);
|
||||||
this._version = path ? path._version : 0;
|
that._segment = segment;
|
||||||
}
|
|
||||||
return curve;
|
return curve;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return curve
|
||||||
|
|| trySegment(this._segment)
|
||||||
|
|| trySegment(this._segment1)
|
||||||
|
|| trySegment(this._segment2.getPrevious());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -271,6 +282,70 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
||||||
return curve && curve.split(this.getParameter(), true);
|
return curve && curve.split(this.getParameter(), true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isCrossing: function(report) {
|
||||||
|
// Implementation based on work by Andy Finnell:
|
||||||
|
// http://losingfight.com/blog/2011/07/09/how-to-implement-boolean-operations-on-bezier-paths-part-3/
|
||||||
|
// https://bitbucket.org/andyfinnell/vectorboolean
|
||||||
|
var intersection = this._intersection,
|
||||||
|
crossing = this._crossing;
|
||||||
|
if (crossing != null || !intersection)
|
||||||
|
return crossing || false;
|
||||||
|
// TODO: isTangent() ?
|
||||||
|
// TODO: isAtEndPoint() ?
|
||||||
|
// -> Return if it's a tangent, or if not at an end point, only end
|
||||||
|
// point intersections need more checking!
|
||||||
|
var tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||||
|
tMax = 1 - tMin,
|
||||||
|
PI = Math.PI,
|
||||||
|
PI_2 = PI * 2,
|
||||||
|
// TODO: Make getCurve() sync work in boolean ops after splitting!!!
|
||||||
|
c2 = this._curve,
|
||||||
|
c1 = c2.getPrevious(),
|
||||||
|
c4 = intersection._curve,
|
||||||
|
c3 = c4.getPrevious();
|
||||||
|
if (!c1 || !c3)
|
||||||
|
return this._crossing = false;
|
||||||
|
if (report) {
|
||||||
|
new Path.Circle({
|
||||||
|
center: this.getPoint(),
|
||||||
|
radius: 10,
|
||||||
|
strokeColor: 'red'
|
||||||
|
});
|
||||||
|
new Path({
|
||||||
|
segments: [c1.getSegment1(), c1.getSegment2(), c2.getSegment2()],
|
||||||
|
strokeColor: 'red'
|
||||||
|
});
|
||||||
|
new Path({
|
||||||
|
segments: [c3.getSegment1(), c3.getSegment2(), c4.getSegment2()],
|
||||||
|
strokeColor: 'orange'
|
||||||
|
});
|
||||||
|
console.log(c1.getValues(), c2.getValues(), c3.getValues(), c4.getValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAngle(tangent) {
|
||||||
|
var a = tangent.getAngleInRadians();
|
||||||
|
return a < -PI ? a + PI_2 : a >= PI ? a - PI_2 : a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInRange(angle, min, max) {
|
||||||
|
return min < max
|
||||||
|
? angle > min && angle < max
|
||||||
|
// The range wraps around 0:
|
||||||
|
: angle > min && angle <= PI || angle >= -PI && angle < max;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate angles for all four tangents at the intersection point
|
||||||
|
var a1 = getAngle(c1.getTangentAt(tMax, true).negate()),
|
||||||
|
a2 = getAngle(c2.getTangentAt(tMin, true)),
|
||||||
|
a3 = getAngle(c3.getTangentAt(tMax, true).negate()),
|
||||||
|
a4 = getAngle(c4.getTangentAt(tMin, true));
|
||||||
|
|
||||||
|
// Count how many times curve2 angles appear between the curve1 angles
|
||||||
|
// If each pair of angles split the other two, then the edges cross.
|
||||||
|
return (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2))
|
||||||
|
&& (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether tow CurveLocation objects are describing the same location
|
* Checks whether tow CurveLocation objects are describing the same location
|
||||||
* on a path, by applying the same tolerances as elsewhere when dealing with
|
* on a path, by applying the same tolerances as elsewhere when dealing with
|
||||||
|
|
Loading…
Reference in a new issue