car_web2 / simulation /video_generator.py
wesam0099's picture
Deploy CrashLens
8adf0de
"""
Professional Video Generator for Accident Scenarios
====================================================
Generates high-quality animated visualizations of accident scenarios
with realistic car graphics, detailed roads, and smooth animations.
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.patches as patches
from matplotlib.patches import Rectangle, Circle, FancyBboxPatch, Polygon, Wedge, Arc
from matplotlib.collections import PatchCollection
from matplotlib.transforms import Affine2D
import matplotlib.patheffects as path_effects
from pathlib import Path
import json
# Project paths
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "visualizations"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
class Car:
"""Professional car visualization with realistic shape."""
def __init__(self, ax, x, y, angle=0, color='#e74c3c', label='', scale=1.0):
self.ax = ax
self.x = x
self.y = y
self.angle = angle # degrees, 0 = facing right
self.color = color
self.label = label
self.scale = scale
self.patches = []
self.draw()
def draw(self):
"""Draw the car with all its components."""
# Clear previous patches
for p in self.patches:
p.remove()
self.patches = []
# Car dimensions (scaled)
length = 4.5 * self.scale
width = 2.0 * self.scale
# Create car body shape (rounded rectangle with hood and trunk)
# Main body
body = FancyBboxPatch(
(self.x - length/2, self.y - width/2),
length, width,
boxstyle="round,pad=0,rounding_size=0.3",
facecolor=self.color,
edgecolor='#2c3e50',
linewidth=2,
zorder=10
)
# Windshield area (darker)
windshield_length = length * 0.35
windshield = FancyBboxPatch(
(self.x - length/2 + length*0.55, self.y - width/2 + width*0.1),
windshield_length, width * 0.8,
boxstyle="round,pad=0,rounding_size=0.2",
facecolor='#34495e',
edgecolor='#2c3e50',
linewidth=1,
zorder=11
)
# Front windshield highlight
highlight = FancyBboxPatch(
(self.x - length/2 + length*0.58, self.y - width/2 + width*0.15),
windshield_length * 0.3, width * 0.7,
boxstyle="round,pad=0,rounding_size=0.1",
facecolor='#5d6d7e',
edgecolor='none',
alpha=0.5,
zorder=12
)
# Headlights
headlight_y_offset = width * 0.3
headlight1 = Circle(
(self.x + length/2 - 0.3*self.scale, self.y + headlight_y_offset),
0.25 * self.scale,
facecolor='#f1c40f',
edgecolor='#f39c12',
linewidth=1,
zorder=12
)
headlight2 = Circle(
(self.x + length/2 - 0.3*self.scale, self.y - headlight_y_offset),
0.25 * self.scale,
facecolor='#f1c40f',
edgecolor='#f39c12',
linewidth=1,
zorder=12
)
# Taillights
taillight1 = Rectangle(
(self.x - length/2, self.y + width/2 - 0.5*self.scale),
0.3 * self.scale, 0.35 * self.scale,
facecolor='#c0392b',
edgecolor='#922b21',
linewidth=1,
zorder=12
)
taillight2 = Rectangle(
(self.x - length/2, self.y - width/2 + 0.15*self.scale),
0.3 * self.scale, 0.35 * self.scale,
facecolor='#c0392b',
edgecolor='#922b21',
linewidth=1,
zorder=12
)
# Wheels
wheel_size = 0.5 * self.scale
wheel_positions = [
(self.x - length/2 + length*0.2, self.y + width/2 - 0.1*self.scale),
(self.x - length/2 + length*0.2, self.y - width/2 + 0.1*self.scale),
(self.x + length/2 - length*0.2, self.y + width/2 - 0.1*self.scale),
(self.x + length/2 - length*0.2, self.y - width/2 + 0.1*self.scale),
]
wheels = []
for wx, wy in wheel_positions:
# Tire
tire = Circle(
(wx, wy), wheel_size,
facecolor='#2c3e50',
edgecolor='#1a252f',
linewidth=1.5,
zorder=9
)
# Hubcap
hubcap = Circle(
(wx, wy), wheel_size * 0.6,
facecolor='#7f8c8d',
edgecolor='#95a5a6',
linewidth=1,
zorder=9
)
wheels.extend([tire, hubcap])
# Add shadow under car
shadow = FancyBboxPatch(
(self.x - length/2 + 0.2, self.y - width/2 - 0.3),
length, width,
boxstyle="round,pad=0,rounding_size=0.3",
facecolor='black',
edgecolor='none',
alpha=0.2,
zorder=5
)
# Store all patches
all_patches = [shadow, body, windshield, highlight,
headlight1, headlight2, taillight1, taillight2] + wheels
# Apply rotation transform
transform = Affine2D().rotate_deg_around(self.x, self.y, self.angle) + self.ax.transData
for patch in all_patches:
patch.set_transform(transform)
self.ax.add_patch(patch)
self.patches.append(patch)
def set_position(self, x, y, angle=None):
"""Update car position and optionally angle."""
self.x = x
self.y = y
if angle is not None:
self.angle = angle
self.draw()
def remove(self):
"""Remove all patches."""
for p in self.patches:
try:
p.remove()
except:
pass
self.patches = []
class ProfessionalAccidentVideoGenerator:
"""Generate professional animated videos of accident scenarios."""
def __init__(self, width=800, height=600, dpi=100):
self.width = width
self.height = height
self.dpi = dpi
self.fig_width = width / dpi
self.fig_height = height / dpi
# Color scheme
self.colors = {
'background': '#1a1a2e',
'road': '#4a4a5a',
'road_edge': '#6a6a7a',
'lane_marking': '#ffffff',
'center_line': '#f1c40f',
'grass': '#2d5a27',
'sidewalk': '#95a5a6',
'car1': '#e74c3c', # Red
'car2': '#3498db', # Blue
'impact': '#f39c12',
'text': '#ecf0f1'
}
def _draw_road_texture(self, ax, road_rect, orientation='horizontal'):
"""Add realistic road texture and markings."""
x, y, w, h = road_rect
# Road base with gradient effect
road = FancyBboxPatch(
(x, y), w, h,
boxstyle="round,pad=0,rounding_size=0.5",
facecolor=self.colors['road'],
edgecolor=self.colors['road_edge'],
linewidth=3,
zorder=1
)
ax.add_patch(road)
# Add road edge lines
if orientation == 'horizontal':
# Top edge
ax.plot([x, x + w], [y + h, y + h], color='#ffffff', linewidth=2, zorder=2)
# Bottom edge
ax.plot([x, x + w], [y, y], color='#ffffff', linewidth=2, zorder=2)
else:
# Left edge
ax.plot([x, x], [y, y + h], color='#ffffff', linewidth=2, zorder=2)
# Right edge
ax.plot([x + w, x + w], [y, y + h], color='#ffffff', linewidth=2, zorder=2)
def _draw_lane_markings(self, ax, start, end, center_y, lane_width, is_two_way=False):
"""Draw dashed lane markings."""
dash_length = 3
gap_length = 2
if is_two_way:
# Center double yellow line
ax.plot([start, end], [center_y + 0.3, center_y + 0.3],
color=self.colors['center_line'], linewidth=2.5, zorder=3)
ax.plot([start, end], [center_y - 0.3, center_y - 0.3],
color=self.colors['center_line'], linewidth=2.5, zorder=3)
# Dashed white lines for lanes
x = start
while x < end:
ax.plot([x, min(x + dash_length, end)], [center_y + lane_width, center_y + lane_width],
color=self.colors['lane_marking'], linewidth=2, zorder=3, alpha=0.8)
ax.plot([x, min(x + dash_length, end)], [center_y - lane_width, center_y - lane_width],
color=self.colors['lane_marking'], linewidth=2, zorder=3, alpha=0.8)
x += dash_length + gap_length
def _add_impact_effect(self, ax, x, y, intensity=1.0):
"""Add explosion/impact effect."""
# Multiple circles for burst effect
for i in range(5):
radius = (2 + i * 0.8) * intensity
alpha = 0.8 - i * 0.15
color = '#f39c12' if i < 2 else '#e74c3c'
impact = Circle(
(x, y), radius,
facecolor=color,
edgecolor='#f1c40f',
alpha=alpha,
linewidth=2 if i == 0 else 0,
zorder=20 - i
)
ax.add_patch(impact)
# Spark lines
for angle in range(0, 360, 45):
rad = np.radians(angle)
length = 4 * intensity
ax.plot(
[x, x + length * np.cos(rad)],
[y, y + length * np.sin(rad)],
color='#f1c40f', linewidth=2, alpha=0.8, zorder=21
)
def generate_rear_end_collision(self, output_path=None):
"""Generate professional rear-end collision animation."""
if output_path is None:
output_path = OUTPUT_DIR / "rear_end_collision.gif"
fig, ax = plt.subplots(figsize=(self.fig_width, self.fig_height), dpi=self.dpi)
ax.set_xlim(0, 100)
ax.set_ylim(0, 75)
ax.set_aspect('equal')
ax.set_facecolor(self.colors['background'])
fig.patch.set_facecolor(self.colors['background'])
ax.axis('off')
# Draw environment
# Grass/ground on sides
grass_top = Rectangle((0, 55), 100, 20, facecolor=self.colors['grass'], zorder=0)
grass_bottom = Rectangle((0, 0), 100, 20, facecolor=self.colors['grass'], zorder=0)
ax.add_patch(grass_top)
ax.add_patch(grass_bottom)
# Main road
self._draw_road_texture(ax, (0, 20, 100, 35), 'horizontal')
# Lane markings (single lane road with dashes)
for x in np.arange(0, 100, 8):
ax.plot([x, x + 4], [37.5, 37.5], color='white', linewidth=2, zorder=3, alpha=0.8)
# Add some road details - small dots/texture
for _ in range(50):
rx, ry = np.random.uniform(5, 95), np.random.uniform(22, 53)
dot = Circle((rx, ry), 0.15, facecolor='#3a3a4a', alpha=0.3, zorder=2)
ax.add_patch(dot)
# Title
title = ax.text(50, 70, 'Rear-End Collision Scenario',
color=self.colors['text'], fontsize=18, ha='center',
weight='bold', zorder=30)
title.set_path_effects([path_effects.withStroke(linewidth=3, foreground='black')])
# Subtitle
subtitle = ax.text(50, 5, 'Vehicle 1 (Red) approaching Vehicle 2 (Blue) at high speed',
color=self.colors['text'], fontsize=11, ha='center', zorder=30)
subtitle.set_path_effects([path_effects.withStroke(linewidth=2, foreground='black')])
# Speed indicators
speed1_text = ax.text(5, 65, '● Red Vehicle: 120 km/h', color='#e74c3c', fontsize=10, weight='bold', zorder=30)
speed2_text = ax.text(5, 60, '● Blue Vehicle: 50 km/h', color='#3498db', fontsize=10, weight='bold', zorder=30)
# Animation parameters
frames = 80
impact_frame = 50
# Store car objects
car1_data = {'x': 10, 'y': 32, 'angle': 0}
car2_data = {'x': 55, 'y': 32, 'angle': 0}
# Initial car drawing
car1_patches = []
car2_patches = []
impact_patches = []
def draw_car(x, y, angle, color, ax):
"""Draw a simple but nice car."""
patches = []
scale = 1.2
length = 5 * scale
width = 2.2 * scale
# Shadow
shadow = FancyBboxPatch(
(x - length/2 + 0.3, y - width/2 - 0.4),
length, width,
boxstyle="round,pad=0,rounding_size=0.4",
facecolor='black', alpha=0.3, zorder=4
)
patches.append(shadow)
# Main body
body = FancyBboxPatch(
(x - length/2, y - width/2),
length, width,
boxstyle="round,pad=0,rounding_size=0.4",
facecolor=color, edgecolor='#1a1a2e', linewidth=2, zorder=10
)
patches.append(body)
# Cabin/windshield
cabin = FancyBboxPatch(
(x - length*0.1, y - width/2 + 0.3),
length * 0.35, width - 0.6,
boxstyle="round,pad=0,rounding_size=0.2",
facecolor='#2c3e50', edgecolor='#1a252f', linewidth=1, zorder=11
)
patches.append(cabin)
# Windshield reflection
reflection = FancyBboxPatch(
(x - length*0.05, y - width/2 + 0.5),
length * 0.15, width - 1.0,
boxstyle="round,pad=0,rounding_size=0.1",
facecolor='#5d6d7e', alpha=0.4, zorder=12
)
patches.append(reflection)
# Headlights
hl1 = Circle((x + length/2 - 0.4, y + width/3), 0.35,
facecolor='#f5f5f5', edgecolor='#bdc3c7', zorder=12)
hl2 = Circle((x + length/2 - 0.4, y - width/3), 0.35,
facecolor='#f5f5f5', edgecolor='#bdc3c7', zorder=12)
patches.extend([hl1, hl2])
# Taillights
tl1 = Rectangle((x - length/2, y + width/3 - 0.2), 0.4, 0.4,
facecolor='#c0392b', edgecolor='#922b21', zorder=12)
tl2 = Rectangle((x - length/2, y - width/3 - 0.2), 0.4, 0.4,
facecolor='#c0392b', edgecolor='#922b21', zorder=12)
patches.extend([tl1, tl2])
# Wheels
wheel_positions = [
(x - length/2 + length*0.2, y + width/2 + 0.1),
(x - length/2 + length*0.2, y - width/2 - 0.1),
(x + length/2 - length*0.2, y + width/2 + 0.1),
(x + length/2 - length*0.2, y - width/2 - 0.1),
]
for wx, wy in wheel_positions:
tire = Circle((wx, wy), 0.6, facecolor='#1a1a2e', zorder=8)
rim = Circle((wx, wy), 0.35, facecolor='#7f8c8d', zorder=9)
patches.extend([tire, rim])
# Apply rotation
transform = Affine2D().rotate_deg_around(x, y, angle) + ax.transData
for p in patches:
p.set_transform(transform)
ax.add_patch(p)
return patches
def animate(frame):
nonlocal car1_patches, car2_patches, impact_patches
# Remove old patches
for p in car1_patches + car2_patches + impact_patches:
try:
p.remove()
except:
pass
car1_patches = []
car2_patches = []
impact_patches = []
# Calculate positions
if frame < impact_frame:
# Car 1 moving fast
x1 = 10 + frame * 1.1
# Car 2 moving slow
x2 = 55 + frame * 0.25
angle1 = 0
angle2 = 0
else:
# Post-impact
post = frame - impact_frame
# Car 1 stops abruptly
x1 = 10 + impact_frame * 1.1 + post * 0.1
# Car 2 gets pushed
x2 = 55 + impact_frame * 0.25 + post * 0.8
# Add some rotation from impact
angle1 = -post * 0.5
angle2 = post * 0.8
# Draw cars
car1_patches = draw_car(x1, 32, angle1, self.colors['car1'], ax)
car2_patches = draw_car(x2, 32, angle2, self.colors['car2'], ax)
# Impact effect at collision
if impact_frame <= frame < impact_frame + 10:
intensity = 1.0 - (frame - impact_frame) * 0.1
impact_x = 10 + impact_frame * 1.1 + 3
# Explosion circles
for i in range(4):
radius = (1.5 + i * 0.6) * intensity
alpha = (0.7 - i * 0.15) * intensity
colors = ['#f39c12', '#e74c3c', '#f1c40f', '#e67e22']
impact = Circle(
(impact_x, 32), radius,
facecolor=colors[i], alpha=alpha, zorder=25 - i
)
ax.add_patch(impact)
impact_patches.append(impact)
# Spark lines
for angle in range(0, 360, 30):
rad = np.radians(angle + frame * 10)
length = 3 * intensity
line, = ax.plot(
[impact_x, impact_x + length * np.cos(rad)],
[32, 32 + length * np.sin(rad)],
color='#f1c40f', linewidth=2, alpha=intensity, zorder=26
)
# We need to track these too but plot returns Line2D not Patch
return car1_patches + car2_patches
# Create animation
anim = animation.FuncAnimation(fig, animate, frames=frames, interval=60, blit=False)
anim.save(str(output_path), writer='pillow', fps=20)
plt.close()
return str(output_path)
def generate_head_on_collision(self, output_path=None):
"""Generate professional head-on collision animation."""
if output_path is None:
output_path = OUTPUT_DIR / "head_on_collision.gif"
fig, ax = plt.subplots(figsize=(self.fig_width, self.fig_height), dpi=self.dpi)
ax.set_xlim(0, 100)
ax.set_ylim(0, 75)
ax.set_aspect('equal')
ax.set_facecolor(self.colors['background'])
fig.patch.set_facecolor(self.colors['background'])
ax.axis('off')
# Environment
grass_top = Rectangle((0, 55), 100, 20, facecolor=self.colors['grass'], zorder=0)
grass_bottom = Rectangle((0, 0), 100, 20, facecolor=self.colors['grass'], zorder=0)
ax.add_patch(grass_top)
ax.add_patch(grass_bottom)
# Two-lane road
self._draw_road_texture(ax, (0, 20, 100, 35), 'horizontal')
# Center line (double yellow)
ax.plot([0, 100], [37.8, 37.8], color=self.colors['center_line'], linewidth=3, zorder=3)
ax.plot([0, 100], [37.2, 37.2], color=self.colors['center_line'], linewidth=3, zorder=3)
# Lane markings
for x in np.arange(0, 100, 8):
ax.plot([x, x + 4], [28, 28], color='white', linewidth=2, zorder=3, alpha=0.7)
ax.plot([x, x + 4], [47, 47], color='white', linewidth=2, zorder=3, alpha=0.7)
# Title
title = ax.text(50, 70, 'Head-On Collision Scenario',
color=self.colors['text'], fontsize=18, ha='center',
weight='bold', zorder=30)
title.set_path_effects([path_effects.withStroke(linewidth=3, foreground='black')])
subtitle = ax.text(50, 5, 'Vehicle 1 (Red) crosses center line, Vehicle 2 (Blue) oncoming',
color=self.colors['text'], fontsize=11, ha='center', zorder=30)
subtitle.set_path_effects([path_effects.withStroke(linewidth=2, foreground='black')])
# Speed indicators
ax.text(5, 65, '● Red Vehicle: 90 km/h (drifting)', color='#e74c3c', fontsize=10, weight='bold', zorder=30)
ax.text(5, 60, '● Blue Vehicle: 80 km/h', color='#3498db', fontsize=10, weight='bold', zorder=30)
frames = 80
impact_frame = 45
car1_patches = []
car2_patches = []
impact_patches = []
def draw_car(x, y, angle, color, ax):
"""Draw a car."""
patches = []
scale = 1.2
length = 5 * scale
width = 2.2 * scale
shadow = FancyBboxPatch(
(x - length/2 + 0.3, y - width/2 - 0.4),
length, width,
boxstyle="round,pad=0,rounding_size=0.4",
facecolor='black', alpha=0.3, zorder=4
)
patches.append(shadow)
body = FancyBboxPatch(
(x - length/2, y - width/2),
length, width,
boxstyle="round,pad=0,rounding_size=0.4",
facecolor=color, edgecolor='#1a1a2e', linewidth=2, zorder=10
)
patches.append(body)
cabin = FancyBboxPatch(
(x - length*0.1, y - width/2 + 0.3),
length * 0.35, width - 0.6,
boxstyle="round,pad=0,rounding_size=0.2",
facecolor='#2c3e50', edgecolor='#1a252f', linewidth=1, zorder=11
)
patches.append(cabin)
# Headlights
hl1 = Circle((x + length/2 - 0.4, y + width/3), 0.35,
facecolor='#f5f5f5', edgecolor='#bdc3c7', zorder=12)
hl2 = Circle((x + length/2 - 0.4, y - width/3), 0.35,
facecolor='#f5f5f5', edgecolor='#bdc3c7', zorder=12)
patches.extend([hl1, hl2])
# Taillights
tl1 = Rectangle((x - length/2, y + width/3 - 0.2), 0.4, 0.4,
facecolor='#c0392b', zorder=12)
tl2 = Rectangle((x - length/2, y - width/3 - 0.2), 0.4, 0.4,
facecolor='#c0392b', zorder=12)
patches.extend([tl1, tl2])
# Wheels
wheel_positions = [
(x - length/2 + length*0.2, y + width/2 + 0.1),
(x - length/2 + length*0.2, y - width/2 - 0.1),
(x + length/2 - length*0.2, y + width/2 + 0.1),
(x + length/2 - length*0.2, y - width/2 - 0.1),
]
for wx, wy in wheel_positions:
tire = Circle((wx, wy), 0.6, facecolor='#1a1a2e', zorder=8)
rim = Circle((wx, wy), 0.35, facecolor='#7f8c8d', zorder=9)
patches.extend([tire, rim])
transform = Affine2D().rotate_deg_around(x, y, angle) + ax.transData
for p in patches:
p.set_transform(transform)
ax.add_patch(p)
return patches
def animate(frame):
nonlocal car1_patches, car2_patches, impact_patches
for p in car1_patches + car2_patches + impact_patches:
try:
p.remove()
except:
pass
car1_patches = []
car2_patches = []
impact_patches = []
if frame < impact_frame:
# Car 1 drifting into opposite lane
x1 = 10 + frame * 0.9
y1 = 28 + frame * 0.2 # Drifting up
angle1 = frame * 0.3
# Car 2 coming from right
x2 = 90 - frame * 0.85
y2 = 47
angle2 = 180
else:
post = frame - impact_frame
# Both cars stop and spin
x1 = 10 + impact_frame * 0.9 - post * 0.3
y1 = 28 + impact_frame * 0.2 - post * 0.2
angle1 = impact_frame * 0.3 + post * 4
x2 = 90 - impact_frame * 0.85 + post * 0.3
y2 = 47 + post * 0.2
angle2 = 180 - post * 3
car1_patches = draw_car(x1, y1, angle1, self.colors['car1'], ax)
car2_patches = draw_car(x2, y2, angle2, self.colors['car2'], ax)
# Impact effect
if impact_frame <= frame < impact_frame + 12:
intensity = 1.0 - (frame - impact_frame) * 0.08
impact_x = 50
impact_y = 40
for i in range(5):
radius = (2 + i * 0.8) * intensity
alpha = (0.8 - i * 0.15) * intensity
colors = ['#ffffff', '#f39c12', '#e74c3c', '#f1c40f', '#e67e22']
impact = Circle(
(impact_x, impact_y), radius,
facecolor=colors[i], alpha=alpha, zorder=25 - i
)
ax.add_patch(impact)
impact_patches.append(impact)
return car1_patches + car2_patches
anim = animation.FuncAnimation(fig, animate, frames=frames, interval=60, blit=False)
anim.save(str(output_path), writer='pillow', fps=20)
plt.close()
return str(output_path)
def generate_side_impact_collision(self, output_path=None):
"""Generate professional side impact collision animation."""
if output_path is None:
output_path = OUTPUT_DIR / "side_impact_collision.gif"
fig, ax = plt.subplots(figsize=(self.fig_width, self.fig_height), dpi=self.dpi)
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
ax.set_aspect('equal')
ax.set_facecolor(self.colors['background'])
fig.patch.set_facecolor(self.colors['background'])
ax.axis('off')
# Intersection - draw grass first in corners
# Top-left grass
ax.add_patch(Rectangle((0, 60), 35, 40, facecolor=self.colors['grass'], zorder=0))
# Top-right grass
ax.add_patch(Rectangle((65, 60), 35, 40, facecolor=self.colors['grass'], zorder=0))
# Bottom-left grass
ax.add_patch(Rectangle((0, 0), 35, 40, facecolor=self.colors['grass'], zorder=0))
# Bottom-right grass
ax.add_patch(Rectangle((65, 0), 35, 40, facecolor=self.colors['grass'], zorder=0))
# Horizontal road
self._draw_road_texture(ax, (0, 40, 100, 20), 'horizontal')
# Vertical road
self._draw_road_texture(ax, (35, 0, 30, 100), 'vertical')
# Intersection center
ax.add_patch(Rectangle((35, 40), 30, 20, facecolor=self.colors['road'], zorder=2))
# Crosswalk markings
for y in [40, 58]:
for x in np.arange(37, 63, 3):
ax.add_patch(Rectangle((x, y), 2, 2, facecolor='white', alpha=0.8, zorder=3))
for x in [35, 63]:
for y in np.arange(42, 58, 3):
ax.add_patch(Rectangle((x, y), 2, 2, facecolor='white', alpha=0.8, zorder=3))
# Lane markings on horizontal road
for x in np.arange(0, 35, 6):
ax.plot([x, x + 3], [50, 50], color='white', linewidth=2, zorder=3, alpha=0.7)
for x in np.arange(65, 100, 6):
ax.plot([x, x + 3], [50, 50], color='white', linewidth=2, zorder=3, alpha=0.7)
# Lane markings on vertical road
for y in np.arange(0, 40, 6):
ax.plot([50, 50], [y, y + 3], color='white', linewidth=2, zorder=3, alpha=0.7)
for y in np.arange(60, 100, 6):
ax.plot([50, 50], [y, y + 3], color='white', linewidth=2, zorder=3, alpha=0.7)
# Traffic light poles (simple)
for pos in [(33, 38), (67, 62)]:
ax.add_patch(Circle(pos, 1.5, facecolor='#2c3e50', zorder=5))
ax.add_patch(Circle(pos, 1, facecolor='#e74c3c', zorder=6)) # Red light
for pos in [(33, 62), (67, 38)]:
ax.add_patch(Circle(pos, 1.5, facecolor='#2c3e50', zorder=5))
ax.add_patch(Circle(pos, 1, facecolor='#27ae60', zorder=6)) # Green light
# Title
title = ax.text(50, 95, 'Side Impact Collision Scenario',
color=self.colors['text'], fontsize=18, ha='center',
weight='bold', zorder=30)
title.set_path_effects([path_effects.withStroke(linewidth=3, foreground='black')])
subtitle = ax.text(50, 5, 'Vehicle 1 (Red) runs red light, Vehicle 2 (Blue) T-boned',
color=self.colors['text'], fontsize=11, ha='center', zorder=30)
subtitle.set_path_effects([path_effects.withStroke(linewidth=2, foreground='black')])
ax.text(3, 92, '● Red Vehicle: 70 km/h', color='#e74c3c', fontsize=10, weight='bold', zorder=30)
ax.text(3, 87, '● Blue Vehicle: 50 km/h', color='#3498db', fontsize=10, weight='bold', zorder=30)
frames = 80
impact_frame = 40
car1_patches = []
car2_patches = []
impact_patches = []
def draw_car(x, y, angle, color, ax):
"""Draw a car."""
patches = []
scale = 1.0
length = 5 * scale
width = 2.2 * scale
shadow = FancyBboxPatch(
(x - length/2 + 0.3, y - width/2 - 0.4),
length, width,
boxstyle="round,pad=0,rounding_size=0.4",
facecolor='black', alpha=0.3, zorder=4
)
patches.append(shadow)
body = FancyBboxPatch(
(x - length/2, y - width/2),
length, width,
boxstyle="round,pad=0,rounding_size=0.4",
facecolor=color, edgecolor='#1a1a2e', linewidth=2, zorder=10
)
patches.append(body)
cabin = FancyBboxPatch(
(x - length*0.1, y - width/2 + 0.25),
length * 0.35, width - 0.5,
boxstyle="round,pad=0,rounding_size=0.2",
facecolor='#2c3e50', edgecolor='#1a252f', linewidth=1, zorder=11
)
patches.append(cabin)
# Headlights
hl1 = Circle((x + length/2 - 0.35, y + width/3), 0.3,
facecolor='#f5f5f5', edgecolor='#bdc3c7', zorder=12)
hl2 = Circle((x + length/2 - 0.35, y - width/3), 0.3,
facecolor='#f5f5f5', edgecolor='#bdc3c7', zorder=12)
patches.extend([hl1, hl2])
# Taillights
tl1 = Rectangle((x - length/2, y + width/3 - 0.15), 0.35, 0.3,
facecolor='#c0392b', zorder=12)
tl2 = Rectangle((x - length/2, y - width/3 - 0.15), 0.35, 0.3,
facecolor='#c0392b', zorder=12)
patches.extend([tl1, tl2])
# Wheels
wheel_positions = [
(x - length/2 + length*0.2, y + width/2 + 0.1),
(x - length/2 + length*0.2, y - width/2 - 0.1),
(x + length/2 - length*0.2, y + width/2 + 0.1),
(x + length/2 - length*0.2, y - width/2 - 0.1),
]
for wx, wy in wheel_positions:
tire = Circle((wx, wy), 0.5, facecolor='#1a1a2e', zorder=8)
rim = Circle((wx, wy), 0.3, facecolor='#7f8c8d', zorder=9)
patches.extend([tire, rim])
transform = Affine2D().rotate_deg_around(x, y, angle) + ax.transData
for p in patches:
p.set_transform(transform)
ax.add_patch(p)
return patches
def animate(frame):
nonlocal car1_patches, car2_patches, impact_patches
for p in car1_patches + car2_patches + impact_patches:
try:
p.remove()
except:
pass
car1_patches = []
car2_patches = []
impact_patches = []
if frame < impact_frame:
# Car 1 moving right (horizontal)
x1 = 10 + frame * 1.1
y1 = 45
angle1 = 0
# Car 2 moving down (vertical)
x2 = 55
y2 = 85 - frame * 1.0
angle2 = -90
else:
post = frame - impact_frame
# Both cars pushed to the right
x1 = 10 + impact_frame * 1.1 + post * 0.3
y1 = 45 + post * 0.4
angle1 = post * 3
x2 = 55 + post * 0.5
y2 = 85 - impact_frame * 1.0 - post * 0.2
angle2 = -90 + post * 5
car1_patches = draw_car(x1, y1, angle1, self.colors['car1'], ax)
car2_patches = draw_car(x2, y2, angle2, self.colors['car2'], ax)
# Impact effect
if impact_frame <= frame < impact_frame + 12:
intensity = 1.0 - (frame - impact_frame) * 0.08
impact_x = 52
impact_y = 47
for i in range(5):
radius = (1.5 + i * 0.6) * intensity
alpha = (0.8 - i * 0.15) * intensity
colors = ['#ffffff', '#f39c12', '#e74c3c', '#f1c40f', '#e67e22']
impact = Circle(
(impact_x, impact_y), radius,
facecolor=colors[i], alpha=alpha, zorder=25 - i
)
ax.add_patch(impact)
impact_patches.append(impact)
return car1_patches + car2_patches
anim = animation.FuncAnimation(fig, animate, frames=frames, interval=60, blit=False)
anim.save(str(output_path), writer='pillow', fps=20)
plt.close()
return str(output_path)
def generate_all_scenarios(self):
"""Generate all professional scenario animations."""
print("🎬 Generating professional accident scenario animations...")
videos = {}
print(" ➀ Generating rear-end collision...")
videos['rear_end_collision'] = self.generate_rear_end_collision()
print(f" βœ“ Saved: {videos['rear_end_collision']}")
print(" ➀ Generating head-on collision...")
videos['head_on_collision'] = self.generate_head_on_collision()
print(f" βœ“ Saved: {videos['head_on_collision']}")
print(" ➀ Generating side impact collision...")
videos['side_impact'] = self.generate_side_impact_collision()
print(f" βœ“ Saved: {videos['side_impact']}")
# Save manifest
manifest_path = OUTPUT_DIR / "video_manifest.json"
with open(manifest_path, 'w') as f:
json.dump(videos, f, indent=2)
print(f"\nβœ… All professional animations generated!")
print(f"πŸ“ Location: {OUTPUT_DIR}")
return videos
if __name__ == "__main__":
generator = ProfessionalAccidentVideoGenerator()
generator.generate_all_scenarios()