-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
235 lines (202 loc) · 10.5 KB
/
main.py
File metadata and controls
235 lines (202 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import pygame
from pygame.locals import *
from scripts.physics import update_physics, FPS, TIME_MULTIPLIER
from scripts.camera import Camera, handle_camera_input, handle_locked_camera_input, handle_planetary_input, check_hover
from scripts.visuals import render_scene
from scripts.system_loader import load_solar_system, save_solar_system
import numpy as np
import argparse
import tkinter as tk
from tkinter import simpledialog, messagebox
import os
# Screen dimensions
WIDTH, HEIGHT = 1000, 800
def save_current_system(bodies):
"""Save current system state with user input dialog"""
# Create root window (hidden)
root = tk.Tk()
root.withdraw()
# Get filename from user
filename = simpledialog.askstring(
"Save System",
"Enter filename (without extension):",
parent=root
)
if filename:
# Ensure .json extension
if not filename.endswith('.json'):
filename += '.json'
# Save to systems folder
filepath = os.path.join('systems', filename)
try:
save_solar_system(bodies, filepath)
messagebox.showinfo("Success", f"System saved to {filepath}")
except Exception as e:
messagebox.showerror("Error", f"Failed to save system: {e}")
root.destroy()
def main():
# Parse command line arguments
parser = argparse.ArgumentParser(description='3D Gravity Simulation')
parser.add_argument('--system', '-s', type=str, default='system.json',
help='Path to solar system configuration file (default: system.json)')
args = parser.parse_args()
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.RESIZABLE)
pygame.display.set_caption("3D Gravity Simulation")
clock = pygame.time.Clock()
# Dynamic screen dimensions
current_width, current_height = WIDTH, HEIGHT
# Load celestial bodies from file
bodies = load_solar_system(args.system)
# Camera
camera = Camera()
# Game state
paused = False
show_trails = True
show_ui = True # Toggle for UI instructions
time_multiplier = TIME_MULTIPLIER
movement_speed_multiplier = 1.0 # For adjustable camera movement speed
locked_body = None # Camera lock target
planetary_body = None # Planetary mode target
running = True
while running:
# Handle events
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == VIDEORESIZE:
# Handle window resize
old_width, old_height = current_width, current_height
current_width, current_height = event.w, event.h
screen = pygame.display.set_mode((current_width, current_height), pygame.RESIZABLE)
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
elif event.key == K_SPACE:
paused = not paused
elif event.key == K_t:
show_trails = not show_trails
elif event.key == K_c:
show_ui = not show_ui
elif event.key == K_l:
# Lock camera to hovered body or unlock
hovered_body = check_hover(pygame.mouse.get_pos(), bodies, camera, current_width, current_height)
locked_body = hovered_body if hovered_body else None
# Clear planetary mode when locking
planetary_body = None
elif event.key == K_p:
# Planetary mode: fix distance to hovered body
hovered_body = check_hover(pygame.mouse.get_pos(), bodies, camera, current_width, current_height)
planetary_body = hovered_body if hovered_body else None
# Clear lock mode when entering planetary mode
locked_body = None
# Clear planetary attributes
for attr in ['planetary_initial_forward', 'planetary_initial_anchor', 'planetary_initial_pitch', 'planetary_initial_yaw']:
if hasattr(camera, attr):
delattr(camera, attr)
camera.reset_rotation()
if not locked_body and planetary_body:
# CRITICAL FIX: Position camera at north pole and set up orientation
# North pole is at body.position + (0, radius + offset, 0) in world coordinates
north_pole_offset = np.array([0.0, planetary_body.radius + 7e7, 0.0])
camera.position = planetary_body.position + north_pole_offset
# Set camera orientation directly for north pole view
# At north pole: up points away from planet, forward points down, right points east
planetary_up_vector = np.array([0.0, 1.0, 0.0]) # Up at north pole (world Y)
camera.up = planetary_up_vector / np.linalg.norm(planetary_up_vector) # Normalize
camera.forward = -planetary_up_vector # Look down toward planet center
camera.forward = camera.forward / np.linalg.norm(camera.forward) # Normalize
# Calculate right vector to maintain orthogonal system (cross product order: up × forward)
camera.right = np.cross(camera.up, camera.forward)
if np.linalg.norm(camera.right) > 1e-6:
camera.right = camera.right / np.linalg.norm(camera.right)
else:
# Fallback if cross product fails
camera.right = np.array([1.0, 0.0, 0.0])
# Initialize planetary coordinate system
# planetary_up should point away from planet center (surface normal)
radial_vector = camera.position - planetary_body.position
if np.linalg.norm(radial_vector) > 0:
camera.planetary_up = radial_vector / np.linalg.norm(radial_vector)
else:
camera.planetary_up = np.array([0.0, 1.0, 0.0])
# planetary_right should be stable (east-west direction)
# At north pole, east is along world X axis
camera.planetary_right = np.array([1.0, 0.0, 0.0])
# Clear any existing manual rotation
if hasattr(camera, 'manual_rotation'):
camera.manual_rotation = np.eye(3)
else:
camera.manual_rotation = np.eye(3)
elif event.key == K_RETURN:
# Save current system state
save_current_system(bodies)
elif event.key == K_r:
# Reset simulation
bodies = load_solar_system(args.system)
time_multiplier = TIME_MULTIPLIER
movement_speed_multiplier = 1.0
# Reset camera to default state
camera.reset_rotation()
camera.position = np.array([0.0, 0.0, -5e11]) # Default position
# Clear any active modes
locked_body = None
planetary_body = None
elif event.key == K_PLUS or event.key == K_EQUALS:
# Speed up time
time_multiplier = min(time_multiplier * 2, 100.0)
elif event.key == K_MINUS:
# Slow down time
time_multiplier = max(time_multiplier / 2, 0.01)
elif event.key == K_PERIOD:
# Faster movement speed
movement_speed_multiplier = min(movement_speed_multiplier * 1.5, 10.0)
elif event.key == K_COMMA:
# Slower movement speed
movement_speed_multiplier = max(movement_speed_multiplier / 1.5, 0.1)
# Handle continuous input
keys = pygame.key.get_pressed()
if locked_body:
# When locked, only allow orbital movement (WASD) and zoom (QE)
handle_locked_camera_input(camera, keys, movement_speed_multiplier, locked_body)
elif planetary_body:
# Planetary mode: normal rotation + perpendicular movement
handle_planetary_input(camera, keys, movement_speed_multiplier, planetary_body)
else:
# Normal camera controls when not locked
handle_camera_input(camera, keys, movement_speed_multiplier)
# Update physics
if not paused:
# Store planetary body position before physics update
if planetary_body:
old_planetary_position = planetary_body.position.copy()
update_physics(bodies, time_multiplier)
# Apply planetary body movement to camera
if planetary_body:
planetary_movement = planetary_body.position - old_planetary_position
camera.position += planetary_movement
# Update camera lock
if locked_body:
# Calculate camera movement to follow locked body
if not hasattr(camera, 'lock_offset'):
# Initialize lock offset when first locking
camera.lock_offset = camera.position - locked_body.position
# Move camera with locked body
camera.position = locked_body.position + camera.lock_offset
elif planetary_body:
# Planetary mode: handle movement in handle_planetary_input
# Don't override camera position here - let handle_planetary_input manage it
pass
else:
# Clear lock offset when not locked
if hasattr(camera, 'lock_offset'):
delattr(camera, 'lock_offset')
# Render
render_scene(screen, bodies, camera, show_trails, show_ui, time_multiplier, movement_speed_multiplier, current_width, current_height, locked_body, planetary_body)
# Update display
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()