Where ideas percolate and thoughts brew

Circle Packing

About This Sketch

Watch as circles populate the canvas and grow organically until they fill the available space. This algorithm mimics natural packing phenomena seen in biological cells, soap bubbles, and crystal formations.

The emergent pattern is never the same twiceโ€”randomness in initial placement leads to unique final configurations. This demonstrates how simple local rules (don't overlap) can create complex global patterns.

Algorithm

Circles are randomly placed and grow until they touch each other or the canvas edge. This creates organic, efficient packing patterns similar to bubble formations. **Key Concepts:** - **Collision Detection**: Checking distance between circle centers - **Constrained Growth**: Circles expand until touching neighbors - **Space Filling**: Emergent tessellation from simple rules - **Iterative Refinement**: Gradual system evolution toward equilibrium **How it works:** 1. Attempt to place new circle at random position 2. Check if it overlaps any existing circles 3. If no overlap, add to collection 4. For each existing circle, check if it can grow 5. Grow if not touching any other circle or boundary 6. Continue until space is efficiently packed 7. Color by age/order to show growth sequence

Pseudocode

SETUP:
  circles = empty list

DRAW (every frame):
  IF circles.count < max:
    new_circle = random position, radius 3

    overlapping = FALSE
    FOR EACH existing circle:
      IF distance < sum of radii:
        overlapping = TRUE

    IF NOT overlapping:
      ADD new_circle to list

  FOR EACH circle:
    can_grow = TRUE

    FOR EACH other circle:
      IF distance < sum of radii + buffer:
        can_grow = FALSE

    IF can_grow AND within bounds:
      radius = radius + 0.5

  FOR EACH circle:
    DRAW circle with color based on age

Source Code

let sketch = function(p) {
    let circles = [];
    let maxCircles = 500;

    p.setup = function() {
        p.createCanvas(400, 300);
        p.colorMode(p.RGB);
    };

    p.draw = function() {
        const colors = getThemeColors();
        p.background(...colors.bg);

        // Try to add new circles
        if (circles.length < maxCircles) {
            let newCircle = {
                x: p.random(p.width),
                y: p.random(p.height),
                r: 3
            };

            let overlapping = false;
            for (let other of circles) {
                let d = p.dist(newCircle.x, newCircle.y, other.x, other.y);
                if (d < newCircle.r + other.r) {
                    overlapping = true;
                    break;
                }
            }

            if (!overlapping) {
                circles.push(newCircle);
            }
        }

        // Grow circles
        for (let circle of circles) {
            let canGrow = true;
            for (let other of circles) {
                if (circle === other) continue;
                let d = p.dist(circle.x, circle.y, other.x, other.y);
                if (d < circle.r + other.r + 2) {
                    canGrow = false;
                    break;
                }
            }

            if (canGrow &&
                circle.x - circle.r > 0 &&
                circle.x + circle.r < p.width &&
                circle.y - circle.r > 0 &&
                circle.y + circle.r < p.height) {
                circle.r += 0.2;
            }
        }

        // Draw circles
        p.noFill();
        for (let i = 0; i < circles.length; i++) {
            let circle = circles[i];
            let t = (i / circles.length);
            let colorChoice = t < 0.33 ? colors.accent1 :
                             t < 0.66 ? colors.accent2 : colors.accent3;

            p.stroke(...colorChoice, 180);
            p.strokeWeight(1.5);
            p.circle(circle.x, circle.y, circle.r * 2);
        }
    };
};