Today marks the public release of the Leap Motion controller: people who pre-ordered devices will start to receive them today, so it seemed a good time to post something related to it.
Last week I came across a really interesting JavaScript library called Paper.js. According to its website, it’s “an open source vector graphics scripting framework that runs on top of the HTML5 Canvas. It offers a clean Scene Graph / Document Object Model and a lot of powerful functionality to create and work with vector graphics and bezier curves, all neatly wrapped up in a well designed, consistent and clean programming interface.”
If you want to validate this assertion, just take a look at these examples, clicking down through the list and checking out the source code using the link in the top-right of each page. Very cool stuff.
For instance, here’s a really neat example – especially relevant to people who care about CAD – of intersections between “paths” calculated dynamically:
Given the fact there’s a JavaScript library available for the Leap Motion controller called LeapJS, it seemed like a fun idea to hook the two together. I’m fairly familiar with the LeapJS implementation – having created a few (as yet unpublished) prototypes to make use of AutoCAD 2014’s JavaScript API – so I just needed to take a look at what needed to be done with Paper.js.
Paper.js provides a scripting environment known as PaperScript. It’s based on JavaScript but adds additional capabilities to the language and abstracts away boilerplate code. Most of the posted Paper.js examples are in PaperScript, so there’s a bit more work to do if we’re going to adapt any to work from plain old JavaScript.
The example I chose to hook up to Leap Motion is called “Lines” and is part of the Paper.js download (I’m using v0.9.8). Any of the samples in the “examples\Animated” folder would probably be good candidates for integrating with Leap Motion, though.
The first step was to get the PaperScript code working in JavaScript, as per the instructions in this tutorial. This mostly involved adding a few manual calls to the Paper.js framework and changing the PaperScript code to replace its (very convenient) arithmetic operations between points (etc.) with more manual approaches such as adding respective X and Y values together and using trigonometry to calculate the 2D vector from a distance at a certain angle. Not that hard to do, but it needed doing.
Here’s the HTML page with embedded JavaScript code that integrates LeapJS with Paper.js:
<!DOCTYPE html>
<html>
<head>
<!-- Reference the Paper.js and Leap.js libraries (minified
versions of each also exist -->
<script type="text/javascript" src="js/paper.js"></script>
<script type="text/javascript" src="js/leap.js"></script>
<script type="text/javascript">
// Global variables
var amount = 45; // The number of lines that make up the ball
var ballSize = 200; // The initial size of the ball
var position; // The last position of the ball
var cursor; // The position of the cursor (also set by Leap)
var children; // The lines that make up the ball
// As we're in JavaScript (rather than PaperScript), we need to
// jump through a few extra hoops to get Paper.js working
paper.install(window);
// Our iterate function that does the bulk of the work
function iterate(count) {
// We need to calculate deltas between points manually
// (PaperScript provides arithmetic operators on - for
// instance - points, which eases this pain)
// Find out how the cursor has moved since the last
// iteration (and move it a tenth of the distance along
// that path, presumably for smoother animation)
var deltaX = cursor.x - position.x;
var deltaY = cursor.y - position.y;
position.x += deltaX / 10;
position.y += deltaY / 10;
// Loop through the lines in the ball and adjust them
for (var i = 1; i < amount; i++) {
var path = children[i];
var length = Math.abs(Math.sin(i + count / 40) * ballSize);
var am2pi = 2 * Math.PI / amount; // Common value
// Trigonometric operation needs to be done manually
// (PaperScript allows you to add vectors defined by
// angle and length)
path.segments = [
new Point(
position.x + deltaX / 1.5, position.y + deltaY / 1.5
),
new Point(
position.x + length * Math.sin(i * am2pi),
position.y + length * Math.cos(i * am2pi)
),
new Point(
position.x + length * Math.sin((i + 1) * am2pi),
position.y + length * Math.cos((i + 1) * am2pi)
)
];
// The colour varies based on the size of the line
// (but shouldn't depend on the size of the ball)
path.fillColor.hue = count - (length * 200 / ballSize);
}
}
// Our main startup function
window.onload = function () {
// Define the Canvas element as our target
paper.setup('myCanvas');
// Assign (initial) values to our globals
position = view.center;
cursor = view.center;
children = project.activeLayer.children;
// Create the lines in our ball
for (var i = 0; i < amount; i++) {
var path = new Path({
fillColor: {
hue: 1,
saturation: 1,
brightness: Math.random()
},
closed: true
});
}
// We need a tool to intercept our mouse movements
var tool = new Tool();
tool.onMouseMove = function (event) {
iterate();
cursor = event.point;
}
// We also iterate when the mouse isn't moving
view.onFrame = function (event) {
iterate(event.count);
}
}
// If the Leap Motion library is available, set up our
// Leap loop. Will only be called if a controller is
// available, though
if (typeof Leap !== "undefined")
{
// Setup Leap loop with frame callback function
Leap.loop(function (frame) {
// We just want to get the first hand
if (frame.hands.length > 0) {
var hand = frame.hands[0];
// Define the size of the ball based on the height of
// the hand
ballSize = hand.sphereCenter[1] * 3;
// And we set the location of the ball - relative to
// the center of the view - based on the position of the
// hand in Leap space
cursor =
new Point(
hand.sphereCenter[0] * 4 + view.size.width / 2,
hand.sphereCenter[2] * 4 + view.size.height / 2
);
}
})
}
</script>
</head>
<body style='background-color:black'>
<canvas id="myCanvas" resize></canvas>
</body>
</html>
You should be able to see it in action below or by opening this link (assuming you have a compatible browser, which means it won't work with IE8 and below… if using IE9 or above and don’t see anything, make sure you don’t have “compatibility mode” enabled for this site). The page doesn’t actually require a Leap Motion controller to work – if you move your mouse cursor over the window, the ball of lines should react. Using Leap Motion you can not only move the ball around but also change its size by adjusting the elevation of your hand.
It was a fun mini-project to get these two JavaScript libraries working in the same HTML page. I’m definitely planning to do more with Paper.js as the opportunity arises (and will inevitably work more with Leap Motion, too: they seem to have made solid progress with their runtime in recent weeks/months, and it’ll certainly be interesting to see how people respond to the product’s release).
[A reminder that I'm on holiday for the whole of this week. I have a couple of posts queued up for the rest of the week about an Arduino project I've been fooling around with.]
Update:
See this post. It turns out you can make use of JavaScript libraries directly from PaperScript, which means a lot of the work performed for this post is redundant. Ho hum.