/* 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 13 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 = 900; // x dimension of applet var yStage = 500; // 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; // BOOLEANS FOR TESTING PURPOSES var clearScreen = false; var timer = 0; var timerPause = 750; var timerMax = 775; // ******************************************************************************** // SETUP FUNCTION // ******************************************************************************** function setup() { createCanvas(900, 500); bgColor = 20; background(bgColor); frameRate(30); createCircles(); } // ******************************************************************************** // MAIN LOOP FUNCTION // ******************************************************************************** function draw() { if (clearScreen) { background(bgColor); } dealWithTimer(); if (timer < timerPause) { tellCirclesToBehave(); } } function createCircles() { gravity = random(0.005, 0.1); xGrav = xMid + random(-50, 50); yGrav = yMid + random(-50, 50); xGravOffset = random(1.0, 1.24); maxDistance = random(75, 150); angleOffset = random(360); initRadius = random(130, 140); for (var i = 0; i < totalCircles; i++) { var initAngle = i * 3.6 + angleOffset; 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 dealWithTimer(){ if (timer > timerMax){ timer = 0; background(bgColor); createCircles(); } timer++; } function Circle(xSent, ySent, xvSent, yvSent, indexSent) { this.index = indexSent; // Circle global ID this.x = xSent; // Circle x position this.y = ySent; // Circle y position this.r = 4; // Circle radius 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 = 0; // Number of collisions in one frame this.numConnections = 0; // Total number of collisions this.xd = 0; // Distance to target along x axis this.yd = 0; // Distance to target along y axis this.d = 0; // Distance to target this.alphaVar = random(35); // Late addition variable for alpha modification this.cAngle = 0; // Angle of collision in degrees this.cTheta = 0; // Angle of collision in radians this.cxv = 0; // Collision velocity along x axis this.cyv = 0; // Collision velocity along y axis this.gAngle = 0; // Angle to gravity center in degrees this.gTheta = 0; // Angle to gravity center in radians this.gxv = 0; // Gravity velocity along x axis this.gyv = 0; // Gravity velocity along y axis this.behave = function() { this.move(); this.areWeClose(); this.areWeColliding(); // This is the current area of trouble 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, xGrav, yGrav); this.gTheta = (-(this.gAngle * PI)) / 180; this.gxv = cos(this.gTheta) * gravity * xGravOffset; 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() { noFill(); this.d = findDistance(this.x, this.y, xMid, yMid); if (this.d > initRadius){ stroke(255 - this.d, this.d - this.alphaVar); } else { stroke(this.d, this.d - this.alphaVar); } point(this.x,this.y); noStroke(); if (this.numCollisions > 0){ fill(255, 4); ellipse(this.x, this.y, this.r * 5.0, this.r * 5.0 + 5); ellipse(this.x, this.y, this.r * 3.0, this.r * 3.0 + 5); if (this.d > initRadius){ fill(255,255); } else { fill(0,255); } ellipse(this.x, this.y, this.r * 0.5, this.r * 0.5); } for (var i = 0; i < totalCircles; i++){ if (this.hasCollided[i] && i < this.index){ stroke(abs(this.angles[i] - 180)*1.3, 5); line (this.x, this.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; }