Skip to content

Commit c368d33

Browse files
fix: auto-scale pendulum arms to fit canvas
1 parent 719e7d0 commit c368d33

2 files changed

Lines changed: 33 additions & 41 deletions

File tree

app/(core)/data/chapters.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,6 @@ const chapters = [
8989
icon: "/icons/threebody.png",
9090
relatedBlogSlug: "physics-behind-three-body-problem",
9191
},
92-
{
93-
id: 12,
94-
name: "Double Pendulum",
95-
desc: "Simulate a double pendulum and explore chaotic motion. Watch how small changes in initial conditions lead to completely different trajectories — a classic example of chaos theory.",
96-
link: "/simulations/DoublePendulum",
97-
tags: [TAGS.ADVANCED, TAGS.DYNAMICS, TAGS.OSCILLATIONS, TAGS.ENERGY],
98-
icon: "/icons/pendulam.png",
99-
},
10092
{
10193
id: 11,
10294
name: "Horizontal Spring",
@@ -105,6 +97,14 @@ const chapters = [
10597
tags: [TAGS.MEDIUM, TAGS.DYNAMICS, TAGS.SPRINGS, TAGS.OSCILLATIONS],
10698
icon: "/icons/spring.png",
10799
},
100+
{
101+
id: 12,
102+
name: "Double Pendulum",
103+
desc: "Simulate a double pendulum and explore chaotic motion. Watch how small changes in initial conditions lead to completely different trajectories — a classic example of chaos theory.",
104+
link: "/simulations/DoublePendulum",
105+
tags: [TAGS.ADVANCED, TAGS.DYNAMICS, TAGS.OSCILLATIONS, TAGS.ENERGY],
106+
icon: "/icons/pendulam.png",
107+
},
108108
{
109109
id: 0,
110110
name: "Browser Performance & Stress Test",

simulations/DoublePendulum.jsx

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,13 @@ class DoublePendulumSystem {
4545
this.angVel2 = angVel2;
4646
}
4747

48-
/**
49-
* Equations of motion for double pendulum (Lagrangian mechanics)
50-
*/
5148
static derivatives(state, m1, m2, L1, L2, g, damping) {
5249
const { angle1, angle2, angVel1, angVel2 } = state;
5350
const d = angle1 - angle2;
5451

5552
const denom1 = 2 * m1 + m2 - m2 * Math.cos(2 * d);
5653
const denom2 = (L2 / L1) * denom1;
5754

58-
// Angular acceleration for pendulum 1
5955
const acc1 =
6056
(-g * (2 * m1 + m2) * Math.sin(angle1) -
6157
m2 * g * Math.sin(angle1 - 2 * angle2) -
@@ -66,7 +62,6 @@ class DoublePendulumSystem {
6662
damping * angVel1) /
6763
(L1 * denom1);
6864

69-
// Angular acceleration for pendulum 2
7065
const acc2 =
7166
(2 *
7267
Math.sin(d) *
@@ -84,9 +79,6 @@ class DoublePendulumSystem {
8479
};
8580
}
8681

87-
/**
88-
* RK4 integration step
89-
*/
9082
static rk4Step(state, m1, m2, L1, L2, g, damping, dt) {
9183
const deriv = (s) =>
9284
DoublePendulumSystem.derivatives(s, m1, m2, L1, L2, g, damping);
@@ -115,9 +107,6 @@ class DoublePendulumSystem {
115107
);
116108
}
117109

118-
/**
119-
* Get positions of both bobs given anchor in meters
120-
*/
121110
getPositions(anchorX, anchorY, L1, L2) {
122111
const x1 = anchorX + L1 * Math.sin(this.angle1);
123112
const y1 = anchorY - L1 * Math.cos(this.angle1);
@@ -126,9 +115,6 @@ class DoublePendulumSystem {
126115
return { x1, y1, x2, y2 };
127116
}
128117

129-
/**
130-
* Compute total kinetic energy
131-
*/
132118
getKineticEnergy(m1, m2, L1, L2) {
133119
const { angle1, angle2, angVel1, angVel2 } = this;
134120
const v1sq = (L1 * angVel1) ** 2;
@@ -139,13 +125,9 @@ class DoublePendulumSystem {
139125
return 0.5 * m1 * v1sq + 0.5 * m2 * v2sq;
140126
}
141127

142-
/**
143-
* Compute total potential energy (relative to anchor)
144-
*/
145128
getPotentialEnergy(m1, m2, L1, L2, g, anchorY) {
146129
const y1 = anchorY - L1 * Math.cos(this.angle1);
147130
const y2 = y1 - L2 * Math.cos(this.angle2);
148-
// Negate because physics y is flipped vs screen
149131
return -m1 * g * y1 - m2 * g * y2;
150132
}
151133
}
@@ -162,6 +144,7 @@ export default function DoublePendulum() {
162144

163145
const systemRef = useRef(null);
164146
const anchorRef = useRef({ x: 0, y: 0 });
147+
const scaledLengthsRef = useRef({ L1: 1, L2: 1 });
165148
const trailRef = useRef([]);
166149

167150
const { simData, updateSimInfo } = useSimInfo();
@@ -181,9 +164,23 @@ export default function DoublePendulum() {
181164
const w = p.width;
182165
const h = p.height;
183166

167+
// Anchor near the top-center
168+
const anchorYpx = h * 0.55;
184169
anchorRef.current = {
185170
x: toMeters(w / 2),
186-
y: toMeters(h * 0.25),
171+
y: toMeters(anchorYpx),
172+
};
173+
174+
// Scale arms so L1+L2 fits within 80% of the remaining canvas height
175+
const L1 = inputsRef.current.length1;
176+
const L2 = inputsRef.current.length2;
177+
const totalPx = toPixels(L1 + L2);
178+
const maxPx = (h - anchorYpx) * 0.8;
179+
const scale = totalPx > maxPx ? maxPx / totalPx : 1;
180+
181+
scaledLengthsRef.current = {
182+
L1: L1 * scale,
183+
L2: L2 * scale,
187184
};
188185

189186
const angle1 = (inputsRef.current.initialAngle1 * Math.PI) / 180;
@@ -210,8 +207,8 @@ export default function DoublePendulum() {
210207

211208
const dt = computeDelta(p);
212209
const inp = inputsRef.current;
210+
const { L1, L2 } = scaledLengthsRef.current;
213211

214-
// Physics: multiple sub-steps for stability
215212
if (dt > 0) {
216213
const subSteps = 8;
217214
const subDt = dt / subSteps;
@@ -220,8 +217,8 @@ export default function DoublePendulum() {
220217
systemRef.current,
221218
inp.mass1,
222219
inp.mass2,
223-
inp.length1,
224-
inp.length2,
220+
L1,
221+
L2,
225222
inp.gravity,
226223
inp.damping,
227224
subDt
@@ -232,11 +229,10 @@ export default function DoublePendulum() {
232229
const { x1, y1, x2, y2 } = systemRef.current.getPositions(
233230
anchorRef.current.x,
234231
anchorRef.current.y,
235-
inp.length1,
236-
inp.length2
232+
L1,
233+
L2
237234
);
238235

239-
// Trail: record position of bob 2 (in screen coords)
240236
if (inp.trailEnabled) {
241237
trailRef.current.push({
242238
x: toPixels(x2),
@@ -251,18 +247,17 @@ export default function DoublePendulum() {
251247

252248
renderScene(p, x1, y1, x2, y2);
253249

254-
// Energies
255250
const ke = systemRef.current.getKineticEnergy(
256251
inp.mass1,
257252
inp.mass2,
258-
inp.length1,
259-
inp.length2
253+
L1,
254+
L2
260255
);
261256
const pe = systemRef.current.getPotentialEnergy(
262257
inp.mass1,
263258
inp.mass2,
264-
inp.length1,
265-
inp.length2,
259+
L1,
260+
L2,
266261
inp.gravity,
267262
anchorRef.current.y
268263
);
@@ -287,7 +282,6 @@ export default function DoublePendulum() {
287282
const [r, g, b] = Array.isArray(bg) ? bg : [20, 20, 30];
288283
const inp = inputsRef.current;
289284

290-
// Trail layer
291285
if (!inp.trailEnabled) {
292286
trailLayer.background(r, g, b);
293287
} else {
@@ -296,7 +290,6 @@ export default function DoublePendulum() {
296290
trailLayer.rect(0, 0, trailLayer.width, trailLayer.height);
297291
}
298292

299-
// Draw trail on trail layer
300293
const trail = trailRef.current;
301294
if (trail.length > 1) {
302295
for (let i = 1; i < trail.length; i++) {
@@ -317,7 +310,6 @@ export default function DoublePendulum() {
317310
p.clear();
318311
p.image(trailLayer, 0, 0);
319312

320-
// Screen coords
321313
const ax = toPixels(anchorRef.current.x);
322314
const ay = physicsYToScreenY(anchorRef.current.y);
323315
const sx1 = toPixels(x1);

0 commit comments

Comments
 (0)