2011-04-19 15:27:59 -04:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
|
|
<title>Example</title>
|
|
|
|
<script type="text/javascript">var root = '../../'</script>
|
|
|
|
<script type="text/javascript" src="../../src/load.js"></script>
|
|
|
|
<script type="text/paperscript" canvas="canvas">
|
2011-04-20 07:07:40 -04:00
|
|
|
// Adapted from Flocking Processing example by Daniel Schiffman:
|
2011-04-19 17:32:34 -04:00
|
|
|
// http://processing.org/learning/topics/flocking.html
|
|
|
|
|
2011-04-19 15:27:59 -04:00
|
|
|
document.currentStyle = {
|
|
|
|
strokeColor: 'white',
|
|
|
|
strokeWidth: 2,
|
|
|
|
strokeCap: 'round'
|
|
|
|
};
|
|
|
|
|
2011-04-20 07:07:40 -04:00
|
|
|
var head = new Path.Oval([0, 0], [13, 8]);
|
|
|
|
head.fillColor = 'white';
|
|
|
|
head.strokeColor = null;
|
|
|
|
var headSymbol = new Symbol(head);
|
|
|
|
|
2011-04-28 15:13:51 -04:00
|
|
|
var size = document.size;
|
|
|
|
|
2011-04-19 15:27:59 -04:00
|
|
|
var Boid = Base.extend({
|
|
|
|
initialize: function(position, maxSpeed, maxForce) {
|
|
|
|
var strength = Math.random() * 0.5;
|
|
|
|
this.acc = new Point(0, 0);
|
|
|
|
this.vel = new Point(Math.random() * 2 - 1, Math.random() * 2 - 1);
|
|
|
|
this.loc = position.clone();
|
|
|
|
this.r = 30;
|
|
|
|
this.maxSpeed = maxSpeed + strength;
|
|
|
|
this.maxForce = maxForce + strength;
|
2011-04-20 07:07:40 -04:00
|
|
|
this.head = new PlacedSymbol(head);
|
2011-04-19 15:27:59 -04:00
|
|
|
this.path = new Path();
|
2011-04-20 07:07:40 -04:00
|
|
|
this.shortPath = new Path();
|
2011-04-26 10:01:41 -04:00
|
|
|
this.shortPath.strokeWidth = 4;
|
2011-04-19 15:27:59 -04:00
|
|
|
for(var i = 0, l = strength * 10 + 10; i < l; i++) {
|
|
|
|
this.path.add(this.loc);
|
2011-04-20 07:07:40 -04:00
|
|
|
if (i < 3)
|
|
|
|
this.shortPath.add(this.loc);
|
2011-04-19 15:27:59 -04:00
|
|
|
}
|
2011-04-20 07:07:40 -04:00
|
|
|
|
2011-04-19 15:27:59 -04:00
|
|
|
this.firstSegment = this.path.segments[0];
|
|
|
|
this.count = 0;
|
2011-04-20 07:07:40 -04:00
|
|
|
this.lastRot = 0;
|
2011-04-19 15:27:59 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
run: function(boids) {
|
|
|
|
this.lastLoc = this.loc.clone();
|
2011-04-26 10:01:41 -04:00
|
|
|
if (!groupTogether) {
|
2011-04-19 15:27:59 -04:00
|
|
|
this.flock(boids);
|
2011-04-20 07:07:40 -04:00
|
|
|
} else {
|
|
|
|
this.align(boids);
|
|
|
|
}
|
2011-04-19 15:27:59 -04:00
|
|
|
this.borders();
|
|
|
|
|
|
|
|
this.update();
|
|
|
|
this.firstSegment.point = this.loc;
|
|
|
|
var lastPoint = this.firstSegment.point;
|
|
|
|
var lastVector = this.loc - this.lastLoc;
|
|
|
|
var segments = this.path.segments;
|
|
|
|
for (var i = 1, l = segments.length; i < l; i++) {
|
|
|
|
var segment = segments[i];
|
|
|
|
var vector = lastPoint - segment.point;
|
|
|
|
this.count += this.vel.length * 10;
|
|
|
|
var rotated = lastVector.rotate(90).normalize(Math.sin((this.count + i * 3) / 300));
|
|
|
|
lastPoint = segment.point = lastPoint + lastVector.normalize(-5 - this.vel.length / 3);
|
|
|
|
segment.point += rotated;
|
|
|
|
lastVector = vector;
|
|
|
|
}
|
|
|
|
this.head.position = this.loc;
|
2011-04-20 07:07:40 -04:00
|
|
|
var vector = this.loc - this.lastLoc;
|
|
|
|
var rot = vector.angle;
|
|
|
|
this.head.rotate(rot - this.lastRot);
|
|
|
|
this.lastRot = rot;
|
|
|
|
|
|
|
|
var shortSegments = this.shortPath.segments;
|
2011-04-26 10:01:41 -04:00
|
|
|
for (var i = 0; i < 3; i++)
|
2011-04-20 07:07:40 -04:00
|
|
|
shortSegments[i] = segments[i].clone();
|
2011-04-19 15:27:59 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
// We accumulate a new acceleration each time based on three rules
|
|
|
|
flock: function(boids) {
|
|
|
|
var sep = this.separate(boids) * 3;
|
|
|
|
var ali = this.align(boids);
|
|
|
|
var coh = this.cohesion(boids);
|
|
|
|
this.acc += sep + ali + coh;
|
|
|
|
},
|
|
|
|
|
|
|
|
update: function() {
|
|
|
|
// Update velocity
|
|
|
|
this.vel += this.acc;
|
|
|
|
// Limit speed (vector#limit?)
|
|
|
|
this.vel.length = Math.min(this.maxSpeed, this.vel.length);
|
|
|
|
this.loc += this.vel;
|
|
|
|
// Reset acceleration to 0 each cycle
|
|
|
|
this.acc.length = 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
seek: function(target) {
|
|
|
|
this.acc += this.steer(target, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
arrive: function(target) {
|
|
|
|
this.acc += this.steer(target, true);
|
|
|
|
},
|
|
|
|
|
|
|
|
// 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.loc,
|
|
|
|
d = desired.length;
|
|
|
|
if (d > 0) {
|
|
|
|
// Two options for desired vector magnitude (1 -- based on distance, 2 -- maxSpeed)
|
|
|
|
if (slowdown && d < 100) {
|
|
|
|
desired.length = this.maxSpeed * (d / 100); // This damping is somewhat arbitrary
|
|
|
|
} else {
|
|
|
|
desired.length = this.maxSpeed;
|
|
|
|
}
|
|
|
|
steer = desired - this.vel;
|
|
|
|
steer.length = Math.min(this.maxForce, steer.length);
|
|
|
|
} else {
|
|
|
|
steer = new Point(0, 0);
|
|
|
|
}
|
|
|
|
return steer;
|
|
|
|
},
|
|
|
|
|
|
|
|
borders: function() {
|
|
|
|
var loc = this.loc,
|
|
|
|
r = this.r,
|
2011-04-28 14:46:24 -04:00
|
|
|
oldLoc = this.loc.clone(),
|
2011-04-28 15:13:51 -04:00
|
|
|
width = size.width,
|
|
|
|
height = size.height;
|
2011-04-19 15:27:59 -04:00
|
|
|
if (loc.x < -r) loc.x = width + r;
|
|
|
|
if (loc.y < -r) loc.y = height + r;
|
|
|
|
if (loc.x > width + r) loc.x = -r;
|
|
|
|
if (loc.y > height + r) loc.y = -r;
|
|
|
|
var vector = this.loc - oldLoc;
|
2011-04-26 10:01:41 -04:00
|
|
|
if (!vector.isZero())
|
2011-04-19 15:27:59 -04:00
|
|
|
this.path.position += vector;
|
|
|
|
},
|
|
|
|
|
|
|
|
separate: function(boids) {
|
|
|
|
var desiredSeperation = 60,
|
|
|
|
steer = new Point(0, 0),
|
|
|
|
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 d = other.loc.getDistance(this.loc);
|
|
|
|
if (d > 0 && d < desiredSeperation) {
|
|
|
|
// Calculate vector pointing away from neighbor
|
|
|
|
var diff = this.loc - other.loc;
|
|
|
|
steer += diff.normalize(1 / d);
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Average -- divide by how many
|
|
|
|
if (count > 0)
|
|
|
|
steer /= count;
|
2011-04-26 10:01:41 -04:00
|
|
|
if (steer.length > 0) {
|
2011-04-19 15:27:59 -04:00
|
|
|
// Implement Reynolds: Steering = Desired - Velocity
|
|
|
|
steer.length = this.maxSpeed;
|
|
|
|
steer -= this.vel;
|
|
|
|
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,
|
|
|
|
steer = new Point(0, 0),
|
|
|
|
count = 0,
|
2011-04-20 07:07:40 -04:00
|
|
|
nearest = 999,
|
|
|
|
closestPoint;
|
2011-04-19 15:27:59 -04:00
|
|
|
for (var i = 0, l = boids.length; i < l; i++) {
|
|
|
|
var other = boids[i];
|
|
|
|
var d = this.loc.getDistance(other.loc);
|
2011-04-26 10:01:41 -04:00
|
|
|
if (d > 0 && d < nearest) {
|
2011-04-20 07:07:40 -04:00
|
|
|
closestPoint = other.loc;
|
2011-04-19 15:27:59 -04:00
|
|
|
nearest = d;
|
2011-04-20 07:07:40 -04:00
|
|
|
}
|
2011-04-26 10:01:41 -04:00
|
|
|
if (d > 0 && d < neighborDist) {
|
2011-04-19 15:27:59 -04:00
|
|
|
steer += other.vel;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
2011-04-20 07:07:40 -04:00
|
|
|
|
2011-04-19 15:27:59 -04:00
|
|
|
if (count > 0)
|
|
|
|
steer /= count;
|
|
|
|
if (steer.length > 0) {
|
|
|
|
// Implement Reynolds: Steering = Desired - Velocity
|
|
|
|
steer.length = this.maxSpeed;
|
|
|
|
steer -= this.vel;
|
|
|
|
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,
|
|
|
|
sum = new Point(0, 0),
|
|
|
|
count = 0;
|
|
|
|
for (var i = 0, l = boids.length; i < l; i++) {
|
|
|
|
var other = boids[i];
|
|
|
|
var d = this.loc.getDistance(other.loc);
|
2011-04-26 10:01:41 -04:00
|
|
|
if (d > 0 && d < neighborDist) {
|
2011-04-19 15:27:59 -04:00
|
|
|
sum += other.loc; // Add location
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (count > 0) {
|
|
|
|
sum /= count;
|
|
|
|
// Steer towards the location
|
|
|
|
return this.steer(sum, false);
|
|
|
|
}
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2011-04-28 15:16:05 -04:00
|
|
|
var boids = [];
|
2011-04-20 07:07:40 -04:00
|
|
|
for (var i = 0; i < 30; i++) {
|
2011-04-19 15:27:59 -04:00
|
|
|
var position = Point.random() * document.size;
|
|
|
|
boids.push(new Boid(position, 10, 0.05));
|
|
|
|
}
|
2011-04-27 18:19:33 -04:00
|
|
|
|
2011-04-19 17:32:34 -04:00
|
|
|
var heartPath = new Path(
|
2011-04-27 18:19:33 -04:00
|
|
|
new Segment([514.6962890625, 624.703125], [7.0966796875, -26.3369140625], [-7.10205078125, -27.0244140625]),
|
|
|
|
new Segment([484.29052734375, 548.6025390625], [13.16845703125, 23.7060546875], [-13.173828125, -23.70703125]),
|
|
|
|
new Segment([407.84619140625, 438.14453125], [37.79296875, 49.935546875], [-27.71630859375, -36.6435546875]),
|
|
|
|
new Segment([356.654296875, 368.400390625], [6.41015625, 9.8505859375], [-10.53759765625, -16.02978515625]),
|
|
|
|
new Segment([333.80712890625, 324.25146484375], [4.69189453125, 13.3994140625], [-4.697265625, -13.39892578125]),
|
|
|
|
new Segment([326.76416015625, 283.53857421875], [0, 13.74267578125], [0, -25.42431640625]),
|
|
|
|
new Segment([352.18798828125, 219.634765625], [-16.95263671875, 17.17822265625], [16.94775390625, -17.1787109375]),
|
|
|
|
new Segment([415.0615234375, 193.8671875], [-24.96826171875, 0], [25.19287109375, 0]),
|
|
|
|
new Segment([480.68310546875, 220.66552734375], [-18.552734375, -17.86572265625], [13.96826171875, 13.28662109375]),
|
|
|
|
new Segment([514.6962890625, 280.10302734375], [-8.70703125, -26.3369140625], [7.55859375, -25.88037109375]),
|
|
|
|
new Segment([546.6484375, 221.0087890625], [-13.7431640625, 13.517578125], [19.0087890625, -18.32177734375]),
|
|
|
|
new Segment([612.61328125, 193.5234375], [-24.9677734375, 0], [24.7373046875, 0]),
|
|
|
|
new Segment([675.486328125, 219.119140625], [-17.177734375, -17.06005859375], [17.1787109375, 17.06591796875]),
|
|
|
|
new Segment([701.2548828125, 280.10302734375], [0, -23.58837890625], [0, 20.61376953125]),
|
|
|
|
new Segment([686.1376953125, 344.52197265625], [10.076171875, -22.33203125], [-10.08203125, 22.33203125]),
|
|
|
|
new Segment([627.73046875, 432.3046875], [28.8603515625, -36.1875], [-37.5673828125, 47.412109375]),
|
|
|
|
new Segment([545.6171875, 549.1171875], [17.1787109375, -30.458984375], [-13.517578125, 24.0498046875])
|
2011-04-19 15:27:59 -04:00
|
|
|
);
|
2011-04-19 17:32:34 -04:00
|
|
|
heartPath.closed = true;
|
|
|
|
heartPath.position = document.bounds.center;
|
|
|
|
heartPath.strokeColor = null;
|
|
|
|
heartPath.scale(1.5);
|
2011-04-27 18:19:33 -04:00
|
|
|
|
2011-04-19 15:27:59 -04:00
|
|
|
var count = 0,
|
|
|
|
groupTogether = false,
|
2011-04-19 17:32:34 -04:00
|
|
|
pathLength = heartPath.length,
|
2011-04-19 15:27:59 -04:00
|
|
|
mouseDown = false;
|
2011-04-27 18:19:33 -04:00
|
|
|
|
2011-04-22 11:31:46 -04:00
|
|
|
function onFrame() {
|
2011-04-19 15:27:59 -04:00
|
|
|
count++;
|
|
|
|
for(var i = 0, l = boids.length; i < l; i++) {
|
|
|
|
if (groupTogether) {
|
2011-04-27 16:40:52 -04:00
|
|
|
var point = heartPath.getPointAt(((i + count / 30) % l) / l * pathLength);
|
2011-04-19 15:27:59 -04:00
|
|
|
boids[i].arrive(point);
|
|
|
|
}
|
|
|
|
boids[i].run(boids);
|
|
|
|
}
|
|
|
|
}
|
2011-04-27 18:19:33 -04:00
|
|
|
|
2011-04-19 15:27:59 -04:00
|
|
|
function onMouseDown(event) {
|
2011-04-19 15:38:06 -04:00
|
|
|
groupTogether = !groupTogether;
|
2011-04-19 15:27:59 -04:00
|
|
|
}
|
2011-04-28 14:46:24 -04:00
|
|
|
|
2011-04-28 14:57:04 -04:00
|
|
|
var layer = document.activeLayer;
|
|
|
|
function onKeyDown(event) {
|
|
|
|
if (event.character == 'space')
|
|
|
|
layer.selected = !layer.selected;
|
|
|
|
}
|
|
|
|
|
2011-04-28 14:46:24 -04:00
|
|
|
// Resize the document, whenever the window is resized,
|
|
|
|
window.onresize = function(event) {
|
2011-04-28 15:18:10 -04:00
|
|
|
document.size = [window.innerWidth, window.innerHeight];
|
|
|
|
size = document.size;
|
2011-04-28 14:46:24 -04:00
|
|
|
heartPath.position = document.bounds.center;
|
|
|
|
document.redraw();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call straight away to resize the document to the current
|
|
|
|
// window size:
|
|
|
|
window.onresize();
|
2011-04-19 15:27:59 -04:00
|
|
|
</script>
|
|
|
|
</head>
|
2011-04-28 14:46:24 -04:00
|
|
|
<body style='background-color: black; margin: 0; overflow: hidden'>
|
|
|
|
<canvas id='canvas'></canvas>
|
2011-04-19 15:27:59 -04:00
|
|
|
</body>
|