2217 lines
68 KiB
Python
2217 lines
68 KiB
Python
"""
|
|
SURYA — A Mathematical Journey Through Feeling
|
|
Das's 14-track emotional odyssey rendered in mathematical beauty
|
|
|
|
Created with Manim Community Edition
|
|
"""
|
|
|
|
from manim import *
|
|
import numpy as np
|
|
from scipy.interpolate import interp1d
|
|
import random
|
|
|
|
# ============================================================================
|
|
# CONFIGURATION
|
|
# ============================================================================
|
|
|
|
config.background_color = "#030305"
|
|
|
|
# Color palette matching the SURYA website aesthetic
|
|
class SuryaColors:
|
|
# Track colors
|
|
INTRO = "#a78bfa" # Purple
|
|
SKIN = "#f472b6" # Pink
|
|
SAVED = "#22d3ee" # Cyan
|
|
NOTHING = "#666666" # Grey
|
|
RELIEF = "#a78bfa" # Purple
|
|
NATURE = "#5eead4" # Teal
|
|
DREAM = "#c4b5fd" # Soft purple
|
|
IDK = "#f472b6" # Dark pink
|
|
WITH_U = "#fbbf24" # Golden (THE TURN)
|
|
POOR = "#fb923c" # Orange
|
|
WAIT = "#a78bfa" # Purple longing
|
|
RUN = "#22d3ee" # Cyan urgent
|
|
MEDS = "#ef4444" # Red
|
|
HOLLOW = "#fbbf24" # Golden resolution
|
|
|
|
# Supporting colors
|
|
DARK_BG = "#030305"
|
|
SOUL_WHITE = "#ffffff"
|
|
GLOW_SOFT = "#ffffff40"
|
|
|
|
|
|
# ============================================================================
|
|
# CUSTOM MATHEMATICAL OBJECTS
|
|
# ============================================================================
|
|
|
|
class SoulOrb(VGroup):
|
|
"""The persistent soul orb - heart of the journey"""
|
|
def __init__(self, color=WHITE, radius=0.3, glow_radius=1.5, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
# Core orb
|
|
self.core = Dot(radius=radius, color=color)
|
|
self.core.set_fill(color, opacity=1)
|
|
|
|
# Glow layers (gaussian-like falloff)
|
|
self.glow_layers = VGroup()
|
|
for i in range(8):
|
|
factor = (i + 1) / 8
|
|
glow = Circle(radius=radius + glow_radius * factor)
|
|
opacity = 0.3 * (1 - factor) ** 2
|
|
glow.set_stroke(color, width=2, opacity=opacity)
|
|
glow.set_fill(color, opacity=opacity * 0.3)
|
|
self.glow_layers.add(glow)
|
|
|
|
# Pulsing ring
|
|
self.ring = Circle(radius=radius * 2)
|
|
self.ring.set_stroke(color, width=1, opacity=0.5)
|
|
|
|
self.add(self.glow_layers, self.core, self.ring)
|
|
self.color = color
|
|
|
|
def pulse(self, scale_factor=1.3, run_time=1):
|
|
return AnimationGroup(
|
|
self.ring.animate.scale(scale_factor).set_opacity(0),
|
|
rate_func=smooth
|
|
)
|
|
|
|
|
|
class Spirograph(VMobject):
|
|
"""Parametric spirograph curves - mathematical beauty"""
|
|
def __init__(self, R=3, r=1, d=1.5, color=WHITE, num_points=1000, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.R, self.r, self.d = R, r, d
|
|
|
|
t = np.linspace(0, 2 * np.pi * self._lcm_period(), num_points)
|
|
|
|
# Hypotrochoid equations
|
|
x = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
|
|
y = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)
|
|
|
|
points = [np.array([x[i], y[i], 0]) for i in range(len(t))]
|
|
self.set_points_smoothly(points)
|
|
self.set_stroke(color, width=2, opacity=0.8)
|
|
|
|
def _lcm_period(self):
|
|
from math import gcd
|
|
r_int, R_int = int(self.r * 100), int(self.R * 100)
|
|
return r_int // gcd(r_int, R_int)
|
|
|
|
|
|
class LissajousCurve(VMobject):
|
|
"""Lissajous figures - chaos and confusion"""
|
|
def __init__(self, a=3, b=4, delta=np.pi/2, color=WHITE, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
t = np.linspace(0, 2 * np.pi, 1000)
|
|
x = 2 * np.sin(a * t + delta)
|
|
y = 2 * np.sin(b * t)
|
|
|
|
points = [np.array([x[i], y[i], 0]) for i in range(len(t))]
|
|
self.set_points_smoothly(points)
|
|
self.set_stroke(color, width=2)
|
|
|
|
|
|
class FlowerOfLife(VGroup):
|
|
"""Sacred geometry - spiritual moments"""
|
|
def __init__(self, rings=2, radius=0.5, color=WHITE, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
centers = [ORIGIN]
|
|
for ring in range(1, rings + 1):
|
|
for i in range(6 * ring):
|
|
angle = i * TAU / (6 * ring)
|
|
dist = radius * ring * np.sqrt(3)
|
|
centers.append(np.array([
|
|
dist * np.cos(angle),
|
|
dist * np.sin(angle),
|
|
0
|
|
]))
|
|
|
|
for center in centers[:19]: # Classic 19 circles
|
|
circle = Circle(radius=radius, color=color)
|
|
circle.move_to(center)
|
|
circle.set_stroke(color, width=1.5, opacity=0.6)
|
|
self.add(circle)
|
|
|
|
|
|
class GoldenSpiral(VMobject):
|
|
"""Golden ratio spiral - resolution and beauty"""
|
|
def __init__(self, turns=4, color=GOLD, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
phi = (1 + np.sqrt(5)) / 2 # Golden ratio
|
|
t = np.linspace(0, turns * 2 * np.pi, 500)
|
|
|
|
# Logarithmic spiral with golden ratio
|
|
r = 0.1 * phi ** (t / (2 * np.pi))
|
|
x = r * np.cos(t)
|
|
y = r * np.sin(t)
|
|
|
|
points = [np.array([x[i], y[i], 0]) for i in range(len(t))]
|
|
self.set_points_smoothly(points)
|
|
self.set_stroke(color, width=3)
|
|
|
|
|
|
class FractalTree(VGroup):
|
|
"""Branching fractals - nature and growth"""
|
|
def __init__(self, depth=6, length=2, angle=25, color=GREEN, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self._build_tree(ORIGIN, UP * length, depth, length, angle, color)
|
|
|
|
def _build_tree(self, start, direction, depth, length, angle, color):
|
|
if depth == 0:
|
|
return
|
|
|
|
end = start + direction
|
|
branch = Line(start, end, color=color)
|
|
branch.set_stroke(width=depth * 0.8, opacity=0.3 + 0.1 * depth)
|
|
self.add(branch)
|
|
|
|
# Recursive branches
|
|
new_length = length * 0.7
|
|
left_dir = rotate_vector(direction, angle * DEGREES) * 0.7
|
|
right_dir = rotate_vector(direction, -angle * DEGREES) * 0.7
|
|
|
|
self._build_tree(end, left_dir, depth - 1, new_length, angle, color)
|
|
self._build_tree(end, right_dir, depth - 1, new_length, angle, color)
|
|
|
|
|
|
class VoronoiFragments(VGroup):
|
|
"""Voronoi diagram - fragmentation and emptiness"""
|
|
def __init__(self, num_points=30, color=WHITE, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
# Generate random points
|
|
points = np.random.uniform(-4, 4, (num_points, 2))
|
|
|
|
# Create approximate Voronoi cells using circles
|
|
for point in points:
|
|
cell = Dot(point=[point[0], point[1], 0], radius=0.05, color=color)
|
|
cell.set_fill(color, opacity=0.3)
|
|
self.add(cell)
|
|
|
|
# Add connecting lines
|
|
for other in points:
|
|
if np.linalg.norm(point - other) < 2 and not np.array_equal(point, other):
|
|
mid = (point + other) / 2
|
|
angle = np.arctan2(other[1] - point[1], other[0] - point[0])
|
|
line = Line(
|
|
[mid[0] - np.sin(angle), mid[1] + np.cos(angle), 0],
|
|
[mid[0] + np.sin(angle), mid[1] - np.cos(angle), 0],
|
|
color=color
|
|
)
|
|
line.set_stroke(width=0.5, opacity=0.2)
|
|
self.add(line)
|
|
|
|
|
|
class ParticleField(VGroup):
|
|
"""Particles following mathematical fields"""
|
|
def __init__(self, num_particles=100, color=WHITE, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
for _ in range(num_particles):
|
|
x = random.uniform(-7, 7)
|
|
y = random.uniform(-4, 4)
|
|
size = random.uniform(0.02, 0.06)
|
|
opacity = random.uniform(0.2, 0.6)
|
|
|
|
particle = Dot(point=[x, y, 0], radius=size, color=color)
|
|
particle.set_fill(color, opacity=opacity)
|
|
self.add(particle)
|
|
|
|
|
|
class StrangeAttractor(VMobject):
|
|
"""Lorenz-like strange attractor for chaos moments"""
|
|
def __init__(self, color=RED, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
# Simplified attractor visualization
|
|
sigma, rho, beta = 10, 28, 8/3
|
|
dt = 0.01
|
|
x, y, z = 0.1, 0, 0
|
|
|
|
points = []
|
|
for _ in range(3000):
|
|
dx = sigma * (y - x) * dt
|
|
dy = (x * (rho - z) - y) * dt
|
|
dz = (x * y - beta * z) * dt
|
|
x, y, z = x + dx, y + dy, z + dz
|
|
points.append(np.array([x * 0.1, (z - 25) * 0.1, 0]))
|
|
|
|
self.set_points_smoothly(points)
|
|
self.set_stroke(color, width=1, opacity=0.7)
|
|
|
|
|
|
# ============================================================================
|
|
# TRACK ANIMATIONS
|
|
# ============================================================================
|
|
|
|
class Track01_Skin(Scene):
|
|
"""Alienation - "don't fit in my own skin" """
|
|
def construct(self):
|
|
# Create distorted soul trying to find its shape
|
|
soul = SoulOrb(color=SuryaColors.SKIN, radius=0.3)
|
|
self.play(FadeIn(soul, scale=0.5), run_time=2)
|
|
|
|
# Morphing spirograph representing identity struggle
|
|
spiro1 = Spirograph(R=3, r=0.7, d=1.2, color=SuryaColors.SKIN)
|
|
spiro2 = Spirograph(R=3, r=1.3, d=0.8, color=SuryaColors.SKIN)
|
|
spiro1.scale(0.8)
|
|
spiro2.scale(0.8)
|
|
|
|
self.play(Create(spiro1), run_time=3)
|
|
|
|
# Soul pulsates uncomfortably
|
|
self.play(
|
|
soul.animate.scale(0.7),
|
|
spiro1.animate.set_opacity(0.5),
|
|
run_time=1
|
|
)
|
|
self.play(
|
|
soul.animate.scale(1.3),
|
|
run_time=1
|
|
)
|
|
|
|
# Transform to different identity
|
|
self.play(
|
|
Transform(spiro1, spiro2),
|
|
soul.animate.scale(0.8),
|
|
run_time=4
|
|
)
|
|
|
|
# Particles of self floating away
|
|
particles = ParticleField(50, color=SuryaColors.SKIN)
|
|
self.play(FadeIn(particles, lag_ratio=0.1), run_time=2)
|
|
|
|
# Everything fades with lingering unease
|
|
self.play(
|
|
FadeOut(spiro1),
|
|
FadeOut(particles),
|
|
soul.animate.scale(0.5).set_opacity(0.3),
|
|
run_time=3
|
|
)
|
|
self.wait(1)
|
|
|
|
|
|
class Track02_USavedMe(Scene):
|
|
"""Hope emerges - "you saved me from my broken soul" """
|
|
def construct(self):
|
|
# Broken soul fragments
|
|
soul = SoulOrb(color=SuryaColors.SAVED, radius=0.2)
|
|
soul.set_opacity(0.5)
|
|
|
|
# Fragments scattered
|
|
fragments = VGroup(*[
|
|
Dot(point=[random.uniform(-2, 2), random.uniform(-2, 2), 0],
|
|
radius=0.05, color=SuryaColors.SAVED)
|
|
for _ in range(20)
|
|
])
|
|
|
|
self.play(FadeIn(fragments, lag_ratio=0.1), run_time=2)
|
|
|
|
# Fragments begin to coalesce
|
|
self.play(
|
|
*[f.animate.move_to(ORIGIN) for f in fragments],
|
|
run_time=3,
|
|
rate_func=smooth
|
|
)
|
|
|
|
# Soul emerges whole
|
|
self.play(
|
|
FadeOut(fragments),
|
|
FadeIn(soul, scale=2),
|
|
run_time=2
|
|
)
|
|
|
|
# Hopeful spirograph expands outward
|
|
spiro = Spirograph(R=4, r=1.5, d=2, color=SuryaColors.SAVED)
|
|
spiro.scale(0)
|
|
self.add(spiro)
|
|
|
|
self.play(
|
|
spiro.animate.scale(0.7),
|
|
soul.animate.scale(1.5).set_opacity(1),
|
|
run_time=4
|
|
)
|
|
|
|
# Sacred geometry appears - spiritual awakening
|
|
flower = FlowerOfLife(rings=2, radius=0.6, color=SuryaColors.SAVED)
|
|
flower.set_opacity(0)
|
|
self.add(flower)
|
|
|
|
self.play(
|
|
flower.animate.set_opacity(0.4),
|
|
spiro.animate.set_opacity(0.3),
|
|
run_time=3
|
|
)
|
|
|
|
# Gentle pulsing in harmony
|
|
self.play(
|
|
soul.animate.scale(1.2),
|
|
flower.animate.scale(1.1),
|
|
run_time=1.5
|
|
)
|
|
self.play(
|
|
soul.animate.scale(1/1.2),
|
|
flower.animate.scale(1/1.1),
|
|
run_time=1.5
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(spiro, flower),
|
|
soul.animate.scale(0.6),
|
|
run_time=2
|
|
)
|
|
|
|
|
|
class Track03_Nothing(Scene):
|
|
"""Emptiness - "I lost my heart, now I feel nothing" """
|
|
def construct(self):
|
|
# Soul starts visible but will fade
|
|
soul = SoulOrb(color=SuryaColors.NOTHING, radius=0.3)
|
|
self.add(soul)
|
|
|
|
# Voronoi fragmentation - self breaking apart
|
|
voronoi = VoronoiFragments(num_points=25, color=SuryaColors.NOTHING)
|
|
|
|
# Soul slowly dims
|
|
self.play(
|
|
soul.animate.scale(0.7).set_opacity(0.5),
|
|
run_time=3
|
|
)
|
|
|
|
# Fragments appear
|
|
self.play(Create(voronoi), run_time=4)
|
|
|
|
# Everything becomes grey and distant
|
|
grey_circle = Circle(radius=3, color=SuryaColors.NOTHING)
|
|
grey_circle.set_stroke(width=1, opacity=0.2)
|
|
grey_circle.set_fill(SuryaColors.NOTHING, opacity=0.05)
|
|
|
|
self.play(
|
|
FadeIn(grey_circle),
|
|
soul.animate.scale(0.3).set_opacity(0.2),
|
|
voronoi.animate.set_opacity(0.1),
|
|
run_time=4
|
|
)
|
|
|
|
# Hollow pulsing - mechanical, lifeless
|
|
for _ in range(2):
|
|
self.play(
|
|
grey_circle.animate.scale(1.1),
|
|
run_time=1,
|
|
rate_func=there_and_back
|
|
)
|
|
|
|
# Static emptiness
|
|
self.wait(2)
|
|
|
|
# Fragments scatter into void
|
|
self.play(
|
|
FadeOut(voronoi, shift=UP * 2),
|
|
FadeOut(grey_circle),
|
|
soul.animate.set_opacity(0.1),
|
|
run_time=3
|
|
)
|
|
|
|
|
|
class Track04_SweetRelief(Scene):
|
|
"""Haunted - "seeing ghosts around my throat" """
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.RELIEF, radius=0.25)
|
|
self.add(soul)
|
|
|
|
# Ghost-like spiraling forms
|
|
ghosts = VGroup()
|
|
for i in range(5):
|
|
ghost = Spirograph(R=2 + i * 0.3, r=0.5, d=1 + i * 0.2,
|
|
color=SuryaColors.RELIEF)
|
|
ghost.scale(0.5)
|
|
ghost.set_opacity(0.1 + i * 0.05)
|
|
ghost.rotate(i * PI / 5)
|
|
ghosts.add(ghost)
|
|
|
|
self.play(
|
|
*[Create(g) for g in ghosts],
|
|
run_time=4,
|
|
lag_ratio=0.3
|
|
)
|
|
|
|
# Ghosts circle the soul menacingly
|
|
self.play(
|
|
Rotate(ghosts, PI, about_point=ORIGIN),
|
|
soul.animate.scale(0.8),
|
|
run_time=4
|
|
)
|
|
|
|
# Soul struggles
|
|
self.play(
|
|
soul.animate.shift(UP * 0.3),
|
|
run_time=0.5
|
|
)
|
|
self.play(
|
|
soul.animate.shift(DOWN * 0.3),
|
|
run_time=0.5
|
|
)
|
|
|
|
# Ghosts close in
|
|
self.play(
|
|
ghosts.animate.scale(0.7),
|
|
soul.animate.set_opacity(0.6),
|
|
run_time=3
|
|
)
|
|
|
|
# Brief moment of relief (ghosts recede slightly)
|
|
self.play(
|
|
ghosts.animate.scale(1.3).set_opacity(0.1),
|
|
soul.animate.scale(1.2).set_opacity(1),
|
|
run_time=2
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(ghosts),
|
|
run_time=2
|
|
)
|
|
|
|
|
|
class Track05_Tiptoe(Scene):
|
|
"""Tense, cautious movement"""
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.RELIEF, radius=0.2)
|
|
soul.shift(LEFT * 4)
|
|
self.add(soul)
|
|
|
|
# Careful path - Lissajous representing caution
|
|
path = LissajousCurve(a=3, b=2, delta=PI/4, color=SuryaColors.RELIEF)
|
|
path.set_opacity(0.3)
|
|
|
|
self.play(Create(path), run_time=2)
|
|
|
|
# Soul moves carefully along path
|
|
# Small, hesitant steps
|
|
positions = [LEFT * 3, LEFT * 2, LEFT * 1, ORIGIN, RIGHT * 1, RIGHT * 2, RIGHT * 3]
|
|
|
|
for pos in positions:
|
|
self.play(
|
|
soul.animate.move_to(pos),
|
|
run_time=0.8,
|
|
rate_func=smooth
|
|
)
|
|
# Pause - checking surroundings
|
|
self.wait(0.3)
|
|
|
|
# Tension builds - path becomes more complex
|
|
path2 = LissajousCurve(a=5, b=4, delta=PI/3, color=SuryaColors.RELIEF)
|
|
path2.set_opacity(0.2)
|
|
|
|
self.play(
|
|
Transform(path, path2),
|
|
soul.animate.move_to(ORIGIN),
|
|
run_time=3
|
|
)
|
|
|
|
# Soul retreats to center, bracing
|
|
self.play(
|
|
soul.animate.scale(0.7),
|
|
path.animate.set_opacity(0.1),
|
|
run_time=2
|
|
)
|
|
|
|
self.play(FadeOut(path), run_time=1)
|
|
|
|
|
|
class Track06_NaturesCall(Scene):
|
|
"""Peaceful interlude - "thank you for joining us" """
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.NATURE, radius=0.3)
|
|
self.play(FadeIn(soul), run_time=2)
|
|
|
|
# Fractal tree grows - nature emerging
|
|
tree = FractalTree(depth=6, length=1.5, color=SuryaColors.NATURE)
|
|
tree.shift(DOWN * 2)
|
|
tree.set_opacity(0)
|
|
|
|
self.add(tree)
|
|
self.play(
|
|
tree.animate.set_opacity(0.6),
|
|
run_time=5
|
|
)
|
|
|
|
# Particles like leaves/pollen floating up
|
|
particles = VGroup()
|
|
for _ in range(50):
|
|
p = Dot(
|
|
point=[random.uniform(-4, 4), random.uniform(-3, -2), 0],
|
|
radius=random.uniform(0.02, 0.05),
|
|
color=SuryaColors.NATURE
|
|
)
|
|
p.set_opacity(random.uniform(0.3, 0.7))
|
|
particles.add(p)
|
|
|
|
self.play(FadeIn(particles, lag_ratio=0.05), run_time=2)
|
|
|
|
# Particles float upward peacefully
|
|
self.play(
|
|
*[p.animate.shift(UP * random.uniform(4, 6)) for p in particles],
|
|
run_time=6,
|
|
rate_func=smooth
|
|
)
|
|
|
|
# Golden ratio spiral emerges - nature's mathematics
|
|
spiral = GoldenSpiral(turns=3, color=SuryaColors.NATURE)
|
|
spiral.scale(0.3)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(
|
|
spiral.animate.set_opacity(0.5).scale(2),
|
|
soul.animate.scale(1.3),
|
|
run_time=4
|
|
)
|
|
|
|
# Peaceful conclusion
|
|
self.play(
|
|
tree.animate.set_opacity(0.2),
|
|
spiral.animate.set_opacity(0.2),
|
|
run_time=3
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(tree, spiral, particles),
|
|
run_time=2
|
|
)
|
|
|
|
|
|
class Track07_Dreamcatcher(Scene):
|
|
"""Falling - "falling through the cracks" """
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.DREAM, radius=0.3)
|
|
soul.shift(UP * 3)
|
|
self.play(FadeIn(soul), run_time=1)
|
|
|
|
# Dreamcatcher web - sacred geometry
|
|
web = VGroup()
|
|
for i in range(6):
|
|
angle = i * PI / 3
|
|
line = Line(ORIGIN, 3 * np.array([np.cos(angle), np.sin(angle), 0]))
|
|
line.set_stroke(SuryaColors.DREAM, width=1, opacity=0.4)
|
|
web.add(line)
|
|
|
|
for r in [0.5, 1, 1.5, 2, 2.5]:
|
|
circle = Circle(radius=r, color=SuryaColors.DREAM)
|
|
circle.set_stroke(width=1, opacity=0.3)
|
|
web.add(circle)
|
|
|
|
self.play(Create(web), run_time=3)
|
|
|
|
# Soul begins to fall through the web
|
|
self.play(
|
|
soul.animate.shift(DOWN * 2),
|
|
run_time=2,
|
|
rate_func=rate_functions.ease_in_quad
|
|
)
|
|
|
|
# Passing through layers - web pulses
|
|
for _ in range(3):
|
|
self.play(
|
|
web.animate.scale(1.1).set_opacity(0.2),
|
|
run_time=0.5
|
|
)
|
|
self.play(
|
|
web.animate.scale(1/1.1).set_opacity(0.4),
|
|
run_time=0.5
|
|
)
|
|
self.play(
|
|
soul.animate.shift(DOWN * 1),
|
|
run_time=1.5
|
|
)
|
|
|
|
# Soul falls through completely
|
|
self.play(
|
|
soul.animate.shift(DOWN * 2).scale(0.5).set_opacity(0.4),
|
|
web.animate.set_opacity(0.1),
|
|
run_time=3,
|
|
rate_func=rate_functions.ease_in_quad
|
|
)
|
|
|
|
self.play(FadeOut(web), run_time=1)
|
|
|
|
|
|
class Track08_IDK(Scene):
|
|
"""Confusion - "drugs don't work on me no more" """
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.IDK, radius=0.25)
|
|
soul.set_opacity(0.6)
|
|
self.add(soul)
|
|
|
|
# Strange attractor - chaos and confusion
|
|
attractor = StrangeAttractor(color=SuryaColors.IDK)
|
|
attractor.scale(0.5)
|
|
|
|
self.play(Create(attractor), run_time=5)
|
|
|
|
# Soul wanders erratically
|
|
random_points = [
|
|
np.array([random.uniform(-2, 2), random.uniform(-1.5, 1.5), 0])
|
|
for _ in range(8)
|
|
]
|
|
|
|
for point in random_points:
|
|
self.play(
|
|
soul.animate.move_to(point),
|
|
run_time=0.6,
|
|
rate_func=rate_functions.ease_in_out_sine
|
|
)
|
|
|
|
# Multiple Lissajous curves - mind racing
|
|
curves = VGroup()
|
|
for i in range(4):
|
|
curve = LissajousCurve(
|
|
a=random.randint(2, 5),
|
|
b=random.randint(2, 5),
|
|
delta=random.uniform(0, PI),
|
|
color=SuryaColors.IDK
|
|
)
|
|
curve.scale(0.5 + i * 0.2)
|
|
curve.set_opacity(0.2)
|
|
curves.add(curve)
|
|
|
|
self.play(
|
|
*[Create(c) for c in curves],
|
|
run_time=3,
|
|
lag_ratio=0.2
|
|
)
|
|
|
|
# Everything spins in confusion
|
|
self.play(
|
|
Rotate(curves, PI, about_point=ORIGIN),
|
|
soul.animate.move_to(ORIGIN).scale(0.8),
|
|
run_time=4
|
|
)
|
|
|
|
# Collapse into uncertainty
|
|
self.play(
|
|
FadeOut(attractor, curves),
|
|
soul.animate.scale(0.5).set_opacity(0.4),
|
|
run_time=3
|
|
)
|
|
|
|
|
|
class Track09_WithU(Scene):
|
|
"""THE TURN - "floating through the stars" - Two souls meet"""
|
|
def construct(self):
|
|
# Primary soul
|
|
soul1 = SoulOrb(color=WHITE, radius=0.3)
|
|
soul1.shift(LEFT * 3)
|
|
|
|
# Second soul appears - golden warmth
|
|
soul2 = SoulOrb(color=SuryaColors.WITH_U, radius=0.25)
|
|
soul2.shift(RIGHT * 5)
|
|
soul2.set_opacity(0)
|
|
|
|
self.play(FadeIn(soul1), run_time=2)
|
|
|
|
# Stars begin appearing
|
|
stars = VGroup()
|
|
for _ in range(80):
|
|
star = Dot(
|
|
point=[random.uniform(-7, 7), random.uniform(-4, 4), 0],
|
|
radius=random.uniform(0.01, 0.04),
|
|
color=WHITE
|
|
)
|
|
star.set_opacity(0)
|
|
stars.add(star)
|
|
|
|
self.add(stars)
|
|
self.play(
|
|
*[s.animate.set_opacity(random.uniform(0.3, 0.9)) for s in stars],
|
|
run_time=3,
|
|
lag_ratio=0.02
|
|
)
|
|
|
|
# Second soul emerges from the stars
|
|
self.play(
|
|
soul2.animate.set_opacity(1).shift(LEFT * 2),
|
|
run_time=3
|
|
)
|
|
|
|
# Souls move toward each other
|
|
self.play(
|
|
soul1.animate.shift(RIGHT * 1.5),
|
|
soul2.animate.shift(LEFT * 1.5),
|
|
run_time=3,
|
|
rate_func=smooth
|
|
)
|
|
|
|
# They begin orbiting each other - binary star system
|
|
orbit_center = (soul1.get_center() + soul2.get_center()) / 2
|
|
|
|
# Golden spiral emerges between them
|
|
spiral = GoldenSpiral(turns=4, color=SuryaColors.WITH_U)
|
|
spiral.scale(0.4)
|
|
spiral.move_to(orbit_center)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(spiral.animate.set_opacity(0.5), run_time=2)
|
|
|
|
# Orbiting animation
|
|
for _ in range(2):
|
|
self.play(
|
|
Rotate(soul1, PI, about_point=orbit_center),
|
|
Rotate(soul2, PI, about_point=orbit_center),
|
|
Rotate(spiral, PI/2, about_point=orbit_center),
|
|
run_time=4,
|
|
rate_func=smooth
|
|
)
|
|
|
|
# Souls move closer, colors blend
|
|
self.play(
|
|
soul1.animate.scale(1.2).move_to(orbit_center + LEFT * 0.3),
|
|
soul2.animate.scale(1.2).move_to(orbit_center + RIGHT * 0.3),
|
|
spiral.animate.scale(1.5),
|
|
run_time=3
|
|
)
|
|
|
|
# Radiant climax - sacred geometry
|
|
flower = FlowerOfLife(rings=2, radius=0.5, color=SuryaColors.WITH_U)
|
|
flower.move_to(orbit_center)
|
|
flower.set_opacity(0)
|
|
|
|
self.play(
|
|
flower.animate.set_opacity(0.4).scale(1.2),
|
|
stars.animate.set_opacity(0.8),
|
|
run_time=3
|
|
)
|
|
|
|
# Final pulse of connection
|
|
self.play(
|
|
soul1.animate.scale(1.3),
|
|
soul2.animate.scale(1.3),
|
|
flower.animate.scale(1.3),
|
|
run_time=1.5
|
|
)
|
|
self.play(
|
|
soul1.animate.scale(1/1.3),
|
|
soul2.animate.scale(1/1.3),
|
|
flower.animate.scale(1/1.3),
|
|
run_time=1.5
|
|
)
|
|
|
|
# Gentle fade maintaining connection
|
|
self.play(
|
|
flower.animate.set_opacity(0.1),
|
|
spiral.animate.set_opacity(0.2),
|
|
stars.animate.set_opacity(0.3),
|
|
run_time=3
|
|
)
|
|
|
|
|
|
class Track10_PoorYouPoorMe(Scene):
|
|
"""Bittersweet - "you left your cardigan on my bed" """
|
|
def construct(self):
|
|
# Two souls, but one is fading
|
|
soul1 = SoulOrb(color=SuryaColors.POOR, radius=0.3)
|
|
soul2 = SoulOrb(color=SuryaColors.WITH_U, radius=0.25)
|
|
soul2.shift(RIGHT * 1.5)
|
|
|
|
self.play(FadeIn(soul1, soul2), run_time=2)
|
|
|
|
# Spirograph of intertwined paths
|
|
spiro = Spirograph(R=3, r=1.8, d=1.5, color=SuryaColors.POOR)
|
|
spiro.scale(0.6)
|
|
spiro.set_opacity(0.4)
|
|
|
|
self.play(Create(spiro), run_time=4)
|
|
|
|
# One soul begins to drift away
|
|
self.play(
|
|
soul2.animate.shift(RIGHT * 2).set_opacity(0.6),
|
|
run_time=3
|
|
)
|
|
|
|
# Lingering warmth - particles like memories
|
|
memories = VGroup()
|
|
for _ in range(30):
|
|
m = Dot(
|
|
point=soul2.get_center() + np.array([
|
|
random.uniform(-1, 1),
|
|
random.uniform(-1, 1),
|
|
0
|
|
]),
|
|
radius=0.03,
|
|
color=SuryaColors.WITH_U
|
|
)
|
|
m.set_opacity(0.5)
|
|
memories.add(m)
|
|
|
|
self.play(FadeIn(memories), run_time=2)
|
|
|
|
# Memories drift toward remaining soul
|
|
self.play(
|
|
*[m.animate.move_to(
|
|
soul1.get_center() + np.array([random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5), 0])
|
|
) for m in memories],
|
|
soul2.animate.shift(RIGHT * 2).set_opacity(0.2),
|
|
run_time=4
|
|
)
|
|
|
|
# Bittersweet pulse
|
|
self.play(
|
|
soul1.animate.scale(1.2),
|
|
spiro.animate.set_opacity(0.6),
|
|
run_time=2
|
|
)
|
|
self.play(
|
|
soul1.animate.scale(1/1.2),
|
|
spiro.animate.set_opacity(0.3),
|
|
run_time=2
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(soul2, memories, spiro),
|
|
run_time=3
|
|
)
|
|
|
|
|
|
class Track11_Wait4U(Scene):
|
|
"""Longing - waiting"""
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.WAIT, radius=0.3)
|
|
self.play(FadeIn(soul), run_time=2)
|
|
|
|
# Concentric circles - time passing, waiting
|
|
circles = VGroup()
|
|
for i in range(8):
|
|
c = Circle(radius=0.5 + i * 0.4, color=SuryaColors.WAIT)
|
|
c.set_stroke(width=1, opacity=0.3 - i * 0.03)
|
|
circles.add(c)
|
|
|
|
self.play(
|
|
*[Create(c) for c in circles],
|
|
run_time=3,
|
|
lag_ratio=0.3
|
|
)
|
|
|
|
# Slow pulsing - patient waiting
|
|
for _ in range(3):
|
|
self.play(
|
|
circles.animate.scale(1.1),
|
|
soul.animate.scale(0.9),
|
|
run_time=2
|
|
)
|
|
self.play(
|
|
circles.animate.scale(1/1.1),
|
|
soul.animate.scale(1/0.9),
|
|
run_time=2
|
|
)
|
|
|
|
# Spiral of longing
|
|
spiral = GoldenSpiral(turns=5, color=SuryaColors.WAIT)
|
|
spiral.scale(0.3)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(
|
|
spiral.animate.set_opacity(0.4).scale(2),
|
|
circles.animate.set_opacity(0.1),
|
|
run_time=4
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(circles, spiral),
|
|
run_time=2
|
|
)
|
|
|
|
|
|
class Track12_RunToU(Scene):
|
|
"""Urgency - "if the sky was falling I would run to you" """
|
|
def construct(self):
|
|
# Soul starts at edge, urgent energy
|
|
soul = SoulOrb(color=SuryaColors.RUN, radius=0.3)
|
|
soul.shift(LEFT * 5)
|
|
|
|
# Target - the other soul (faint in distance)
|
|
target = SoulOrb(color=SuryaColors.WITH_U, radius=0.25)
|
|
target.shift(RIGHT * 5)
|
|
target.set_opacity(0.3)
|
|
|
|
self.play(FadeIn(soul, target), run_time=1)
|
|
|
|
# Streaking particles - speed lines
|
|
speed_lines = VGroup()
|
|
for _ in range(20):
|
|
y = random.uniform(-3, 3)
|
|
line = Line(LEFT * 7, RIGHT * 7, color=SuryaColors.RUN)
|
|
line.shift(UP * y)
|
|
line.set_stroke(width=1, opacity=0.2)
|
|
speed_lines.add(line)
|
|
|
|
self.play(
|
|
*[Create(l) for l in speed_lines],
|
|
run_time=1
|
|
)
|
|
|
|
# Running motion - urgent spirograph
|
|
spiro = Spirograph(R=2, r=0.3, d=2, color=SuryaColors.RUN)
|
|
spiro.scale(0.3)
|
|
spiro.move_to(soul.get_center())
|
|
spiro.set_opacity(0)
|
|
|
|
# Urgent movement toward target
|
|
self.play(
|
|
soul.animate.shift(RIGHT * 4),
|
|
spiro.animate.shift(RIGHT * 4).set_opacity(0.5),
|
|
target.animate.set_opacity(0.6),
|
|
Rotate(spiro, TAU * 2, about_point=soul.get_center()),
|
|
run_time=3,
|
|
rate_func=rate_functions.ease_in_quad
|
|
)
|
|
|
|
# Final push
|
|
self.play(
|
|
soul.animate.shift(RIGHT * 4),
|
|
target.animate.shift(LEFT * 1).set_opacity(1),
|
|
speed_lines.animate.set_opacity(0.5),
|
|
run_time=2,
|
|
rate_func=rate_functions.ease_out_quad
|
|
)
|
|
|
|
# Collision/embrace - burst of energy
|
|
burst = VGroup()
|
|
for i in range(12):
|
|
angle = i * PI / 6
|
|
ray = Line(ORIGIN, 2 * np.array([np.cos(angle), np.sin(angle), 0]))
|
|
ray.set_stroke(SuryaColors.RUN, width=3, opacity=0.6)
|
|
burst.add(ray)
|
|
burst.move_to(RIGHT * 2)
|
|
|
|
self.play(
|
|
FadeIn(burst, scale=0.5),
|
|
soul.animate.move_to(RIGHT * 2).scale(1.3),
|
|
target.animate.move_to(RIGHT * 2).scale(1.3),
|
|
run_time=1
|
|
)
|
|
|
|
self.play(
|
|
burst.animate.scale(2).set_opacity(0),
|
|
FadeOut(speed_lines, spiro),
|
|
run_time=2
|
|
)
|
|
|
|
|
|
class Track13_Medications(Scene):
|
|
"""Struggle - "raging in my head" """
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.MEDS, radius=0.3)
|
|
self.play(FadeIn(soul), run_time=1)
|
|
|
|
# Strange attractor - chaos in the mind
|
|
attractor = StrangeAttractor(color=SuryaColors.MEDS)
|
|
attractor.scale(0.6)
|
|
|
|
self.play(Create(attractor), run_time=3)
|
|
|
|
# Aggressive spirograph - war in the head
|
|
spiros = VGroup()
|
|
for i in range(3):
|
|
s = Spirograph(R=2 + i, r=0.5 + i * 0.3, d=1.5, color=SuryaColors.MEDS)
|
|
s.scale(0.4)
|
|
s.set_opacity(0.3)
|
|
s.rotate(i * PI / 3)
|
|
spiros.add(s)
|
|
|
|
self.play(
|
|
*[Create(s) for s in spiros],
|
|
run_time=3
|
|
)
|
|
|
|
# Soul shakes violently
|
|
original_pos = soul.get_center()
|
|
for _ in range(6):
|
|
offset = np.array([random.uniform(-0.3, 0.3), random.uniform(-0.3, 0.3), 0])
|
|
self.play(
|
|
soul.animate.move_to(original_pos + offset),
|
|
run_time=0.15
|
|
)
|
|
self.play(soul.animate.move_to(original_pos), run_time=0.2)
|
|
|
|
# Red pulses - pain
|
|
for _ in range(3):
|
|
pulse = Circle(radius=0.5, color=SuryaColors.MEDS)
|
|
pulse.set_stroke(width=3, opacity=0.8)
|
|
self.add(pulse)
|
|
self.play(
|
|
pulse.animate.scale(4).set_opacity(0),
|
|
spiros.animate.rotate(PI/6),
|
|
run_time=1
|
|
)
|
|
self.remove(pulse)
|
|
|
|
# Struggle continues
|
|
self.play(
|
|
Rotate(attractor, PI, about_point=ORIGIN),
|
|
spiros.animate.set_opacity(0.5),
|
|
soul.animate.scale(0.8),
|
|
run_time=3
|
|
)
|
|
|
|
# Eventual exhaustion
|
|
self.play(
|
|
attractor.animate.set_opacity(0.2),
|
|
spiros.animate.set_opacity(0.1).scale(0.8),
|
|
soul.animate.scale(0.7).set_opacity(0.6),
|
|
run_time=3
|
|
)
|
|
|
|
self.play(FadeOut(attractor, spiros), run_time=2)
|
|
|
|
|
|
class Track14_Hollow(Scene):
|
|
"""Resolution - "you took my sorrow and flew it to the moon" """
|
|
def construct(self):
|
|
# Soul begins small and humble
|
|
soul = SoulOrb(color=SuryaColors.HOLLOW, radius=0.2)
|
|
soul.set_opacity(0.5)
|
|
self.play(FadeIn(soul), run_time=2)
|
|
|
|
# Stars return - hope
|
|
stars = VGroup()
|
|
for _ in range(100):
|
|
star = Dot(
|
|
point=[random.uniform(-7, 7), random.uniform(-4, 4), 0],
|
|
radius=random.uniform(0.01, 0.05),
|
|
color=WHITE
|
|
)
|
|
star.set_opacity(0)
|
|
stars.add(star)
|
|
|
|
self.add(stars)
|
|
self.play(
|
|
*[s.animate.set_opacity(random.uniform(0.2, 0.8)) for s in stars],
|
|
soul.animate.set_opacity(0.8).scale(1.2),
|
|
run_time=4,
|
|
lag_ratio=0.01
|
|
)
|
|
|
|
# Golden spiral - resolution through golden ratio
|
|
spiral = GoldenSpiral(turns=5, color=SuryaColors.HOLLOW)
|
|
spiral.scale(0.2)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(
|
|
spiral.animate.set_opacity(0.6).scale(3),
|
|
run_time=5
|
|
)
|
|
|
|
# Moon appears (large circle in upper area)
|
|
moon = Circle(radius=1.5, color=SuryaColors.HOLLOW)
|
|
moon.set_fill(SuryaColors.HOLLOW, opacity=0.1)
|
|
moon.set_stroke(SuryaColors.HOLLOW, width=2, opacity=0.5)
|
|
moon.shift(UP * 2 + RIGHT * 3)
|
|
|
|
self.play(FadeIn(moon, scale=0.5), run_time=3)
|
|
|
|
# Sorrow (dark particles) flying to the moon
|
|
sorrow = VGroup()
|
|
for _ in range(20):
|
|
s = Dot(
|
|
point=soul.get_center() + np.array([
|
|
random.uniform(-0.5, 0.5),
|
|
random.uniform(-0.5, 0.5),
|
|
0
|
|
]),
|
|
radius=0.04,
|
|
color=GREY
|
|
)
|
|
sorrow.add(s)
|
|
|
|
self.play(FadeIn(sorrow), run_time=1)
|
|
|
|
# Sorrow flies to moon
|
|
self.play(
|
|
*[s.animate.move_to(moon.get_center() + np.array([
|
|
random.uniform(-0.5, 0.5),
|
|
random.uniform(-0.5, 0.5),
|
|
0
|
|
])).set_opacity(0) for s in sorrow],
|
|
soul.animate.scale(1.3).set_opacity(1),
|
|
run_time=4
|
|
)
|
|
|
|
# Sacred geometry finale
|
|
flower = FlowerOfLife(rings=2, radius=0.6, color=SuryaColors.HOLLOW)
|
|
flower.set_opacity(0)
|
|
|
|
self.play(
|
|
flower.animate.set_opacity(0.4),
|
|
spiral.animate.set_opacity(0.8),
|
|
run_time=3
|
|
)
|
|
|
|
# Final radiant pulse
|
|
self.play(
|
|
soul.animate.scale(1.5),
|
|
flower.animate.scale(1.3),
|
|
spiral.animate.scale(1.2),
|
|
stars.animate.set_opacity(1),
|
|
run_time=2
|
|
)
|
|
|
|
# Peaceful hold
|
|
self.wait(3)
|
|
|
|
# Gentle fade to completion
|
|
self.play(
|
|
soul.animate.scale(2).set_opacity(0.5),
|
|
flower.animate.set_opacity(0.2),
|
|
spiral.animate.set_opacity(0.3),
|
|
stars.animate.set_opacity(0.5),
|
|
moon.animate.set_opacity(0.3),
|
|
run_time=4
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# MAIN JOURNEY - FULL COMPOSITION
|
|
# ============================================================================
|
|
|
|
class SuryaJourney(Scene):
|
|
"""
|
|
SURYA — The Complete Journey Through Feeling
|
|
14 tracks of mathematical beauty
|
|
"""
|
|
|
|
def construct(self):
|
|
# Persistent elements
|
|
self.soul = SoulOrb(color=WHITE, radius=0.3)
|
|
self.stars = self.create_stars()
|
|
self.particles = self.create_particles()
|
|
|
|
# Journey begins
|
|
self.intro()
|
|
self.track_01_skin()
|
|
self.track_02_u_saved_me()
|
|
self.track_03_nothing()
|
|
self.track_04_sweet_relief()
|
|
self.track_05_tiptoe()
|
|
self.track_06_natures_call()
|
|
self.track_07_dreamcatcher()
|
|
self.track_08_idk()
|
|
self.track_09_with_u()
|
|
self.track_10_poor_you_poor_me()
|
|
self.track_11_wait_4_u()
|
|
self.track_12_run_to_u()
|
|
self.track_13_medications()
|
|
self.track_14_hollow()
|
|
self.finale()
|
|
|
|
def create_stars(self):
|
|
stars = VGroup()
|
|
for _ in range(100):
|
|
star = Dot(
|
|
point=[random.uniform(-7, 7), random.uniform(-4, 4), 0],
|
|
radius=random.uniform(0.01, 0.05),
|
|
color=WHITE
|
|
)
|
|
star.set_opacity(0)
|
|
stars.add(star)
|
|
return stars
|
|
|
|
def create_particles(self):
|
|
particles = VGroup()
|
|
for _ in range(60):
|
|
p = Dot(
|
|
point=[random.uniform(-7, 7), random.uniform(-5, -4), 0],
|
|
radius=random.uniform(0.02, 0.05),
|
|
color=WHITE
|
|
)
|
|
p.set_opacity(0)
|
|
particles.add(p)
|
|
return particles
|
|
|
|
def transition_color(self, new_color, run_time=2):
|
|
"""Smoothly transition soul and ambient elements to new color"""
|
|
new_soul = SoulOrb(color=new_color, radius=self.soul.core.radius * 5)
|
|
new_soul.move_to(self.soul.get_center())
|
|
|
|
self.play(
|
|
Transform(self.soul, new_soul),
|
|
run_time=run_time
|
|
)
|
|
|
|
def show_title(self, number, title, lyric="", color=WHITE):
|
|
"""Display track title"""
|
|
num_text = Text(f"{number:02d}", font_size=24, color=color)
|
|
num_text.set_opacity(0.4)
|
|
num_text.to_edge(UP, buff=1)
|
|
|
|
title_text = Text(title, font_size=72, color=color, slant=ITALIC)
|
|
title_text.next_to(num_text, DOWN, buff=0.5)
|
|
|
|
lyric_text = Text(lyric, font_size=28, color=color, slant=ITALIC)
|
|
lyric_text.set_opacity(0.6)
|
|
lyric_text.next_to(title_text, DOWN, buff=0.5)
|
|
|
|
group = VGroup(num_text, title_text, lyric_text)
|
|
|
|
self.play(
|
|
FadeIn(group, shift=UP * 0.5),
|
|
run_time=1.5
|
|
)
|
|
self.wait(1)
|
|
self.play(
|
|
FadeOut(group, shift=UP * 0.5),
|
|
run_time=1
|
|
)
|
|
|
|
# =========== INTRO ===========
|
|
def intro(self):
|
|
# Title sequence
|
|
surya_text = Text("SURYA", font_size=120, color=WHITE)
|
|
sub_text = Text("a journey through feeling", font_size=32, color=WHITE)
|
|
sub_text.set_opacity(0.5)
|
|
sub_text.next_to(surya_text, DOWN, buff=0.5)
|
|
artist_text = Text("DAS", font_size=20, color=WHITE)
|
|
artist_text.set_opacity(0.3)
|
|
artist_text.next_to(sub_text, DOWN, buff=1)
|
|
|
|
self.play(
|
|
FadeIn(surya_text, scale=0.9),
|
|
run_time=3
|
|
)
|
|
self.play(FadeIn(sub_text, artist_text), run_time=2)
|
|
self.wait(2)
|
|
|
|
# Soul emerges
|
|
self.add(self.stars, self.particles)
|
|
self.play(
|
|
FadeIn(self.soul, scale=0.5),
|
|
FadeOut(surya_text, sub_text, artist_text),
|
|
run_time=3
|
|
)
|
|
self.wait(1)
|
|
|
|
# =========== TRACK 01: SKIN ===========
|
|
def track_01_skin(self):
|
|
color = SuryaColors.SKIN
|
|
self.show_title(1, "skin", '"don\'t fit in my own skin"', color)
|
|
|
|
# Transform soul color
|
|
self.transition_color(color)
|
|
|
|
# Morphing spirograph - identity struggle
|
|
spiro1 = Spirograph(R=3, r=0.7, d=1.2, color=color)
|
|
spiro2 = Spirograph(R=3, r=1.3, d=0.8, color=color)
|
|
spiro1.scale(0.8)
|
|
spiro2.scale(0.8)
|
|
|
|
self.play(Create(spiro1), run_time=3)
|
|
|
|
# Soul pulsates uncomfortably
|
|
self.play(
|
|
self.soul.animate.scale(0.7),
|
|
run_time=1
|
|
)
|
|
self.play(
|
|
self.soul.animate.scale(1.4),
|
|
run_time=1
|
|
)
|
|
|
|
# Transform identity
|
|
self.play(
|
|
Transform(spiro1, spiro2),
|
|
self.soul.animate.scale(0.8),
|
|
run_time=4
|
|
)
|
|
|
|
# Particles of self floating
|
|
temp_particles = ParticleField(40, color=color)
|
|
self.play(FadeIn(temp_particles, lag_ratio=0.05), run_time=2)
|
|
|
|
self.play(
|
|
temp_particles.animate.shift(UP * 2).set_opacity(0),
|
|
run_time=3
|
|
)
|
|
|
|
# Fade out
|
|
self.play(
|
|
FadeOut(spiro1, temp_particles),
|
|
self.soul.animate.scale(0.8),
|
|
run_time=2
|
|
)
|
|
|
|
# =========== TRACK 02: U SAVED ME ===========
|
|
def track_02_u_saved_me(self):
|
|
color = SuryaColors.SAVED
|
|
self.show_title(2, "u saved me", '"you saved me from my broken soul"', color)
|
|
|
|
# Soul fragments scattered
|
|
fragments = VGroup(*[
|
|
Dot(point=[random.uniform(-2, 2), random.uniform(-2, 2), 0],
|
|
radius=0.05, color=color)
|
|
for _ in range(25)
|
|
])
|
|
|
|
self.play(
|
|
FadeIn(fragments, lag_ratio=0.05),
|
|
self.soul.animate.set_opacity(0.3),
|
|
run_time=2
|
|
)
|
|
|
|
# Fragments coalesce
|
|
self.play(
|
|
*[f.animate.move_to(self.soul.get_center()) for f in fragments],
|
|
run_time=3
|
|
)
|
|
self.transition_color(color)
|
|
|
|
self.play(
|
|
FadeOut(fragments),
|
|
self.soul.animate.set_opacity(1).scale(1.3),
|
|
run_time=2
|
|
)
|
|
|
|
# Sacred geometry - spiritual awakening
|
|
flower = FlowerOfLife(rings=2, radius=0.6, color=color)
|
|
flower.set_opacity(0)
|
|
self.add(flower)
|
|
|
|
# Hopeful spirograph
|
|
spiro = Spirograph(R=4, r=1.5, d=2, color=color)
|
|
spiro.scale(0.7)
|
|
spiro.set_opacity(0)
|
|
self.add(spiro)
|
|
|
|
self.play(
|
|
flower.animate.set_opacity(0.4),
|
|
spiro.animate.set_opacity(0.5),
|
|
run_time=4
|
|
)
|
|
|
|
# Harmony pulse
|
|
self.play(
|
|
self.soul.animate.scale(1.2),
|
|
flower.animate.scale(1.1),
|
|
run_time=1.5
|
|
)
|
|
self.play(
|
|
self.soul.animate.scale(1/1.2),
|
|
flower.animate.scale(1/1.1),
|
|
run_time=1.5
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(flower, spiro),
|
|
run_time=2
|
|
)
|
|
|
|
# =========== TRACK 03: NOTHING ===========
|
|
def track_03_nothing(self):
|
|
color = SuryaColors.NOTHING
|
|
self.show_title(3, "nothing", '"I lost my heart, now I feel nothing"', color)
|
|
|
|
self.transition_color(color)
|
|
|
|
# Voronoi fragmentation
|
|
voronoi = VoronoiFragments(num_points=25, color=color)
|
|
|
|
self.play(
|
|
self.soul.animate.scale(0.6),
|
|
run_time=2
|
|
)
|
|
|
|
self.play(Create(voronoi), run_time=4)
|
|
|
|
# Grey void
|
|
grey_circle = Circle(radius=3, color=color)
|
|
grey_circle.set_stroke(width=1, opacity=0.2)
|
|
grey_circle.set_fill(color, opacity=0.03)
|
|
|
|
self.play(
|
|
FadeIn(grey_circle),
|
|
self.soul.animate.scale(0.5).set_opacity(0.3),
|
|
voronoi.animate.set_opacity(0.1),
|
|
run_time=4
|
|
)
|
|
|
|
# Hollow pulsing
|
|
for _ in range(2):
|
|
self.play(
|
|
grey_circle.animate.scale(1.1),
|
|
run_time=1.5,
|
|
rate_func=there_and_back
|
|
)
|
|
|
|
self.wait(1)
|
|
|
|
# Scatter into void
|
|
self.play(
|
|
FadeOut(voronoi, shift=UP),
|
|
FadeOut(grey_circle),
|
|
self.soul.animate.set_opacity(0.2),
|
|
run_time=3
|
|
)
|
|
|
|
# =========== TRACK 04: SWEET RELIEF ===========
|
|
def track_04_sweet_relief(self):
|
|
color = SuryaColors.RELIEF
|
|
self.show_title(4, "sweet relief", '"seeing ghosts around my throat"', color)
|
|
|
|
self.transition_color(color)
|
|
self.play(self.soul.animate.set_opacity(0.8).scale(1.5), run_time=1)
|
|
|
|
# Ghost spirals
|
|
ghosts = VGroup()
|
|
for i in range(5):
|
|
ghost = Spirograph(R=2 + i * 0.3, r=0.5, d=1 + i * 0.2, color=color)
|
|
ghost.scale(0.5)
|
|
ghost.set_opacity(0.15 + i * 0.05)
|
|
ghost.rotate(i * PI / 5)
|
|
ghosts.add(ghost)
|
|
|
|
self.play(
|
|
*[Create(g) for g in ghosts],
|
|
run_time=4,
|
|
lag_ratio=0.3
|
|
)
|
|
|
|
# Ghosts circle menacingly
|
|
self.play(
|
|
Rotate(ghosts, PI, about_point=ORIGIN),
|
|
self.soul.animate.scale(0.8),
|
|
run_time=4
|
|
)
|
|
|
|
# Soul struggles
|
|
self.play(self.soul.animate.shift(UP * 0.3), run_time=0.4)
|
|
self.play(self.soul.animate.shift(DOWN * 0.3), run_time=0.4)
|
|
|
|
# Ghosts close in
|
|
self.play(
|
|
ghosts.animate.scale(0.7),
|
|
run_time=3
|
|
)
|
|
|
|
# Brief relief
|
|
self.play(
|
|
ghosts.animate.scale(1.5).set_opacity(0.05),
|
|
self.soul.animate.scale(1.2).set_opacity(1),
|
|
run_time=2
|
|
)
|
|
|
|
self.play(FadeOut(ghosts), run_time=2)
|
|
|
|
# =========== TRACK 05: TIPTOE ===========
|
|
def track_05_tiptoe(self):
|
|
color = SuryaColors.RELIEF
|
|
self.show_title(5, "tiptoe", "", color)
|
|
|
|
# Lissajous path
|
|
path = LissajousCurve(a=3, b=2, delta=PI/4, color=color)
|
|
path.set_opacity(0.3)
|
|
|
|
self.play(Create(path), run_time=2)
|
|
|
|
# Careful movement
|
|
self.soul.move_to(LEFT * 3)
|
|
positions = [LEFT * 2, LEFT * 1, ORIGIN, RIGHT * 1, RIGHT * 2]
|
|
|
|
for pos in positions:
|
|
self.play(
|
|
self.soul.animate.move_to(pos),
|
|
run_time=0.8
|
|
)
|
|
self.wait(0.2)
|
|
|
|
# Tension path
|
|
path2 = LissajousCurve(a=5, b=4, delta=PI/3, color=color)
|
|
path2.set_opacity(0.2)
|
|
|
|
self.play(
|
|
Transform(path, path2),
|
|
self.soul.animate.move_to(ORIGIN).scale(0.8),
|
|
run_time=3
|
|
)
|
|
|
|
self.play(FadeOut(path), run_time=1)
|
|
|
|
# =========== TRACK 06: NATURE'S CALL ===========
|
|
def track_06_natures_call(self):
|
|
color = SuryaColors.NATURE
|
|
self.show_title(6, "nature's call", '"thank you for joining us"', color)
|
|
|
|
self.transition_color(color)
|
|
self.play(self.soul.animate.scale(1.3), run_time=1)
|
|
|
|
# Fractal tree grows
|
|
tree = FractalTree(depth=6, length=1.5, color=color)
|
|
tree.shift(DOWN * 2)
|
|
tree.set_opacity(0)
|
|
self.add(tree)
|
|
|
|
self.play(
|
|
tree.animate.set_opacity(0.5),
|
|
run_time=5
|
|
)
|
|
|
|
# Nature particles float up
|
|
nature_particles = VGroup()
|
|
for _ in range(40):
|
|
p = Dot(
|
|
point=[random.uniform(-4, 4), random.uniform(-3, -2), 0],
|
|
radius=random.uniform(0.02, 0.05),
|
|
color=color
|
|
)
|
|
p.set_opacity(random.uniform(0.3, 0.6))
|
|
nature_particles.add(p)
|
|
|
|
self.play(FadeIn(nature_particles, lag_ratio=0.03), run_time=2)
|
|
|
|
self.play(
|
|
*[p.animate.shift(UP * random.uniform(4, 6)) for p in nature_particles],
|
|
run_time=5
|
|
)
|
|
|
|
# Golden spiral - nature's math
|
|
spiral = GoldenSpiral(turns=3, color=color)
|
|
spiral.scale(0.3)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(
|
|
spiral.animate.set_opacity(0.5).scale(2),
|
|
run_time=4
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(tree, spiral, nature_particles),
|
|
run_time=2
|
|
)
|
|
|
|
# =========== TRACK 07: DREAMCATCHER ===========
|
|
def track_07_dreamcatcher(self):
|
|
color = SuryaColors.DREAM
|
|
self.show_title(7, "dreamcatcher", '"falling through the cracks"', color)
|
|
|
|
self.transition_color(color)
|
|
self.soul.move_to(UP * 2.5)
|
|
|
|
# Dreamcatcher web
|
|
web = VGroup()
|
|
for i in range(6):
|
|
angle = i * PI / 3
|
|
line = Line(ORIGIN, 3 * np.array([np.cos(angle), np.sin(angle), 0]))
|
|
line.set_stroke(color, width=1, opacity=0.4)
|
|
web.add(line)
|
|
|
|
for r in [0.5, 1, 1.5, 2, 2.5]:
|
|
circle = Circle(radius=r, color=color)
|
|
circle.set_stroke(width=1, opacity=0.3)
|
|
web.add(circle)
|
|
|
|
self.play(Create(web), run_time=3)
|
|
|
|
# Soul falls through
|
|
self.play(
|
|
self.soul.animate.shift(DOWN * 2),
|
|
run_time=2,
|
|
rate_func=rate_functions.ease_in_quad
|
|
)
|
|
|
|
# Passing through layers
|
|
for _ in range(2):
|
|
self.play(
|
|
web.animate.scale(1.1).set_opacity(0.2),
|
|
run_time=0.5
|
|
)
|
|
self.play(
|
|
web.animate.scale(1/1.1).set_opacity(0.4),
|
|
run_time=0.5
|
|
)
|
|
self.play(
|
|
self.soul.animate.shift(DOWN * 1.2),
|
|
run_time=1.5
|
|
)
|
|
|
|
# Falls through completely
|
|
self.play(
|
|
self.soul.animate.move_to(ORIGIN).scale(0.7).set_opacity(0.5),
|
|
web.animate.set_opacity(0.1),
|
|
run_time=3
|
|
)
|
|
|
|
self.play(FadeOut(web), run_time=1)
|
|
|
|
# =========== TRACK 08: IDK ===========
|
|
def track_08_idk(self):
|
|
color = SuryaColors.IDK
|
|
self.show_title(8, "idk", '"drugs don\'t work on me no more"', color)
|
|
|
|
self.transition_color(color)
|
|
self.play(self.soul.animate.set_opacity(0.7).scale(1.2), run_time=1)
|
|
|
|
# Strange attractor - chaos
|
|
attractor = StrangeAttractor(color=color)
|
|
attractor.scale(0.5)
|
|
|
|
self.play(Create(attractor), run_time=4)
|
|
|
|
# Erratic wandering
|
|
for _ in range(6):
|
|
point = np.array([random.uniform(-1.5, 1.5), random.uniform(-1, 1), 0])
|
|
self.play(
|
|
self.soul.animate.move_to(point),
|
|
run_time=0.5
|
|
)
|
|
|
|
# Multiple confused curves
|
|
curves = VGroup()
|
|
for i in range(3):
|
|
curve = LissajousCurve(
|
|
a=random.randint(2, 5),
|
|
b=random.randint(2, 5),
|
|
delta=random.uniform(0, PI),
|
|
color=color
|
|
)
|
|
curve.scale(0.5 + i * 0.2)
|
|
curve.set_opacity(0.2)
|
|
curves.add(curve)
|
|
|
|
self.play(
|
|
*[Create(c) for c in curves],
|
|
run_time=3,
|
|
lag_ratio=0.2
|
|
)
|
|
|
|
# Spin in confusion
|
|
self.play(
|
|
Rotate(curves, PI, about_point=ORIGIN),
|
|
self.soul.animate.move_to(ORIGIN).scale(0.8),
|
|
run_time=4
|
|
)
|
|
|
|
# Collapse
|
|
self.play(
|
|
FadeOut(attractor, curves),
|
|
self.soul.animate.scale(0.7).set_opacity(0.5),
|
|
run_time=3
|
|
)
|
|
|
|
# =========== TRACK 09: WITH U (THE TURN) ===========
|
|
def track_09_with_u(self):
|
|
color = SuryaColors.WITH_U
|
|
self.show_title(9, "with u", '"floating through the stars"', color)
|
|
|
|
# This is THE TURN - second soul appears
|
|
self.play(self.soul.animate.shift(LEFT * 2).set_opacity(1).scale(1.3), run_time=2)
|
|
self.transition_color(WHITE)
|
|
|
|
# Stars light up
|
|
self.play(
|
|
*[s.animate.set_opacity(random.uniform(0.3, 0.9)) for s in self.stars],
|
|
run_time=3,
|
|
lag_ratio=0.01
|
|
)
|
|
|
|
# Second soul emerges - golden
|
|
soul2 = SoulOrb(color=color, radius=0.25)
|
|
soul2.shift(RIGHT * 5)
|
|
soul2.set_opacity(0)
|
|
self.add(soul2)
|
|
|
|
self.play(
|
|
soul2.animate.set_opacity(1).shift(LEFT * 3),
|
|
run_time=3
|
|
)
|
|
|
|
# Souls approach each other
|
|
self.play(
|
|
self.soul.animate.shift(RIGHT * 0.5),
|
|
soul2.animate.shift(LEFT * 0.5),
|
|
run_time=2
|
|
)
|
|
|
|
# Golden spiral between them
|
|
orbit_center = (self.soul.get_center() + soul2.get_center()) / 2
|
|
spiral = GoldenSpiral(turns=4, color=color)
|
|
spiral.scale(0.4)
|
|
spiral.move_to(orbit_center)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(spiral.animate.set_opacity(0.6), run_time=2)
|
|
|
|
# Binary orbit
|
|
for _ in range(2):
|
|
self.play(
|
|
Rotate(self.soul, PI, about_point=orbit_center),
|
|
Rotate(soul2, PI, about_point=orbit_center),
|
|
Rotate(spiral, PI/2, about_point=orbit_center),
|
|
run_time=4
|
|
)
|
|
|
|
# Closer, colors blend
|
|
self.play(
|
|
self.soul.animate.scale(1.2).move_to(orbit_center + LEFT * 0.3),
|
|
soul2.animate.scale(1.2).move_to(orbit_center + RIGHT * 0.3),
|
|
spiral.animate.scale(1.5),
|
|
run_time=3
|
|
)
|
|
|
|
# Sacred geometry climax
|
|
flower = FlowerOfLife(rings=2, radius=0.5, color=color)
|
|
flower.move_to(orbit_center)
|
|
flower.set_opacity(0)
|
|
|
|
self.play(
|
|
flower.animate.set_opacity(0.4).scale(1.2),
|
|
self.stars.animate.set_opacity(0.9),
|
|
run_time=3
|
|
)
|
|
|
|
# Peak pulse
|
|
self.play(
|
|
self.soul.animate.scale(1.3),
|
|
soul2.animate.scale(1.3),
|
|
flower.animate.scale(1.3),
|
|
run_time=1.5
|
|
)
|
|
self.play(
|
|
self.soul.animate.scale(1/1.3),
|
|
soul2.animate.scale(1/1.3),
|
|
flower.animate.scale(1/1.3),
|
|
run_time=1.5
|
|
)
|
|
|
|
# Store soul2 for later
|
|
self.soul2 = soul2
|
|
|
|
self.play(
|
|
flower.animate.set_opacity(0.1),
|
|
spiral.animate.set_opacity(0.2),
|
|
run_time=2
|
|
)
|
|
|
|
self.play(FadeOut(flower, spiral), run_time=1)
|
|
|
|
# =========== TRACK 10: POOR YOU POOR ME ===========
|
|
def track_10_poor_you_poor_me(self):
|
|
color = SuryaColors.POOR
|
|
self.show_title(10, "poor you poor me", '"you left your cardigan on my bed"', color)
|
|
|
|
self.transition_color(color)
|
|
|
|
# Bittersweet spirograph
|
|
spiro = Spirograph(R=3, r=1.8, d=1.5, color=color)
|
|
spiro.scale(0.6)
|
|
spiro.set_opacity(0.4)
|
|
|
|
self.play(Create(spiro), run_time=3)
|
|
|
|
# One soul drifts away
|
|
self.play(
|
|
self.soul2.animate.shift(RIGHT * 3).set_opacity(0.4),
|
|
run_time=4
|
|
)
|
|
|
|
# Memories drift
|
|
memories = VGroup()
|
|
for _ in range(20):
|
|
m = Dot(
|
|
point=self.soul2.get_center() + np.array([
|
|
random.uniform(-0.5, 0.5),
|
|
random.uniform(-0.5, 0.5),
|
|
0
|
|
]),
|
|
radius=0.03,
|
|
color=SuryaColors.WITH_U
|
|
)
|
|
m.set_opacity(0.5)
|
|
memories.add(m)
|
|
|
|
self.play(FadeIn(memories), run_time=1)
|
|
|
|
self.play(
|
|
*[m.animate.move_to(
|
|
self.soul.get_center() + np.array([random.uniform(-0.3, 0.3), random.uniform(-0.3, 0.3), 0])
|
|
) for m in memories],
|
|
self.soul2.animate.shift(RIGHT * 2).set_opacity(0.1),
|
|
run_time=4
|
|
)
|
|
|
|
# Bittersweet pulse
|
|
self.play(
|
|
self.soul.animate.scale(1.2),
|
|
spiro.animate.set_opacity(0.6),
|
|
run_time=2
|
|
)
|
|
self.play(
|
|
self.soul.animate.scale(1/1.2),
|
|
spiro.animate.set_opacity(0.3),
|
|
run_time=2
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(self.soul2, memories, spiro),
|
|
run_time=2
|
|
)
|
|
|
|
# =========== TRACK 11: WAIT 4 U ===========
|
|
def track_11_wait_4_u(self):
|
|
color = SuryaColors.WAIT
|
|
self.show_title(11, "wait 4 u", "", color)
|
|
|
|
self.transition_color(color)
|
|
self.soul.move_to(ORIGIN)
|
|
|
|
# Concentric circles - time passing
|
|
circles = VGroup()
|
|
for i in range(8):
|
|
c = Circle(radius=0.5 + i * 0.4, color=color)
|
|
c.set_stroke(width=1, opacity=0.3 - i * 0.03)
|
|
circles.add(c)
|
|
|
|
self.play(
|
|
*[Create(c) for c in circles],
|
|
run_time=3,
|
|
lag_ratio=0.3
|
|
)
|
|
|
|
# Patient pulsing
|
|
for _ in range(2):
|
|
self.play(
|
|
circles.animate.scale(1.1),
|
|
self.soul.animate.scale(0.9),
|
|
run_time=2
|
|
)
|
|
self.play(
|
|
circles.animate.scale(1/1.1),
|
|
self.soul.animate.scale(1/0.9),
|
|
run_time=2
|
|
)
|
|
|
|
# Longing spiral
|
|
spiral = GoldenSpiral(turns=5, color=color)
|
|
spiral.scale(0.3)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(
|
|
spiral.animate.set_opacity(0.4).scale(2),
|
|
circles.animate.set_opacity(0.1),
|
|
run_time=4
|
|
)
|
|
|
|
self.play(FadeOut(circles, spiral), run_time=2)
|
|
|
|
# =========== TRACK 12: RUN TO U ===========
|
|
def track_12_run_to_u(self):
|
|
color = SuryaColors.RUN
|
|
self.show_title(12, "run to u", '"if the sky was falling I would run to you"', color)
|
|
|
|
self.transition_color(color)
|
|
self.soul.move_to(LEFT * 4)
|
|
|
|
# Target in distance
|
|
target = SoulOrb(color=SuryaColors.WITH_U, radius=0.25)
|
|
target.shift(RIGHT * 4)
|
|
target.set_opacity(0.3)
|
|
self.add(target)
|
|
|
|
# Speed lines
|
|
speed_lines = VGroup()
|
|
for _ in range(15):
|
|
y = random.uniform(-3, 3)
|
|
line = Line(LEFT * 7, RIGHT * 7, color=color)
|
|
line.shift(UP * y)
|
|
line.set_stroke(width=1, opacity=0.2)
|
|
speed_lines.add(line)
|
|
|
|
self.play(FadeIn(speed_lines), run_time=0.5)
|
|
|
|
# Urgent run
|
|
self.play(
|
|
self.soul.animate.shift(RIGHT * 4),
|
|
target.animate.set_opacity(0.7),
|
|
run_time=2,
|
|
rate_func=rate_functions.ease_in_quad
|
|
)
|
|
|
|
# Final push
|
|
self.play(
|
|
self.soul.animate.shift(RIGHT * 3),
|
|
target.animate.shift(LEFT * 1).set_opacity(1),
|
|
run_time=1.5,
|
|
rate_func=rate_functions.ease_out_quad
|
|
)
|
|
|
|
# Collision burst
|
|
burst = VGroup()
|
|
for i in range(12):
|
|
angle = i * PI / 6
|
|
ray = Line(ORIGIN, 2 * np.array([np.cos(angle), np.sin(angle), 0]))
|
|
ray.set_stroke(color, width=3, opacity=0.6)
|
|
burst.add(ray)
|
|
burst.move_to(RIGHT * 1.5)
|
|
|
|
self.play(
|
|
FadeIn(burst, scale=0.5),
|
|
self.soul.animate.move_to(RIGHT * 1.5).scale(1.2),
|
|
target.animate.move_to(RIGHT * 1.5).scale(1.2),
|
|
run_time=1
|
|
)
|
|
|
|
self.play(
|
|
burst.animate.scale(2).set_opacity(0),
|
|
FadeOut(speed_lines, target),
|
|
run_time=2
|
|
)
|
|
self.remove(burst)
|
|
|
|
# =========== TRACK 13: MEDICATIONS ===========
|
|
def track_13_medications(self):
|
|
color = SuryaColors.MEDS
|
|
self.show_title(13, "medications", '"raging in my head"', color)
|
|
|
|
self.transition_color(color)
|
|
self.soul.move_to(ORIGIN)
|
|
|
|
# Chaos attractor
|
|
attractor = StrangeAttractor(color=color)
|
|
attractor.scale(0.5)
|
|
|
|
self.play(Create(attractor), run_time=3)
|
|
|
|
# Aggressive spirographs
|
|
spiros = VGroup()
|
|
for i in range(3):
|
|
s = Spirograph(R=2 + i, r=0.5 + i * 0.3, d=1.5, color=color)
|
|
s.scale(0.4)
|
|
s.set_opacity(0.3)
|
|
s.rotate(i * PI / 3)
|
|
spiros.add(s)
|
|
|
|
self.play(
|
|
*[Create(s) for s in spiros],
|
|
run_time=2
|
|
)
|
|
|
|
# Soul shakes
|
|
original_pos = self.soul.get_center()
|
|
for _ in range(5):
|
|
offset = np.array([random.uniform(-0.3, 0.3), random.uniform(-0.3, 0.3), 0])
|
|
self.play(
|
|
self.soul.animate.move_to(original_pos + offset),
|
|
run_time=0.1
|
|
)
|
|
self.play(self.soul.animate.move_to(original_pos), run_time=0.2)
|
|
|
|
# Red pulses
|
|
for _ in range(2):
|
|
pulse = Circle(radius=0.5, color=color)
|
|
pulse.set_stroke(width=3, opacity=0.8)
|
|
self.add(pulse)
|
|
self.play(
|
|
pulse.animate.scale(4).set_opacity(0),
|
|
spiros.animate.rotate(PI/6),
|
|
run_time=1
|
|
)
|
|
self.remove(pulse)
|
|
|
|
# Struggle
|
|
self.play(
|
|
Rotate(attractor, PI/2, about_point=ORIGIN),
|
|
spiros.animate.set_opacity(0.5),
|
|
self.soul.animate.scale(0.8),
|
|
run_time=3
|
|
)
|
|
|
|
# Exhaustion
|
|
self.play(
|
|
attractor.animate.set_opacity(0.2),
|
|
spiros.animate.set_opacity(0.1),
|
|
self.soul.animate.scale(0.8).set_opacity(0.6),
|
|
run_time=3
|
|
)
|
|
|
|
self.play(FadeOut(attractor, spiros), run_time=2)
|
|
|
|
# =========== TRACK 14: HOLLOW ===========
|
|
def track_14_hollow(self):
|
|
color = SuryaColors.HOLLOW
|
|
self.show_title(14, "hollow", '"you took my sorrow and flew it to the moon"', color)
|
|
|
|
self.transition_color(color)
|
|
self.play(self.soul.animate.move_to(ORIGIN).scale(1.3).set_opacity(1), run_time=2)
|
|
|
|
# Stars return bright
|
|
self.play(
|
|
*[s.animate.set_opacity(random.uniform(0.4, 0.9)) for s in self.stars],
|
|
run_time=3,
|
|
lag_ratio=0.01
|
|
)
|
|
|
|
# Golden spiral - resolution
|
|
spiral = GoldenSpiral(turns=5, color=color)
|
|
spiral.scale(0.2)
|
|
spiral.set_opacity(0)
|
|
|
|
self.play(
|
|
spiral.animate.set_opacity(0.6).scale(3),
|
|
run_time=5
|
|
)
|
|
|
|
# Moon appears
|
|
moon = Circle(radius=1.5, color=color)
|
|
moon.set_fill(color, opacity=0.1)
|
|
moon.set_stroke(color, width=2, opacity=0.5)
|
|
moon.shift(UP * 2 + RIGHT * 3)
|
|
|
|
self.play(FadeIn(moon, scale=0.5), run_time=3)
|
|
|
|
# Sorrow flies to moon
|
|
sorrow = VGroup()
|
|
for _ in range(15):
|
|
s = Dot(
|
|
point=self.soul.get_center() + np.array([
|
|
random.uniform(-0.4, 0.4),
|
|
random.uniform(-0.4, 0.4),
|
|
0
|
|
]),
|
|
radius=0.04,
|
|
color=GREY
|
|
)
|
|
sorrow.add(s)
|
|
|
|
self.play(FadeIn(sorrow), run_time=1)
|
|
|
|
self.play(
|
|
*[s.animate.move_to(moon.get_center() + np.array([
|
|
random.uniform(-0.4, 0.4),
|
|
random.uniform(-0.4, 0.4),
|
|
0
|
|
])).set_opacity(0) for s in sorrow],
|
|
self.soul.animate.scale(1.3).set_opacity(1),
|
|
run_time=4
|
|
)
|
|
|
|
# Sacred geometry finale
|
|
flower = FlowerOfLife(rings=2, radius=0.6, color=color)
|
|
flower.set_opacity(0)
|
|
|
|
self.play(
|
|
flower.animate.set_opacity(0.4),
|
|
spiral.animate.set_opacity(0.8),
|
|
run_time=3
|
|
)
|
|
|
|
# Final radiance
|
|
self.play(
|
|
self.soul.animate.scale(1.5),
|
|
flower.animate.scale(1.3),
|
|
spiral.animate.scale(1.2),
|
|
self.stars.animate.set_opacity(1),
|
|
run_time=2
|
|
)
|
|
|
|
self.wait(2)
|
|
|
|
# Store for finale
|
|
self.flower = flower
|
|
self.spiral = spiral
|
|
self.moon = moon
|
|
|
|
# =========== FINALE ===========
|
|
def finale(self):
|
|
# Final title
|
|
finale_text = Text("ready to feel something?", font_size=48,
|
|
color=SuryaColors.HOLLOW, slant=ITALIC)
|
|
finale_text.shift(DOWN * 1)
|
|
finale_text.set_opacity(0)
|
|
|
|
self.play(
|
|
finale_text.animate.set_opacity(0.8),
|
|
self.soul.animate.scale(1.2),
|
|
run_time=3
|
|
)
|
|
|
|
self.wait(2)
|
|
|
|
# Gentle fade
|
|
self.play(
|
|
self.soul.animate.scale(2).set_opacity(0.3),
|
|
self.flower.animate.set_opacity(0.1),
|
|
self.spiral.animate.set_opacity(0.2),
|
|
self.stars.animate.set_opacity(0.4),
|
|
self.moon.animate.set_opacity(0.2),
|
|
finale_text.animate.set_opacity(0.4),
|
|
run_time=4
|
|
)
|
|
|
|
# SURYA text returns
|
|
surya_final = Text("SURYA", font_size=80, color=SuryaColors.HOLLOW)
|
|
surya_final.set_opacity(0)
|
|
surya_final.shift(UP * 1)
|
|
|
|
das_final = Text("DAS", font_size=24, color=WHITE)
|
|
das_final.set_opacity(0)
|
|
das_final.next_to(surya_final, DOWN, buff=0.5)
|
|
|
|
self.play(
|
|
surya_final.animate.set_opacity(0.6),
|
|
das_final.animate.set_opacity(0.3),
|
|
finale_text.animate.shift(DOWN * 0.5).set_opacity(0.2),
|
|
run_time=3
|
|
)
|
|
|
|
self.wait(3)
|
|
|
|
# Final fade to black
|
|
self.play(
|
|
*[mob.animate.set_opacity(0) for mob in self.mobjects],
|
|
run_time=4
|
|
)
|
|
|
|
self.wait(1)
|
|
|
|
|
|
# ============================================================================
|
|
# INDIVIDUAL TRACK SCENES (for testing)
|
|
# ============================================================================
|
|
|
|
class TestSoul(Scene):
|
|
"""Quick test of soul orb"""
|
|
def construct(self):
|
|
soul = SoulOrb(color=SuryaColors.WITH_U, radius=0.3)
|
|
self.play(FadeIn(soul, scale=0.5), run_time=2)
|
|
|
|
for _ in range(3):
|
|
self.play(soul.animate.scale(1.3), run_time=1)
|
|
self.play(soul.animate.scale(1/1.3), run_time=1)
|
|
|
|
self.wait(1)
|
|
|
|
|
|
class TestMathObjects(Scene):
|
|
"""Test mathematical objects"""
|
|
def construct(self):
|
|
# Spirograph
|
|
spiro = Spirograph(R=3, r=1, d=1.5, color=PINK)
|
|
spiro.scale(0.5)
|
|
self.play(Create(spiro), run_time=3)
|
|
self.wait(1)
|
|
|
|
# Golden spiral
|
|
spiral = GoldenSpiral(turns=4, color=GOLD)
|
|
spiral.scale(0.5)
|
|
self.play(Transform(spiro, spiral), run_time=2)
|
|
self.wait(1)
|
|
|
|
# Flower of life
|
|
flower = FlowerOfLife(rings=2, radius=0.5, color=TEAL)
|
|
self.play(FadeIn(flower), run_time=2)
|
|
self.wait(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("SURYA — A Mathematical Journey Through Feeling")
|
|
print("Render with: manim -pqh --fps 60 surya_journey.py SuryaJourney")
|
|
print("Preview with: manim -pql surya_journey.py SuryaJourney")
|