-
-
Save DanielHabib/707a7b2f4b9f6b80fa383572dab6417b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from spatialstudio import splv | |
| import math | |
| import time | |
| import random | |
| def create_physics_showcase(): | |
| """ | |
| Create a clean physics showcase with bouncing balls cascading down | |
| multiple angled planes - demonstrating realistic gravity, collisions, and energy transfer! | |
| """ | |
| # Video parameters | |
| W, H, D = 400, 256, 64 | |
| framerate = 60.0 # Higher framerate for smoother physics | |
| total_duration = 25.0 # 25 seconds of physics demonstration | |
| total_frames = int(total_duration * framerate) | |
| encoder = splv.Encoder( | |
| W, H, D, | |
| framerate=framerate, | |
| outputPath="physics_showcase.splv" | |
| ) | |
| print(f"Creating Enhanced Physics Showcase: {total_frames} frames at {framerate} FPS") | |
| # Initialize the physics world | |
| balls = initialize_balls(W, H, D) | |
| planes = create_collision_planes(W, H, D) | |
| particle_effects = [] # For collision sparks and effects | |
| for frame_num in range(total_frames): | |
| frame = splv.Frame(W, H, D) | |
| # Calculate time-based variables | |
| t = frame_num / framerate | |
| # Skip background gradient for cleaner look | |
| # add_background_gradient(frame, t, W, H, D) | |
| # Draw the collision planes with simple, bold colors | |
| draw_simple_planes(frame, planes, W, H, D) | |
| # Update ball physics | |
| update_ball_physics(balls, planes, W, H, D, particle_effects) | |
| # Update and draw particle effects | |
| update_particle_effects(particle_effects) | |
| draw_particle_effects(frame, particle_effects) | |
| # Draw balls with enhanced motion trails | |
| draw_enhanced_balls_with_trails(frame, balls, t) | |
| # Spawn new balls more frequently with variety | |
| spawn_frequency = 45 if frame_num < total_frames * 0.3 else 60 # More balls at start | |
| if frame_num % spawn_frequency == 0 and frame_num < total_frames * 0.85: | |
| spawn_new_ball(balls, W, H, D, frame_num) | |
| # Occasionally spawn multiple balls at once for excitement | |
| if frame_num % 300 == 150: # Every 5 seconds, offset timing | |
| for _ in range(3): | |
| spawn_new_ball(balls, W, H, D, frame_num) | |
| encoder.encode(frame) | |
| if frame_num % 180 == 0: | |
| progress = frame_num / total_frames | |
| print(f"Progress: {frame_num}/{total_frames} ({progress*100:.1f}%)") | |
| encoder.finish() | |
| print("Physics showcase completed!") | |
| def initialize_balls(W, H, D): | |
| """Initialize the physics balls with realistic properties""" | |
| balls = [] | |
| # Simple, clean primary colors that will show up clearly | |
| colors = [ | |
| (255, 0, 0), # Pure Red | |
| (0, 255, 0), # Pure Green | |
| (0, 0, 255), # Pure Blue | |
| (255, 255, 0), # Yellow | |
| (255, 0, 255), # Magenta | |
| (0, 255, 255), # Cyan | |
| (255, 128, 0), # Orange | |
| (128, 0, 255), # Purple | |
| (255, 192, 203), # Pink | |
| (0, 128, 255), # Sky Blue | |
| ] | |
| # Start with more balls at different positions | |
| for i in range(5): | |
| ball = { | |
| 'x': W * 0.15 + i * W * 0.15 + random.uniform(-20, 20), | |
| 'y': H - 10 - i * 25 + random.uniform(-10, 10), | |
| 'z': D * 0.2 + i * D * 0.15 + random.uniform(-5, 5), | |
| 'vx': random.uniform(-2, 2), | |
| 'vy': random.uniform(-1, 1), | |
| 'vz': random.uniform(-1, 1), | |
| 'radius': random.uniform(2.5, 7), | |
| 'color': colors[i % len(colors)], | |
| 'bounce_damping': random.uniform(0.65, 0.9), | |
| 'trail': [], # Store recent positions for trail effect | |
| 'active': True, | |
| 'glow_intensity': random.uniform(0.8, 1.2), | |
| 'spin': random.uniform(-0.1, 0.1) # For visual rotation effect | |
| } | |
| balls.append(ball) | |
| return balls | |
| def create_collision_planes(W, H, D): | |
| """Create a series of stepped collision planes""" | |
| planes = [] | |
| # Create 4 stepped planes with bold, simple colors | |
| plane_colors = [ | |
| (200, 200, 200), # Light Gray | |
| (160, 160, 160), # Medium Gray | |
| (120, 120, 120), # Dark Gray | |
| (80, 80, 80), # Darker Gray | |
| ] | |
| for i in range(4): | |
| plane = { | |
| 'y': H - 40 - i * 45, # Stepping down | |
| 'x_start': 50 + i * 60, | |
| 'x_end': 200 + i * 60, | |
| 'z_start': 10, | |
| 'z_end': D - 10, | |
| 'angle': 0, # Flat planes for now | |
| 'color': plane_colors[i] | |
| } | |
| planes.append(plane) | |
| # Add one angled ramp at the end with a bold blue color | |
| ramp = { | |
| 'y': 30, | |
| 'x_start': 280, | |
| 'x_end': 350, | |
| 'z_start': 15, | |
| 'z_end': D - 15, | |
| 'angle': -15, # Sloped downward | |
| 'color': (0, 100, 200) # Bold Blue | |
| } | |
| planes.append(ramp) | |
| return planes | |
| def add_background_gradient(frame, t, W, H, D): | |
| """Add a subtle animated background gradient""" | |
| for y in range(0, H, 8): # Sample every 8th row for performance | |
| for x in range(0, W, 12): # Sample every 12th column | |
| for z in range(0, D, 6): # Sample every 6th depth | |
| # Create a subtle moving gradient | |
| gradient_factor = (y / H) * 0.3 + math.sin(t * 0.5 + x * 0.01) * 0.1 | |
| color_intensity = max(0, int(gradient_factor * 40)) | |
| bg_color = ( | |
| max(0, min(255, color_intensity)), | |
| max(0, min(255, color_intensity // 2)), | |
| max(0, min(255, color_intensity + 10)) | |
| ) | |
| if 0 <= x < W and 0 <= y < H and 0 <= z < D: | |
| frame[x, y, z] = bg_color | |
| def draw_simple_planes(frame, planes, W, H, D): | |
| """Draw collision planes with simple, bold colors""" | |
| for plane in planes: | |
| # Draw main plane surface with solid color | |
| for x in range(int(plane['x_start']), int(plane['x_end'])): | |
| for z in range(int(plane['z_start']), int(plane['z_end'])): | |
| if 0 <= x < W and 0 <= z < D: | |
| y = int(plane['y']) | |
| if 0 <= y < H: | |
| frame[x, y, z] = plane['color'] | |
| # Add simple thickness | |
| if y - 1 >= 0: | |
| darker_color = tuple(max(0, c - 40) for c in plane['color']) | |
| frame[x, y - 1, z] = darker_color | |
| def draw_enhanced_planes(frame, planes, W, H, D, t): | |
| """Draw collision planes with enhanced visuals and animation""" | |
| for i, plane in enumerate(planes): | |
| # Add subtle animation to plane colors | |
| color_pulse = math.sin(t * 2 + i * 0.5) * 0.2 + 0.8 | |
| animated_color = tuple(int(c * color_pulse) for c in plane['color']) | |
| # Draw main plane surface | |
| for x in range(int(plane['x_start']), int(plane['x_end'])): | |
| for z in range(int(plane['z_start']), int(plane['z_end'])): | |
| if 0 <= x < W and 0 <= z < D: | |
| y = int(plane['y']) | |
| if 0 <= y < H: | |
| # Add some texture variation | |
| texture_variation = int(math.sin(x * 0.3) * math.cos(z * 0.3) * 20) | |
| textured_color = tuple(max(0, min(255, c + texture_variation)) for c in animated_color) | |
| frame[x, y, z] = textured_color | |
| # Add thickness with shading | |
| for thickness in range(1, 3): | |
| if y - thickness >= 0: | |
| shade_factor = 1.0 - thickness * 0.3 | |
| shaded_color = tuple(int(c * shade_factor) for c in textured_color) | |
| frame[x, y - thickness, z] = shaded_color | |
| def draw_planes(frame, planes, W, H, D): | |
| """Draw all collision planes""" | |
| for plane in planes: | |
| # Draw flat rectangular planes | |
| for x in range(int(plane['x_start']), int(plane['x_end'])): | |
| for z in range(int(plane['z_start']), int(plane['z_end'])): | |
| if 0 <= x < W and 0 <= z < D: | |
| y = int(plane['y']) | |
| if 0 <= y < H: | |
| frame[x, y, z] = plane['color'] | |
| # Add some thickness | |
| if y > 0: | |
| frame[x, y-1, z] = tuple(c // 2 for c in plane['color']) | |
| def update_ball_physics(balls, planes, W, H, D, particle_effects): | |
| """Update physics for all balls""" | |
| gravity = -0.3 | |
| air_resistance = 0.99 | |
| for ball in balls: | |
| if not ball['active']: | |
| continue | |
| # Apply gravity | |
| ball['vy'] += gravity | |
| # Apply air resistance | |
| ball['vx'] *= air_resistance | |
| ball['vz'] *= air_resistance | |
| # Update position | |
| ball['x'] += ball['vx'] | |
| ball['y'] += ball['vy'] | |
| ball['z'] += ball['vz'] | |
| # Store trail positions | |
| ball['trail'].append((ball['x'], ball['y'], ball['z'])) | |
| if len(ball['trail']) > 15: # Keep last 15 positions | |
| ball['trail'].pop(0) | |
| # Check collision with planes | |
| check_plane_collisions(ball, planes, particle_effects) | |
| # Boundary collisions | |
| if ball['x'] <= ball['radius'] or ball['x'] >= W - ball['radius']: | |
| ball['vx'] *= -ball['bounce_damping'] | |
| ball['x'] = max(ball['radius'], min(W - ball['radius'], ball['x'])) | |
| if ball['z'] <= ball['radius'] or ball['z'] >= D - ball['radius']: | |
| ball['vz'] *= -ball['bounce_damping'] | |
| ball['z'] = max(ball['radius'], min(D - ball['radius'], ball['z'])) | |
| # Remove balls that fall too far | |
| if ball['y'] < -50: | |
| ball['active'] = False | |
| def check_plane_collisions(ball, planes, particle_effects): | |
| """Check and handle collisions between ball and planes""" | |
| for plane in planes: | |
| # Check if ball is within plane bounds | |
| if (plane['x_start'] <= ball['x'] <= plane['x_end'] and | |
| plane['z_start'] <= ball['z'] <= plane['z_end']): | |
| plane_y = plane['y'] | |
| # Check if ball is colliding with plane from above | |
| if (ball['y'] - ball['radius'] <= plane_y and | |
| ball['y'] - ball['radius'] >= plane_y - 5 and | |
| ball['vy'] < 0): # Moving downward | |
| # Create collision particles | |
| impact_strength = abs(ball['vy']) | |
| for _ in range(int(impact_strength * 3)): | |
| particle = { | |
| 'x': ball['x'] + random.uniform(-ball['radius'], ball['radius']), | |
| 'y': plane_y + 1, | |
| 'z': ball['z'] + random.uniform(-ball['radius'], ball['radius']), | |
| 'vx': random.uniform(-impact_strength, impact_strength), | |
| 'vy': random.uniform(0, impact_strength * 2), | |
| 'vz': random.uniform(-impact_strength * 0.5, impact_strength * 0.5), | |
| 'color': ball['color'], | |
| 'life': 30, # Frames to live | |
| 'max_life': 30 | |
| } | |
| particle_effects.append(particle) | |
| # Bounce off the plane | |
| ball['vy'] *= -ball['bounce_damping'] | |
| ball['y'] = plane_y + ball['radius'] + 1 | |
| # Add some horizontal velocity based on impact | |
| impact_factor = abs(ball['vy']) * 0.15 | |
| ball['vx'] += random.uniform(-impact_factor, impact_factor) | |
| ball['vz'] += random.uniform(-impact_factor * 0.5, impact_factor * 0.5) | |
| break # Only collide with one plane per frame | |
| def draw_balls_with_trails(frame, balls, t): | |
| """Draw balls with motion trails""" | |
| for ball in balls: | |
| if not ball['active']: | |
| continue | |
| # Draw trail | |
| for i, (tx, ty, tz) in enumerate(ball['trail']): | |
| if i < len(ball['trail']) - 1: # Don't draw trail at current position | |
| trail_alpha = i / len(ball['trail']) | |
| trail_color = tuple(int(c * trail_alpha * 0.5) for c in ball['color']) | |
| # Draw trail point | |
| tx, ty, tz = int(tx), int(ty), int(tz) | |
| if 0 <= tx < frame.get_dims()[0] and 0 <= ty < frame.get_dims()[1] and 0 <= tz < frame.get_dims()[2]: | |
| frame[tx, ty, tz] = trail_color | |
| # Draw main ball | |
| draw_sphere(frame, ball['x'], ball['y'], ball['z'], ball['radius'], ball['color']) | |
| def update_particle_effects(particle_effects): | |
| """Update all particle effects""" | |
| for particle in particle_effects[:]: | |
| # Apply gravity to particles | |
| particle['vy'] -= 0.1 | |
| # Update position | |
| particle['x'] += particle['vx'] | |
| particle['y'] += particle['vy'] | |
| particle['z'] += particle['vz'] | |
| # Apply air resistance | |
| particle['vx'] *= 0.98 | |
| particle['vz'] *= 0.98 | |
| # Decrease life | |
| particle['life'] -= 1 | |
| # Remove dead particles | |
| if particle['life'] <= 0: | |
| particle_effects.remove(particle) | |
| def draw_particle_effects(frame, particle_effects): | |
| """Draw all particle effects""" | |
| W, H, D = frame.get_dims() | |
| for particle in particle_effects: | |
| x, y, z = int(particle['x']), int(particle['y']), int(particle['z']) | |
| if 0 <= x < W and 0 <= y < H and 0 <= z < D: | |
| # Fade particle based on remaining life | |
| life_factor = particle['life'] / particle['max_life'] | |
| faded_color = tuple(max(0, min(255, int(c * life_factor))) for c in particle['color']) | |
| frame[x, y, z] = faded_color | |
| def draw_enhanced_balls_with_trails(frame, balls, t): | |
| """Draw balls with enhanced motion trails and glow effects""" | |
| for ball in balls: | |
| if not ball['active']: | |
| continue | |
| # Draw enhanced trail with varying thickness and fun colors | |
| for i, (tx, ty, tz) in enumerate(ball['trail']): | |
| if i < len(ball['trail']) - 1: # Don't draw trail at current position | |
| trail_alpha = (i / len(ball['trail'])) ** 0.7 # Non-linear fade | |
| trail_intensity = trail_alpha * ball['glow_intensity'] | |
| # Create complementary trail colors that shift through the spectrum | |
| trail_shift = (i / len(ball['trail'])) * 0.5 # Color shift factor | |
| r, g, b = ball['color'] | |
| # Create rainbow-like trail effect | |
| trail_r = int((r * (1 - trail_shift) + g * trail_shift) * trail_intensity * 0.8) | |
| trail_g = int((g * (1 - trail_shift) + b * trail_shift) * trail_intensity * 0.8) | |
| trail_b = int((b * (1 - trail_shift) + r * trail_shift) * trail_intensity * 0.8) | |
| trail_color = (trail_r, trail_g, trail_b) | |
| # Draw trail with slight thickness | |
| tx, ty, tz = int(tx), int(ty), int(tz) | |
| W, H, D = frame.get_dims() | |
| for dx in range(-1, 2): | |
| for dy in range(-1, 2): | |
| for dz in range(-1, 2): | |
| nx, ny, nz = tx + dx, ty + dy, tz + dz | |
| if (0 <= nx < W and 0 <= ny < H and 0 <= nz < D and | |
| abs(dx) + abs(dy) + abs(dz) <= 2): # Diamond shape | |
| brightness = 1.0 - (abs(dx) + abs(dy) + abs(dz)) * 0.3 | |
| final_color = tuple(max(0, min(255, int(c * brightness))) for c in trail_color) | |
| frame[nx, ny, nz] = final_color | |
| # Draw main ball with simple, clear colors | |
| draw_simple_sphere(frame, ball['x'], ball['y'], ball['z'], ball['radius'], ball['color']) | |
| def draw_simple_sphere(frame, cx, cy, cz, radius, color): | |
| """Draw a simple, bright sphere that clearly shows the color""" | |
| W, H, D = frame.get_dims() | |
| for x in range(max(0, int(cx - radius)), min(W, int(cx + radius + 1))): | |
| for y in range(max(0, int(cy - radius)), min(H, int(cy + radius + 1))): | |
| for z in range(max(0, int(cz - radius)), min(D, int(cz + radius + 1))): | |
| dx = x - cx | |
| dy = y - cy | |
| dz = z - cz | |
| distance = math.sqrt(dx*dx + dy*dy + dz*dz) | |
| if distance <= radius: | |
| # Simple brightness based on distance from center | |
| brightness = 1.0 - (distance / radius) * 0.3 # Keep most of the color | |
| bright_color = tuple(max(50, int(c * brightness)) for c in color) # Minimum brightness of 50 | |
| frame[x, y, z] = bright_color | |
| def draw_enhanced_sphere(frame, cx, cy, cz, radius, color, glow_intensity, rotation): | |
| """Draw an enhanced sphere with glow effects""" | |
| W, H, D = frame.get_dims() | |
| # Draw glow effect first (larger radius, dimmer) | |
| glow_radius = radius + 2 | |
| for x in range(max(0, int(cx - glow_radius)), min(W, int(cx + glow_radius + 1))): | |
| for y in range(max(0, int(cy - glow_radius)), min(H, int(cy + glow_radius + 1))): | |
| for z in range(max(0, int(cz - glow_radius)), min(D, int(cz + glow_radius + 1))): | |
| dx = x - cx | |
| dy = y - cy | |
| dz = z - cz | |
| distance = math.sqrt(dx*dx + dy*dy + dz*dz) | |
| if radius < distance <= glow_radius: | |
| glow_factor = 1.0 - ((distance - radius) / (glow_radius - radius)) | |
| glow_factor *= glow_intensity * 0.3 | |
| glow_color = tuple(max(0, min(255, int(c * glow_factor))) for c in color) | |
| frame[x, y, z] = glow_color | |
| # Draw main sphere | |
| for x in range(max(0, int(cx - radius)), min(W, int(cx + radius + 1))): | |
| for y in range(max(0, int(cy - radius)), min(H, int(cy + radius + 1))): | |
| for z in range(max(0, int(cz - radius)), min(D, int(cz + radius + 1))): | |
| dx = x - cx | |
| dy = y - cy | |
| dz = z - cz | |
| distance = math.sqrt(dx*dx + dy*dy + dz*dz) | |
| if distance <= radius: | |
| # Enhanced shading with rotation effect | |
| brightness = 1.0 - (distance / radius) * 0.4 | |
| rotation_effect = math.sin(rotation + dx * 0.5) * 0.1 + 1.0 | |
| final_brightness = brightness * rotation_effect * glow_intensity | |
| shaded_color = tuple(max(0, min(255, int(c * final_brightness))) for c in color) | |
| frame[x, y, z] = shaded_color | |
| def draw_sphere(frame, cx, cy, cz, radius, color): | |
| """Draw a sphere at the given position""" | |
| W, H, D = frame.get_dims() | |
| for x in range(max(0, int(cx - radius)), min(W, int(cx + radius + 1))): | |
| for y in range(max(0, int(cy - radius)), min(H, int(cy + radius + 1))): | |
| for z in range(max(0, int(cz - radius)), min(D, int(cz + radius + 1))): | |
| # Calculate distance from center | |
| dx = x - cx | |
| dy = y - cy | |
| dz = z - cz | |
| distance = math.sqrt(dx*dx + dy*dy + dz*dz) | |
| if distance <= radius: | |
| # Add some shading based on distance from center | |
| brightness = 1.0 - (distance / radius) * 0.3 | |
| shaded_color = tuple(int(c * brightness) for c in color) | |
| frame[x, y, z] = shaded_color | |
| def spawn_new_ball(balls, W, H, D, frame_num=0): | |
| """Spawn a new ball at a random position with enhanced variety""" | |
| colors = [ | |
| (255, 0, 0), # Pure Red | |
| (0, 255, 0), # Pure Green | |
| (0, 0, 255), # Pure Blue | |
| (255, 255, 0), # Yellow | |
| (255, 0, 255), # Magenta | |
| (0, 255, 255), # Cyan | |
| (255, 128, 0), # Orange | |
| (128, 0, 255), # Purple | |
| (255, 192, 203), # Pink | |
| (0, 128, 255), # Sky Blue | |
| (128, 255, 0), # Lime | |
| (255, 64, 0), # Red-Orange | |
| ] | |
| # Vary spawn positions based on time for more interesting patterns | |
| spawn_variation = math.sin(frame_num * 0.02) * W * 0.2 | |
| new_ball = { | |
| 'x': W * 0.5 + spawn_variation + random.uniform(-W * 0.3, W * 0.3), | |
| 'y': H - random.uniform(5, 15), | |
| 'z': D * 0.5 + random.uniform(-D * 0.3, D * 0.3), | |
| 'vx': random.uniform(-3, 3), | |
| 'vy': random.uniform(-2, 2), | |
| 'vz': random.uniform(-2, 2), | |
| 'radius': random.uniform(2, 8), # Larger size variety | |
| 'color': random.choice(colors), | |
| 'bounce_damping': random.uniform(0.5, 0.95), # More bounce variety | |
| 'trail': [], | |
| 'active': True, | |
| 'glow_intensity': random.uniform(0.8, 1.5), | |
| 'spin': random.uniform(-0.2, 0.2) | |
| } | |
| balls.append(new_ball) | |
| if __name__ == "__main__": | |
| start_time = time.time() | |
| create_physics_showcase() | |
| end_time = time.time() | |
| print(f"Total generation time: {end_time - start_time:.2f} seconds") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment