<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Differential Equations: Gravity Simulator</title>
<style>
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
canvas { display: block; }
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 5px;
max-width: 300px;
}
#controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 10px;
color: white;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
button {
padding: 8px 15px;
background: #4CAF50;
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
}
button:hover { background: #45a049; }
.slider-container {
margin: 5px 0;
}
.slider-container label {
display: inline-block;
width: 120px;
}
#planet-info {
margin-top: 10px;
}
#graph-container {
position: absolute;
top: 10px;
right: 10px;
width: 300px;
height: 200px;
background: rgba(0,0,0,0.5);
}
</style>
</head>
<body>
<div id="ui">
<h2>Gravity Simulator: Solving d²r/dt² = GM/r²</h2>
<p>Click to add planets. Adjust mass/velocity to change orbits.</p>
<div id="planet-info"></div>
</div>
<div id="controls">
<button id="add-planet">🪐 Add Planet</button>
<button id="pause-btn">⏸️ Pause</button>
<button id="step-btn">⏯️ Step Frame</button>
<button id="reset-btn">🔄 Reset</button>
<div class="slider-container">
<label for="mass-slider">Mass:</label>
<input type="range" id="mass-slider" min="1" max="100" value="10">
<span id="mass-value">10</span>
</div>
<div class="slider-container">
<label for="speed-slider">Initial Speed:</label>
<input type="range" id="speed-slider" min="0" max="5" step="0.1" value="1">
<span id="speed-value">1</span>
</div>
</div>
<div id="graph-container"></div>
<!-- Three.js & Plotly for graphs -->
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000033);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 20, 30);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Central star (fixed gravity source)
const starGeometry = new THREE.SphereGeometry(2, 32, 32);
const starMaterial = new THREE.MeshPhongMaterial({
color: 0xFFFF00,
emissive: 0xFFFF00,
emissiveIntensity: 0.5
});
const star = new THREE.Mesh(starGeometry, starMaterial);
scene.add(star);
// Physics variables
const G = 6.67430e-11; // Gravitational constant
const planets = [];
let isPaused = false;
let currentMass = 10;
let currentSpeed = 1;
// Planet class (solves differential equations)
class Planet {
constructor(mass, position, velocity) {
this.mass = mass;
this.position = position.clone();
this.velocity = velocity.clone();
this.radius = Math.cbrt(mass) * 0.3;
// Create 3D mesh
this.geometry = new THREE.SphereGeometry(this.radius, 16, 16);
this.material = new THREE.MeshPhongMaterial({
color: new THREE.Color(Math.random() * 0xFFFFFF)
});
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.position.copy(position);
scene.add(this.mesh);
// Store trajectory points
this.trajectory = [];
this.trajectoryLine = null;
}
// Solve F = ma (d²r/dt² = -GM/r²)
update(dt) {
const r = this.position.length();
const acceleration = this.position.clone()
.normalize()
.multiplyScalar(-G * 1000 / (r * r)); // Scaled for visualization
this.velocity.add(acceleration.multiplyScalar(dt));
this.position.add(this.velocity.clone().multiplyScalar(dt));
this.mesh.position.copy(this.position);
// Update trajectory
this.trajectory.push(this.position.clone());
if (this.trajectory.length > 1000) this.trajectory.shift();
if (this.trajectory.length > 1) {
if (this.trajectoryLine) scene.remove(this.trajectoryLine);
const lineGeometry = new THREE.BufferGeometry().setFromPoints(this.trajectory);
const lineMaterial = new THREE.LineBasicMaterial({ color: 0xFFFFFF, transparent: true, opacity: 0.5 });
this.trajectoryLine = new THREE.Line(lineGeometry, lineMaterial);
scene.add(this.trajectoryLine);
}
}
}
// Add a new planet
function addPlanet() {
const position = new THREE.Vector3(
(Math.random() - 0.5) * 20,
0,
(Math.random() - 0.5) * 20
);
// Set initial velocity (tangential to star)
const direction = new THREE.Vector3(-position.z, 0, position.x).normalize();
const velocity = direction.multiplyScalar(currentSpeed);
const planet = new Planet(currentMass, position, velocity);
planets.push(planet);
document.getElementById("planet-info").innerHTML =
`Planets: ${planets.length}<br>Mass: ${currentMass}<br>Speed: ${currentSpeed.toFixed(1)}`;
}
// Update physics
function updatePhysics(dt) {
planets.forEach(planet => planet.update(dt));
updateGraph();
}
// Energy graph (Plotly)
function updateGraph() {
if (planets.length === 0) return;
const planet = planets[0];
const r = planet.position.length();
const v = planet.velocity.length();
// Kinetic energy: KE = ½mv²
const KE = 0.5 * planet.mass * v * v;
// Potential energy: PE = -GMm/r
const PE = -G * 1000 * planet.mass / r;
const data = [{
y: [KE, PE, KE + PE],
type: 'bar',
name: 'Energy',
marker: { color: ['#FF5733', '#33FF57', '#3357FF'] }
}];
Plotly.newPlot('graph-container', data, {
title: 'Energy Conservation (KE + PE = Constant)',
barmode: 'stack'
});
}
// Event listeners
document.getElementById("add-planet").addEventListener("click", addPlanet);
document.getElementById("pause-btn").addEventListener("click", () => {
isPaused = !isPaused;
document.getElementById("pause-btn").textContent = isPaused ? "▶️ Play" : "⏸️ Pause";
});
document.getElementById("step-btn").addEventListener("click", () => {
if (isPaused) updatePhysics(0.1);
});
document.getElementById("reset-btn").addEventListener("click", () => {
planets.forEach(planet => scene.remove(planet.mesh));
planets.length = 0;
document.getElementById("planet-info").textContent = "";
});
document.getElementById("mass-slider").addEventListener("input", (e) => {
currentMass = parseFloat(e.target.value);
document.getElementById("mass-value").textContent = currentMass;
});
document.getElementById("speed-slider").addEventListener("input", (e) => {
currentSpeed = parseFloat(e.target.value);
document.getElementById("speed-value").textContent = currentSpeed;
});
// Animation loop
let lastTime = 0;
function animate(currentTime) {
requestAnimationFrame(animate);
const deltaTime = (currentTime - lastTime) / 1000; // Convert to seconds
lastTime = currentTime;
if (!isPaused && deltaTime < 0.1) {
updatePhysics(deltaTime);
}
controls.update();
renderer.render(scene, camera);
}
animate(0);
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>