Creating Kite Animation
3D Voxel Animation: Flying Kites
This guide walks you through how to generate a looping 3D voxel animation of kites using SpatialStudio.
The script creates colorful kites that fly, dance, and sway in the wind 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
- Spawns 6 kites, each with:
- A diamond-shaped voxel body with colorful patterns
- A long flowing tail with ribbons
- Dynamic wind movement and rotation
- Animates them soaring through the sky for 8 seconds at 30 FPS
- Outputs the file
kites.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
). -
Kite body Kites are drawn as diamond shapes with geometric patterns and bright colors.
-
Tail ribbons Each kite gets a long, wavy tail made of colorful segments that flutter in the wind.
-
Wind simulation Sine and cosine functions create natural swaying, dipping, and rising motions.
-
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 kites.py
and run:
python kites.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/kites.splv"
# Kite settings
KITE_COUNT = 6
KITE_SIZE = 12
TAIL_LENGTH = 35
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_kite_body(volume, cx, cy, cz, color, size, t, kite_id):
# Create diamond shape
for dx in range(-size, size+1):
for dy in range(-size, size+1):
for dz in range(-3, 4):
# Diamond pattern
if abs(dx) + abs(dy) <= size and abs(dz) <= 2:
# Add pattern based on position
pattern_x = dx + size
pattern_y = dy + size
# Create geometric patterns
if (pattern_x + pattern_y) % 4 < 2:
brightness = 1.2
else:
brightness = 0.8
# Add some animation to the pattern
wave = np.sin(t * 2.0 + kite_id * 0.5) * 0.1
brightness += wave
final_color = tuple(min(255, int(c * brightness)) for c in color)
add_voxel(volume, cx+dx, cy+dy, cz+dz, final_color)
# Add kite frame (darker lines)
frame_color = tuple(int(c * 0.3) for c in color)
# Vertical line
for dy in range(-size, size+1):
add_voxel(volume, cx, cy+dy, cz, frame_color)
# Horizontal line
for dx in range(-size, size+1):
add_voxel(volume, cx+dx, cy, cz, frame_color)
def generate_kite_tail(volume, cx, cy, cz, t, kite_id):
tail_colors = [
(255, 100, 100), (100, 255, 100), (100, 100, 255),
(255, 255, 100), (255, 100, 255), (100, 255, 255)
]
for i in range(TAIL_LENGTH):
segment = i / TAIL_LENGTH
# Tail follows behind the kite with wave motion
wave_x = np.sin(t * 3.0 + i * 0.3 + kite_id) * (3 + segment * 2)
wave_z = np.cos(t * 2.5 + i * 0.2 + kite_id) * (2 + segment * 1.5)
wave_y = np.sin(t * 1.8 + i * 0.4 + kite_id) * segment * 3
tx = cx + int(wave_x)
ty = cy - int(segment * 15) + int(wave_y) # Tail hangs down
tz = cz + int(wave_z)
# Alternate colors along the tail
color_idx = (i // 3) % len(tail_colors)
color = tail_colors[color_idx]
# Make tail segments slightly larger
for dx in range(-1, 2):
for dy in range(-1, 2):
if dx*dx + dy*dy <= 1:
add_voxel(volume, tx+dx, ty+dy, tz, color)
def generate_kite_swarm(volume, t):
kite_colors = [
(255, 0, 0), # Red
(0, 255, 0), # Green
(0, 0, 255), # Blue
(255, 165, 0), # Orange
(255, 0, 255), # Magenta
(255, 255, 0), # Yellow
]
for i in range(KITE_COUNT):
# Each kite follows a different flight path
angle = (i / KITE_COUNT) * 2 * np.pi
# Create circular motion with some randomness
radius = 25 + 10 * np.sin(i * 1.3)
height_offset = 15 * np.sin(t * 0.8 + i * 0.7)
# Wind effects
wind_x = np.sin(t * 1.2 + i * 0.5) * 8
wind_y = np.cos(t * 0.9 + i * 0.3) * 6
wind_z = np.sin(t * 1.5 + i * 0.8) * 10
kx = CENTER_X + int(radius * np.cos(angle + t * 0.4) + wind_x)
ky = CENTER_Y + int(height_offset + wind_y)
kz = CENTER_Z + int(radius * np.sin(angle + t * 0.3) + wind_z)
color = kite_colors[i % len(kite_colors)]
# Generate kite and its tail
generate_kite_body(volume, kx, ky, kz, color, KITE_SIZE, t, i)
generate_kite_tail(volume, kx, ky, kz, t, i)
def add_wind_particles(volume, t):
# Add some wind effect particles
particle_color = (200, 200, 255)
for i in range(50):
# Create moving particles to show wind
px = int((i * 17 + t * 20) % SIZE)
py = int((i * 23 + t * 15) % SIZE)
pz = int((i * 31 + t * 25) % SIZE)
# Make particles fade in and out
alpha = int(128 * (0.5 + 0.5 * np.sin(t * 4 + i * 0.1)))
if alpha > 50: # Only show visible particles
add_voxel(volume, px, py, pz, particle_color)
def generate_scene(volume, t):
generate_kite_swarm(volume, t)
add_wind_particles(volume, t)
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
for frame in tqdm(range(FRAMES), desc="Generating flying kites"):
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
- Change
KITE_COUNT
to add more kites to the sky - Modify
kite_colors
to use your favorite color palette - Adjust
TAIL_LENGTH
to make shorter or longer kite tails - Experiment with wind strength by changing the wave amplitudes
- Add ground elements by drawing voxels at low Y coordinates
- Create different kite shapes by modifying the diamond pattern logic