1171 lines
43 KiB
Python
1171 lines
43 KiB
Python
#!/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)
|