mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Merge branch 'new-smooth' into develop
This commit is contained in:
commit
20f90bbee2
6 changed files with 279 additions and 169 deletions
|
@ -2445,24 +2445,24 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* @return {Item[]} an array containing the removed items
|
||||
*/
|
||||
/**
|
||||
* Removes the children from the specified `from` index to the `to` index
|
||||
* from the parent's {@link #children} array.
|
||||
* Removes the children from the specified `start` index to and excluding
|
||||
* the `end` index from the parent's {@link #children} array.
|
||||
*
|
||||
* @name Item#removeChildren
|
||||
* @function
|
||||
* @param {Number} from the beginning index, inclusive
|
||||
* @param {Number} [to=children.length] the ending index, exclusive
|
||||
* @param {Number} start the beginning index, inclusive
|
||||
* @param {Number} [end=children.length] the ending index, exclusive
|
||||
* @return {Item[]} an array containing the removed items
|
||||
*/
|
||||
removeChildren: function(from, to) {
|
||||
removeChildren: function(start, end) {
|
||||
if (!this._children)
|
||||
return null;
|
||||
from = from || 0;
|
||||
to = Base.pick(to, this._children.length);
|
||||
start = start || 0;
|
||||
end = Base.pick(end, this._children.length);
|
||||
// Use Base.splice(), which adjusts #_index for the items above, and
|
||||
// deletes it for the removed items. Calling #_remove() afterwards is
|
||||
// fine, since it only calls Base.splice() if #_index is set.
|
||||
var removed = Base.splice(this._children, null, from, to - from);
|
||||
var removed = Base.splice(this._children, null, start, end - start);
|
||||
for (var i = removed.length - 1; i >= 0; i--) {
|
||||
// Don't notify parent each time, notify it separately after.
|
||||
removed[i]._remove(true, false);
|
||||
|
|
|
@ -140,12 +140,6 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
children[i].reverse();
|
||||
},
|
||||
|
||||
smooth: function() {
|
||||
var children = this._children;
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
children[i].smooth();
|
||||
},
|
||||
|
||||
// DOCS: reduce()
|
||||
// TEST: reduce()
|
||||
reduce: function reduce(options) {
|
||||
|
@ -165,6 +159,13 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
return reduce.base.call(this);
|
||||
},
|
||||
|
||||
// NOTE: Documentation is in PathItem.js
|
||||
smooth: function(options) {
|
||||
var children = this._children;
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
children[i].smooth(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies whether the compound path is oriented clock-wise.
|
||||
*
|
||||
|
|
308
src/path/Path.js
308
src/path/Path.js
|
@ -411,21 +411,21 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
var total = this._countCurves(),
|
||||
// If we're adding a new segment to the end of an open path,
|
||||
// we need to step one index down to get its curve.
|
||||
from = index > 0 && index + amount - 1 === total ? index - 1
|
||||
start = index > 0 && index + amount - 1 === total ? index - 1
|
||||
: index,
|
||||
start = from,
|
||||
to = Math.min(from + amount, total);
|
||||
insert = start,
|
||||
end = Math.min(start + amount, total);
|
||||
if (segs._curves) {
|
||||
// Reuse removed curves.
|
||||
curves.splice.apply(curves, [from, 0].concat(segs._curves));
|
||||
start += segs._curves.length;
|
||||
curves.splice.apply(curves, [start, 0].concat(segs._curves));
|
||||
insert += segs._curves.length;
|
||||
}
|
||||
// Insert new curves, but do not initialize their segments yet,
|
||||
// since #_adjustCurves() handles all that for us.
|
||||
for (var i = start; i < to; i++)
|
||||
for (var i = insert; i < end; i++)
|
||||
curves.splice(i, 0, new Curve(this, null, null));
|
||||
// Adjust segments for the curves before and after the removed ones
|
||||
this._adjustCurves(from, to);
|
||||
this._adjustCurves(start, end);
|
||||
}
|
||||
// Use SEGMENTS notification instead of GEOMETRY since curves are kept
|
||||
// up-to-date by _adjustCurves() and don't need notification.
|
||||
|
@ -436,11 +436,11 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
/**
|
||||
* Adjusts segments of curves before and after inserted / removed segments.
|
||||
*/
|
||||
_adjustCurves: function(from, to) {
|
||||
_adjustCurves: function(start, end) {
|
||||
var segments = this._segments,
|
||||
curves = this._curves,
|
||||
curve;
|
||||
for (var i = from; i < to; i++) {
|
||||
for (var i = start; i < end; i++) {
|
||||
curve = curves[i];
|
||||
curve._path = this;
|
||||
curve._segment1 = segments[i];
|
||||
|
@ -449,14 +449,14 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
}
|
||||
// If it's the first segment, correct the last segment of closed
|
||||
// paths too:
|
||||
if (curve = curves[this._closed && from === 0 ? segments.length - 1
|
||||
: from - 1]) {
|
||||
curve._segment2 = segments[from] || segments[0];
|
||||
if (curve = curves[this._closed && start === 0 ? segments.length - 1
|
||||
: start - 1]) {
|
||||
curve._segment2 = segments[start] || segments[0];
|
||||
curve._changed();
|
||||
}
|
||||
// Fix the segment after the modified range, if it exists
|
||||
if (curve = curves[to]) {
|
||||
curve._segment1 = segments[to];
|
||||
if (curve = curves[end]) {
|
||||
curve._segment1 = segments[end];
|
||||
curve._changed();
|
||||
}
|
||||
},
|
||||
|
@ -719,13 +719,13 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
* // Select the path, so we can see its segments:
|
||||
* path.selected = true;
|
||||
*/
|
||||
removeSegments: function(from, to, _includeCurves) {
|
||||
from = from || 0;
|
||||
to = Base.pick(to, this._segments.length);
|
||||
removeSegments: function(start, end, _includeCurves) {
|
||||
start = start || 0;
|
||||
end = Base.pick(end, this._segments.length);
|
||||
var segments = this._segments,
|
||||
curves = this._curves,
|
||||
count = segments.length, // segment count before removal
|
||||
removed = segments.splice(from, to - from),
|
||||
removed = segments.splice(start, end - start),
|
||||
amount = removed.length;
|
||||
if (!amount)
|
||||
return removed;
|
||||
|
@ -738,7 +738,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
segment._index = segment._path = null;
|
||||
}
|
||||
// Adjust the indices of the segments above.
|
||||
for (var i = from, l = segments.length; i < l; i++)
|
||||
for (var i = start, l = segments.length; i < l; i++)
|
||||
segments[i]._index = i;
|
||||
// Keep curves in sync
|
||||
if (curves) {
|
||||
|
@ -746,9 +746,9 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
// one to the left of the segment, not to the right, as normally).
|
||||
// Also take into account closed paths, which have one curve more
|
||||
// than segments.
|
||||
var index = from > 0 && to === count + (this._closed ? 1 : 0)
|
||||
? from - 1
|
||||
: from,
|
||||
var index = start > 0 && end === count + (this._closed ? 1 : 0)
|
||||
? start - 1
|
||||
: start,
|
||||
curves = curves.splice(index, amount);
|
||||
// Unlink the removed curves from the path.
|
||||
for (var i = curves.length - 1; i >= 0; i--)
|
||||
|
@ -2056,9 +2056,147 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
*/
|
||||
getNearestPoint: function(/* point */) {
|
||||
return this.getNearestLocation.apply(this, arguments).getPoint();
|
||||
},
|
||||
|
||||
// NOTE: Documentation is in PathItem.js
|
||||
smooth: function(options) {
|
||||
// Helper method to pick the right from / to indices.
|
||||
// Supports numbers and segment objects.
|
||||
// For numbers, the `to` index is exclusive, while for segments and
|
||||
// curves, it is inclusive, handled by the `offset` parameter.
|
||||
function getIndex(value, _default) {
|
||||
var index = value == null
|
||||
? _default
|
||||
: typeof value === 'number'
|
||||
? value
|
||||
// Support both Segment and Curve through getIndex()
|
||||
: value.getIndex
|
||||
? value.getIndex()
|
||||
+ (_default && value instanceof Curve ? 1 : 0)
|
||||
: _default;
|
||||
// Handle negative values based on whether a path is open or not:
|
||||
// Ranges on closed paths are allowed to wrapped around the
|
||||
// beginning/end (e.g. start near the end, end near the beginning),
|
||||
// while ranges on open paths stay within the path's open range.
|
||||
return Numerical.clamp(index < 0 && closed
|
||||
? index % length
|
||||
: index < 0 ? index + length : index, 0, length);
|
||||
}
|
||||
|
||||
var opts = options || {},
|
||||
type = opts.type,
|
||||
segments = this._segments,
|
||||
length = segments.length,
|
||||
range = opts.from !== undefined || opts.to !== undefined,
|
||||
from = getIndex(opts.from, 0),
|
||||
to = getIndex(opts.to, length - 1),
|
||||
closed = this._closed;
|
||||
if (from > to) {
|
||||
if (closed) {
|
||||
from -= length;
|
||||
} else {
|
||||
var tmp = from;
|
||||
from = to;
|
||||
to = tmp;
|
||||
}
|
||||
}
|
||||
if (!type || type === 'continuous') {
|
||||
// Continuous smoothing approach based on work by Lubos Brieda,
|
||||
// Particle In Cell Consulting LLC, but further simplified by
|
||||
// addressing handle symmetry across segments, and the possibility
|
||||
// to process x and y coordinates simultaneously. Also added
|
||||
// handling of closed paths.
|
||||
// https://www.particleincell.com/2012/bezier-splines/
|
||||
var min = Math.min,
|
||||
amount = to - from + 1,
|
||||
n = amount - 1;
|
||||
// Overlap by up to 4 points on closed paths since a current segment
|
||||
// is affected by its 4 neighbors on both sides (?).
|
||||
var loop = closed && !range,
|
||||
padding = loop ? min(amount, 4) : 1,
|
||||
paddingLeft = padding,
|
||||
paddingRight = padding,
|
||||
knots = [];
|
||||
if (!closed) {
|
||||
// If the path is open and a range is defined, try using a
|
||||
// padding of 1 on either side.
|
||||
paddingLeft = min(1, from);
|
||||
paddingRight = min(1, length - to - 1);
|
||||
}
|
||||
// Set up the knots array now, taking the paddings into account.
|
||||
n += paddingLeft + paddingRight;
|
||||
if (n <= 1)
|
||||
return;
|
||||
for (var i = 0, j = from - paddingLeft; i <= n; i++, j++) {
|
||||
knots[i] = segments[(j < 0 ? j + length : j) % length]._point;
|
||||
}
|
||||
|
||||
// Right-hand side vectors, with left most segment added.
|
||||
var a = [0],
|
||||
b = [2],
|
||||
c = [1],
|
||||
rx = [knots[0]._x + 2 * knots[1]._x],
|
||||
ry = [knots[0]._y + 2 * knots[1]._y],
|
||||
n_1 = n - 1;
|
||||
|
||||
// Internal segments
|
||||
for (var i = 1; i < n_1; i++) {
|
||||
a[i] = 1;
|
||||
b[i] = 4;
|
||||
c[i] = 1;
|
||||
rx[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x;
|
||||
ry[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y;
|
||||
}
|
||||
|
||||
// Right segment
|
||||
a[n_1] = 2;
|
||||
b[n_1] = 7;
|
||||
c[n_1] = 0;
|
||||
rx[n_1] = 8 * knots[n_1]._x + knots[n]._x;
|
||||
ry[n_1] = 8 * knots[n_1]._y + knots[n]._y;
|
||||
|
||||
// Solve Ax = b with the Thomas algorithm (from Wikipedia)
|
||||
for (var i = 1, j = 0; i < n; i++, j++) {
|
||||
var m = a[i] / b[j];
|
||||
b[i] = b[i] - m * c[j];
|
||||
rx[i] = rx[i] - m * rx[j];
|
||||
ry[i] = ry[i] - m * ry[j];
|
||||
}
|
||||
|
||||
var px = [],
|
||||
py = [];
|
||||
px[n_1] = rx[n_1] / b[n_1];
|
||||
py[n_1] = ry[n_1] / b[n_1];
|
||||
for (var i = n - 2; i >= 0; i--) {
|
||||
px[i] = (rx[i] - c[i] * px[i + 1]) / b[i];
|
||||
py[i] = (ry[i] - c[i] * py[i + 1]) / b[i];
|
||||
}
|
||||
px[n] = (3 * knots[n]._x - px[n_1]) / 2;
|
||||
py[n] = (3 * knots[n]._y - py[n_1]) / 2;
|
||||
|
||||
// Now update the segments
|
||||
for (var i = paddingLeft, max = n - paddingRight, j = from;
|
||||
i <= max; i++, j++) {
|
||||
var segment = segments[j < 0 ? j + length : j],
|
||||
pt = segment._point,
|
||||
hx = px[i] - pt._x,
|
||||
hy = py[i] - pt._y;
|
||||
if (loop || i < max)
|
||||
segment.setHandleOut(hx, hy);
|
||||
if (loop || i > paddingLeft)
|
||||
segment.setHandleIn(-hx, -hy);
|
||||
}
|
||||
} else {
|
||||
// All other smoothing methods are handled directly on the segments:
|
||||
for (var i = from; i <= to; i++) {
|
||||
segments[i < 0 ? i + length : i].smooth(opts,
|
||||
i === from, i === to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
new function() { // Scope for drawing
|
||||
|
||||
// Note that in the code below we're often accessing _x and _y on point
|
||||
// objects that were read from segments. This is because the SegmentPoint
|
||||
// class overrides the plain x / y properties with getter / setters and
|
||||
|
@ -2250,132 +2388,6 @@ new function() { // Scope for drawing
|
|||
}
|
||||
};
|
||||
},
|
||||
new function() { // Path Smoothing
|
||||
/**
|
||||
* Solves a tri-diagonal system for one of coordinates (x or y) of first
|
||||
* bezier control points.
|
||||
*
|
||||
* @param rhs right hand side vector
|
||||
* @return Solution vector
|
||||
*/
|
||||
function getFirstControlPoints(rhs) {
|
||||
var n = rhs.length,
|
||||
x = [], // Solution vector.
|
||||
tmp = [], // Temporary workspace.
|
||||
b = 2;
|
||||
x[0] = rhs[0] / b;
|
||||
// Decomposition and forward substitution.
|
||||
for (var i = 1; i < n; i++) {
|
||||
tmp[i] = 1 / b;
|
||||
b = (i < n - 1 ? 4 : 2) - tmp[i];
|
||||
x[i] = (rhs[i] - x[i - 1]) / b;
|
||||
}
|
||||
// Back-substitution.
|
||||
for (var i = 1; i < n; i++) {
|
||||
x[n - i - 1] -= tmp[n - i] * x[n - i];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
return {
|
||||
// NOTE: Documentation for smooth() is in PathItem
|
||||
smooth: function() {
|
||||
// This code is based on the work by Oleg V. Polikarpotchkin,
|
||||
// http://ov-p.spaces.live.com/blog/cns!39D56F0C7A08D703!147.entry
|
||||
// It was extended to support closed paths by averaging overlapping
|
||||
// beginnings and ends. The result of this approach is very close to
|
||||
// Polikarpotchkin's closed curve solution, but reuses the same
|
||||
// algorithm as for open paths, and is probably executing faster as
|
||||
// well, so it is preferred.
|
||||
var segments = this._segments,
|
||||
size = segments.length,
|
||||
closed = this._closed,
|
||||
n = size,
|
||||
// Add overlapping ends for averaging handles in closed paths
|
||||
overlap = 0;
|
||||
if (size <= 2)
|
||||
return;
|
||||
if (closed) {
|
||||
// Overlap up to 4 points since averaging beziers affect the 4
|
||||
// neighboring points
|
||||
overlap = Math.min(size, 4);
|
||||
n += Math.min(size, overlap) * 2;
|
||||
}
|
||||
var knots = [];
|
||||
for (var i = 0; i < size; i++)
|
||||
knots[i + overlap] = segments[i]._point;
|
||||
if (closed) {
|
||||
// If we're averaging, add the 4 last points again at the
|
||||
// beginning, and the 4 first ones at the end.
|
||||
for (var i = 0; i < overlap; i++) {
|
||||
knots[i] = segments[i + size - overlap]._point;
|
||||
knots[i + size + overlap] = segments[i]._point;
|
||||
}
|
||||
} else {
|
||||
n--;
|
||||
}
|
||||
// Calculate first Bezier control points
|
||||
// Right hand side vector
|
||||
var rhs = [];
|
||||
|
||||
// Set right hand side X values
|
||||
for (var i = 1; i < n - 1; i++)
|
||||
rhs[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x;
|
||||
rhs[0] = knots[0]._x + 2 * knots[1]._x;
|
||||
rhs[n - 1] = 3 * knots[n - 1]._x;
|
||||
// Get first control points X-values
|
||||
var x = getFirstControlPoints(rhs);
|
||||
|
||||
// Set right hand side Y values
|
||||
for (var i = 1; i < n - 1; i++)
|
||||
rhs[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y;
|
||||
rhs[0] = knots[0]._y + 2 * knots[1]._y;
|
||||
rhs[n - 1] = 3 * knots[n - 1]._y;
|
||||
// Get first control points Y-values
|
||||
var y = getFirstControlPoints(rhs);
|
||||
|
||||
if (closed) {
|
||||
// Do the actual averaging simply by linearly fading between the
|
||||
// overlapping values.
|
||||
for (var i = 0, j = size; i < overlap; i++, j++) {
|
||||
var f1 = i / overlap,
|
||||
f2 = 1 - f1,
|
||||
ie = i + overlap,
|
||||
je = j + overlap;
|
||||
// Beginning
|
||||
x[j] = x[i] * f1 + x[j] * f2;
|
||||
y[j] = y[i] * f1 + y[j] * f2;
|
||||
// End
|
||||
x[je] = x[ie] * f2 + x[je] * f1;
|
||||
y[je] = y[ie] * f2 + y[je] * f1;
|
||||
}
|
||||
n--;
|
||||
}
|
||||
var handleIn = null;
|
||||
// Now set the calculated handles
|
||||
for (var i = overlap; i <= n - overlap; i++) {
|
||||
var segment = segments[i - overlap];
|
||||
if (handleIn)
|
||||
segment.setHandleIn(handleIn.subtract(segment._point));
|
||||
if (i < n) {
|
||||
segment.setHandleOut(
|
||||
new Point(x[i], y[i]).subtract(segment._point));
|
||||
handleIn = i < n - 1
|
||||
? new Point(
|
||||
2 * knots[i + 1]._x - x[i + 1],
|
||||
2 * knots[i + 1]._y - y[i + 1])
|
||||
: new Point(
|
||||
(knots[n]._x + x[n - 1]) / 2,
|
||||
(knots[n]._y + y[n - 1]) / 2);
|
||||
}
|
||||
}
|
||||
if (closed && handleIn) {
|
||||
var segment = this._segments[0];
|
||||
segment.setHandleIn(handleIn.subtract(segment._point));
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
new function() { // PostScript-style drawing commands
|
||||
/**
|
||||
* Helper method that returns the current segment and checks if a moveTo()
|
||||
|
|
|
@ -317,12 +317,24 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
}
|
||||
|
||||
/**
|
||||
* Smooth bezier curves without changing the amount of segments or their
|
||||
* points, by only smoothing and adjusting their handle points, for both
|
||||
* open ended and closed paths.
|
||||
* TODO: continuous:
|
||||
* Smooths the path item by adjusting its curve handles so that the first
|
||||
* and second derivatives of all involved curves are continuous across their
|
||||
* boundaries.
|
||||
*/
|
||||
/**
|
||||
* Smooths the path item without changing the amount of segments in the path
|
||||
* or moving their locations, by only smoothing and adjusting the angle and
|
||||
* length of their handles.
|
||||
* This works for open paths as well as closed paths.
|
||||
*
|
||||
* @name PathItem#smooth
|
||||
* @function
|
||||
* @param {Object} [options] TODO
|
||||
* TODO: controls the amount of smoothing as a factor by which to scale each
|
||||
* handle.
|
||||
*
|
||||
* @see Segment#smooth(options)
|
||||
*
|
||||
* @example {@paperscript}
|
||||
* // Smoothing a closed shape:
|
||||
|
|
|
@ -380,6 +380,81 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
|| this._path._closed && segments[0]) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Smooths the bezier curves that pass through this segment without moving
|
||||
* its point, by taking into its distance to the neighboring segments and
|
||||
* changing the direction and length of the segment's handles accordingly.
|
||||
*
|
||||
* @param {Object} [options] TODO
|
||||
* TODO: controls the amount of smoothing as a factor by which to scale each
|
||||
* handle.
|
||||
*
|
||||
* @see PathItem#smooth(options)
|
||||
*/
|
||||
smooth: function(options, _first, _last) {
|
||||
// _first = _last = false;
|
||||
var opts = options || {},
|
||||
type = opts.type,
|
||||
factor = opts.factor,
|
||||
prev = this.getPrevious(),
|
||||
next = this.getNext(),
|
||||
// Some precalculations valid for both 'catmull-rom' and 'geometric'
|
||||
p0 = (prev || this)._point,
|
||||
p1 = this._point,
|
||||
p2 = (next || this)._point,
|
||||
d1 = p0.getDistance(p1),
|
||||
d2 = p1.getDistance(p2);
|
||||
if (!type || type === 'catmull-rom') {
|
||||
// Implementation of by Catmull-Rom splines with factor parameter
|
||||
// based on work by @nicholaswmin:
|
||||
// https://github.com/nicholaswmin/VectorTests
|
||||
// Using these factors produces different types of splines:
|
||||
// 0.0: the standard, uniform Catmull-Rom spline
|
||||
// 0.5: the centripetal Catmull-Rom spline, guaranteeing no self-
|
||||
// intersections
|
||||
// 1.0: the chordal Catmull-Rom spline
|
||||
var a = factor === undefined ? 0.5 : factor,
|
||||
d1_a = Math.pow(d1, a),
|
||||
d1_2a = d1_a * d1_a,
|
||||
d2_a = Math.pow(d2, a),
|
||||
d2_2a = d2_a * d2_a;
|
||||
if (!_first && prev) {
|
||||
var A = 2 * d2_2a + 3 * d2_a * d1_a + d1_2a,
|
||||
N = 3 * d2_a * (d2_a + d1_a);
|
||||
this.setHandleIn(N !== 0
|
||||
? new Point(
|
||||
(d2_2a * p0._x + A * p1._x - d1_2a * p2._x) / N - p1._x,
|
||||
(d2_2a * p0._y + A * p1._y - d1_2a * p2._y) / N - p1._y)
|
||||
: new Point());
|
||||
}
|
||||
if (!_last && next) {
|
||||
var A = 2 * d1_2a + 3 * d1_a * d2_a + d2_2a,
|
||||
N = 3 * d1_a * (d1_a + d2_a);
|
||||
this.setHandleOut(N !== 0
|
||||
? new Point(
|
||||
(d1_2a * p2._x + A * p1._x - d2_2a * p0._x) / N - p1._x,
|
||||
(d1_2a * p2._y + A * p1._y - d2_2a * p0._y) / N - p1._y)
|
||||
: new Point());
|
||||
}
|
||||
} else if (type === 'geometric') {
|
||||
// Geometric smoothing approach based on:
|
||||
// http://www.antigrain.com/research/bezier_interpolation/
|
||||
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
|
||||
// http://bseth99.github.io/projects/animate/2-bezier-curves.html
|
||||
if (prev && next) {
|
||||
var vector = p0.subtract(p2),
|
||||
t = factor === undefined ? 0.4 : factor,
|
||||
k = t * d1 / (d1 + d2);
|
||||
if (!_first)
|
||||
this.setHandleIn(vector.multiply(k));
|
||||
if (!_last)
|
||||
this.setHandleOut(vector.multiply(k - t));
|
||||
}
|
||||
} else {
|
||||
throw new Error('Smoothing method \'' + type + '\' not supported.');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The previous segment in the {@link Path#segments} array that the
|
||||
* segment belongs to. If the segments belongs to a closed path, the last
|
||||
|
|
|
@ -63,7 +63,7 @@ var Numerical = new function() {
|
|||
EPSILON = 1e-12,
|
||||
MACHINE_EPSILON = 1.12e-16;
|
||||
|
||||
function clip(value, min, max) {
|
||||
function clamp(value, min, max) {
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
|
@ -133,6 +133,16 @@ var Numerical = new function() {
|
|||
return val >= -EPSILON && val <= EPSILON;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a number whose value is clamped by the given range.
|
||||
*
|
||||
* @param {Number} value the value to be clamped
|
||||
* @param {Number} min the lower boundary of the range
|
||||
* @param {Number} max the upper boundary of the range
|
||||
* @return {Number} a number in the range of [min, max]
|
||||
*/
|
||||
clamp: clamp,
|
||||
|
||||
/**
|
||||
* Gauss-Legendre Numerical Integration.
|
||||
*/
|
||||
|
@ -254,10 +264,10 @@ var Numerical = new function() {
|
|||
// We need to include EPSILON in the comparisons with min / max,
|
||||
// as some solutions are ever so lightly out of bounds.
|
||||
if (isFinite(x1) && (min == null || x1 > eMin && x1 < eMax))
|
||||
roots[count++] = min == null ? x1 : clip(x1, min, max);
|
||||
roots[count++] = min == null ? x1 : clamp(x1, min, max);
|
||||
if (x2 !== x1
|
||||
&& isFinite(x2) && (min == null || x2 > eMin && x2 < eMax))
|
||||
roots[count++] = min == null ? x2 : clip(x2, min, max);
|
||||
roots[count++] = min == null ? x2 : clamp(x2, min, max);
|
||||
return count;
|
||||
},
|
||||
|
||||
|
@ -349,7 +359,7 @@ var Numerical = new function() {
|
|||
var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max);
|
||||
if (isFinite(x) && (count === 0 || x !== roots[count - 1])
|
||||
&& (min == null || x > min - EPSILON && x < max + EPSILON))
|
||||
roots[count++] = min == null ? x : clip(x, min, max);
|
||||
roots[count++] = min == null ? x : clamp(x, min, max);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue