Skip to content

Instantly share code, notes, and snippets.

@sunjay
Created May 23, 2014 00:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save sunjay/3f67f2cbd26554e5000a to your computer and use it in GitHub Desktop.
Save sunjay/3f67f2cbd26554e5000a to your computer and use it in GitHub Desktop.
Generating Parallel Trails using HTML5 Canvas

Generating Parallel Trails using HTML5 Canvas

Author: Sunjay Varma Date: May 22, 2014

This is a demonstration of a method of generating parallel trails of points in JavaScript. They are called "trails" since this implementaion uses mouse movements to generate curves that "trail" behind the mouse.

  • Supports minimum sampling distance between points
  • Generate any number of parallel points
  • See how the formula for the parallel points was derived: https://dl.dropboxusercontent.com/u/6086480/Deriving%20a%20Formula%20for%20Parallel%20Points.pdf
  • To try it simply download the gist, unzip or unpack it if necessary and then double click on canvastrails.html (move your mouse around the page to see trails)
  • main.js contains many different configuration options that can be used to play with the generated curves

There is a known bug where the trails cross over themselves from time to time. If you fix it, please fork and let me know.

You can reach me through my website http://www.sunjay.ca

<!DOCTYPE html>
<html>
<head>
<title>Canvas Trails</title>
<script type="text/javascript" src="line.js"></script>
<script type="text/javascript" src="trails.js"></script>
<script type="text/javascript" src="main.js"></script>
</head>
<body>
<canvas id="canvas" width="1200" height="600" style="border: 1px solid #EEE;">
</body>
</html>
/*
Point and Line objects for use in geometric calculations.
Original Author: Sunjay Varma (www.sunjay.ca)
Originally Created: May 21, 2014
*/
// Represents a simple point (x, y)
function Point(x, y) {
this.x = x;
this.y = y;
}
// Gets the distance from this point to another
Point.prototype.distanceTo = function(point) {
return Math.sqrt(Math.pow(point.x-this.x, 2) + Math.pow(point.y-this.y, 2));
};
// Represents a line in the form y = (slope)*x + intercept
function Line(slope, intercept) {
this.slope = slope;
this.intercept = intercept;
}
// Evaluates the line's y value at some x
Line.prototype.evaluate = function(x) {
return this.slope * x + this.intercept;
};
// Generates a new line perpendicular to this one passing through point
Line.prototype.perpendicularFromPoint = function(point) {
var pslope = -1/this.slope;
var yint = point.y - pslope * point.x;
return new Line(pslope, yint);
};
// Generates a new perpendicular line to this one passing through the same (0, intercept)
Line.prototype.perpendicular = function() {
// Probably could have used perpendicularFromPoint, but this will be
// faster in the long run.
return new Line(-1/this.slope, this.intercept);
};
// Line from p1 to p2
Line.fromPoints = function(p1, p2) {
var slope = (p2.y - p1.y)/(p2.x - p1.x);
return new Line(slope, p1.y - slope * p1.x);
};
/*
Example JavaScript of an implementation of canvas trails
Original Author: Sunjay Varma (www.sunjay.ca)
Originally Created: May 21, 2014
*/
window.onload = function() {
/* GLOBAL CONSTANTS */
// Number of consecutive points to track
var MAX_POINTS = 10;
// Minimum distance between consecutive points
var MIN_DISTANCE = 1; // px
// Relative horizontal distance between parallel trails
var PARALLEL_DISTANCE = 10; // px
// Number of total parallel lines to render
var PARALLEL_LINES = 5;
// Retrieve the drawing canvas
var canvas = document.getElementById("canvas");
// Make the canvas full screen
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
// Get the drawing context
var context = canvas.getContext("2d");
function clearCanvas() {
canvas.width = canvas.width;
}
function drawTrail(points) {
if (!points || points.length <= 1) {
return;
}
context.beginPath();
var last_point = points[0];
context.moveTo(last_point.x, last_point.y);
for (var i = 1; i < points.length; i++) {
context.quadraticCurveTo(last_point.x, last_point.y, points[i].x, points[i].y);
last_point = points[i];
}
context.stroke();
}
var points = [];
canvas.onmousemove = function(event) {
var x = event.clientX - canvas.offsetLeft;
var y = event.clientY - canvas.offsetTop;
point = new Point(x, y);
if (!points.length || point.distanceTo(points[points.length-1]) >= MIN_DISTANCE) {
points.push(point);
if (points.length >= MAX_POINTS) {
points.splice(0, points.length - MAX_POINTS);
}
}
clearCanvas();
context.strokeStyle = "#99A";
var parallelTrails = generateParallelTrails(points, PARALLEL_LINES, PARALLEL_DISTANCE);
for (var i = 0; i < parallelTrails.length; i++) {
drawTrail(parallelTrails[i]);
}
};
};
window.onresize = function() {
// Retrieve the drawing canvas
var canvas = document.getElementById("canvas");
// Make the canvas full screen
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
};
/*
JavaScript for generating the points of any number of parallel trails
based on a single set of points that represent some line/curve.
Original Author: Sunjay Varma (www.sunjay.ca)
Originally Created: May 21, 2014
*/
// Utility function for squaring a number
function sq(n) { return Math.pow(n, 2); }
// Function which generates a parallel set of points (to the given centerpoints)
// The parallel points will be `offset` away from each point in centerpoints
function parallelTrail(centerpoints, offset) {
if (offset === 0) {
return centerpoints;
}
if (!centerpoints || centerpoints.length <= 1) {
return [];
}
var parallelPoints = [];
var last_point = centerpoints[1];
var factor = (offset < 0)? -1 : 1;
for (var i = 0; i < centerpoints.length; i++) {
var point = centerpoints[i];
var line = Line.fromPoints(last_point, point);
// perpendicular line at point
var pline = line.perpendicularFromPoint(point);
if (!isFinite(pline.slope)) {
last_point = point;
continue;
}
// The point on the parallel line
var sqrt = Math.sqrt;
//http://www.wolframalpha.com/input/?i=solve+for+a+in+d%5E2+%3D+%28a-w%29%5E2+%2B+%28p*a+%2B+c+-+z%29%5E2
var parallelX = (factor * sqrt(-sq(pline.intercept)-2*pline.intercept*pline.slope*point.x + 2*pline.intercept*point.y + sq(offset*pline.slope) + sq(offset) - sq(pline.slope*point.x) + 2*pline.slope*point.x*point.y - sq(point.y)) - pline.intercept*pline.slope + pline.slope*point.y + point.x)/(sq(pline.slope) + 1);
var parallelY = pline.evaluate(parallelX);
parallelPoints.push(new Point(parallelX, parallelY));
last_point = point;
}
return parallelPoints;
}
// Function that generates `numtrails` parallel trails to the given
// `centerpoints`. `centerpoints` is an array of Point objects.
function generateParallelTrails(centerpoints, numtrails, parallel_distance) {
if (!numtrails) {
throw "Invalid number of trails " + numtrails;
}
var parallelTrails = [];
var half = Math.floor(numtrails/2);
for (var i = -half; i <= (numtrails-half-1); i++) {
var parallelTrailPoints = parallelTrail(centerpoints, i * parallel_distance);
parallelTrails.push(parallelTrailPoints);
}
return parallelTrails
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment