295 lines
10 KiB
Python
295 lines
10 KiB
Python
"""Animated solar system demonstration."""
|
|
import mcrfpy
|
|
import math
|
|
from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system,
|
|
create_planet, create_moon, point_on_circle,
|
|
SCREEN_WIDTH, SCREEN_HEIGHT)
|
|
|
|
|
|
class SolarSystemDemo(GeometryDemoScreen):
|
|
"""Demonstrate animated orbital mechanics with timer-based updates."""
|
|
|
|
name = "Solar System Animation"
|
|
description = "Planets orbiting with discrete time steps"
|
|
|
|
def setup(self):
|
|
self.add_title("Animated Solar System")
|
|
self.add_description("Planets snap to grid positions as time advances (1 tick = 1 turn)")
|
|
|
|
margin = 30
|
|
top_area = 80
|
|
bottom_panel = 60
|
|
|
|
# Screen layout - centered, with room for Earth's moon orbit
|
|
frame_width = SCREEN_WIDTH - 2 * margin
|
|
frame_height = SCREEN_HEIGHT - top_area - bottom_panel - margin
|
|
|
|
# Center of display area, shifted down a bit to give room for moon orbit at top
|
|
self.center_x = margin + frame_width // 2
|
|
self.center_y = top_area + frame_height // 2 + 30 # Shifted down
|
|
self.scale = 2.0 # Pixels per grid unit (larger for 1024x768)
|
|
|
|
# Background
|
|
bg = mcrfpy.Frame(pos=(margin, top_area), size=(frame_width, frame_height))
|
|
bg.fill_color = mcrfpy.Color(5, 5, 15)
|
|
bg.outline = 1
|
|
bg.outline_color = mcrfpy.Color(40, 40, 80)
|
|
self.ui.append(bg)
|
|
|
|
# Store frame boundaries for info panel
|
|
self.frame_bottom = top_area + frame_height
|
|
|
|
# Create the solar system using geometry module
|
|
self.star = create_solar_system(
|
|
grid_width=200, grid_height=200,
|
|
star_radius=15, star_orbit_radius=25
|
|
)
|
|
|
|
# Create planets with different orbital speeds
|
|
self.planet1 = create_planet(
|
|
name="Mercury",
|
|
star=self.star,
|
|
orbital_radius=60,
|
|
surface_radius=5,
|
|
orbit_ring_radius=12,
|
|
angular_velocity=12, # Fast orbit
|
|
initial_angle=0
|
|
)
|
|
|
|
self.planet2 = create_planet(
|
|
name="Venus",
|
|
star=self.star,
|
|
orbital_radius=100,
|
|
surface_radius=8,
|
|
orbit_ring_radius=16,
|
|
angular_velocity=7, # Medium orbit
|
|
initial_angle=120
|
|
)
|
|
|
|
self.planet3 = create_planet(
|
|
name="Earth",
|
|
star=self.star,
|
|
orbital_radius=150,
|
|
surface_radius=10,
|
|
orbit_ring_radius=20,
|
|
angular_velocity=4, # Slow orbit
|
|
initial_angle=240
|
|
)
|
|
|
|
# Moon orbiting Earth
|
|
self.moon = create_moon(
|
|
name="Luna",
|
|
planet=self.planet3,
|
|
orbital_radius=30,
|
|
surface_radius=3,
|
|
orbit_ring_radius=8,
|
|
angular_velocity=15, # Faster than Earth
|
|
initial_angle=45
|
|
)
|
|
|
|
self.planets = [self.planet1, self.planet2, self.planet3]
|
|
self.moons = [self.moon]
|
|
|
|
# Current time step
|
|
self.current_time = 0
|
|
|
|
# Store UI elements for updating
|
|
self.planet_circles = {}
|
|
self.orbit_rings = {}
|
|
self.moon_circles = {}
|
|
|
|
# Draw static elements (star, orbit paths)
|
|
self._draw_static_elements()
|
|
|
|
# Draw initial planet positions
|
|
self._draw_planets()
|
|
|
|
# Info panel below the main frame
|
|
panel_y = self.frame_bottom + 10
|
|
panel = mcrfpy.Frame(pos=(30, panel_y), size=(SCREEN_WIDTH - 60, 45))
|
|
panel.fill_color = mcrfpy.Color(20, 20, 35)
|
|
panel.outline = 1
|
|
panel.outline_color = mcrfpy.Color(60, 60, 100)
|
|
self.ui.append(panel)
|
|
|
|
# Time display
|
|
self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(40, panel_y + 12))
|
|
self.time_label.fill_color = mcrfpy.Color(255, 255, 255)
|
|
self.ui.append(self.time_label)
|
|
|
|
# Instructions
|
|
self.add_label("Time advances automatically every second", 200, panel_y + 12, (150, 150, 150))
|
|
|
|
# Start the animation timer
|
|
self.add_timer("solar_tick", self._tick, 1000) # 1 second per turn
|
|
|
|
def _to_screen(self, grid_pos):
|
|
"""Convert grid position to screen coordinates."""
|
|
gx, gy = grid_pos
|
|
# Center on screen, with star at center
|
|
star_pos = self.star.base_position
|
|
dx = (gx - star_pos[0]) * self.scale
|
|
dy = (gy - star_pos[1]) * self.scale
|
|
return (self.center_x + dx, self.center_y + dy)
|
|
|
|
def _draw_static_elements(self):
|
|
"""Draw elements that don't move (star, orbital paths)."""
|
|
star_screen = self._to_screen(self.star.base_position)
|
|
|
|
# Star
|
|
star_circle = mcrfpy.Circle(
|
|
center=star_screen,
|
|
radius=self.star.surface_radius * self.scale,
|
|
fill_color=mcrfpy.Color(255, 220, 100),
|
|
outline_color=mcrfpy.Color(255, 180, 50),
|
|
outline=3
|
|
)
|
|
self.ui.append(star_circle)
|
|
|
|
# Star glow effect
|
|
for i in range(3):
|
|
glow = mcrfpy.Circle(
|
|
center=star_screen,
|
|
radius=(self.star.surface_radius + 5 + i * 8) * self.scale,
|
|
fill_color=mcrfpy.Color(0, 0, 0, 0),
|
|
outline_color=mcrfpy.Color(255, 200, 50, 50 - i * 15),
|
|
outline=2
|
|
)
|
|
self.ui.append(glow)
|
|
|
|
# Orbital paths (static ellipses showing where planets travel)
|
|
for planet in self.planets:
|
|
path = mcrfpy.Circle(
|
|
center=star_screen,
|
|
radius=planet.orbital_radius * self.scale,
|
|
fill_color=mcrfpy.Color(0, 0, 0, 0),
|
|
outline_color=mcrfpy.Color(40, 40, 60),
|
|
outline=1
|
|
)
|
|
self.ui.append(path)
|
|
|
|
# Star label
|
|
self.add_label("Star", star_screen[0] - 15, star_screen[1] + self.star.surface_radius * self.scale + 5,
|
|
(255, 220, 100))
|
|
|
|
def _draw_planets(self):
|
|
"""Draw planets at their current positions."""
|
|
for planet in self.planets:
|
|
self._draw_planet(planet)
|
|
|
|
for moon in self.moons:
|
|
self._draw_moon(moon)
|
|
|
|
def _draw_planet(self, planet):
|
|
"""Draw a single planet."""
|
|
# Get grid position at current time
|
|
grid_pos = planet.grid_position_at_time(self.current_time)
|
|
screen_pos = self._to_screen(grid_pos)
|
|
|
|
# Color based on planet
|
|
colors = {
|
|
"Mercury": (180, 180, 180),
|
|
"Venus": (255, 200, 150),
|
|
"Earth": (100, 150, 255),
|
|
}
|
|
color = colors.get(planet.name, (150, 150, 150))
|
|
|
|
# Planet surface
|
|
planet_circle = mcrfpy.Circle(
|
|
center=screen_pos,
|
|
radius=planet.surface_radius * self.scale,
|
|
fill_color=mcrfpy.Color(*color),
|
|
outline_color=mcrfpy.Color(255, 255, 255, 100),
|
|
outline=1
|
|
)
|
|
self.ui.append(planet_circle)
|
|
self.planet_circles[planet.name] = planet_circle
|
|
|
|
# Orbit ring around planet
|
|
orbit_ring = mcrfpy.Circle(
|
|
center=screen_pos,
|
|
radius=planet.orbit_ring_radius * self.scale,
|
|
fill_color=mcrfpy.Color(0, 0, 0, 0),
|
|
outline_color=mcrfpy.Color(50, 150, 50, 100),
|
|
outline=1
|
|
)
|
|
self.ui.append(orbit_ring)
|
|
self.orbit_rings[planet.name] = orbit_ring
|
|
|
|
# Planet label
|
|
label = mcrfpy.Caption(
|
|
text=planet.name,
|
|
pos=(screen_pos[0] - 20, screen_pos[1] - planet.surface_radius * self.scale - 15)
|
|
)
|
|
label.fill_color = mcrfpy.Color(*color)
|
|
self.ui.append(label)
|
|
# Store label for updating
|
|
if not hasattr(self, 'planet_labels'):
|
|
self.planet_labels = {}
|
|
self.planet_labels[planet.name] = label
|
|
|
|
def _draw_moon(self, moon):
|
|
"""Draw a moon."""
|
|
grid_pos = moon.grid_position_at_time(self.current_time)
|
|
screen_pos = self._to_screen(grid_pos)
|
|
|
|
moon_circle = mcrfpy.Circle(
|
|
center=screen_pos,
|
|
radius=moon.surface_radius * self.scale,
|
|
fill_color=mcrfpy.Color(200, 200, 200),
|
|
outline_color=mcrfpy.Color(255, 255, 255, 100),
|
|
outline=1
|
|
)
|
|
self.ui.append(moon_circle)
|
|
self.moon_circles[moon.name] = moon_circle
|
|
|
|
# Moon's orbit path around Earth
|
|
parent_pos = self._to_screen(moon.parent.grid_position_at_time(self.current_time))
|
|
moon_path = mcrfpy.Circle(
|
|
center=parent_pos,
|
|
radius=moon.orbital_radius * self.scale,
|
|
fill_color=mcrfpy.Color(0, 0, 0, 0),
|
|
outline_color=mcrfpy.Color(60, 60, 80),
|
|
outline=1
|
|
)
|
|
self.ui.append(moon_path)
|
|
self.orbit_rings[moon.name + "_path"] = moon_path
|
|
|
|
def _tick(self, runtime):
|
|
"""Advance time by one turn and update planet positions."""
|
|
self.current_time += 1
|
|
|
|
# Update time display
|
|
self.time_label.text = f"Turn: {self.current_time}"
|
|
|
|
# Update planet positions
|
|
for planet in self.planets:
|
|
grid_pos = planet.grid_position_at_time(self.current_time)
|
|
screen_pos = self._to_screen(grid_pos)
|
|
|
|
# Update circle position
|
|
if planet.name in self.planet_circles:
|
|
self.planet_circles[planet.name].center = screen_pos
|
|
self.orbit_rings[planet.name].center = screen_pos
|
|
|
|
# Update label position
|
|
if hasattr(self, 'planet_labels') and planet.name in self.planet_labels:
|
|
self.planet_labels[planet.name].pos = (
|
|
screen_pos[0] - 20,
|
|
screen_pos[1] - planet.surface_radius * self.scale - 15
|
|
)
|
|
|
|
# Update moon positions
|
|
for moon in self.moons:
|
|
grid_pos = moon.grid_position_at_time(self.current_time)
|
|
screen_pos = self._to_screen(grid_pos)
|
|
|
|
if moon.name in self.moon_circles:
|
|
self.moon_circles[moon.name].center = screen_pos
|
|
|
|
# Update moon's orbital path center
|
|
parent_pos = self._to_screen(moon.parent.grid_position_at_time(self.current_time))
|
|
path_key = moon.name + "_path"
|
|
if path_key in self.orbit_rings:
|
|
self.orbit_rings[path_key].center = parent_pos
|