xxxxxxxxxx
//https://www.openprocessing.org/sketch/147268
/* PARTICLE PROPERTIES */
final Boolean COLLISIONS = true;
final Boolean EXPLOSIONS = true;
final int MAX_PARTICLES = 75;
final int SIZE_MULTIPIER = 8;
final float MAX_SPEED = 1.5;
final float MAX_MASS = 2.5;
final float STARTING_MASS = 0.25;
final float BOUNCE = -0.5;
final float EXPLOSION_FORCE = 1.0;
/* SIMULATION PROPERTIES */
final float GRAVITY = 0.15;
final int EXPLOSION_DELAY = 2000; // ms (2000 = 2 seconds)
ArrayList<Particle> particles = new ArrayList<Particle>();
Particle attractor = new Particle();
Boolean dragging = false;
int backgroundMode = 1;
int time;
void setup()
{
for (int x = 0; x < MAX_PARTICLES; x++) { // Fill arraylist of active particles
particles.add(new Particle());
}
size(600, 500, P2D);
noStroke();
time = millis();
attractor.velocity = new PVector(); // Zero velocity of attractor
attractor.mass = 20.0;
}
void draw()
{
//fill(50);
//text("Flow (V1.0)", 10, 20);
// Draw background
switch (backgroundMode) {
case 1:
// Motion blur
fill(255, 20);
rect(0, 0, width, height);
fill(0);
break;
case 2:
// Clear
background(255);
break;
case 3:
// No background
break;
}
// For every particle
for (Particle p1 : particles) {
if (p1.collided) { continue; } // If particle has collided, dont act on it
// Collision detection (still a bit large for bigger particles)
if (COLLISIONS) {
for (Particle p2 : particles) {
if (p1 != p2 && p2.collided == false) {
float distance = PVector.dist(p1.pos, p2.pos);
if (distance >= 0 && distance <= (p1.mass * SIZE_MULTIPIER)) { // Distance check
if ((p1.mass + p2.mass) <= MAX_MASS) { // Size check
p2.collided = true;
p1.collide(p2);
}
}
}
}
}
if (!dragging) {
// Apply forces to active particles
for (Particle p2 : particles) {
if (p1 != p2 && p2.collided == false) {
PVector force = p1.attract(p2);
p2.applyForce(force);
}
}
} else {
PVector force = attractor.attract(p1);
p1.applyForce(force);
}
// Move and display particle
p1.move();
p1.display();
}
// If mouse is being dragged pull all particles towards mouse
if (dragging) {
for (Particle part : particles) {
PVector force = attractor.attract(part);
//part.applyForce(force);
}
}
// Explode large particles every x milliseconds
if (EXPLOSIONS) {
if (millis() > time + EXPLOSION_DELAY) {
for (Particle part : particles) {
if (part.mass > (STARTING_MASS * 4) && !part.collided) {
part.explode();
}
}
time = millis();
}
}
}
class Particle
{
ColourGenerator colour = new ColourGenerator();
PVector acceleration = new PVector(0, 0);
PVector velocity = new PVector(random(-MAX_SPEED, MAX_SPEED), random(-MAX_SPEED, MAX_SPEED)); // Random vector;
PVector pos;
float mass = STARTING_MASS;
Boolean collided = false;
Particle()
{
pos = new PVector(random(width), random(height)); // Start at random location
}
void move()
{
// Apply our acceleration and speed vector
velocity.add(acceleration);
pos.add(velocity);
acceleration.mult(0);
// Boundary check
if (pos.x < 0)
{
pos.x = 0;
velocity.x *= BOUNCE;
}
else if (pos.x > width)
{
pos.x = width;
velocity.x *= BOUNCE;
}
if (pos.y < 0)
{
pos.y = 0;
velocity.y *= BOUNCE;
}
else if (pos.y > height)
{
pos.y = height;
velocity.y *= BOUNCE;
}
}
PVector attract(Particle p)
{
PVector force = PVector.sub(this.pos, p.pos); // Calculate distance for force
float distance = force.mag(); // Distance between particles
distance = constrain(distance, 5.0, 25.0); // Constrain to avoid extreme results
force.normalize(); // Normalize, get direction, distance doesnt matter
float strength = (GRAVITY * this.mass * p.mass) / (distance * distance); // Calculate strength of gravitational force
force.mult(strength); // Apply force to our vector
return force;
}
void explode()
{
float numParticles = this.mass / STARTING_MASS; // Split the mass based on starting mass (work out num of particles to explode)
int x = 1; // Skip first particle (it's this)
for (Particle part : particles) { // Find 'collided' particles to use in explosion
if (x >= round(numParticles)) {
break;
}
if (part.collided) { // Found particle to repurpose
part.collided = false;
part.pos = new PVector(pos.x, pos.y);
part.velocity = new PVector(random(-EXPLOSION_FORCE, EXPLOSION_FORCE), random(-EXPLOSION_FORCE, EXPLOSION_FORCE));
part.mass = STARTING_MASS;
// Move particles to avoid false positive hit detection
for (int y = 0; y < 20; y++) {
part.move();
}
x++;
}
}
// Reset mass
this.mass = STARTING_MASS;
}
void collide(Particle part)
{
this.mass += part.mass;
}
void applyForce(PVector force)
{
PVector f = PVector.div(force, mass);
acceleration.add(f);
}
void display()
{
colour.update();
noFill();
fill(colour.R, colour.G, colour.B);
ellipse(pos.x, pos.y, this.mass * SIZE_MULTIPIER, this.mass * SIZE_MULTIPIER);
}
}
class ColourGenerator
{
final static float MIN_SPEED = 0.2;//7;
final static float MAX_SPEED = 0.7;//1.5;
float R, G, B;
float Rspeed, Gspeed, Bspeed;
ColourGenerator()
{
init();
}
public void init()
{
// Random starting colour
R = random(255);
G = random(255);
B = random(255);
// Random transition
Rspeed = (random(1) > 0.5 ? 1 : -1) * random(MIN_SPEED, MAX_SPEED);
Gspeed = (random(1) > 0.5 ? 1 : -1) * random(MIN_SPEED, MAX_SPEED);
Bspeed = (random(1) > 0.5 ? 1 : -1) * random(MIN_SPEED, MAX_SPEED);
}
public void update()
{
// Random transition (keep within RGB colour range)
Rspeed = ((R += Rspeed) > 255 || (R < 0)) ? -Rspeed : Rspeed;
Gspeed = ((G += Gspeed) > 255 || (G < 0)) ? -Gspeed : Gspeed;
Bspeed = ((B += Bspeed) > 255 || (B < 0)) ? -Bspeed : Bspeed;
}
}
void mousePressed()
{
dragging = true;
attractor.pos.x = mouseX;
attractor.pos.y = mouseY;
}
void mouseDragged()
{
dragging = true;
attractor.pos.x = mouseX;
attractor.pos.y = mouseY;
}
void mouseReleased()
{
dragging = false;
}
void keyPressed()
{
if (key == ENTER) {
backgroundMode++;
if (backgroundMode == 3) {
backgroundMode = 0;
}
}
}