2 patterns-earthbound-Radial-Breathing-Loop
Balazs Horvath edited this page 2026-04-18 11:13:14 +02:00

Radial Breathing Loop

Radial pulsing pattern with smooth breathing animation that loops over 600 frames.

Loop Guarantee

Breathing (300 frames), radial (60 frames), angular (40 frames), and color (50 frames) periods all divide 600.

Mathematical Formula


R = \sqrt{x^2 + y^2}

\theta = \arctan2(y, x)

\text{breath} = 0.5 + 0.5 \cdot \sin(\frac{2\pi t}{300})

\text{radial} = \sin(R \cdot k_r - \frac{2\pi t}{60})

\text{angular} = \sin(\theta \cdot k_a + \frac{2\pi t}{40})

\text{pattern} = \text{radial} \times \text{angular} \times \text{breath}

Where:

  • Breathing period: 300 frames
  • Radial period: 60 frames
  • Angular period: 40 frames
  • Color period: 50 frames

How It Works

This pattern creates a radial "breathing" effect:

  1. Convert to polar coordinates (radius, angle)
  2. Create a breathing pulse that expands and contracts
  3. Add radial waves emanating from center
  4. Add angular waves rotating around center
  5. Combine all effects
  6. Map to color with time-based cycling

Implementation

import torch

width, height = 512, 512
fps = 60
duration = 10
total_frames = fps * duration
frames = []

for t in range(total_frames):
    x = torch.linspace(-1, 1, width)
    y = torch.linspace(-1, 1, height)
    X, Y = torch.meshgrid(x, y, indexing='ij')
    
    R = torch.sqrt(X**2 + Y**2)
    theta = torch.atan2(Y, X)
    
    # Breathing: complete 2 cycles over 600 frames (300-frame period)
    breath = 0.5 + 0.5 * torch.sin(t * (2*torch.pi/300))
    
    # Radial wave with 60-frame period
    radial = torch.sin(R * 10 - t * (2*torch.pi/60))
    
    # Angular wave with 40-frame period
    angular = torch.sin(theta * 5 + t * (2*torch.pi/40))
    
    pattern = radial * angular * breath
    
    # Psychedelic color mapping
    r = torch.sin(pattern * 3 + t * (2*torch.pi/50))
    g = torch.sin(pattern * 3 + t * (2*torch.pi/50) + 2*torch.pi/3)
    b = torch.sin(pattern * 3 + t * (2*torch.pi/50) + 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

R = torch.sqrt(X**2 + Y**2)

Radial distance from center. Center is 0, corners are ~1.41.

theta = torch.atan2(Y, X)

Angular position in radians. Ranges from -π to π.

breath = 0.5 + 0.5 * torch.sin(t * (2*torch.pi/300))

Breathing pulse. Period of 300 frames (half the total duration). Completes 2 full breath cycles over 600 frames.

radial = torch.sin(R * 10 - t * (2*torch.pi/60))

Radial wave emanating from center. Period of 60 frames. The - sign makes waves move outward.

angular = torch.sin(theta * 5 + t * (2*torch.pi/40))

Angular wave rotating around center. Period of 40 frames.

pattern = radial * angular * breath

Multiplies all three effects. The breathing modulates the overall intensity.

r = torch.sin(pattern * 3 + t * (2*torch.pi/50))

Color mapping with period of 50 frames.

Mathematical Insight

Why These Periods?

  • 300: 600 / 300 = 2 (2 breath cycles)
  • 60: 600 / 60 = 10 (10 radial wave cycles)
  • 40: 600 / 40 = 15 (15 angular rotations)
  • 50: 600 / 50 = 12 (12 color cycles)

All divide 600 evenly, ensuring perfect looping.

Breathing Effect

breath = 0.5 + 0.5 * sin(2πt/300)

This creates a value that:

  • Oscillates between 0 and 1
  • Has period of 300 frames
  • Completes 2 full cycles over 600 frames

Multiplying the pattern by breath makes the entire effect pulse in and out.

Polar Coordinates

Using polar coordinates (R, θ) makes radial and angular effects much easier to implement than Cartesian coordinates.

Customization

Faster Breathing

breath = 0.5 + 0.5 * torch.sin(t * (2*torch.pi/150))  # 150-frame period

More Radial Waves

radial = torch.sin(R * 15 - t * (2*torch.pi/60))

Different Angular Pattern

angular = torch.sin(theta * 8 + t * (2*torch.pi/40))

No Breathing

breath = 1.0  # Constant

Performance Notes

  • torch.sqrt() and torch.atan2() are fast
  • All operations are vectorized
  • No expensive filtering

Loop Verification

# After generating frames
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