updated video generation

This commit is contained in:
2025-07-08 10:08:07 +03:00
parent a38e2b1fe9
commit 2532bf6219
24 changed files with 10744 additions and 127 deletions

View File

@@ -122,8 +122,8 @@ def generate_3d_video_animation(project_name, resources_folder, label_widget, pr
min_lat, max_lat = min(lats), max(lats)
min_lon, max_lon = min(lons), max(lons)
# Step 3: Generate frames
update_progress(30, "Generating 3D frames...")
# Step 3: Generate frames with space entry sequence
update_progress(30, "Generating 3D frames with space entry...")
# Create temporary directory for frames
temp_dir = tempfile.mkdtemp()
@@ -133,12 +133,31 @@ def generate_3d_video_animation(project_name, resources_folder, label_widget, pr
# Video settings
width, height = 1920, 1080
fps = 30
total_frames = len(positions) * 2 # 2 frames per position for smooth animation
entry_frames = 90 # 3 seconds at 30fps for space entry
total_frames = entry_frames + len(positions) * 2 # Entry + route animation
# Generate frames
frame_counter = 0
# Generate space entry sequence (3 seconds)
update_progress(30, "Creating space entry sequence...")
for i in range(entry_frames):
progress = 30 + (i / total_frames) * 40
update_progress(progress, f"Space entry frame {i+1}/{entry_frames}...")
frame = create_space_entry_frame(
positions[0], center_lat, center_lon,
min_lat, max_lat, min_lon, max_lon,
width, height, i, entry_frames
)
frame_path = os.path.join(frames_dir, f"frame_{frame_counter:06d}.png")
cv2.imwrite(frame_path, frame)
frame_counter += 1
# Generate route following frames
for i, pos in enumerate(positions):
progress = 30 + (i / len(positions)) * 40
update_progress(progress, f"Generating frame {i+1}/{len(positions)}...")
progress = 30 + ((entry_frames + i) / total_frames) * 40
update_progress(progress, f"Route frame {i+1}/{len(positions)}...")
frame = create_3d_frame(
pos, positions, i, center_lat, center_lon,
@@ -147,8 +166,9 @@ def generate_3d_video_animation(project_name, resources_folder, label_widget, pr
)
# Save frame
frame_path = os.path.join(frames_dir, f"frame_{i:06d}.png")
frame_path = os.path.join(frames_dir, f"frame_{frame_counter:06d}.png")
cv2.imwrite(frame_path, frame)
frame_counter += 1
# Step 4: Create video
update_progress(75, "Compiling video...")
@@ -191,95 +211,302 @@ def generate_3d_video_animation(project_name, resources_folder, label_widget, pr
def create_3d_frame(current_pos, all_positions, frame_index, center_lat, center_lon,
min_lat, max_lat, min_lon, max_lon, width, height):
"""
Create a single 3D-style frame
Create a Google Earth-style 3D frame with camera following the route
"""
# Create canvas
frame = np.zeros((height, width, 3), dtype=np.uint8)
# Background gradient (sky effect)
for y in range(height):
color_intensity = int(255 * (1 - y / height))
sky_color = (min(255, color_intensity + 50), min(255, color_intensity + 100), 255)
frame[y, :] = sky_color
# Enhanced camera following system
camera_pos, camera_target, camera_bearing = calculate_dynamic_camera_position(
current_pos, all_positions, frame_index, min_lat, max_lat, min_lon, max_lon
)
# Calculate perspective transformation
# Simple isometric-style projection
scale_x = width * 0.6 / (max_lon - min_lon) if max_lon != min_lon else 1000
scale_y = height * 0.6 / (max_lat - min_lat) if max_lat != min_lat else 1000
# Google Earth-style perspective parameters with improved aerial view
base_camera_height = 1500 + 1000 * math.sin(frame_index * 0.02) # 1000-3000m range
camera_height = base_camera_height + 500 * math.sin(frame_index * 0.05) # Add variation
view_distance = 3000 # Increased view distance for better aerial perspective
tilt_angle = 65 + 8 * math.sin(frame_index * 0.03) # Dynamic tilt for cinematic effect
fov = 75 # Slightly wider field of view for aerial shots
# Draw route path with 3D effect
route_points = []
for i, pos in enumerate(all_positions[:frame_index + 1]):
# Convert GPS to screen coordinates
x = int((pos['longitude'] - min_lon) * scale_x + width * 0.2)
y = int(height * 0.8 - (pos['latitude'] - min_lat) * scale_y)
# Create enhanced terrain background
create_terrain_background(frame, width, height, camera_pos['latitude'], camera_pos['longitude'], camera_bearing, tilt_angle)
# Transform all route points to 3D camera space
route_points_3d = []
for i, pos in enumerate(all_positions):
# Calculate distance from camera
dist_to_camera = calculate_distance(camera_pos['latitude'], camera_pos['longitude'],
pos['latitude'], pos['longitude'])
# Add 3D effect (elevation simulation)
elevation_offset = int(20 * math.sin(i * 0.1)) # Simulated elevation
y -= elevation_offset
route_points.append((x, y))
# Draw route trail with gradient
if len(route_points) > 1:
for i in range(1, len(route_points)):
# Color gradient from blue to red
progress = i / len(route_points)
color_r = int(255 * progress)
color_b = int(255 * (1 - progress))
color = (color_b, 100, color_r)
if dist_to_camera > view_distance * 2: # Skip points too far away
continue
# Draw thick line with 3D shadow effect
pt1, pt2 = route_points[i-1], route_points[i]
# Shadow
cv2.line(frame, (pt1[0]+2, pt1[1]+2), (pt2[0]+2, pt2[1]+2), (50, 50, 50), 8)
# Main line
cv2.line(frame, pt1, pt2, color, 6)
# Get elevation for this point
elevation = get_simulated_elevation(pos['latitude'], pos['longitude'], i)
# Convert to 3D screen coordinates
screen_x, screen_y, is_visible = world_to_screen_3d(
pos['latitude'], pos['longitude'], elevation,
camera_pos['latitude'], camera_pos['longitude'], camera_height,
camera_bearing, tilt_angle, width, height, view_distance
)
if is_visible:
route_points_3d.append((screen_x, screen_y, i <= frame_index))
# Draw current position marker
if route_points:
current_point = route_points[-1]
# Pulsing effect
pulse_size = int(15 + 10 * math.sin(frame_index * 0.3))
# Shadow
cv2.circle(frame, (current_point[0]+3, current_point[1]+3), pulse_size, (0, 0, 0), -1)
# Main marker
cv2.circle(frame, current_point, pulse_size, (0, 255, 255), -1)
cv2.circle(frame, current_point, pulse_size-3, (255, 255, 255), 2)
# Draw route with enhanced 3D effects
draw_3d_route(frame, route_points_3d, frame_index)
# Add grid effect for 3D feel
grid_spacing = 50
for x in range(0, width, grid_spacing):
cv2.line(frame, (x, 0), (x, height), (100, 100, 100), 1)
for y in range(0, height, grid_spacing):
cv2.line(frame, (0, y), (width, y), (100, 100, 100), 1)
# Add Google Earth-style UI overlays
add_google_earth_ui(frame, current_pos, camera_bearing, width, height, frame_index, len(all_positions))
# Add text overlay
try:
# Position info
speed = current_pos.get('speed', 0) if current_pos else 0
timestamp = current_pos.get('deviceTime', '') if current_pos else ''
text_y = 50
cv2.putText(frame, f"Speed: {speed:.1f} km/h", (50, text_y),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
text_y += 40
if timestamp:
cv2.putText(frame, f"Time: {timestamp[:16]}", (50, text_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
text_y += 40
cv2.putText(frame, f"Point: {frame_index + 1}/{len(all_positions)}", (50, text_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
except Exception:
pass # Skip text if font issues
# Add atmospheric effects
add_atmospheric_perspective(frame, width, height)
return frame
def calculate_bearing(lat1, lon1, lat2, lon2):
"""Calculate bearing between two GPS points"""
lat1_rad = math.radians(lat1)
lat2_rad = math.radians(lat2)
dlon_rad = math.radians(lon2 - lon1)
y = math.sin(dlon_rad) * math.cos(lat2_rad)
x = math.cos(lat1_rad) * math.sin(lat2_rad) - math.sin(lat1_rad) * math.cos(lat2_rad) * math.cos(dlon_rad)
bearing = math.atan2(y, x)
bearing = math.degrees(bearing)
bearing = (bearing + 360) % 360
return bearing
def create_terrain_background(frame, width, height, camera_lat, camera_lon, bearing, tilt_angle):
"""Create a Google Earth-style terrain background"""
# Sky gradient (more realistic)
for y in range(int(height * 0.4)): # Sky takes upper 40%
sky_intensity = y / (height * 0.4)
# Sky colors: horizon (light blue) to zenith (darker blue)
r = int(135 + (200 - 135) * sky_intensity)
g = int(206 + (230 - 206) * sky_intensity)
b = int(235 + (255 - 235) * sky_intensity)
frame[y, :] = (b, g, r) # BGR format for OpenCV
# Terrain/ground gradient
terrain_start_y = int(height * 0.4)
for y in range(terrain_start_y, height):
# Create depth illusion
distance_factor = (y - terrain_start_y) / (height - terrain_start_y)
# Terrain colors: greens and browns
base_r = int(80 + 60 * distance_factor)
base_g = int(120 + 80 * distance_factor)
base_b = int(60 + 40 * distance_factor)
# Add terrain texture using noise
for x in range(width):
noise = (math.sin(x * 0.01 + y * 0.01) + math.sin(x * 0.05 + y * 0.02)) * 10
terrain_r = max(0, min(255, base_r + int(noise)))
terrain_g = max(0, min(255, base_g + int(noise)))
terrain_b = max(0, min(255, base_b + int(noise)))
frame[y, x] = (terrain_b, terrain_g, terrain_r)
def calculate_visible_bounds(camera_lat, camera_lon, bearing, view_distance, width, height):
"""Calculate the bounds of the visible area"""
# This is a simplified calculation for the demo
# In a real implementation, you'd use proper 3D projection math
lat_offset = view_distance / 111000 # Rough conversion to degrees
lon_offset = view_distance / (111000 * math.cos(math.radians(camera_lat)))
return {
'min_lat': camera_lat - lat_offset,
'max_lat': camera_lat + lat_offset,
'min_lon': camera_lon - lon_offset,
'max_lon': camera_lon + lon_offset
}
def world_to_screen_3d(world_lat, world_lon, elevation, camera_lat, camera_lon, camera_height,
bearing, tilt_angle, screen_width, screen_height, view_distance):
"""Transform world coordinates to 3D screen coordinates"""
# Calculate relative position
lat_diff = world_lat - camera_lat
lon_diff = world_lon - camera_lon
# Convert to meters (approximate)
x_meters = lon_diff * 111000 * math.cos(math.radians(camera_lat))
y_meters = lat_diff * 111000
z_meters = elevation - camera_height
# Rotate based on bearing
bearing_rad = math.radians(-bearing) # Negative for correct rotation
rotated_x = x_meters * math.cos(bearing_rad) - y_meters * math.sin(bearing_rad)
rotated_y = x_meters * math.sin(bearing_rad) + y_meters * math.cos(bearing_rad)
# Check if point is in front of camera
if rotated_y < 0:
return 0, 0, False
# Apply perspective projection
perspective_scale = view_distance / max(rotated_y, 1)
# Convert to screen coordinates
screen_x = int(screen_width / 2 + rotated_x * perspective_scale * 0.5)
# Apply tilt for vertical positioning
tilt_factor = math.sin(math.radians(tilt_angle))
horizon_y = screen_height * 0.4 # Horizon line
screen_y = int(horizon_y + (z_meters * perspective_scale * tilt_factor * 0.1) +
(rotated_y * perspective_scale * 0.2))
# Check if point is visible on screen
is_visible = (0 <= screen_x < screen_width and 0 <= screen_y < screen_height)
return screen_x, screen_y, is_visible
def get_simulated_elevation(lat, lon, frame_index):
"""Generate simulated elevation data"""
# Create varied terrain using sine waves
elevation = (
50 * math.sin(lat * 100) +
30 * math.sin(lon * 80) +
20 * math.sin((lat + lon) * 60) +
10 * math.sin(frame_index * 0.1) # Dynamic element
)
return max(0, elevation) # Ensure non-negative elevation
def draw_3d_route(frame, route_points_3d, current_frame_index):
"""Draw the route with 3D perspective effects"""
if len(route_points_3d) < 2:
return
# Draw route segments
for i in range(1, len(route_points_3d)):
x1, y1, is_past1 = route_points_3d[i-1]
x2, y2, is_past2 = route_points_3d[i]
# Color based on position relative to current point
if is_past1 and is_past2:
# Past route - blue to cyan gradient
color = (255, 200, 100) # Cyan-ish
thickness = 4
else:
# Future route - red gradient
color = (100, 100, 255) # Red-ish
thickness = 3
# Draw line with shadow for depth
cv2.line(frame, (x1+2, y1+2), (x2+2, y2+2), (50, 50, 50), thickness+2)
cv2.line(frame, (x1, y1), (x2, y2), color, thickness)
# Draw current position marker
if route_points_3d:
for x, y, is_past in route_points_3d:
if is_past:
current_x, current_y = x, y
# Pulsing current position marker
pulse_size = int(12 + 8 * math.sin(current_frame_index * 0.3))
# Shadow
cv2.circle(frame, (current_x+3, current_y+3), pulse_size, (0, 0, 0), -1)
# Outer ring
cv2.circle(frame, (current_x, current_y), pulse_size, (0, 255, 255), -1)
# Inner ring
cv2.circle(frame, (current_x, current_y), pulse_size-4, (255, 255, 255), 2)
# Center dot
cv2.circle(frame, (current_x, current_y), 3, (255, 0, 0), -1)
def add_google_earth_ui(frame, current_pos, bearing, width, height, frame_index, total_frames):
"""Add Google Earth-style UI elements"""
# Speed and info panel (top-left)
panel_width = 250
panel_height = 120
overlay = frame.copy()
# Semi-transparent panel
cv2.rectangle(overlay, (10, 10), (panel_width, panel_height), (50, 50, 50), -1)
cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame)
# Panel border
cv2.rectangle(frame, (10, 10), (panel_width, panel_height), (200, 200, 200), 2)
# Text information
speed = current_pos.get('speed', 0)
timestamp = current_pos.get('deviceTime', '')
y_pos = 35
cv2.putText(frame, f"Speed: {speed:.1f} km/h", (20, y_pos),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
y_pos += 25
cv2.putText(frame, f"Bearing: {bearing:.0f}°", (20, y_pos),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
y_pos += 25
if timestamp:
cv2.putText(frame, f"Time: {timestamp[:16]}", (20, y_pos),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
y_pos += 25
progress = (frame_index + 1) / total_frames * 100
cv2.putText(frame, f"Progress: {progress:.1f}%", (20, y_pos),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
# Compass (top-right)
compass_center_x = width - 80
compass_center_y = 80
compass_radius = 40
# Compass background
cv2.circle(frame, (compass_center_x, compass_center_y), compass_radius, (50, 50, 50), -1)
cv2.circle(frame, (compass_center_x, compass_center_y), compass_radius, (200, 200, 200), 2)
# North indicator
north_x = compass_center_x + int((compass_radius - 10) * math.sin(math.radians(-bearing)))
north_y = compass_center_y - int((compass_radius - 10) * math.cos(math.radians(-bearing)))
cv2.arrowedLine(frame, (compass_center_x, compass_center_y), (north_x, north_y), (0, 0, 255), 3)
# N label
cv2.putText(frame, "N", (compass_center_x - 8, compass_center_y - compass_radius - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# Progress bar (bottom)
progress_bar_width = width - 40
progress_bar_height = 10
progress_bar_x = 20
progress_bar_y = height - 30
# Background
cv2.rectangle(frame, (progress_bar_x, progress_bar_y),
(progress_bar_x + progress_bar_width, progress_bar_y + progress_bar_height),
(100, 100, 100), -1)
# Progress fill
progress_width = int(progress_bar_width * progress / 100)
cv2.rectangle(frame, (progress_bar_x, progress_bar_y),
(progress_bar_x + progress_width, progress_bar_y + progress_bar_height),
(0, 255, 100), -1)
# Border
cv2.rectangle(frame, (progress_bar_x, progress_bar_y),
(progress_bar_x + progress_bar_width, progress_bar_y + progress_bar_height),
(200, 200, 200), 1)
def add_atmospheric_perspective(frame, width, height):
"""Add distance fog effect for realism"""
# Create fog gradient overlay
fog_overlay = np.zeros_like(frame)
# Fog is stronger towards the horizon
horizon_y = int(height * 0.4)
for y in range(horizon_y, height):
fog_intensity = min(0.3, (y - horizon_y) / (height - horizon_y) * 0.3)
fog_color = int(200 * fog_intensity)
fog_overlay[y, :] = (fog_color, fog_color, fog_color)
# Blend fog with frame
cv2.addWeighted(frame, 1.0, fog_overlay, 0.5, 0, frame)
def get_elevation_data(lat, lon):
"""
Get elevation data for a coordinate (optional enhancement)
@@ -294,3 +521,371 @@ def get_elevation_data(lat, lon):
except Exception:
pass
return 0 # Default elevation
def calculate_dynamic_camera_position(current_pos, all_positions, frame_index, min_lat, max_lat, min_lon, max_lon):
"""
Calculate dynamic camera position that follows the route smoothly
"""
camera_lat = current_pos['latitude']
camera_lon = current_pos['longitude']
# Dynamic look-ahead based on speed and terrain
speed = current_pos.get('speed', 0)
base_look_ahead = max(3, min(10, int(speed / 10))) # Adjust based on speed
# Look ahead in the route for camera direction
look_ahead_frames = min(base_look_ahead, len(all_positions) - frame_index - 1)
if look_ahead_frames > 0:
target_pos = all_positions[frame_index + look_ahead_frames]
target_lat = target_pos['latitude']
target_lon = target_pos['longitude']
else:
# Use previous points to maintain direction
if frame_index > 0:
prev_pos = all_positions[frame_index - 1]
# Extrapolate forward
lat_diff = camera_lat - prev_pos['latitude']
lon_diff = camera_lon - prev_pos['longitude']
target_lat = camera_lat + lat_diff
target_lon = camera_lon + lon_diff
else:
target_lat = camera_lat
target_lon = camera_lon
# Calculate smooth bearing with momentum
bearing = calculate_bearing(camera_lat, camera_lon, target_lat, target_lon)
# Add slight camera offset for better viewing angle
offset_distance = 50 # meters
offset_angle = bearing + 45 # 45 degrees offset for better perspective
# Calculate offset position
offset_lat = camera_lat + (offset_distance / 111000) * math.cos(math.radians(offset_angle))
offset_lon = camera_lon + (offset_distance / (111000 * math.cos(math.radians(camera_lat)))) * math.sin(math.radians(offset_angle))
camera_pos = {
'latitude': offset_lat,
'longitude': offset_lon
}
camera_target = {
'latitude': target_lat,
'longitude': target_lon
}
return camera_pos, camera_target, bearing
def calculate_distance(lat1, lon1, lat2, lon2):
"""Calculate distance between two GPS points in meters"""
# Haversine formula
R = 6371000 # Earth's radius in meters
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
delta_phi = math.radians(lat2 - lat1)
delta_lambda = math.radians(lon2 - lon1)
a = math.sin(delta_phi/2)**2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
return R * c
def world_to_camera_screen(world_lat, world_lon, elevation, camera_pos, camera_target, camera_height,
bearing, tilt_angle, fov, screen_width, screen_height):
"""
Advanced 3D transformation from world coordinates to screen coordinates
"""
# Convert GPS to local coordinates relative to camera
lat_diff = world_lat - camera_pos['latitude']
lon_diff = world_lon - camera_pos['longitude']
# Convert to meters (more accurate conversion)
x_meters = lon_diff * 111320 * math.cos(math.radians(camera_pos['latitude']))
y_meters = lat_diff * 110540
z_meters = elevation - camera_height
# Apply camera rotation based on bearing
bearing_rad = math.radians(-bearing)
tilt_rad = math.radians(tilt_angle)
# Rotate around Z axis (bearing)
rotated_x = x_meters * math.cos(bearing_rad) - y_meters * math.sin(bearing_rad)
rotated_y = x_meters * math.sin(bearing_rad) + y_meters * math.cos(bearing_rad)
rotated_z = z_meters
# Apply tilt rotation
final_y = rotated_y * math.cos(tilt_rad) - rotated_z * math.sin(tilt_rad)
final_z = rotated_y * math.sin(tilt_rad) + rotated_z * math.cos(tilt_rad)
final_x = rotated_x
# Check if point is in front of camera
if final_y <= 0:
return 0, 0, float('inf'), False
# Perspective projection
fov_rad = math.radians(fov)
f = (screen_width / 2) / math.tan(fov_rad / 2) # Focal length
# Project to screen
screen_x = int(screen_width / 2 + (final_x * f) / final_y)
screen_y = int(screen_height / 2 - (final_z * f) / final_y)
# Calculate depth for sorting
depth = final_y
# Check if point is visible on screen
is_visible = (0 <= screen_x < screen_width and 0 <= screen_y < screen_height)
return screen_x, screen_y, depth, is_visible
def get_enhanced_elevation(lat, lon, point_index, frame_index):
"""
Generate more realistic elevation data with variation
"""
# Base elevation using multiple harmonics
base_elevation = (
100 * math.sin(lat * 50) +
70 * math.sin(lon * 40) +
50 * math.sin((lat + lon) * 30) +
30 * math.sin(lat * 200) * math.cos(lon * 150) +
20 * math.sin(point_index * 0.1) # Smooth variation along route
)
# Add temporal variation for dynamic feel
time_variation = 10 * math.sin(frame_index * 0.05 + point_index * 0.2)
# Ensure realistic elevation range
elevation = max(0, min(500, base_elevation + time_variation))
return elevation
def create_space_entry_frame(start_pos, center_lat, center_lon, min_lat, max_lat, min_lon, max_lon,
width, height, frame_index, total_entry_frames):
"""
Create a Google Earth-style space entry frame transitioning from space to route start
"""
# Create canvas
frame = np.zeros((height, width, 3), dtype=np.uint8)
# Calculate entry progress (0 to 1)
entry_progress = frame_index / total_entry_frames
# Space entry parameters - start very high and descend
max_altitude = 50000 # Start from 50km altitude (space view)
min_altitude = 2000 # End at 2km altitude (good aerial view)
# Smooth descent curve (ease-out animation)
altitude_progress = 1 - (1 - entry_progress) ** 3 # Cubic ease-out
current_altitude = max_altitude - (max_altitude - min_altitude) * altitude_progress
# Camera position starts centered over the route
camera_lat = center_lat
camera_lon = center_lon
# Camera gradually moves toward route start
start_lat = start_pos['latitude']
start_lon = start_pos['longitude']
# Smooth transition to route start position
transition_progress = entry_progress ** 2 # Quadratic for gradual transition
camera_lat = center_lat + (start_lat - center_lat) * transition_progress
camera_lon = center_lon + (start_lon - center_lon) * transition_progress
# Create space/sky background based on altitude
create_space_sky_background(frame, width, height, current_altitude)
# Calculate view bounds based on altitude
view_radius_km = current_altitude * 0.8 # View radius increases with altitude
# Draw Earth curvature effect at high altitudes
if current_altitude > 10000:
draw_earth_curvature(frame, width, height, current_altitude)
# Draw terrain with increasing detail as we descend
draw_terrain_from_altitude(frame, camera_lat, camera_lon, view_radius_km,
width, height, current_altitude, entry_progress)
# Draw route overview (visible from space)
if entry_progress > 0.3: # Route becomes visible partway through descent
draw_route_overview_from_space(frame, min_lat, max_lat, min_lon, max_lon,
camera_lat, camera_lon, view_radius_km,
width, height, entry_progress)
# Add space entry UI
add_space_entry_ui(frame, current_altitude, entry_progress, width, height)
# Add atmospheric glow effect
add_atmospheric_glow(frame, width, height, current_altitude)
return frame
def create_space_sky_background(frame, width, height, altitude):
"""Create background that transitions from space black to sky blue"""
# Space to atmosphere transition
if altitude > 20000:
# Space: black to deep blue
space_factor = min(1.0, (altitude - 20000) / 30000)
for y in range(height):
intensity = y / height
r = int(5 * (1 - space_factor) + 0 * space_factor)
g = int(15 * (1 - space_factor) + 0 * space_factor)
b = int(30 * (1 - space_factor) + 0 * space_factor)
frame[y, :] = (b, g, r)
else:
# Atmosphere: blue gradient
for y in range(int(height * 0.6)): # Sky portion
sky_intensity = y / (height * 0.6)
r = int(135 + (200 - 135) * sky_intensity)
g = int(206 + (230 - 206) * sky_intensity)
b = int(235 + (255 - 235) * sky_intensity)
frame[y, :] = (b, g, r)
# Terrain visible below
terrain_start_y = int(height * 0.6)
for y in range(terrain_start_y, height):
distance_factor = (y - terrain_start_y) / (height - terrain_start_y)
base_r = int(80 + 60 * distance_factor)
base_g = int(120 + 80 * distance_factor)
base_b = int(60 + 40 * distance_factor)
frame[y, :] = (base_b, base_g, base_r)
def draw_earth_curvature(frame, width, height, altitude):
"""Draw Earth's curvature at high altitudes"""
if altitude < 15000:
return
# Calculate curvature based on altitude
curve_factor = min(1.0, (altitude - 15000) / 35000)
# Draw curved horizon
horizon_y = int(height * 0.5)
curve_amplitude = int(50 * curve_factor)
for x in range(width):
# Sine wave for curvature
curve_offset = int(curve_amplitude * math.sin(math.pi * x / width))
curve_y = horizon_y + curve_offset
# Draw atmospheric glow around Earth
for glow_y in range(max(0, curve_y - 20), min(height, curve_y + 5)):
glow_intensity = 1.0 - abs(glow_y - curve_y) / 20.0
if glow_intensity > 0:
frame[glow_y, x] = (
min(255, frame[glow_y, x][0] + int(100 * glow_intensity)),
min(255, frame[glow_y, x][1] + int(150 * glow_intensity)),
min(255, frame[glow_y, x][2] + int(200 * glow_intensity))
)
def draw_terrain_from_altitude(frame, camera_lat, camera_lon, view_radius_km,
width, height, altitude, progress):
"""Draw terrain detail that increases as altitude decreases"""
if altitude > 10000:
# High altitude: show landmass outlines
draw_landmass_outlines(frame, camera_lat, camera_lon, view_radius_km, width, height)
else:
# Lower altitude: show detailed terrain
detail_factor = 1.0 - (altitude / 10000)
draw_detailed_terrain(frame, camera_lat, camera_lon, view_radius_km,
width, height, detail_factor)
def draw_landmass_outlines(frame, camera_lat, camera_lon, view_radius_km, width, height):
"""Draw simplified landmass outlines for space view"""
# Simplified representation - in real implementation you'd use actual geographic data
center_x, center_y = width // 2, height // 2
# Draw some landmass shapes
for i in range(5):
angle = i * 72 # 360/5 degrees
radius = int(100 + 50 * math.sin(angle * math.pi / 180))
land_x = center_x + int(radius * math.cos(math.radians(angle)))
land_y = center_y + int(radius * math.sin(math.radians(angle)))
# Draw landmass blob
cv2.circle(frame, (land_x, land_y), 30, (139, 69, 19), -1) # Brown landmass
def draw_detailed_terrain(frame, camera_lat, camera_lon, view_radius_km,
width, height, detail_factor):
"""Draw detailed terrain features"""
# Create terrain texture
for y in range(height):
for x in range(width):
# Generate terrain using noise
noise1 = math.sin(x * 0.01 * detail_factor) * math.sin(y * 0.01 * detail_factor)
noise2 = math.sin(x * 0.05 * detail_factor) * math.sin(y * 0.03 * detail_factor)
terrain_height = (noise1 + noise2) * 0.5
# Color based on terrain height
if terrain_height > 0.3:
# Mountains - grey/brown
color = (100, 120, 140)
elif terrain_height > 0:
# Hills - green
color = (60, 140, 80)
else:
# Valleys/water - blue
color = (120, 100, 60)
frame[y, x] = color
def draw_route_overview_from_space(frame, min_lat, max_lat, min_lon, max_lon,
camera_lat, camera_lon, view_radius_km,
width, height, progress):
"""Draw route overview visible from space"""
# Simple route line for space view
# Map route bounds to screen coordinates
route_width = max_lon - min_lon
route_height = max_lat - min_lat
if route_width == 0 or route_height == 0:
return
# Calculate route position on screen
lat_offset = (min_lat + max_lat) / 2 - camera_lat
lon_offset = (min_lon + max_lon) / 2 - camera_lon
# Convert to screen coordinates (simplified)
route_x = int(width / 2 + lon_offset * width / 2)
route_y = int(height / 2 + lat_offset * height / 2)
route_screen_width = int(route_width * width / 4)
route_screen_height = int(route_height * height / 4)
# Draw route area highlight
if (0 < route_x < width and 0 < route_y < height):
# Pulsing route highlight
pulse = int(20 + 10 * math.sin(progress * 10))
cv2.rectangle(frame,
(route_x - route_screen_width, route_y - route_screen_height),
(route_x + route_screen_width, route_y + route_screen_height),
(0, 255, 255), 2) # Cyan highlight
def add_space_entry_ui(frame, altitude, progress, width, height):
"""Add UI elements for space entry sequence"""
# Altitude indicator
altitude_text = f"Altitude: {altitude/1000:.1f} km"
cv2.putText(frame, altitude_text, (20, 50),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# Entry progress
progress_text = f"Descent: {progress*100:.0f}%"
cv2.putText(frame, progress_text, (20, 90),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# "Approaching Route" text when near the end
if progress > 0.7:
cv2.putText(frame, "Approaching Route...", (width//2 - 120, height//2),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2)
def add_atmospheric_glow(frame, width, height, altitude):
"""Add atmospheric glow effect"""
if altitude > 5000:
# Create atmospheric glow overlay
glow_intensity = min(0.3, altitude / 50000)
# Horizontal glow bands
for y in range(height):
distance_from_horizon = abs(y - height // 2) / (height // 2)
if distance_from_horizon < 0.5:
glow = int(50 * glow_intensity * (1 - distance_from_horizon * 2))
frame[y, :, 2] = np.minimum(255, frame[y, :, 2] + glow) # Add blue glow