Fake circle packing

Moving circles around until they stop overlapping.

Log in to post a comment.

// LL 2021

const particle_count = 250; // min=1 max=1000 step=1
const iterations = 100; // min=0 max=1000 step=1
const collision_loop = 101 // min=1 max=1000 step=10
const min_radius = 0.06 // min=0.001 max=0.1 step=0.001
const max_radius = 0.7 // min=0.01 max=1.0 step=0.01
const min_bias = 6 // min=1 max=50 step=1
const fill_step = 0.75; // min=0.05 max=1 step=0.05
const seed = 0; // min=0 max=100 step=1
const scale = 100; // min=10 max=1000 step=10

Canvas.setpenopacity(1);

const turtle = new Turtle();

class Particle {
    constructor(x, y, r) {
        this.x = x;
        this.y = y;
        this.r = r;
    }
    
    update() {
        const factor = 0.99;
        this.x *= factor;
        this.y *= factor;
    }
}

function resolve_collisions() {
    for (var r=0; r<collision_loop; r++) {
        for (var i=0; i<particle_count; i++) {
            for (var j=i+1; j<particle_count; j++) {
                var dx = particles[i].x - particles[j].x;
                var dy = particles[i].y - particles[j].y;
                var dist = Math.sqrt(dx*dx + dy*dy);
                const min_dist = particles[i].r + particles[j].r;
                while (dist < min_dist) {
                    const factor = 0.001;
                    particles[i].x += dx * factor;
                    particles[i].y += dy * factor;
                    particles[j].x -= dx * factor;
                    particles[j].y -= dy * factor;
                    dx = particles[i].x - particles[j].x;
                    dy = particles[i].y - particles[j].y;
                    dist = Math.sqrt(dx*dx + dy*dy);
                }
            }
        }
    }
}

function random_particle() {
    const x = rng.nextFloat() * 2 - 1;
    const y = rng.nextFloat() * 2 - 1;
    const r = Math.pow(rng.nextFloat(), min_bias) * (max_radius - min_radius) + min_radius;
    return new Particle(x, y, r);
}

var particles = null;

function update_particles(loop_count) {
    resolve_collisions();
    for (var i=0; i<loop_count; i++) {
        for (var p=0; p<particle_count; p++) {
            particles[p].update();
        }
        resolve_collisions();
    }
}

function walk(i, t) {
    if (particles == null) {
        particles = Array.from({length: particle_count}, (_, id) => (random_particle()));
        update_particles(t * iterations);
    }
    
    turtle.jump(particles[i].x * scale, particles[i].y * scale - particles[i].r * scale);
    turtle.circle(particles[i].r * scale);
    for (var r = particles[i].r * scale - fill_step; r > 0 && fill_step < 1; r -= fill_step) {
        turtle.jump(particles[i].x * scale, particles[i].y * scale - r);
        turtle.circle(r)
    }
    return (i+1) < particle_count;
}

///////

//// Random with seed

function RNG(_seed) {
  // LCG using GCC's constants
  this.m = 0x80000000; // 2**31;
  this.a = 1103515245;
  this.c = 12345;

  this.state = _seed ? _seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextFloat = function() {
  // returns in range [0,1]
  this.state = (this.a * this.state + this.c) % this.m;
  return this.state / (this.m - 1);
}
var rng = new RNG(seed);