The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>Candy Crash</title>
	<link rel="stylesheet" href="../css/style.css">
	<script type="text/javascript" src="../../dist/paper-full.js"></script>
	<script type="text/paperscript" canvas="canvas">
	// kynd.info 2014

	function Ball(r, p, v) {
		this.radius = r;
		this.point = p;
		this.vector = v;
		this.maxVec = 15;
		this.numSegment = Math.floor(r / 3 + 2);
		this.boundOffset = [];
		this.boundOffsetBuff = [];
		this.sidePoints = [];
		this.path = new Path({
			fillColor: {
				hue: Math.random() * 360,
				saturation: 1,
				brightness: 1
			},
			blendMode: 'screen'
		});

		for (var i = 0; i < this.numSegment; i ++) {
			this.boundOffset.push(this.radius);
			this.boundOffsetBuff.push(this.radius);
			this.path.add(new Point());
			this.sidePoints.push(new Point({
				angle: 360 / this.numSegment * i,
				length: 1
			}));
		}
	}

	Ball.prototype = {
		iterate: function() {
			this.checkBorders();
			if (this.vector.length > this.maxVec)
				this.vector.length = this.maxVec;
			this.point += this.vector;
			this.updateShape();
		},

		checkBorders: function() {
			var size = view.size;
			if (this.point.x < -this.radius)
				this.point.x = size.width + this.radius;
			if (this.point.x > size.width + this.radius)
				this.point.x = -this.radius;
			if (this.point.y < -this.radius)
				this.point.y = size.height + this.radius;
			if (this.point.y > size.height + this.radius)
				this.point.y = -this.radius;
		},

		updateShape: function() {
			var segments = this.path.segments;
			for (var i = 0; i < this.numSegment; i ++)
				segments[i].point = this.getSidePoint(i);

			this.path.smooth();
			for (var i = 0; i < this.numSegment; i ++) {
				if (this.boundOffset[i] < this.radius / 4)
					this.boundOffset[i] = this.radius / 4;
				var next = (i + 1) % this.numSegment;
				var prev = (i > 0) ? i - 1 : this.numSegment - 1;
				var offset = this.boundOffset[i];
				offset += (this.radius - offset) / 15;
				offset += ((this.boundOffset[next] + this.boundOffset[prev]) / 2 - offset) / 3;
				this.boundOffsetBuff[i] = this.boundOffset[i] = offset;
			}
		},

		react: function(b) {
			var dist = this.point.getDistance(b.point);
			if (dist < this.radius + b.radius && dist != 0) {
				var overlap = this.radius + b.radius - dist;
				var direc = (this.point - b.point).normalize(overlap * 0.015);
				this.vector += direc;
				b.vector -= direc;

				this.calcBounds(b);
				b.calcBounds(this);
				this.updateBounds();
				b.updateBounds();
			}
		},

		getBoundOffset: function(b) {
			var diff = this.point - b;
			var angle = (diff.angle + 180) % 360;
			return this.boundOffset[Math.floor(angle / 360 * this.boundOffset.length)];
		},

		calcBounds: function(b) {
			for (var i = 0; i < this.numSegment; i ++) {
				var tp = this.getSidePoint(i);
				var bLen = b.getBoundOffset(tp);
				var td = tp.getDistance(b.point);
				if (td < bLen) {
					this.boundOffsetBuff[i] -= (bLen  - td) / 2;
				}
			}
		},

		getSidePoint: function(index) {
			return this.point + this.sidePoints[index] * this.boundOffset[index];
		},

		updateBounds: function() {
			for (var i = 0; i < this.numSegment; i ++)
				this.boundOffset[i] = this.boundOffsetBuff[i];
		}
	};

	//--------------------- main ---------------------

	var balls = [];
	var numBalls = 18;
	for (var i = 0; i < numBalls; i++) {
		var position = Point.random() * view.size;
		var vector = new Point({
			angle: 360 * Math.random(),
			length: Math.random() * 10
		});
		var radius = Math.random() * 60 + 60;
		balls.push(new Ball(radius, position, vector));
	}

	function onFrame() {
		for (var i = 0; i < balls.length - 1; i++) {
			for (var j = i + 1; j < balls.length; j++) {
				balls[i].react(balls[j]);
			}
		}
		for (var i = 0, l = balls.length; i < l; i++) {
			balls[i].iterate();
		}
	}

	</script>
</head>
<body>
	<canvas id="canvas" resize hidpi="off" style="background:black"></canvas>
</body>
</html>