import ddf.minim.*;
import ddf.minim.signals.*;
static final float VOLUME = 0.05;
static final float BRIGHTNESS = 0.1;
static float BOULE_RADIUS = 24;
static float BOULE_SPEED = 120;
static float BOULE_ATTEN = 0.05;
static final float F = 110; // Fundamental frequency
int[] multis = { 3, 4, 6, 8, 9, 12 };
Boule[] boules = new Boule[multis.length];
float t; // Time of last update, in seconds
Minim minim;
AudioOutput out;
void setup() {
size(640, 480);
minim = new Minim(this);
out = minim.getLineOut(Minim.STEREO);
for (int i = 0; i < boules.length; i++) {
boules[i] = new Boule(random(width), random(height), multis[i]);
boules[i].setVelocity(BOULE_SPEED, random(TWO_PI));
boules[i].osc = new SineWave(F * multis[i], 0, out.sampleRate());
out.addSignal(boules[i].osc);
}
t = millis() * 1e-3;
}
void draw() {
// Apply motion update
float tt = millis() * 1e-3;
for (int i = 0; i < boules.length; i++)
boules[i].update(tt - t);
t = tt;
// Check for collisions
for (int i = 0; i < boules.length; i++) {
for (int j = i + 1; j < boules.length; j++) {
if (boules[i].collidesWith(boules[j])) {
// Calculate offset
float dx = boules[j].posX - boules[i].posX;
float dy = boules[j].posY - boules[i].posY;
// Calculate distance and angle
float s = sqrt(dx * dx + dy * dy);
float theta = atan2(dy, dx);
// Calculate speed factors
float p = boules[j].speedFactor(s, theta) - boules[i].speedFactor(s, theta);
// Update speeds
boules[i].velX += p * dx;
boules[i].velY += p * dy;
boules[j].velX -= p * dx;
boules[j].velY -= p * dy;
// Adjust strength
boules[i].strength += abs(p);
boules[j].strength += abs(p);
// Shift first object away from second, so they don't keep colliding
float q = (boules[i].radius + boules[j].radius) / s - 1;
boules[i].posX -= q * dx;
boules[i].posY -= q * dy;
}
}
}
// Draw display
loadPixels();
for (int y = 0; y < height; y++) {
int y_offset = y * width;
for (int x = 0; x < width; x++) {
float vis = 1;
float value = 0;
for (int i = 0; i < boules.length; i++) {
float r = dist(x, y, boules[i].posX, boules[i].posY);
value += BRIGHTNESS * sq(boules[i].radius / r) * boules[i].strength;
float z = r - boules[i].radius;
vis *= (z < 0) ? 0 : (z < 1) ? z : 1;
}
pixels[y_offset + x] = color(0xff * vis * value);
}
}
updatePixels();
}
void stop() {
out.close();
minim.stop();
super.stop();
}
class Boule {
float posX, posY;
float velX, velY;
float radius = BOULE_RADIUS;
float strength = 0;
int multi;
Oscillator osc;
Boule(float x, float y, int multi) {
this.posX = x;
this.posY = y;
this.multi = multi;
}
void setVelocity(float speed, float direction) {
velX = speed * cos(direction);
velY = speed * sin(direction);
}
float getSpeed() {
return sqrt(velX * velX + velY * velY);
}
float getDirection() {
return atan2(velY, velX);
}
float getTransparency(float r) {
return (r < radius) ? 0 : (r < radius + 1) ? r - radius : 1;
}
float speedFactor(float s, float theta) {
return cos(getDirection() - theta) * getSpeed() / s;
}
boolean collidesWith(Boule other) {
return dist(this.posX, this.posY, other.posX, other.posY) <= this.radius + other.radius;
}
void update(float dt) {
// Update position
posX += velX * dt;
posY += velY * dt;
// Check for collision with left/right edge
if (posX < radius) {
posX = 2 * radius - posX;
velX = -velX;
}
else if (posX >= width - radius) {
posX = 2 * (width - radius) - posX;
velX = -velX;
}
// Check for collision with top/bottom edge
if (posY < radius) {
posY = 2 * radius - posY;
velY = -velY;
}
else if (posY >= height - radius) {
posY = 2 * (height - radius) - posY;
velY = -velY;
}
// Recalculate strength
float decay = BOULE_ATTEN * strength * exp(-dt);
if (strength > decay)
strength -= decay;
else
strength = 0;
// Update oscillator amplitude
osc.setAmp(VOLUME * strength / multi);
}
}