“Pulley Physics Simulation” is an interactive educational physics engine that demonstrates the principles of pulley systems, rope dynamics, and gravitational motion. The application provides real-time visualization of two masses connected by a rope over a pulley, with comprehensive data tracking through Chart.js integration, allowing students and educators to explore mechanical physics concepts through hands-on experimentation.
pullsim/
└── index.html # Complete physics simulation with integrated charting (249 lines)
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
const SCALE = 50; /* pixels per meter conversion */
const G = 9.81; /* acceleration due to gravity (m/s²) */
// Core physics variables
let time = 0; /* simulation time */
let phase = 'connected'; /* rope state: 'connected' or 'broken' */
let yA = initialHeight; /* position of mass A */
let yB = initialHeight; /* position of mass B */
let vA = 0, vB = 0; /* velocities of masses A and B */
let aA = 0, aB = 0; /* accelerations of masses A and B */
// Atwood machine physics for connected masses
let connectedAcc = (massA - massB) / (massA + massB) * G;
// Position calculation using kinematic equations
const displacement = 0.5 * connectedAcc * Math.pow(time, 2);
yA = initialHeight - displacement; /* heavier mass moves down */
yB = initialHeight + displacement; /* lighter mass moves up */
// Velocity calculation
vA = connectedAcc * time * (massA > massB ? 1 : -1);
vB = -vA; /* opposite direction */
// Acceleration assignment
aA = connectedAcc;
aB = -connectedAcc;
if (phase === 'broken') {
// Independent free fall for each mass
aA = G * (massA > massB ? 1 : -1); /* gravitational acceleration */
aB = -aA; /* opposite acceleration */
// Velocity integration
vA += aA * dt;
vB += aB * dt;
// Position integration
yA += vA * dt;
yB += vB * dt;
}
// Ground collision constraint
yA = Math.max(yA, 0);
yB = Math.max(yB, 0);
function draw() {
ctx.clearRect(0, 0, simCanvas.width, simCanvas.height);
// Draw pulley wheel
ctx.beginPath();
ctx.arc(simCanvas.width/2, pulleyY, 20, 0, Math.PI*2);
ctx.fillStyle = '#666';
ctx.fill();
// Draw connecting ropes (only when connected)
if (phase === 'connected') {
ctx.beginPath();
ctx.moveTo(simCanvas.width/2, pulleyY);
ctx.lineTo(simCanvas.width/2 - 100, simCanvas.height - yA * SCALE);
ctx.strokeStyle = '#000';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(simCanvas.width/2, pulleyY);
ctx.lineTo(simCanvas.width/2 + 100, simCanvas.height - yB * SCALE);
ctx.stroke();
}
// Draw masses with distinct colors
ctx.fillStyle = '#ff0000'; /* Red for mass A */
ctx.fillRect(simCanvas.width/2 - 120, simCanvas.height - yA * SCALE - 20, 40, 40);
ctx.fillStyle = '#0000ff'; /* Blue for mass B */
ctx.fillRect(simCanvas.width/2 + 80, simCanvas.height - yB * SCALE - 20, 40, 40);
}
chart = new Chart(chartCtx, {
type: 'line',
data: {
labels: [], /* time labels */
datasets: [
createDataset('A Acceleration', 0),
createDataset('A Velocity', 1),
createDataset('A Distance', 2),
createDataset('B Acceleration', 3),
createDataset('B Velocity', 4),
createDataset('B Distance', 5)
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { title: { display: true, text: 'Time (s)' } }
}
}
});
function updateChart(time) {
chart.data.labels.push(time.toFixed(2));
chart.data.datasets[0].data.push(aA); /* Acceleration A */
chart.data.datasets[1].data.push(vA); /* Velocity A */
chart.data.datasets[2].data.push(yA); /* Position A */
chart.data.datasets[3].data.push(aB); /* Acceleration B */
chart.data.datasets[4].data.push(vB); /* Velocity B */
chart.data.datasets[5].data.push(yB); /* Position B */
// Maintain rolling window of 200 data points
if (chart.data.labels.length > 200) {
chart.data.labels.shift();
chart.data.datasets.forEach(d => d.data.shift());
}
chart.update();
}
function createDataset(label, index) {
return {
label: label,
data: [],
borderColor: colors[index], /* Color-coded for identification */
hidden: false,
tension: 0.1, /* Smooth curve interpolation */
pointRadius: 0 /* No data point markers */
};
}
<input type="number" id="massA" placeholder="Mass A (kg)" step="0.1">
<input type="number" id="massB" placeholder="Mass B (kg)" step="0.1">
<input type="number" id="breakTime" placeholder="Break time (s)" step="0.1">
<input type="number" id="initialHeight" placeholder="Initial height (m)" step="0.1">
<div class="dataset-controls">
<label><input type="checkbox" checked data-index="0"> A Acc</label>
<label><input type="checkbox" checked data-index="1"> A Vel</label>
<label><input type="checkbox" checked data-index="2"> A Dist</label>
<label><input type="checkbox" checked data-index="3"> B Acc</label>
<label><input type="checkbox" checked data-index="4"> B Vel</label>
<label><input type="checkbox" checked data-index="5"> B Dist</label>
</div>
document.querySelectorAll('input[type="checkbox"]').forEach((checkbox, i) => {
checkbox.onchange = () => chart.data.datasets[i].hidden = !checkbox.checked;
});
let animationFrameId;
let lastTime = performance.now();
function animate() {
if (!simRunning) return;
const now = performance.now();
const dt = (now - lastTime) / 1000; /* Delta time in seconds */
lastTime = now;
// Physics update
time += dt;
updatePhysics(dt);
draw();
updateChart(time);
// Simulation end condition
if (yA <= 0 && yB <= 0) {
simRunning = false;
return;
}
animationFrameId = requestAnimationFrame(animate);
}
function startSimulation() {
if (simRunning) {
cancelAnimationFrame(animationFrameId);
simRunning = false;
}
// Initialize physics parameters
const massA = parseFloat(document.getElementById('massA').value) || 1;
const massB = parseFloat(document.getElementById('massB').value) || 1;
const breakTime = parseFloat(document.getElementById('breakTime').value) || 0;
const initialHeight = parseFloat(document.getElementById('initialHeight').value) || 5;
// Start simulation
simRunning = true;
animationFrameId = requestAnimationFrame(animate);
}
if (phase === 'connected') {
if (time >= breakTime) {
phase = 'broken';
// Calculate velocities at moment of rope break
vA = connectedAcc * breakTime * (massA > massB ? 1 : -1);
vB = -vA;
}
}
Connected Phase:
Broken Phase:
The simulation demonstrates classical Atwood machine physics:
Acceleration = (m₁ - m₂) / (m₁ + m₂) × g
Where:
Position: s = ut + ½at² Velocity: v = u + at Acceleration: a = constant (connected phase) or g (broken phase)
The simulation tracks six simultaneous parameters:
const colors = ['#ff0000', '#ff8000', '#ff00ff', '#0000ff', '#00ffff', '#00ff00'];
// Rolling data window prevents memory overflow
if (chart.data.labels.length > 200) {
chart.data.labels.shift();
chart.data.datasets.forEach(d => d.data.shift());
}
The Pulley Physics Simulation successfully combines accurate physics modeling with interactive visualization to create an effective educational tool for mechanics education. The real-time charting capabilities and parameter control system provide students with hands-on experience in experimental physics methodology.
Technical Rating: 8.6/10
The application demonstrates how interactive simulations can effectively bridge theoretical physics concepts with practical experimentation, making complex mechanical systems accessible to students at various educational levels.