fix: Refine geometry demos for 1024x768 and fix animations
- Fix timer restart when switching between animated demo scenes - Update all demos from 800x600 to 1024x768 resolution - Add screen_angle_between() for correct arc angles in screen coords - Fix arc directions by accounting for screen Y inversion - Reposition labels to avoid text overlaps - Shift solar system center down to prevent moon orbit overflow - Reposition ship/target in pathfinding demo to avoid sun clipping - Scale menu screen to fill 1024x768 with wider buttons - Regenerate all demo screenshots 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
576481957a
commit
51e96c0c6b
12 changed files with 497 additions and 337 deletions
|
|
@ -1,9 +1,8 @@
|
|||
"""Static pathfinding demonstration with planets and orbit rings."""
|
||||
import mcrfpy
|
||||
import math
|
||||
from .base import (GeometryDemoScreen, OrbitalBody, bresenham_circle, filled_circle,
|
||||
angle_between, distance, point_on_circle, is_viable_waypoint,
|
||||
nearest_orbit_entry, optimal_exit_heading)
|
||||
from .base import (GeometryDemoScreen, bresenham_circle, filled_circle,
|
||||
screen_angle_between, distance, SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||
|
||||
|
||||
class PathfindingStaticDemo(GeometryDemoScreen):
|
||||
|
|
@ -16,16 +15,20 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
self.add_title("Pathfinding Through Orbital Bodies")
|
||||
self.add_description("Using free orbital movement to optimize travel paths")
|
||||
|
||||
# Create a scenario with multiple planets
|
||||
# Ship needs to go from bottom-left to top-right
|
||||
# Optimal path uses planetary orbits as "free repositioning stations"
|
||||
margin = 30
|
||||
top_area = 80
|
||||
legend_height = 70
|
||||
|
||||
# Main display area - use most of screen
|
||||
frame_width = SCREEN_WIDTH - 2 * margin
|
||||
frame_height = SCREEN_HEIGHT - top_area - margin - legend_height
|
||||
|
||||
self.cell_size = 8
|
||||
self.offset_x = 50
|
||||
self.offset_y = 100
|
||||
self.grid_x = margin + 20
|
||||
self.grid_y = top_area + 20
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(pos=(30, 80), size=(740, 480))
|
||||
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)
|
||||
|
|
@ -33,55 +36,60 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
|
||||
# Define planets (center_x, center_y, surface_radius, orbit_radius, name)
|
||||
self.planets = [
|
||||
(20, 45, 8, 14, "Alpha"),
|
||||
(55, 25, 5, 10, "Beta"),
|
||||
(70, 50, 6, 12, "Gamma"),
|
||||
(25, 50, 6, 12, "Alpha"),
|
||||
(60, 25, 4, 9, "Beta"),
|
||||
(85, 55, 5, 11, "Gamma"),
|
||||
]
|
||||
|
||||
# Ship start and end
|
||||
self.ship_start = (5, 55)
|
||||
self.ship_end = (85, 10)
|
||||
self.ship_start = (8, 65)
|
||||
self.ship_end = (105, 15)
|
||||
|
||||
# Draw grid reference (faint)
|
||||
# Draw grid reference
|
||||
self._draw_grid_reference()
|
||||
|
||||
# Draw planets with surfaces and orbit rings
|
||||
# Draw planets
|
||||
for px, py, sr, orbit_r, name in self.planets:
|
||||
self._draw_planet(px, py, sr, orbit_r, name)
|
||||
|
||||
# Calculate and draw optimal path
|
||||
# Draw optimal path
|
||||
self._draw_optimal_path()
|
||||
|
||||
# Draw ship and target
|
||||
self._draw_ship_and_target()
|
||||
|
||||
# Legend
|
||||
self._draw_legend()
|
||||
# Legend at bottom
|
||||
self._draw_legend(margin, top_area + frame_height + 10)
|
||||
|
||||
def _to_screen(self, gx, gy):
|
||||
"""Convert grid coords to screen coords."""
|
||||
return (self.offset_x + gx * self.cell_size,
|
||||
self.offset_y + gy * self.cell_size)
|
||||
"""Convert grid coords to screen coords (center of cell)."""
|
||||
return (self.grid_x + gx * self.cell_size + self.cell_size // 2,
|
||||
self.grid_y + gy * self.cell_size + self.cell_size // 2)
|
||||
|
||||
def _to_screen_corner(self, gx, gy):
|
||||
"""Convert grid coords to screen coords (corner of cell)."""
|
||||
return (self.grid_x + gx * self.cell_size,
|
||||
self.grid_y + gy * self.cell_size)
|
||||
|
||||
def _draw_grid_reference(self):
|
||||
"""Draw faint grid lines for reference."""
|
||||
for i in range(0, 91, 10):
|
||||
# Vertical lines
|
||||
x = self.offset_x + i * self.cell_size
|
||||
max_x = 115
|
||||
max_y = 75
|
||||
for i in range(0, max_x + 1, 10):
|
||||
x = self.grid_x + i * self.cell_size
|
||||
line = mcrfpy.Line(
|
||||
start=(x, self.offset_y),
|
||||
end=(x, self.offset_y + 60 * self.cell_size),
|
||||
start=(x, self.grid_y),
|
||||
end=(x, self.grid_y + max_y * self.cell_size),
|
||||
color=mcrfpy.Color(30, 30, 50),
|
||||
thickness=1
|
||||
)
|
||||
self.ui.append(line)
|
||||
|
||||
for i in range(0, 61, 10):
|
||||
# Horizontal lines
|
||||
y = self.offset_y + i * self.cell_size
|
||||
for i in range(0, max_y + 1, 10):
|
||||
y = self.grid_y + i * self.cell_size
|
||||
line = mcrfpy.Line(
|
||||
start=(self.offset_x, y),
|
||||
end=(self.offset_x + 90 * self.cell_size, y),
|
||||
start=(self.grid_x, y),
|
||||
end=(self.grid_x + max_x * self.cell_size, y),
|
||||
color=mcrfpy.Color(30, 30, 50),
|
||||
thickness=1
|
||||
)
|
||||
|
|
@ -91,7 +99,7 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
"""Draw a planet with surface and orbit ring."""
|
||||
sx, sy = self._to_screen(cx, cy)
|
||||
|
||||
# Orbit ring (using mcrfpy.Circle for smooth rendering)
|
||||
# Orbit ring (smooth circle)
|
||||
orbit = mcrfpy.Circle(
|
||||
center=(sx, sy),
|
||||
radius=orbit_r * self.cell_size,
|
||||
|
|
@ -101,10 +109,10 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
)
|
||||
self.ui.append(orbit)
|
||||
|
||||
# Also draw Bresenham orbit cells for accuracy demo
|
||||
# Bresenham orbit cells
|
||||
orbit_cells = bresenham_circle((cx, cy), orbit_r)
|
||||
for gx, gy in orbit_cells:
|
||||
px, py = self._to_screen(gx, gy)
|
||||
px, py = self._to_screen_corner(gx, gy)
|
||||
cell = mcrfpy.Frame(
|
||||
pos=(px, py),
|
||||
size=(self.cell_size - 1, self.cell_size - 1)
|
||||
|
|
@ -112,10 +120,10 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
cell.fill_color = mcrfpy.Color(40, 100, 40, 100)
|
||||
self.ui.append(cell)
|
||||
|
||||
# Planet surface (filled circle)
|
||||
# Planet surface
|
||||
surface_cells = filled_circle((cx, cy), surface_r)
|
||||
for gx, gy in surface_cells:
|
||||
px, py = self._to_screen(gx, gy)
|
||||
px, py = self._to_screen_corner(gx, gy)
|
||||
dist = math.sqrt((gx - cx)**2 + (gy - cy)**2)
|
||||
intensity = int(180 * (1 - dist / (surface_r + 1)))
|
||||
cell = mcrfpy.Frame(
|
||||
|
|
@ -125,75 +133,90 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
cell.fill_color = mcrfpy.Color(60 + intensity, 80 + intensity//2, 150)
|
||||
self.ui.append(cell)
|
||||
|
||||
# Planet label
|
||||
self.add_label(name, sx - 15, sy - surface_r * self.cell_size - 15, (150, 150, 200))
|
||||
# Planet label (below planet to avoid overlap)
|
||||
self.add_label(name, sx - 15, sy + (surface_r + 2) * self.cell_size, (150, 150, 200))
|
||||
|
||||
def _draw_optimal_path(self):
|
||||
"""Calculate and draw the optimal path using orbital waypoints."""
|
||||
# The optimal path:
|
||||
# 1. Ship starts at (5, 55)
|
||||
# 2. Direct line to Alpha's orbit entry
|
||||
# 3. Free arc around Alpha to optimal exit
|
||||
# 4. Direct line to Gamma's orbit entry
|
||||
# 5. Free arc around Gamma to optimal exit
|
||||
# 6. Direct line to target (85, 10)
|
||||
"""Draw the optimal path using orbital waypoints."""
|
||||
# The path:
|
||||
# 1. Ship at (8, 65) -> Alpha orbit entry
|
||||
# 2. Arc around Alpha -> exit toward Gamma
|
||||
# 3. Straight to Gamma orbit entry
|
||||
# 4. Arc around Gamma -> exit toward target
|
||||
# 5. Straight to target (105, 15)
|
||||
|
||||
path_segments = []
|
||||
# Alpha: center (25, 50), orbit_r=12
|
||||
alpha_center = (25, 50)
|
||||
alpha_orbit = 12
|
||||
|
||||
# Current position
|
||||
current = self.ship_start
|
||||
# Gamma: center (85, 55), orbit_r=11
|
||||
gamma_center = (85, 55)
|
||||
gamma_orbit = 11
|
||||
|
||||
# For this demo, manually define the path through Alpha and Gamma
|
||||
# (In a real implementation, this would be computed by the pathfinder)
|
||||
ship_screen = self._to_screen(*self.ship_start)
|
||||
target_screen = self._to_screen(*self.ship_end)
|
||||
|
||||
# Planet Alpha (20, 45, orbit_r=14)
|
||||
alpha_center = (20, 45)
|
||||
alpha_orbit = 14
|
||||
# --- Segment 1: Ship to Alpha orbit entry ---
|
||||
# Entry angle: direction from Alpha to ship
|
||||
entry_angle_alpha = screen_angle_between(
|
||||
self._to_screen(*alpha_center),
|
||||
ship_screen
|
||||
)
|
||||
entry_alpha = (
|
||||
alpha_center[0] + alpha_orbit * math.cos(math.radians(entry_angle_alpha)),
|
||||
alpha_center[1] - alpha_orbit * math.sin(math.radians(entry_angle_alpha)) # Screen Y inverted
|
||||
)
|
||||
entry_alpha_screen = self._to_screen(*entry_alpha)
|
||||
|
||||
# Entry to Alpha
|
||||
entry_angle_alpha = angle_between(alpha_center, current)
|
||||
entry_alpha = point_on_circle(alpha_center, alpha_orbit, entry_angle_alpha)
|
||||
self._draw_path_line(ship_screen, entry_alpha_screen, (100, 200, 255))
|
||||
|
||||
# Draw line: start -> Alpha entry
|
||||
self._draw_path_line(current, entry_alpha, (100, 200, 255))
|
||||
current = entry_alpha
|
||||
# --- Segment 2: Arc around Alpha toward Gamma ---
|
||||
exit_angle_alpha = screen_angle_between(
|
||||
self._to_screen(*alpha_center),
|
||||
self._to_screen(*gamma_center)
|
||||
)
|
||||
exit_alpha = (
|
||||
alpha_center[0] + alpha_orbit * math.cos(math.radians(exit_angle_alpha)),
|
||||
alpha_center[1] - alpha_orbit * math.sin(math.radians(exit_angle_alpha))
|
||||
)
|
||||
exit_alpha_screen = self._to_screen(*exit_alpha)
|
||||
|
||||
# Exit from Alpha toward Gamma
|
||||
gamma_center = (70, 50)
|
||||
exit_angle_alpha = angle_between(alpha_center, gamma_center)
|
||||
exit_alpha = point_on_circle(alpha_center, alpha_orbit, exit_angle_alpha)
|
||||
self._draw_orbit_arc(self._to_screen(*alpha_center), alpha_orbit * self.cell_size,
|
||||
entry_angle_alpha, exit_angle_alpha)
|
||||
|
||||
# Draw arc on Alpha's orbit
|
||||
self._draw_orbit_arc(alpha_center, alpha_orbit, entry_angle_alpha, exit_angle_alpha)
|
||||
current = exit_alpha
|
||||
# --- Segment 3: Alpha exit to Gamma entry ---
|
||||
entry_angle_gamma = screen_angle_between(
|
||||
self._to_screen(*gamma_center),
|
||||
exit_alpha_screen
|
||||
)
|
||||
entry_gamma = (
|
||||
gamma_center[0] + gamma_orbit * math.cos(math.radians(entry_angle_gamma)),
|
||||
gamma_center[1] - gamma_orbit * math.sin(math.radians(entry_angle_gamma))
|
||||
)
|
||||
entry_gamma_screen = self._to_screen(*entry_gamma)
|
||||
|
||||
# Planet Gamma (70, 50, orbit_r=12)
|
||||
gamma_orbit = 12
|
||||
self._draw_path_line(exit_alpha_screen, entry_gamma_screen, (100, 200, 255))
|
||||
|
||||
# Entry to Gamma
|
||||
entry_angle_gamma = angle_between(gamma_center, current)
|
||||
entry_gamma = point_on_circle(gamma_center, gamma_orbit, entry_angle_gamma)
|
||||
# --- Segment 4: Arc around Gamma toward target ---
|
||||
exit_angle_gamma = screen_angle_between(
|
||||
self._to_screen(*gamma_center),
|
||||
target_screen
|
||||
)
|
||||
exit_gamma = (
|
||||
gamma_center[0] + gamma_orbit * math.cos(math.radians(exit_angle_gamma)),
|
||||
gamma_center[1] - gamma_orbit * math.sin(math.radians(exit_angle_gamma))
|
||||
)
|
||||
exit_gamma_screen = self._to_screen(*exit_gamma)
|
||||
|
||||
# Draw line: Alpha exit -> Gamma entry
|
||||
self._draw_path_line(current, entry_gamma, (100, 200, 255))
|
||||
current = entry_gamma
|
||||
self._draw_orbit_arc(self._to_screen(*gamma_center), gamma_orbit * self.cell_size,
|
||||
entry_angle_gamma, exit_angle_gamma)
|
||||
|
||||
# Exit from Gamma toward target
|
||||
exit_angle_gamma = angle_between(gamma_center, self.ship_end)
|
||||
exit_gamma = point_on_circle(gamma_center, gamma_orbit, exit_angle_gamma)
|
||||
# --- Segment 5: Gamma exit to target ---
|
||||
self._draw_path_line(exit_gamma_screen, target_screen, (100, 200, 255))
|
||||
|
||||
# Draw arc on Gamma's orbit
|
||||
self._draw_orbit_arc(gamma_center, gamma_orbit, entry_angle_gamma, exit_angle_gamma)
|
||||
current = exit_gamma
|
||||
|
||||
# Final segment to target
|
||||
self._draw_path_line(current, self.ship_end, (100, 200, 255))
|
||||
|
||||
# For comparison, draw direct path (inefficient)
|
||||
direct_start = self._to_screen(*self.ship_start)
|
||||
direct_end = self._to_screen(*self.ship_end)
|
||||
# Draw direct path for comparison
|
||||
direct_line = mcrfpy.Line(
|
||||
start=direct_start, end=direct_end,
|
||||
start=ship_screen, end=target_screen,
|
||||
color=mcrfpy.Color(255, 100, 100, 80),
|
||||
thickness=1
|
||||
)
|
||||
|
|
@ -201,10 +224,8 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
|
||||
def _draw_path_line(self, p1, p2, color):
|
||||
"""Draw a path segment line."""
|
||||
s1 = self._to_screen(p1[0], p1[1])
|
||||
s2 = self._to_screen(p2[0], p2[1])
|
||||
line = mcrfpy.Line(
|
||||
start=s1, end=s2,
|
||||
start=p1, end=p2,
|
||||
color=mcrfpy.Color(*color),
|
||||
thickness=3
|
||||
)
|
||||
|
|
@ -212,81 +233,82 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
|||
|
||||
def _draw_orbit_arc(self, center, radius, start_angle, end_angle):
|
||||
"""Draw an arc showing orbital movement (free movement)."""
|
||||
sx, sy = self._to_screen(center[0], center[1])
|
||||
# Ensure we draw the shorter arc
|
||||
diff = end_angle - start_angle
|
||||
if diff > 180:
|
||||
start_angle, end_angle = end_angle, start_angle
|
||||
elif diff < -180:
|
||||
start_angle, end_angle = end_angle, start_angle
|
||||
|
||||
# Normalize angles for drawing
|
||||
# Screen coordinates have Y inverted, so negate angles
|
||||
arc = mcrfpy.Arc(
|
||||
center=(sx, sy),
|
||||
radius=radius * self.cell_size,
|
||||
start_angle=-start_angle,
|
||||
end_angle=-end_angle,
|
||||
center=center,
|
||||
radius=radius,
|
||||
start_angle=min(start_angle, end_angle),
|
||||
end_angle=max(start_angle, end_angle),
|
||||
color=mcrfpy.Color(255, 255, 100),
|
||||
thickness=4
|
||||
)
|
||||
self.ui.append(arc)
|
||||
|
||||
def _draw_ship_and_target(self):
|
||||
"""Draw ship start and target end positions."""
|
||||
"""Draw ship and target markers."""
|
||||
ship_screen = self._to_screen(*self.ship_start)
|
||||
target_screen = self._to_screen(*self.ship_end)
|
||||
|
||||
# Ship
|
||||
ship_x, ship_y = self._to_screen(*self.ship_start)
|
||||
ship = mcrfpy.Circle(
|
||||
center=(ship_x + self.cell_size//2, ship_y + self.cell_size//2),
|
||||
center=ship_screen,
|
||||
radius=10,
|
||||
fill_color=mcrfpy.Color(255, 200, 100),
|
||||
outline_color=mcrfpy.Color(255, 255, 200),
|
||||
outline=2
|
||||
)
|
||||
self.ui.append(ship)
|
||||
self.add_label("SHIP", ship_x - 10, ship_y + 20, (255, 200, 100))
|
||||
self.add_label("SHIP", ship_screen[0] - 15, ship_screen[1] + 15, (255, 200, 100))
|
||||
|
||||
# Target
|
||||
target_x, target_y = self._to_screen(*self.ship_end)
|
||||
target = mcrfpy.Circle(
|
||||
center=(target_x + self.cell_size//2, target_y + self.cell_size//2),
|
||||
center=target_screen,
|
||||
radius=10,
|
||||
fill_color=mcrfpy.Color(255, 100, 100),
|
||||
outline_color=mcrfpy.Color(255, 200, 200),
|
||||
outline=2
|
||||
)
|
||||
self.ui.append(target)
|
||||
self.add_label("TARGET", target_x - 15, target_y + 20, (255, 100, 100))
|
||||
|
||||
def _draw_legend(self):
|
||||
"""Draw legend explaining the visualization."""
|
||||
leg_x = 50
|
||||
leg_y = 520
|
||||
self.add_label("TARGET", target_screen[0] - 25, target_screen[1] + 15, (255, 100, 100))
|
||||
|
||||
def _draw_legend(self, x, y):
|
||||
"""Draw legend."""
|
||||
# Blue line = movement cost
|
||||
line1 = mcrfpy.Line(
|
||||
start=(leg_x, leg_y + 10), end=(leg_x + 30, leg_y + 10),
|
||||
start=(x, y + 15), end=(x + 40, y + 15),
|
||||
color=mcrfpy.Color(100, 200, 255),
|
||||
thickness=3
|
||||
)
|
||||
self.ui.append(line1)
|
||||
self.add_label("Impulse movement (costs energy)", leg_x + 40, leg_y + 3, (150, 150, 150))
|
||||
self.add_label("Impulse movement (costs energy)", x + 50, y + 8, (150, 150, 150))
|
||||
|
||||
# Yellow arc = free movement
|
||||
arc1 = mcrfpy.Arc(
|
||||
center=(leg_x + 15, leg_y + 45), radius=15,
|
||||
center=(x + 20, y + 50), radius=18,
|
||||
start_angle=0, end_angle=180,
|
||||
color=mcrfpy.Color(255, 255, 100),
|
||||
thickness=3
|
||||
)
|
||||
self.ui.append(arc1)
|
||||
self.add_label("Orbital movement (FREE)", leg_x + 40, leg_y + 35, (255, 255, 100))
|
||||
self.add_label("Orbital movement (FREE)", x + 50, y + 40, (255, 255, 100))
|
||||
|
||||
# Red line = direct (inefficient)
|
||||
# Red line = direct
|
||||
line2 = mcrfpy.Line(
|
||||
start=(leg_x + 300, leg_y + 10), end=(leg_x + 330, leg_y + 10),
|
||||
start=(x + 400, y + 15), end=(x + 440, y + 15),
|
||||
color=mcrfpy.Color(255, 100, 100, 80),
|
||||
thickness=1
|
||||
)
|
||||
self.ui.append(line2)
|
||||
self.add_label("Direct path (for comparison)", leg_x + 340, leg_y + 3, (150, 150, 150))
|
||||
self.add_label("Direct path (comparison)", x + 450, y + 8, (150, 150, 150))
|
||||
|
||||
# Green cells = orbit ring
|
||||
cell1 = mcrfpy.Frame(pos=(leg_x + 300, leg_y + 35), size=(15, 15))
|
||||
cell1 = mcrfpy.Frame(pos=(x + 400, y + 40), size=(15, 15))
|
||||
cell1.fill_color = mcrfpy.Color(40, 100, 40)
|
||||
self.ui.append(cell1)
|
||||
self.add_label("Orbit ring cells (valid ship positions)", leg_x + 320, leg_y + 35, (150, 150, 150))
|
||||
self.add_label("Orbit ring (ship positions)", x + 420, y + 40, (150, 150, 150))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue