Table of Contents
- Animation Patterns - Mathematical Foundations
- Plasma Effect
- Perlin Noise
- Mathematical Concept
- How It Works (Simplified)
- Approximation Using Gaussian Filter
- Mathematical Formula
- Implementation
- Why Sigma Varies with Time
- Sine Wave Interference
- Mathematical Formula
- Constructive vs Destructive Interference
- Mathematical Insight
- Implementation
- Creating Moiré Patterns
- Mandelbrot Zoom
- Radial Patterns
- Color Cycling
- Performance Considerations
- Mathematical References
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.
Animation Patterns - Mathematical Foundations
This document explains the mathematical foundations behind common animation patterns used with ScriptableImage. Each pattern includes the mathematical formula, explanation, and implementation details.
Plasma Effect
Mathematical Formula
The plasma effect is created by summing multiple sine waves with different frequencies and phases:
P(x, y, t) = sin(x + t) + sin(y + t) + sin((x + y)/2 + t)
Where:
x, yare spatial coordinates (normalized to [0, 2π])tis time (controls animation speed)- The result is normalized to [0, 1]
How It Works
- Coordinate Grid: Create a 2D grid where
xandyrange from0to2\pi - Wave Summation: Add three sine waves:
- Horizontal wave:
\sin(x + t)- moves left to right - Vertical wave:
\sin(y + t)- moves top to bottom - Diagonal wave:
\sin(\frac{x + y}{2} + t)- moves diagonally
- Horizontal wave:
- Normalization: The sum ranges from
[-3, 3], normalize to[0, 1]
Why It Creates Plasma
The interference of multiple sine waves creates a complex, shifting pattern:
- Where waves constructively interfere (add together), you get bright regions
- Where waves destructively interfere (cancel out), you get dark regions
- The time parameter causes these regions to shift smoothly
Implementation
import torch
width, height = 512, 512
num_frames = 30
for t in range(num_frames):
# Create coordinate grids
x = torch.linspace(0, 2 * torch.pi, width)
y = torch.linspace(0, 2 * torch.pi, height)
X, Y = torch.meshgrid(x, y, indexing='ij')
# Plasma formula
plasma = torch.sin(X + t/5) + torch.sin(Y + t/5) + torch.sin((X + Y)/2 + t/5)
# Normalize from [-3, 3] to [0, 1]
plasma = (plasma + 3) / 6
Variations
More Waves: Add more sine waves for more complexity
plasma = (torch.sin(X + t/5) +
torch.sin(Y + t/5) +
torch.sin((X + Y)/2 + t/5) +
torch.sin((X - Y)/2 + t/5))
# Normalize from [-4, 4] to [0, 1]
plasma = (plasma + 4) / 8
Different Frequencies: Use different frequency multipliers
plasma = torch.sin(2*X + t/5) + torch.sin(3*Y + t/5) + torch.sin((X + Y)/3 + t/5)
Perlin Noise
Mathematical Concept
Perlin noise is a gradient noise that creates smooth, natural-looking random patterns. Unlike pure random noise which is completely uncorrelated, Perlin noise has spatial coherence - nearby points have similar values.
How It Works (Simplified)
- Grid: Overlay a grid on the space
- Gradients: Assign a random gradient vector to each grid point
- Dot Products: For each point, compute dot products with nearby grid gradients
- Interpolation: Smoothly interpolate between these dot products
Approximation Using Gaussian Filter
A simple approximation of Perlin-like noise can be created by:
- Generate random noise
- Apply a Gaussian filter to smooth it
- This creates spatially coherent noise
Mathematical Formula
The Gaussian filter smooths noise using a Gaussian kernel:
G(x) = \frac{1}{\sigma\sqrt{2\pi}} \cdot e^{-\frac{x^2}{2\sigma^2}}
Where:
\sigma(sigma) controls the blur radius- Larger
\sigma= smoother result - The kernel is normalized to sum to 1
Convolving noise with this Gaussian kernel smooths it while preserving large-scale structure.
Implementation
import numpy as np
from scipy.ndimage import gaussian_filter
width, height = 512, 512
num_frames = 30
for t in range(num_frames):
# Generate random noise
noise = np.random.rand(height, width, 3)
# Apply Gaussian filter with varying sigma for animation
sigma = 5 + 10 * np.sin(t / 10)
smooth_noise = gaussian_filter(noise, sigma=sigma)
# Normalize to [0, 1]
smooth_noise = np.clip(smooth_noise, 0, 1)
Why Sigma Varies with Time
By varying sigma over time:
- Small sigma: sharp, detailed noise
- Large sigma: smooth, blurry noise
- Varying sigma creates animation where details appear and disappear
Sine Wave Interference
Mathematical Formula
Wave interference is the sum of multiple sine waves:ω is the angular frequency (temporal frequency)
- The sum creates an interference pattern
Constructive vs Destructive Interference
- Constructive: Peaks align with peaks → brighter regions
- Destructive: Peaks align with troughs → darker regions
- Partial: Partial alignment → intermediate brightness
Mathematical Insight
The sum of two sine waves with the same frequency:
sin(A) + sin(B) = 2 sin((A+B)/2) cos((A-B)/2)
This identity shows that summing waves creates both a combined wave and an envelope that modulates it.
Implementation
import torch
width, height = 512, 512
num_frames = 30
for t in range(num_frames):
# Create coordinate grids
x = torch.linspace(0, 4 * torch.pi, width)
y = torch.linspace(0, 4 * torch.pi, height)
X, Y = torch.meshgrid(x, y, indexing='ij')
# Three sine waves
wave1 = torch.sin(X + t/5)
wave2 = torch.sin(Y + t/5)
wave3 = torch.sin((X + Y)/2 + t/5)
# Interference pattern
interference = wave1 + wave2 + wave3
# Normalize from [-3, 3] to [0, 1]
interference = (interference + 3) / 6
Creating Moiré Patterns
Moiré patterns occur when waves with similar but slightly different frequencies interfere:
wave1 = torch.sin(X + t/5)
wave2 = torch.sin(1.1 * X + t/5) # Slightly different frequency
moire = wave1 + wave2
The slight frequency difference creates a beating pattern that shifts over time.
Mandelbrot Zoom
Mathematical Formulaxr-h
(x + iy)² + (a + ib) = (x² - y² + a) + i(2xy + b)
### Implementation
```python for n in range(max_iter):
if abs(z) > 2:
return n
z = z*z + c
return max_iter
for t in range(num_frames):
# Zoom parameters
zoom = 2.0 / (1.5 ** (t / 10))
center_x, center_y = -0.5, 0
# Generate coordinate grid
x = np.linspace(center_x - zoom, center_x + zoom, width)
y = np.linspace(center_y - zoom, center_y + zoom, height)
X, Y = np.meshgrid(x, y)
C = X + 1j * Y
# Compute Mandelbrot set
iterations = np.zeros_like(C, dtype=int)
for i in range(len(C.flat)):
iterations.flat[i] = mandelbrot(C.flat[i])
# Color mapping
rgb = np.zeros((height, width, 3), dtype=np.uint8)
rgb[..., 0] = (iterations * 10) % 256
rgb[..., 1] = (iterations * 20) % 256
rgb[..., 2] = (iterations * 30) % 256
Why It Zooms
The zoom is controlled by the coordinate range:
- Large range: shows more of the set (zoomed out)
- Small range: shows less of the set (zoomed in)
- Exponential zoom (
1.5 ** (t/10)) creates smooth zooming
Radial Patterns
Mathematical Formula
Radial patterns use polar coordinates:
R = \sqrt{x^2 + y^2}
\theta = \arctan2(y, x)
Where f() is any function of the radius.
Common Radial Functions
Linear Gradient: f(r) = 1 - r
- Bright at center, dark at edges
Gaussian: f(r) = exp(-r² / (2σ²))
- Smooth falloff from center
Inverse Square: f(r) = 1 / (1 + r²)
- Slower falloff
Implementation
import torch
width, height = 512, 512
# Create coordinates from -1 to 1
y_coords = torch.linspace(-1, 1, height)
x_coords = torch.linspace(-1, 1, width)
Y, X = torch.meshgrid(y_coords, x_coords, indexing='ij')
# Calculate distance from center
distance = torch.sqrt(X**2 + Y**2)
# Different radial patterns
linear = 1 - distance
gaussian = torch.exp(-distance**2 / (2 * 0.3**2))
inverse_square = 1 / (1 + distance**2)
Animated Radial Patterns
import torch
num_frames = 30
for t in range(num_frames):
# Vary the Gaussian sigma over time
sigma = 0.2 + 0.3 * np.sin(t / 10)
radial = torch.exp(-distance**2 / (2 * sigma**2))
Color Cycling
Mathematical Concept
Color cycling maps a scalar value to RGB using phase-shifted sine waves:
R = sin(v * π + φ)
G = sin(v * π + φ + 2π/3)
B = sin(v * π + φ + 4π/3)
Where:
vis the input value (normalized to [0, 1])φis the phase shift (for animation)- The offsets (2π/3, 4π/3) are 120° and 240°
Why 120° Offsets
The color wheel is a circle:
- Red at 0°
- Green at 120°
- Blue at 240°
By offsetting the sine waves by 120°, we ensure smooth transitions through all colors as the input value changes.
Implementation
import torch
# Input value (e.g., from plasma effect)
value = plasma # Normalized to [0, 1]
# Color cycling with animation
t = frame_number / 10
r = torch.sin(value * torch.pi + t)
g = torch.sin(value * torch.pi + t + 2*torch.pi/3)
b = torch.sin(value * torch.pi + t + 4*torch.pi/3)
# Normalize from [-1, 1] to [0, 1]
rgb = torch.stack([r, g, b], dim=-1)
rgb = (rgb + 1) / 2
Performance Considerations
Vectorization
Always use vectorized operations (PyTorch/NumPy) instead of Python loops:
Slow:
for i in range(height):
for j in range(width):
result[i, j] = math.sin(x[i, j])
Fast:
result = torch.sin(X)
Pre-allocation
Pre-allocate arrays instead of growing them:
Slow:
frames = []
for t in range(30):
frame = compute_frame(t)
frames.append(frame)
output = torch.stack(frames)
Better (if size is known):
output = torch.zeros((30, height, width, 3))
for t in range(30):
output[t] = compute_frame(t)
Resolution Trade-offs
- 512×512: Good balance of quality and speed
- 1024×1024: Higher quality, 4× slower
- 256×256: Faster, good for previews
Mathematical References
Sine Wave Properties
- Period: 2π
- Range: [-1, 1]
- Derivative: cos(x)
- Integral: -cos(x)
Gaussian Function
- Maximum at x=0: 1
- Inflection points at x=±σ
- 68% of area within ±σ
- 95% of area within ±2σ
Complex Numbers
- Magnitude: |a + ib| = sqrt(a² + b²)
- Addition: (a + ib) + (c + id) = (a+c) + i(b+d)
- Multiplication: (a + ib)(c + id) = (ac-bd) + i(ad+bc)