Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Solar System Sandbox</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
#canvas-container { | |
position: relative; | |
width: 100%; | |
height: 100vh; | |
background: radial-gradient(ellipse at center, #000000 0%, #1a1a2e 100%); | |
overflow: hidden; | |
} | |
#controls { | |
position: absolute; | |
top: 20px; | |
left: 20px; | |
background: rgba(0, 0, 0, 0.7); | |
padding: 15px; | |
border-radius: 10px; | |
color: white; | |
z-index: 100; | |
max-width: 300px; | |
} | |
#playback-controls { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
background: rgba(0, 0, 0, 0.7); | |
padding: 10px; | |
border-radius: 10px; | |
color: white; | |
z-index: 100; | |
display: flex; | |
gap: 8px; | |
} | |
.planet { | |
border-radius: 50%; | |
position: absolute; | |
box-shadow: 0 0 20px rgba(255, 255, 255, 0.3); | |
} | |
.trail { | |
position: absolute; | |
border-radius: 50%; | |
background: rgba(255, 255, 255, 0.2); | |
pointer-events: none; | |
} | |
.info-panel { | |
position: absolute; | |
bottom: 20px; | |
left: 20px; | |
background: rgba(0, 0, 0, 0.7); | |
padding: 15px; | |
border-radius: 10px; | |
color: white; | |
z-index: 100; | |
max-width: 300px; | |
} | |
.gravity-line { | |
position: absolute; | |
background: rgba(255, 255, 255, 0.3); | |
height: 1px; | |
transform-origin: left center; | |
pointer-events: none; | |
} | |
.playback-btn { | |
background: rgba(255, 255, 255, 0.1); | |
border: none; | |
color: white; | |
width: 36px; | |
height: 36px; | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.playback-btn:hover { | |
background: rgba(255, 255, 255, 0.2); | |
transform: scale(1.1); | |
} | |
.playback-btn.active { | |
background: rgba(255, 255, 255, 0.3); | |
transform: scale(1.1); | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white overflow-hidden"> | |
<div id="canvas-container"> | |
<div id="controls" class="space-y-4"> | |
<h2 class="text-xl font-bold">Solar System Sandbox</h2> | |
<div class="space-y-2"> | |
<h3 class="font-semibold">Add Celestial Body</h3> | |
<div class="flex space-x-2"> | |
<button id="add-planet" class="bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded">Planet</button> | |
<button id="add-star" class="bg-yellow-600 hover:bg-yellow-700 px-3 py-1 rounded">Star</button> | |
<button id="add-asteroid" class="bg-gray-600 hover:bg-gray-700 px-3 py-1 rounded">Asteroid</button> | |
</div> | |
</div> | |
<div class="space-y-2"> | |
<h3 class="font-semibold">Physics Settings</h3> | |
<div class="flex items-center justify-between"> | |
<label>Gravity:</label> | |
<input id="gravity-slider" type="range" min="0" max="2" step="0.1" value="0.5" class="w-32"> | |
<span id="gravity-value">0.5</span> | |
</div> | |
<div class="flex items-center justify-between"> | |
<label>Time Scale:</label> | |
<input id="time-scale-slider" type="range" min="0.1" max="5" step="0.1" value="1" class="w-32"> | |
<span id="time-scale-value">1.0</span> | |
</div> | |
</div> | |
<div class="space-y-2"> | |
<h3 class="font-semibold">Visual Effects</h3> | |
<div class="flex items-center space-x-2"> | |
<input id="show-trails" type="checkbox" checked> | |
<label for="show-trails">Show Trails</label> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<input id="show-gravity" type="checkbox"> | |
<label for="show-gravity">Show Gravity</label> | |
</div> | |
</div> | |
<div> | |
<button id="clear-all" class="bg-red-600 hover:bg-red-700 px-3 py-1 rounded">Clear All</button> | |
</div> | |
</div> | |
<div id="playback-controls"> | |
<button id="rewind-btn" class="playback-btn" title="Rewind (0.5x)"> | |
<i class="fas fa-backward"></i> | |
</button> | |
<button id="play-btn" class="playback-btn active" title="Play"> | |
<i class="fas fa-play"></i> | |
</button> | |
<button id="pause-btn" class="playback-btn" title="Pause"> | |
<i class="fas fa-pause"></i> | |
</button> | |
<button id="fastforward-btn" class="playback-btn" title="Fast Forward (2x)"> | |
<i class="fas fa-forward"></i> | |
</button> | |
</div> | |
<div id="info-panel" class="info-panel hidden"> | |
<h3 class="font-semibold" id="selected-name">Selected Body</h3> | |
<div class="grid grid-cols-2 gap-2 mt-2"> | |
<div>Mass:</div> | |
<div id="selected-mass">0</div> | |
<div>Radius:</div> | |
<div id="selected-radius">0</div> | |
<div>Velocity:</div> | |
<div id="selected-velocity">0</div> | |
<div>Position:</div> | |
<div id="selected-position">0, 0</div> | |
</div> | |
<div class="mt-2 flex space-x-2"> | |
<button id="delete-body" class="bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm">Delete</button> | |
<button id="freeze-body" class="bg-gray-600 hover:bg-gray-700 px-2 py-1 rounded text-sm">Freeze</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Initialize Matter.js | |
const { Engine, Render, World, Bodies, Body, Vector, Composite, Mouse, MouseConstraint } = Matter; | |
// Create engine | |
const engine = Engine.create({ | |
gravity: { x: 0, y: 0 }, | |
enableSleeping: true | |
}); | |
// Get container dimensions | |
const container = document.getElementById('canvas-container'); | |
const width = container.clientWidth; | |
const height = container.clientHeight; | |
// Store celestial bodies and their visual elements | |
const bodies = []; | |
const visualElements = {}; | |
const trails = []; | |
const gravityLines = []; | |
// Physics settings | |
let globalGravity = 0.5; | |
let timeScale = 1.0; | |
let showTrails = true; | |
let showGravity = false; | |
let selectedBody = null; | |
// Playback state | |
let isPlaying = true; | |
let playbackSpeed = 1.0; | |
let lastTimestamp = 0; | |
// Colors for different body types | |
const bodyColors = { | |
star: '#FDB813', | |
planet: '#4D8BFF', | |
asteroid: '#AAAAAA' | |
}; | |
// Initialize UI controls | |
document.getElementById('gravity-slider').addEventListener('input', (e) => { | |
globalGravity = parseFloat(e.target.value); | |
document.getElementById('gravity-value').textContent = globalGravity; | |
}); | |
document.getElementById('time-scale-slider').addEventListener('input', (e) => { | |
timeScale = parseFloat(e.target.value); | |
document.getElementById('time-scale-value').textContent = timeScale.toFixed(1); | |
}); | |
document.getElementById('show-trails').addEventListener('change', (e) => { | |
showTrails = e.target.checked; | |
document.querySelectorAll('.trail').forEach(trail => { | |
trail.style.display = showTrails ? 'block' : 'none'; | |
}); | |
}); | |
document.getElementById('show-gravity').addEventListener('change', (e) => { | |
showGravity = e.target.checked; | |
document.querySelectorAll('.gravity-line').forEach(line => { | |
line.style.display = showGravity ? 'block' : 'none'; | |
}); | |
}); | |
document.getElementById('clear-all').addEventListener('click', () => { | |
// Remove all bodies | |
bodies.forEach(body => { | |
World.remove(engine.world, body); | |
}); | |
bodies.length = 0; | |
// Remove all visual elements | |
Object.keys(visualElements).forEach(id => { | |
const element = visualElements[id]; | |
if (element.parentNode) { | |
element.parentNode.removeChild(element); | |
} | |
}); | |
Object.keys(visualElements).forEach(key => delete visualElements[key]); | |
// Clear trails | |
trails.forEach(trail => { | |
if (trail.parentNode) { | |
trail.parentNode.removeChild(trail); | |
} | |
}); | |
trails.length = 0; | |
// Clear gravity lines | |
gravityLines.forEach(line => { | |
if (line.parentNode) { | |
line.parentNode.removeChild(line); | |
} | |
}); | |
gravityLines.length = 0; | |
// Hide info panel | |
document.getElementById('info-panel').classList.add('hidden'); | |
selectedBody = null; | |
}); | |
// Playback controls | |
document.getElementById('play-btn').addEventListener('click', () => { | |
isPlaying = true; | |
playbackSpeed = 1.0; | |
updatePlaybackButtons(); | |
lastTimestamp = performance.now(); // Reset timestamp when resuming | |
}); | |
document.getElementById('pause-btn').addEventListener('click', () => { | |
isPlaying = false; | |
updatePlaybackButtons(); | |
}); | |
document.getElementById('rewind-btn').addEventListener('click', () => { | |
isPlaying = true; | |
playbackSpeed = 0.5; | |
updatePlaybackButtons(); | |
lastTimestamp = performance.now(); // Reset timestamp when changing speed | |
}); | |
document.getElementById('fastforward-btn').addEventListener('click', () => { | |
isPlaying = true; | |
playbackSpeed = 2.0; | |
updatePlaybackButtons(); | |
lastTimestamp = performance.now(); // Reset timestamp when changing speed | |
}); | |
function updatePlaybackButtons() { | |
// Reset all buttons | |
document.querySelectorAll('.playback-btn').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
// Activate the appropriate button | |
if (!isPlaying) { | |
document.getElementById('pause-btn').classList.add('active'); | |
} else { | |
if (playbackSpeed === 0.5) { | |
document.getElementById('rewind-btn').classList.add('active'); | |
} else if (playbackSpeed === 2.0) { | |
document.getElementById('fastforward-btn').classList.add('active'); | |
} else { | |
document.getElementById('play-btn').classList.add('active'); | |
} | |
} | |
} | |
// Add body buttons | |
document.getElementById('add-planet').addEventListener('click', () => { | |
addCelestialBody('planet', width / 2, height / 2); | |
}); | |
document.getElementById('add-star').addEventListener('click', () => { | |
addCelestialBody('star', width / 2, height / 2); | |
}); | |
document.getElementById('add-asteroid').addEventListener('click', () => { | |
addCelestialBody('asteroid', width / 2, height / 2); | |
}); | |
// Body interaction buttons | |
document.getElementById('delete-body').addEventListener('click', () => { | |
if (selectedBody) { | |
deleteBody(selectedBody); | |
document.getElementById('info-panel').classList.add('hidden'); | |
selectedBody = null; | |
} | |
}); | |
document.getElementById('freeze-body').addEventListener('click', () => { | |
if (selectedBody) { | |
selectedBody.isStatic = !selectedBody.isStatic; | |
Body.setStatic(selectedBody, selectedBody.isStatic); | |
document.getElementById('freeze-body').textContent = | |
selectedBody.isStatic ? 'Unfreeze' : 'Freeze'; | |
} | |
}); | |
// Add a celestial body to the simulation | |
function addCelestialBody(type, x, y) { | |
let radius, mass, options = {}; | |
switch (type) { | |
case 'star': | |
radius = 30 + Math.random() * 20; | |
mass = radius * 100; | |
options = { | |
render: { | |
fillStyle: bodyColors.star, | |
strokeStyle: '#FFD700', | |
lineWidth: 2 | |
}, | |
friction: 0, | |
frictionAir: 0, | |
frictionStatic: 0, | |
restitution: 0.9 | |
}; | |
break; | |
case 'planet': | |
radius = 10 + Math.random() * 15; | |
mass = radius * 20; | |
options = { | |
render: { | |
fillStyle: bodyColors.planet, | |
strokeStyle: '#7FB2FF', | |
lineWidth: 1 | |
}, | |
friction: 0, | |
frictionAir: 0.01, | |
frictionStatic: 0, | |
restitution: 0.7 | |
}; | |
break; | |
case 'asteroid': | |
radius = 3 + Math.random() * 7; | |
mass = radius * 5; | |
options = { | |
render: { | |
fillStyle: bodies.asteroid, | |
strokeStyle: '#DDDDDD', | |
lineWidth: 1 | |
}, | |
friction: 0, | |
frictionAir: 0.02, | |
frictionStatic: 0, | |
restitution: 0.5 | |
}; | |
break; | |
} | |
// Create physics body | |
const body = Bodies.circle(x, y, radius, options); | |
body.mass = mass; | |
body.type = type; | |
body.name = `${type.charAt(0).toUpperCase() + type.slice(1)}-${bodies.length + 1}`; | |
// Add some initial velocity if not a star | |
if (type !== 'star') { | |
const angle = Math.random() * Math.PI * 2; | |
const speed = 1 + Math.random() * 3; | |
Body.setVelocity(body, { | |
x: Math.cos(angle) * speed, | |
y: Math.sin(angle) * speed | |
}); | |
} else { | |
body.isStatic = true; | |
} | |
// Add to world | |
World.add(engine.world, body); | |
bodies.push(body); | |
// Create visual element | |
createVisualElement(body); | |
return body; | |
} | |
// Create a visual representation of a body | |
function createVisualElement(body) { | |
const element = document.createElement('div'); | |
element.className = 'planet'; | |
element.style.width = `${body.circleRadius * 2}px`; | |
element.style.height = `${body.circleRadius * 2}px`; | |
element.style.left = `${body.position.x - body.circleRadius}px`; | |
element.style.top = `${body.position.y - body.circleRadius}px`; | |
// Set color based on type | |
element.style.backgroundColor = bodyColors[body.type]; | |
// Add glow effect for stars | |
if (body.type === 'star') { | |
element.style.boxShadow = `0 0 ${body.circleRadius * 2}px ${bodyColors.star}`; | |
} | |
// Add click event | |
element.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
selectBody(body); | |
}); | |
// Add drag event | |
element.addEventListener('mousedown', startDrag); | |
container.appendChild(element); | |
visualElements[body.id] = element; | |
} | |
// Select a body and show its info | |
function selectBody(body) { | |
selectedBody = body; | |
// Update info panel | |
document.getElementById('selected-name').textContent = body.name; | |
document.getElementById('selected-mass').textContent = body.mass.toFixed(2); | |
document.getElementById('selected-radius').textContent = body.circleRadius.toFixed(2); | |
const velocity = Vector.magnitude(body.velocity); | |
document.getElementById('selected-velocity').textContent = velocity.toFixed(2); | |
document.getElementById('selected-position').textContent = | |
`${body.position.x.toFixed(0)}, ${body.position.y.toFixed(0)}`; | |
document.getElementById('freeze-body').textContent = | |
body.isStatic ? 'Unfreeze' : 'Freeze'; | |
document.getElementById('info-panel').classList.remove('hidden'); | |
} | |
// Delete a body | |
function deleteBody(body) { | |
// Remove from physics world | |
World.remove(engine.world, body); | |
// Remove from bodies array | |
const index = bodies.indexOf(body); | |
if (index > -1) { | |
bodies.splice(index, 1); | |
} | |
// Remove visual element | |
const element = visualElements[body.id]; | |
if (element && element.parentNode) { | |
element.parentNode.removeChild(element); | |
} | |
delete visualElements[body.id]; | |
// Remove any trails associated with this body | |
const bodyTrails = trails.filter(t => t.dataset.bodyId === body.id.toString()); | |
bodyTrails.forEach(trail => { | |
if (trail.parentNode) { | |
trail.parentNode.removeChild(trail); | |
} | |
const trailIndex = trails.indexOf(trail); | |
if (trailIndex > -1) { | |
trails.splice(trailIndex, 1); | |
} | |
}); | |
} | |
// Start dragging a body | |
function startDrag(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
const element = e.target; | |
const bodyId = Object.keys(visualElements).find(id => visualElements[id] === element); | |
const body = bodies.find(b => b.id.toString() === bodyId); | |
if (!body) return; | |
selectBody(body); | |
// Calculate offset from mouse to body center | |
const rect = element.getBoundingClientRect(); | |
const offsetX = e.clientX - rect.left - body.circleRadius; | |
const offsetY = e.clientY - rect.top - body.circleRadius; | |
// Set body to static while dragging | |
const wasStatic = body.isStatic; | |
Body.setStatic(body, true); | |
function moveBody(e) { | |
const x = e.clientX - offsetX; | |
const y = e.clientY - offsetY; | |
Body.setPosition(body, { x, y }); | |
// Update visual element | |
const visual = visualElements[body.id]; | |
if (visual) { | |
visual.style.left = `${x - body.circleRadius}px`; | |
visual.style.top = `${y - body.circleRadius}px`; | |
} | |
} | |
function endDrag(e) { | |
document.removeEventListener('mousemove', moveBody); | |
document.removeEventListener('mouseup', endDrag); | |
// Restore static state | |
Body.setStatic(body, wasStatic); | |
// If not static, set velocity based on drag speed | |
if (!wasStatic) { | |
const x = e.clientX - offsetX; | |
const y = e.clientY - offsetY; | |
const deltaX = x - body.position.x; | |
const deltaY = y - body.position.y; | |
Body.setVelocity(body, { | |
x: deltaX * 0.5, | |
y: deltaY * 0.5 | |
}); | |
} | |
} | |
document.addEventListener('mousemove', moveBody); | |
document.addEventListener('mouseup', endDrag); | |
} | |
// Click on empty space to deselect | |
container.addEventListener('click', (e) => { | |
if (e.target === container) { | |
document.getElementById('info-panel').classList.add('hidden'); | |
selectedBody = null; | |
} | |
}); | |
// Add mouse control for creating gravity wells | |
const mouse = Mouse.create(container); | |
const mouseConstraint = MouseConstraint.create(engine, { | |
mouse: mouse, | |
constraint: { | |
stiffness: 0.2, | |
render: { | |
visible: false | |
} | |
} | |
}); | |
World.add(engine.world, mouseConstraint); | |
// Add asteroids on right click | |
container.addEventListener('contextmenu', (e) => { | |
e.preventDefault(); | |
addCelestialBody('asteroid', e.clientX, e.clientY); | |
}); | |
// Main animation loop | |
function run(timestamp) { | |
if (!lastTimestamp) { | |
lastTimestamp = timestamp; | |
} | |
const deltaTime = timestamp - lastTimestamp; | |
lastTimestamp = timestamp; | |
if (isPlaying) { | |
// Calculate the time step based on playback speed and time scale | |
const timeStep = deltaTime * 0.06 * playbackSpeed * timeScale; | |
// Update physics | |
Engine.update(engine, timeStep); | |
// Apply custom gravity between bodies | |
applyGravity(); | |
// Update visual elements | |
updateVisuals(); | |
// Add trails | |
if (showTrails) { | |
addTrails(); | |
} | |
// Add gravity visualization | |
if (showGravity) { | |
visualizeGravity(); | |
} | |
} | |
requestAnimationFrame(run); | |
} | |
// Apply gravity between all bodies | |
function applyGravity() { | |
for (let i = 0; i < bodies.length; i++) { | |
const bodyA = bodies[i]; | |
for (let j = i + 1; j < bodies.length; j++) { | |
const bodyB = bodies[j]; | |
// Skip if either body is static | |
if (bodyA.isStatic && bodyB.isStatic) continue; | |
// Calculate distance between bodies | |
const direction = Vector.sub(bodyB.position, bodyA.position); | |
const distance = Vector.magnitude(direction); | |
const minDistance = bodyA.circleRadius + bodyB.circleRadius; | |
// Skip if bodies are too close (to prevent extreme forces) | |
if (distance < minDistance) continue; | |
// Calculate gravitational force (Newton's law of universal gravitation) | |
const forceMagnitude = globalGravity * bodyA.mass * bodyB.mass / (distance * distance); | |
const force = Vector.normalise(direction); | |
Vector.mult(force, forceMagnitude); | |
// Apply forces to both bodies | |
if (!bodyA.isStatic) { | |
Body.applyForce(bodyA, bodyA.position, { | |
x: force.x * 0.5, | |
y: force.y * 0.5 | |
}); | |
} | |
if (!bodyB.isStatic) { | |
Body.applyForce(bodyB, bodyB.position, { | |
x: -force.x * 0.5, | |
y: -force.y * 0.5 | |
}); | |
} | |
} | |
} | |
} | |
// Update positions of visual elements | |
function updateVisuals() { | |
bodies.forEach(body => { | |
const element = visualElements[body.id]; | |
if (element) { | |
element.style.left = `${body.position.x - body.circleRadius}px`; | |
element.style.top = `${body.position.y - body.circleRadius}px`; | |
// Rotate planets slightly for visual effect | |
if (body.type === 'planet') { | |
const rotation = (body.angle * 180 / Math.PI) % 360; | |
element.style.transform = `rotate(${rotation}deg)`; | |
} | |
} | |
}); | |
// Update info panel if a body is selected | |
if (selectedBody) { | |
document.getElementById('selected-velocity').textContent = | |
Vector.magnitude(selectedBody.velocity).toFixed(2); | |
document.getElementById('selected-position').textContent = | |
`${selectedBody.position.x.toFixed(0)}, ${selectedBody.position.y.toFixed(0)}`; | |
} | |
} | |
// Add motion trails behind moving bodies | |
function addTrails() { | |
bodies.forEach(body => { | |
// Skip static bodies | |
if (body.isStatic) return; | |
// Skip if velocity is very low | |
if (Vector.magnitude(body.velocity) < 0.1) return; | |
// Create a trail element | |
const trail = document.createElement('div'); | |
trail.className = 'trail'; | |
trail.style.width = `${body.circleRadius * 0.5}px`; | |
trail.style.height = `${body.circleRadius * 0.5}px`; | |
trail.style.left = `${body.position.x - body.circleRadius * 0.25}px`; | |
trail.style.top = `${body.position.y - body.circleRadius * 0.25}px`; | |
trail.dataset.bodyId = body.id; | |
// Set color based on body type | |
trail.style.backgroundColor = bodyColors[body.type]; | |
container.appendChild(trail); | |
trails.push(trail); | |
// Fade out and remove old trails | |
if (trails.length > 100) { | |
const oldTrail = trails.shift(); | |
if (oldTrail.parentNode) { | |
oldTrail.parentNode.removeChild(oldTrail); | |
} | |
} | |
}); | |
} | |
// Visualize gravity between bodies | |
function visualizeGravity() { | |
// Clear old gravity lines | |
gravityLines.forEach(line => { | |
if (line.parentNode) { | |
line.parentNode.removeChild(line); | |
} | |
}); | |
gravityLines.length = 0; | |
// Create new gravity lines between close bodies | |
for (let i = 0; i < bodies.length; i++) { | |
const bodyA = bodies[i]; | |
for (let j = i + 1; j < bodies.length; j++) { | |
const bodyB = bodies[j]; | |
// Calculate distance | |
const direction = Vector.sub(bodyB.position, bodyA.position); | |
const distance = Vector.magnitude(direction); | |
const maxDistance = Math.min(width, height) * 0.4; | |
// Only draw lines for relatively close bodies | |
if (distance < maxDistance) { | |
const line = document.createElement('div'); | |
line.className = 'gravity-line'; | |
line.style.width = `${distance}px`; | |
line.style.left = `${bodyA.position.x}px`; | |
line.style.top = `${bodyA.position.y}px`; | |
// Calculate angle | |
const angle = Math.atan2(bodyB.position.y - bodyA.position.y, | |
bodyB.position.x - bodyA.position.x); | |
line.style.transform = `rotate(${angle}rad)`; | |
// Set opacity based on distance (closer = more opaque) | |
const opacity = 1 - (distance / maxDistance); | |
line.style.opacity = opacity * 0.5; | |
container.appendChild(line); | |
gravityLines.push(line); | |
} | |
} | |
} | |
} | |
// Start with a simple solar system | |
function initSolarSystem() { | |
// Add a central star | |
const sun = addCelestialBody('star', width / 2, height / 2); | |
sun.mass = 10000; // Very massive sun | |
// Add some planets | |
for (let i = 0; i < 5; i++) { | |
const angle = Math.random() * Math.PI * 2; | |
const distance = 100 + Math.random() * 200; | |
const planet = addCelestialBody('planet', | |
width / 2 + Math.cos(angle) * distance, | |
height / 2 + Math.sin(angle) * distance | |
); | |
// Set initial velocity for orbit | |
const orbitSpeed = Math.sqrt(sun.mass / distance) * 0.3; | |
Body.setVelocity(planet, { | |
x: Math.cos(angle + Math.PI/2) * orbitSpeed, | |
y: Math.sin(angle + Math.PI/2) * orbitSpeed | |
}); | |
} | |
// Add some asteroids | |
for (let i = 0; i < 10; i++) { | |
const angle = Math.random() * Math.PI * 2; | |
const distance = 200 + Math.random() * 300; | |
addCelestialBody('asteroid', | |
width / 2 + Math.cos(angle) * distance, | |
height / 2 + Math.sin(angle) * distance | |
); | |
} | |
} | |
// Start the simulation | |
initSolarSystem(); | |
requestAnimationFrame(run); | |
</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=ItsMeDevRoland/se" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |