/* 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 Robert Hodgin <http://flight404.com> 6 April 2004 Processing v.68 <http://processing.org> */ // ******************************************************************************** // INITIALIZE VARIABLES // ******************************************************************************** var xStage = 600; // x dimension of applet var yStage = 600; // y dimension of applet var xMid = xStage / 2; // x midpoint of applet var yMid = yStage / 2; // y midpoint of applet var totalCircles = 100; // total number of circles var circle = []; // Circle object array var gravity; // Strength of gravitational pull var xGrav; // x point of center of gravity var yGrav; // y point of center of gravity var xGravOffset; var angleOffset; var initRadius; var maxDistance; var bgColor; // ******************************************************************************** // SETUP FUNCTION // ******************************************************************************** function setup() { createCanvas(600, 600); bgColor = 255; background(bgColor); frameRate(30); createCircles(); } // ******************************************************************************** // MAIN LOOP FUNCTION // ******************************************************************************** function draw() { if (mouseIsPressed) { createCircles(); } background(bgColor); tellCirclesToBehave(); } function createCircles() { gravity = .075; maxDistance = 150; angleOffset = random(360); //circle = new Circle[totalCircles]; initRadius = 150; for (var i = 0; i < totalCircles; i++) { var initAngle = i * 3.6 + angleOffset + random(10); var initTheta = (-((initAngle) * PI)) / 180; var initxv = cos(initTheta) * initRadius; var inityv = sin(initTheta) * initRadius; var xPos = xMid + initxv; var yPos = yMid + inityv; circle[i] = new Circle(xPos, yPos, 0, 0, i); } } function tellCirclesToBehave() { for (var i = 0; i < totalCircles; i++) { circle[i].behave(); } } function Circle(xSent, ySent, xvSent, yvSent, indexSent) { this.x = xSent; // Circle x position this.y = ySent; // Circle y position this.r = 2; // Circle radius this.index = indexSent; // Circle global ID this.xv = xvSent;// Current velocity along x axis this.yv = yvSent;// Current velocity along y axis this.mightCollide = [];// Collision might happen this.hasCollided = []; // Collision is happening this.distances = []; // Storage for the distance between circles this.angles = [];// Storage for the angle between two connected circles this.thetas = [];// Storage for the radians between two connected circles this.numCollisions; // Number of collisions in one frame this.numConnections; // Total number of collisions this.xd; // Distance to target along x axis this.yd; // Distance to target along y axis this.d; // Distance to target this.alphaVar; // Late addition variable for alpha modification this.cAngle; // Angle of collision in degrees this.cTheta; // Angle of collision in radians this.cxv; // Collision velocity along x axis this.cyv; // Collision velocity along y axis this.gAngle; // Angle to gravity center in degrees this.gTheta; // Angle to gravity center in radians this.gxv; // Gravity velocity along x axis this.gyv; // Gravity velocity along y axis this.behave = function() { this.move(); this.areWeClose(); this.areWeColliding(); this.areWeConnected(); this.applyGravity(); this.render(); this.reset(); } this.areWeClose = function() { for (var i = 0; i < totalCircles; i++) { if (i != this.index) { if (abs(this.x - circle[i].x) < 50 && abs(this.y - circle[i].y) < 50) { this.mightCollide[i] = true; } else { this.mightCollide[i] = false; } } } } this.areWeColliding = function() { for (var i = 0; i < totalCircles; i++) { if (this.mightCollide[i] && i != this.index) { this.distances[i] = findDistance(this.x, this.y, circle[i].x, circle[i].y); if (this.distances[i] < (this.r + circle[i].r) * 1.1) { this.hasCollided[i] = true; circle[i].hasCollided[this.index] = true; this.angles[i] = findAngle(this.x, this.y, circle[i].x, circle[i].y); this.thetas[i] = (-(this.angles[i] * PI)) / 180.0; this.cxv += cos(this.thetas[i]) * ((circle[i].r + this.r) / 2.0); this.cyv += sin(this.thetas[i]) * ((circle[i].r + this.r) / 2.0); this.numCollisions += 1; } } } if (this.numCollisions > 0) { this.xv = -this.cxv / this.numCollisions; this.yv = -this.cyv / this.numCollisions; } this.cxv = 0.0; this.cyv = 0.0; } this.areWeConnected = function() { for (var i = 0; i < totalCircles; i++) { if (this.hasCollided[i] && i != this.index) { this.distances[i] = findDistance(this.x, this.y, circle[i].x, circle[i].y); if (this.distances[i] < maxDistance) { this.angles[i] = findAngle(this.x, this.y, circle[i].x, circle[i].y); this.thetas[i] = (-(this.angles[i] * PI)) / 180.0; this.cxv += cos(this.thetas[i]) * (circle[i].r / 8.0); this.cyv += sin(this.thetas[i]) * (circle[i].r / 8.0); this.numConnections += 1; } else { this.hasCollided[i] = false; circle[i].hasCollided[this.index] = false; } } } if (this.numConnections > 0) { this.xv += (this.cxv / this.numConnections) / 4.0; this.yv += (this.cyv / this.numConnections) / 4.0; } this.cxv = 0.0; this.cyv = 0.0; this.r = this.numConnections * .85 + 2; } this.applyGravity = function() { this.gAngle = findAngle(this.x, this.y, xMid, yMid); this.gTheta = (-(this.gAngle * PI)) / 180; this.gxv = cos(this.gTheta) * gravity; this.gyv = sin(this.gTheta) * gravity; this.xv += this.gxv; this.yv += this.gyv; } this.move = function() { this.x += this.xv; this.y += this.yv; } this.render = function() { noStroke(); fill(0, 25); ellipse(this.x, this.y, this.r, this.r); fill(0 + this.r * 10, 50); ellipse(this.x, this.y, this.r * .5, this.r * .5); fill(0 + this.r * 10); ellipse(this.x, this.y, this.r * .3, this.r * .3); if (this.numCollisions > 0) { noStroke(); fill(0, 25); ellipse(this.x, this.y, this.r, this.r); fill(0, 55); ellipse(this.x, this.y, this.r * .85, this.r * .85); fill(0); ellipse(this.x, this.y, this.r * .7, this.r * .7); } for (var i = 0; i < totalCircles; i++) { if (this.hasCollided[i] && i < this.index) { this.xd = this.x - circle[i].x; this.yd = this.y - circle[i].y; stroke(0, 150 - this.distances[i] * 2.0); noFill(); beginShape(); vertex(this.x, this.y); vertex(this.x - this.xd * .25 + random(-1.0, 1.0), this.y - this.yd * .25 + random(-1.0, 1.0)); vertex(this.x - this.xd * .5 + random(-3.0, 3.0), this.y - this.yd * .5 + random(-3.0, 3.0)); vertex(this.x - this.xd * .75 + random(-1.0, 1.0), this.y - this.yd * .75 + random(-1.0, 1.0)); vertex(circle[i].x, circle[i].y); endShape(); //line (x, y, circle[i].x, circle[i].y); } } noStroke(); } this.reset = function() { this.numCollisions = 0; this.numConnections = 0; } } function findDistance(x1, y1, x2, y2) { var xd = x1 - x2; var yd = y1 - y2; var td = sqrt(xd * xd + yd * yd); return td; } function findAngle(x1, y1, x2, y2) { var xd = x1 - x2; var yd = y1 - y2; var t = atan2(yd, xd); var a = (180 + (-(180 * t) / PI)); return a; }