Parallax-Bicycle/SmoothLine.js

140 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;
}
}