still updating the 3d video
This commit is contained in:
@@ -1,40 +1,30 @@
|
||||
# Enhanced 3D Video Animation Feature
|
||||
# Professional Google Earth-Style 3D Video Animation
|
||||
|
||||
## Overview
|
||||
The Enhanced 3D Video Animation feature generates professional, Google Earth-style video animations from GPS route data with spectacular space entry sequences. This upgraded system creates cinematic flythrough experiences starting from space and descending to follow the route with dynamic camera movement, realistic perspective, and advanced visual effects.
|
||||
The Professional Google Earth-Style 3D Video Animation feature generates cinematic, high-quality video animations from GPS route data with realistic space entry sequences. This system creates authentic Google Earth-style visuals with professional terrain rendering, atmospheric effects, and spectacular space-to-Earth transitions.
|
||||
|
||||
## Core Enhancements
|
||||
## Major Visual Enhancements
|
||||
|
||||
### Space Entry Sequence (NEW!)
|
||||
- **Spectacular Entry from Space**: 3-second cinematic descent from 50km altitude
|
||||
- **Smooth Space-to-Earth Transition**: Seamless transition from space view to aerial following
|
||||
- **Earth Curvature Effects**: Realistic Earth curvature visible at high altitudes
|
||||
- **Atmospheric Layers**: Progressive atmospheric effects during descent
|
||||
- **Route Identification**: Route becomes visible and highlighted during descent
|
||||
### Realistic Google Earth Visuals
|
||||
- **Authentic Earth Sphere Rendering**: Realistic planetary view from space with proper curvature
|
||||
- **Professional Terrain Textures**: Multi-layer terrain with forests, mountains, plains, deserts, and water bodies
|
||||
- **Geographic Feature Simulation**: Coastlines, rivers, and landmasses with fractal-like detail
|
||||
- **Atmospheric Scattering**: Realistic atmospheric effects and color gradients
|
||||
- **Cloud Layer Rendering**: Dynamic cloud formations with proper shadows
|
||||
|
||||
### Advanced Camera System
|
||||
- **Improved Aerial Perspective**: Camera height optimized for 1000-3000m range
|
||||
- **Dynamic Camera Following**: Intelligent camera positioning that follows the route
|
||||
- **Speed-Adaptive Look-Ahead**: Camera direction adjusts based on vehicle speed
|
||||
- **Smooth Camera Transitions**: Fluid camera movements with momentum
|
||||
- **Enhanced Perspective Offset**: Camera positioned for optimal aerial viewing angles
|
||||
- **Dynamic Height & Tilt**: Camera height and angle adapt to terrain and speed
|
||||
### Enhanced Space Entry Sequence
|
||||
- **Spectacular Space View**: Authentic space background with star fields and Earth sphere
|
||||
- **Realistic Atmospheric Entry**: Progressive transition through atmospheric layers
|
||||
- **Earth's Terminator Line**: Day/night boundary visible at high altitudes
|
||||
- **Professional UI**: Google Earth-style information panels and progress indicators
|
||||
- **Cinematic Descent**: Smooth altitude progression from 50km to route level
|
||||
|
||||
### Google Earth-Style Perspective
|
||||
- **True 3D Projection**: Proper field-of-view perspective projection
|
||||
- **Depth-Aware Rendering**: Objects rendered in correct Z-order
|
||||
- **Enhanced Aerial Views**: Optimized 1000-3000m altitude for perfect aerial perspective
|
||||
- **Realistic Elevation**: Enhanced terrain with multi-layered elevation simulation
|
||||
- **Atmospheric Perspective**: Distance fog and haze effects for depth
|
||||
- **Terrain Grid**: Perspective grid for enhanced depth perception
|
||||
|
||||
### Enhanced Visual Effects
|
||||
- **Space-to-Earth Transition**: Spectacular entry sequence with space background
|
||||
- **Multi-Layer Terrain**: Realistic terrain with varied colors and textures
|
||||
- **Gradient Backgrounds**: Dynamic space-to-sky-to-terrain transitions
|
||||
- **Enhanced Route Visualization**: Depth-based thickness and opacity
|
||||
- **Advanced Markers**: Multi-layer current position with shadows and glows
|
||||
- **Direction Indicators**: Speed-based directional arrows
|
||||
### Advanced Terrain System
|
||||
- **Multi-Octave Terrain Generation**: Realistic landscape using multiple noise layers
|
||||
- **Geographic Coordinate Influence**: Terrain varies based on actual GPS coordinates
|
||||
- **Atmospheric Perspective**: Distance-based color shifts and haze effects
|
||||
- **Cloud Shadow Mapping**: Realistic shadow patterns on terrain
|
||||
- **Enhanced Color Palette**: Professional color schemes for different terrain types
|
||||
|
||||
### Professional UI Elements
|
||||
- **Information Panel**: Speed, bearing, altitude, time, and progress with gradients
|
||||
@@ -48,11 +38,14 @@ The Enhanced 3D Video Animation feature generates professional, Google Earth-sty
|
||||
- **Frame Rate**: 30 FPS (smooth motion)
|
||||
- **Format**: MP4 video (universal compatibility)
|
||||
- **Compression**: MP4V codec optimized for quality
|
||||
- **Space Entry**: 3-second descent from 50km altitude
|
||||
- **Visual Quality**: Professional Google Earth-style rendering
|
||||
- **Space Entry**: 3-second descent from 50km altitude with realistic visuals
|
||||
- **Camera Height**: 1000-3000m (dynamic aerial perspective)
|
||||
- **View Distance**: 3000m ahead (enhanced for aerial views)
|
||||
- **Field of View**: 75° (optimized for aerial perspective)
|
||||
- **Tilt Angle**: 65-73° (dynamic for terrain following)
|
||||
- **Terrain Detail**: Multi-layer realistic terrain with 6+ terrain types
|
||||
- **Color Depth**: Professional color palette with atmospheric effects
|
||||
- **Entry Altitude Range**: 50km → 2km (space to aerial transition)
|
||||
|
||||
## Advanced Animation Features
|
||||
@@ -226,14 +219,18 @@ Metadata Addition → File Output
|
||||
1. **Space View (0-1 seconds)**: Starts from 50km altitude with black space background and Earth curvature
|
||||
2. **Atmospheric Entry (1-2 seconds)**: Gradual transition showing atmospheric layers and blue sky emergence
|
||||
3. **Route Approach (2-3 seconds)**: Descent to 2km altitude with route becoming visible and highlighted
|
||||
4. **Aerial Following (3+ seconds)**: Seamless transition to dynamic camera following at optimal aerial height
|
||||
4. **Transition Bridge (3-3.5 seconds)**: Smooth bridge frame announcing route start
|
||||
5. **Aerial Following (3.5+ seconds)**: Seamless transition to dynamic camera following at optimal aerial height
|
||||
|
||||
### Technical Implementation
|
||||
- **Altitude Range**: 50,000m → 2,000m → 1,000-3,000m (dynamic)
|
||||
- **Descent Curve**: Cubic ease-out for natural deceleration
|
||||
- **Camera Transition**: Smooth movement from center overview to route start
|
||||
- **Transition Bridge**: Dedicated frame for smooth space-to-route handoff
|
||||
- **Visual Effects**: Earth curvature, atmospheric glow, space-to-sky gradient
|
||||
- **Route Visibility**: Progressive highlighting during descent approach
|
||||
- **Error Handling**: Robust fallback frames ensure generation continues
|
||||
- **Variable Safety**: Protected against undefined position markers
|
||||
|
||||
### Enhanced Aerial Perspective
|
||||
- **Optimal Height Range**: 1000-3000 meters for perfect aerial views
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
3D Video Animation Generator
|
||||
Creates Relive-style 3D video animations from GPS route data
|
||||
Creates professional Google Earth-style 3D video animations from GPS route data
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -9,10 +9,11 @@ import math
|
||||
import requests
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
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):
|
||||
"""
|
||||
@@ -144,6 +145,7 @@ def generate_3d_video_animation(project_name, resources_folder, label_widget, pr
|
||||
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,
|
||||
@@ -154,11 +156,21 @@ def generate_3d_video_animation(project_name, resources_folder, label_widget, pr
|
||||
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,
|
||||
@@ -170,6 +182,29 @@ def generate_3d_video_animation(project_name, resources_folder, label_widget, pr
|
||||
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...")
|
||||
|
||||
@@ -252,7 +287,10 @@ def create_3d_frame(current_pos, all_positions, frame_index, center_lat, center_
|
||||
)
|
||||
|
||||
if is_visible:
|
||||
route_points_3d.append((screen_x, screen_y, i <= frame_index))
|
||||
# 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)
|
||||
@@ -281,35 +319,144 @@ def calculate_bearing(lat1, lon1, lat2, lon2):
|
||||
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)
|
||||
"""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
|
||||
|
||||
# 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)
|
||||
# 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)
|
||||
|
||||
# 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 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)
|
||||
|
||||
# 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)))
|
||||
# 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
|
||||
|
||||
frame[y, x] = (terrain_b, terrain_g, terrain_r)
|
||||
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"""
|
||||
@@ -400,10 +547,20 @@ def draw_3d_route(frame, route_points_3d, current_frame_index):
|
||||
|
||||
# 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))
|
||||
|
||||
@@ -662,63 +819,222 @@ def get_enhanced_elevation(lat, lon, point_index, frame_index):
|
||||
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 a realistic Google Earth-style space entry frame
|
||||
"""
|
||||
# Create canvas
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
# Realistic altitude progression
|
||||
max_altitude = 50000 # 50km - edge of space
|
||||
min_altitude = 2000 # 2km - final descent altitude
|
||||
|
||||
# Smooth descent curve (ease-out animation)
|
||||
altitude_progress = 1 - (1 - entry_progress) ** 3 # Cubic ease-out
|
||||
# Smooth descent with realistic deceleration
|
||||
altitude_progress = 1 - (1 - entry_progress) ** 2.5
|
||||
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
|
||||
# Create realistic Earth background
|
||||
create_realistic_earth_background(frame, width, height, current_altitude, center_lat, center_lon, entry_progress)
|
||||
|
||||
# Camera gradually moves toward route start
|
||||
start_lat = start_pos['latitude']
|
||||
start_lon = start_pos['longitude']
|
||||
# Add realistic cloud layers
|
||||
add_realistic_cloud_layers(frame, width, height, current_altitude, entry_progress)
|
||||
|
||||
# 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
|
||||
# Draw geographic features (landmasses, coastlines, rivers)
|
||||
draw_geographic_features(frame, width, height, center_lat, center_lon, current_altitude, entry_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,
|
||||
# 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)
|
||||
|
||||
# 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 realistic atmospheric effects
|
||||
add_space_atmospheric_effects(frame, width, height, current_altitude, entry_progress)
|
||||
|
||||
# Add space entry UI
|
||||
add_space_entry_ui(frame, current_altitude, entry_progress, width, height)
|
||||
# Add professional UI with realistic information
|
||||
add_space_entry_professional_ui(frame, current_altitude, entry_progress, start_pos, width, height)
|
||||
|
||||
# Add atmospheric glow effect
|
||||
add_atmospheric_glow(frame, width, height, current_altitude)
|
||||
# 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
|
||||
@@ -770,10 +1086,16 @@ def draw_earth_curvature(frame, width, height, altitude):
|
||||
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, 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))
|
||||
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,
|
||||
@@ -888,4 +1210,418 @@ def add_atmospheric_glow(frame, width, height, altitude):
|
||||
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
|
||||
# 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)
|
||||
|
||||
@@ -1 +1 @@
|
||||
gAAAAABobLgWZWKYF0nkSynV8d6s9J_G4GWuCbRofa_raK783ueF0ES9WXnIX02OcwMWWgpV1Ps4DJxDBTXtAQfjWHR0WrIN-FfcnViS1PEFFNDUtsN_PSSTND2vLOQEMRtUYYKG_UDZ
|
||||
gAAAAABobPcZPB2ZDJEqHef1TANu1yOi8sYRts1-zpn_zcMBH3ydy0TWJp_G1YWt_G7dpvK29qN2UtnLkhi6-_EXq9AWcy1xHgF6FL5jl27RhdBF3-zNDSFOcDDnSVVbgvMjG88tOBMa
|
||||
BIN
resources/projects/day1/day1_3d_animation_20250708_142815.mp4
Normal file
BIN
resources/projects/day1/day1_3d_animation_20250708_142815.mp4
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
resources/projects/day1/preview.png
Normal file
BIN
resources/projects/day1/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 907 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 907 KiB |
Binary file not shown.
107
test_enhanced_video.py
Normal file
107
test_enhanced_video.py
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test the enhanced Google Earth-style video generation
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append('/home/pi/Desktop/traccar_animation')
|
||||
|
||||
from py_scripts.video_3d_generator import create_space_entry_frame, create_3d_frame
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
def test_enhanced_visuals():
|
||||
"""Test the enhanced Google Earth-style visuals"""
|
||||
print("Testing enhanced Google Earth-style video generation...")
|
||||
|
||||
# Test parameters
|
||||
start_pos = {
|
||||
'latitude': 45.7749,
|
||||
'longitude': -122.4194,
|
||||
'speed': 0,
|
||||
'deviceTime': '2025-07-08 12:00:00'
|
||||
}
|
||||
|
||||
positions = [start_pos]
|
||||
center_lat = 45.7749
|
||||
center_lon = -122.4194
|
||||
min_lat = 45.7700
|
||||
max_lat = 45.7800
|
||||
min_lon = -122.4250
|
||||
max_lon = -122.4150
|
||||
width = 1920
|
||||
height = 1080
|
||||
|
||||
# Test space entry frames at different altitudes
|
||||
test_frames = [0, 30, 60, 89] # Beginning, middle, end of space entry
|
||||
|
||||
for frame_idx in test_frames:
|
||||
print(f"Testing space entry frame {frame_idx}/90...")
|
||||
|
||||
try:
|
||||
frame = create_space_entry_frame(
|
||||
start_pos, center_lat, center_lon,
|
||||
min_lat, max_lat, min_lon, max_lon,
|
||||
width, height, frame_idx, 90
|
||||
)
|
||||
|
||||
# Verify frame quality
|
||||
if frame is None:
|
||||
print(f"❌ Frame {frame_idx} is None")
|
||||
return False
|
||||
|
||||
if frame.shape != (height, width, 3):
|
||||
print(f"❌ Frame {frame_idx} wrong shape: {frame.shape}")
|
||||
return False
|
||||
|
||||
# Check for visual diversity (not just black/empty)
|
||||
unique_colors = len(np.unique(frame.reshape(-1, frame.shape[2]), axis=0))
|
||||
if unique_colors < 100: # Should have many colors for realistic visuals
|
||||
print(f"❌ Frame {frame_idx} too few colors: {unique_colors}")
|
||||
return False
|
||||
|
||||
print(f"✅ Space entry frame {frame_idx} - Colors: {unique_colors}, Range: {np.min(frame)}-{np.max(frame)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Space entry frame {frame_idx} failed: {e}")
|
||||
return False
|
||||
|
||||
# Test route following frame
|
||||
print("Testing enhanced route following frame...")
|
||||
try:
|
||||
route_frame = create_3d_frame(
|
||||
start_pos, positions, 0, center_lat, center_lon,
|
||||
min_lat, max_lat, min_lon, max_lon, width, height
|
||||
)
|
||||
|
||||
if route_frame is None:
|
||||
print("❌ Route frame is None")
|
||||
return False
|
||||
|
||||
unique_colors = len(np.unique(route_frame.reshape(-1, route_frame.shape[2]), axis=0))
|
||||
if unique_colors < 100:
|
||||
print(f"❌ Route frame too few colors: {unique_colors}")
|
||||
return False
|
||||
|
||||
print(f"✅ Route frame - Colors: {unique_colors}, Range: {np.min(route_frame)}-{np.max(route_frame)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Route frame failed: {e}")
|
||||
return False
|
||||
|
||||
print("✅ All enhanced visual tests passed!")
|
||||
print("🌍 Google Earth-style backgrounds are working properly")
|
||||
print("🚀 Space entry sequence has realistic visuals")
|
||||
print("🎬 High-quality terrain and atmospheric effects generated")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_enhanced_visuals()
|
||||
if success:
|
||||
print("\n🎉 Enhanced Google Earth-style video generation is ready!")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n❌ Enhanced video generation test failed")
|
||||
sys.exit(1)
|
||||
115
test_transition.py
Normal file
115
test_transition.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the transition from space entry to route following works correctly
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append('/home/pi/Desktop/traccar_animation')
|
||||
|
||||
from py_scripts.video_3d_generator import create_3d_frame, draw_3d_route
|
||||
import numpy as np
|
||||
|
||||
def test_route_transition():
|
||||
"""Test the transition from space entry to route following"""
|
||||
print("Testing route transition...")
|
||||
|
||||
# Sample route positions
|
||||
positions = [
|
||||
{'latitude': 45.7749, 'longitude': -122.4194, 'speed': 0, 'deviceTime': '2025-07-08 12:00:00'},
|
||||
{'latitude': 45.7750, 'longitude': -122.4195, 'speed': 20, 'deviceTime': '2025-07-08 12:01:00'},
|
||||
{'latitude': 45.7751, 'longitude': -122.4196, 'speed': 30, 'deviceTime': '2025-07-08 12:02:00'},
|
||||
{'latitude': 45.7752, 'longitude': -122.4197, 'speed': 40, 'deviceTime': '2025-07-08 12:03:00'},
|
||||
{'latitude': 45.7753, 'longitude': -122.4198, 'speed': 50, 'deviceTime': '2025-07-08 12:04:00'},
|
||||
]
|
||||
|
||||
# Test parameters
|
||||
center_lat = 45.7751
|
||||
center_lon = -122.4196
|
||||
min_lat = 45.7740
|
||||
max_lat = 45.7760
|
||||
min_lon = -122.4210
|
||||
max_lon = -122.4180
|
||||
width = 1920
|
||||
height = 1080
|
||||
|
||||
# Test the first few frames (transition period)
|
||||
for frame_index in range(len(positions)):
|
||||
print(f"Testing route frame {frame_index}...")
|
||||
try:
|
||||
current_pos = positions[frame_index]
|
||||
frame = create_3d_frame(
|
||||
current_pos, positions, frame_index,
|
||||
center_lat, center_lon, min_lat, max_lat, min_lon, max_lon,
|
||||
width, height
|
||||
)
|
||||
|
||||
# Check frame integrity
|
||||
if frame is None:
|
||||
print(f"ERROR: Route frame {frame_index} is None")
|
||||
return False
|
||||
|
||||
if frame.shape != (height, width, 3):
|
||||
print(f"ERROR: Route frame {frame_index} has wrong shape: {frame.shape}")
|
||||
return False
|
||||
|
||||
# Check for valid pixel values
|
||||
if np.any(frame < 0) or np.any(frame > 255):
|
||||
print(f"ERROR: Route frame {frame_index} has invalid pixel values")
|
||||
return False
|
||||
|
||||
print(f"Route frame {frame_index} OK - Shape: {frame.shape}, Min: {np.min(frame)}, Max: {np.max(frame)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Route frame {frame_index} failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
print("All route transition frames generated successfully!")
|
||||
return True
|
||||
|
||||
def test_draw_3d_route():
|
||||
"""Test the draw_3d_route function with various scenarios"""
|
||||
print("Testing draw_3d_route function...")
|
||||
|
||||
# Create test frame
|
||||
frame = np.zeros((1080, 1920, 3), dtype=np.uint8)
|
||||
|
||||
# Test scenarios
|
||||
test_cases = [
|
||||
# Empty route
|
||||
([], 0, "Empty route"),
|
||||
# Single point - no past points
|
||||
([(960, 540, False)], 0, "Single future point"),
|
||||
# Single point - current point
|
||||
([(960, 540, True)], 0, "Single current point"),
|
||||
# Multiple points - beginning of route
|
||||
([(960, 540, True), (970, 550, False), (980, 560, False)], 0, "Beginning of route"),
|
||||
# Multiple points - middle of route
|
||||
([(950, 530, True), (960, 540, True), (970, 550, False), (980, 560, False)], 1, "Middle of route"),
|
||||
]
|
||||
|
||||
for route_points_3d, frame_index, description in test_cases:
|
||||
print(f"Testing: {description}")
|
||||
try:
|
||||
test_frame = frame.copy()
|
||||
draw_3d_route(test_frame, route_points_3d, frame_index)
|
||||
print(f"✅ {description} - OK")
|
||||
except Exception as e:
|
||||
print(f"❌ {description} - Failed: {e}")
|
||||
return False
|
||||
|
||||
print("All draw_3d_route tests passed!")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
route_success = test_route_transition()
|
||||
draw_success = test_draw_3d_route()
|
||||
|
||||
if route_success and draw_success:
|
||||
print("✅ All transition tests PASSED")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("❌ Some transition tests FAILED")
|
||||
sys.exit(1)
|
||||
75
test_video_generator.py
Normal file
75
test_video_generator.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the enhanced 3D video generator works without overflow errors
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append('/home/pi/Desktop/traccar_animation')
|
||||
|
||||
from py_scripts.video_3d_generator import create_space_entry_frame
|
||||
import numpy as np
|
||||
|
||||
def test_space_entry_frame():
|
||||
"""Test the space entry frame generation"""
|
||||
print("Testing space entry frame generation...")
|
||||
|
||||
# Sample position data
|
||||
start_pos = {
|
||||
'latitude': 45.7749,
|
||||
'longitude': -122.4194,
|
||||
'speed': 50,
|
||||
'deviceTime': '2025-07-08 12:00:00'
|
||||
}
|
||||
|
||||
# Test parameters
|
||||
center_lat = 45.7749
|
||||
center_lon = -122.4194
|
||||
min_lat = 45.7000
|
||||
max_lat = 45.8500
|
||||
min_lon = -122.5000
|
||||
max_lon = -122.3500
|
||||
width = 1920
|
||||
height = 1080
|
||||
|
||||
# Test multiple frames to ensure no overflow
|
||||
for frame_index in range(0, 90, 10): # Test every 10th frame
|
||||
print(f"Testing frame {frame_index}/90...")
|
||||
try:
|
||||
frame = create_space_entry_frame(
|
||||
start_pos, center_lat, center_lon,
|
||||
min_lat, max_lat, min_lon, max_lon,
|
||||
width, height, frame_index, 90
|
||||
)
|
||||
|
||||
# Check frame integrity
|
||||
if frame is None:
|
||||
print(f"ERROR: Frame {frame_index} is None")
|
||||
return False
|
||||
|
||||
if frame.shape != (height, width, 3):
|
||||
print(f"ERROR: Frame {frame_index} has wrong shape: {frame.shape}")
|
||||
return False
|
||||
|
||||
# Check for valid pixel values
|
||||
if np.any(frame < 0) or np.any(frame > 255):
|
||||
print(f"ERROR: Frame {frame_index} has invalid pixel values")
|
||||
return False
|
||||
|
||||
print(f"Frame {frame_index} OK - Shape: {frame.shape}, Min: {np.min(frame)}, Max: {np.max(frame)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Frame {frame_index} failed: {e}")
|
||||
return False
|
||||
|
||||
print("All space entry frames generated successfully!")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_space_entry_frame()
|
||||
if success:
|
||||
print("✅ Space entry frame generation test PASSED")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("❌ Space entry frame generation test FAILED")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user