Physics · Mechanics · common
Damped Pendulum
a sine-shaped pull back to the bottom, minus friction
A swinging weight pulled by sin θ: its period is only 2π√(L/g) for small swings, and damping decays the arc. Grapples, lamps and ropes in games.
The orange bob is the full nonlinear pendulum; the faint cyan ghost follows the small-angle (harmonic) model from the same release angle. At 60° the ghost pulls ahead each swing; drop the release angle below 15° and they move together. Damping shrinks both arcs inside the grey release arc.
What it is
A pendulum is a weight on a rigid rod or taut string. Gravity's component along the arc is −g·sin θ, so the angular equation is θ'' = −(g/L)·sin θ. Only for small angles, where sin θ ≈ θ, does it become a simple harmonic oscillator with period 2π√(L/g) that is independent of amplitude.
Release it from 60° and the real period is about 7% longer than that formula: the restoring force near the top is weaker than the linear model assumes, so a wide swing lags. A damping term −b·θ' (air drag, pivot friction) makes the amplitude shrink by e^(−bt/2).
In games the same equation drives grappling hooks, swinging lamps, chains, hanging signs and rope bridges. Integrate the full sin θ version in a fixed timestep loop; the small-angle shortcut breaks down exactly in the big, dramatic swings players notice.
Key formulas
- Equation of motion
- Small-angle period
- Large-angle correction
θ₀ in radians; within 0.1% up to about 70°.
- Amplitude decay (light damping)
Rules of thumb
- A 1 m pendulum on Earth ticks about once per second each way (T ≈ 2.0 s).
- The period scales with √L: four times the rope length doubles the swing time. Mass does not appear at all.
- Simulate the angle, not x/y, for a rigid rod; for a rope that can go slack, use a Verlet particle with a distance constraint instead.
- Use semi-implicit Euler at 120–240 Hz; explicit Euler makes an undamped pendulum swing higher every cycle.
Implementation
import * as THREE from "three"
export function makePendulum(pivot: THREE.Vector3, length: number, theta0: number) {
let theta = theta0
let omega = 0
return {
/** Fixed-step update; b is damping in 1/s. */
step(dt: number, g = 9.81, b = 0.2) {
omega += (-(g / length) * Math.sin(theta) - b * omega) * dt
theta += omega * dt
},
place(bob: THREE.Object3D) {
bob.position.set(
pivot.x + length * Math.sin(theta),
pivot.y - length * Math.cos(theta),
pivot.z
)
},
push(impulse: number) { omega += impulse / length }, // e.g. on hit
}
}Common mistakes
- Using
θinstead ofsin θfor big swings: the motion looks too regular and too fast. - Integrating with explicit Euler: with zero damping the amplitude grows until the pendulum loops over the top.
- Expressing damping per frame (
omega *= 0.99), which makes the decay rate depend on frame rate. Useexp(−b·dt)or the−b·ωterm. - Mixing degrees and radians in
Math.sin.
Prompt
Implement a damped pendulum for a Three.js scene in TypeScript. State: angle θ (radians) and angular velocity ω. Each fixed 1/240 s step apply semi-implicit Euler to θ'' = −(g/L)·sin θ − b·θ'. Expose step(dt), reset(θ0), applyImpulse(j) and bobPosition(out: THREE.Vector3) relative to a pivot. Handle θ0 up to 179° and L > 0 validation. Write unit tests: for θ0 = 0.01 rad and b = 0 the measured period matches 2π√(L/g) within 0.5%, and for θ0 = 60° it is about 7% longer.
Related
Further reading
Updated 2026-11-01