""" 3D Video Animation Generator Creates professional Google Earth-style 3D video animations from GPS route data """ import json import os import math import requests import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont, ImageFilter import tempfile import shutil from datetime import datetime import random def generate_3d_video_animation(project_name, resources_folder, label_widget, progress_widget, popup_widget, clock_module): """ Generate a 3D video animation similar to Relive Args: project_name: Name of the project resources_folder: Path to resources folder label_widget: Kivy label for status updates progress_widget: Kivy progress bar popup_widget: Kivy popup to dismiss when done clock_module: Kivy Clock module for scheduling """ def update_progress(progress_val, status_text): """Update UI from background thread""" def _update(dt): progress_widget.value = progress_val label_widget.text = status_text clock_module.schedule_once(_update, 0) def finish_generation(success, message, output_path=None): """Finish the generation process""" def _finish(dt): if popup_widget: popup_widget.dismiss() # Show result popup from kivy.uix.popup import Popup from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.uix.label import Label result_layout = BoxLayout(orientation='vertical', spacing=10, padding=10) if success: result_label = Label( text=f"3D Video Generated Successfully!\n\nSaved to:\n{output_path}", color=(0, 1, 0, 1), halign="center" ) open_btn = Button( text="Open Video Folder", size_hint_y=None, height=40, background_color=(0.2, 0.7, 0.2, 1) ) open_btn.bind(on_press=lambda x: (os.system(f"xdg-open '{os.path.dirname(output_path)}'"), result_popup.dismiss())) result_layout.add_widget(result_label) result_layout.add_widget(open_btn) else: result_label = Label( text=f"Generation Failed:\n{message}", color=(1, 0, 0, 1), halign="center" ) result_layout.add_widget(result_label) close_btn = Button( text="Close", size_hint_y=None, height=40, background_color=(0.3, 0.3, 0.3, 1) ) result_layout.add_widget(close_btn) result_popup = Popup( title="3D Video Generation Result", content=result_layout, size_hint=(0.9, 0.6), auto_dismiss=False ) close_btn.bind(on_press=lambda x: result_popup.dismiss()) result_popup.open() clock_module.schedule_once(_finish, 0) def run_generation(): """Main generation function""" try: # Step 1: Load route data update_progress(10, "Loading route data...") project_folder = os.path.join(resources_folder, "projects", project_name) positions_path = os.path.join(project_folder, "positions.json") if not os.path.exists(positions_path): finish_generation(False, "No route data found!") return with open(positions_path, "r") as f: positions = json.load(f) if len(positions) < 10: finish_generation(False, "Route too short for 3D animation (minimum 10 points)") return # Step 2: Calculate route bounds and center update_progress(20, "Calculating route boundaries...") lats = [pos['latitude'] for pos in positions] lons = [pos['longitude'] for pos in positions] center_lat = sum(lats) / len(lats) center_lon = sum(lons) / len(lons) min_lat, max_lat = min(lats), max(lats) min_lon, max_lon = min(lons), max(lons) # 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() frames_dir = os.path.join(temp_dir, "frames") os.makedirs(frames_dir) # Video settings width, height = 1920, 1080 fps = 30 entry_frames = 90 # 3 seconds at 30fps for space entry total_frames = entry_frames + len(positions) * 2 # Entry + route animation 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}...") try: 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 except Exception as e: print(f"Error generating space entry frame {i}: {e}") # Create a simple fallback frame fallback_frame = np.zeros((height, width, 3), dtype=np.uint8) fallback_frame[:] = (0, 0, 50) # Space-like background frame_path = os.path.join(frames_dir, f"frame_{frame_counter:06d}.png") cv2.imwrite(frame_path, fallback_frame) frame_counter += 1 # Generate route following frames for i, pos in enumerate(positions): progress = 30 + ((entry_frames + i) / total_frames) * 40 update_progress(progress, f"Route frame {i+1}/{len(positions)}...") try: frame = create_3d_frame( pos, positions, i, center_lat, center_lon, min_lat, max_lat, min_lon, max_lon, width, height ) # Save frame frame_path = os.path.join(frames_dir, f"frame_{frame_counter:06d}.png") cv2.imwrite(frame_path, frame) frame_counter += 1 except Exception as e: print(f"Error generating route frame {i}: {e}") # Create a simple fallback frame to continue generation fallback_frame = np.zeros((height, width, 3), dtype=np.uint8) fallback_frame[:] = (50, 50, 100) # Dark blue background frame_path = os.path.join(frames_dir, f"frame_{frame_counter:06d}.png") cv2.imwrite(frame_path, fallback_frame) frame_counter += 1 # Add transition bridge frame (smooth transition from space to route) try: update_progress(progress, "Creating transition bridge...") transition_frame = create_transition_bridge_frame( positions[0], center_lat, center_lon, min_lat, max_lat, min_lon, max_lon, width, height ) frame_path = os.path.join(frames_dir, f"frame_{frame_counter:06d}.png") cv2.imwrite(frame_path, transition_frame) frame_counter += 1 except Exception as e: print(f"Warning: Could not create transition bridge frame: {e}") # Step 4: Create video update_progress(75, "Compiling video...") # Output path output_filename = f"{project_name}_3d_animation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4" output_path = os.path.join(project_folder, output_filename) # Create video writer fourcc = cv2.VideoWriter_fourcc(*'mp4v') video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) # Add frames to video frame_files = sorted([f for f in os.listdir(frames_dir) if f.endswith('.png')]) for frame_file in frame_files: frame_path = os.path.join(frames_dir, frame_file) frame = cv2.imread(frame_path) video_writer.write(frame) video_writer.release() # Step 5: Add audio (optional) update_progress(90, "Adding finishing touches...") # Clean up shutil.rmtree(temp_dir) update_progress(100, "3D Video generated successfully!") finish_generation(True, "Success!", output_path) except Exception as e: finish_generation(False, str(e)) # Start generation in background import threading thread = threading.Thread(target=run_generation) thread.daemon = True thread.start() 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 Google Earth-style 3D frame with camera following the route """ # Create canvas frame = np.zeros((height, width, 3), dtype=np.uint8) # 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 ) # 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 # 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']) if dist_to_camera > view_distance * 2: # Skip points too far away continue # 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: # Mark points as past, current, or future # Ensure at least the current position (frame_index) is marked as past is_past_or_current = i <= frame_index route_points_3d.append((screen_x, screen_y, is_past_or_current)) # Draw route with enhanced 3D effects draw_3d_route(frame, route_points_3d, frame_index) # Add Google Earth-style UI overlays add_google_earth_ui(frame, current_pos, camera_bearing, width, height, frame_index, len(all_positions)) # 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 professional Google Earth-style terrain background""" # Enhanced sky gradient with realistic atmospheric scattering for y in range(int(height * 0.35)): # Sky takes upper 35% sky_intensity = y / (height * 0.35) # Realistic sky colors with atmospheric perspective horizon_r, horizon_g, horizon_b = 255, 248, 220 # Warm horizon zenith_r, zenith_g, zenith_b = 135, 206, 235 # Sky blue r = int(horizon_r + (zenith_r - horizon_r) * sky_intensity) g = int(horizon_g + (zenith_g - horizon_g) * sky_intensity) b = int(horizon_b + (zenith_b - horizon_b) * sky_intensity) frame[y, :] = (b, g, r) # BGR format for OpenCV # Realistic terrain with multiple layers and textures terrain_start_y = int(height * 0.35) create_enhanced_terrain_layer(frame, width, height, terrain_start_y, camera_lat, camera_lon) # Add atmospheric haze for depth add_atmospheric_haze(frame, width, height, terrain_start_y) # Add realistic cloud shadows add_cloud_shadows(frame, width, height, terrain_start_y) def create_enhanced_terrain_layer(frame, width, height, start_y, camera_lat, camera_lon): """Create enhanced terrain with realistic colors and textures""" for y in range(start_y, height): distance_factor = (y - start_y) / (height - start_y) for x in range(width): # Multiple noise layers for realistic terrain variation terrain_color = generate_enhanced_terrain_color(x, y, camera_lat, camera_lon, width, height, distance_factor) frame[y, x] = terrain_color def generate_enhanced_terrain_color(x, y, camera_lat, camera_lon, width, height, distance_factor): """Generate enhanced terrain color with realistic geographic features""" # Base terrain using multiple octaves of noise noise_scale1 = 0.01 noise_scale2 = 0.005 noise_scale3 = 0.002 # Primary terrain features terrain1 = math.sin(x * noise_scale1) * math.sin(y * noise_scale1) terrain2 = math.sin(x * noise_scale2 + 100) * math.sin(y * noise_scale2 + 100) * 0.7 terrain3 = math.sin(x * noise_scale3 + 200) * math.sin(y * noise_scale3 + 200) * 0.3 combined_terrain = terrain1 + terrain2 + terrain3 # Simulate geographic coordinate influence lat_influence = math.sin(camera_lat * 0.1) * 0.5 lon_influence = math.cos(camera_lon * 0.1) * 0.3 geographic_factor = lat_influence + lon_influence final_terrain = combined_terrain + geographic_factor # Classify terrain types based on noise if final_terrain > 1.2: # High mountains - snow-capped peaks base_color = (240, 248, 255) # Alice blue elif final_terrain > 0.8: # Mountains - rocky gray/brown base_color = (105, 105, 105) # Dim gray elif final_terrain > 0.4: # Hills - forest green base_color = (34, 139, 34) # Forest green elif final_terrain > 0.1: # Plains - grassland base_color = (124, 252, 0) # Lawn green elif final_terrain > -0.2: # Agricultural areas - golden base_color = (255, 215, 0) # Gold elif final_terrain > -0.5: # Desert/arid - sandy brown base_color = (244, 164, 96) # Sandy brown else: # Water bodies - deep blue base_color = (25, 25, 112) # Midnight blue # Apply distance-based atmospheric perspective atmosphere_fade = 1.0 - (distance_factor * 0.4) final_color = tuple(int(c * atmosphere_fade + 200 * (1 - atmosphere_fade)) for c in base_color) # Add subtle texture variation texture_noise = (math.sin(x * 0.1) * math.sin(y * 0.1)) * 10 final_color = tuple(max(0, min(255, c + int(texture_noise))) for c in final_color) return final_color def add_atmospheric_haze(frame, width, height, terrain_start_y): """Add realistic atmospheric haze for depth perception""" haze_overlay = np.zeros_like(frame) for y in range(terrain_start_y, height): distance_factor = (y - terrain_start_y) / (height - terrain_start_y) haze_intensity = distance_factor * 0.3 # Stronger haze in distance if haze_intensity > 0: haze_color = int(220 * haze_intensity) # Light blue-gray haze haze_overlay[y, :] = (haze_color, haze_color, haze_color) # Blend haze with terrain cv2.addWeighted(frame, 1.0, haze_overlay, 0.3, 0, frame) def add_cloud_shadows(frame, width, height, terrain_start_y): """Add realistic cloud shadows on terrain""" shadow_overlay = np.zeros_like(frame) # Generate cloud shadow patterns for shadow_id in range(3): shadow_center_x = int(width * (0.2 + shadow_id * 0.3)) shadow_center_y = int(terrain_start_y + (height - terrain_start_y) * 0.3) shadow_radius = 80 + shadow_id * 30 # Create soft circular shadows for y in range(max(terrain_start_y, shadow_center_y - shadow_radius), min(height, shadow_center_y + shadow_radius)): for x in range(max(0, shadow_center_x - shadow_radius), min(width, shadow_center_x + shadow_radius)): distance = math.sqrt((x - shadow_center_x)**2 + (y - shadow_center_y)**2) if distance < shadow_radius: shadow_intensity = 1.0 - (distance / shadow_radius) shadow_intensity *= 0.3 # Subtle shadows shadow_value = int(50 * shadow_intensity) shadow_overlay[y, x] = (shadow_value, shadow_value, shadow_value) # Apply shadows frame_dark = frame.astype(np.int32) - shadow_overlay.astype(np.int32) frame[:] = np.clip(frame_dark, 0, 255).astype(np.uint8) 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: # Find the current position - look for the last "past" point, or use the first point current_x, current_y = None, None # Try to find the last past point for x, y, is_past in route_points_3d: if is_past: current_x, current_y = x, y # If no past points found (beginning of route), use the first point if current_x is None and len(route_points_3d) > 0: current_x, current_y, _ = route_points_3d[0] # Only draw marker if we have a valid position if current_x is not None and current_y is not None: # 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) """ try: # Using a free elevation API url = f"https://api.open-elevation.com/api/v1/lookup?locations={lat},{lon}" response = requests.get(url, timeout=5) if response.status_code == 200: data = response.json() return data['results'][0]['elevation'] 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 realistic Google Earth-style space entry frame """ # Create high-resolution canvas frame = np.zeros((height, width, 3), dtype=np.uint8) # Calculate entry progress (0 to 1) entry_progress = frame_index / total_entry_frames # Realistic altitude progression max_altitude = 50000 # 50km - edge of space min_altitude = 2000 # 2km - final descent altitude # Smooth descent with realistic deceleration altitude_progress = 1 - (1 - entry_progress) ** 2.5 current_altitude = max_altitude - (max_altitude - min_altitude) * altitude_progress # Create realistic Earth background create_realistic_earth_background(frame, width, height, current_altitude, center_lat, center_lon, entry_progress) # Add realistic cloud layers add_realistic_cloud_layers(frame, width, height, current_altitude, entry_progress) # Draw geographic features (landmasses, coastlines, rivers) draw_geographic_features(frame, width, height, center_lat, center_lon, current_altitude, entry_progress) # Add route visualization that becomes more detailed as we descend if entry_progress > 0.4: # Route becomes visible halfway through descent draw_route_from_space(frame, min_lat, max_lat, min_lon, max_lon, center_lat, center_lon, width, height, current_altitude, entry_progress) # Add realistic atmospheric effects add_space_atmospheric_effects(frame, width, height, current_altitude, entry_progress) # Add professional UI with realistic information add_space_entry_professional_ui(frame, current_altitude, entry_progress, start_pos, width, height) # Add Earth's terminator line (day/night boundary) if at high altitude if current_altitude > 20000: add_earth_terminator(frame, width, height, current_altitude) return frame def create_realistic_earth_background(frame, width, height, altitude, center_lat, center_lon, progress): """Create realistic Earth background based on altitude""" if altitude > 30000: # Space view - show Earth as sphere create_earth_sphere_view(frame, width, height, altitude, center_lat, center_lon) elif altitude > 15000: # High atmosphere - curved horizon create_curved_horizon_view(frame, width, height, altitude, center_lat, center_lon) else: # Lower atmosphere - flat perspective create_flat_earth_view(frame, width, height, altitude, center_lat, center_lon, progress) def create_earth_sphere_view(frame, width, height, altitude, center_lat, center_lon): """Create spherical Earth view for space altitudes""" # Space background - deep black with stars frame[:] = (5, 5, 15) # Very dark blue-black # Add stars np.random.seed(42) # Consistent star pattern for _ in range(200): x = np.random.randint(0, width) y = np.random.randint(0, height // 2) # Stars only in upper half brightness = np.random.randint(100, 255) frame[y, x] = (brightness, brightness, brightness) # Earth sphere earth_radius = min(width, height) // 3 earth_center_x = width // 2 earth_center_y = int(height * 0.7) # Earth in lower portion # Create Earth disk y_coords, x_coords = np.ogrid[:height, :width] earth_mask = (x_coords - earth_center_x)**2 + (y_coords - earth_center_y)**2 <= earth_radius**2 # Earth base colors (blue oceans, green/brown land) earth_colors = create_earth_surface_colors(width, height, earth_center_x, earth_center_y, earth_radius) frame[earth_mask] = earth_colors[earth_mask] # Add Earth's atmospheric glow add_earth_atmospheric_glow(frame, earth_center_x, earth_center_y, earth_radius, width, height) def create_curved_horizon_view(frame, width, height, altitude, center_lat, center_lon): """Create curved horizon view for high atmosphere""" # Sky gradient from space to atmosphere for y in range(height): if y < height * 0.3: # Upper atmosphere/space intensity = y / (height * 0.3) r = int(5 + (50 - 5) * intensity) g = int(10 + (80 - 10) * intensity) b = int(25 + (120 - 25) * intensity) else: # Lower atmosphere intensity = (y - height * 0.3) / (height * 0.7) r = int(50 + (135 - 50) * intensity) g = int(80 + (206 - 80) * intensity) b = int(120 + (235 - 120) * intensity) frame[y, :] = (b, g, r) # Curved horizon line horizon_y = int(height * 0.6) curvature = altitude / 1000 # More curvature at higher altitude for x in range(width): curve_offset = int(curvature * math.sin(math.pi * x / width)) curve_y = horizon_y + curve_offset # Earth surface below horizon if curve_y < height: earth_surface_color = get_earth_surface_color(x, curve_y, center_lat, center_lon, width, height) for y in range(curve_y, height): if y < height: frame[y, x] = earth_surface_color def create_flat_earth_view(frame, width, height, altitude, center_lat, center_lon, progress): """Create flat Earth perspective for lower altitudes""" # Realistic sky gradient for y in range(int(height * 0.4)): sky_intensity = y / (height * 0.4) 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 with realistic colors and textures terrain_start_y = int(height * 0.4) for y in range(terrain_start_y, height): for x in range(width): terrain_color = generate_realistic_terrain_color(x, y, center_lat, center_lon, width, height, altitude) frame[y, x] = terrain_color def add_realistic_cloud_layers(frame, width, height, altitude, progress): """Add realistic cloud formations""" if altitude < 30000: # Clouds visible below 30km cloud_density = max(0.1, 1.0 - altitude / 30000) # Generate cloud layer using Perlin-like noise for y in range(int(height * 0.3), int(height * 0.7)): for x in range(0, width, 4): # Sample every 4 pixels for performance cloud_noise = ( math.sin(x * 0.02 + progress * 10) * math.sin(y * 0.03 + progress * 8) * math.sin((x + y) * 0.01 + progress * 5) ) cloud_intensity = max(0, cloud_noise * cloud_density) if cloud_intensity > 0.3: cloud_alpha = min(0.6, cloud_intensity) cloud_color = (255, 255, 255) # Blend cloud with existing background for dx in range(4): if x + dx < width: current_color = frame[y, x + dx] blended = [ int(current_color[i] * (1 - cloud_alpha) + cloud_color[i] * cloud_alpha) for i in range(3) ] frame[y, x + dx] = tuple(blended) def draw_geographic_features(frame, width, height, center_lat, center_lon, altitude, progress): """Draw realistic geographic features like coastlines and rivers""" if altitude < 20000: # Geographic features visible below 20km detail_level = 1.0 - (altitude / 20000) # Simulate coastlines using fractal-like patterns coastline_points = generate_coastline_pattern(center_lat, center_lon, width, height, detail_level) for points in coastline_points: if len(points) > 1: # Draw coastline for i in range(len(points) - 1): cv2.line(frame, points[i], points[i + 1], (139, 69, 19), 2) # Brown coastline # Add major rivers if detail level is high enough if detail_level > 0.5: river_points = generate_river_pattern(center_lat, center_lon, width, height, detail_level) for points in river_points: if len(points) > 1: for i in range(len(points) - 1): cv2.line(frame, points[i], points[i + 1], (255, 178, 102), 1) # Blue rivers def create_earth_surface_colors(width, height, center_x, center_y, radius): """Generate realistic Earth surface colors""" colors = np.zeros((height, width, 3), dtype=np.uint8) for y in range(height): for x in range(width): dist_from_center = math.sqrt((x - center_x)**2 + (y - center_y)**2) if dist_from_center <= radius: # Use longitude/latitude to determine terrain type angle = math.atan2(y - center_y, x - center_x) # Simulate different terrain types terrain_noise = ( math.sin(angle * 3) * math.sin(dist_from_center * 0.1) * math.cos(angle * 2 + dist_from_center * 0.05) ) if terrain_noise > 0.3: # Land - green/brown colors[y, x] = (34, 139, 34) # Forest green elif terrain_noise > 0: # Land - brown/tan colors[y, x] = (160, 82, 45) # Saddle brown else: # Ocean - blue colors[y, x] = (139, 0, 0) # Dark blue return colors 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: # Use numpy operations to prevent overflow current_pixel = frame[glow_y, x].astype(np.int32) # Convert to int32 to prevent overflow glow_r = int(100 * glow_intensity) glow_g = int(150 * glow_intensity) glow_b = int(200 * glow_intensity) frame[glow_y, x] = ( min(255, max(0, current_pixel[0] + glow_r)), min(255, max(0, current_pixel[1] + glow_g)), min(255, max(0, current_pixel[2] + glow_b)) ) 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)) # Use numpy operations to safely add glow without overflow current_blue = frame[y, :, 2].astype(np.int32) frame[y, :, 2] = np.clip(current_blue + glow, 0, 255).astype(np.uint8) def ensure_safe_pixel_value(value): """Ensure pixel values are within valid range (0-255)""" return max(0, min(255, int(value))) def safe_frame_assignment(frame, y, x, color): """Safely assign color values to frame to prevent overflow""" if isinstance(color, (list, tuple)) and len(color) == 3: frame[y, x] = ( ensure_safe_pixel_value(color[0]), ensure_safe_pixel_value(color[1]), ensure_safe_pixel_value(color[2]) ) else: # Single value color safe_value = ensure_safe_pixel_value(color) frame[y, x] = (safe_value, safe_value, safe_value) def create_transition_bridge_frame(start_pos, center_lat, center_lon, min_lat, max_lat, min_lon, max_lon, width, height): """ Create a smooth transition frame that bridges space entry to route following """ # This is essentially the first route frame but with a slightly higher camera position # to create a smooth transition from the space entry descent # Create canvas frame = np.zeros((height, width, 3), dtype=np.uint8) # Transition camera parameters (between space entry end and route start) camera_height = 2500 # Slightly higher than normal route following camera_lat = start_pos['latitude'] camera_lon = start_pos['longitude'] # Create sky background (transition from space-like to normal sky) for y in range(int(height * 0.5)): # Sky portion sky_intensity = y / (height * 0.5) r = int(100 + (200 - 100) * sky_intensity) g = int(150 + (230 - 150) * sky_intensity) b = int(200 + (255 - 200) * sky_intensity) frame[y, :] = (b, g, r) # Terrain background terrain_start_y = int(height * 0.5) 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) # Add route start indicator route_center_x = width // 2 route_center_y = int(height * 0.7) # Draw route start marker cv2.circle(frame, (route_center_x, route_center_y), 20, (0, 255, 255), 3) cv2.circle(frame, (route_center_x, route_center_y), 8, (255, 255, 255), -1) # Add "Route Starting" text cv2.putText(frame, "Route Starting...", (width//2 - 100, height//2 + 100), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2) return frame def get_earth_surface_color(x, y, center_lat, center_lon, width, height): """Get realistic Earth surface color for a given screen position""" # Convert screen position to geographic simulation terrain_noise = ( math.sin(x * 0.01) * math.sin(y * 0.01) + math.sin(x * 0.05) * math.sin(y * 0.03) + math.sin(x * 0.02 + y * 0.02) ) if terrain_noise > 0.5: # Mountains/hills - brown/gray return (101, 67, 33) elif terrain_noise > 0: # Plains/grassland - green return (34, 139, 34) elif terrain_noise > -0.3: # Desert/dry land - tan return (194, 178, 128) else: # Water - blue return (65, 105, 225) def generate_realistic_terrain_color(x, y, center_lat, center_lon, width, height, altitude): """Generate realistic terrain color with proper texturing""" # Multiple noise layers for realistic terrain noise1 = math.sin(x * 0.005) * math.sin(y * 0.005) noise2 = math.sin(x * 0.02) * math.sin(y * 0.02) * 0.5 noise3 = math.sin(x * 0.1) * math.sin(y * 0.1) * 0.2 combined_noise = noise1 + noise2 + noise3 # Distance from center affects terrain type center_x, center_y = width // 2, height // 2 distance_from_center = math.sqrt((x - center_x)**2 + (y - center_y)**2) / min(width, height) # Terrain classification based on noise and position if combined_noise > 0.7: # High mountains - gray/white base_color = (169, 169, 169) elif combined_noise > 0.3: # Hills/forests - green base_color = (34, 139, 34) elif combined_noise > 0: # Plains - light green base_color = (124, 252, 0) elif combined_noise > -0.3: # Desert/dry areas - tan base_color = (210, 180, 140) else: # Water/wetlands - blue base_color = (65, 105, 225) # Add altitude-based atmospheric perspective distance_factor = min(1.0, (y - height * 0.4) / (height * 0.6)) atmosphere_factor = 1.0 - (distance_factor * 0.3) final_color = tuple(int(c * atmosphere_factor) for c in base_color) return final_color def generate_coastline_pattern(center_lat, center_lon, width, height, detail_level): """Generate realistic coastline patterns""" coastlines = [] if detail_level > 0.3: # Generate several coastline segments for coast_id in range(3): points = [] start_x = int(width * (0.1 + coast_id * 0.3)) start_y = int(height * 0.6) # Generate fractal-like coastline for i in range(20): noise_x = int(20 * math.sin(i * 0.5 + coast_id) * detail_level) noise_y = int(10 * math.cos(i * 0.3 + coast_id) * detail_level) x = start_x + i * 30 + noise_x y = start_y + noise_y if 0 <= x < width and 0 <= y < height: points.append((x, y)) if len(points) > 1: coastlines.append(points) return coastlines def generate_river_pattern(center_lat, center_lon, width, height, detail_level): """Generate realistic river patterns""" rivers = [] if detail_level > 0.6: # Generate a few major rivers for river_id in range(2): points = [] start_x = int(width * (0.2 + river_id * 0.6)) start_y = int(height * 0.3) # Rivers flow generally downward with meanders for i in range(15): meander = int(15 * math.sin(i * 0.8 + river_id * 2)) x = start_x + meander y = start_y + i * 25 if 0 <= x < width and 0 <= y < height: points.append((x, y)) if len(points) > 1: rivers.append(points) return rivers def add_earth_atmospheric_glow(frame, center_x, center_y, radius, width, height): """Add realistic atmospheric glow around Earth""" for y in range(max(0, center_y - radius - 50), min(height, center_y + radius + 50)): for x in range(max(0, center_x - radius - 50), min(width, center_x + radius + 50)): distance = math.sqrt((x - center_x)**2 + (y - center_y)**2) if radius < distance < radius + 40: # Atmospheric glow glow_intensity = 1.0 - (distance - radius) / 40.0 if glow_intensity > 0: glow_blue = int(100 * glow_intensity) current_color = frame[y, x].astype(np.int32) frame[y, x] = ( np.clip(current_color[0] + glow_blue // 3, 0, 255), np.clip(current_color[1] + glow_blue // 2, 0, 255), np.clip(current_color[2] + glow_blue, 0, 255) ) def draw_route_from_space(frame, min_lat, max_lat, min_lon, max_lon, camera_lat, camera_lon, width, height, altitude, progress): """Draw route visualization visible from space with realistic styling""" # Calculate route bounds on screen lat_center = (min_lat + max_lat) / 2 lon_center = (min_lon + max_lon) / 2 # Project to screen coordinates lat_offset = (lat_center - camera_lat) * 100000 / altitude lon_offset = (lon_center - camera_lon) * 100000 / altitude route_x = int(width / 2 + lon_offset) route_y = int(height / 2 + lat_offset) if 0 < route_x < width and 0 < route_y < height: # Route area highlight - pulsing effect pulse = 1.0 + 0.3 * math.sin(progress * 20) route_size = int(20 * pulse) # Outer glow cv2.circle(frame, (route_x, route_y), route_size + 10, (100, 100, 255), 2) # Main route indicator cv2.circle(frame, (route_x, route_y), route_size, (255, 255, 100), 3) # Inner core cv2.circle(frame, (route_x, route_y), route_size // 2, (255, 255, 255), -1) # Route path preview (simplified) route_length = int(40 + progress * 60) for i in range(5): angle = i * math.pi / 2 end_x = route_x + int(route_length * math.cos(angle)) end_y = route_y + int(route_length * math.sin(angle)) cv2.line(frame, (route_x, route_y), (end_x, end_y), (255, 200, 100), 2) def add_space_atmospheric_effects(frame, width, height, altitude, progress): """Add realistic atmospheric effects based on altitude""" if altitude > 25000: # Space effects - stars and cosmic background add_star_field(frame, width, height, altitude) if 5000 < altitude < 30000: # Atmospheric scattering effects add_atmospheric_scattering(frame, width, height, altitude) # Earth's limb glow at high altitudes if altitude > 15000: add_earth_limb_glow(frame, width, height, altitude) def add_star_field(frame, width, height, altitude): """Add realistic star field for space views""" star_intensity = min(1.0, (altitude - 25000) / 25000) num_stars = int(300 * star_intensity) np.random.seed(42) # Consistent star pattern for _ in range(num_stars): x = np.random.randint(0, width) y = np.random.randint(0, height // 2) # Stars mainly in upper half # Vary star brightness and size brightness = np.random.randint(150, 255) size = np.random.choice([1, 1, 1, 2], p=[0.7, 0.2, 0.05, 0.05]) if size == 1: frame[y, x] = (brightness, brightness, brightness) else: cv2.circle(frame, (x, y), size, (brightness, brightness, brightness), -1) def add_atmospheric_scattering(frame, width, height, altitude): """Add atmospheric scattering effects""" scattering_intensity = 1.0 - (altitude / 30000) # Blue scattering in upper atmosphere for y in range(0, int(height * 0.5)): scatter_factor = scattering_intensity * (1.0 - y / (height * 0.5)) blue_scatter = int(20 * scatter_factor) for x in range(0, width, 4): # Sample for performance current_color = frame[y, x:x+4] frame[y, x:x+4] = np.minimum(255, current_color + [blue_scatter//2, blue_scatter//2, blue_scatter]) def add_earth_limb_glow(frame, width, height, altitude): """Add Earth's limb glow effect""" limb_intensity = min(1.0, (altitude - 15000) / 20000) # Horizontal glow band representing Earth's atmosphere glow_y = int(height * 0.7) glow_height = int(30 * limb_intensity) for y in range(max(0, glow_y - glow_height), min(height, glow_y + glow_height)): glow_factor = 1.0 - abs(y - glow_y) / glow_height blue_glow = int(80 * glow_factor * limb_intensity) frame[y, :, 2] = np.minimum(255, frame[y, :, 2] + blue_glow) def add_earth_terminator(frame, width, height, altitude): """Add Earth's day/night terminator line""" if altitude > 20000: # Diagonal terminator line terminator_angle = math.pi / 6 # 30 degrees for y in range(height): terminator_x = int(width * 0.3 + y * math.tan(terminator_angle)) if 0 < terminator_x < width: # Darken the night side for x in range(0, terminator_x): current_color = frame[y, x].astype(np.int32) darkened = current_color * 0.3 # 70% darker frame[y, x] = np.clip(darkened, 0, 255).astype(np.uint8) # Add terminator glow glow_width = 20 for dx in range(-glow_width, glow_width): glow_x = terminator_x + dx if 0 <= glow_x < width: glow_intensity = 1.0 - abs(dx) / glow_width orange_glow = int(50 * glow_intensity) current_color = frame[y, glow_x].astype(np.int32) frame[y, glow_x] = np.clip(current_color + [orange_glow//2, orange_glow//2, orange_glow], 0, 255) def add_space_entry_professional_ui(frame, altitude, progress, start_pos, width, height): """Add professional Google Earth-style UI for space entry""" # Create semi-transparent overlay panels overlay = frame.copy() # Main information panel (top-left) panel_width = 320 panel_height = 140 cv2.rectangle(overlay, (20, 20), (panel_width, panel_height), (40, 40, 40), -1) cv2.addWeighted(overlay, 0.8, frame, 0.2, 0, frame) # Panel border with gradient effect cv2.rectangle(frame, (20, 20), (panel_width, panel_height), (200, 200, 200), 2) cv2.rectangle(frame, (22, 22), (panel_width-2, panel_height-2), (100, 100, 100), 1) # Altitude display (large, prominent) altitude_text = f"{altitude/1000:.1f} km" cv2.putText(frame, "ALTITUDE", (30, 45), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (150, 150, 150), 1) cv2.putText(frame, altitude_text, (30, 75), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255, 255, 255), 2) # Descent progress progress_text = f"DESCENT: {progress*100:.0f}%" cv2.putText(frame, progress_text, (30, 105), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (100, 255, 100), 2) # Coordinates coord_text = f"LAT: {start_pos['latitude']:.4f}" cv2.putText(frame, coord_text, (30, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1) coord_text = f"LON: {start_pos['longitude']:.4f}" cv2.putText(frame, coord_text, (30, 145), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1) # Speed indicator (top-right) speed_panel_x = width - 250 cv2.rectangle(overlay, (speed_panel_x, 20), (width - 20, 100), (40, 40, 40), -1) cv2.addWeighted(overlay, 0.8, frame, 0.2, 0, frame) cv2.rectangle(frame, (speed_panel_x, 20), (width - 20, 100), (200, 200, 200), 2) descent_speed = int((1 - progress) * 15000 + 500) # Simulated descent speed cv2.putText(frame, "DESCENT RATE", (speed_panel_x + 10, 45), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (150, 150, 150), 1) cv2.putText(frame, f"{descent_speed} m/min", (speed_panel_x + 10, 75), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 100, 100), 2) # Entry status message (center) if progress < 0.3: status_text = "INITIATING DESCENT FROM SPACE" text_color = (100, 200, 255) elif progress < 0.7: status_text = "ENTERING EARTH'S ATMOSPHERE" text_color = (255, 200, 100) else: status_text = "APPROACHING ROUTE START POINT" text_color = (100, 255, 100) text_size = cv2.getTextSize(status_text, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2)[0] text_x = (width - text_size[0]) // 2 text_y = height - 80 # Text background cv2.rectangle(frame, (text_x - 10, text_y - 35), (text_x + text_size[0] + 10, text_y + 10), (0, 0, 0), -1) cv2.putText(frame, status_text, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.8, text_color, 2) # Progress bar (bottom) progress_bar_width = width - 100 progress_bar_height = 8 progress_bar_x = 50 progress_bar_y = height - 40 # Background cv2.rectangle(frame, (progress_bar_x, progress_bar_y), (progress_bar_x + progress_bar_width, progress_bar_y + progress_bar_height), (60, 60, 60), -1) # Progress fill with color gradient progress_width = int(progress_bar_width * progress) if progress_width > 0: # Create gradient effect for x in range(progress_width): color_factor = x / progress_width if progress_width > 0 else 0 r = int(255 * (1 - color_factor) + 100 * color_factor) g = int(100 * (1 - color_factor) + 255 * color_factor) b = 100 cv2.rectangle(frame, (progress_bar_x + x, progress_bar_y), (progress_bar_x + x + 1, progress_bar_y + progress_bar_height), (b, g, r), -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)