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

Moiré Interference Loop

Interference pattern with phase shifts that loop perfectly over 600 frames.

Loop Guarantee

Phase shifts (50, 75, 100 frames) are divisors of 600. Roll shifts complete full cycles over 600 frames.

Mathematical Formula


I = \sin(k_1 x + \frac{2\pi t}{50}) \times \sin(k_2 y + \frac{2\pi t}{75}) \times \sin(k_3 (x+y) + \frac{2\pi t}{100})

Where:

  • Periods: 50, 75, 100 frames
  • All divide 600 evenly
  • Roll shifts also complete full cycles

How It Works

This pattern combines wave interference with rolling color shifts:

  1. Generate three interfering waves with different periods
  2. Apply interference (multiplication)
  3. Shift color channels by rolling the pattern
  4. Roll amounts increase linearly with time

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(0, 10*torch.pi, width)
    y = torch.linspace(0, 10*torch.pi, height)
    X, Y = torch.meshgrid(x, y, indexing='ij')
    
    # Phase shifts with periods dividing 600
    wave1 = torch.sin(X * 2 + t * (2*torch.pi/50))   # 50-frame period
    wave2 = torch.sin(Y * 2 + t * (2*torch.pi/75))   # 75-frame period
    wave3 = torch.sin((X + Y) * 2 + t * (2*torch.pi/100))  # 100-frame period
    
    interference = wave1 * wave2 * wave3
    
    # Color shifts with different periods for visual interest
    r = torch.abs(interference)
    g = torch.abs(torch.roll(interference, int(t * width / total_frames), dims=0))
    b = torch.abs(torch.roll(interference, int(t * height / total_frames), dims=1))
    
    rgb = torch.stack([r, g, b], dim=-1).clamp(0, 1)
    frames.append(rgb)

output_image = torch.stack(frames, dim=0)

Line-by-Line Explanation

wave1 = torch.sin(X * 2 + t * (2*torch.pi/50))

Period of 50 frames. 600 / 50 = 12 full cycles over the loop.

wave2 = torch.sin(Y * 2 + t * (2*torch.pi/75))

Period of 75 frames. 600 / 75 = 8 full cycles.

wave3 = torch.sin((X + Y) * 2 + t * (2*torch.pi/100))

Period of 100 frames. 600 / 100 = 6 full cycles.

g = torch.abs(torch.roll(interference, int(t * width / total_frames), dims=0))

Rolls interference vertically. At t=600, shift = width, which wraps to original position (modulo width).

b = torch.abs(torch.roll(interference, int(t * height / total_frames), dims=1))

Rolls interference horizontally. At t=600, shift = height, which wraps to original position.

Mathematical Insight

Why These Periods?

  • 50: 600 / 50 = 12 (integer)
  • 75: 600 / 75 = 8 (integer)
  • 100: 600 / 100 = 6 (integer)

All divide 600 evenly, ensuring the waves return to their starting values at frame 600.

Rolling Shifts

int(t * width / total_frames)

At t=600, this equals width. Rolling by width pixels wraps around to the original position (modulo arithmetic). Same for height.

This ensures the color shifts also loop perfectly.

Customization

Different Periods

wave1 = torch.sin(X * 2 + t * (2*torch.pi/60))  # 60-frame period
wave2 = torch.sin(Y * 2 + t * (2*torch.pi/80))  # 80-frame period

No Color Rolling

r = torch.abs(interference)
g = torch.abs(interference)
b = torch.abs(interference)

Different Frequencies

wave1 = torch.sin(X * 3 + t * (2*torch.pi/50))  # Tighter pattern

Performance Notes

  • All operations are vectorized
  • torch.roll() is efficient
  • 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