2026-02-05 23:01:36 -05:00

1171 lines
43 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
SURYA - Epic Manim Animation
Das's debut album visualized in stunning 3D mathematics
NOTE: For LaTeX equations, install MacTeX: brew install --cask mactex
This version uses Text fallbacks for systems without LaTeX.
"""
from manim import *
import numpy as np
import random
# Try to use LaTeX, fall back to Text
try:
test = MathTex(r"\phi")
USE_LATEX = True
except:
USE_LATEX = False
# ============================================================================
# COLOR PALETTE (matching the website)
# ============================================================================
COLORS = {
"intro": "#1a0a2e", # Deep purple
"skin": "#f472b6", # Pink
"u_saved_me": "#22d3ee", # Cyan
"nothing": "#666666", # Grey
"sweet_relief": "#a78bfa", # Purple
"natures_call": "#5eead4", # Teal
"dreamcatcher": "#c4b5fd", # Soft purple
"idk": "#f472b6", # Dark pink
"with_u": "#fbbf24", # GOLD - THE TURN!
"poor_you": "#fb923c", # Orange
"wait_4_u": "#818cf8", # Indigo
"run_to_u": "#22d3ee", # Cyan
"medications": "#ef4444", # Red
"hollow": "#fbbf24", # Gold
}
DURATION = 25 # seconds per track
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
def create_title(text, color, corner=UL):
"""Create corner-positioned title that won't cover graphics."""
title = Text(text, font_size=28, color=color)
title.to_corner(corner, buff=0.4)
return title
def create_subtitle(text, color):
"""Create bottom-edge subtitle for lyrics/quotes."""
sub = Text(text, font_size=18, color=color, opacity=0.7)
sub.to_edge(DOWN, buff=0.2)
return sub
def create_equation(latex_str, text_str, font_size=24, color=WHITE):
"""Create equation - uses LaTeX if available, Text fallback otherwise."""
try:
return MathTex(latex_str, font_size=font_size, color=color)
except:
return Text(text_str, font_size=font_size-4, color=color)
def smooth_text_animation(scene, text, hold_time=2):
"""Fade in text, hold, fade out - smooth and subtle."""
scene.play(FadeIn(text, shift=UP*0.1), run_time=0.5)
scene.wait(hold_time)
scene.play(FadeOut(text, shift=UP*0.1), run_time=0.5)
# ============================================================================
# TRACK 1: SKIN (INTRO) - 3D Morphing Parametric Surface
# ============================================================================
class Track01Skin(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)
self.camera.background_color = COLORS["intro"]
# Title in corner
title = create_title("01 · SKIN (INTRO)", COLORS["skin"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"S(u,v) = (\sin u \cos v, \sin u \sin v, \cos u + \sin 3v)",
"S(u,v) = parametric surface",
font_size=20, color=COLORS["skin"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=1)
self.wait(1)
self.play(FadeOut(equation), run_time=0.5)
# Create morphing skin-like surface
def skin_surface(u, v, t=0):
x = np.sin(u) * np.cos(v) * (1 + 0.2*np.sin(3*u + t))
y = np.sin(u) * np.sin(v) * (1 + 0.2*np.cos(3*v + t))
z = np.cos(u) + 0.3*np.sin(3*v + 2*u + t)
return np.array([x, y, z])
surface = Surface(
lambda u, v: skin_surface(u, v, 0),
u_range=[0, PI],
v_range=[0, TAU],
resolution=(40, 40),
fill_opacity=0.8,
)
surface.set_color_by_gradient(COLORS["skin"], COLORS["intro"], COLORS["skin"])
self.begin_ambient_camera_rotation(rate=0.15)
self.play(Create(surface), run_time=3)
# Morph the surface over time
for t in np.linspace(0, 4*PI, 40):
new_surface = Surface(
lambda u, v, t=t: skin_surface(u, v, t),
u_range=[0, PI],
v_range=[0, TAU],
resolution=(40, 40),
fill_opacity=0.8,
)
new_surface.set_color_by_gradient(COLORS["skin"], COLORS["intro"], COLORS["skin"])
self.play(Transform(surface, new_surface), run_time=0.5)
self.play(FadeOut(title), FadeOut(surface), run_time=1)
# ============================================================================
# TRACK 2: U SAVED ME - Particles Coalescing into Sacred Geometry
# ============================================================================
class Track02USavedMe(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75*DEGREES, theta=-30*DEGREES)
self.camera.background_color = "#0a1628"
title = create_title("02 · U SAVED ME", COLORS["u_saved_me"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"\text{Icosahedron: } \phi = \frac{1+\sqrt{5}}{2}",
"φ = (1 + √5) / 2 ≈ 1.618",
font_size=20, color=COLORS["u_saved_me"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create scattered particles
particles = VGroup()
target_positions = []
# Generate sacred geometry target points (icosahedron vertices + midpoints)
phi = (1 + np.sqrt(5)) / 2 # Golden ratio
icosa_verts = [
[0, 1, phi], [0, -1, phi], [0, 1, -phi], [0, -1, -phi],
[1, phi, 0], [-1, phi, 0], [1, -phi, 0], [-1, -phi, 0],
[phi, 0, 1], [-phi, 0, 1], [phi, 0, -1], [-phi, 0, -1]
]
for v in icosa_verts:
norm = np.linalg.norm(v)
target_positions.append(np.array(v) / norm * 2.5)
# Add midpoints for denser geometry
for i, v1 in enumerate(icosa_verts):
for v2 in icosa_verts[i+1:]:
mid = (np.array(v1) + np.array(v2)) / 2
if np.linalg.norm(mid) > 0.5:
norm = np.linalg.norm(mid)
target_positions.append(mid / norm * 2.5)
# Create particles at random positions
for i, target in enumerate(target_positions[:60]):
start_pos = np.array([
random.uniform(-6, 6),
random.uniform(-6, 6),
random.uniform(-6, 6)
])
particle = Dot3D(point=start_pos, radius=0.08, color=COLORS["u_saved_me"])
particles.add(particle)
self.play(FadeIn(particles), run_time=1)
self.begin_ambient_camera_rotation(rate=0.12)
# Coalesce particles to sacred geometry positions
animations = []
for i, (particle, target) in enumerate(zip(particles, target_positions[:60])):
animations.append(particle.animate.move_to(target))
self.play(*animations, run_time=4, rate_func=smooth)
# Draw connecting lines (edges of sacred geometry)
lines = VGroup()
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
dist = np.linalg.norm(p1.get_center() - p2.get_center())
if dist < 2.2:
line = Line3D(p1.get_center(), p2.get_center(),
color=COLORS["u_saved_me"], stroke_width=1)
line.set_opacity(0.5)
lines.add(line)
self.play(Create(lines), run_time=3)
self.wait(10)
# Pulse effect
self.play(
lines.animate.set_opacity(1),
particles.animate.set_color(WHITE),
run_time=1
)
self.play(
lines.animate.set_opacity(0.5),
particles.animate.set_color(COLORS["u_saved_me"]),
run_time=1
)
self.play(FadeOut(title), FadeOut(particles), FadeOut(lines), run_time=1)
# ============================================================================
# TRACK 3: NOTHING - 3D Voronoi-like Explosion into Void
# ============================================================================
class Track03Nothing(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)
self.camera.background_color = "#0a0a0a"
title = create_title("03 · NOTHING", COLORS["nothing"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"\emptyset = \{x : x \neq x\}",
"∅ = { } — the empty set",
font_size=22, color=COLORS["nothing"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create a solid sphere that will shatter
sphere = Sphere(radius=2, resolution=(30, 30))
sphere.set_color(COLORS["nothing"])
sphere.set_opacity(0.8)
self.play(Create(sphere), run_time=2)
self.wait(1)
# Create fragments
fragments = VGroup()
for _ in range(60):
center = np.array([
random.uniform(-1.8, 1.8),
random.uniform(-1.8, 1.8),
random.uniform(-1.8, 1.8)
])
if np.linalg.norm(center) < 2:
size = random.uniform(0.15, 0.4)
# Create simple 3D dots as fragments
fragment = Dot3D(center, radius=size, color=COLORS["nothing"])
fragment.set_opacity(random.uniform(0.5, 0.9))
fragments.add(fragment)
# Explosion effect
self.play(FadeOut(sphere), FadeIn(fragments), run_time=0.5)
self.begin_ambient_camera_rotation(rate=0.1)
# Fragments drift outward into void
drift_animations = []
for fragment in fragments:
direction = fragment.get_center()
if np.linalg.norm(direction) > 0.1:
direction = direction / np.linalg.norm(direction)
else:
direction = np.array([random.uniform(-1,1), random.uniform(-1,1), random.uniform(-1,1)])
target = fragment.get_center() + direction * random.uniform(4, 8)
drift_animations.append(fragment.animate.move_to(target).set_opacity(0.1))
self.play(*drift_animations, run_time=15, rate_func=linear)
self.play(FadeOut(title), FadeOut(fragments), run_time=1)
# ============================================================================
# TRACK 4: SWEET RELIEF - Ghost Spirals in 3D
# ============================================================================
class Track04SweetRelief(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75*DEGREES, theta=-60*DEGREES)
self.camera.background_color = "#1a0a2e"
title = create_title("04 · SWEET RELIEF", COLORS["sweet_relief"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"\gamma(t) = (r\cos t, r\sin t, ct)",
"helix: (r·cos(t), r·sin(t), c·t)",
font_size=22, color=COLORS["sweet_relief"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create multiple ghost spirals
spirals = VGroup()
for i in range(5):
phase = i * TAU / 5
radius = 1.5 + i * 0.3
opacity = 0.8 - i * 0.12
spiral = ParametricFunction(
lambda t, r=radius, p=phase: np.array([
r * np.cos(t + p),
r * np.sin(t + p),
t * 0.4
]),
t_range=[-4*PI, 4*PI],
color=COLORS["sweet_relief"],
stroke_width=3 - i*0.4,
stroke_opacity=opacity
)
spirals.add(spiral)
self.begin_ambient_camera_rotation(rate=0.2)
for spiral in spirals:
self.play(Create(spiral), run_time=1.5)
# Add ethereal particles floating around spirals
particles = VGroup()
for _ in range(100):
t = random.uniform(-4*PI, 4*PI)
r = random.uniform(0.5, 3)
theta = random.uniform(0, TAU)
pos = np.array([r*np.cos(theta), r*np.sin(theta), t*0.4])
particle = Dot3D(pos, radius=0.03, color=COLORS["sweet_relief"])
particle.set_opacity(random.uniform(0.3, 0.7))
particles.add(particle)
self.play(FadeIn(particles), run_time=2)
# Gentle floating animation
self.wait(12)
self.play(FadeOut(title), FadeOut(spirals), FadeOut(particles), run_time=1)
# ============================================================================
# TRACK 5: TIPTOE - 3D Lissajous Curves
# ============================================================================
class Track05Tiptoe(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES)
self.camera.background_color = "#0f172a"
title = create_title("05 · TIPTOE", "#60a5fa")
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"(x,y,z) = (A\sin at, B\sin bt, C\sin ct)",
"Lissajous: (A·sin(at), B·sin(bt), C·sin(ct))",
font_size=20, color="#60a5fa"
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create 3D Lissajous curves with different frequency ratios
curves = VGroup()
ratios = [(3, 2, 5), (5, 4, 3), (7, 6, 5), (4, 3, 7)]
colors = ["#60a5fa", "#818cf8", "#a78bfa", "#c4b5fd"]
for (a, b, c), color in zip(ratios, colors):
curve = ParametricFunction(
lambda t, a=a, b=b, c=c: np.array([
2.5 * np.sin(a * t + PI/4),
2.5 * np.sin(b * t),
2.5 * np.sin(c * t)
]),
t_range=[0, TAU],
color=color,
stroke_width=2.5
)
curves.add(curve)
self.begin_ambient_camera_rotation(rate=0.15)
for curve in curves:
self.play(Create(curve), run_time=3)
# Gentle pulsing
self.play(curves.animate.set_stroke(width=4), run_time=1)
self.play(curves.animate.set_stroke(width=2.5), run_time=1)
self.wait(10)
self.play(FadeOut(title), FadeOut(curves), run_time=1)
# ============================================================================
# TRACK 6: NATURE'S CALL - 3D Fractal Tree
# ============================================================================
class Track06NaturesCall(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES, zoom=0.8)
self.camera.background_color = "#052e16"
title = create_title("06 · NATURE'S CALL", COLORS["natures_call"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"F \to F[+F]F[-F]F",
"L-System: F → F[+F]F[-F]F",
font_size=20, color=COLORS["natures_call"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Build 3D fractal tree
branches = VGroup()
leaves = VGroup()
def add_branch(start, direction, length, depth, branches_group, leaves_group):
if depth == 0 or length < 0.1:
# Add leaf
leaf = Dot3D(start, radius=0.08, color="#22c55e")
leaf.set_opacity(0.8)
leaves_group.add(leaf)
return
end = start + direction * length
branch = Line3D(start, end, color=COLORS["natures_call"], stroke_width=max(1, depth*0.8))
branches_group.add(branch)
# Create child branches
for angle in [PI/5, -PI/5, PI/6, -PI/6]:
if random.random() > 0.4:
# Rotate direction
rot_matrix = np.array([
[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]
])
new_dir = rot_matrix @ direction
new_dir[2] += 0.3 # Tendency to grow up
new_dir = new_dir / np.linalg.norm(new_dir)
add_branch(end, new_dir, length * 0.7, depth - 1, branches_group, leaves_group)
# Start tree from bottom
start = np.array([0, 0, -3])
direction = np.array([0, 0, 1])
add_branch(start, direction, 1.5, 5, branches, leaves)
self.begin_ambient_camera_rotation(rate=0.1)
# Grow branches progressively
batch_size = max(1, len(branches)//10)
for i in range(0, len(branches), batch_size):
batch = branches[i:i+batch_size]
self.play(Create(batch), run_time=0.8)
# Bloom leaves
self.play(FadeIn(leaves, scale=0.5), run_time=3)
self.wait(8)
self.play(FadeOut(title), FadeOut(branches), FadeOut(leaves), run_time=1)
# ============================================================================
# TRACK 7: DREAMCATCHER (INTERLUDE) - Sacred Geometry Web
# ============================================================================
class Track07Dreamcatcher(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=80*DEGREES, theta=-45*DEGREES)
self.camera.background_color = "#1e1b4b"
title = create_title("07 · DREAMCATCHER", COLORS["dreamcatcher"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"\phi = \frac{1 + \sqrt{5}}{2}",
"φ = (1 + √5) / 2 ≈ 1.618",
font_size=24, color=COLORS["dreamcatcher"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create dreamcatcher structure - concentric rings with web
rings = VGroup()
web_lines = VGroup()
# Outer rings
for i, r in enumerate([3, 2.5, 2, 1.5, 1]):
ring = Circle(radius=r, color=COLORS["dreamcatcher"], stroke_width=2-i*0.3)
ring.rotate(PI/2, axis=RIGHT) # Make it face camera
rings.add(ring)
# Web pattern (radial lines)
for angle in np.linspace(0, TAU, 12, endpoint=False):
line = Line3D(
np.array([0, 0, 0]),
np.array([3*np.cos(angle), 0, 3*np.sin(angle)]),
color=COLORS["dreamcatcher"],
stroke_width=1
)
web_lines.add(line)
# Spiral web
spiral = ParametricFunction(
lambda t: np.array([
(0.3 + t*0.4) * np.cos(t*3),
0,
(0.3 + t*0.4) * np.sin(t*3)
]),
t_range=[0, 7],
color=COLORS["dreamcatcher"],
stroke_width=1.5
)
self.begin_ambient_camera_rotation(rate=0.25)
self.play(Create(rings), run_time=3)
self.play(Create(web_lines), run_time=2)
self.play(Create(spiral), run_time=3)
# Add glowing center
center_glow = Dot3D(ORIGIN, radius=0.15, color=WHITE)
self.play(FadeIn(center_glow, scale=0.5), run_time=1)
# Pulse
self.play(center_glow.animate.scale(1.5).set_color(COLORS["dreamcatcher"]), run_time=1)
self.play(center_glow.animate.scale(1/1.5).set_color(WHITE), run_time=1)
self.wait(10)
self.play(FadeOut(title), FadeOut(rings), FadeOut(web_lines),
FadeOut(spiral), FadeOut(center_glow), run_time=1)
# ============================================================================
# TRACK 8: IDK - 3D Lorenz Attractor
# ============================================================================
class Track08IDK(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES, zoom=0.6)
self.camera.background_color = "#1f0a2e"
title = create_title("08 · IDK", COLORS["idk"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"\dot{x} = \sigma(y-x), \dot{y} = x(\rho-z)-y",
"Lorenz: dx/dt = σ(y-x), dy/dt = x(ρ-z)-y",
font_size=16, color=COLORS["idk"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(2)
self.play(FadeOut(equation), run_time=0.5)
# Generate Lorenz attractor points
sigma, rho, beta = 10, 28, 8/3
dt = 0.005
def lorenz_step(x, y, z):
dx = sigma * (y - x) * dt
dy = (x * (rho - z) - y) * dt
dz = (x * y - beta * z) * dt
return x + dx, y + dy, z + dz
# Generate trajectory
x, y, z = 0.1, 0, 0
points = []
for _ in range(8000):
x, y, z = lorenz_step(x, y, z)
points.append([x * 0.12, y * 0.12, (z - 25) * 0.12])
# Create curve from points
attractor = VMobject()
attractor.set_points_smoothly([np.array(p) for p in points[::4]])
attractor.set_stroke(color=COLORS["idk"], width=1.5, opacity=0.9)
self.begin_ambient_camera_rotation(rate=0.15)
self.play(Create(attractor), run_time=12, rate_func=linear)
# Add trailing particle
particle = Dot3D(np.array(points[-1]), radius=0.1, color=WHITE)
self.play(FadeIn(particle), run_time=0.5)
# Animate particle along last part of trajectory
for i in range(-100, -1, 5):
self.play(particle.animate.move_to(np.array(points[i])), run_time=0.1)
self.wait(5)
self.play(FadeOut(title), FadeOut(attractor), FadeOut(particle), run_time=1)
# ============================================================================
# TRACK 9: WITH U - THE TURN! Two Souls Orbiting + Stars
# ============================================================================
class Track09WithU(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=70*DEGREES, theta=-60*DEGREES)
self.camera.background_color = "#0c0a09"
title = create_title("09 · WITH U", COLORS["with_u"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"F = G\frac{m_1 m_2}{r^2}",
"F = G · m₁m₂ / r²",
font_size=26, color=COLORS["with_u"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create two souls (glowing spheres)
soul1 = Sphere(radius=0.3, resolution=(20, 20))
soul1.set_color(COLORS["with_u"])
soul1.set_opacity(0.9)
soul2 = Sphere(radius=0.25, resolution=(20, 20))
soul2.set_color("#fcd34d") # Slightly different gold
soul2.set_opacity(0.9)
# Position at opposite sides
soul1.move_to([2, 0, 0])
soul2.move_to([-2, 0, 0])
self.play(FadeIn(soul1, scale=0.5), FadeIn(soul2, scale=0.5), run_time=2)
# Orbital trails
trail1 = VMobject(stroke_color=COLORS["with_u"], stroke_width=2, stroke_opacity=0.6)
trail2 = VMobject(stroke_color="#fcd34d", stroke_width=2, stroke_opacity=0.6)
self.add(trail1, trail2)
self.begin_ambient_camera_rotation(rate=0.1)
# Animate orbital dance
orbit_time = 10
steps = 150
for i in range(steps):
t = i / steps * orbit_time
angle = t * 1.5
# Elliptical orbits with slight vertical motion
r1 = 2 + 0.3*np.sin(angle*2)
r2 = 2 + 0.3*np.sin(angle*2 + PI)
pos1 = np.array([r1*np.cos(angle), r1*np.sin(angle), 0.5*np.sin(angle*3)])
pos2 = np.array([r2*np.cos(angle + PI), r2*np.sin(angle + PI), 0.5*np.sin(angle*3 + PI)])
self.play(
soul1.animate.move_to(pos1),
soul2.animate.move_to(pos2),
run_time=orbit_time/steps,
rate_func=linear
)
# Update trails
if len(trail1.points) > 0:
trail1.add_line_to(pos1)
trail2.add_line_to(pos2)
else:
trail1.start_new_path(pos1)
trail2.start_new_path(pos2)
# EPIC MOMENT - Stars appear!
stars = VGroup()
for _ in range(200):
star_pos = np.array([
random.uniform(-8, 8),
random.uniform(-8, 8),
random.uniform(-5, 5)
])
star = Dot3D(star_pos, radius=0.03, color=WHITE)
star.set_opacity(random.uniform(0.3, 1.0))
stars.add(star)
self.play(FadeIn(stars, lag_ratio=0.02), run_time=3)
# Souls come together
self.play(
soul1.animate.move_to([0.3, 0, 0]),
soul2.animate.move_to([-0.3, 0, 0]),
run_time=2
)
# Glow brighter together
self.play(
soul1.animate.scale(1.3).set_color(WHITE),
soul2.animate.scale(1.3).set_color(WHITE),
stars.animate.set_opacity(0.5),
run_time=1.5
)
self.wait(2)
self.play(FadeOut(title), FadeOut(soul1), FadeOut(soul2),
FadeOut(trail1), FadeOut(trail2), FadeOut(stars), run_time=1.5)
# ============================================================================
# TRACK 10: POOR YOU POOR ME - Drifting Objects in Orange Glow
# ============================================================================
class Track10PoorYou(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75*DEGREES, theta=-50*DEGREES)
self.camera.background_color = "#1c1917"
title = create_title("10 · POOR YOU POOR ME", COLORS["poor_you"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"\vec{v}(t) = \vec{v}_0 e^{-\lambda t}",
"v(t) = v₀ · e^(-λt) → 0",
font_size=22, color=COLORS["poor_you"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create drifting geometric objects
objects = VGroup()
# Various shapes drifting apart
for i in range(8):
angle = i * TAU / 8
pos = np.array([1.5*np.cos(angle), 1.5*np.sin(angle), random.uniform(-1, 1)])
if i % 4 == 0:
shape = Sphere(radius=0.3, resolution=(10, 10))
elif i % 4 == 1:
shape = Dot3D(pos, radius=0.25)
elif i % 4 == 2:
shape = Sphere(radius=0.35, resolution=(8, 8))
else:
shape = Dot3D(pos, radius=0.2)
shape.set_color(COLORS["poor_you"])
shape.set_opacity(0.8)
shape.move_to(pos)
objects.add(shape)
# Add some smaller fragments
for _ in range(25):
frag = Dot3D(
point=np.array([random.uniform(-3, 3), random.uniform(-3, 3), random.uniform(-2, 2)]),
radius=random.uniform(0.05, 0.15),
color=COLORS["poor_you"]
)
frag.set_opacity(random.uniform(0.4, 0.8))
objects.add(frag)
self.play(FadeIn(objects), run_time=2)
self.begin_ambient_camera_rotation(rate=0.08)
# Bittersweet drifting apart
drift_animations = []
for obj in objects:
direction = obj.get_center()
if np.linalg.norm(direction) > 0.1:
direction = direction / np.linalg.norm(direction)
else:
direction = np.array([random.uniform(-1,1), random.uniform(-1,1), random.uniform(-1,1)])
target = obj.get_center() + direction * random.uniform(1, 3)
drift_animations.append(
obj.animate.move_to(target).set_opacity(obj.get_fill_opacity() * 0.5)
)
self.play(*drift_animations, run_time=15, rate_func=smooth)
self.wait(3)
self.play(FadeOut(title), FadeOut(objects), run_time=1)
# ============================================================================
# TRACK 11: WAIT 4 U - Concentric Pulsing Spheres
# ============================================================================
class Track11Wait4U(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)
self.camera.background_color = "#0f172a"
title = create_title("11 · WAIT 4 U", COLORS["wait_4_u"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"r(t) = r_0 + A\sin(\omega t)",
"r(t) = r₀ + A·sin(ωt)",
font_size=22, color=COLORS["wait_4_u"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create concentric spheres
spheres = VGroup()
base_radii = [0.5, 1, 1.5, 2, 2.5, 3]
for i, r in enumerate(base_radii):
sphere = Sphere(radius=r, resolution=(20, 20))
sphere.set_color(COLORS["wait_4_u"])
sphere.set_opacity(0.2 - i*0.025)
sphere.set_stroke(color=COLORS["wait_4_u"], width=1, opacity=0.5)
spheres.add(sphere)
self.play(Create(spheres), run_time=3)
self.begin_ambient_camera_rotation(rate=0.12)
# Pulsing animation - waves emanating outward
for cycle in range(8):
pulse_animations = []
for i, (sphere, base_r) in enumerate(zip(spheres, base_radii)):
phase = i * 0.2
new_r = base_r * (1 + 0.15 * np.sin(cycle * PI/2 + phase))
new_sphere = Sphere(radius=new_r, resolution=(20, 20))
new_sphere.set_color(COLORS["wait_4_u"])
new_sphere.set_opacity(0.2 - i*0.025)
pulse_animations.append(Transform(sphere, new_sphere))
self.play(*pulse_animations, run_time=1.5)
self.wait(5)
self.play(FadeOut(title), FadeOut(spheres), run_time=1)
# ============================================================================
# TRACK 12: RUN TO U - Speed Lines + 3D Collision
# ============================================================================
class Track12RunToU(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
self.camera.background_color = "#082f49"
title = create_title("12 · RUN TO U", COLORS["run_to_u"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"p_1 + p_2 = p_{final}",
"p₁ + p₂ = p_final (momentum)",
font_size=22, color=COLORS["run_to_u"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Create two objects rushing toward each other
obj1 = Sphere(radius=0.4, resolution=(15, 15))
obj1.set_color(COLORS["run_to_u"])
obj1.move_to([-5, 0, 0])
obj2 = Sphere(radius=0.35, resolution=(15, 15))
obj2.set_color("#67e8f9")
obj2.move_to([5, 0, 0])
# Speed lines
speed_lines = VGroup()
for _ in range(30):
y = random.uniform(-3, 3)
z = random.uniform(-2, 2)
line = Line3D([-6, y, z], [6, y, z], color=COLORS["run_to_u"], stroke_width=1)
line.set_opacity(random.uniform(0.2, 0.6))
speed_lines.add(line)
self.play(FadeIn(speed_lines), run_time=1)
self.play(FadeIn(obj1), FadeIn(obj2), run_time=1)
self.begin_ambient_camera_rotation(rate=0.15)
# Rush toward each other with increasing speed
self.play(
obj1.animate.move_to([-2, 0, 0]),
obj2.animate.move_to([2, 0, 0]),
run_time=3,
rate_func=rate_functions.ease_in_quad
)
self.play(
obj1.animate.move_to([0, 0, 0]),
obj2.animate.move_to([0, 0, 0]),
run_time=1.5,
rate_func=rate_functions.ease_in_expo
)
# Collision burst!
burst_particles = []
for _ in range(50):
direction = np.array([random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)])
direction = direction / np.linalg.norm(direction)
particle = Dot3D(ORIGIN, radius=0.1, color=WHITE)
burst_particles.append((particle, direction))
burst_group = VGroup(*[p for p, d in burst_particles])
self.play(
FadeOut(obj1), FadeOut(obj2),
FadeIn(burst_group, scale=0.5),
run_time=0.3
)
# Expand burst
burst_anims = []
for particle, direction in burst_particles:
target = direction * random.uniform(2, 5)
burst_anims.append(particle.animate.move_to(target).set_opacity(0))
self.play(*burst_anims, run_time=3)
# New combined entity emerges
combined = Sphere(radius=0.6, resolution=(25, 25))
combined.set_color_by_gradient(COLORS["run_to_u"], "#67e8f9")
self.play(FadeIn(combined, scale=0.3), run_time=2)
# Gentle rotation
self.wait(8)
self.play(FadeOut(title), FadeOut(combined), FadeOut(speed_lines), FadeOut(burst_group), run_time=1)
# ============================================================================
# TRACK 13: MEDICATIONS - Chaos Attractor with Red Pulsing
# ============================================================================
class Track13Medications(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES, zoom=0.7)
self.camera.background_color = "#1c0a0a"
title = create_title("13 · MEDICATIONS", COLORS["medications"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"\frac{dx}{dt} = a(y-x)",
"Chen attractor: dx/dt = a(y-x)",
font_size=18, color=COLORS["medications"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Generate Chen attractor (another chaotic system)
a, b, c = 35, 3, 28
dt = 0.002
def chen_step(x, y, z):
dx = a * (y - x) * dt
dy = ((c - a) * x - x * z + c * y) * dt
dz = (x * y - b * z) * dt
return x + dx, y + dy, z + dz
x, y, z = 0.1, 0.1, 0.1
points = []
for _ in range(10000):
x, y, z = chen_step(x, y, z)
points.append([x * 0.08, y * 0.08, z * 0.08])
attractor = VMobject()
attractor.set_points_smoothly([np.array(p) for p in points[::5]])
attractor.set_stroke(color=COLORS["medications"], width=1.2, opacity=0.8)
self.begin_ambient_camera_rotation(rate=0.18)
self.play(Create(attractor), run_time=8, rate_func=linear)
# Red pulsing effect
for _ in range(6):
self.play(
attractor.animate.set_stroke(color="#ff6b6b", opacity=1),
run_time=0.5
)
self.play(
attractor.animate.set_stroke(color=COLORS["medications"], opacity=0.8),
run_time=0.5
)
self.wait(5)
self.play(FadeOut(title), FadeOut(attractor), run_time=1)
# ============================================================================
# TRACK 14: HOLLOW - Golden Spiral to Moon, Epic Finale
# ============================================================================
class Track14Hollow(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES, zoom=0.7)
self.camera.background_color = "#1a0a2e"
title = create_title("14 · HOLLOW", COLORS["hollow"])
self.add_fixed_in_frame_mobjects(title)
self.play(FadeIn(title, shift=RIGHT*0.2), run_time=0.8)
# Equation
equation = create_equation(
r"r = a \cdot \phi^{\theta/90°}",
"Golden spiral: r = a · φ^(θ/90°)",
font_size=22, color=COLORS["hollow"]
)
equation.to_corner(DR, buff=0.5)
self.add_fixed_in_frame_mobjects(equation)
self.play(FadeIn(equation), run_time=0.8)
self.wait(1.5)
self.play(FadeOut(equation), run_time=0.5)
# Golden spiral
phi = (1 + np.sqrt(5)) / 2
spiral = ParametricFunction(
lambda t: np.array([
0.1 * phi**(t / (PI/2)) * np.cos(t),
0.1 * phi**(t / (PI/2)) * np.sin(t),
t * 0.05
]),
t_range=[0, 6*PI],
color=COLORS["hollow"],
stroke_width=3
)
self.begin_ambient_camera_rotation(rate=0.1)
self.play(Create(spiral), run_time=5)
# Moon appears in distance
moon = Sphere(radius=1.2, resolution=(30, 30))
moon.set_color("#fef3c7")
moon.set_opacity(0.9)
moon.move_to([6, 4, 3])
self.play(FadeIn(moon, scale=0.5), run_time=2)
# Stars appear
stars = VGroup()
for _ in range(150):
star_pos = np.array([
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-5, 8)
])
star = Dot3D(star_pos, radius=0.03, color=WHITE)
star.set_opacity(random.uniform(0.2, 0.8))
stars.add(star)
self.play(FadeIn(stars, lag_ratio=0.01), run_time=3)
# Epic camera pullback
self.stop_ambient_camera_rotation()
self.move_camera(
phi=60 * DEGREES,
theta=-60 * DEGREES,
frame_center=ORIGIN,
zoom=0.5,
run_time=8,
rate_func=smooth
)
# Final glow
self.play(
moon.animate.set_color(COLORS["hollow"]),
spiral.animate.set_stroke(color=WHITE, width=4),
run_time=2
)
# Fade to completion
self.wait(2)
all_objects = VGroup(spiral, moon, stars)
self.play(FadeOut(title), FadeOut(all_objects), run_time=2)
# ============================================================================
# COMBINED MEGA SCENE - All Tracks in Sequence
# ============================================================================
class SuryaEpic(ThreeDScene):
"""
Master scene combining all tracks.
For full render, use individual track classes and combine with ffmpeg.
This serves as a preview/test scene.
"""
def construct(self):
self.camera.background_color = "#1a0a2e"
# Intro title
album_title = Text("S U R Y A", font_size=72, color=COLORS["hollow"])
artist = Text("Das", font_size=36, color=WHITE)
artist.next_to(album_title, DOWN, buff=0.5)
self.add_fixed_in_frame_mobjects(album_title, artist)
self.play(FadeIn(album_title, shift=UP*0.5), run_time=2)
self.play(FadeIn(artist), run_time=1)
self.wait(2)
self.play(FadeOut(album_title), FadeOut(artist), run_time=1)
# Note: For full animation, render each track separately and combine
# This is just a preview intro
preview = Text(
"Render individual tracks with:\nmanim -pqh surya_epic.py Track01Skin",
font_size=20, color=WHITE
)
self.add_fixed_in_frame_mobjects(preview)
self.play(FadeIn(preview), run_time=1)
self.wait(3)