Table of Contents
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Seamless Plasma Loop
Classic plasma with loop-friendly sine wave periods that divide evenly into 600 frames (10 seconds at 60fps).
Loop Guarantee
All sine wave periods (60, 40, 30, 20 frames) are divisors of 600, ensuring frame 0 and frame 599 produce identical outputs.
Mathematical Formula
P(x, y, t) = \frac{\sin(x + \frac{2\pi t}{60}) + \sin(y + \frac{2\pi t}{40}) + \sin(\frac{x+y}{2} + \frac{2\pi t}{30}) + \sin(\frac{x-y}{2} + \frac{2\pi t}{20})}{4}
Where:
- Periods: 60, 40, 30, 20 frames
- All periods divide 600 evenly
\frac{600}{60} = 10,\frac{600}{40} = 15,\frac{600}{30} = 20,\frac{600}{20} = 30
How It Works
For a seamless loop, the animation must return to its starting state after the total number of frames. This is achieved by:
- Choosing sine wave periods that divide the total frame count
- Using the same periods for all time-dependent calculations
- Ensuring all periodic operations complete integer revolutions
Implementation
import torch
width, height = 512, 512
fps = 60
duration = 10 # seconds
total_frames = fps * duration # 600 frames
frames = []
for t in range(total_frames):
x = torch.linspace(0, 2*torch.pi, width)
y = torch.linspace(0, 2*torch.pi, height)
X, Y = torch.meshgrid(x, y, indexing='ij')
# Use periods that divide evenly into 600 for perfect loop
# 600 / 60 = 10, 600 / 40 = 15, 600 / 30 = 20
p1 = torch.sin(X + t * (2*torch.pi/60)) # 60-frame period
p2 = torch.sin(Y + t * (2*torch.pi/40)) # 40-frame period
p3 = torch.sin((X + Y) / 2 + t * (2*torch.pi/30)) # 30-frame period
p4 = torch.sin((X - Y) / 2 + t * (2*torch.pi/20)) # 20-frame period
plasma = (p1 + p2 + p3 + p4) / 4
# Warm sunset palette
r = plasma * 0.8 + 0.2
g = plasma * 0.4 + 0.3
b = plasma * 0.2 + 0.6
rgb = torch.stack([r, g, b], dim=-1).clamp(0, 1)
frames.append(rgb)
output_image = torch.stack(frames, dim=0) # Shape: [600, H, W, 3]
Line-by-Line Explanation
total_frames = fps * duration # 600 frames
Total frames = 60 fps × 10 seconds = 600 frames.
p1 = torch.sin(X + t * (2*torch.pi/60))
Period of 60 frames. After 60 frames, t * (2π/60) increases by 2π, completing one full sine cycle.
p2 = torch.sin(Y + t * (2*torch.pi/40))
Period of 40 frames. After 40 frames, completes one cycle.
p3 = torch.sin((X + Y) / 2 + t * (2*torch.pi/30))
Period of 30 frames.
p4 = torch.sin((X - Y) / 2 + t * (2*torch.pi/20))
Period of 20 frames.
Mathematical Insight
Why Divisors of 600?
For a seamless loop at frame N:
sin(θ + N * (2π/P)) = sin(θ)
This requires:
N * (2π/P) = 2π * k (where k is integer)
N / P = k
So P must divide N evenly. Our periods (60, 40, 30, 20) all divide 600:
- 600 / 60 = 10 (integer)
- 600 / 40 = 15 (integer)
- 600 / 30 = 20 (integer)
- 600 / 20 = 30 (integer)
Verifying the Loop
After 600 frames:
- p1 completes 600/60 = 10 full cycles
- p2 completes 600/40 = 15 full cycles
- p3 completes 600/30 = 20 full cycles
- p4 completes 600/20 = 30 full cycles
All return to their starting values, so the plasma at frame 600 matches frame 0.
Customization
Different Duration
duration = 5 # 5 seconds
total_frames = fps * duration # 300 frames
# Use periods that divide 300: 50, 60, 75, 100
Different FPS
fps = 30 # 30 fps
total_frames = fps * duration # 300 frames
More Waves
p5 = torch.sin(X * 2 + t * (2*torch.pi/25)) # 25-frame period
plasma = (p1 + p2 + p3 + p4 + p5) / 5
Different Palette
# Neon vaporwave
r = plasma * 0.6 + 0.4
g = plasma * 0.2 + 0.1
b = plasma * 0.8 + 0.5
Performance Notes
- Precompute coordinate grids
- All operations are vectorized
- 600 frames at 512×512 may be slow on CPU
Loop Verification
# After generating frames, verify loop
first_frame = output_image[0]
last_frame = output_image[-1]
diff = torch.abs(first_frame - last_frame).max()
print(f"Max difference: {diff.item()}")
# Should be close to 0 (floating point precision)