Skip to content

Commit a22b1c9

Browse files
committed
add curve interpolation of animateShow of line with smoothness
1 parent c274bef commit a22b1c9

File tree

4 files changed

+105
-68
lines changed

4 files changed

+105
-68
lines changed

src/core/Canvas.js

Lines changed: 62 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { isGradient } from './util/style';
1313
import { createEl } from './util/dom';
1414
import Browser from './Browser';
15+
import Point from '../geo/Point';
1516
import { getFont, getAlignPoint } from './util/strings';
1617

1718
const DEFAULT_STROKE_COLOR = '#000';
@@ -534,65 +535,51 @@ const Canvas = {
534535
}
535536
},
536537

537-
// paintSmoothLine(ctx, points, lineOpacity, smoothValue, close) {
538-
// if (!points || points.length <= 2) {
539-
// return;
540-
// }
541-
// function getQuadControlPoint(x0, y0, x1, y1, smoothValue) {
542-
// const dist = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
543-
// const perpDist = dist * smoothValue;
544-
// const midx = (x0 + x1) / 2, midy = (y0 + y1) / 2;
545-
// const degree = Math.PI / 2 - computeDegree(x0, y0, x1, y1);
546-
// const dx = Math.cos(degree) * perpDist, dy = Math.sin(degree) * perpDist;
547-
// return [midx - dx, midy + dy];
548-
// }
549-
// ctx.beginPath();
550-
// ctx.moveTo(points[0].x, points[0].y);
551-
// if (points.length <= 2 || !smoothValue) {
552-
// Canvas._path(ctx, points);
553-
// return;
554-
// }
555-
// const l = close ? points.length + 1 : points.length;
556-
// let prevCtrlPoint;
557-
// for (let i = 1; i < l; i++) {
558-
// const prevPoint = points[i - 1];
559-
// const currentPoint = i === points.length ? points[0] : points[i];
560-
// let ctrlPoint;
561-
// if (i === 1) {
562-
// // the first control point
563-
// ctrlPoint = getQuadControlPoint(prevPoint.x, prevPoint.y, points[i].x, points[i].y, smoothValue);
564-
// } else {
565-
// // the following control point
566-
// const x = 2 * prevPoint.x - prevCtrlPoint[0], y = 2 * prevPoint.y - prevCtrlPoint[1];
567-
// ctrlPoint = [x, y];
568-
// }
569-
// if (i < points.length) {
570-
// ctx.quadraticCurveTo(ctrlPoint[0], ctrlPoint[1], currentPoint.x, currentPoint.y);
571-
// prevPoint.nextCtrlPoint = ctrlPoint;
572-
// currentPoint.prevCtrlPoint = ctrlPoint;
573-
// prevCtrlPoint = ctrlPoint;
574-
// } else {
575-
// //the closing curve, draw a bezierCurve
576-
// //the second control point, the opposite one of the first vertex's next control point
577-
// const x1 = 2 * currentPoint.x - currentPoint.nextCtrlPoint[0], y1 = 2 * currentPoint.y - currentPoint.nextCtrlPoint[1];
578-
// ctx.bezierCurveTo(ctrlPoint[0], ctrlPoint[1], x1, y1, currentPoint.x, currentPoint.y);
579-
// }
580-
581-
// }
582-
// // points[points.length - 1].prevCtrlPoint = prevCtrlPoint;
583-
// Canvas._stroke(ctx, lineOpacity);
584-
// },
585-
586-
paintSmoothLine(ctx, points, lineOpacity, smoothValue, close) {
538+
paintSmoothLine(ctx, points, lineOpacity, smoothValue, close, tailIdx, tailRatio) {
587539
if (!points) {
588540
return;
589541
}
590542
if (points.length <= 2 || !smoothValue) {
591543
Canvas.path(ctx, points, lineOpacity);
592544
return;
593545
}
546+
547+
//推算 cubic 贝塞尔曲线片段的起终点和控制点坐标
548+
//t0: 片段起始比例 0-1
549+
//t1: 片段结束比例 0-1
550+
//x1, y1, 曲线起点
551+
//bx1, by1, bx2, by2,曲线控制点
552+
//x2, y2 曲线终点
553+
//结果是曲线片段的起点,2个控制点坐标和终点坐标
554+
function interpolate(t0, t1, x1, y1, bx1, by1, bx2, by2, x2, y2) {
555+
const u0 = 1.0 - t0;
556+
const u1 = 1.0 - t1;
557+
558+
const qxa = x1 * u0 * u0 + bx1 * 2 * t0 * u0 + bx2 * t0 * t0;
559+
const qxb = x1 * u1 * u1 + bx1 * 2 * t1 * u1 + bx2 * t1 * t1;
560+
const qxc = bx1 * u0 * u0 + bx2 * 2 * t0 * u0 + x2 * t0 * t0;
561+
const qxd = bx1 * u1 * u1 + bx2 * 2 * t1 * u1 + x2 * t1 * t1;
562+
563+
const qya = y1 * u0 * u0 + by1 * 2 * t0 * u0 + by2 * t0 * t0;
564+
const qyb = y1 * u1 * u1 + by1 * 2 * t1 * u1 + by2 * t1 * t1;
565+
const qyc = by1 * u0 * u0 + by2 * 2 * t0 * u0 + y2 * t0 * t0;
566+
const qyd = by1 * u1 * u1 + by2 * 2 * t1 * u1 + y2 * t1 * t1;
567+
568+
// const xa = qxa * u0 + qxc * t0;
569+
const xb = qxa * u1 + qxc * t1;
570+
const xc = qxb * u0 + qxd * t0;
571+
const xd = qxb * u1 + qxd * t1;
572+
573+
// const ya = qya * u0 + qyc * t0;
574+
const yb = qya * u1 + qyc * t1;
575+
const yc = qyb * u0 + qyd * t0;
576+
const yd = qyb * u1 + qyd * t1;
577+
578+
return [xb, yb, xc, yc, xd, yd];
579+
}
580+
594581
//from http://www.antigrain.com/research/bezier_interpolation/
595-
function getCubicControlPoints(x0, y0, x1, y1, x2, y2, x3, y3, smoothValue) {
582+
function getCubicControlPoints(x0, y0, x1, y1, x2, y2, x3, y3, smoothValue, t) {
596583
// Assume we need to calculate the control
597584
// points between (x1,y1) and (x2,y2).
598585
// Then x0,y0 - the previous vertex,
@@ -620,13 +607,19 @@ const Canvas = {
620607
ctrl2X = xm2 + (xc2 - xm2) * smoothValue + x2 - xm2,
621608
ctrl2Y = ym2 + (yc2 - ym2) * smoothValue + y2 - ym2;
622609

623-
return [ctrl1X, ctrl1Y, ctrl2X, ctrl2Y];
610+
const ctrlPoints = [ctrl1X, ctrl1Y, ctrl2X, ctrl2Y];
611+
if (t < 1) {
612+
return interpolate(0, t, x1, y1, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, x2, y2);
613+
} else {
614+
return ctrlPoints;
615+
}
624616
}
625-
const count = points.length;
626-
const l = close ? count : count - 1;
617+
let count = points.length;
618+
let l = close ? count : count - 1;
627619

628620
ctx.beginPath();
629621
ctx.moveTo(points[0].x, points[0].y);
622+
if (tailRatio !== undefined) l -= Math.max(l - tailIdx - 1, 0);
630623
let preCtrlPoints;
631624
for (let i = 0; i < l; i++) {
632625
const x1 = points[i].x, y1 = points[i].y;
@@ -662,17 +655,28 @@ const Canvas = {
662655
y3 = points[i + 2 - count].y;
663656
}
664657

665-
const ctrlPoints = getCubicControlPoints(x0, y0, x1, y1, x2, y2, x3, y3, smoothValue);
666-
ctx.bezierCurveTo(ctrlPoints[0], ctrlPoints[1], ctrlPoints[2], ctrlPoints[3], x2, y2);
658+
const ctrlPoints = getCubicControlPoints(x0, y0, x1, y1, x2, y2, x3, y3, smoothValue, i === l - 1 ? tailRatio : 1);
659+
if (i === l - 1 && tailRatio >= 0 && tailRatio < 1) {
660+
ctx.bezierCurveTo(ctrlPoints[0], ctrlPoints[1], ctrlPoints[2], ctrlPoints[3], ctrlPoints[4], ctrlPoints[5]);
661+
points.splice(l - 1, count - (l - 1) - 1);
662+
const lastPoint = new Point(ctrlPoints[4], ctrlPoints[5]);
663+
lastPoint.prevCtrlPoint = new Point(ctrlPoints[2], ctrlPoints[3]);
664+
points.push(lastPoint);
665+
count = points.length;
666+
} else {
667+
ctx.bezierCurveTo(ctrlPoints[0], ctrlPoints[1], ctrlPoints[2], ctrlPoints[3], x2, y2);
668+
}
667669
points[i].nextCtrlPoint = ctrlPoints.slice(0, 2);
668670
points[i].prevCtrlPoint = preCtrlPoints ? preCtrlPoints.slice(2) : null;
669671
preCtrlPoints = ctrlPoints;
670672
}
671-
if (!close) {
673+
if (!close && points[1].prevCtrlPoint) {
672674
points[0].nextCtrlPoint = points[1].prevCtrlPoint;
673675
delete points[0].prevCtrlPoint;
674676
}
675-
points[count - 1].prevCtrlPoint = points[count - 2].nextCtrlPoint;
677+
if (!points[count - 1].prevCtrlPoint) {
678+
points[count - 1].prevCtrlPoint = points[count - 2].nextCtrlPoint;
679+
}
676680
Canvas._stroke(ctx, lineOpacity);
677681
},
678682

src/geometry/Path.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class Path extends Geometry {
112112
delete this._aniShowCenter;
113113
delete this._animIdx;
114114
delete this._animLenSoFar;
115+
delete this._animTailRatio;
115116
this.setCoordinates(coordinates);
116117
}
117118
if (cb) {
@@ -147,18 +148,25 @@ class Path extends Geometry {
147148
p2 = coordinates[idx + 1],
148149
span = targetLength - this._animLenSoFar,
149150
r = span / segLen;
151+
this._animTailRatio = r;
150152
const x = p1.x + (p2.x - p1.x) * r,
151153
y = p1.y + (p2.y - p1.y) * r,
152154
targetCoord = new Coordinate(x, y);
153-
const animCoords = coordinates.slice(0, this._animIdx + 1);
154-
animCoords.push(targetCoord);
155155
const isPolygon = !!this.getShell;
156-
if (isPolygon) {
157-
this.setCoordinates([this._aniShowCenter].concat(animCoords));
158-
} else {
156+
if (!isPolygon && this.options['smoothness'] > 0) {
157+
//smooth line needs to set current coordinates plus 2 more to caculate correct control points
158+
const animCoords = coordinates.slice(0, this._animIdx + 3);
159159
this.setCoordinates(animCoords);
160+
} else {
161+
const animCoords = coordinates.slice(0, this._animIdx + 1);
162+
animCoords.push(targetCoord);
163+
if (isPolygon) {
164+
this.setCoordinates([this._aniShowCenter].concat(animCoords));
165+
} else {
166+
this.setCoordinates(animCoords);
167+
}
160168
}
161-
return animCoords[animCoords.length - 1];
169+
return targetCoord;
162170
}
163171

164172
_getCenterInExtent(extent, coordinates, clipFn) {

src/renderer/geometry/VectorRenderer.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,11 @@ LineString.include({
155155

156156
_paintOn(ctx, points, lineOpacity, fillOpacity, dasharray) {
157157
if (this.options['smoothness']) {
158-
Canvas.paintSmoothLine(ctx, points, lineOpacity, this.options['smoothness']);
159-
this._paintArrow(ctx, points, lineOpacity);
158+
Canvas.paintSmoothLine(ctx, points, lineOpacity, this.options['smoothness'], false, this._animIdx, this._animTailRatio);
160159
} else {
161160
Canvas.path(ctx, points, lineOpacity, null, dasharray);
162-
this._paintArrow(ctx, points, lineOpacity);
163161
}
162+
this._paintArrow(ctx, points, lineOpacity);
164163
},
165164

166165
_getArrowPlacement() {

test/geometry/LineStringSpec.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ describe('Geometry.LineString', function () {
383383
});
384384

385385
describe('animateShow', function () {
386-
it('animateShow', function (done) {
386+
it('#animateShow', function (done) {
387387
layer = new maptalks.VectorLayer('id2');
388388
var polyline = new maptalks.LineString([
389389
map.getCenter(),
@@ -410,6 +410,32 @@ describe('Geometry.LineString', function () {
410410
});
411411
layer.addGeometry(polyline).addTo(map);
412412
});
413+
it('#animateShow with smoothness', function (done) {
414+
layer = new maptalks.VectorLayer('id2');
415+
var polyline = new maptalks.LineString([
416+
map.getCenter(),
417+
map.getCenter().add(0.01, 0.01)
418+
], {
419+
'smoothness' : 0.1,
420+
'visible' : false
421+
});
422+
layer.once('layerload', function () {
423+
var geojson = polyline.toGeoJSON();
424+
expect(layer._getRenderer().isBlank()).to.be.ok();
425+
polyline.animateShow({
426+
'duration' : 100,
427+
'easing' : 'out'
428+
}, function (frame) {
429+
if (frame.state.playState === 'finished') {
430+
expect(layer).to.be.painted(0, 0);
431+
expect(polyline.toGeoJSON()).to.be.eql(geojson);
432+
done();
433+
}
434+
});
435+
436+
});
437+
layer.addGeometry(polyline).addTo(map);
438+
});
413439
it('#649 fix infinite loop if removed during animateShow', function (done) {
414440
layer = new maptalks.VectorLayer('id2', { drawImmediate : true });
415441
var polyline = new maptalks.LineString([

0 commit comments

Comments
 (0)