2 patterns-mathematical-foundations
Balazs Horvath edited this page 2026-04-18 11:13:05 +02:00
This file contains ambiguous Unicode characters

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

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, y are spatial coordinates (normalized to [0, 2π])
  • t is time (controls animation speed)
  • The result is normalized to [0, 1]

How It Works

  1. Coordinate Grid: Create a 2D grid where x and y range from 0 to 2\pi
  2. 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
  3. 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)

  1. Grid: Overlay a grid on the space
  2. Gradients: Assign a random gradient vector to each grid point
  3. Dot Products: For each point, compute dot products with nearby grid gradients
  4. Interpolation: Smoothly interpolate between these dot products

Approximation Using Gaussian Filter

A simple approximation of Perlin-like noise can be created by:

  1. Generate random noise
  2. Apply a Gaussian filter to smooth it
  3. 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:

  • v is 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)