2 patterns-earthbound-Kaleidoscope-Loop
Balazs Horvath edited this page 2026-04-18 11:13:16 +02:00

Kaleidoscope Loop

Symmetrical kaleidoscope with rotation that loops perfectly over 600 frames.

Loop Guarantee

Pattern (50 frames), color (40 frames), and rotation (600 frames) ensure perfect loop.

Mathematical Formula


\theta' = |(\theta \cdot \frac{4}{\pi}) \bmod 2 - 1| \cdot \frac{\pi}{4}

X' = R \cdot \cos(\theta')

Y' = R \cdot \sin(\theta')

\text{pattern} = \sin(X' \cdot k + \frac{2\pi t}{50}) \cdot \cos(Y' \cdot k + \frac{2\pi t}{50})

\text{rotated} = \text{rotate}(\text{pattern}, \frac{2\pi t}{600})

Where:

  • Pattern period: 50 frames
  • Color period: 40 frames
  • Rotation: completes 360^\circ over 600 frames

How It Works

This pattern creates a kaleidoscope effect by:

  1. Convert to polar coordinates
  2. Apply symmetry transformation (8-fold)
  3. Generate pattern on symmetrical coordinates
  4. Apply rotation
  5. 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')
    
    # Convert to polar
    R = torch.sqrt(X**2 + Y**2)
    theta = torch.atan2(Y, X)
    
    # Create 8-fold symmetry
    theta_sym = torch.abs((theta * 4 / torch.pi) % 2 - 1) * torch.pi / 4
    
    # Convert back to cartesian for pattern
    X_sym = R * torch.cos(theta_sym)
    Y_sym = R * torch.sin(theta_sym)
    
    # Rotating pattern with 50-frame period
    pattern = torch.sin(X_sym * 10 + t * (2*torch.pi/50)) * torch.cos(Y_sym * 10 + t * (2*torch.pi/50))
    
    # Complete rotation over 600 frames
    rotation = t * (2*torch.pi / total_frames)
    X_rot = X * torch.cos(rotation) - Y * torch.sin(rotation)
    Y_rot = X * torch.sin(rotation) + Y * torch.cos(rotation)
    
    # Apply pattern to rotated coordinates
    final_pattern = torch.sin(X_rot * 5 + pattern) * torch.cos(Y_rot * 5 + pattern)
    
    # Color mapping with 40-frame period
    r = torch.sin(final_pattern + t * (2*torch.pi/40))
    g = torch.sin(final_pattern + t * (2*torch.pi/40) + 2*torch.pi/3)
    b = torch.sin(final_pattern + t * (2*torch.pi/40) + 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

theta_sym = torch.abs((theta * 4 / torch.pi) % 2 - 1) * torch.pi / 4

Creates 8-fold symmetry:

  • theta * 4/π: Scales angle
  • mod 2: Wraps to [0, 2)
  • - 1: Centers around 0
  • abs(): Creates mirror symmetry
  • * π/4: Rescales to [0, π/4]

This maps any angle to one of 8 symmetric segments.

X_sym = R * torch.cos(theta_sym)
Y_sym = R * torch.sin(theta_sym)

Converts symmetrical angle back to Cartesian coordinates for pattern generation.

pattern = torch.sin(X_sym * 10 + t * (2*torch.pi/50)) * torch.cos(Y_sym * 10 + t * (2*torch.pi/50))

Generates pattern on symmetrical coordinates with 50-frame period.

rotation = t * (2*torch.pi / total_frames)

Rotation angle increases from 0 to 2π over 600 frames.

X_rot = X * torch.cos(rotation) - Y * torch.sin(rotation)
Y_rot = X * torch.sin(rotation) + Y * torch.cos(rotation)

Standard 2D rotation matrix applied to coordinates.

final_pattern = torch.sin(X_rot * 5 + pattern) * torch.cos(Y_rot * 5 + pattern)

Applies pattern to rotated coordinates. The pattern itself becomes part of the coordinate transformation.

Mathematical Insight

8-Fold Symmetry

The symmetry transformation maps any angle θ to one of 8 segments:

  • Segment 0: [0, π/4]
  • Segment 1: [π/4, π/2]
  • ...
  • Segment 7: [7π/4, 2π]

All angles are mirrored into segment 0, creating 8-fold rotational symmetry.

Why This Creates Kaleidoscope

By mapping all angles to one segment and then generating a pattern, the pattern is automatically replicated 8 times around the circle. The rotation then animates the entire kaleidoscope.

Customization

Different Symmetry

# 6-fold symmetry
theta_sym = torch.abs((theta * 3 / torch.pi) % 2 - 1) * torch.pi / 3

No Pattern in Rotation

final_pattern = torch.sin(X_rot * 5) * torch.cos(Y_rot * 5)

Different Rotation Speed

rotation = t * (4*torch.pi / total_frames)  # 2 full revolutions

Performance Notes

  • Trigonometric functions are expensive
  • Symmetry transformation is fast
  • Rotation is vectorized

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