feat: Add geometry module demo system for orbital mechanics
Creates comprehensive visual demonstrations of the geometry module: Static demos: - Bresenham algorithms: circle/line rasterization on grid cells - Angle calculations: line elements showing angles between points, waypoint viability with angle thresholds, orbit exit headings - Pathfinding: planets with surfaces and orbit rings, optimal path using orbital slingshots vs direct path comparison Animated demos: - Solar system: planets orbiting star with discrete time steps, nested moon orbit, position updates every second - Pathfinding through moving system: ship navigates to target using orbital intercepts, anticipating planetary motion Includes 5 screenshot outputs demonstrating each feature. Run: ./mcrogueface --headless --exec tests/geometry_demo/geometry_main.py Interactive: ./mcrogueface tests/geometry_demo/geometry_main.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bc95cb1f0b
commit
198686cba9
14 changed files with 1718 additions and 0 deletions
319
tests/geometry_demo/screens/angle_lines_demo.py
Normal file
319
tests/geometry_demo/screens/angle_lines_demo.py
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
"""Angle calculation demonstration with Line elements."""
|
||||
import mcrfpy
|
||||
import math
|
||||
from .base import (GeometryDemoScreen, angle_between, angle_difference,
|
||||
normalize_angle, point_on_circle, distance)
|
||||
|
||||
|
||||
class AngleLinesDemo(GeometryDemoScreen):
|
||||
"""Demonstrate angle calculations between points using Line elements."""
|
||||
|
||||
name = "Angle Calculations"
|
||||
description = "Visualizing angles between grid positions"
|
||||
|
||||
def setup(self):
|
||||
self.add_title("Angle Calculations & Line Elements")
|
||||
self.add_description("Computing headings, deviations, and opposite angles for pathfinding")
|
||||
|
||||
# Demo 1: Basic angle between two points
|
||||
self._demo_basic_angle()
|
||||
|
||||
# Demo 2: Angle between three points (deviation)
|
||||
self._demo_angle_deviation()
|
||||
|
||||
# Demo 3: Waypoint viability visualization
|
||||
self._demo_waypoint_viability()
|
||||
|
||||
# Demo 4: Orbit exit heading
|
||||
self._demo_orbit_exit()
|
||||
|
||||
def _demo_basic_angle(self):
|
||||
"""Show angle from point A to point B."""
|
||||
bg = mcrfpy.Frame(pos=(30, 80), size=(350, 200))
|
||||
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("Basic Angle Calculation", 50, 85, (255, 200, 100))
|
||||
|
||||
# Point A (origin)
|
||||
ax, ay = 100, 180
|
||||
# Point B (target)
|
||||
bx, by = 300, 120
|
||||
|
||||
angle = angle_between((ax, ay), (bx, by))
|
||||
dist = distance((ax, ay), (bx, by))
|
||||
|
||||
# Draw the line A to B (green)
|
||||
line_ab = mcrfpy.Line(
|
||||
start=(ax, ay), end=(bx, by),
|
||||
color=mcrfpy.Color(100, 255, 100),
|
||||
thickness=3
|
||||
)
|
||||
self.ui.append(line_ab)
|
||||
|
||||
# Draw reference line (east from A) in gray
|
||||
line_ref = mcrfpy.Line(
|
||||
start=(ax, ay), end=(ax + 150, ay),
|
||||
color=mcrfpy.Color(100, 100, 100),
|
||||
thickness=1
|
||||
)
|
||||
self.ui.append(line_ref)
|
||||
|
||||
# Draw arc showing the angle
|
||||
arc = mcrfpy.Arc(
|
||||
center=(ax, ay), radius=40,
|
||||
start_angle=0, end_angle=-angle, # Negative because screen Y is inverted
|
||||
color=mcrfpy.Color(255, 255, 100),
|
||||
thickness=2
|
||||
)
|
||||
self.ui.append(arc)
|
||||
|
||||
# Points
|
||||
point_a = mcrfpy.Circle(center=(ax, ay), radius=8,
|
||||
fill_color=mcrfpy.Color(255, 100, 100))
|
||||
point_b = mcrfpy.Circle(center=(bx, by), radius=8,
|
||||
fill_color=mcrfpy.Color(100, 255, 100))
|
||||
self.ui.append(point_a)
|
||||
self.ui.append(point_b)
|
||||
|
||||
# Labels
|
||||
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(f"Angle: {angle:.1f}°", 50, 250, (255, 255, 100))
|
||||
self.add_label(f"Distance: {dist:.1f}", 180, 250, (150, 150, 150))
|
||||
|
||||
def _demo_angle_deviation(self):
|
||||
"""Show angle deviation when considering a waypoint."""
|
||||
bg = mcrfpy.Frame(pos=(400, 80), size=(380, 200))
|
||||
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("Waypoint Deviation", 420, 85, (255, 200, 100))
|
||||
self.add_label("Is planet C a useful waypoint from A to B?", 420, 105, (150, 150, 150))
|
||||
|
||||
# Ship at A, target at B, potential waypoint C
|
||||
ax, ay = 450, 230
|
||||
bx, by = 720, 180
|
||||
cx, cy = 550, 150
|
||||
|
||||
# Calculate angles
|
||||
angle_to_target = angle_between((ax, ay), (bx, by))
|
||||
angle_to_waypoint = angle_between((ax, ay), (cx, cy))
|
||||
deviation = abs(angle_difference(angle_to_target, angle_to_waypoint))
|
||||
|
||||
# Draw line A to B (direct path - green)
|
||||
line_ab = mcrfpy.Line(
|
||||
start=(ax, ay), end=(bx, by),
|
||||
color=mcrfpy.Color(100, 255, 100),
|
||||
thickness=2
|
||||
)
|
||||
self.ui.append(line_ab)
|
||||
|
||||
# Draw line A to C (waypoint path - yellow if viable, red if not)
|
||||
viable = deviation <= 45
|
||||
waypoint_color = mcrfpy.Color(255, 255, 100) if viable else mcrfpy.Color(255, 100, 100)
|
||||
line_ac = mcrfpy.Line(
|
||||
start=(ax, ay), end=(cx, cy),
|
||||
color=waypoint_color,
|
||||
thickness=2
|
||||
)
|
||||
self.ui.append(line_ac)
|
||||
|
||||
# Draw deviation arc
|
||||
arc = mcrfpy.Arc(
|
||||
center=(ax, ay), radius=50,
|
||||
start_angle=-angle_to_target, end_angle=-angle_to_waypoint,
|
||||
color=waypoint_color,
|
||||
thickness=2
|
||||
)
|
||||
self.ui.append(arc)
|
||||
|
||||
# Points
|
||||
point_a = mcrfpy.Circle(center=(ax, ay), radius=8,
|
||||
fill_color=mcrfpy.Color(255, 100, 100))
|
||||
point_b = mcrfpy.Circle(center=(bx, by), radius=8,
|
||||
fill_color=mcrfpy.Color(100, 255, 100))
|
||||
point_c = mcrfpy.Circle(center=(cx, cy), radius=12,
|
||||
fill_color=mcrfpy.Color(100, 100, 200),
|
||||
outline_color=mcrfpy.Color(150, 150, 255),
|
||||
outline=2)
|
||||
self.ui.append(point_a)
|
||||
self.ui.append(point_b)
|
||||
self.ui.append(point_c)
|
||||
|
||||
# Labels
|
||||
self.add_label("A (ship)", ax - 10, ay + 10, (255, 100, 100))
|
||||
self.add_label("B (target)", bx - 20, by + 15, (100, 255, 100))
|
||||
self.add_label("C (planet)", cx + 15, cy - 5, (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):
|
||||
"""Show multiple potential waypoints with viability indicators."""
|
||||
bg = mcrfpy.Frame(pos=(30, 300), size=(350, 280))
|
||||
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("Multiple Waypoint Analysis", 50, 305, (255, 200, 100))
|
||||
|
||||
# Ship and target
|
||||
ax, ay = 80, 450
|
||||
bx, by = 320, 380
|
||||
|
||||
angle_to_target = angle_between((ax, ay), (bx, by))
|
||||
|
||||
# Draw direct path
|
||||
line_ab = mcrfpy.Line(
|
||||
start=(ax, ay), end=(bx, by),
|
||||
color=mcrfpy.Color(100, 255, 100, 128),
|
||||
thickness=2
|
||||
)
|
||||
self.ui.append(line_ab)
|
||||
|
||||
# Potential waypoints at various angles
|
||||
waypoints = [
|
||||
(150, 360, "W1"), # Ahead and left - viable
|
||||
(200, 500, "W2"), # Below path - marginal
|
||||
(100, 540, "W3"), # Behind - not viable
|
||||
(250, 340, "W4"), # Almost on path - very viable
|
||||
]
|
||||
|
||||
threshold = 45
|
||||
for wx, wy, label in waypoints:
|
||||
angle_to_wp = angle_between((ax, ay), (wx, wy))
|
||||
deviation = abs(angle_difference(angle_to_target, angle_to_wp))
|
||||
viable = deviation <= threshold
|
||||
|
||||
# Line to waypoint
|
||||
color_tuple = (100, 255, 100) if viable else (255, 100, 100)
|
||||
color = mcrfpy.Color(*color_tuple)
|
||||
line = mcrfpy.Line(
|
||||
start=(ax, ay), end=(wx, wy),
|
||||
color=color,
|
||||
thickness=1
|
||||
)
|
||||
self.ui.append(line)
|
||||
|
||||
# Waypoint circle
|
||||
wp_circle = mcrfpy.Circle(
|
||||
center=(wx, wy), radius=15,
|
||||
fill_color=mcrfpy.Color(80, 80, 120),
|
||||
outline_color=color,
|
||||
outline=2
|
||||
)
|
||||
self.ui.append(wp_circle)
|
||||
|
||||
self.add_label(f"{label}:{deviation:.0f}°", wx + 18, wy - 8, color_tuple)
|
||||
|
||||
# Ship and target markers
|
||||
ship = mcrfpy.Circle(center=(ax, ay), radius=8,
|
||||
fill_color=mcrfpy.Color(255, 200, 100))
|
||||
target = mcrfpy.Circle(center=(bx, by), radius=8,
|
||||
fill_color=mcrfpy.Color(100, 255, 100))
|
||||
self.ui.append(ship)
|
||||
self.ui.append(target)
|
||||
|
||||
self.add_label("Ship", ax - 5, ay + 12, (255, 200, 100))
|
||||
self.add_label("Target", bx - 15, by + 12, (100, 255, 100))
|
||||
self.add_label(f"Threshold: {threshold}°", 50, 555, (150, 150, 150))
|
||||
|
||||
def _demo_orbit_exit(self):
|
||||
"""Show optimal orbit exit heading toward target."""
|
||||
bg = mcrfpy.Frame(pos=(400, 300), size=(380, 280))
|
||||
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("Orbit Exit Heading", 420, 305, (255, 200, 100))
|
||||
self.add_label("Ship in orbit chooses optimal exit point", 420, 325, (150, 150, 150))
|
||||
|
||||
# Planet center and orbit
|
||||
px, py = 520, 450
|
||||
orbit_radius = 60
|
||||
surface_radius = 25
|
||||
|
||||
# Target position
|
||||
tx, ty = 720, 380
|
||||
|
||||
# Calculate optimal exit angle
|
||||
exit_angle = angle_between((px, py), (tx, ty))
|
||||
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
|
||||
|
||||
# Draw planet surface
|
||||
planet = mcrfpy.Circle(
|
||||
center=(px, py), radius=surface_radius,
|
||||
fill_color=mcrfpy.Color(80, 120, 180),
|
||||
outline_color=mcrfpy.Color(100, 150, 220),
|
||||
outline=2
|
||||
)
|
||||
self.ui.append(planet)
|
||||
|
||||
# Draw orbit ring
|
||||
orbit = mcrfpy.Circle(
|
||||
center=(px, py), radius=orbit_radius,
|
||||
fill_color=mcrfpy.Color(0, 0, 0, 0),
|
||||
outline_color=mcrfpy.Color(50, 150, 50),
|
||||
outline=2
|
||||
)
|
||||
self.ui.append(orbit)
|
||||
|
||||
# Draw ship positions around orbit (current position)
|
||||
ship_angle = 200 # Current position
|
||||
ship_x = px + orbit_radius * math.cos(math.radians(ship_angle))
|
||||
ship_y = py - orbit_radius * math.sin(math.radians(ship_angle))
|
||||
|
||||
ship = mcrfpy.Circle(
|
||||
center=(ship_x, ship_y), radius=8,
|
||||
fill_color=mcrfpy.Color(255, 200, 100)
|
||||
)
|
||||
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
|
||||
exit_point = mcrfpy.Circle(
|
||||
center=(exit_x, exit_y), radius=6,
|
||||
fill_color=mcrfpy.Color(100, 255, 100)
|
||||
)
|
||||
self.ui.append(exit_point)
|
||||
|
||||
# Draw line from exit to target
|
||||
exit_line = mcrfpy.Line(
|
||||
start=(exit_x, exit_y), end=(tx, ty),
|
||||
color=mcrfpy.Color(100, 255, 100),
|
||||
thickness=2
|
||||
)
|
||||
self.ui.append(exit_line)
|
||||
|
||||
# Target
|
||||
target = mcrfpy.Circle(
|
||||
center=(tx, ty), radius=10,
|
||||
fill_color=mcrfpy.Color(255, 100, 100)
|
||||
)
|
||||
self.ui.append(target)
|
||||
|
||||
# Labels
|
||||
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("Exit", exit_x + 10, exit_y - 10, (100, 255, 100))
|
||||
self.add_label("Target", tx - 15, 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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue