Where ideas percolate and thoughts brew

Positive Thinking Is Making You Fail

About This Sketch

A split visualization accompanying the post "Positive Thinking Is Making You Fail." On the left, particles orbit a glowing goal in eternal ellipses — beautiful, hypnotic, going nowhere. On the right, particles navigate through a series of obstacle barriers, finding each gap and climbing steadily to reach the same kind of goal. The contrast visualizes Gabriele Oettingen's research: positive visualization keeps motivation circling the dream, while mental contrasting — imagining both the goal and the obstacles — moves you through them.

Algorithm

Two contrasting particle systems illustrate the difference between positive thinking and mental contrasting. Left side: Nine particles orbit a glowing goal in elliptical paths at varying speeds and radii. They circle perpetually — drawn to the dream but never landing, never advancing. This is the positive thinking trap: motivation circles the goal without bridging the gap. Right side: Six particles move sequentially through four horizontal barriers, each with a small gap at a different horizontal position. They navigate from the bottom upward, finding each gap in turn before arriving at the goal. Each particle fades in at the start and out at the end, creating a continuous stream of purposeful progress. The visual contrast makes the argument: one system looks beautiful and goes nowhere; the other looks effortful and arrives.

Pseudocode

SETUP:
  Initialize canvas (400x300)

DRAW (every frame):
  Get current theme colors
  Clear background with theme background color
  Draw vertical divider at x=200

  LEFT SIDE (positive thinking):
    Draw glowing goal at center-left with pulsing halo rings
    Draw three faint elliptical orbit paths
    For each of 9 particles:
      Assign to one of three orbit radii
      Calculate angle based on frame count and orbit speed
      Draw particle at orbital position
    Label: "positive thinking"

  RIGHT SIDE (mental contrasting):
    Draw four horizontal barrier lines, each with a gap at different x position
    Draw glowing goal at top-right
    For each of 6 particles (staggered by 0.18 progress offset):
      Calculate progress (0 to 1, looping)
      Map progress to waypoint path through each gap to goal
      Interpolate position between current and next waypoint
      Fade out near loop end for smooth reset
    Label: "mental contrasting"

Source Code

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

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

        let t = p.frameCount * 0.008;

        // Divider
        p.stroke(colors.accent3[0], colors.accent3[1], colors.accent3[2], 60);
        p.strokeWeight(1);
        p.line(200, 20, 200, 282);

        // ---- LEFT SIDE: Positive Thinking ----
        let goalLX = 100, goalLY = 148;

        for (let r = 36; r > 6; r -= 6) {
            p.noStroke();
            p.fill(colors.accent1[0], colors.accent1[1], colors.accent1[2], p.map(r, 6, 36, 0, 28));
            p.ellipse(goalLX, goalLY, r * 2, r * 2);
        }
        p.fill(...colors.accent1);
        p.noStroke();
        p.ellipse(goalLX, goalLY, 12, 12);

        let orbitRadii = [30, 50, 70];
        for (let i = 0; i < orbitRadii.length; i++) {
            p.noFill();
            p.stroke(colors.accent3[0], colors.accent3[1], colors.accent3[2], 18);
            p.strokeWeight(1);
            p.ellipse(goalLX, goalLY, orbitRadii[i] * 2, orbitRadii[i] * 1.15);
        }

        for (let i = 0; i < 9; i++) {
            let orbitIdx = i % 3;
            let r = orbitRadii[orbitIdx];
            let speed = 1.0 - orbitIdx * 0.25;
            let angle = t * speed + (p.TWO_PI / 3) * Math.floor(i / 3);
            let px = goalLX + p.cos(angle) * r;
            let py = goalLY + p.sin(angle) * r * 0.55;
            p.fill(colors.accent2[0], colors.accent2[1], colors.accent2[2], 195);
            p.noStroke();
            p.ellipse(px, py, 5, 5);
        }

        p.noStroke();
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.fill(colors.accent3[0], colors.accent3[1], colors.accent3[2], 140);
        p.text("positive thinking", 100, 290);

        // ---- RIGHT SIDE: Mental Contrasting ----
        let obstacles = [
            { y: 232, gapX: 312, gapW: 28 },
            { y: 186, gapX: 262, gapW: 28 },
            { y: 140, gapX: 322, gapW: 28 },
            { y: 96,  gapX: 272, gapW: 28 },
        ];

        for (let o of obstacles) {
            p.stroke(colors.accent3[0], colors.accent3[1], colors.accent3[2], 140);
            p.strokeWeight(2);
            p.line(210, o.y, o.gapX - o.gapW / 2, o.y);
            p.line(o.gapX + o.gapW / 2, o.y, 392, o.y);
        }

        let goalRX = 300, goalRY = 54;
        for (let r = 24; r > 6; r -= 6) {
            p.noStroke();
            p.fill(colors.accent1[0], colors.accent1[1], colors.accent1[2], p.map(r, 6, 24, 0, 35));
            p.ellipse(goalRX, goalRY, r * 2, r * 2);
        }
        p.fill(...colors.accent1);
        p.noStroke();
        p.ellipse(goalRX, goalRY, 12, 12);

        let waypoints = [
            { x: 300, y: 265 },
            { x: obstacles[0].gapX, y: 232 },
            { x: obstacles[1].gapX, y: 186 },
            { x: obstacles[2].gapX, y: 140 },
            { x: obstacles[3].gapX, y: 96 },
            { x: goalRX, y: goalRY },
        ];

        for (let i = 0; i < 6; i++) {
            let progress = ((p.frameCount * 0.004 + i * 0.18) % 1);
            let segCount = waypoints.length - 1;
            let totalProg = progress * segCount;
            let segIdx = Math.min(Math.floor(totalProg), segCount - 1);
            let segT = totalProg - segIdx;

            let wp1 = waypoints[segIdx];
            let wp2 = waypoints[segIdx + 1];
            let px = p.lerp(wp1.x, wp2.x, segT);
            let py = p.lerp(wp1.y, wp2.y, segT);

            let alpha = progress < 0.88 ? 215 : p.map(progress, 0.88, 1.0, 215, 0);
            p.fill(colors.accent2[0], colors.accent2[1], colors.accent2[2], alpha);
            p.noStroke();
            p.ellipse(px, py, 5, 5);
        }

        p.noStroke();
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.fill(colors.accent3[0], colors.accent3[1], colors.accent3[2], 140);
        p.text("mental contrasting", 300, 290);
    };
};