139 lines
5.2 KiB
JavaScript
139 lines
5.2 KiB
JavaScript
"use strict";
|
|
/*******************************************************************************
|
|
**************************** ES6 Smooth Line Class ****************************
|
|
*******************************************************************************
|
|
* v0.1
|
|
*******************************************************************************
|
|
* A smooth line class built upon my earlier bezier curve and vector classes.
|
|
*
|
|
* Given a number of points (not all of which have to be specified at once),
|
|
* this class will add the appropriate lineTos to the given drawing context.
|
|
*
|
|
* This class was originally written on Codepen. Links:
|
|
*
|
|
* Codepen: https://codepen.io/sbrl/details/zrEyrg/
|
|
* Blog post: (coming soon!)
|
|
*
|
|
* This class depends on my earler bezier curve and vector classes. Links:
|
|
*
|
|
* Vector class: https://gist.github.com/sbrl/69a8fa588865cacef9c0
|
|
* Bezier curve class: https://gist.github.com/sbrl/efd57e458e71f8a54171
|
|
*
|
|
* Bug reports can be made as a comment on this gist, or
|
|
* sent to <bugs@starbeamrainbowlabs.com>. Alternatively, you can tweet
|
|
* me at @SBRLabs.
|
|
*******************************************************************************
|
|
* Author: Starbeamrainbowlabs <bugs@starbeamrainbowlabs.com>
|
|
*
|
|
* Changelog:
|
|
* v0.1: Initial revision.
|
|
*
|
|
*/
|
|
|
|
class SmoothLine
|
|
{
|
|
constructor()
|
|
{
|
|
this.points = [];
|
|
this.interpolatedPoints = [];
|
|
this.bezierCurves = [];
|
|
|
|
this.lastPointLength = -1;
|
|
}
|
|
|
|
/**
|
|
* Adds one or more points to the smooth line.
|
|
* @param {Vector} point A single vector or an array of vectors to add onto the end of the smooth line.
|
|
*/
|
|
add(point)
|
|
{
|
|
if (Array.isArray(point))
|
|
this.points.push(...point);
|
|
else
|
|
this.points.push(point);
|
|
}
|
|
|
|
/**
|
|
* Internal. Interpolates THe given array of vectors once.
|
|
* @param {Vector[]} points The array of vectors to interpolate.
|
|
* @param {number} time The percentage between 0 and 1 at which to interpolate.
|
|
* @return {Vector[]} The interpolated vectors.
|
|
*/
|
|
interpolateOnce(points, time)
|
|
{
|
|
// Input validation checks
|
|
if (time < 0 || time > 1)
|
|
throw new Error(`The time specified was out of bounds! It should be between 0 and 1, but a value of ${time} was provided.`);
|
|
if (!Array.isArray(points))
|
|
throw new Error("THe points provided are not in an array!");
|
|
if (points.length < 3)
|
|
throw new Error("A minimum of 3 points are required to draw a smooth line.");
|
|
|
|
var result = [];
|
|
// Loop over all the points, except the last one
|
|
for (let i = 0; i < points.length - 1; i++) {
|
|
// Find the difference between the current point and the next one along
|
|
// To get the vector of the line between 2 points, you do b - a for the points a and b.
|
|
let difference = points[i + 1].clone().subtract(points[i]);
|
|
|
|
// Multiply the line's vector by the time in order to extract a percentage along the line
|
|
difference.multiply(time);
|
|
|
|
// Add the first point on to put the vector back in the right place,
|
|
// and then add it to the interpolated pionts array.
|
|
// It's important to add the first control point on again here as we
|
|
// made the vector relative to 0 in order to perform the
|
|
// interpolation rather than relative to the first point on the line
|
|
// as it should be.
|
|
result.push(difference.add(points[i]));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Adds the smooth line to the path of the given canvas drawing context.
|
|
* @param {CanvasDrawingContext2D} context The drawing context to add the smooth line to.
|
|
* @param {number} segmentCount The number of segments that each bezier curve should have.
|
|
*/
|
|
line(context, segmentCount)
|
|
{
|
|
if (this.points.length < 3)
|
|
throw new Error(`At least 3 points are required to draw a smooth line, but only ${this.points.length} points are currently specified.`);
|
|
|
|
if (this.lastPointLength !== this.points.length)
|
|
{
|
|
// Reset the bezier curve cache
|
|
this.bezierCurves = [];
|
|
|
|
this.interpolatedPoints = this.interpolateOnce(this.points, 0.5);
|
|
// Loop over every point except the frst & last ones
|
|
for (let i = 1; i < this.points.length - 1; i++)
|
|
{
|
|
let nextPointSet = [
|
|
this.interpolatedPoints[i - 1],
|
|
this.points[i],
|
|
this.interpolatedPoints[i]
|
|
];
|
|
|
|
// If this is the first iteration, make the first point of the bezier curve the first point that we were given
|
|
if (i == 1)
|
|
nextPointSet[0] = this.points[0];
|
|
// If this is the last iteration, make the end point of the bezier curve the last point we were given
|
|
if (i == this.points.length - 2)
|
|
nextPointSet[2] = this.points[this.points.length - 1];
|
|
// The above 2 checks are needed to make sure that the smooth line starts and ends at the points that we were given
|
|
|
|
let nextBezier = new BezierCurve(nextPointSet);
|
|
this.bezierCurves.push(nextBezier);
|
|
}
|
|
}
|
|
|
|
// Spin through all the bezier curves and get them to add themselves to the current path
|
|
for (let i = 0; i < this.bezierCurves.length; i++)
|
|
this.bezierCurves[i].curve(context, segmentCount);
|
|
|
|
// Update the cached poits length
|
|
this.lastPointLength = this.points.length;
|
|
}
|
|
}
|