Creating Lighthouse Animation
Creating a Lighthouse Animation
This guide walks you through how to generate a looping 3D voxel animation of a lighthouse using SpatialStudio.
The script creates a rotating lighthouse beam that sweeps across a dark seascape with waves, then saves the animation to a .splv
file.
What this script does
- Creates a 3D scene of size 128×128×128
- Builds a lighthouse structure with:
- A sturdy stone tower base
- A rotating beacon light at the top
- A sweeping light beam
- Animated ocean waves at the base
- Animates the lighthouse beam rotating for 8 seconds at 30 FPS
- Outputs the file
lighthouse.splv
that you can play in your viewer
How it works (simplified)
-
Voxel volume Each frame is a 3D grid filled with RGBA values (
SIZE × SIZE × SIZE × 4
). -
Lighthouse structure The tower is built with stone-colored voxels in a cylindrical shape, with a beacon house on top.
-
Rotating beam A bright cone of light rotates around the lighthouse, illuminated with yellow/white voxels that fade with distance.
-
Ocean waves Blue voxels create animated waves using sine functions that move around the lighthouse base.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, making the beam rotation and waves loop smoothly. -
Encoding Frames are passed into
splv.Encoder
, which writes them into the.splv
video file.
Try it yourself
Install requirements first:
pip install spatialstudio numpy tqdm
Then copy this script into lighthouse.py
and run:
python lighthouse.py
Full Script
import numpy as np
from spatialstudio import splv
from tqdm import tqdm
# Scene setup
SIZE, FPS, SECONDS = 128, 30, 8
FRAMES = FPS * SECONDS
CENTER_X = CENTER_Y = CENTER_Z = SIZE // 2
OUT_PATH = "../outputs/lighthouse.splv"
# Lighthouse settings
TOWER_HEIGHT = 45
TOWER_RADIUS = 8
BEACON_HEIGHT = 8
BEAM_LENGTH = 50
def add_voxel(volume, x, y, z, color):
if 0 <= x < SIZE and 0 <= y < SIZE and 0 <= z < SIZE:
volume[x, y, z, :3] = color
volume[x, y, z, 3] = 255
def generate_tower(volume, cx, cy, cz):
# Main tower body
tower_color = (120, 120, 120) # Stone gray
for y in range(TOWER_HEIGHT):
radius = TOWER_RADIUS - (y // 15) # Slight taper
for dx in range(-radius, radius+1):
for dz in range(-radius, radius+1):
if dx*dx + dz*dz <= radius*radius:
# Add texture variation
brightness = 0.8 + 0.4 * np.sin(dx*0.3 + dz*0.3 + y*0.1)
final_color = tuple(int(c * brightness) for c in tower_color)
add_voxel(volume, cx+dx, cy-y, cz+dz, final_color)
# Beacon house on top
beacon_color = (180, 180, 180)
beacon_y = cy - TOWER_HEIGHT
for y in range(BEACON_HEIGHT):
for dx in range(-6, 7):
for dz in range(-6, 7):
if abs(dx) <= 5 and abs(dz) <= 5:
add_voxel(volume, cx+dx, beacon_y-y, cz+dz, beacon_color)
def generate_light_beam(volume, cx, cy, cz, t):
beacon_y = cy - TOWER_HEIGHT - BEACON_HEIGHT//2
beam_angle = t * 2.0 # Rotate beam
# Create sweeping beam
for distance in range(1, BEAM_LENGTH):
beam_width = 1 + distance // 8
intensity = max(0.1, 1.0 - distance / BEAM_LENGTH)
# Main beam direction
beam_x = cx + int(distance * np.cos(beam_angle))
beam_z = cz + int(distance * np.sin(beam_angle))
# Beam spread
for spread in range(-beam_width, beam_width+1):
for vertical in range(-2, 3):
offset_x = beam_x + int(spread * np.sin(beam_angle))
offset_z = beam_z - int(spread * np.cos(beam_angle))
light_intensity = intensity * max(0.3, 1.0 - abs(spread) * 0.2)
light_color = (
int(255 * light_intensity),
int(255 * light_intensity * 0.9),
int(100 * light_intensity)
)
add_voxel(volume, offset_x, beacon_y + vertical, offset_z, light_color)
def generate_beacon_light(volume, cx, cy, cz, t):
beacon_y = cy - TOWER_HEIGHT - BEACON_HEIGHT//2
# Bright rotating beacon
pulse = 0.7 + 0.3 * np.sin(t * 8)
beacon_color = (int(255 * pulse), int(200 * pulse), int(50 * pulse))
for dx in range(-3, 4):
for dy in range(-2, 3):
for dz in range(-3, 4):
if dx*dx + dy*dy + dz*dz <= 9:
add_voxel(volume, cx+dx, beacon_y+dy, cz+dz, beacon_color)
def generate_ocean_waves(volume, cx, cy, cz, t):
water_level = cy + 5
wave_colors = [(0, 50, 150), (0, 80, 200), (20, 100, 220)]
for x in range(SIZE):
for z in range(SIZE):
dx = x - cx
dz = z - cz
distance = np.sqrt(dx*dx + dz*dz)
if distance > TOWER_RADIUS + 5: # Don't overlap with tower
# Create wave height using multiple sine waves
wave1 = np.sin((x + z) * 0.1 + t * 3) * 3
wave2 = np.sin((x - z) * 0.15 + t * 2) * 2
wave3 = np.sin(distance * 0.08 + t * 4) * 1
wave_height = int(wave1 + wave2 + wave3)
# Draw water column
for y in range(max(0, water_level + wave_height), water_level + 8):
if y < SIZE:
depth = y - (water_level + wave_height)
color_idx = min(2, depth // 2)
water_color = wave_colors[color_idx]
add_voxel(volume, x, y, z, water_color)
def generate_rocks(volume, cx, cy, cz):
rock_color = (80, 70, 60)
# Add some rocky outcropping around the base
for i in range(6):
angle = i * np.pi / 3
rock_x = cx + int((TOWER_RADIUS + 12) * np.cos(angle))
rock_z = cz + int((TOWER_RADIUS + 12) * np.sin(angle))
for dx in range(-3, 4):
for dy in range(2, 8):
for dz in range(-3, 4):
if dx*dx + dz*dz <= 9 - dy:
add_voxel(volume, rock_x+dx, cy+dy, rock_z+dz, rock_color)
def generate_scene(volume, t):
generate_ocean_waves(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_rocks(volume, CENTER_X, CENTER_Y, CENTER_Z)
generate_tower(volume, CENTER_X, CENTER_Y, CENTER_Z)
generate_beacon_light(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_light_beam(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
for frame in tqdm(range(FRAMES), desc="Generating lighthouse"):
volume = np.zeros((SIZE, SIZE, SIZE, 4), dtype=np.uint8)
t = (frame / FRAMES) * 2*np.pi
generate_scene(volume, t)
enc.encode(splv.Frame(volume, lrAxis="x", udAxis="y", fbAxis="z"))
enc.finish()
print(f"Created {OUT_PATH}")
Next steps
- Adjust
BEAM_LENGTH
to make the light reach further. - Change
TOWER_HEIGHT
to build a taller or shorter lighthouse. - Modify wave parameters to create calmer or stormier seas.
- Add seabirds by creating small moving white voxels.
- Create a night sky with stars using scattered white dots.