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);
};
};