You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
5.0 KiB
HTML
173 lines
5.0 KiB
HTML
5 years ago
|
<!doctype html>
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>pack</title>
|
||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||
|
<script type="text/javascript" src="../../pattern/canvas.js"></script>
|
||
|
</head>
|
||
|
<body>
|
||
|
<script type="text/canvas">
|
||
|
var Circle = Class.extend({
|
||
|
init: function(x, y, radius, image) {
|
||
|
/* An object that can be passed to pack(),
|
||
|
* with a repulsion radius and an image to draw inside the radius.
|
||
|
*/
|
||
|
this.x = x;
|
||
|
this.y = y;
|
||
|
this.radius = radius;
|
||
|
this.image = image;
|
||
|
this.goal = new Point(x,y);
|
||
|
},
|
||
|
contains: function(x, y) {
|
||
|
return geometry.distance(this.x, this.y, x, y) <= this.radius;
|
||
|
},
|
||
|
draw: function() {
|
||
|
var a = geometry.angle(this.x, this.y, this.goal.x, this.goal.y);
|
||
|
var r = this.radius * 1.25; // The cells can overlap a little bit.
|
||
|
var w = this.image.width;
|
||
|
var h = this.image.height;
|
||
|
push();
|
||
|
translate(this.x, this.y);
|
||
|
scale(r * 2 / Math.min(w, h));
|
||
|
rotate(a);
|
||
|
image(this.image, -w/2, -h/2); // Rotate from image center.
|
||
|
pop();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function pack(circles, x, y, padding, exclude) {
|
||
|
/* Circle-packing algorithm.
|
||
|
* Groups the given list of Circle objects around (x,y) in an organic way.
|
||
|
*/
|
||
|
// Ported from Sean McCullough's Processing code:
|
||
|
// http://www.cricketschirping.com/processing/CirclePacking1/
|
||
|
// See also: http://en.wiki.mcneel.com/default.aspx/McNeel/2DCirclePacking
|
||
|
|
||
|
// Repulsive force: move away from intersecting circles.
|
||
|
for (var i=0; i < circles.length; i++) {
|
||
|
for (var j=i+1; j < circles.length; j++) {
|
||
|
var circle1 = circles[i];
|
||
|
var circle2 = circles[j];
|
||
|
var d = geometry.distance(circle1.x, circle1.y, circle2.x, circle2.y);
|
||
|
var r = circle1.radius + circle2.radius + padding;
|
||
|
if (d < r - 0.01) {
|
||
|
var dx = circle2.x - circle1.x;
|
||
|
var dy = circle2.y - circle1.y;
|
||
|
var vx = (dx / d) * (r-d) * 0.5;
|
||
|
var vy = (dy / d) * (r-d) * 0.5;
|
||
|
if (!Array.contains(exclude, circle1)) {
|
||
|
circle1.x -= vx;
|
||
|
circle1.y -= vy;
|
||
|
}
|
||
|
if (!Array.contains(exclude, circle2)) {
|
||
|
circle2.x += vx;
|
||
|
circle2.y += vy;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Attractive force: move all circles to center.
|
||
|
Array.enumerate(circles, function(i, circle) {
|
||
|
circle.goal.x = x;
|
||
|
circle.goal.y = y;
|
||
|
if (!Array.contains(exclude, circle)) {
|
||
|
var damping = Math.pow(circle.radius, 3) * 0.000001; // Big ones in the middle.
|
||
|
var vx = (circle.x - x) * damping;
|
||
|
var vy = (circle.y - y) * damping;
|
||
|
circle.x -= vx;
|
||
|
circle.y -= vy;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function cell(t) {
|
||
|
// Returns a random PNG-image (artwork © Ludivine Lechat).
|
||
|
// Some cells occur more frequently than others:
|
||
|
// t is a number between 0.0 and 1.0 that determines which image to pick.
|
||
|
// This is handy when combined with smoothstep(),
|
||
|
// then we can put a preference on empty blue cells,
|
||
|
// while still ensuring that some of each cell appear.
|
||
|
var url = "http://www.clips.ua.ac.be/media/canvas/examples/g/";
|
||
|
if (t < 0.4) {
|
||
|
url += Array.choice([
|
||
|
"green-empty1.png",
|
||
|
"green-empty2.png",
|
||
|
"green-empty3.png",
|
||
|
"green-block1.png",
|
||
|
"green-block2.png"]);
|
||
|
} else if (t < 0.5) {
|
||
|
url += Array.choice([
|
||
|
"green-circle1.png",
|
||
|
"green-circle2.png"]);
|
||
|
} else if (t < 0.6) {
|
||
|
url += Array.choice([
|
||
|
"green-star1.png",
|
||
|
"green-star2.png"]);
|
||
|
} else {
|
||
|
url += Array.choice([
|
||
|
"blue-block.png",
|
||
|
"blue-circle.png",
|
||
|
"blue-star.png",
|
||
|
"blue-empty1.png",
|
||
|
"blue-empty1.png",
|
||
|
"blue-empty2.png",
|
||
|
"blue-empty2.png",
|
||
|
"blue-empty2.png"]);
|
||
|
}
|
||
|
return new Image(url);
|
||
|
}
|
||
|
|
||
|
function setup(canvas) {
|
||
|
circles = [];
|
||
|
dragged = null;
|
||
|
size(500, 500);
|
||
|
var n = 60;
|
||
|
for (var i=0; i < n; i++) {
|
||
|
// Create a group of n cells.
|
||
|
// Smoothstep yields more numbers near 1.0 than near 0.0,
|
||
|
// so we'll got mostly empty blue cells.
|
||
|
var t = geometry.smoothstep(0, n, i);
|
||
|
circles.push(
|
||
|
new Circle(
|
||
|
random(-100), // Start offscreen to the left.
|
||
|
random(canvas.height),
|
||
|
10 + 0.5 * t * i, // Make the blue cells bigger.
|
||
|
cell(t)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var iterations = 0;
|
||
|
function draw(canvas) {
|
||
|
background(1);
|
||
|
// Cells can be dragged.
|
||
|
if (dragged) {
|
||
|
dragged.x = canvas.mouse.x;
|
||
|
dragged.y = canvas.mouse.y;
|
||
|
iterations = 0;
|
||
|
}
|
||
|
if (!canvas.mouse.pressed) {
|
||
|
dragged = null;
|
||
|
} else if (!dragged) {
|
||
|
for (var i=0; i < circles.length; i++) {
|
||
|
if (circles[i].contains(canvas.mouse.x, canvas.mouse.y)) {
|
||
|
dragged = circles[i];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Draw all cells.
|
||
|
Array.enumerate(circles, function(i, circle) {
|
||
|
circle.draw();
|
||
|
});
|
||
|
// Circle packing.
|
||
|
if (iterations < 1000) {
|
||
|
pack(circles, 250, 250, 2, [dragged]);
|
||
|
}
|
||
|
iterations++;
|
||
|
}
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|