2 patterns-earthbound-Earthbound-Warping-Distortion
Balazs Horvath edited this page 2026-04-18 11:13:11 +02:00

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:

  • R is radial distance from center
  • k_r, k_s are spatial frequencies
  • \omega_r, \omega_s are temporal frequencies
  • a_r, a_s are 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:

  1. Radial warp: Distorts based on distance from center
  2. 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

References