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>
|
|
@ -69,28 +69,34 @@ class GeometryDemoRunner:
|
||||||
mcrfpy.createScene("geo_menu")
|
mcrfpy.createScene("geo_menu")
|
||||||
ui = mcrfpy.sceneUI("geo_menu")
|
ui = mcrfpy.sceneUI("geo_menu")
|
||||||
|
|
||||||
|
# Screen dimensions
|
||||||
|
SCREEN_WIDTH = 1024
|
||||||
|
SCREEN_HEIGHT = 768
|
||||||
|
|
||||||
# Background
|
# Background
|
||||||
bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600))
|
bg = mcrfpy.Frame(pos=(0, 0), size=(SCREEN_WIDTH, SCREEN_HEIGHT))
|
||||||
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
ui.append(bg)
|
ui.append(bg)
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
title = mcrfpy.Caption(text="Geometry Module Demo", pos=(400, 30))
|
title = mcrfpy.Caption(text="Geometry Module Demo", pos=(SCREEN_WIDTH // 2, 40))
|
||||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||||
title.outline = 2
|
title.outline = 2
|
||||||
title.outline_color = mcrfpy.Color(0, 0, 0)
|
title.outline_color = mcrfpy.Color(0, 0, 0)
|
||||||
ui.append(title)
|
ui.append(title)
|
||||||
|
|
||||||
subtitle = mcrfpy.Caption(text="Pinships Orbital Mechanics", pos=(400, 70))
|
subtitle = mcrfpy.Caption(text="Pinships Orbital Mechanics", pos=(SCREEN_WIDTH // 2, 80))
|
||||||
subtitle.fill_color = mcrfpy.Color(180, 180, 180)
|
subtitle.fill_color = mcrfpy.Color(180, 180, 180)
|
||||||
ui.append(subtitle)
|
ui.append(subtitle)
|
||||||
|
|
||||||
# Menu items
|
# Menu items - wider buttons centered on 1024 width
|
||||||
|
btn_width = 500
|
||||||
|
btn_x = (SCREEN_WIDTH - btn_width) // 2
|
||||||
for i, screen in enumerate(self.screens):
|
for i, screen in enumerate(self.screens):
|
||||||
y = 130 + i * 60
|
y = 140 + i * 70
|
||||||
|
|
||||||
# Button frame
|
# Button frame
|
||||||
btn = mcrfpy.Frame(pos=(200, y), size=(400, 50))
|
btn = mcrfpy.Frame(pos=(btn_x, y), size=(btn_width, 60))
|
||||||
btn.fill_color = mcrfpy.Color(30, 40, 60)
|
btn.fill_color = mcrfpy.Color(30, 40, 60)
|
||||||
btn.outline = 2
|
btn.outline = 2
|
||||||
btn.outline_color = mcrfpy.Color(80, 100, 150)
|
btn.outline_color = mcrfpy.Color(80, 100, 150)
|
||||||
|
|
@ -102,21 +108,21 @@ class GeometryDemoRunner:
|
||||||
btn.children.append(label)
|
btn.children.append(label)
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
desc = mcrfpy.Caption(text=screen.description, pos=(20, 32))
|
desc = mcrfpy.Caption(text=screen.description, pos=(20, 35))
|
||||||
desc.fill_color = mcrfpy.Color(120, 120, 150)
|
desc.fill_color = mcrfpy.Color(120, 120, 150)
|
||||||
btn.children.append(desc)
|
btn.children.append(desc)
|
||||||
|
|
||||||
# Instructions
|
# Instructions
|
||||||
instr1 = mcrfpy.Caption(text="Press 1-5 to view demos", pos=(300, 480))
|
instr1 = mcrfpy.Caption(text="Press 1-5 to view demos", pos=(SCREEN_WIDTH // 2 - 100, 540))
|
||||||
instr1.fill_color = mcrfpy.Color(150, 150, 150)
|
instr1.fill_color = mcrfpy.Color(150, 150, 150)
|
||||||
ui.append(instr1)
|
ui.append(instr1)
|
||||||
|
|
||||||
instr2 = mcrfpy.Caption(text="ESC = return to menu | Q = quit", pos=(270, 510))
|
instr2 = mcrfpy.Caption(text="ESC = return to menu | Q = quit", pos=(SCREEN_WIDTH // 2 - 130, 580))
|
||||||
instr2.fill_color = mcrfpy.Color(100, 100, 100)
|
instr2.fill_color = mcrfpy.Color(100, 100, 100)
|
||||||
ui.append(instr2)
|
ui.append(instr2)
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
credits = mcrfpy.Caption(text="Geometry module: src/scripts/geometry.py", pos=(250, 560))
|
credits = mcrfpy.Caption(text="Geometry module: src/scripts/geometry.py", pos=(SCREEN_WIDTH // 2 - 150, 700))
|
||||||
credits.fill_color = mcrfpy.Color(80, 80, 100)
|
credits.fill_color = mcrfpy.Color(80, 80, 100)
|
||||||
ui.append(credits)
|
ui.append(credits)
|
||||||
|
|
||||||
|
|
@ -170,12 +176,13 @@ class GeometryDemoRunner:
|
||||||
if key in [f"Num{n}" for n in "123456789"]:
|
if key in [f"Num{n}" for n in "123456789"]:
|
||||||
idx = int(key[-1]) - 1
|
idx = int(key[-1]) - 1
|
||||||
if idx < len(self.screens):
|
if idx < len(self.screens):
|
||||||
# Clean up previous screen's timers
|
# Clean up ALL screen's timers first
|
||||||
for screen in self.screens:
|
for screen in self.screens:
|
||||||
screen.cleanup()
|
screen.cleanup()
|
||||||
|
# Switch to selected scene
|
||||||
mcrfpy.setScene(self.screens[idx].scene_name)
|
mcrfpy.setScene(self.screens[idx].scene_name)
|
||||||
# Re-setup the screen to restart animations
|
# Restart timers for the selected screen
|
||||||
# (timers were cleaned up, need to restart)
|
self.screens[idx].restart_timers()
|
||||||
|
|
||||||
# ESC returns to menu
|
# ESC returns to menu
|
||||||
elif key == "Escape":
|
elif key == "Escape":
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
"""Angle calculation demonstration with Line elements."""
|
"""Angle calculation demonstration with Line elements."""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import math
|
import math
|
||||||
from .base import (GeometryDemoScreen, angle_between, angle_difference,
|
from .base import (GeometryDemoScreen, screen_angle_between, angle_difference,
|
||||||
normalize_angle, point_on_circle, distance)
|
normalize_angle, distance, SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
|
|
||||||
|
|
||||||
class AngleLinesDemo(GeometryDemoScreen):
|
class AngleLinesDemo(GeometryDemoScreen):
|
||||||
|
|
@ -15,34 +15,48 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
self.add_title("Angle Calculations & Line Elements")
|
self.add_title("Angle Calculations & Line Elements")
|
||||||
self.add_description("Computing headings, deviations, and opposite angles for pathfinding")
|
self.add_description("Computing headings, deviations, and opposite angles for pathfinding")
|
||||||
|
|
||||||
# Demo 1: Basic angle between two points
|
margin = 30
|
||||||
self._demo_basic_angle()
|
frame_gap = 20
|
||||||
|
top_area = 80
|
||||||
|
bottom_margin = 30
|
||||||
|
|
||||||
# Demo 2: Angle between three points (deviation)
|
# Calculate frame dimensions for 2x2 layout
|
||||||
self._demo_angle_deviation()
|
frame_width = (SCREEN_WIDTH - 2 * margin - frame_gap) // 2
|
||||||
|
available_height = SCREEN_HEIGHT - top_area - bottom_margin - frame_gap
|
||||||
|
frame_height = available_height // 2
|
||||||
|
|
||||||
# Demo 3: Waypoint viability visualization
|
# Demo 1: Basic angle between two points (top-left)
|
||||||
self._demo_waypoint_viability()
|
self._demo_basic_angle(margin, top_area, frame_width, frame_height)
|
||||||
|
|
||||||
# Demo 4: Orbit exit heading
|
# Demo 2: Angle deviation (top-right)
|
||||||
self._demo_orbit_exit()
|
self._demo_angle_deviation(margin + frame_width + frame_gap, top_area,
|
||||||
|
frame_width, frame_height)
|
||||||
|
|
||||||
def _demo_basic_angle(self):
|
# Demo 3: Multiple waypoints (bottom-left)
|
||||||
|
self._demo_waypoint_viability(margin, top_area + frame_height + frame_gap,
|
||||||
|
frame_width, frame_height)
|
||||||
|
|
||||||
|
# Demo 4: Orbit exit heading (bottom-right)
|
||||||
|
self._demo_orbit_exit(margin + frame_width + frame_gap, top_area + frame_height + frame_gap,
|
||||||
|
frame_width, frame_height)
|
||||||
|
|
||||||
|
def _demo_basic_angle(self, fx, fy, fw, fh):
|
||||||
"""Show angle from point A to point B."""
|
"""Show angle from point A to point B."""
|
||||||
bg = mcrfpy.Frame(pos=(30, 80), size=(350, 200))
|
bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh))
|
||||||
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
bg.outline = 1
|
bg.outline = 1
|
||||||
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
self.ui.append(bg)
|
self.ui.append(bg)
|
||||||
|
|
||||||
self.add_label("Basic Angle Calculation", 50, 85, (255, 200, 100))
|
self.add_label("Basic Angle Calculation", fx + 10, fy + 5, (255, 200, 100))
|
||||||
|
|
||||||
# Point A (origin)
|
# Point A (origin) - lower left area of frame
|
||||||
ax, ay = 100, 180
|
ax, ay = fx + 80, fy + fh - 80
|
||||||
# Point B (target)
|
# Point B (target) - upper right area of frame
|
||||||
bx, by = 300, 120
|
bx, by = fx + fw - 100, fy + 100
|
||||||
|
|
||||||
angle = angle_between((ax, ay), (bx, by))
|
# Calculate angle using screen coordinates
|
||||||
|
angle = screen_angle_between((ax, ay), (bx, by))
|
||||||
dist = distance((ax, ay), (bx, by))
|
dist = distance((ax, ay), (bx, by))
|
||||||
|
|
||||||
# Draw the line A to B (green)
|
# Draw the line A to B (green)
|
||||||
|
|
@ -54,17 +68,19 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
self.ui.append(line_ab)
|
self.ui.append(line_ab)
|
||||||
|
|
||||||
# Draw reference line (east from A) in gray
|
# Draw reference line (east from A) in gray
|
||||||
|
ref_length = 120
|
||||||
line_ref = mcrfpy.Line(
|
line_ref = mcrfpy.Line(
|
||||||
start=(ax, ay), end=(ax + 150, ay),
|
start=(ax, ay), end=(ax + ref_length, ay),
|
||||||
color=mcrfpy.Color(100, 100, 100),
|
color=mcrfpy.Color(100, 100, 100),
|
||||||
thickness=1
|
thickness=1
|
||||||
)
|
)
|
||||||
self.ui.append(line_ref)
|
self.ui.append(line_ref)
|
||||||
|
|
||||||
# Draw arc showing the angle
|
# Draw arc showing the angle (from reference to target line)
|
||||||
|
# Arc goes from 0 degrees (east) to the calculated angle
|
||||||
arc = mcrfpy.Arc(
|
arc = mcrfpy.Arc(
|
||||||
center=(ax, ay), radius=40,
|
center=(ax, ay), radius=50,
|
||||||
start_angle=0, end_angle=-angle, # Negative because screen Y is inverted
|
start_angle=0, end_angle=angle,
|
||||||
color=mcrfpy.Color(255, 255, 100),
|
color=mcrfpy.Color(255, 255, 100),
|
||||||
thickness=2
|
thickness=2
|
||||||
)
|
)
|
||||||
|
|
@ -79,30 +95,30 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
self.ui.append(point_b)
|
self.ui.append(point_b)
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
self.add_label("A", ax - 20, ay - 5, (255, 100, 100))
|
self.add_label("A", ax - 20, ay + 5, (255, 100, 100))
|
||||||
self.add_label("B", bx + 10, by - 5, (100, 255, 100))
|
self.add_label("B", bx + 12, by - 5, (100, 255, 100))
|
||||||
self.add_label(f"Angle: {angle:.1f}°", 50, 250, (255, 255, 100))
|
self.add_label(f"Angle: {angle:.1f} deg", fx + 10, fy + fh - 45, (255, 255, 100))
|
||||||
self.add_label(f"Distance: {dist:.1f}", 180, 250, (150, 150, 150))
|
self.add_label(f"Distance: {dist:.1f}", fx + 10, fy + fh - 25, (150, 150, 150))
|
||||||
|
|
||||||
def _demo_angle_deviation(self):
|
def _demo_angle_deviation(self, fx, fy, fw, fh):
|
||||||
"""Show angle deviation when considering a waypoint."""
|
"""Show angle deviation when considering a waypoint."""
|
||||||
bg = mcrfpy.Frame(pos=(400, 80), size=(380, 200))
|
bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh))
|
||||||
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
bg.outline = 1
|
bg.outline = 1
|
||||||
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
self.ui.append(bg)
|
self.ui.append(bg)
|
||||||
|
|
||||||
self.add_label("Waypoint Deviation", 420, 85, (255, 200, 100))
|
self.add_label("Waypoint Deviation", fx + 10, fy + 5, (255, 200, 100))
|
||||||
self.add_label("Is planet C a useful waypoint from A to B?", 420, 105, (150, 150, 150))
|
self.add_label("Is planet C useful from A to B?", fx + 10, fy + 25, (150, 150, 150))
|
||||||
|
|
||||||
# Ship at A, target at B, potential waypoint C
|
# Ship at A, target at B, potential waypoint C
|
||||||
ax, ay = 450, 230
|
ax, ay = fx + 60, fy + fh - 100
|
||||||
bx, by = 720, 180
|
bx, by = fx + fw - 60, fy + fh - 60
|
||||||
cx, cy = 550, 150
|
cx, cy = fx + fw // 2, fy + 100
|
||||||
|
|
||||||
# Calculate angles
|
# Calculate angles using screen coordinates
|
||||||
angle_to_target = angle_between((ax, ay), (bx, by))
|
angle_to_target = screen_angle_between((ax, ay), (bx, by))
|
||||||
angle_to_waypoint = angle_between((ax, ay), (cx, cy))
|
angle_to_waypoint = screen_angle_between((ax, ay), (cx, cy))
|
||||||
deviation = abs(angle_difference(angle_to_target, angle_to_waypoint))
|
deviation = abs(angle_difference(angle_to_target, angle_to_waypoint))
|
||||||
|
|
||||||
# Draw line A to B (direct path - green)
|
# Draw line A to B (direct path - green)
|
||||||
|
|
@ -113,21 +129,28 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
)
|
)
|
||||||
self.ui.append(line_ab)
|
self.ui.append(line_ab)
|
||||||
|
|
||||||
# Draw line A to C (waypoint path - yellow if viable, red if not)
|
# Draw line A to C (waypoint path)
|
||||||
viable = deviation <= 45
|
viable = deviation <= 45
|
||||||
waypoint_color = mcrfpy.Color(255, 255, 100) if viable else mcrfpy.Color(255, 100, 100)
|
waypoint_color = (255, 255, 100) if viable else (255, 100, 100)
|
||||||
line_ac = mcrfpy.Line(
|
line_ac = mcrfpy.Line(
|
||||||
start=(ax, ay), end=(cx, cy),
|
start=(ax, ay), end=(cx, cy),
|
||||||
color=waypoint_color,
|
color=mcrfpy.Color(*waypoint_color),
|
||||||
thickness=2
|
thickness=2
|
||||||
)
|
)
|
||||||
self.ui.append(line_ac)
|
self.ui.append(line_ac)
|
||||||
|
|
||||||
# Draw deviation arc
|
# Draw arc showing the deviation angle between the two directions
|
||||||
|
# Arc should go from angle_to_target to angle_to_waypoint
|
||||||
|
start_ang = min(angle_to_target, angle_to_waypoint)
|
||||||
|
end_ang = max(angle_to_target, angle_to_waypoint)
|
||||||
|
# If the arc would be > 180, we need to go the other way
|
||||||
|
if end_ang - start_ang > 180:
|
||||||
|
start_ang, end_ang = end_ang, start_ang + 360
|
||||||
|
|
||||||
arc = mcrfpy.Arc(
|
arc = mcrfpy.Arc(
|
||||||
center=(ax, ay), radius=50,
|
center=(ax, ay), radius=50,
|
||||||
start_angle=-angle_to_target, end_angle=-angle_to_waypoint,
|
start_angle=start_ang, end_angle=end_ang,
|
||||||
color=waypoint_color,
|
color=mcrfpy.Color(*waypoint_color),
|
||||||
thickness=2
|
thickness=2
|
||||||
)
|
)
|
||||||
self.ui.append(arc)
|
self.ui.append(arc)
|
||||||
|
|
@ -145,30 +168,31 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
self.ui.append(point_b)
|
self.ui.append(point_b)
|
||||||
self.ui.append(point_c)
|
self.ui.append(point_c)
|
||||||
|
|
||||||
# Labels
|
# Labels - positioned to avoid overlap
|
||||||
self.add_label("A (ship)", ax - 10, ay + 10, (255, 100, 100))
|
self.add_label("A (ship)", ax - 15, ay + 15, (255, 100, 100))
|
||||||
self.add_label("B (target)", bx - 20, by + 15, (100, 255, 100))
|
self.add_label("B (target)", bx - 30, by + 15, (100, 255, 100))
|
||||||
self.add_label("C (planet)", cx + 15, cy - 5, (150, 150, 255))
|
self.add_label("C (planet)", cx + 15, cy - 10, (150, 150, 255))
|
||||||
label_color = (255, 255, 100) if viable else (255, 100, 100)
|
|
||||||
self.add_label(f"Deviation: {deviation:.1f}°", 550, 250, label_color)
|
|
||||||
status = "VIABLE (<45°)" if viable else "NOT VIABLE (>45°)"
|
|
||||||
self.add_label(status, 680, 250, label_color)
|
|
||||||
|
|
||||||
def _demo_waypoint_viability(self):
|
# Status at bottom
|
||||||
|
self.add_label(f"Deviation: {deviation:.1f} deg", fx + 10, fy + fh - 45, waypoint_color)
|
||||||
|
status = "VIABLE (<45 deg)" if viable else "NOT VIABLE (>45 deg)"
|
||||||
|
self.add_label(status, fx + 200, fy + fh - 45, waypoint_color)
|
||||||
|
|
||||||
|
def _demo_waypoint_viability(self, fx, fy, fw, fh):
|
||||||
"""Show multiple potential waypoints with viability indicators."""
|
"""Show multiple potential waypoints with viability indicators."""
|
||||||
bg = mcrfpy.Frame(pos=(30, 300), size=(350, 280))
|
bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh))
|
||||||
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
bg.outline = 1
|
bg.outline = 1
|
||||||
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
self.ui.append(bg)
|
self.ui.append(bg)
|
||||||
|
|
||||||
self.add_label("Multiple Waypoint Analysis", 50, 305, (255, 200, 100))
|
self.add_label("Multiple Waypoint Analysis", fx + 10, fy + 5, (255, 200, 100))
|
||||||
|
|
||||||
# Ship and target
|
# Ship and target positions
|
||||||
ax, ay = 80, 450
|
ax, ay = fx + 60, fy + fh - 80
|
||||||
bx, by = 320, 380
|
bx, by = fx + fw - 80, fy + fh // 2
|
||||||
|
|
||||||
angle_to_target = angle_between((ax, ay), (bx, by))
|
angle_to_target = screen_angle_between((ax, ay), (bx, by))
|
||||||
|
|
||||||
# Draw direct path
|
# Draw direct path
|
||||||
line_ab = mcrfpy.Line(
|
line_ab = mcrfpy.Line(
|
||||||
|
|
@ -178,26 +202,25 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
)
|
)
|
||||||
self.ui.append(line_ab)
|
self.ui.append(line_ab)
|
||||||
|
|
||||||
# Potential waypoints at various angles
|
# Potential waypoints at various positions
|
||||||
waypoints = [
|
waypoints = [
|
||||||
(150, 360, "W1"), # Ahead and left - viable
|
(fx + 180, fy + 80, "W1"), # Upper area
|
||||||
(200, 500, "W2"), # Below path - marginal
|
(fx + 280, fy + fh - 60, "W2"), # Right of path
|
||||||
(100, 540, "W3"), # Behind - not viable
|
(fx + 80, fy + fh - 150, "W3"), # Left/behind
|
||||||
(250, 340, "W4"), # Almost on path - very viable
|
(fx + fw - 150, fy + fh // 2 - 30, "W4"), # Near target
|
||||||
]
|
]
|
||||||
|
|
||||||
threshold = 45
|
threshold = 45
|
||||||
for wx, wy, label in waypoints:
|
for wx, wy, label in waypoints:
|
||||||
angle_to_wp = angle_between((ax, ay), (wx, wy))
|
angle_to_wp = screen_angle_between((ax, ay), (wx, wy))
|
||||||
deviation = abs(angle_difference(angle_to_target, angle_to_wp))
|
deviation = abs(angle_difference(angle_to_target, angle_to_wp))
|
||||||
viable = deviation <= threshold
|
viable = deviation <= threshold
|
||||||
|
|
||||||
# Line to waypoint
|
# Line to waypoint
|
||||||
color_tuple = (100, 255, 100) if viable else (255, 100, 100)
|
color_tuple = (100, 255, 100) if viable else (255, 100, 100)
|
||||||
color = mcrfpy.Color(*color_tuple)
|
|
||||||
line = mcrfpy.Line(
|
line = mcrfpy.Line(
|
||||||
start=(ax, ay), end=(wx, wy),
|
start=(ax, ay), end=(wx, wy),
|
||||||
color=color,
|
color=mcrfpy.Color(*color_tuple),
|
||||||
thickness=1
|
thickness=1
|
||||||
)
|
)
|
||||||
self.ui.append(line)
|
self.ui.append(line)
|
||||||
|
|
@ -206,12 +229,12 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
wp_circle = mcrfpy.Circle(
|
wp_circle = mcrfpy.Circle(
|
||||||
center=(wx, wy), radius=15,
|
center=(wx, wy), radius=15,
|
||||||
fill_color=mcrfpy.Color(80, 80, 120),
|
fill_color=mcrfpy.Color(80, 80, 120),
|
||||||
outline_color=color,
|
outline_color=mcrfpy.Color(*color_tuple),
|
||||||
outline=2
|
outline=2
|
||||||
)
|
)
|
||||||
self.ui.append(wp_circle)
|
self.ui.append(wp_circle)
|
||||||
|
|
||||||
self.add_label(f"{label}:{deviation:.0f}°", wx + 18, wy - 8, color_tuple)
|
self.add_label(f"{label}:{deviation:.0f}", wx + 18, wy - 8, color_tuple)
|
||||||
|
|
||||||
# Ship and target markers
|
# Ship and target markers
|
||||||
ship = mcrfpy.Circle(center=(ax, ay), radius=8,
|
ship = mcrfpy.Circle(center=(ax, ay), radius=8,
|
||||||
|
|
@ -221,33 +244,38 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
self.ui.append(ship)
|
self.ui.append(ship)
|
||||||
self.ui.append(target)
|
self.ui.append(target)
|
||||||
|
|
||||||
self.add_label("Ship", ax - 5, ay + 12, (255, 200, 100))
|
self.add_label("Ship", ax - 10, ay + 12, (255, 200, 100))
|
||||||
self.add_label("Target", bx - 15, by + 12, (100, 255, 100))
|
self.add_label("Target", bx - 20, by + 12, (100, 255, 100))
|
||||||
self.add_label(f"Threshold: {threshold}°", 50, 555, (150, 150, 150))
|
self.add_label(f"Threshold: {threshold} deg", fx + 10, fy + fh - 25, (150, 150, 150))
|
||||||
|
|
||||||
def _demo_orbit_exit(self):
|
def _demo_orbit_exit(self, fx, fy, fw, fh):
|
||||||
"""Show optimal orbit exit heading toward target."""
|
"""Show optimal orbit exit heading toward target."""
|
||||||
bg = mcrfpy.Frame(pos=(400, 300), size=(380, 280))
|
bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh))
|
||||||
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
bg.outline = 1
|
bg.outline = 1
|
||||||
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
self.ui.append(bg)
|
self.ui.append(bg)
|
||||||
|
|
||||||
self.add_label("Orbit Exit Heading", 420, 305, (255, 200, 100))
|
self.add_label("Orbit Exit Heading", fx + 10, fy + 5, (255, 200, 100))
|
||||||
self.add_label("Ship in orbit chooses optimal exit point", 420, 325, (150, 150, 150))
|
self.add_label("Ship repositions FREE in orbit", fx + 10, fy + 25, (150, 150, 150))
|
||||||
|
|
||||||
# Planet center and orbit
|
# Planet center and orbit
|
||||||
px, py = 520, 450
|
px, py = fx + fw // 3, fy + fh // 2
|
||||||
orbit_radius = 60
|
orbit_radius = 70
|
||||||
surface_radius = 25
|
surface_radius = 30
|
||||||
|
|
||||||
# Target position
|
# Target position
|
||||||
tx, ty = 720, 380
|
tx, ty = fx + fw - 80, fy + 100
|
||||||
|
|
||||||
# Calculate optimal exit angle
|
# Calculate optimal exit angle (toward target in screen coords)
|
||||||
exit_angle = angle_between((px, py), (tx, ty))
|
exit_angle = screen_angle_between((px, py), (tx, ty))
|
||||||
exit_x = px + orbit_radius * math.cos(math.radians(exit_angle))
|
exit_x = px + orbit_radius * math.cos(math.radians(exit_angle))
|
||||||
exit_y = py - orbit_radius * math.sin(math.radians(exit_angle)) # Flip for screen coords
|
exit_y = py - orbit_radius * math.sin(math.radians(exit_angle)) # Negate for screen Y
|
||||||
|
|
||||||
|
# Ship's current position on orbit (arbitrary starting position)
|
||||||
|
ship_angle = exit_angle + 120 # 120 degrees away from exit
|
||||||
|
ship_x = px + orbit_radius * math.cos(math.radians(ship_angle))
|
||||||
|
ship_y = py - orbit_radius * math.sin(math.radians(ship_angle))
|
||||||
|
|
||||||
# Draw planet surface
|
# Draw planet surface
|
||||||
planet = mcrfpy.Circle(
|
planet = mcrfpy.Circle(
|
||||||
|
|
@ -267,27 +295,25 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
)
|
)
|
||||||
self.ui.append(orbit)
|
self.ui.append(orbit)
|
||||||
|
|
||||||
# Draw ship positions around orbit (current position)
|
# Draw arc showing orbital movement from ship to exit (FREE movement)
|
||||||
ship_angle = 200 # Current position
|
# Arc goes from ship_angle to exit_angle
|
||||||
ship_x = px + orbit_radius * math.cos(math.radians(ship_angle))
|
start_ang = min(ship_angle, exit_angle)
|
||||||
ship_y = py - orbit_radius * math.sin(math.radians(ship_angle))
|
end_ang = max(ship_angle, exit_angle)
|
||||||
|
orbit_arc = mcrfpy.Arc(
|
||||||
|
center=(px, py), radius=orbit_radius,
|
||||||
|
start_angle=start_ang, end_angle=end_ang,
|
||||||
|
color=mcrfpy.Color(255, 255, 100),
|
||||||
|
thickness=4
|
||||||
|
)
|
||||||
|
self.ui.append(orbit_arc)
|
||||||
|
|
||||||
|
# Draw ship
|
||||||
ship = mcrfpy.Circle(
|
ship = mcrfpy.Circle(
|
||||||
center=(ship_x, ship_y), radius=8,
|
center=(ship_x, ship_y), radius=8,
|
||||||
fill_color=mcrfpy.Color(255, 200, 100)
|
fill_color=mcrfpy.Color(255, 200, 100)
|
||||||
)
|
)
|
||||||
self.ui.append(ship)
|
self.ui.append(ship)
|
||||||
|
|
||||||
# Draw path: ship moves along orbit (free) to exit point
|
|
||||||
# Arc from ship position to exit position
|
|
||||||
orbit_arc = mcrfpy.Arc(
|
|
||||||
center=(px, py), radius=orbit_radius,
|
|
||||||
start_angle=-ship_angle, end_angle=-exit_angle,
|
|
||||||
color=mcrfpy.Color(255, 255, 100),
|
|
||||||
thickness=3
|
|
||||||
)
|
|
||||||
self.ui.append(orbit_arc)
|
|
||||||
|
|
||||||
# Draw exit point
|
# Draw exit point
|
||||||
exit_point = mcrfpy.Circle(
|
exit_point = mcrfpy.Circle(
|
||||||
center=(exit_x, exit_y), radius=6,
|
center=(exit_x, exit_y), radius=6,
|
||||||
|
|
@ -310,10 +336,12 @@ class AngleLinesDemo(GeometryDemoScreen):
|
||||||
)
|
)
|
||||||
self.ui.append(target)
|
self.ui.append(target)
|
||||||
|
|
||||||
# Labels
|
# Labels - positioned to avoid overlap
|
||||||
self.add_label("Planet", px - 20, py + surface_radius + 5, (100, 150, 220))
|
self.add_label("Planet", px - 20, py + surface_radius + 5, (100, 150, 220))
|
||||||
self.add_label("Ship", ship_x - 25, ship_y - 15, (255, 200, 100))
|
self.add_label("Ship", ship_x - 30, ship_y - 15, (255, 200, 100))
|
||||||
self.add_label("Exit", exit_x + 10, exit_y - 10, (100, 255, 100))
|
self.add_label("Exit", exit_x + 10, exit_y - 15, (100, 255, 100))
|
||||||
self.add_label("Target", tx - 15, ty + 15, (255, 100, 100))
|
self.add_label("Target", tx - 20, ty + 15, (255, 100, 100))
|
||||||
self.add_label(f"Exit angle: {exit_angle:.1f}°", 420, 555, (150, 150, 150))
|
|
||||||
self.add_label("Yellow arc = free orbital movement", 550, 555, (255, 255, 100))
|
# Info at bottom
|
||||||
|
self.add_label(f"Exit angle: {exit_angle:.1f} deg", fx + 10, fy + fh - 45, (150, 150, 150))
|
||||||
|
self.add_label("Yellow = FREE orbital move", fx + 200, fy + fh - 45, (255, 255, 100))
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,30 @@
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import math
|
||||||
|
|
||||||
# Add scripts path for geometry module
|
# Add scripts path for geometry module
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src', 'scripts'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src', 'scripts'))
|
||||||
from geometry import *
|
from geometry import *
|
||||||
|
|
||||||
|
# Screen resolution
|
||||||
|
SCREEN_WIDTH = 1024
|
||||||
|
SCREEN_HEIGHT = 768
|
||||||
|
|
||||||
|
|
||||||
|
def screen_angle_between(p1, p2):
|
||||||
|
"""
|
||||||
|
Calculate angle from p1 to p2 in screen coordinates.
|
||||||
|
In screen coords, Y increases downward, so we negate dy.
|
||||||
|
Returns angle in degrees where 0=right, 90=up, 180=left, 270=down.
|
||||||
|
"""
|
||||||
|
dx = p2[0] - p1[0]
|
||||||
|
dy = p1[1] - p2[1] # Negate because screen Y is inverted
|
||||||
|
angle = math.degrees(math.atan2(dy, dx))
|
||||||
|
if angle < 0:
|
||||||
|
angle += 360
|
||||||
|
return angle
|
||||||
|
|
||||||
|
|
||||||
class GeometryDemoScreen:
|
class GeometryDemoScreen:
|
||||||
"""Base class for geometry demo screens."""
|
"""Base class for geometry demo screens."""
|
||||||
|
|
@ -19,6 +38,7 @@ class GeometryDemoScreen:
|
||||||
mcrfpy.createScene(scene_name)
|
mcrfpy.createScene(scene_name)
|
||||||
self.ui = mcrfpy.sceneUI(scene_name)
|
self.ui = mcrfpy.sceneUI(scene_name)
|
||||||
self.timers = [] # Track timer names for cleanup
|
self.timers = [] # Track timer names for cleanup
|
||||||
|
self._timer_configs = [] # Store timer configs for restart
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""Override to set up the screen content."""
|
"""Override to set up the screen content."""
|
||||||
|
|
@ -32,13 +52,21 @@ class GeometryDemoScreen:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def restart_timers(self):
|
||||||
|
"""Re-register timers after cleanup."""
|
||||||
|
for name, callback, interval in self._timer_configs:
|
||||||
|
try:
|
||||||
|
mcrfpy.setTimer(name, callback, interval)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Timer restart failed: {e}")
|
||||||
|
|
||||||
def get_screenshot_name(self):
|
def get_screenshot_name(self):
|
||||||
"""Return the screenshot filename for this screen."""
|
"""Return the screenshot filename for this screen."""
|
||||||
return f"{self.scene_name}.png"
|
return f"{self.scene_name}.png"
|
||||||
|
|
||||||
def add_title(self, text, y=10):
|
def add_title(self, text, y=10):
|
||||||
"""Add a title caption."""
|
"""Add a title caption centered at top."""
|
||||||
title = mcrfpy.Caption(text=text, pos=(400, y))
|
title = mcrfpy.Caption(text=text, pos=(SCREEN_WIDTH // 2, y))
|
||||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||||
title.outline = 2
|
title.outline = 2
|
||||||
title.outline_color = mcrfpy.Color(0, 0, 0)
|
title.outline_color = mcrfpy.Color(0, 0, 0)
|
||||||
|
|
@ -79,6 +107,10 @@ class GeometryDemoScreen:
|
||||||
pass # Out of bounds
|
pass # Out of bounds
|
||||||
|
|
||||||
def add_timer(self, name, callback, interval):
|
def add_timer(self, name, callback, interval):
|
||||||
"""Add a timer and track it for cleanup."""
|
"""Add a timer and track it for cleanup/restart."""
|
||||||
|
if callback is None:
|
||||||
|
print(f"Warning: Timer '{name}' callback is None, skipping")
|
||||||
|
return
|
||||||
mcrfpy.setTimer(name, callback, interval)
|
mcrfpy.setTimer(name, callback, interval)
|
||||||
self.timers.append(name)
|
self.timers.append(name)
|
||||||
|
self._timer_configs.append((name, callback, interval))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""Bresenham circle algorithm demonstration on a grid."""
|
"""Bresenham circle algorithm demonstration on a grid."""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
from .base import GeometryDemoScreen, bresenham_circle, bresenham_line, filled_circle
|
import math
|
||||||
|
from .base import GeometryDemoScreen, bresenham_circle, bresenham_line, filled_circle, SCREEN_WIDTH, SCREEN_HEIGHT
|
||||||
|
|
||||||
|
|
||||||
class BresenhamDemo(GeometryDemoScreen):
|
class BresenhamDemo(GeometryDemoScreen):
|
||||||
|
|
@ -13,48 +14,71 @@ class BresenhamDemo(GeometryDemoScreen):
|
||||||
self.add_title("Bresenham Circle & Line Algorithms")
|
self.add_title("Bresenham Circle & Line Algorithms")
|
||||||
self.add_description("Grid-aligned geometric primitives for orbit rings and LOS calculations")
|
self.add_description("Grid-aligned geometric primitives for orbit rings and LOS calculations")
|
||||||
|
|
||||||
# Create a grid for circle demo
|
|
||||||
grid_w, grid_h = 25, 18
|
|
||||||
cell_size = 16
|
cell_size = 16
|
||||||
|
margin = 30
|
||||||
|
frame_gap = 20
|
||||||
|
|
||||||
# We need a texture for the grid - create a simple one
|
# Calculate frame dimensions for 2x2 layout
|
||||||
# Actually, let's use Grid's built-in cell coloring via GridPoint
|
# Available width: 1024 - 2*margin = 964, split into 2 with gap
|
||||||
|
frame_width = (SCREEN_WIDTH - 2 * margin - frame_gap) // 2 # ~472 each
|
||||||
|
# Available height for frames: 768 - 80 (top) - 30 (bottom margin)
|
||||||
|
top_area = 80
|
||||||
|
bottom_margin = 30
|
||||||
|
available_height = SCREEN_HEIGHT - top_area - bottom_margin - frame_gap
|
||||||
|
frame_height = available_height // 2 # ~314 each
|
||||||
|
|
||||||
# Create display area with Frame background
|
# Top-left: Bresenham Circle
|
||||||
bg1 = mcrfpy.Frame(pos=(30, 80), size=(420, 310))
|
self._draw_circle_demo(margin, top_area, frame_width, frame_height, cell_size)
|
||||||
bg1.fill_color = mcrfpy.Color(15, 15, 25)
|
|
||||||
bg1.outline = 1
|
|
||||||
bg1.outline_color = mcrfpy.Color(60, 60, 100)
|
|
||||||
self.ui.append(bg1)
|
|
||||||
|
|
||||||
self.add_label("Bresenham Circle (radius=8)", 50, 85, (255, 200, 100))
|
# Top-right: Bresenham Lines
|
||||||
self.add_label("Center: (12, 9)", 50, 105, (150, 150, 150))
|
self._draw_lines_demo(margin + frame_width + frame_gap, top_area, frame_width, frame_height, cell_size)
|
||||||
|
|
||||||
|
# Bottom-left: Filled Circle
|
||||||
|
self._draw_filled_demo(margin, top_area + frame_height + frame_gap, frame_width, frame_height, cell_size)
|
||||||
|
|
||||||
|
# Bottom-right: Planet + Orbit Ring
|
||||||
|
self._draw_combined_demo(margin + frame_width + frame_gap, top_area + frame_height + frame_gap,
|
||||||
|
frame_width, frame_height, cell_size)
|
||||||
|
|
||||||
|
def _draw_circle_demo(self, x, y, w, h, cell_size):
|
||||||
|
"""Draw Bresenham circle demonstration."""
|
||||||
|
bg = mcrfpy.Frame(pos=(x, y), size=(w, h))
|
||||||
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
|
bg.outline = 1
|
||||||
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
|
self.ui.append(bg)
|
||||||
|
|
||||||
|
self.add_label("Bresenham Circle (radius=8)", x + 10, y + 5, (255, 200, 100))
|
||||||
|
self.add_label("Center: (12, 9)", x + 10, y + 25, (150, 150, 150))
|
||||||
|
|
||||||
|
# Grid origin for this demo
|
||||||
|
grid_x = x + 20
|
||||||
|
grid_y = y + 50
|
||||||
|
|
||||||
# Draw circle using UICircle primitives to show the cells
|
|
||||||
center = (12, 9)
|
center = (12, 9)
|
||||||
radius = 8
|
radius = 8
|
||||||
circle_cells = bresenham_circle(center, radius)
|
circle_cells = bresenham_circle(center, radius)
|
||||||
|
|
||||||
# Draw each cell as a small rectangle
|
# Draw each cell
|
||||||
for x, y in circle_cells:
|
for cx, cy in circle_cells:
|
||||||
px = 40 + x * cell_size
|
px = grid_x + cx * cell_size
|
||||||
py = 120 + y * cell_size
|
py = grid_y + cy * cell_size
|
||||||
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
||||||
cell_rect.fill_color = mcrfpy.Color(100, 200, 255)
|
cell_rect.fill_color = mcrfpy.Color(100, 200, 255)
|
||||||
cell_rect.outline = 0
|
cell_rect.outline = 0
|
||||||
self.ui.append(cell_rect)
|
self.ui.append(cell_rect)
|
||||||
|
|
||||||
# Draw center point
|
# Draw center point
|
||||||
cx_px = 40 + center[0] * cell_size
|
cx_px = grid_x + center[0] * cell_size
|
||||||
cy_px = 120 + center[1] * cell_size
|
cy_px = grid_y + center[1] * cell_size
|
||||||
center_rect = mcrfpy.Frame(pos=(cx_px, cy_px), size=(cell_size - 1, cell_size - 1))
|
center_rect = mcrfpy.Frame(pos=(cx_px, cy_px), size=(cell_size - 1, cell_size - 1))
|
||||||
center_rect.fill_color = mcrfpy.Color(255, 100, 100)
|
center_rect.fill_color = mcrfpy.Color(255, 100, 100)
|
||||||
self.ui.append(center_rect)
|
self.ui.append(center_rect)
|
||||||
|
|
||||||
# Draw the actual circle outline for comparison
|
# Draw actual circle outline for comparison (centered on cells)
|
||||||
actual_circle = mcrfpy.Circle(
|
actual_circle = mcrfpy.Circle(
|
||||||
center=(40 + center[0] * cell_size + cell_size // 2,
|
center=(grid_x + center[0] * cell_size + cell_size // 2,
|
||||||
120 + center[1] * cell_size + cell_size // 2),
|
grid_y + center[1] * cell_size + cell_size // 2),
|
||||||
radius=radius * cell_size,
|
radius=radius * cell_size,
|
||||||
fill_color=mcrfpy.Color(0, 0, 0, 0),
|
fill_color=mcrfpy.Color(0, 0, 0, 0),
|
||||||
outline_color=mcrfpy.Color(255, 255, 100, 128),
|
outline_color=mcrfpy.Color(255, 255, 100, 128),
|
||||||
|
|
@ -62,14 +86,18 @@ class BresenhamDemo(GeometryDemoScreen):
|
||||||
)
|
)
|
||||||
self.ui.append(actual_circle)
|
self.ui.append(actual_circle)
|
||||||
|
|
||||||
# Second demo: Bresenham line
|
def _draw_lines_demo(self, x, y, w, h, cell_size):
|
||||||
bg2 = mcrfpy.Frame(pos=(470, 80), size=(310, 310))
|
"""Draw Bresenham lines demonstration."""
|
||||||
bg2.fill_color = mcrfpy.Color(15, 15, 25)
|
bg = mcrfpy.Frame(pos=(x, y), size=(w, h))
|
||||||
bg2.outline = 1
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
bg2.outline_color = mcrfpy.Color(60, 60, 100)
|
bg.outline = 1
|
||||||
self.ui.append(bg2)
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
|
self.ui.append(bg)
|
||||||
|
|
||||||
self.add_label("Bresenham Lines", 490, 85, (255, 200, 100))
|
self.add_label("Bresenham Lines", x + 10, y + 5, (255, 200, 100))
|
||||||
|
|
||||||
|
grid_x = x + 20
|
||||||
|
grid_y = y + 40
|
||||||
|
|
||||||
# Draw multiple lines at different angles
|
# Draw multiple lines at different angles
|
||||||
lines_data = [
|
lines_data = [
|
||||||
|
|
@ -80,91 +108,100 @@ class BresenhamDemo(GeometryDemoScreen):
|
||||||
|
|
||||||
for start, end, color in lines_data:
|
for start, end, color in lines_data:
|
||||||
line_cells = bresenham_line(start, end)
|
line_cells = bresenham_line(start, end)
|
||||||
for x, y in line_cells:
|
for cx, cy in line_cells:
|
||||||
px = 480 + x * cell_size
|
px = grid_x + cx * cell_size
|
||||||
py = 110 + y * cell_size
|
py = grid_y + cy * cell_size
|
||||||
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
||||||
cell_rect.fill_color = mcrfpy.Color(*color)
|
cell_rect.fill_color = mcrfpy.Color(*color)
|
||||||
self.ui.append(cell_rect)
|
self.ui.append(cell_rect)
|
||||||
|
|
||||||
# Draw the actual line for comparison
|
# Draw the actual line for comparison (through cell centers)
|
||||||
line = mcrfpy.Line(
|
line = mcrfpy.Line(
|
||||||
start=(480 + start[0] * cell_size + cell_size // 2,
|
start=(grid_x + start[0] * cell_size + cell_size // 2,
|
||||||
110 + start[1] * cell_size + cell_size // 2),
|
grid_y + start[1] * cell_size + cell_size // 2),
|
||||||
end=(480 + end[0] * cell_size + cell_size // 2,
|
end=(grid_x + end[0] * cell_size + cell_size // 2,
|
||||||
110 + end[1] * cell_size + cell_size // 2),
|
grid_y + end[1] * cell_size + cell_size // 2),
|
||||||
color=mcrfpy.Color(255, 255, 255, 128),
|
color=mcrfpy.Color(255, 255, 255, 128),
|
||||||
thickness=1
|
thickness=1
|
||||||
)
|
)
|
||||||
self.ui.append(line)
|
self.ui.append(line)
|
||||||
|
|
||||||
# Third demo: Filled circle (planet surface)
|
def _draw_filled_demo(self, x, y, w, h, cell_size):
|
||||||
bg3 = mcrfpy.Frame(pos=(30, 410), size=(200, 170))
|
"""Draw filled circle demonstration."""
|
||||||
bg3.fill_color = mcrfpy.Color(15, 15, 25)
|
bg = mcrfpy.Frame(pos=(x, y), size=(w, h))
|
||||||
bg3.outline = 1
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
bg3.outline_color = mcrfpy.Color(60, 60, 100)
|
bg.outline = 1
|
||||||
self.ui.append(bg3)
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
|
self.ui.append(bg)
|
||||||
|
|
||||||
self.add_label("Filled Circle (radius=4)", 50, 415, (255, 200, 100))
|
self.add_label("Filled Circle (radius=5)", x + 10, y + 5, (255, 200, 100))
|
||||||
self.add_label("Planet surface representation", 50, 435, (150, 150, 150))
|
self.add_label("Planet surface representation", x + 10, y + 25, (150, 150, 150))
|
||||||
|
|
||||||
fill_center = (6, 5)
|
grid_x = x + 50
|
||||||
fill_radius = 4
|
grid_y = y + 60
|
||||||
|
|
||||||
|
fill_center = (8, 8)
|
||||||
|
fill_radius = 5
|
||||||
filled_cells = filled_circle(fill_center, fill_radius)
|
filled_cells = filled_circle(fill_center, fill_radius)
|
||||||
|
|
||||||
for x, y in filled_cells:
|
for cx, cy in filled_cells:
|
||||||
px = 40 + x * cell_size
|
px = grid_x + cx * cell_size
|
||||||
py = 460 + y * cell_size
|
py = grid_y + cy * cell_size
|
||||||
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
|
||||||
# Gradient based on distance from center
|
# Gradient based on distance from center
|
||||||
dist = ((x - fill_center[0])**2 + (y - fill_center[1])**2) ** 0.5
|
dist = math.sqrt((cx - fill_center[0])**2 + (cy - fill_center[1])**2)
|
||||||
intensity = int(255 * (1 - dist / (fill_radius + 1)))
|
intensity = int(255 * (1 - dist / (fill_radius + 1)))
|
||||||
|
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
||||||
cell_rect.fill_color = mcrfpy.Color(intensity, intensity // 2, 50)
|
cell_rect.fill_color = mcrfpy.Color(intensity, intensity // 2, 50)
|
||||||
self.ui.append(cell_rect)
|
self.ui.append(cell_rect)
|
||||||
|
|
||||||
# Fourth demo: Combined - planet with orbit ring
|
def _draw_combined_demo(self, x, y, w, h, cell_size):
|
||||||
bg4 = mcrfpy.Frame(pos=(250, 410), size=(530, 170))
|
"""Draw planet + orbit ring demonstration."""
|
||||||
bg4.fill_color = mcrfpy.Color(15, 15, 25)
|
bg = mcrfpy.Frame(pos=(x, y), size=(w, h))
|
||||||
bg4.outline = 1
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
||||||
bg4.outline_color = mcrfpy.Color(60, 60, 100)
|
bg.outline = 1
|
||||||
self.ui.append(bg4)
|
bg.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
|
self.ui.append(bg)
|
||||||
|
|
||||||
self.add_label("Planet + Orbit Ring", 270, 415, (255, 200, 100))
|
self.add_label("Planet + Orbit Ring", x + 10, y + 5, (255, 200, 100))
|
||||||
self.add_label("Surface (r=3) + Orbit (r=7)", 270, 435, (150, 150, 150))
|
self.add_label("Surface (r=3) + Orbit (r=8)", x + 10, y + 25, (150, 150, 150))
|
||||||
|
|
||||||
planet_center = (16, 5)
|
grid_x = x + 60
|
||||||
|
grid_y = y + 50
|
||||||
|
|
||||||
|
planet_center = (12, 10)
|
||||||
surface_radius = 3
|
surface_radius = 3
|
||||||
orbit_radius = 7
|
orbit_radius = 8
|
||||||
|
|
||||||
# Draw orbit ring (behind planet)
|
# Draw orbit ring (behind planet)
|
||||||
orbit_cells = bresenham_circle(planet_center, orbit_radius)
|
orbit_cells = bresenham_circle(planet_center, orbit_radius)
|
||||||
for x, y in orbit_cells:
|
for cx, cy in orbit_cells:
|
||||||
px = 260 + x * cell_size
|
px = grid_x + cx * cell_size
|
||||||
py = 460 + y * cell_size
|
py = grid_y + cy * cell_size
|
||||||
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
||||||
cell_rect.fill_color = mcrfpy.Color(50, 150, 50, 180)
|
cell_rect.fill_color = mcrfpy.Color(50, 150, 50, 180)
|
||||||
self.ui.append(cell_rect)
|
self.ui.append(cell_rect)
|
||||||
|
|
||||||
# Draw planet surface (on top)
|
# Draw planet surface (on top)
|
||||||
surface_cells = filled_circle(planet_center, surface_radius)
|
surface_cells = filled_circle(planet_center, surface_radius)
|
||||||
for x, y in surface_cells:
|
for cx, cy in surface_cells:
|
||||||
px = 260 + x * cell_size
|
px = grid_x + cx * cell_size
|
||||||
py = 460 + y * cell_size
|
py = grid_y + cy * cell_size
|
||||||
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
dist = math.sqrt((cx - planet_center[0])**2 + (cy - planet_center[1])**2)
|
||||||
dist = ((x - planet_center[0])**2 + (y - planet_center[1])**2) ** 0.5
|
|
||||||
intensity = int(200 * (1 - dist / (surface_radius + 1)))
|
intensity = int(200 * (1 - dist / (surface_radius + 1)))
|
||||||
|
cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
||||||
cell_rect.fill_color = mcrfpy.Color(50 + intensity, 100 + intensity // 2, 200)
|
cell_rect.fill_color = mcrfpy.Color(50 + intensity, 100 + intensity // 2, 200)
|
||||||
self.ui.append(cell_rect)
|
self.ui.append(cell_rect)
|
||||||
|
|
||||||
# Legend
|
# Legend in bottom-left of frame
|
||||||
self.add_label("Legend:", 600, 455, (200, 200, 200))
|
leg_x = x + 10
|
||||||
|
leg_y = y + h - 50
|
||||||
|
|
||||||
leg1 = mcrfpy.Frame(pos=(600, 475), size=(12, 12))
|
leg1 = mcrfpy.Frame(pos=(leg_x, leg_y), size=(12, 12))
|
||||||
leg1.fill_color = mcrfpy.Color(100, 150, 200)
|
leg1.fill_color = mcrfpy.Color(100, 150, 200)
|
||||||
self.ui.append(leg1)
|
self.ui.append(leg1)
|
||||||
self.add_label("Planet surface", 620, 473, (150, 150, 150))
|
self.add_label("Planet", leg_x + 18, leg_y - 2, (150, 150, 150))
|
||||||
|
|
||||||
leg2 = mcrfpy.Frame(pos=(600, 495), size=(12, 12))
|
leg2 = mcrfpy.Frame(pos=(leg_x, leg_y + 20), size=(12, 12))
|
||||||
leg2.fill_color = mcrfpy.Color(50, 150, 50)
|
leg2.fill_color = mcrfpy.Color(50, 150, 50)
|
||||||
self.ui.append(leg2)
|
self.ui.append(leg2)
|
||||||
self.add_label("Orbit ring (ship positions)", 620, 493, (150, 150, 150))
|
self.add_label("Orbit ring", leg_x + 18, leg_y + 18, (150, 150, 150))
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import math
|
||||||
from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system,
|
from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system,
|
||||||
create_planet, point_on_circle, distance, angle_between,
|
create_planet, point_on_circle, distance, angle_between,
|
||||||
normalize_angle, is_viable_waypoint, nearest_orbit_entry,
|
normalize_angle, is_viable_waypoint, nearest_orbit_entry,
|
||||||
optimal_exit_heading)
|
optimal_exit_heading, screen_angle_between,
|
||||||
|
SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
|
|
||||||
|
|
||||||
class PathfindingAnimatedDemo(GeometryDemoScreen):
|
class PathfindingAnimatedDemo(GeometryDemoScreen):
|
||||||
|
|
@ -17,18 +18,29 @@ class PathfindingAnimatedDemo(GeometryDemoScreen):
|
||||||
self.add_title("Pathfinding Through Moving Planets")
|
self.add_title("Pathfinding Through Moving Planets")
|
||||||
self.add_description("Ship anticipates planetary motion to use orbital slingshots")
|
self.add_description("Ship anticipates planetary motion to use orbital slingshots")
|
||||||
|
|
||||||
# Screen layout
|
margin = 30
|
||||||
self.center_x = 400
|
top_area = 80
|
||||||
self.center_y = 320
|
bottom_panel = 60
|
||||||
self.scale = 2.0
|
|
||||||
|
# Screen layout - full width for 1024x768
|
||||||
|
frame_width = SCREEN_WIDTH - 2 * margin
|
||||||
|
frame_height = SCREEN_HEIGHT - top_area - bottom_panel - margin
|
||||||
|
|
||||||
|
# Center of display area
|
||||||
|
self.center_x = margin + frame_width // 2
|
||||||
|
self.center_y = top_area + frame_height // 2
|
||||||
|
self.scale = 2.5 # Larger scale for better visibility
|
||||||
|
|
||||||
# Background
|
# Background
|
||||||
bg = mcrfpy.Frame(pos=(50, 80), size=(700, 460))
|
bg = mcrfpy.Frame(pos=(margin, top_area), size=(frame_width, frame_height))
|
||||||
bg.fill_color = mcrfpy.Color(5, 5, 15)
|
bg.fill_color = mcrfpy.Color(5, 5, 15)
|
||||||
bg.outline = 1
|
bg.outline = 1
|
||||||
bg.outline_color = mcrfpy.Color(40, 40, 80)
|
bg.outline_color = mcrfpy.Color(40, 40, 80)
|
||||||
self.ui.append(bg)
|
self.ui.append(bg)
|
||||||
|
|
||||||
|
# Store frame boundaries
|
||||||
|
self.frame_bottom = top_area + frame_height
|
||||||
|
|
||||||
# Create solar system
|
# Create solar system
|
||||||
self.star = create_solar_system(
|
self.star = create_solar_system(
|
||||||
grid_width=200, grid_height=200,
|
grid_width=200, grid_height=200,
|
||||||
|
|
@ -39,7 +51,7 @@ class PathfindingAnimatedDemo(GeometryDemoScreen):
|
||||||
self.planet = create_planet(
|
self.planet = create_planet(
|
||||||
name="Waypoint",
|
name="Waypoint",
|
||||||
star=self.star,
|
star=self.star,
|
||||||
orbital_radius=80,
|
orbital_radius=60, # Smaller orbit to not clip edges
|
||||||
surface_radius=8,
|
surface_radius=8,
|
||||||
orbit_ring_radius=15,
|
orbit_ring_radius=15,
|
||||||
angular_velocity=5, # Moves 5 degrees per turn
|
angular_velocity=5, # Moves 5 degrees per turn
|
||||||
|
|
@ -47,9 +59,10 @@ class PathfindingAnimatedDemo(GeometryDemoScreen):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ship state
|
# Ship state
|
||||||
self.ship_speed = 10 # Grid units per turn
|
self.ship_speed = 8 # Grid units per turn
|
||||||
self.ship_pos = [30, 100] # Start position (grid coords, relative to star)
|
# Position ship further from sun to avoid line clipping through it
|
||||||
self.ship_target = [100, -80] # Target position
|
self.ship_pos = [-80, 60] # Start position (grid coords, relative to star) - lower left
|
||||||
|
self.ship_target = [80, -60] # Target position - upper right
|
||||||
self.ship_state = "approach" # approach, orbiting, exiting, traveling
|
self.ship_state = "approach" # approach, orbiting, exiting, traveling
|
||||||
self.ship_orbit_angle = 0
|
self.ship_orbit_angle = 0
|
||||||
self.current_time = 0
|
self.current_time = 0
|
||||||
|
|
@ -234,24 +247,25 @@ class PathfindingAnimatedDemo(GeometryDemoScreen):
|
||||||
|
|
||||||
def _draw_info_panel(self):
|
def _draw_info_panel(self):
|
||||||
"""Draw information panel."""
|
"""Draw information panel."""
|
||||||
panel = mcrfpy.Frame(pos=(50, 545), size=(700, 45))
|
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.fill_color = mcrfpy.Color(20, 20, 35)
|
||||||
panel.outline = 1
|
panel.outline = 1
|
||||||
panel.outline_color = mcrfpy.Color(60, 60, 100)
|
panel.outline_color = mcrfpy.Color(60, 60, 100)
|
||||||
self.ui.append(panel)
|
self.ui.append(panel)
|
||||||
|
|
||||||
# Time display
|
# Time display
|
||||||
self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(60, 555))
|
self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(40, panel_y + 12))
|
||||||
self.time_label.fill_color = mcrfpy.Color(255, 255, 255)
|
self.time_label.fill_color = mcrfpy.Color(255, 255, 255)
|
||||||
self.ui.append(self.time_label)
|
self.ui.append(self.time_label)
|
||||||
|
|
||||||
# Status display
|
# Status display
|
||||||
self.status_label = mcrfpy.Caption(text="Status: Approaching planet", pos=(180, 555))
|
self.status_label = mcrfpy.Caption(text="Status: Approaching planet", pos=(180, panel_y + 12))
|
||||||
self.status_label.fill_color = mcrfpy.Color(100, 200, 255)
|
self.status_label.fill_color = mcrfpy.Color(100, 200, 255)
|
||||||
self.ui.append(self.status_label)
|
self.ui.append(self.status_label)
|
||||||
|
|
||||||
# Distance display
|
# Distance display
|
||||||
self.dist_label = mcrfpy.Caption(text="Distance to target: ---", pos=(450, 555))
|
self.dist_label = mcrfpy.Caption(text="Distance to target: ---", pos=(550, panel_y + 12))
|
||||||
self.dist_label.fill_color = mcrfpy.Color(150, 150, 150)
|
self.dist_label.fill_color = mcrfpy.Color(150, 150, 150)
|
||||||
self.ui.append(self.dist_label)
|
self.ui.append(self.dist_label)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
"""Static pathfinding demonstration with planets and orbit rings."""
|
"""Static pathfinding demonstration with planets and orbit rings."""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import math
|
import math
|
||||||
from .base import (GeometryDemoScreen, OrbitalBody, bresenham_circle, filled_circle,
|
from .base import (GeometryDemoScreen, bresenham_circle, filled_circle,
|
||||||
angle_between, distance, point_on_circle, is_viable_waypoint,
|
screen_angle_between, distance, SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
nearest_orbit_entry, optimal_exit_heading)
|
|
||||||
|
|
||||||
|
|
||||||
class PathfindingStaticDemo(GeometryDemoScreen):
|
class PathfindingStaticDemo(GeometryDemoScreen):
|
||||||
|
|
@ -16,16 +15,20 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
||||||
self.add_title("Pathfinding Through Orbital Bodies")
|
self.add_title("Pathfinding Through Orbital Bodies")
|
||||||
self.add_description("Using free orbital movement to optimize travel paths")
|
self.add_description("Using free orbital movement to optimize travel paths")
|
||||||
|
|
||||||
# Create a scenario with multiple planets
|
margin = 30
|
||||||
# Ship needs to go from bottom-left to top-right
|
top_area = 80
|
||||||
# Optimal path uses planetary orbits as "free repositioning stations"
|
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.cell_size = 8
|
||||||
self.offset_x = 50
|
self.grid_x = margin + 20
|
||||||
self.offset_y = 100
|
self.grid_y = top_area + 20
|
||||||
|
|
||||||
# Background
|
# 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.fill_color = mcrfpy.Color(5, 5, 15)
|
||||||
bg.outline = 1
|
bg.outline = 1
|
||||||
bg.outline_color = mcrfpy.Color(40, 40, 80)
|
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)
|
# Define planets (center_x, center_y, surface_radius, orbit_radius, name)
|
||||||
self.planets = [
|
self.planets = [
|
||||||
(20, 45, 8, 14, "Alpha"),
|
(25, 50, 6, 12, "Alpha"),
|
||||||
(55, 25, 5, 10, "Beta"),
|
(60, 25, 4, 9, "Beta"),
|
||||||
(70, 50, 6, 12, "Gamma"),
|
(85, 55, 5, 11, "Gamma"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Ship start and end
|
# Ship start and end
|
||||||
self.ship_start = (5, 55)
|
self.ship_start = (8, 65)
|
||||||
self.ship_end = (85, 10)
|
self.ship_end = (105, 15)
|
||||||
|
|
||||||
# Draw grid reference (faint)
|
# Draw grid reference
|
||||||
self._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:
|
for px, py, sr, orbit_r, name in self.planets:
|
||||||
self._draw_planet(px, py, sr, orbit_r, name)
|
self._draw_planet(px, py, sr, orbit_r, name)
|
||||||
|
|
||||||
# Calculate and draw optimal path
|
# Draw optimal path
|
||||||
self._draw_optimal_path()
|
self._draw_optimal_path()
|
||||||
|
|
||||||
# Draw ship and target
|
# Draw ship and target
|
||||||
self._draw_ship_and_target()
|
self._draw_ship_and_target()
|
||||||
|
|
||||||
# Legend
|
# Legend at bottom
|
||||||
self._draw_legend()
|
self._draw_legend(margin, top_area + frame_height + 10)
|
||||||
|
|
||||||
def _to_screen(self, gx, gy):
|
def _to_screen(self, gx, gy):
|
||||||
"""Convert grid coords to screen coords."""
|
"""Convert grid coords to screen coords (center of cell)."""
|
||||||
return (self.offset_x + gx * self.cell_size,
|
return (self.grid_x + gx * self.cell_size + self.cell_size // 2,
|
||||||
self.offset_y + gy * self.cell_size)
|
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):
|
def _draw_grid_reference(self):
|
||||||
"""Draw faint grid lines for reference."""
|
"""Draw faint grid lines for reference."""
|
||||||
for i in range(0, 91, 10):
|
max_x = 115
|
||||||
# Vertical lines
|
max_y = 75
|
||||||
x = self.offset_x + i * self.cell_size
|
for i in range(0, max_x + 1, 10):
|
||||||
|
x = self.grid_x + i * self.cell_size
|
||||||
line = mcrfpy.Line(
|
line = mcrfpy.Line(
|
||||||
start=(x, self.offset_y),
|
start=(x, self.grid_y),
|
||||||
end=(x, self.offset_y + 60 * self.cell_size),
|
end=(x, self.grid_y + max_y * self.cell_size),
|
||||||
color=mcrfpy.Color(30, 30, 50),
|
color=mcrfpy.Color(30, 30, 50),
|
||||||
thickness=1
|
thickness=1
|
||||||
)
|
)
|
||||||
self.ui.append(line)
|
self.ui.append(line)
|
||||||
|
|
||||||
for i in range(0, 61, 10):
|
for i in range(0, max_y + 1, 10):
|
||||||
# Horizontal lines
|
y = self.grid_y + i * self.cell_size
|
||||||
y = self.offset_y + i * self.cell_size
|
|
||||||
line = mcrfpy.Line(
|
line = mcrfpy.Line(
|
||||||
start=(self.offset_x, y),
|
start=(self.grid_x, y),
|
||||||
end=(self.offset_x + 90 * self.cell_size, y),
|
end=(self.grid_x + max_x * self.cell_size, y),
|
||||||
color=mcrfpy.Color(30, 30, 50),
|
color=mcrfpy.Color(30, 30, 50),
|
||||||
thickness=1
|
thickness=1
|
||||||
)
|
)
|
||||||
|
|
@ -91,7 +99,7 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
||||||
"""Draw a planet with surface and orbit ring."""
|
"""Draw a planet with surface and orbit ring."""
|
||||||
sx, sy = self._to_screen(cx, cy)
|
sx, sy = self._to_screen(cx, cy)
|
||||||
|
|
||||||
# Orbit ring (using mcrfpy.Circle for smooth rendering)
|
# Orbit ring (smooth circle)
|
||||||
orbit = mcrfpy.Circle(
|
orbit = mcrfpy.Circle(
|
||||||
center=(sx, sy),
|
center=(sx, sy),
|
||||||
radius=orbit_r * self.cell_size,
|
radius=orbit_r * self.cell_size,
|
||||||
|
|
@ -101,10 +109,10 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
||||||
)
|
)
|
||||||
self.ui.append(orbit)
|
self.ui.append(orbit)
|
||||||
|
|
||||||
# Also draw Bresenham orbit cells for accuracy demo
|
# Bresenham orbit cells
|
||||||
orbit_cells = bresenham_circle((cx, cy), orbit_r)
|
orbit_cells = bresenham_circle((cx, cy), orbit_r)
|
||||||
for gx, gy in orbit_cells:
|
for gx, gy in orbit_cells:
|
||||||
px, py = self._to_screen(gx, gy)
|
px, py = self._to_screen_corner(gx, gy)
|
||||||
cell = mcrfpy.Frame(
|
cell = mcrfpy.Frame(
|
||||||
pos=(px, py),
|
pos=(px, py),
|
||||||
size=(self.cell_size - 1, self.cell_size - 1)
|
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)
|
cell.fill_color = mcrfpy.Color(40, 100, 40, 100)
|
||||||
self.ui.append(cell)
|
self.ui.append(cell)
|
||||||
|
|
||||||
# Planet surface (filled circle)
|
# Planet surface
|
||||||
surface_cells = filled_circle((cx, cy), surface_r)
|
surface_cells = filled_circle((cx, cy), surface_r)
|
||||||
for gx, gy in surface_cells:
|
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)
|
dist = math.sqrt((gx - cx)**2 + (gy - cy)**2)
|
||||||
intensity = int(180 * (1 - dist / (surface_r + 1)))
|
intensity = int(180 * (1 - dist / (surface_r + 1)))
|
||||||
cell = mcrfpy.Frame(
|
cell = mcrfpy.Frame(
|
||||||
|
|
@ -125,75 +133,90 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
||||||
cell.fill_color = mcrfpy.Color(60 + intensity, 80 + intensity//2, 150)
|
cell.fill_color = mcrfpy.Color(60 + intensity, 80 + intensity//2, 150)
|
||||||
self.ui.append(cell)
|
self.ui.append(cell)
|
||||||
|
|
||||||
# Planet label
|
# Planet label (below planet to avoid overlap)
|
||||||
self.add_label(name, sx - 15, sy - surface_r * self.cell_size - 15, (150, 150, 200))
|
self.add_label(name, sx - 15, sy + (surface_r + 2) * self.cell_size, (150, 150, 200))
|
||||||
|
|
||||||
def _draw_optimal_path(self):
|
def _draw_optimal_path(self):
|
||||||
"""Calculate and draw the optimal path using orbital waypoints."""
|
"""Draw the optimal path using orbital waypoints."""
|
||||||
# The optimal path:
|
# The path:
|
||||||
# 1. Ship starts at (5, 55)
|
# 1. Ship at (8, 65) -> Alpha orbit entry
|
||||||
# 2. Direct line to Alpha's orbit entry
|
# 2. Arc around Alpha -> exit toward Gamma
|
||||||
# 3. Free arc around Alpha to optimal exit
|
# 3. Straight to Gamma orbit entry
|
||||||
# 4. Direct line to Gamma's orbit entry
|
# 4. Arc around Gamma -> exit toward target
|
||||||
# 5. Free arc around Gamma to optimal exit
|
# 5. Straight to target (105, 15)
|
||||||
# 6. Direct line to target (85, 10)
|
|
||||||
|
|
||||||
path_segments = []
|
# Alpha: center (25, 50), orbit_r=12
|
||||||
|
alpha_center = (25, 50)
|
||||||
|
alpha_orbit = 12
|
||||||
|
|
||||||
# Current position
|
# Gamma: center (85, 55), orbit_r=11
|
||||||
current = self.ship_start
|
gamma_center = (85, 55)
|
||||||
|
gamma_orbit = 11
|
||||||
|
|
||||||
# For this demo, manually define the path through Alpha and Gamma
|
ship_screen = self._to_screen(*self.ship_start)
|
||||||
# (In a real implementation, this would be computed by the pathfinder)
|
target_screen = self._to_screen(*self.ship_end)
|
||||||
|
|
||||||
# Planet Alpha (20, 45, orbit_r=14)
|
# --- Segment 1: Ship to Alpha orbit entry ---
|
||||||
alpha_center = (20, 45)
|
# Entry angle: direction from Alpha to ship
|
||||||
alpha_orbit = 14
|
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
|
self._draw_path_line(ship_screen, entry_alpha_screen, (100, 200, 255))
|
||||||
entry_angle_alpha = angle_between(alpha_center, current)
|
|
||||||
entry_alpha = point_on_circle(alpha_center, alpha_orbit, entry_angle_alpha)
|
|
||||||
|
|
||||||
# Draw line: start -> Alpha entry
|
# --- Segment 2: Arc around Alpha toward Gamma ---
|
||||||
self._draw_path_line(current, entry_alpha, (100, 200, 255))
|
exit_angle_alpha = screen_angle_between(
|
||||||
current = entry_alpha
|
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
|
self._draw_orbit_arc(self._to_screen(*alpha_center), alpha_orbit * self.cell_size,
|
||||||
gamma_center = (70, 50)
|
entry_angle_alpha, exit_angle_alpha)
|
||||||
exit_angle_alpha = angle_between(alpha_center, gamma_center)
|
|
||||||
exit_alpha = point_on_circle(alpha_center, alpha_orbit, exit_angle_alpha)
|
|
||||||
|
|
||||||
# Draw arc on Alpha's orbit
|
# --- Segment 3: Alpha exit to Gamma entry ---
|
||||||
self._draw_orbit_arc(alpha_center, alpha_orbit, entry_angle_alpha, exit_angle_alpha)
|
entry_angle_gamma = screen_angle_between(
|
||||||
current = exit_alpha
|
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)
|
self._draw_path_line(exit_alpha_screen, entry_gamma_screen, (100, 200, 255))
|
||||||
gamma_orbit = 12
|
|
||||||
|
|
||||||
# Entry to Gamma
|
# --- Segment 4: Arc around Gamma toward target ---
|
||||||
entry_angle_gamma = angle_between(gamma_center, current)
|
exit_angle_gamma = screen_angle_between(
|
||||||
entry_gamma = point_on_circle(gamma_center, gamma_orbit, entry_angle_gamma)
|
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_orbit_arc(self._to_screen(*gamma_center), gamma_orbit * self.cell_size,
|
||||||
self._draw_path_line(current, entry_gamma, (100, 200, 255))
|
entry_angle_gamma, exit_angle_gamma)
|
||||||
current = entry_gamma
|
|
||||||
|
|
||||||
# Exit from Gamma toward target
|
# --- Segment 5: Gamma exit to target ---
|
||||||
exit_angle_gamma = angle_between(gamma_center, self.ship_end)
|
self._draw_path_line(exit_gamma_screen, target_screen, (100, 200, 255))
|
||||||
exit_gamma = point_on_circle(gamma_center, gamma_orbit, exit_angle_gamma)
|
|
||||||
|
|
||||||
# Draw arc on Gamma's orbit
|
# Draw direct path for comparison
|
||||||
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)
|
|
||||||
direct_line = mcrfpy.Line(
|
direct_line = mcrfpy.Line(
|
||||||
start=direct_start, end=direct_end,
|
start=ship_screen, end=target_screen,
|
||||||
color=mcrfpy.Color(255, 100, 100, 80),
|
color=mcrfpy.Color(255, 100, 100, 80),
|
||||||
thickness=1
|
thickness=1
|
||||||
)
|
)
|
||||||
|
|
@ -201,10 +224,8 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
||||||
|
|
||||||
def _draw_path_line(self, p1, p2, color):
|
def _draw_path_line(self, p1, p2, color):
|
||||||
"""Draw a path segment line."""
|
"""Draw a path segment line."""
|
||||||
s1 = self._to_screen(p1[0], p1[1])
|
|
||||||
s2 = self._to_screen(p2[0], p2[1])
|
|
||||||
line = mcrfpy.Line(
|
line = mcrfpy.Line(
|
||||||
start=s1, end=s2,
|
start=p1, end=p2,
|
||||||
color=mcrfpy.Color(*color),
|
color=mcrfpy.Color(*color),
|
||||||
thickness=3
|
thickness=3
|
||||||
)
|
)
|
||||||
|
|
@ -212,81 +233,82 @@ class PathfindingStaticDemo(GeometryDemoScreen):
|
||||||
|
|
||||||
def _draw_orbit_arc(self, center, radius, start_angle, end_angle):
|
def _draw_orbit_arc(self, center, radius, start_angle, end_angle):
|
||||||
"""Draw an arc showing orbital movement (free movement)."""
|
"""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(
|
arc = mcrfpy.Arc(
|
||||||
center=(sx, sy),
|
center=center,
|
||||||
radius=radius * self.cell_size,
|
radius=radius,
|
||||||
start_angle=-start_angle,
|
start_angle=min(start_angle, end_angle),
|
||||||
end_angle=-end_angle,
|
end_angle=max(start_angle, end_angle),
|
||||||
color=mcrfpy.Color(255, 255, 100),
|
color=mcrfpy.Color(255, 255, 100),
|
||||||
thickness=4
|
thickness=4
|
||||||
)
|
)
|
||||||
self.ui.append(arc)
|
self.ui.append(arc)
|
||||||
|
|
||||||
def _draw_ship_and_target(self):
|
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
|
||||||
ship_x, ship_y = self._to_screen(*self.ship_start)
|
|
||||||
ship = mcrfpy.Circle(
|
ship = mcrfpy.Circle(
|
||||||
center=(ship_x + self.cell_size//2, ship_y + self.cell_size//2),
|
center=ship_screen,
|
||||||
radius=10,
|
radius=10,
|
||||||
fill_color=mcrfpy.Color(255, 200, 100),
|
fill_color=mcrfpy.Color(255, 200, 100),
|
||||||
outline_color=mcrfpy.Color(255, 255, 200),
|
outline_color=mcrfpy.Color(255, 255, 200),
|
||||||
outline=2
|
outline=2
|
||||||
)
|
)
|
||||||
self.ui.append(ship)
|
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
|
||||||
target_x, target_y = self._to_screen(*self.ship_end)
|
|
||||||
target = mcrfpy.Circle(
|
target = mcrfpy.Circle(
|
||||||
center=(target_x + self.cell_size//2, target_y + self.cell_size//2),
|
center=target_screen,
|
||||||
radius=10,
|
radius=10,
|
||||||
fill_color=mcrfpy.Color(255, 100, 100),
|
fill_color=mcrfpy.Color(255, 100, 100),
|
||||||
outline_color=mcrfpy.Color(255, 200, 200),
|
outline_color=mcrfpy.Color(255, 200, 200),
|
||||||
outline=2
|
outline=2
|
||||||
)
|
)
|
||||||
self.ui.append(target)
|
self.ui.append(target)
|
||||||
self.add_label("TARGET", target_x - 15, target_y + 20, (255, 100, 100))
|
self.add_label("TARGET", target_screen[0] - 25, target_screen[1] + 15, (255, 100, 100))
|
||||||
|
|
||||||
def _draw_legend(self):
|
|
||||||
"""Draw legend explaining the visualization."""
|
|
||||||
leg_x = 50
|
|
||||||
leg_y = 520
|
|
||||||
|
|
||||||
|
def _draw_legend(self, x, y):
|
||||||
|
"""Draw legend."""
|
||||||
# Blue line = movement cost
|
# Blue line = movement cost
|
||||||
line1 = mcrfpy.Line(
|
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),
|
color=mcrfpy.Color(100, 200, 255),
|
||||||
thickness=3
|
thickness=3
|
||||||
)
|
)
|
||||||
self.ui.append(line1)
|
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
|
# Yellow arc = free movement
|
||||||
arc1 = mcrfpy.Arc(
|
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,
|
start_angle=0, end_angle=180,
|
||||||
color=mcrfpy.Color(255, 255, 100),
|
color=mcrfpy.Color(255, 255, 100),
|
||||||
thickness=3
|
thickness=3
|
||||||
)
|
)
|
||||||
self.ui.append(arc1)
|
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(
|
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),
|
color=mcrfpy.Color(255, 100, 100, 80),
|
||||||
thickness=1
|
thickness=1
|
||||||
)
|
)
|
||||||
self.ui.append(line2)
|
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
|
# 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)
|
cell1.fill_color = mcrfpy.Color(40, 100, 40)
|
||||||
self.ui.append(cell1)
|
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))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import math
|
import math
|
||||||
from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system,
|
from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system,
|
||||||
create_planet, create_moon, point_on_circle)
|
create_planet, create_moon, point_on_circle,
|
||||||
|
SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||||
|
|
||||||
|
|
||||||
class SolarSystemDemo(GeometryDemoScreen):
|
class SolarSystemDemo(GeometryDemoScreen):
|
||||||
|
|
@ -15,18 +16,29 @@ class SolarSystemDemo(GeometryDemoScreen):
|
||||||
self.add_title("Animated Solar System")
|
self.add_title("Animated Solar System")
|
||||||
self.add_description("Planets snap to grid positions as time advances (1 tick = 1 turn)")
|
self.add_description("Planets snap to grid positions as time advances (1 tick = 1 turn)")
|
||||||
|
|
||||||
# Screen layout
|
margin = 30
|
||||||
self.center_x = 400
|
top_area = 80
|
||||||
self.center_y = 320
|
bottom_panel = 60
|
||||||
self.scale = 1.5 # Pixels per grid unit
|
|
||||||
|
# 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
|
# Background
|
||||||
bg = mcrfpy.Frame(pos=(50, 80), size=(700, 480))
|
bg = mcrfpy.Frame(pos=(margin, top_area), size=(frame_width, frame_height))
|
||||||
bg.fill_color = mcrfpy.Color(5, 5, 15)
|
bg.fill_color = mcrfpy.Color(5, 5, 15)
|
||||||
bg.outline = 1
|
bg.outline = 1
|
||||||
bg.outline_color = mcrfpy.Color(40, 40, 80)
|
bg.outline_color = mcrfpy.Color(40, 40, 80)
|
||||||
self.ui.append(bg)
|
self.ui.append(bg)
|
||||||
|
|
||||||
|
# Store frame boundaries for info panel
|
||||||
|
self.frame_bottom = top_area + frame_height
|
||||||
|
|
||||||
# Create the solar system using geometry module
|
# Create the solar system using geometry module
|
||||||
self.star = create_solar_system(
|
self.star = create_solar_system(
|
||||||
grid_width=200, grid_height=200,
|
grid_width=200, grid_height=200,
|
||||||
|
|
@ -92,13 +104,21 @@ class SolarSystemDemo(GeometryDemoScreen):
|
||||||
# Draw initial planet positions
|
# Draw initial planet positions
|
||||||
self._draw_planets()
|
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
|
# Time display
|
||||||
self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(60, 530))
|
self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(40, panel_y + 12))
|
||||||
self.time_label.fill_color = mcrfpy.Color(255, 255, 255)
|
self.time_label.fill_color = mcrfpy.Color(255, 255, 255)
|
||||||
self.ui.append(self.time_label)
|
self.ui.append(self.time_label)
|
||||||
|
|
||||||
# Instructions
|
# Instructions
|
||||||
self.add_label("Time advances automatically every second", 200, 530, (150, 150, 150))
|
self.add_label("Time advances automatically every second", 200, panel_y + 12, (150, 150, 150))
|
||||||
|
|
||||||
# Start the animation timer
|
# Start the animation timer
|
||||||
self.add_timer("solar_tick", self._tick, 1000) # 1 second per turn
|
self.add_timer("solar_tick", self._tick, 1000) # 1 second per turn
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |