| | """ |
| | 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 |
| |
|
| | |
| | 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 |
| | self.color = color |
| | self.label = label |
| | self.scale = scale |
| | self.patches = [] |
| | self.draw() |
| | |
| | def draw(self): |
| | """Draw the car with all its components.""" |
| | |
| | for p in self.patches: |
| | p.remove() |
| | self.patches = [] |
| | |
| | |
| | length = 4.5 * self.scale |
| | width = 2.0 * self.scale |
| | |
| | |
| | |
| | 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_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 |
| | ) |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | 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 = Circle( |
| | (wx, wy), wheel_size, |
| | facecolor='#2c3e50', |
| | edgecolor='#1a252f', |
| | linewidth=1.5, |
| | zorder=9 |
| | ) |
| | |
| | hubcap = Circle( |
| | (wx, wy), wheel_size * 0.6, |
| | facecolor='#7f8c8d', |
| | edgecolor='#95a5a6', |
| | linewidth=1, |
| | zorder=9 |
| | ) |
| | wheels.extend([tire, hubcap]) |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | all_patches = [shadow, body, windshield, highlight, |
| | headlight1, headlight2, taillight1, taillight2] + wheels |
| | |
| | |
| | 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 |
| | |
| | |
| | self.colors = { |
| | 'background': '#1a1a2e', |
| | 'road': '#4a4a5a', |
| | 'road_edge': '#6a6a7a', |
| | 'lane_marking': '#ffffff', |
| | 'center_line': '#f1c40f', |
| | 'grass': '#2d5a27', |
| | 'sidewalk': '#95a5a6', |
| | 'car1': '#e74c3c', |
| | 'car2': '#3498db', |
| | '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 = 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) |
| | |
| | |
| | if orientation == 'horizontal': |
| | |
| | ax.plot([x, x + w], [y + h, y + h], color='#ffffff', linewidth=2, zorder=2) |
| | |
| | ax.plot([x, x + w], [y, y], color='#ffffff', linewidth=2, zorder=2) |
| | else: |
| | |
| | ax.plot([x, x], [y, y + h], color='#ffffff', linewidth=2, zorder=2) |
| | |
| | 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: |
| | |
| | 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) |
| | |
| | |
| | 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.""" |
| | |
| | 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) |
| | |
| | |
| | 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') |
| | |
| | |
| | |
| | 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) |
| | |
| | |
| | self._draw_road_texture(ax, (0, 20, 100, 35), 'horizontal') |
| | |
| | |
| | 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) |
| | |
| | |
| | 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 = 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 = 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')]) |
| | |
| | |
| | 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) |
| | |
| | |
| | frames = 80 |
| | impact_frame = 50 |
| | |
| | |
| | car1_data = {'x': 10, 'y': 32, 'angle': 0} |
| | car2_data = {'x': 55, 'y': 32, 'angle': 0} |
| | |
| | |
| | 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 = 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) |
| | |
| | |
| | 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) |
| | |
| | |
| | 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]) |
| | |
| | |
| | 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]) |
| | |
| | |
| | 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: |
| | |
| | x1 = 10 + frame * 1.1 |
| | |
| | x2 = 55 + frame * 0.25 |
| | angle1 = 0 |
| | angle2 = 0 |
| | else: |
| | |
| | post = frame - impact_frame |
| | |
| | x1 = 10 + impact_frame * 1.1 + post * 0.1 |
| | |
| | x2 = 55 + impact_frame * 0.25 + post * 0.8 |
| | |
| | angle1 = -post * 0.5 |
| | angle2 = post * 0.8 |
| | |
| | |
| | car1_patches = draw_car(x1, 32, angle1, self.colors['car1'], ax) |
| | car2_patches = draw_car(x2, 32, angle2, self.colors['car2'], ax) |
| | |
| | |
| | if impact_frame <= frame < impact_frame + 10: |
| | intensity = 1.0 - (frame - impact_frame) * 0.1 |
| | impact_x = 10 + impact_frame * 1.1 + 3 |
| | |
| | |
| | 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) |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | 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_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') |
| | |
| | |
| | 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) |
| | |
| | |
| | self._draw_road_texture(ax, (0, 20, 100, 35), 'horizontal') |
| | |
| | |
| | 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) |
| | |
| | |
| | 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 = 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')]) |
| | |
| | |
| | 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) |
| | |
| | |
| | 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]) |
| | |
| | |
| | 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]) |
| | |
| | |
| | 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: |
| | |
| | x1 = 10 + frame * 0.9 |
| | y1 = 28 + frame * 0.2 |
| | angle1 = frame * 0.3 |
| | |
| | |
| | x2 = 90 - frame * 0.85 |
| | y2 = 47 |
| | angle2 = 180 |
| | else: |
| | post = frame - impact_frame |
| | |
| | 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) |
| | |
| | |
| | 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') |
| | |
| | |
| | |
| | ax.add_patch(Rectangle((0, 60), 35, 40, facecolor=self.colors['grass'], zorder=0)) |
| | |
| | ax.add_patch(Rectangle((65, 60), 35, 40, facecolor=self.colors['grass'], zorder=0)) |
| | |
| | ax.add_patch(Rectangle((0, 0), 35, 40, facecolor=self.colors['grass'], zorder=0)) |
| | |
| | ax.add_patch(Rectangle((65, 0), 35, 40, facecolor=self.colors['grass'], zorder=0)) |
| | |
| | |
| | self._draw_road_texture(ax, (0, 40, 100, 20), 'horizontal') |
| | |
| | |
| | self._draw_road_texture(ax, (35, 0, 30, 100), 'vertical') |
| | |
| | |
| | ax.add_patch(Rectangle((35, 40), 30, 20, facecolor=self.colors['road'], zorder=2)) |
| | |
| | |
| | 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)) |
| | |
| | |
| | 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) |
| | |
| | |
| | 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) |
| | |
| | |
| | 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)) |
| | 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)) |
| | |
| | |
| | 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) |
| | |
| | |
| | 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]) |
| | |
| | |
| | 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]) |
| | |
| | |
| | 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: |
| | |
| | x1 = 10 + frame * 1.1 |
| | y1 = 45 |
| | angle1 = 0 |
| | |
| | |
| | x2 = 55 |
| | y2 = 85 - frame * 1.0 |
| | angle2 = -90 |
| | else: |
| | post = frame - impact_frame |
| | |
| | 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) |
| | |
| | |
| | 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']}") |
| | |
| | |
| | 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() |
| |
|