/*
A surface filled with one hundred medium to small sized circles.
Each circle has a different size and direction, but moves at the same slow rate.
Display:
>>> A. The instantaneous intersections of the circles
B. The aggregate intersections of the circles
Ported to p5.js by Casey Reas
11 July 2016
p5.js 0.5.2
Restored by Casey Reas <http://reas.com>
22 June 2016
Processing v.3.1.1 <http://processing.org>
Implemented by William Ngan <http://metaphorical.net>
4 April 2004
Processing v.68 <http://processing.org>
*/
var cc = [];
var numCircles = 100;
function setup() {
createCanvas(600, 600);
frameRate(30);
for (var i = 0; i < numCircles; i++) {
cc[i] = new Circle(random(width), random(height), 15 + random(20), i);
cc[i].makeHairs();
}
}
function draw() {
background(255);
noStroke();
fill(0);
for (var i = 0; i < cc.length; i++) {
cc[i].draw();
}
noFill();
stroke(255);
for (var i = 0; i < cc.length; i++) {
cc[i].drawHair();
}
}
function Circle(px, py, pr, id) {
this.x = px;
this.y = py;
this.r = pr;
this.r2 = this.r * this.r;
this.d = this.r * 2;
this.d2 = this.d * this.d;
this.id = id;
this.gray = 0;
this.hairs = [];
this.numHairs = 30;
this.stepA = 2 * PI / this.numHairs;
this.sp1 = random(2);
this.sp2 = random(2);
this.sp3 = random(2);
this.ac1 = random(0.5) - random(0.5);
this.ac2 = random(0.5) - random(0.5);
this.ac3 = random(0.5) - random(0.5);
this.inx = 0;
this.iny = 0;
this.hasIntersect = false;
this.makeHairs = function() {
for (var i = 0; i < this.numHairs; i++) {
this.hairs[i] = new Hair(cos(this.stepA * i) * this.r,
sin(this.stepA * i) * this.r, 5, this.stepA * i + PI, cc[this.id]);
}
}
this.draw = function() {
fill(0);
noStroke();
ellipse(this.x, this.y, this.d, this.d);
this.move();
}
this.drawHair = function() {
for (var i = 0; i < this.hairs.length; i++) {
this.hairs[i].updatePos();
this.hairs[i].draw();
}
}
this.move = function() {
var angle = sin(this.sp1) - cos(this.sp2);
this.sp1 += this.ac1;
this.sp2 += this.ac2;
this.sp3 += this.ac3;
angle = (angle < 0) ? angle + TWO_PI : ((angle >= TWO_PI) ? angle - TWO_PI : angle);
this.x += sin(angle);
this.y -= cos(angle);
this.checkBounds();
this.checkIntersect();
}
this.checkIntersect = function() {
var flag = false;
var flag2 = false;
for (var i = 0; i < cc.length; i++) {
if (i != this.id) {
flag = this.intersect(cc[i]);
if (!flag2) {
flag2 = flag;
}
}
}
if (flag2) {
this.hairFocus(this.inx, this.iny);
} else if (this.hasIntersect) {
this.hairFocusRevert();
}
this.hasIntersect = flag2;
}
this.intersect = function(cB) {
var dx = this.x - cB.x;
var dy = this.y - cB.y;
var d2 = dx * dx + dy * dy;
var d = sqrt(d2);
if (d > this.r + cB.r || d < abs(this.r - cB.r)) return false;
var a = (this.r2 - cB.r2 + d2) / (2 * d);
var h = sqrt(this.r2 - a * a);
var x2 = this.x + a * (cB.x - this.x) / d;
var y2 = this.y + a * (cB.y - this.y) / d;
var paX = x2 + h * (cB.y - this.y) / d;
var paY = y2 - h * (cB.x - this.x) / d;
this.repel(atan2(dy, dx));
fill(0);
ellipse(paX, paY, 15, 15);
this.inx = x2;
this.iny = y2;
return true;
}
this.repel = function(angle) {
this.x = this.x + cos(angle) / 4;
this.y = this.y + sin(angle) / 4;
}
this.hairFocus = function(px, py) {
for (var i = 0; i < this.hairs.length; i++) {
this.hairs[i].focus(px, py);
}
}
this.hairFocusRevert = function() {
for (var i = 0; i < this.hairs.length; i++) {
this.hairs[i].revertFocus();
}
}
this.checkBounds = function() {
if (this.x > width) this.x = 0;
if (this.x < 0) this.x = width;
if (this.y > height) this.y = 0;
if (this.y < 0) this.y = height;
}
}
function Hair(rx, ry, r, a, parentCircle) {
this.regX = rx;
this.regY = ry;
this.radius = r;
this.origRadius = r;
this.angle = a;
this.origAngle = a;
this.pc = parentCircle; // Parent circle
this.nextX = this.pc.x + this.regX + cos(this.angle) * this.radius;
this.nextY = this.pc.y + this.regY + sin(this.angle) * this.radius;
this.x = this.nextX;
this.y = this.nextY;
this.speedFactor = 5;
this.updatePos = function() {
this.nextX = this.pc.x + this.regX + cos(this.angle) * this.radius;
this.nextY = this.pc.y + this.regY + sin(this.angle) * this.radius;
var dx = this.nextX - this.x;
var dy = this.nextY - this.y;
if (abs(dx) > 1) {
this.x += dx / this.speedFactor;
this.y += dy / this.speedFactor;
}
if (abs(dx) > 200 || abs(dy) > 200) {
this.x = this.nextX;
this.y = this.nextY;
}
}
this.draw = function() {
stroke(255);
line(this.pc.x+this.regX, this.pc.y+this.regY, this.x, this.y);
}
this.focus = function(px, py) {
var dx = px - (this.pc.x + this.regX);
var dy = py - (this.pc.y + this.regY);
this.angle = atan2(dy, dx);
this.radius = dist(px, py, this.pc.x+this.regX, this.pc.y+this.regY);
}
this.revertFocus = function() {
this.angle = this.origAngle;
this.radius = this.origRadius;
}
}