@@ -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