Creating Butterfly Animation
Creating a 3D Voxel Butterfly Animation
This guide walks you through how to generate a looping 3D voxel animation of butterflies using SpatialStudio.
The script creates graceful butterflies that flutter their wings and dance through 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 butterflies, each with:
- Colorful animated wings that flap realistically
- A detailed segmented body
- Antennae that sway gently
- Wing patterns with spots and gradients
- Animates them flying in figure-8 patterns for 10 seconds at 30 FPS
- Outputs the file
butterfly.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
). -
Wing animation Wings are drawn as elliptical shapes that rotate up and down with realistic flapping motion using sine waves.
-
Body structure Each butterfly gets a segmented body with varying thickness and natural color transitions.
-
Flight patterns Butterflies follow figure-8 paths at different heights and speeds for natural-looking movement.
-
Wing details Colorful patterns, spots, and gradients are procedurally generated on each wing surface.
-
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 butterfly.py
and run:
python butterfly.py
Full Script
import numpy as np
from spatialstudio import splv
from tqdm import tqdm
# Scene setup
SIZE, FPS, SECONDS = 128, 30, 10
FRAMES = FPS * SECONDS
CENTER_X = CENTER_Y = CENTER_Z = SIZE // 2
OUT_PATH = "../outputs/butterfly.splv"
# Butterfly settings
BUTTERFLY_COUNT = 6
WING_SIZE = 12
BODY_LENGTH = 16
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_wing(volume, cx, cy, cz, wing_angle, is_left, color, t):
# Wing flapping motion
flap_angle = np.sin(t * 8.0) * 0.8
wing_rotation = wing_angle + flap_angle
# Wing shape parameters
wing_width = WING_SIZE
wing_height = int(WING_SIZE * 0.8)
for dx in range(-wing_width, wing_width+1):
for dy in range(-wing_height, wing_height+1):
# Create wing shape (elliptical)
distance = np.sqrt((dx/wing_width)**2 + (dy/wing_height)**2)
if distance <= 1.0:
# Apply wing rotation
if is_left:
rx = int(dx * np.cos(wing_rotation) - dy * np.sin(wing_rotation))
ry = int(dx * np.sin(wing_rotation) + dy * np.cos(wing_rotation))
else:
rx = int(dx * np.cos(-wing_rotation) - dy * np.sin(-wing_rotation))
ry = int(dx * np.sin(-wing_rotation) + dy * np.cos(-wing_rotation))
# Add wing patterns
pattern_factor = np.sin(dx * 0.3) * np.cos(dy * 0.4) * 0.3 + 0.7
wing_color = tuple(int(c * pattern_factor) for c in color)
# Add spots
spot_dist = np.sqrt((dx - wing_width//3)**2 + (dy - wing_height//3)**2)
if spot_dist < 3:
wing_color = (20, 20, 20) # Dark spots
add_voxel(volume, cx + rx, cy + ry, cz, wing_color)
def generate_body(volume, cx, cy, cz, t):
body_color = (101, 67, 33) # Brown body
head_color = (139, 90, 43) # Lighter brown head
# Generate segmented body
for i in range(BODY_LENGTH):
y_pos = cy - BODY_LENGTH//2 + i
segment_width = 2 if i < 3 or i > BODY_LENGTH-4 else 3 # Thinner at ends
for dx in range(-segment_width, segment_width+1):
for dz in range(-1, 2):
if dx*dx + dz*dz <= segment_width*segment_width:
# Head is lighter
color = head_color if i < 3 else body_color
add_voxel(volume, cx + dx, y_pos, cz + dz, color)
def generate_antennae(volume, cx, cy, cz, t):
antenna_color = (80, 50, 20)
sway = np.sin(t * 1.5) * 2
# Left antenna
for i in range(6):
x = cx - 2 + int(sway * (i/6))
y = cy - BODY_LENGTH//2 - i
z = cz + int(np.sin(t + i * 0.3))
add_voxel(volume, x, y, z, antenna_color)
# Right antenna
for i in range(6):
x = cx + 2 + int(sway * (i/6))
y = cy - BODY_LENGTH//2 - i
z = cz + int(np.sin(t + i * 0.3))
add_voxel(volume, x, y, z, antenna_color)
def generate_butterfly(volume, bx, by, bz, color, t, phase_offset):
adjusted_t = t + phase_offset
# Generate body
generate_body(volume, bx, by, bz, adjusted_t)
# Generate antennae
generate_antennae(volume, bx, by, bz, adjusted_t)
# Generate wings
wing_offset = 8
generate_wing(volume, bx - wing_offset, by, bz, 0.2, True, color, adjusted_t) # Left wing
generate_wing(volume, bx + wing_offset, by, bz, -0.2, False, color, adjusted_t) # Right wing
def generate_butterfly_swarm(volume, t):
colors = [
(255, 140, 0), # Orange
(255, 20, 147), # Deep pink
(138, 43, 226), # Blue violet
(50, 205, 50), # Lime green
(255, 215, 0), # Gold
(220, 20, 60), # Crimson
]
for i in range(BUTTERFLY_COUNT):
# Figure-8 flight pattern
angle = t * 1.2 + i * (2*np.pi / BUTTERFLY_COUNT)
figure8_x = np.sin(angle) * 25
figure8_y = np.sin(angle * 2) * 15
figure8_z = np.cos(angle * 0.8) * 20
# Height variation
height_offset = i * 8 - 20
bx = int(CENTER_X + figure8_x)
by = int(CENTER_Y + figure8_y + height_offset)
bz = int(CENTER_Z + figure8_z)
color = colors[i % len(colors)]
phase_offset = i * 0.5 # Stagger wing flapping
generate_butterfly(volume, bx, by, bz, color, t, phase_offset)
def generate_scene(volume, t):
generate_butterfly_swarm(volume, t)
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
for frame in tqdm(range(FRAMES), desc="Generating butterflies"):
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
BUTTERFLY_COUNT
to spawn more butterflies. - Edit
colors
array for different wing colors. - Modify the flight pattern by changing the figure-8 equations.
- Adjust
WING_SIZE
andBODY_LENGTH
for different butterfly sizes. - Add more complex wing patterns by modifying the
generate_wing
function. - Create seasonal variations by changing color palettes.