Table of Contents
Earthbound Warping Distortion
Combines HDMA-style scanline shifting with radial distortion for the characteristic Earthbound warping effect.
Mathematical Formula
R = \sqrt{x^2 + y^2}
\text{warp}_{\text{radial}} = \sin(R \cdot k_r + \omega_r \cdot t) \cdot a_r
\text{warp}_{\text{scanline}} = \sin(y \cdot k_s + \omega_s \cdot t) \cdot a_s
X' = x + \text{warp}_{\text{radial}} \cdot x + \text{warp}_{\text{scanline}}
Y' = y + \text{warp}_{\text{radial}} \cdot y
Where:
Ris radial distance from centerk_r, k_sare spatial frequencies\omega_r, \omega_sare temporal frequenciesa_r, a_sare amplitudes
flowchart TD
A[Create coordinate grids] --> B[Convert to polar]
B --> C[Calculate radial distance R]
C --> D[Calculate radial warp]
C --> E[Calculate scanline warp]
D --> F[Apply radial distortion to X]
E --> F
D --> G[Apply radial distortion to Y]
F --> H[Generate base pattern]
G --> H
H --> I[Color mapping]
I --> J[Output animation]
How It Works
This pattern combines two distortion types:
- Radial warp: Distorts based on distance from center
- Scanline warp: Distorts based on vertical position
The combination creates the characteristic "wobbly" effect seen in Earthbound battle backgrounds.
Implementation
import torch
width, height = 512, 512
frames = []
for t in range(30):
x = torch.linspace(-1, 1, width)
y = torch.linspace(-1, 1, height)
X, Y = torch.meshgrid(x, y, indexing='ij')
# Radial distance from center
R = torch.sqrt(X**2 + Y**2)
# Earthbound-style warping: combination of radial and scanline distortion
radial_warp = torch.sin(R * 5 + t/5) * 0.1
scanline_warp = torch.sin(Y * 10 + t/3) * 0.05
# Combined distortion
X_distorted = X + radial_warp * X + scanline_warp
Y_distorted = Y + radial_warp * Y
# Generate base pattern
pattern = torch.sin(X_distorted * 10 + t/5) * torch.cos(Y_distorted * 10 + t/7)
# Color mapping
r = torch.sin(pattern * 3 + t/10)
g = torch.sin(pattern * 3 + t/10 + 2*torch.pi/3)
b = torch.sin(pattern * 3 + t/10 + 4*torch.pi/3)
rgb = torch.stack([r, g, b], dim=-1)
rgb = ((rgb + 1) / 2).clamp(0, 1)
frames.append(rgb)
output_image = torch.stack(frames, dim=0)
Line-by-Line Explanation
x = torch.linspace(-1, 1, width)
y = torch.linspace(-1, 1, height)
Coordinates range from -1 to 1, centered at (0, 0). This makes radial calculations easier.
R = torch.sqrt(X**2 + Y**2)
Calculates Euclidean distance from center. Center is 0, corners are ~1.41.
radial_warp = torch.sin(R * 5 + t/5) * 0.1
Radial distortion based on distance from center. * 5 controls spatial frequency, * 0.1 controls amplitude.
scanline_warp = torch.sin(Y * 10 + t/3) * 0.05
Vertical scanline distortion. Higher frequency (* 10) and lower amplitude (* 0.05) than radial.
X_distorted = X + radial_warp * X + scanline_warp
Applies both distortions to X coordinate. Radial warp scales with X (stronger at edges), scanline warp is constant across X.
Y_distorted = Y + radial_warp * Y
Applies only radial distortion to Y coordinate. This creates asymmetric warping.
pattern = torch.sin(X_distorted * 10 + t/5) * torch.cos(Y_distorted * 10 + t/7)
Generates pattern on distorted coordinates. Different frequencies for X and Y create complex interference.
Mathematical Insight
Why Multiply Radial Warp by X?
X + radial_warp * X
This makes the radial distortion stronger at the edges:
- At center (X=0): No radial distortion
- At edges (X≈1): Full radial distortion
This creates a lens-like effect where the center is stable and edges wobble more.
Asymmetric Distortion
X gets both radial and scanline distortion, but Y only gets radial. This asymmetry creates the characteristic Earthbound "wobble" where horizontal and vertical distortions differ.
Customization
Stronger Radial Warp
radial_warp = torch.sin(R * 5 + t/5) * 0.2 # Double amplitude
More Scanline Warp
scanline_warp = torch.sin(Y * 10 + t/3) * 0.1 # Double amplitude
Symmetric Distortion
X_distorted = X + radial_warp * X + scanline_warp
Y_distorted = Y + radial_warp * Y + scanline_warp # Add scanline to Y too
Different Pattern
pattern = torch.sin(X_distorted * 5) + torch.cos(Y_distorted * 5)
Performance Notes
- Coordinate grids are precomputed implicitly
- All operations are vectorized
- No expensive filtering or interpolation