|
|
<!DOCTYPE html> |
|
|
<html lang="fr"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Gravity Simulator - Comparaison interplanétaire</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.planet { |
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
|
|
will-change: transform; |
|
|
} |
|
|
.planet:hover { |
|
|
transform: scale(1.05); |
|
|
box-shadow: 0 10px 25px rgba(0,0,0,0.2); |
|
|
} |
|
|
.gravity-well { |
|
|
position: absolute; |
|
|
border-radius: 50%; |
|
|
opacity: 0.2; |
|
|
transform: translate(-50%, -50%); |
|
|
} |
|
|
#simulationCanvas { |
|
|
background: radial-gradient(circle at center, #1a202c 0%, #0f172a 100%); |
|
|
border-radius: 12px; |
|
|
box-shadow: inset 0 0 20px rgba(0,0,0,0.5); |
|
|
} |
|
|
.object { |
|
|
position: absolute; |
|
|
border-radius: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
} |
|
|
@keyframes float { |
|
|
0%, 100% { transform: translateY(0); } |
|
|
50% { transform: translateY(-10px); } |
|
|
} |
|
|
.floating { |
|
|
animation: float 3s ease-in-out infinite; |
|
|
} |
|
|
.planet-stats { |
|
|
position: absolute; |
|
|
background: rgba(30, 41, 59, 0.8); |
|
|
border-radius: 8px; |
|
|
padding: 8px; |
|
|
font-size: 12px; |
|
|
pointer-events: none; |
|
|
transform: translateX(-50%); |
|
|
} |
|
|
.fall-data { |
|
|
position: absolute; |
|
|
bottom: 10px; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
background: rgba(30, 41, 59, 0.8); |
|
|
border-radius: 8px; |
|
|
padding: 8px; |
|
|
font-size: 12px; |
|
|
text-align: center; |
|
|
min-width: 120px; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-900 text-gray-100 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
<header class="mb-10 text-center"> |
|
|
<h1 class="text-4xl md:text-5xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent"> |
|
|
Gravity Simulator |
|
|
</h1> |
|
|
<p class="text-xl text-gray-300 max-w-2xl mx-auto"> |
|
|
Comparez les effets de la gravité sur différentes planètes en temps réel |
|
|
</p> |
|
|
</header> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-10"> |
|
|
|
|
|
<div class="bg-gray-800 p-6 rounded-xl shadow-lg col-span-1"> |
|
|
<h2 class="text-2xl font-semibold mb-4 flex items-center"> |
|
|
<i class="fas fa-sliders-h mr-2 text-blue-400"></i> Paramètres |
|
|
</h2> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<label class="block text-gray-300 mb-2">Planètes à comparer</label> |
|
|
<div class="grid grid-cols-2 gap-2"> |
|
|
<button id="earthBtn" class="planet-btn bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
|
|
<i class="fas fa-globe-americas mr-2"></i> Terre |
|
|
</button> |
|
|
<button id="moonBtn" class="planet-btn bg-gray-400 hover:bg-gray-500 text-gray-900 py-2 px-4 rounded flex items-center justify-center"> |
|
|
<i class="fas fa-moon mr-2"></i> Lune |
|
|
</button> |
|
|
<button id="marsBtn" class="planet-btn bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
|
|
<i class="fas fa-globe mr-2"></i> Mars |
|
|
</button> |
|
|
<button id="jupiterBtn" class="planet-btn bg-yellow-600 hover:bg-yellow-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
|
|
<i class="fas fa-globe mr-2"></i> Jupiter |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<label class="block text-gray-300 mb-2">Objet à lâcher</label> |
|
|
<select id="objectType" class="w-full bg-gray-700 border border-gray-600 rounded py-2 px-3 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
<option value="ball">Balle (1kg)</option> |
|
|
<option value="feather">Plume (0.01kg)</option> |
|
|
<option value="hammer">Marteau (5kg)</option> |
|
|
<option value="car">Voiture (1000kg)</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<label class="block text-gray-300 mb-2">Hauteur de chute</label> |
|
|
<input id="heightSlider" type="range" min="50" max="300" value="150" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> |
|
|
<div class="flex justify-between text-sm text-gray-400"> |
|
|
<span>50m</span> |
|
|
<span id="heightValue">150m</span> |
|
|
<span>300m</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex space-x-3"> |
|
|
<button id="startBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
|
|
<i class="fas fa-play mr-2"></i> Démarrer |
|
|
</button> |
|
|
<button id="resetBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
|
|
<i class="fas fa-redo mr-2"></i> Réinitialiser |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-800 p-6 rounded-xl shadow-lg col-span-2"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-2xl font-semibold flex items-center"> |
|
|
<i class="fas fa-atom mr-2 text-purple-400"></i> Simulation |
|
|
</h2> |
|
|
<div class="text-sm bg-gray-700 px-3 py-1 rounded-full flex items-center"> |
|
|
<i class="fas fa-info-circle mr-1 text-blue-400"></i> |
|
|
<span id="timeScale">Temps réel</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="relative h-96 w-full" id="simulationContainer"> |
|
|
<canvas id="simulationCanvas" class="w-full h-full"></canvas> |
|
|
|
|
|
|
|
|
<div id="planetsContainer" class="absolute inset-0"></div> |
|
|
|
|
|
|
|
|
<div id="resultsPanel" class="absolute bottom-4 left-4 bg-gray-900 bg-opacity-80 p-3 rounded-lg hidden"> |
|
|
<div class="grid grid-cols-3 gap-4 text-sm"> |
|
|
<div> |
|
|
<div class="text-gray-400">Temps de chute:</div> |
|
|
<div id="fallTime" class="font-mono">0.00s</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-gray-400">Vitesse finale:</div> |
|
|
<div id="finalSpeed" class="font-mono">0.00 m/s</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-gray-400">Force gravitationnelle:</div> |
|
|
<div id="gravForce" class="font-mono">0.00 N</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4" id="planetStats"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-800 p-6 rounded-xl shadow-lg mb-10"> |
|
|
<h2 class="text-2xl font-semibold mb-4 flex items-center"> |
|
|
<i class="fas fa-table mr-2 text-green-400"></i> Données planétaires |
|
|
</h2> |
|
|
|
|
|
<div class="overflow-x-auto"> |
|
|
<table class="w-full text-left"> |
|
|
<thead class="bg-gray-700"> |
|
|
<tr> |
|
|
<th class="py-3 px-4">Planète</th> |
|
|
<th class="py-3 px-4">Gravité (m/s²)</th> |
|
|
<th class="py-3 px-4">Masse (×10²⁴ kg)</th> |
|
|
<th class="py-3 px-4">Rayon (km)</th> |
|
|
<th class="py-3 px-4">Temps de chute de 100m</th> |
|
|
<th class="py-3 px-4">Vitesse finale (100m)</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody id="planetData"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-800 p-6 rounded-xl shadow-lg"> |
|
|
<h2 class="text-2xl font-semibold mb-4 flex items-center"> |
|
|
<i class="fas fa-book-open mr-2 text-yellow-400"></i> Science de la gravité |
|
|
</h2> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<h3 class="text-lg font-medium mb-2 text-blue-300">Loi de la gravitation universelle</h3> |
|
|
<p class="text-gray-300 mb-3"> |
|
|
La force gravitationnelle entre deux objets est donnée par la formule: |
|
|
</p> |
|
|
<div class="bg-gray-800 p-3 rounded font-mono text-center mb-3"> |
|
|
F = G × (m₁ × m₂) / r² |
|
|
</div> |
|
|
<p class="text-gray-300"> |
|
|
Où F est la force, G la constante gravitationnelle (6.674×10⁻¹¹ N·m²/kg²), |
|
|
m₁ et m₂ les masses des objets, et r la distance entre leurs centres. |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<h3 class="text-lg font-medium mb-2 text-purple-300">Mouvement des corps en chute libre</h3> |
|
|
<p class="text-gray-300 mb-3"> |
|
|
Pour un objet en chute libre près de la surface d'une planète: |
|
|
</p> |
|
|
<div class="bg-gray-800 p-3 rounded font-mono text-center mb-3"> |
|
|
v = √(2 × g × h)<br> |
|
|
t = √(2 × h / g) |
|
|
</div> |
|
|
<p class="text-gray-300"> |
|
|
Où v est la vitesse finale, g l'accélération gravitationnelle, |
|
|
h la hauteur de chute, et t le temps de chute. |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const G = 6.67430e-11; |
|
|
const PLANETS = { |
|
|
earth: { name: "Terre", color: "#3b82f6", g: 9.81, mass: 5.972, radius: 6371, emoji: "🌍" }, |
|
|
moon: { name: "Lune", color: "#9ca3af", g: 1.62, mass: 0.07346, radius: 1737, emoji: "🌕" }, |
|
|
mars: { name: "Mars", color: "#ef4444", g: 3.71, mass: 0.64171, radius: 3389, emoji: "♂️" }, |
|
|
jupiter: { name: "Jupiter", color: "#f59e0b", g: 24.79, mass: 1898.19, radius: 69911, emoji: "♃" } |
|
|
}; |
|
|
|
|
|
const OBJECTS = { |
|
|
ball: { name: "Balle", mass: 1, color: "#ffffff", radius: 10 }, |
|
|
feather: { name: "Plume", mass: 0.01, color: "#f3f4f6", radius: 5 }, |
|
|
hammer: { name: "Marteau", mass: 5, color: "#a1a1aa", radius: 12 }, |
|
|
car: { name: "Voiture", mass: 1000, color: "#60a5fa", radius: 15 } |
|
|
}; |
|
|
|
|
|
|
|
|
let selectedPlanets = []; |
|
|
let simulationRunning = false; |
|
|
let animationId = null; |
|
|
let startTime = null; |
|
|
let objects = []; |
|
|
let currentHeight = 150; |
|
|
|
|
|
|
|
|
const simulationContainer = document.getElementById('simulationContainer'); |
|
|
const simulationCanvas = document.getElementById('simulationCanvas'); |
|
|
const planetsContainer = document.getElementById('planetsContainer'); |
|
|
const planetStats = document.getElementById('planetStats'); |
|
|
const planetData = document.getElementById('planetData'); |
|
|
const heightSlider = document.getElementById('heightSlider'); |
|
|
const heightValue = document.getElementById('heightValue'); |
|
|
const objectType = document.getElementById('objectType'); |
|
|
const startBtn = document.getElementById('startBtn'); |
|
|
const resetBtn = document.getElementById('resetBtn'); |
|
|
const resultsPanel = document.getElementById('resultsPanel'); |
|
|
const fallTime = document.getElementById('fallTime'); |
|
|
const finalSpeed = document.getElementById('finalSpeed'); |
|
|
const gravForce = document.getElementById('gravForce'); |
|
|
|
|
|
|
|
|
const ctx = simulationCanvas.getContext('2d'); |
|
|
simulationCanvas.width = simulationContainer.clientWidth; |
|
|
simulationCanvas.height = simulationContainer.clientHeight; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.planet-btn').forEach(btn => { |
|
|
btn.addEventListener('click', function() { |
|
|
const planetId = this.id.replace('Btn', ''); |
|
|
togglePlanetSelection(planetId); |
|
|
}); |
|
|
}); |
|
|
|
|
|
heightSlider.addEventListener('input', function() { |
|
|
currentHeight = parseInt(this.value); |
|
|
heightValue.textContent = `${currentHeight}m`; |
|
|
updateFallData(); |
|
|
}); |
|
|
|
|
|
startBtn.addEventListener('click', startSimulation); |
|
|
resetBtn.addEventListener('click', resetSimulation); |
|
|
|
|
|
|
|
|
function togglePlanetSelection(planetId) { |
|
|
const index = selectedPlanets.indexOf(planetId); |
|
|
const btn = document.getElementById(`${planetId}Btn`); |
|
|
|
|
|
if (index === -1) { |
|
|
if (selectedPlanets.length < 3) { |
|
|
selectedPlanets.push(planetId); |
|
|
btn.classList.add('ring-2', 'ring-blue-400'); |
|
|
renderPlanetSelection(); |
|
|
updatePlanetDataTable(); |
|
|
updateFallData(); |
|
|
} |
|
|
} else { |
|
|
selectedPlanets.splice(index, 1); |
|
|
btn.classList.remove('ring-2', 'ring-blue-400'); |
|
|
renderPlanetSelection(); |
|
|
updatePlanetDataTable(); |
|
|
updateFallData(); |
|
|
} |
|
|
} |
|
|
|
|
|
function renderPlanetSelection() { |
|
|
planetsContainer.innerHTML = ''; |
|
|
planetStats.innerHTML = ''; |
|
|
|
|
|
if (selectedPlanets.length === 0) { |
|
|
planetsContainer.innerHTML = ` |
|
|
<div class="absolute inset-0 flex items-center justify-center text-gray-500"> |
|
|
<div class="text-center"> |
|
|
<i class="fas fa-globe-europe text-5xl mb-4"></i> |
|
|
<p>Sélectionnez au moins une planète pour commencer</p> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
const containerWidth = simulationContainer.clientWidth; |
|
|
const planetWidth = containerWidth / selectedPlanets.length; |
|
|
|
|
|
selectedPlanets.forEach((planetId, index) => { |
|
|
const planet = PLANETS[planetId]; |
|
|
const left = (index * planetWidth) + (planetWidth / 2); |
|
|
|
|
|
|
|
|
const planetEl = document.createElement('div'); |
|
|
planetEl.className = `planet absolute top-1/2 flex flex-col items-center`; |
|
|
planetEl.style.left = `${left}px`; |
|
|
planetEl.style.transform = 'translate(-50%, -50%)'; |
|
|
planetEl.innerHTML = ` |
|
|
<div class="w-16 h-16 rounded-full shadow-lg mb-2" style="background-color: ${planet.color}"> |
|
|
<div class="w-full h-full flex items-center justify-center text-2xl"> |
|
|
${planet.emoji} |
|
|
</div> |
|
|
</div> |
|
|
<div class="text-sm font-medium">${planet.name}</div> |
|
|
<div class="text-xs text-gray-400">${planet.g}m/s²</div> |
|
|
`; |
|
|
planetsContainer.appendChild(planetEl); |
|
|
|
|
|
|
|
|
const gravityWell = document.createElement('div'); |
|
|
gravityWell.className = 'gravity-well'; |
|
|
gravityWell.style.width = `${planetWidth * 0.8}px`; |
|
|
gravityWell.style.height = `${planetWidth * 0.8}px`; |
|
|
gravityWell.style.left = `${left}px`; |
|
|
gravityWell.style.top = '50%'; |
|
|
gravityWell.style.backgroundColor = planet.color; |
|
|
planetsContainer.appendChild(gravityWell); |
|
|
|
|
|
|
|
|
const fallDataEl = document.createElement('div'); |
|
|
fallDataEl.className = 'fall-data'; |
|
|
fallDataEl.style.left = `${left}px`; |
|
|
fallDataEl.style.color = planet.color; |
|
|
fallDataEl.innerHTML = ` |
|
|
<div>Hauteur: ${currentHeight}m</div> |
|
|
<div>Temps: ${calculateFallTime(currentHeight, planet.g).toFixed(2)}s</div> |
|
|
<div>Vitesse: ${calculateFinalSpeed(currentHeight, planet.g).toFixed(2)}m/s</div> |
|
|
`; |
|
|
fallDataEl.id = `fall-data-${planetId}`; |
|
|
planetsContainer.appendChild(fallDataEl); |
|
|
|
|
|
|
|
|
const statsEl = document.createElement('div'); |
|
|
statsEl.className = 'bg-gray-700 p-4 rounded-lg'; |
|
|
statsEl.innerHTML = ` |
|
|
<div class="flex items-center mb-2"> |
|
|
<div class="w-6 h-6 rounded-full mr-2" style="background-color: ${planet.color}"></div> |
|
|
<h3 class="font-medium">${planet.name}</h3> |
|
|
</div> |
|
|
<div class="grid grid-cols-2 gap-2 text-sm"> |
|
|
<div> |
|
|
<div class="text-gray-400">Gravité:</div> |
|
|
<div>${planet.g} m/s²</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-gray-400">Masse:</div> |
|
|
<div>${planet.mass} ×10²⁴ kg</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-gray-400">Rayon:</div> |
|
|
<div>${planet.radius} km</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-gray-400">Poids de l'objet:</div> |
|
|
<div id="weight-${planetId}">0 N</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
planetStats.appendChild(statsEl); |
|
|
}); |
|
|
} |
|
|
|
|
|
function calculateFallTime(height, gravity) { |
|
|
return Math.sqrt(2 * height / gravity); |
|
|
} |
|
|
|
|
|
function calculateFinalSpeed(height, gravity) { |
|
|
return Math.sqrt(2 * gravity * height); |
|
|
} |
|
|
|
|
|
function updateFallData() { |
|
|
selectedPlanets.forEach(planetId => { |
|
|
const planet = PLANETS[planetId]; |
|
|
const fallDataEl = document.getElementById(`fall-data-${planetId}`); |
|
|
if (fallDataEl) { |
|
|
fallDataEl.innerHTML = ` |
|
|
<div>Hauteur: ${currentHeight}m</div> |
|
|
<div>Temps: ${calculateFallTime(currentHeight, planet.g).toFixed(2)}s</div> |
|
|
<div>Vitesse: ${calculateFinalSpeed(currentHeight, planet.g).toFixed(2)}m/s</div> |
|
|
`; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function updatePlanetDataTable() { |
|
|
planetData.innerHTML = ''; |
|
|
|
|
|
Object.keys(PLANETS).forEach(planetId => { |
|
|
const planet = PLANETS[planetId]; |
|
|
const isSelected = selectedPlanets.includes(planetId); |
|
|
const fallTime100m = calculateFallTime(100, planet.g); |
|
|
const finalSpeed100m = calculateFinalSpeed(100, planet.g); |
|
|
|
|
|
const row = document.createElement('tr'); |
|
|
row.className = isSelected ? 'bg-gray-700' : 'border-b border-gray-700'; |
|
|
row.innerHTML = ` |
|
|
<td class="py-3 px-4 flex items-center"> |
|
|
${isSelected ? `<span class="w-2 h-2 rounded-full mr-2" style="background-color: ${planet.color}"></span>` : ''} |
|
|
${planet.name} |
|
|
</td> |
|
|
<td class="py-3 px-4">${planet.g}</td> |
|
|
<td class="py-3 px-4">${planet.mass}</td> |
|
|
<td class="py-3 px-4">${planet.radius}</td> |
|
|
<td class="py-3 px-4">${fallTime100m.toFixed(2)} s</td> |
|
|
<td class="py-3 px-4">${finalSpeed100m.toFixed(2)} m/s</td> |
|
|
`; |
|
|
planetData.appendChild(row); |
|
|
}); |
|
|
} |
|
|
|
|
|
function startSimulation() { |
|
|
if (selectedPlanets.length === 0 || simulationRunning) return; |
|
|
|
|
|
simulationRunning = true; |
|
|
startBtn.disabled = true; |
|
|
startTime = Date.now(); |
|
|
objects = []; |
|
|
|
|
|
const height = currentHeight; |
|
|
const object = OBJECTS[objectType.value]; |
|
|
|
|
|
|
|
|
selectedPlanets.forEach((planetId, index) => { |
|
|
const planet = PLANETS[planetId]; |
|
|
const containerWidth = simulationContainer.clientWidth; |
|
|
const planetWidth = containerWidth / selectedPlanets.length; |
|
|
const planetX = (index * planetWidth) + (planetWidth / 2); |
|
|
|
|
|
const obj = { |
|
|
planetId, |
|
|
x: planetX, |
|
|
y: 50, |
|
|
vy: 0, |
|
|
mass: object.mass, |
|
|
radius: object.radius, |
|
|
color: object.color, |
|
|
planetX, |
|
|
planetY: simulationContainer.clientHeight / 2, |
|
|
g: planet.g, |
|
|
height, |
|
|
falling: false, |
|
|
startTime: null, |
|
|
fallDuration: null, |
|
|
finalSpeed: null, |
|
|
statsEl: null |
|
|
}; |
|
|
|
|
|
|
|
|
obj.statsEl = document.createElement('div'); |
|
|
obj.statsEl.className = 'planet-stats'; |
|
|
obj.statsEl.style.left = `${planetX}px`; |
|
|
obj.statsEl.style.top = '20px'; |
|
|
obj.statsEl.style.color = planet.color; |
|
|
obj.statsEl.innerHTML = ` |
|
|
<div>Temps: <span class="time-value">0.00</span>s</div> |
|
|
<div>Vitesse: <span class="speed-value">0.00</span>m/s</div> |
|
|
`; |
|
|
planetsContainer.appendChild(obj.statsEl); |
|
|
|
|
|
objects.push(obj); |
|
|
|
|
|
|
|
|
const weightEl = document.getElementById(`weight-${planetId}`); |
|
|
if (weightEl) { |
|
|
weightEl.textContent = `${(object.mass * planet.g).toFixed(2)} N`; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
animate(); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultsPanel.classList.remove('hidden'); |
|
|
}, 500); |
|
|
} |
|
|
|
|
|
function animate() { |
|
|
animationId = requestAnimationFrame(animate); |
|
|
const now = Date.now(); |
|
|
const elapsedTime = (now - startTime) / 1000; |
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, simulationCanvas.width, simulationCanvas.height); |
|
|
|
|
|
|
|
|
drawStars(); |
|
|
|
|
|
|
|
|
objects.forEach(obj => { |
|
|
if (!obj.falling && elapsedTime > 1) { |
|
|
obj.falling = true; |
|
|
obj.startTime = now; |
|
|
} |
|
|
|
|
|
if (obj.falling) { |
|
|
|
|
|
const fallTime = (now - obj.startTime) / 1000; |
|
|
|
|
|
|
|
|
|
|
|
const scaleFactor = (simulationCanvas.height / 2 - 50) / obj.height; |
|
|
obj.y = 50 + 0.5 * obj.g * fallTime * fallTime * scaleFactor; |
|
|
|
|
|
|
|
|
obj.vy = obj.g * fallTime; |
|
|
|
|
|
|
|
|
if (obj.statsEl) { |
|
|
obj.statsEl.querySelector('.time-value').textContent = fallTime.toFixed(2); |
|
|
obj.statsEl.querySelector('.speed-value').textContent = obj.vy.toFixed(2); |
|
|
} |
|
|
|
|
|
|
|
|
if (obj.y >= obj.planetY - 30) { |
|
|
obj.y = obj.planetY - 30; |
|
|
obj.falling = false; |
|
|
obj.fallDuration = fallTime; |
|
|
obj.finalSpeed = obj.vy; |
|
|
|
|
|
|
|
|
updateResults(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.arc(obj.x, obj.y, obj.radius, 0, Math.PI * 2); |
|
|
ctx.fillStyle = obj.color; |
|
|
ctx.fill(); |
|
|
ctx.closePath(); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.ellipse(obj.x, obj.y + obj.radius + 2, obj.radius, obj.radius/3, 0, 0, Math.PI * 2); |
|
|
ctx.fillStyle = 'rgba(0,0,0,0.3)'; |
|
|
ctx.fill(); |
|
|
ctx.closePath(); |
|
|
}); |
|
|
} |
|
|
|
|
|
function drawStars() { |
|
|
ctx.fillStyle = 'white'; |
|
|
|
|
|
|
|
|
for (let i = 0; i < 50; i++) { |
|
|
const x = Math.random() * simulationCanvas.width; |
|
|
const y = Math.random() * simulationCanvas.height; |
|
|
const radius = Math.random() * 1.5; |
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.arc(x, y, radius, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
} |
|
|
} |
|
|
|
|
|
function updateResults() { |
|
|
if (objects.length === 0) return; |
|
|
|
|
|
|
|
|
const longestFall = objects.reduce((prev, current) => { |
|
|
return (prev.fallDuration || 0) > (current.fallDuration || 0) ? prev : current; |
|
|
}); |
|
|
|
|
|
if (longestFall.fallDuration) { |
|
|
fallTime.textContent = `${longestFall.fallDuration.toFixed(2)}s`; |
|
|
finalSpeed.textContent = `${longestFall.finalSpeed.toFixed(2)} m/s`; |
|
|
|
|
|
|
|
|
const planet = PLANETS[longestFall.planetId]; |
|
|
const object = OBJECTS[objectType.value]; |
|
|
const force = object.mass * planet.g; |
|
|
gravForce.textContent = `${force.toFixed(2)} N`; |
|
|
} |
|
|
} |
|
|
|
|
|
function resetSimulation() { |
|
|
if (animationId) { |
|
|
cancelAnimationFrame(animationId); |
|
|
animationId = null; |
|
|
} |
|
|
|
|
|
simulationRunning = false; |
|
|
startBtn.disabled = false; |
|
|
|
|
|
|
|
|
objects.forEach(obj => { |
|
|
if (obj.statsEl && obj.statsEl.parentNode) { |
|
|
obj.statsEl.parentNode.removeChild(obj.statsEl); |
|
|
} |
|
|
}); |
|
|
|
|
|
objects = []; |
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, simulationCanvas.width, simulationCanvas.height); |
|
|
|
|
|
|
|
|
resultsPanel.classList.add('hidden'); |
|
|
|
|
|
|
|
|
fallTime.textContent = '0.00s'; |
|
|
finalSpeed.textContent = '0.00 m/s'; |
|
|
gravForce.textContent = '0.00 N'; |
|
|
|
|
|
|
|
|
selectedPlanets.forEach(planetId => { |
|
|
const weightEl = document.getElementById(`weight-${planetId}`); |
|
|
if (weightEl) { |
|
|
weightEl.textContent = '0 N'; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
updatePlanetDataTable(); |
|
|
|
|
|
|
|
|
window.addEventListener('resize', function() { |
|
|
simulationCanvas.width = simulationContainer.clientWidth; |
|
|
simulationCanvas.height = simulationContainer.clientHeight; |
|
|
renderPlanetSelection(); |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=SaidAmchghal/simulation-de-gravit" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |