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:
- Generate three interfering waves with different periods
- Apply interference (multiplication)
- Shift color channels by rolling the pattern
- 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