UNPKG

paper

Version:

The Swiss Army Knife of Vector Graphics Scripting

284 lines (258 loc) 11.7 kB
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Tadpoles</title> <link rel="stylesheet" href="../css/style.css"> <script type="text/javascript" src="../../dist/paper-full.js"></script> <script type="text/paperscript" canvas="canvas"> // Adapted from Flocking Processing example by Daniel Schiffman: // http://processing.org/learning/topics/flocking.html var Boid = Base.extend({ initialize: function(position, maxSpeed, maxForce) { var strength = Math.random() * 0.5; this.acceleration = new Point(); this.vector = Point.random() * 2 - 1; this.position = position.clone(); this.radius = 30; this.maxSpeed = maxSpeed + strength; this.maxForce = maxForce + strength; this.amount = strength * 10 + 10; this.count = 0; this.createItems(); }, run: function(boids) { this.lastLoc = this.position.clone(); if (!groupTogether) { this.flock(boids); } else { this.align(boids); } this.borders(); this.update(); this.calculateTail(); this.moveHead(); }, calculateTail: function() { var segments = this.path.segments, shortSegments = this.shortPath.segments; var speed = this.vector.length; var pieceLength = 5 + speed / 3; var point = this.position; segments[0].point = shortSegments[0].point = point; // Chain goes the other way than the movement var lastVector = -this.vector; for (var i = 1; i < this.amount; i++) { var vector = segments[i].point - point; this.count += speed * 10; var wave = Math.sin((this.count + i * 3) / 300); var sway = lastVector.rotate(90).normalize(wave); point += lastVector.normalize(pieceLength) + sway; segments[i].point = point; if (i < 3) shortSegments[i].point = point; lastVector = vector; } this.path.smooth(); }, createItems: function() { this.head = new Shape.Ellipse({ center: [0, 0], size: [13, 8], fillColor: 'white' }); this.path = new Path({ strokeColor: 'white', strokeWidth: 2, strokeCap: 'round' }); for (var i = 0; i < this.amount; i++) this.path.add(new Point()); this.shortPath = new Path({ strokeColor: 'white', strokeWidth: 4, strokeCap: 'round' }); for (var i = 0; i < Math.min(3, this.amount); i++) this.shortPath.add(new Point()); }, moveHead: function() { this.head.position = this.position; this.head.rotation = this.vector.angle; }, // We accumulate a new acceleration each time based on three rules flock: function(boids) { var separation = this.separate(boids) * 3; var alignment = this.align(boids); var cohesion = this.cohesion(boids); this.acceleration += separation + alignment + cohesion; }, update: function() { // Update velocity this.vector += this.acceleration; // Limit speed (vector#limit?) this.vector.length = Math.min(this.maxSpeed, this.vector.length); this.position += this.vector; // Reset acceleration to 0 each cycle this.acceleration = new Point(); }, seek: function(target) { this.acceleration += this.steer(target, false); }, arrive: function(target) { this.acceleration += this.steer(target, true); }, borders: function() { var vector = new Point(); var position = this.position; var radius = this.radius; var size = view.size; if (position.x < -radius) vector.x = size.width + radius; if (position.y < -radius) vector.y = size.height + radius; if (position.x > size.width + radius) vector.x = -size.width -radius; if (position.y > size.height + radius) vector.y = -size.height -radius; if (!vector.isZero()) { this.position += vector; var segments = this.path.segments; for (var i = 0; i < this.amount; i++) { segments[i].point += vector; } } }, // A method that calculates a steering vector towards a target // Takes a second argument, if true, it slows down as it approaches // the target steer: function(target, slowdown) { var steer, desired = target - this.position; var distance = desired.length; // Two options for desired vector magnitude // (1 -- based on distance, 2 -- maxSpeed) if (slowdown && distance < 100) { // This damping is somewhat arbitrary: desired.length = this.maxSpeed * (distance / 100); } else { desired.length = this.maxSpeed; } steer = desired - this.vector; steer.length = Math.min(this.maxForce, steer.length); return steer; }, separate: function(boids) { var desiredSeperation = 60; var steer = new Point(); var count = 0; // For every boid in the system, check if it's too close for (var i = 0, l = boids.length; i < l; i++) { var other = boids[i]; var vector = this.position - other.position; var distance = vector.length; if (distance > 0 && distance < desiredSeperation) { // Calculate vector pointing away from neighbor steer += vector.normalize(1 / distance); count++; } } // Average -- divide by how many if (count > 0) steer /= count; if (!steer.isZero()) { // Implement Reynolds: Steering = Desired - Velocity steer.length = this.maxSpeed; steer -= this.vector; steer.length = Math.min(steer.length, this.maxForce); } return steer; }, // Alignment // For every nearby boid in the system, calculate the average velocity align: function(boids) { var neighborDist = 25; var steer = new Point(); var count = 0; for (var i = 0, l = boids.length; i < l; i++) { var other = boids[i]; var distance = this.position.getDistance(other.position); if (distance > 0 && distance < neighborDist) { steer += other.vector; count++; } } if (count > 0) steer /= count; if (!steer.isZero()) { // Implement Reynolds: Steering = Desired - Velocity steer.length = this.maxSpeed; steer -= this.vector; steer.length = Math.min(steer.length, this.maxForce); } return steer; }, // Cohesion // For the average location (i.e. center) of all nearby boids, // calculate steering vector towards that location cohesion: function(boids) { var neighborDist = 100; var sum = new Point(); var count = 0; for (var i = 0, l = boids.length; i < l; i++) { var other = boids[i]; var distance = this.position.getDistance(other.position); if (distance > 0 && distance < neighborDist) { sum += other.position; // Add location count++; } } if (count > 0) { sum /= count; // Steer towards the location return this.steer(sum, false); } return sum; } }); var heartPath = new Path('M514.69629,624.70313c-7.10205,-27.02441 -17.2373,-52.39453 -30.40576,-76.10059c-13.17383,-23.70703 -38.65137,-60.52246 -76.44434,-110.45801c-27.71631,-36.64355 -44.78174,-59.89355 -51.19189,-69.74414c-10.5376,-16.02979 -18.15527,-30.74951 -22.84717,-44.14893c-4.69727,-13.39893 -7.04297,-26.97021 -7.04297,-40.71289c0,-25.42432 8.47119,-46.72559 25.42383,-63.90381c16.94775,-17.17871 37.90527,-25.76758 62.87354,-25.76758c25.19287,0 47.06885,8.93262 65.62158,26.79834c13.96826,13.28662 25.30615,33.10059 34.01318,59.4375c7.55859,-25.88037 18.20898,-45.57666 31.95215,-59.09424c19.00879,-18.32178 40.99707,-27.48535 65.96484,-27.48535c24.7373,0 45.69531,8.53564 62.87305,25.5957c17.17871,17.06592 25.76855,37.39551 25.76855,60.98389c0,20.61377 -5.04102,42.08691 -15.11719,64.41895c-10.08203,22.33203 -29.54687,51.59521 -58.40723,87.78271c-37.56738,47.41211 -64.93457,86.35352 -82.11328,116.8125c-13.51758,24.0498 -23.82422,49.24902 -30.9209,75.58594z'); var boids = []; var groupTogether = false; // Add the boids: for (var i = 0; i < 30; i++) { var position = Point.random() * view.size; boids.push(new Boid(position, 10, 0.05)); } function onFrame(event) { for (var i = 0, l = boids.length; i < l; i++) { if (groupTogether) { var length = ((i + event.count / 30) % l) / l * heartPath.length; var point = heartPath.getPointAt(length); if (point) boids[i].arrive(point); } boids[i].run(boids); } } // Reposition the heart path whenever the window is resized: function onResize(event) { heartPath.fitBounds(view.bounds); heartPath.scale(0.8); } function onMouseDown(event) { groupTogether = !groupTogether; } function onKeyDown(event) { if (event.key == 'space') { var layer = project.activeLayer; layer.selected = !layer.selected; return false; } } </script> <style> body { background: black; } </style> </head> <body> <canvas id="canvas" resize></canvas> </body> </html>