Where ideas percolate and thoughts brew

All Paths Orbit the Same Origin

About This Sketch

A central node radiates paths that appear to lead forward — but every curve bends back toward the same origin. Particles travel each path, fading in at the source and disappearing at the end, only to begin again. No path escapes the gravitational pull of where it started.

This accompanies the post "Why Most Mentorship Fails," visualizing how advice shaped by a mentor's biography tends to describe their world rather than chart yours.

Algorithm

A central mentor node emits seven bezier curve paths that fan outward then curve back near their origin — appearing to lead forward but returning to the mentor's own neighborhood. Small mentee particles travel along each path, fading in at the source and fading out at the end. The paths never escape the gravitational pull of the central node; they describe the mentor's world, not an open future.

Pseudocode

SETUP:
  Place mentor node at left-center of canvas
  Generate 7 bezier paths fanning from mentor
  Each path curves outward then pulls back near origin
  Spawn one particle per path at random start positions

DRAW:
  Clear background
  FOR each path:
    Draw faint bezier curve
    Draw arrowhead at endpoint
  Draw mentor node (glowing center)
  FOR each particle:
    Advance t along bezier path
    Reset to 0 when t > 1
    Compute bezier position
    Draw particle with sin-based fade (bright in middle, dim at ends)
  Draw caption

Source Code

let mentorX, mentorY;
let paths = [];
let numPaths = 7;
let time = 0;

function bezierPoint(x0, cx1, cx2, x3, t) {
    let mt = 1 - t;
    return mt*mt*mt*x0 + 3*mt*mt*t*cx1 + 3*mt*t*t*cx2 + t*t*t*x3;
}

let sketch = function(p) {
    let particles = [];

    p.setup = function() {
        p.createCanvas(400, 300);
        mentorX = p.width * 0.35;
        mentorY = p.height * 0.5;

        for (let i = 0; i < numPaths; i++) {
            let angle = p.map(i, 0, numPaths, -p.PI * 0.6, p.PI * 0.6);
            let len = p.random(90, 150);
            let cpAngle = angle * 0.5;
            let cpLen = len * 0.8;
            paths.push({
                x0: mentorX, y0: mentorY,
                x3: mentorX + p.cos(angle) * len * 0.4 + p.random(20, 60),
                y3: mentorY + p.sin(angle) * len * 0.3,
                cx1: mentorX + p.cos(angle) * len * 0.6,
                cy1: mentorY + p.sin(angle) * len,
                cx2: mentorX + p.cos(cpAngle) * cpLen,
                cy2: mentorY + p.sin(cpAngle) * cpLen * 0.5,
                speed: p.random(0.003, 0.006)
            });
        }

        for (let i = 0; i < numPaths; i++) {
            particles.push({ pathIndex: i, t: p.random(1), speed: paths[i].speed });
        }
    };

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

        for (let path of paths) {
            p.noFill();
            p.stroke(colors.dim[0], colors.dim[1], colors.dim[2], 100);
            p.strokeWeight(1);
            p.beginShape();
            for (let t = 0; t <= 1; t += 0.02) {
                let x = bezierPoint(path.x0, path.cx1, path.cx2, path.x3, t);
                let y = bezierPoint(path.y0, path.cy1, path.cy2, path.y3, t);
                p.vertex(x, y);
            }
            p.endShape();

            let t1 = 0.97, t2 = 1.0;
            let ax = bezierPoint(path.x0, path.cx1, path.cx2, path.x3, t2);
            let ay = bezierPoint(path.y0, path.cy1, path.cy2, path.y3, t2);
            let bx = bezierPoint(path.x0, path.cx1, path.cx2, path.x3, t1);
            let by = bezierPoint(path.y0, path.cy1, path.cy2, path.y3, t1);
            let angle = p.atan2(ay - by, ax - bx);
            p.stroke(colors.dim[0], colors.dim[1], colors.dim[2], 80);
            p.strokeWeight(1);
            p.line(ax, ay, ax - 6 * p.cos(angle - 0.4), ay - 6 * p.sin(angle - 0.4));
            p.line(ax, ay, ax - 6 * p.cos(angle + 0.4), ay - 6 * p.sin(angle + 0.4));
        }

        p.noStroke();
        p.fill(colors.accent2[0], colors.accent2[1], colors.accent2[2], 50);
        p.ellipse(mentorX, mentorY, 32, 32);
        p.fill(colors.accent2[0], colors.accent2[1], colors.accent2[2], 200);
        p.ellipse(mentorX, mentorY, 14, 14);

        for (let particle of particles) {
            let path = paths[particle.pathIndex];
            particle.t += particle.speed;
            if (particle.t > 1.0) particle.t = 0.0;
            let x = bezierPoint(path.x0, path.cx1, path.cx2, path.x3, particle.t);
            let y = bezierPoint(path.y0, path.cy1, path.cy2, path.y3, particle.t);
            let alpha = p.sin(particle.t * p.PI) * 200;
            p.noStroke();
            p.fill(colors.accent1[0], colors.accent1[1], colors.accent1[2], alpha);
            p.ellipse(x, y, 6, 6);
        }

        p.fill(colors.accent3[0], colors.accent3[1], colors.accent3[2], 140);
        p.noStroke();
        p.textSize(9);
        p.textAlign(p.CENTER);
        p.text("all paths orbit the same origin", p.width / 2, p.height - 12);
    };
};