mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-20 22:39:50 -05:00
Convert CurveFlattener to PathFlattener, which can handle drawing of parts accross curve boundaries.
This commit is contained in:
parent
e097ff1303
commit
fafb6d1d76
6 changed files with 165 additions and 141 deletions
|
@ -50,12 +50,12 @@ var sources = [
|
|||
'src/path/SegmentPoint.js',
|
||||
'src/path/SelectionState.js',
|
||||
'src/path/Curve.js',
|
||||
'src/path/CurveFlattener.js',
|
||||
'src/path/CurveLocation.js',
|
||||
'src/path/PathItem.js',
|
||||
'src/path/Path.js',
|
||||
'src/path/CompoundPath.js',
|
||||
'src/path/Path.Constructors.js',
|
||||
'src/path/PathFlattener.js',
|
||||
|
||||
'src/text/ParagraphStyle.js',
|
||||
'src/text/CharacterStyle.js',
|
||||
|
|
|
@ -67,12 +67,12 @@ var paper = new function() {
|
|||
//#include "path/SegmentPoint.js"
|
||||
//#include "path/SelectionState.js"
|
||||
//#include "path/Curve.js"
|
||||
//#include "path/CurveFlattener.js"
|
||||
//#include "path/CurveLocation.js"
|
||||
//#include "path/PathItem.js"
|
||||
//#include "path/Path.js"
|
||||
//#include "path/CompoundPath.js"
|
||||
//#include "path/Path.Constructors.js"
|
||||
//#include "path/PathFlattener.js"
|
||||
|
||||
//#include "text/ParagraphStyle.js"
|
||||
//#include "text/CharacterStyle.js"
|
||||
|
|
|
@ -195,16 +195,7 @@ var Curve = this.Curve = Base.extend({
|
|||
},
|
||||
|
||||
getCurveValues: function() {
|
||||
var p1 = this._segment1._point,
|
||||
h1 = this._segment1._handleOut,
|
||||
h2 = this._segment2._handleIn,
|
||||
p2 = this._segment2._point;
|
||||
return [
|
||||
p1.x, p1.y,
|
||||
p1.x + h1.x, p1.y + h1.y,
|
||||
p2.x + h2.x, p2.y + h2.y,
|
||||
p2.x, p2.y
|
||||
];
|
||||
return Curve.getCurveValues(this._segment1, this._segment2);
|
||||
},
|
||||
|
||||
// DOCS: document Curve#getLength(from, to)
|
||||
|
@ -445,6 +436,19 @@ var Curve = this.Curve = Base.extend({
|
|||
},
|
||||
|
||||
statics: {
|
||||
getCurveValues: function(segment1, segment2) {
|
||||
var p1 = segment1._point,
|
||||
h1 = segment1._handleOut,
|
||||
h2 = segment2._handleIn,
|
||||
p2 = segment2._point;
|
||||
return [
|
||||
p1.x, p1.y,
|
||||
p1.x + h1.x, p1.y + h1.y,
|
||||
p2.x + h2.x, p2.y + h2.y,
|
||||
p2.x, p2.y
|
||||
];
|
||||
},
|
||||
|
||||
getLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, a, b) {
|
||||
if (a === undefined)
|
||||
a = 0;
|
||||
|
@ -529,6 +533,25 @@ var Curve = this.Curve = Base.extend({
|
|||
];
|
||||
},
|
||||
|
||||
// TODO: Find better name
|
||||
getPart: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, from, to) {
|
||||
var curve = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
|
||||
if (from > 0) {
|
||||
// 8th argument of Curve.subdivide() == t, and values can be
|
||||
// directly used as arguments list for apply().
|
||||
curve[8] = from;
|
||||
curve = Curve.subdivide.apply(Curve, curve)[1]; // right
|
||||
}
|
||||
if (to < 1) {
|
||||
// Se above about curve[8].
|
||||
// Interpolate the parameter at 'to' in the new curve and
|
||||
// cut there
|
||||
curve[8] = (to - from) / (1 - from);
|
||||
curve = Curve.subdivide.apply(Curve, curve)[0]; // left
|
||||
}
|
||||
return curve;
|
||||
},
|
||||
|
||||
isSufficientlyFlat: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
|
||||
// Inspired by Skia, but to be tested:
|
||||
// Calculate 1/3 (m1) and 2/3 (m2) along the line between start
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Paper.js
|
||||
*
|
||||
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
||||
* based on Scriptographer.org and designed to be largely API compatible.
|
||||
* http://paperjs.org/
|
||||
* http://scriptographer.org/
|
||||
*
|
||||
* Distributed under the MIT license. See LICENSE file for details.
|
||||
*
|
||||
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
||||
* http://lehni.org/ & http://jonathanpuckey.com/
|
||||
*
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
var CurveFlattener = Base.extend({
|
||||
initialize: function(curve) {
|
||||
this.parts = [];
|
||||
this.length = 0;
|
||||
// Keep a current index from where we last where in getParameter(), to
|
||||
// optimise for iterator-like usage of the flattener.
|
||||
this.index = 0;
|
||||
this.curve = curve.getCurveValues();
|
||||
this._computeParts(this.curve, 0, 1);
|
||||
},
|
||||
|
||||
_computeParts: function(values, minT, maxT) {
|
||||
// Check if the t-span is big enough for subdivision.
|
||||
// We're not subdividing more than 32 times...
|
||||
if ((maxT - minT) > 1 / 32
|
||||
&& !Curve.isSufficientlyFlat.apply(Curve, values)) {
|
||||
var curves = Curve.subdivide.apply(Curve, values);
|
||||
var halfT = (minT + maxT) / 2;
|
||||
// Recursively subdive and compute parts again.
|
||||
this._computeParts(curves[0], minT, halfT);
|
||||
this._computeParts(curves[1], halfT, maxT);
|
||||
} else {
|
||||
// Calculate distance between p1 and p2
|
||||
var x = values[6] - values[0],
|
||||
y = values[7] - values[1],
|
||||
dist = Math.sqrt(x * x + y * y);
|
||||
if (dist > Numerical.TOLERANCE) {
|
||||
this.length += dist;
|
||||
this.parts.push({
|
||||
length: this.length,
|
||||
value: maxT
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getParameter: function(length) {
|
||||
// Make sure we're not beyond the requested length already. Search the
|
||||
// start position backwards from where to then process the loop below.
|
||||
var i, j = this.index;
|
||||
for (;;) {
|
||||
i = j;
|
||||
if (j == 0 || this.parts[--j].length < length)
|
||||
break;
|
||||
}
|
||||
// Find the segment that succeeds the given length, then interpolate
|
||||
// with the previous segment
|
||||
for (var l = this.parts.length; i < l; i++) {
|
||||
var segment = this.parts[i];
|
||||
if (segment.length >= length) {
|
||||
this.index = i;
|
||||
var prev = this.parts[i - 1],
|
||||
prevValue = prev ? prev.value : 0,
|
||||
prevLength = prev ? prev.length : 0;
|
||||
// Interpolate
|
||||
return prevValue + (segment.value - prevValue)
|
||||
* (length - prevLength) / (segment.length - prevLength);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
|
||||
drawDash: function(ctx, from, to, moveTo) {
|
||||
from = this.getParameter(from);
|
||||
to = this.getParameter(to);
|
||||
var curve = this.curve;
|
||||
if (from > 0) {
|
||||
// 8th argument of Curve.subdivide() == t, and values can be
|
||||
// directly used as arguments list for apply().
|
||||
curve[8] = from; // See above
|
||||
curve = Curve.subdivide.apply(Curve, curve)[1]; // right
|
||||
}
|
||||
if (moveTo) {
|
||||
ctx.moveTo(curve[0], curve[1]);
|
||||
}
|
||||
if (to < 1) {
|
||||
// Se above about curve[8].
|
||||
// Interpolate the parameter at 'to' in the new curve and cut there
|
||||
curve[8] = (to - from) / (1 - from);
|
||||
curve = Curve.subdivide.apply(Curve, curve)[0]; // left
|
||||
}
|
||||
ctx.bezierCurveTo.apply(ctx, curve.slice(2));
|
||||
}
|
||||
});
|
|
@ -760,34 +760,14 @@ var Path = this.Path = PathItem.extend({
|
|||
drawSegment(0);
|
||||
}
|
||||
|
||||
function drawDashes(ctx, curves, dashArray, dashOffset) {
|
||||
var length = 0,
|
||||
function drawDashes(ctx, path, dashArray, dashOffset) {
|
||||
var flattener = new PathFlattener(path),
|
||||
from = dashOffset, to,
|
||||
open = false;
|
||||
for (var i = 0, j = 0, l = curves.length; i < l; i++) {
|
||||
var curve = curves[i];
|
||||
var flattener = new CurveFlattener(curve);
|
||||
length = flattener.length;
|
||||
while (true) {
|
||||
if (open) {
|
||||
flattener.drawDash(ctx, from, to, false);
|
||||
from = to + dashArray[(j++) % dashArray.length];
|
||||
open = false;
|
||||
}
|
||||
to = from + dashArray[(j++) % dashArray.length];
|
||||
flattener.drawDash(ctx, from, to, true);
|
||||
if (to > length) {
|
||||
from = 0;
|
||||
to -= length;
|
||||
open = true;
|
||||
break;
|
||||
}
|
||||
from = to + dashArray[(j++) % dashArray.length];
|
||||
if (from >= length) {
|
||||
from -= length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i = 0;
|
||||
while (from < flattener.length) {
|
||||
to = from + dashArray[(i++) % dashArray.length];
|
||||
flattener.drawPart(ctx, from, to, true);
|
||||
from = to + dashArray[(i++) % dashArray.length];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -834,8 +814,7 @@ var Path = this.Path = PathItem.extend({
|
|||
// We cannot use the path created by drawSegments above
|
||||
// Use CurveFlatteners to draw dashed paths:
|
||||
ctx.beginPath();
|
||||
drawDashes(ctx, this.getCurves(), dashArray,
|
||||
this.getDashOffset());
|
||||
drawDashes(ctx, this, dashArray, this.getDashOffset());
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
|
122
src/path/PathFlattener.js
Normal file
122
src/path/PathFlattener.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Paper.js
|
||||
*
|
||||
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
||||
* based on Scriptographer.org and designed to be largely API compatible.
|
||||
* http://paperjs.org/
|
||||
* http://scriptographer.org/
|
||||
*
|
||||
* Distributed under the MIT license. See LICENSE file for details.
|
||||
*
|
||||
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
||||
* http://lehni.org/ & http://jonathanpuckey.com/
|
||||
*
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
var PathFlattener = Base.extend({
|
||||
initialize: function(path) {
|
||||
this.parts = [];
|
||||
this.curves = [];
|
||||
this.length = 0;
|
||||
// Keep a current index from the part where we last where in
|
||||
// getParameter(), to optimise for iterator-like usage of the flattener.
|
||||
this.index = 0;
|
||||
|
||||
var segments = path._segments,
|
||||
segment1 = segments[0],
|
||||
segment2,
|
||||
that = this;
|
||||
|
||||
function addCurve(segment1, segment2) {
|
||||
var curve = Curve.getCurveValues(segment1, segment2);
|
||||
that.curves.push(curve);
|
||||
that._computeParts(curve, segment1._index, 0, 1);
|
||||
}
|
||||
|
||||
for (var i = 1, l = segments.length; i < l; i++) {
|
||||
segment2 = segments[i];
|
||||
addCurve(segment1, segment2);
|
||||
segment1 = segment2;
|
||||
}
|
||||
if (path._closed)
|
||||
addCurve(segment2, segments[0]);
|
||||
},
|
||||
|
||||
_computeParts: function(curve, index, minT, maxT) {
|
||||
// Check if the t-span is big enough for subdivision.
|
||||
// We're not subdividing more than 32 times...
|
||||
if ((maxT - minT) > 1 / 32
|
||||
&& !Curve.isSufficientlyFlat.apply(Curve, curve)) {
|
||||
var curves = Curve.subdivide.apply(Curve, curve);
|
||||
var halfT = (minT + maxT) / 2;
|
||||
// Recursively subdive and compute parts again.
|
||||
this._computeParts(curves[0], index, minT, halfT);
|
||||
this._computeParts(curves[1], index, halfT, maxT);
|
||||
} else {
|
||||
// Calculate distance between p1 and p2
|
||||
var x = curve[6] - curve[0],
|
||||
y = curve[7] - curve[1],
|
||||
dist = Math.sqrt(x * x + y * y);
|
||||
if (dist > Numerical.TOLERANCE) {
|
||||
this.length += dist;
|
||||
this.parts.push({
|
||||
length: this.length,
|
||||
value: maxT,
|
||||
index: index
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getParameter: function(length) {
|
||||
// Make sure we're not beyond the requested length already. Search the
|
||||
// start position backwards from where to then process the loop below.
|
||||
var i, j = this.index;
|
||||
for (;;) {
|
||||
i = j;
|
||||
if (j == 0 || this.parts[--j].length < length)
|
||||
break;
|
||||
}
|
||||
// Find the part that succeeds the given length, then interpolate
|
||||
// with the previous part
|
||||
for (var l = this.parts.length; i < l; i++) {
|
||||
var part = this.parts[i];
|
||||
if (part.length >= length) {
|
||||
this.index = i;
|
||||
var prev = this.parts[i - 1];
|
||||
// Make sure we only use the previous parameter value if its
|
||||
// for the same curve, by checking index. Use 0 otherwise.
|
||||
var prevVal = prev && prev.index == part.index ? prev.value : 0,
|
||||
prevLen = prev ? prev.length : 0;
|
||||
return {
|
||||
// Interpolate
|
||||
value: prevVal + (part.value - prevVal)
|
||||
* (length - prevLen) / (part.length - prevLen),
|
||||
index: part.index
|
||||
};
|
||||
}
|
||||
}
|
||||
// Return last one
|
||||
var part = this.parts[this.parts.length - 1];
|
||||
return {
|
||||
value: 1,
|
||||
index: part.index
|
||||
};
|
||||
},
|
||||
|
||||
drawPart: function(ctx, from, to, moveTo) {
|
||||
from = this.getParameter(from);
|
||||
to = this.getParameter(to);
|
||||
for (var i = from.index; i <= to.index; i++) {
|
||||
var curve = Curve.getPart.apply(Curve, this.curves[i].concat(
|
||||
i == from.index ? from.value : 0,
|
||||
i == to.index ? to.value : 1));
|
||||
if (moveTo) {
|
||||
ctx.moveTo(curve[0], curve[1]);
|
||||
moveTo = false;
|
||||
}
|
||||
ctx.bezierCurveTo.apply(ctx, curve.slice(2));
|
||||
}
|
||||
}
|
||||
});
|
Loading…
Reference in a new issue