1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
<!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 in Array.range(n)) {
// 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>
|