Creating Sailboat Animation
This guide walks you through how to generate a looping 3D voxel animation of a sailboat using SpatialStudio.
The script creates a detailed sailboat with billowing sails that glides through gentle waves inside a cubic 3D space, then saves the animation to a .splv
file.
What this script does
-
Creates a 3D scene of size 128×128×128
-
Builds a sailboat with:
- A wooden hull with depth and texture
- A tall mast with rigging details
- Billowing sails that wave in the wind
- Dynamic water waves beneath
-
Animates the boat gently rocking and sailing for 8 seconds at 30 FPS
-
Outputs the file
sailboat.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
). -
Hull construction The boat hull is drawn as an elongated shape with brown wood texture and subtle variations.
-
Sail system White sails are generated with sine-wave deformations to simulate wind effects and natural billowing.
-
Water waves Blue voxels create an animated water surface with multiple wave frequencies for realistic motion.
-
Boat movement The entire sailboat rocks and moves slightly using trigonometric functions for natural sailing motion.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, ensuring the motion loops 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 sailboat.py
and run:
python sailboat.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/sailboat.splv"
# Boat settings
HULL_LENGTH = 24
HULL_WIDTH = 8
HULL_HEIGHT = 6
MAST_HEIGHT = 30
SAIL_WIDTH = 16
SAIL_HEIGHT = 20
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_hull(volume, cx, cy, cz, t):
hull_color = (139, 69, 19) # Brown wood
darker_hull = (101, 50, 14) # Darker brown for depth
rock_x = int(2 * np.sin(t * 0.8))
rock_y = int(1 * np.sin(t * 1.2))
rock_z = int(1 * np.cos(t * 0.6))
for dx in range(-HULL_LENGTH//2, HULL_LENGTH//2):
for dy in range(-HULL_HEIGHT, 2):
for dz in range(-HULL_WIDTH//2, HULL_WIDTH//2):
# Create boat hull shape
hull_factor = 1.0 - abs(dx) / (HULL_LENGTH//2)
width_limit = int(HULL_WIDTH//2 * hull_factor)
if abs(dz) <= width_limit:
# Add wood texture
texture = np.sin(dx * 0.3 + dy * 0.2) * 0.1
if dy < -2:
color = tuple(int(c * (1 + texture)) for c in darker_hull)
else:
color = tuple(int(c * (1 + texture)) for c in hull_color)
final_x = cx + dx + rock_x
final_y = cy + dy + rock_y
final_z = cz + dz + rock_z
add_voxel(volume, final_x, final_y, final_z, color)
def generate_mast_and_rigging(volume, cx, cy, cz, t):
mast_color = (101, 67, 33) # Dark brown
rope_color = (160, 82, 45) # Rope brown
rock_x = int(2 * np.sin(t * 0.8))
rock_y = int(1 * np.sin(t * 1.2))
rock_z = int(1 * np.cos(t * 0.6))
# Main mast
for dy in range(2, MAST_HEIGHT):
mast_sway = int(np.sin(t * 1.5) * (dy / MAST_HEIGHT) * 2)
add_voxel(volume, cx + rock_x + mast_sway, cy + dy + rock_y, cz + rock_z, mast_color)
# Add some rigging
if dy % 8 == 0:
for dz in range(-SAIL_WIDTH//2, SAIL_WIDTH//2, 4):
add_voxel(volume, cx + rock_x + mast_sway, cy + dy + rock_y, cz + dz + rock_z, rope_color)
def generate_sails(volume, cx, cy, cz, t):
sail_color = (250, 250, 240) # Off-white
sail_shadow = (220, 220, 200) # Slightly darker for depth
rock_x = int(2 * np.sin(t * 0.8))
rock_y = int(1 * np.sin(t * 1.2))
rock_z = int(1 * np.cos(t * 0.6))
# Main sail
for dy in range(5, 5 + SAIL_HEIGHT):
sail_height_factor = (dy - 5) / SAIL_HEIGHT
for dz in range(-SAIL_WIDTH//2, SAIL_WIDTH//2):
# Create billowing effect
billow = np.sin(t * 2.0 + dy * 0.2 + dz * 0.1) * 3
wind_effect = np.sin(t * 1.5 + dz * 0.3) * 2
sail_x = cx + int(billow + wind_effect) + rock_x
mast_sway = int(np.sin(t * 1.5) * (dy / MAST_HEIGHT) * 2)
# Add depth to sail
for dx in range(-2, 1):
color = sail_shadow if dx < -1 else sail_color
final_x = sail_x + dx + mast_sway
final_y = cy + dy + rock_y
final_z = cz + dz + rock_z
add_voxel(volume, final_x, final_y, final_z, color)
def generate_water(volume, t):
water_color = (30, 144, 255) # Deep sky blue
water_dark = (0, 100, 200) # Darker blue for depth
foam_color = (240, 248, 255) # White foam
water_level = CENTER_Y - 10
for x in range(SIZE):
for z in range(SIZE):
# Create multiple wave frequencies
wave1 = np.sin(x * 0.1 + t * 2.0) * 3
wave2 = np.sin(z * 0.15 + t * 1.5) * 2
wave3 = np.sin((x + z) * 0.05 + t * 0.8) * 1
wave_height = int(wave1 + wave2 + wave3)
surface_y = water_level + wave_height
# Draw water layers
for y in range(max(0, surface_y - 8), min(SIZE, surface_y + 2)):
if y <= surface_y:
if y == surface_y and wave_height > 2:
color = foam_color # Foam on wave peaks
elif y > surface_y - 3:
color = water_color
else:
color = water_dark
add_voxel(volume, x, y, z, color)
def generate_scene(volume, t):
# Generate water first (background)
generate_water(volume, t)
# Generate boat components
generate_hull(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_mast_and_rigging(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_sails(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 sailboat"):
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
HULL_LENGTH
andSAIL_HEIGHT
to change the boat's proportions. - Modify water wave frequencies in
generate_water()
for calmer or choppier seas. - Add clouds by creating white voxels in the upper portion of the scene.
- Experiment with
rock_x
,rock_y
,rock_z
values to change the boat's movement intensity. - Try different sail colors or add multiple sails for a more complex vessel.