Spaces:
Sleeping
Sleeping
// static/script.js | |
let scene, camera, renderer, controls; | |
let spheres = []; | |
let fluidParticles = []; | |
let simulationRunning = false; | |
const PARTICLE_COUNT = 5000; | |
const SPACE_SIZE = 40; | |
const FLUID_SPEED = 0.1; | |
let FLUID_FRICTION = 0.9; | |
let FLUID_DEFLECTION = 0.1; | |
const GRAVITY_CONSTANT = 0.1; | |
// Scaling factors | |
const MASS_SCALE = 1e-26; // Scale down masses for simulation | |
const DISTANCE_SCALE = 1e-7; // Scale down distances (km to simulation units) | |
const VELOCITY_SCALE = 1e-3; // Scale down velocities (km/s to simulation units) | |
init(); | |
animate(); | |
function init() { | |
// Scene setup | |
scene = new THREE.Scene(); | |
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
camera.position.set(0, 20, 40); | |
renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth - 300, window.innerHeight); | |
document.getElementById('scene-container').appendChild(renderer.domElement); | |
controls = new THREE.OrbitControls(camera, renderer.domElement); | |
controls.enableDamping = true; | |
controls.dampingFactor = 0.05; | |
// Add Sun | |
const sunGeometry = new THREE.SphereGeometry(1, 32, 32); | |
const sunMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFF00 }); | |
const sun = new THREE.Mesh(sunGeometry, sunMaterial); | |
sun.position.set(0, 0, 0); | |
sun.userData = { mass: 1.989e30 * MASS_SCALE, velocity: new THREE.Vector3(0, 0, 0) }; | |
scene.add(sun); | |
spheres.push(sun); | |
// Add Earth | |
const earthGeometry = new THREE.SphereGeometry(0.3, 32, 32); | |
const earthMaterial = new THREE.MeshBasicMaterial({ color: 0x0000FF }); | |
const earth = new THREE.Mesh(earthGeometry, earthMaterial); | |
earth.position.set(149.6e6 * DISTANCE_SCALE, 0, 0); | |
earth.userData = { | |
mass: 5.972e24 * MASS_SCALE, | |
velocity: new THREE.Vector3(0, 0, 29.8 * VELOCITY_SCALE), | |
centripetalScale: 1 | |
}; | |
scene.add(earth); | |
spheres.push(earth); | |
// Add Mars | |
const marsGeometry = new THREE.SphereGeometry(0.25, 32, 32); | |
const marsMaterial = new THREE.MeshBasicMaterial({ color: 0xFF4500 }); | |
const mars = new THREE.Mesh(marsGeometry, marsMaterial); | |
mars.position.set(227.9e6 * DISTANCE_SCALE, 0, 0); | |
mars.userData = { | |
mass: 6.417e23 * MASS_SCALE, | |
velocity: new THREE.Vector3(0, 0, 24.1 * VELOCITY_SCALE), | |
centripetalScale: 1 | |
}; | |
scene.add(mars); | |
spheres.push(mars); | |
// Add fluid particles | |
const particleGeometry = new THREE.SphereGeometry(0.05, 8, 8); | |
const particleMaterial = new THREE.MeshBasicMaterial({ color: 0x00BFFF, transparent: true, opacity: 0.5 }); | |
for (let i = 0; i < PARTICLE_COUNT; i++) { | |
const particle = new THREE.Mesh(particleGeometry, particleMaterial); | |
particle.position.set( | |
(Math.random() - 0.5) * SPACE_SIZE, | |
(Math.random() - 0.5) * SPACE_SIZE, | |
(Math.random() - 0.5) * SPACE_SIZE | |
); | |
particle.userData = { | |
velocity: new THREE.Vector3( | |
(Math.random() - 0.5) * FLUID_SPEED, | |
(Math.random() - 0.5) * FLUID_SPEED, | |
(Math.random() - 0.5) * FLUID_SPEED | |
) | |
}; | |
scene.add(particle); | |
fluidParticles.push(particle); | |
} | |
// Add grid helper for reference | |
const gridHelper = new THREE.GridHelper(SPACE_SIZE, 20); | |
gridHelper.position.y = -SPACE_SIZE / 2; | |
scene.add(gridHelper); | |
// Event listeners for controls | |
document.getElementById('start-btn').addEventListener('click', startSimulation); | |
document.getElementById('stop-btn').addEventListener('click', stopSimulation); | |
document.getElementById('reset-btn').addEventListener('click', resetSimulation); | |
document.getElementById('save-btn').addEventListener('click', saveSettings); | |
document.getElementById('load-btn').addEventListener('click', loadSettings); | |
// Update parameters when sliders change | |
['sun', 'earth', 'mars'].forEach(body => { | |
document.getElementById(`${body}-mass`).addEventListener('input', updateParams); | |
document.getElementById(`${body}-x`).addEventListener('input', updateParams); | |
document.getElementById(`${body}-y`).addEventListener('input', updateParams); | |
document.getElementById(`${body}-z`).addEventListener('input', updateParams); | |
if (body !== 'sun') { | |
document.getElementById(`${body}-orbital-velocity`).addEventListener('input', updateParams); | |
document.getElementById(`${body}-centripetal`).addEventListener('input', updateParams); | |
} | |
}); | |
document.getElementById('fluid-friction').addEventListener('input', updateParams); | |
document.getElementById('fluid-deflection').addEventListener('input', updateParams); | |
// Handle window resize | |
window.addEventListener('resize', () => { | |
camera.aspect = (window.innerWidth - 300) / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth - 300, window.innerHeight); | |
}); | |
// Initial update to display scaled values | |
updateParams(); | |
} | |
function updateParams() { | |
// Update Sun | |
const sunMass = parseFloat(document.getElementById('sun-mass').value); | |
spheres[0].userData.mass = sunMass * MASS_SCALE; | |
document.getElementById('sun-mass-scaled').textContent = (sunMass * MASS_SCALE).toExponential(2); | |
spheres[0].position.set( | |
parseFloat(document.getElementById('sun-x').value) * DISTANCE_SCALE, | |
parseFloat(document.getElementById('sun-y').value) * DISTANCE_SCALE, | |
parseFloat(document.getElementById('sun-z').value) * DISTANCE_SCALE | |
); | |
// Update Earth | |
const earthMass = parseFloat(document.getElementById('earth-mass').value); | |
spheres[1].userData.mass = earthMass * MASS_SCALE; | |
document.getElementById('earth-mass-scaled').textContent = (earthMass * MASS_SCALE).toExponential(2); | |
spheres[1].position.set( | |
parseFloat(document.getElementById('earth-x').value) * DISTANCE_SCALE, | |
parseFloat(document.getElementById('earth-y').value) * DISTANCE_SCALE, | |
parseFloat(document.getElementById('earth-z').value) * DISTANCE_SCALE | |
); | |
const earthVelocity = parseFloat(document.getElementById('earth-orbital-velocity').value); | |
spheres[1].userData.velocity.set(0, 0, earthVelocity * VELOCITY_SCALE); | |
document.getElementById('earth-velocity-scaled').textContent = (earthVelocity * VELOCITY_SCALE).toFixed(4); | |
spheres[1].userData.centripetalScale = parseFloat(document.getElementById('earth-centripetal').value); | |
// Update Mars | |
const marsMass = parseFloat(document.getElementById('mars-mass').value); | |
spheres[2].userData.mass = marsMass * MASS_SCALE; | |
document.getElementById('mars-mass-scaled').textContent = (marsMass * MASS_SCALE).toExponential(2); | |
spheres[2].position.set( | |
parseFloat(document.getElementById('mars-x').value) * DISTANCE_SCALE, | |
parseFloat(document.getElementById('mars-y').value) * DISTANCE_SCALE, | |
parseFloat(document.getElementById('mars-z').value) * DISTANCE_SCALE | |
); | |
const marsVelocity = parseFloat(document.getElementById('mars-orbital-velocity').value); | |
spheres[2].userData.velocity.set(0, 0, marsVelocity * VELOCITY_SCALE); | |
document.getElementById('mars-velocity-scaled').textContent = (marsVelocity * VELOCITY_SCALE).toFixed(4); | |
spheres[2].userData.centripetalScale = parseFloat(document.getElementById('mars-centripetal').value); | |
// Update fluid interaction parameters | |
FLUID_FRICTION = parseFloat(document.getElementById('fluid-friction').value); | |
FLUID_DEFLECTION = parseFloat(document.getElementById('fluid-deflection').value); | |
} | |
function startSimulation() { | |
// Update parameters to ensure the simulation uses the latest values | |
updateParams(); | |
simulationRunning = true; | |
document.getElementById('status-message').textContent = 'Simulation started'; | |
document.getElementById('status-message').style.color = '#4CAF50'; | |
} | |
function stopSimulation() { | |
simulationRunning = false; | |
document.getElementById('status-message').textContent = 'Simulation stopped'; | |
document.getElementById('status-message').style.color = '#F44336'; | |
} | |
function resetSimulation() { | |
// Stop the simulation | |
simulationRunning = false; | |
// Reset fluid particles | |
fluidParticles.forEach(particle => { | |
particle.position.set( | |
(Math.random() - 0.5) * SPACE_SIZE, | |
(Math.random() - 0.5) * SPACE_SIZE, | |
(Math.random() - 0.5) * SPACE_SIZE | |
); | |
particle.userData.velocity.set( | |
(Math.random() - 0.5) * FLUID_SPEED, | |
(Math.random() - 0.5) * FLUID_SPEED, | |
(Math.random() - 0.5) * FLUID_SPEED | |
); | |
}); | |
// Reset sphere positions and velocities using current control values | |
updateParams(); | |
document.getElementById('status-message').textContent = 'Simulation reset'; | |
document.getElementById('status-message').style.color = '#2196F3'; | |
} | |
function saveSettings() { | |
const settings = { | |
sun: { | |
mass: parseFloat(document.getElementById('sun-mass').value), | |
position: [ | |
parseFloat(document.getElementById('sun-x').value), | |
parseFloat(document.getElementById('sun-y').value), | |
parseFloat(document.getElementById('sun-z').value) | |
], | |
orbital_velocity: 0 | |
}, | |
earth: { | |
mass: parseFloat(document.getElementById('earth-mass').value), | |
position: [ | |
parseFloat(document.getElementById('earth-x').value), | |
parseFloat(document.getElementById('earth-y').value), | |
parseFloat(document.getElementById('earth-z').value) | |
], | |
orbital_velocity: parseFloat(document.getElementById('earth-orbital-velocity').value) | |
}, | |
mars: { | |
mass: parseFloat(document.getElementById('mars-mass').value), | |
position: [ | |
parseFloat(document.getElementById('mars-x').value), | |
parseFloat(document.getElementById('mars-y').value), | |
parseFloat(document.getElementById('mars-z').value) | |
], | |
orbital_velocity: parseFloat(document.getElementById('mars-orbital-velocity').value) | |
}, | |
fluid_speed: FLUID_SPEED, | |
fluid_friction: FLUID_FRICTION, | |
fluid_deflection: FLUID_DEFLECTION | |
}; | |
fetch('/api/save', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify(settings) | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
document.getElementById('status-message').textContent = data.message || data.status; | |
document.getElementById('status-message').style.color = data.status === 'success' ? '#4CAF50' : '#FF0000'; | |
}) | |
.catch(error => { | |
document.getElementById('status-message').textContent = 'Error saving settings'; | |
document.getElementById('status-message').style.color = '#FF0000'; | |
}); | |
} | |
function loadSettings() { | |
fetch('/api/load') | |
.then(response => response.json()) | |
.then(data => { | |
if (data.status === 'success') { | |
const params = data.params; | |
document.getElementById('sun-mass').value = params.sun.mass; | |
document.getElementById('sun-x').value = params.sun.position[0]; | |
document.getElementById('sun-y').value = params.sun.position[1]; | |
document.getElementById('sun-z').value = params.sun.position[2]; | |
document.getElementById('earth-mass').value = params.earth.mass; | |
document.getElementById('earth-x').value = params.earth.position[0]; | |
document.getElementById('earth-y').value = params.earth.position[1]; | |
document.getElementById('earth-z').value = params.earth.position[2]; | |
document.getElementById('earth-orbital-velocity').value = params.earth.orbital_velocity; | |
document.getElementById('mars-mass').value = params.mars.mass; | |
document.getElementById('mars-x').value = params.mars.position[0]; | |
document.getElementById('mars-y').value = params.mars.position[1]; | |
document.getElementById('mars-z').value = params.mars.position[2]; | |
document.getElementById('mars-orbital-velocity').value = params.mars.orbital_velocity; | |
document.getElementById('fluid-friction').value = params.fluid_friction; | |
document.getElementById('fluid-deflection').value = params.fluid_deflection; | |
updateParams(); | |
document.getElementById('status-message').textContent = 'Settings loaded successfully'; | |
document.getElementById('status-message').style.color = '#4CAF50'; | |
} else { | |
document.getElementById('status-message').textContent = data.message; | |
document.getElementById('status-message').style.color = '#FF0000'; | |
} | |
}) | |
.catch(error => { | |
document.getElementById('status-message').textContent = 'Error loading settings'; | |
document.getElementById('status-message').style.color = '#FF0000'; | |
}); | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
if (simulationRunning) { | |
// Update fluid particles | |
fluidParticles.forEach(particle => { | |
let position = particle.position; | |
let velocity = particle.userData.velocity; | |
// Check for interactions with spheres | |
spheres.forEach(sphere => { | |
let distance = position.distanceTo(sphere.position); | |
let sphereRadius = sphere.geometry.parameters.radius + 0.5; | |
if (distance < sphereRadius) { | |
// Apply friction | |
velocity.multiplyScalar(FLUID_FRICTION); | |
// Apply gravitational deflection | |
let direction = sphere.position.clone().sub(position).normalize(); | |
let forceMagnitude = (FLUID_DEFLECTION * sphere.userData.mass) / (distance * distance); | |
let force = direction.multiplyScalar(forceMagnitude); | |
velocity.add(force); | |
} | |
}); | |
// Update position | |
position.add(velocity); | |
// Boundary conditions (wrap around) | |
if (Math.abs(position.x) > SPACE_SIZE / 2) position.x = -Math.sign(position.x) * SPACE_SIZE / 2; | |
if (Math.abs(position.y) > SPACE_SIZE / 2) position.y = -Math.sign(position.y) * SPACE_SIZE / 2; | |
if (Math.abs(position.z) > SPACE_SIZE / 2) position.z = -Math.sign(position.z) * SPACE_SIZE / 2; | |
}); | |
// Update sphere positions (gravitational interaction and orbital dynamics) | |
spheres.forEach((sphere, i) => { | |
if (i === 0) return; // Sun is stationary | |
let acceleration = new THREE.Vector3(); | |
spheres.forEach((otherSphere, j) => { | |
if (i !== j) { | |
let distance = sphere.position.distanceTo(otherSphere.position); | |
if (distance > 0.1) { | |
let direction = otherSphere.position.clone().sub(sphere.position).normalize(); | |
let force = (GRAVITY_CONSTANT * otherSphere.userData.mass) / (distance * distance); | |
acceleration.add(direction.multiplyScalar(force * sphere.userData.centripetalScale)); | |
} | |
} | |
}); | |
// Update velocity and position | |
sphere.userData.velocity.add(acceleration); | |
sphere.position.add(sphere.userData.velocity); | |
}); | |
} | |
controls.update(); | |
renderer.render(scene, camera); | |
} |