Energy on a Track
A ball follows a sinusoidal track while KE / PE bars show the energy trade-off.
Paste into main.js in the editor, then click Restart.
Complete example
javascript
const groundY = h - 80;
const trackAmp = 60;
const trackFreq = 0.015;
function trackY(x) {
return groundY - trackAmp * Math.sin(trackFreq * x);
}
if (!state.init) {
state.init = true;
state.x = 50;
state.vx = 120;
state.trail = [];
}
// Simplified slope-driven acceleration along x
const slopeTerm = trackAmp * trackFreq * Math.cos(trackFreq * state.x);
const g = 300;
state.vx += g * slopeTerm * slopeTerm * dt * 0.001;
state.x += state.vx * dt;
state.trail.push({ x: state.x, y: trackY(state.x) });
if (state.trail.length > 300) state.trail.shift();
const py = trackY(state.x);
const KE = 0.5 * state.vx * state.vx * 0.01;
const PE = (groundY - py) * 0.5;
const total = KE + PE;
const trailLines = [];
for (let i = 1; i < state.trail.length; i++) {
trailLines.push({
x1: state.trail[i - 1].x, y1: state.trail[i - 1].y,
x2: state.trail[i].x, y2: state.trail[i].y,
color: 'rgba(139,92,246,' + (i / state.trail.length * 0.5) + ')',
width: 1.5,
});
}
const trackLines = [];
for (let x = 0; x < w; x += 4) {
trackLines.push({
x1: x, y1: trackY(x), x2: x + 4, y2: trackY(x + 4),
color: 'rgba(255,255,255,0.12)', width: 1,
});
}
return {
lines: [...trackLines, ...trailLines],
particles: [
{ x: state.x, y: py, r: 10, color: '#8B5CF6', label: 'ball' },
],
bars: [
{ x: 20, y: h - 30 - KE, w: 40, h: KE, color: '#EF4444', label: 'KE' },
{ x: 80, y: h - 30 - PE, w: 40, h: PE, color: '#3B82F6', label: 'PE' },
{ x: 140, y: h - 30 - total, w: 40, h: total, color: '#22C55E', label: 'Total' },
],
text: {
x: state.x.toFixed(0),
vx: state.vx.toFixed(0),
},
};Reading the bars
| Bar | Meaning |
|---|---|
| KE | Grows when the ball is fast (often in valleys) |
| PE | Higher on peaks (larger groundY - py) |
| Total | Rough conserved quantity in this toy model |
Scales are chosen for visibility, not SI units.
TIP
If the track lines array gets large on wide canvases, step x by 6–8 instead of 4, or cache the track polyline in state on init / resize.
Try next
- Wrap
state.xwhen it leaves[0, w] - Compare with the Spring energy bars (true 1D Hooke)
- Derive acceleration from the analytic derivative of
trackYfor a more physical slope model