Creating Spider Animation
Spider - 3D Voxel Animation Tutorial
This guide walks you through how to generate a looping 3D voxel animation of a spider using SpatialStudio.
The script creates a realistic spider with articulated legs that crawls and moves 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 1 animated spider with:
- An oval body (abdomen and thorax)
- 8 articulated legs with realistic joint movement
- Subtle body texture and shading
- Natural crawling motion
- Animates the spider walking in a circular pattern for 8 seconds at 30 FPS
- Outputs the file
spider.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
). -
Spider body The spider consists of two main parts: a larger abdomen and smaller thorax, drawn as connected ellipsoids.
-
Articulated legs Each of the 8 legs has multiple segments that bend naturally as the spider walks, using trigonometric functions for realistic joint angles.
-
Walking animation Legs move in alternating patterns (like real spiders) while the body follows a circular path with subtle up-and-down motion.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, ensuring the walking cycle 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 spider.py
and run:
python spider.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/spider.splv"
# Spider settings
BODY_LENGTH = 12
BODY_WIDTH = 8
LEG_LENGTH = 16
LEG_SEGMENTS = 3
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_spider_body(volume, cx, cy, cz, rotation, t):
# Main body colors
body_color = (50, 25, 10)
highlight_color = (80, 40, 20)
# Rotate position based on walking direction
body_x = cx + int(np.cos(rotation) * 0)
body_z = cz + int(np.sin(rotation) * 0)
# Generate abdomen (larger rear section)
for dx in range(-BODY_LENGTH//2, BODY_LENGTH//2):
for dy in range(-BODY_WIDTH//3, BODY_WIDTH//3):
for dz in range(-BODY_WIDTH//2, BODY_WIDTH//2):
dist = np.sqrt((dx/(BODY_LENGTH*0.4))**2 + (dy/(BODY_WIDTH*0.3))**2 + (dz/(BODY_WIDTH*0.4))**2)
if dist <= 1.0:
# Add texture variation
texture = np.sin(dx*0.3 + dz*0.3 + t) * 0.2
brightness = 1.0 + texture
final_color = tuple(min(255, int(c * brightness)) for c in body_color)
add_voxel(volume, body_x + dx - 4, cy + dy, body_z + dz, final_color)
# Generate thorax (smaller front section)
thorax_size = BODY_WIDTH // 2
for dx in range(-thorax_size, thorax_size):
for dy in range(-thorax_size//2, thorax_size//2):
for dz in range(-thorax_size, thorax_size):
dist = np.sqrt((dx/thorax_size)**2 + (dy/(thorax_size*0.5))**2 + (dz/thorax_size)**2)
if dist <= 1.0:
add_voxel(volume, body_x + dx + 6, cy + dy, body_z + dz, highlight_color)
def generate_spider_leg(volume, start_x, start_y, start_z, leg_angle, walk_phase, side):
leg_color = (40, 20, 8)
joint_color = (60, 30, 15)
# Calculate leg movement based on walk cycle
step_height = max(0, np.sin(walk_phase) * 3)
leg_extend = 1.0 + np.cos(walk_phase) * 0.3
# Leg segments
segment_length = LEG_LENGTH // LEG_SEGMENTS
current_x, current_y, current_z = start_x, start_y, start_z
for segment in range(LEG_SEGMENTS):
# Calculate joint angles
if segment == 0: # Upper leg
angle_offset = leg_angle + np.sin(walk_phase) * 0.4
vertical_angle = -0.3 + np.sin(walk_phase) * 0.2
elif segment == 1: # Middle leg
angle_offset = leg_angle + np.sin(walk_phase + 1) * 0.3
vertical_angle = 0.1 + np.sin(walk_phase + 1) * 0.3
else: # Lower leg
angle_offset = leg_angle + np.sin(walk_phase + 2) * 0.2
vertical_angle = 0.4 - step_height * 0.1
# Calculate segment end position
segment_len = segment_length * leg_extend
end_x = current_x + int(np.cos(angle_offset) * segment_len)
end_y = current_y + int(np.sin(vertical_angle) * segment_len) - int(step_height)
end_z = current_z + int(np.sin(angle_offset) * segment_len)
# Draw segment
steps = max(abs(end_x - current_x), abs(end_y - current_y), abs(end_z - current_z))
if steps > 0:
for i in range(steps + 1):
t = i / steps if steps > 0 else 0
x = int(current_x + (end_x - current_x) * t)
y = int(current_y + (end_y - current_y) * t)
z = int(current_z + (end_z - current_z) * t)
# Joints are slightly thicker
color = joint_color if i == 0 or i == steps else leg_color
add_voxel(volume, x, y, z, color)
# Make joints thicker
if i == 0 or i == steps:
for dx in [-1, 0, 1]:
for dz in [-1, 0, 1]:
if dx*dx + dz*dz <= 1:
add_voxel(volume, x + dx, y, z + dz, color)
current_x, current_y, current_z = end_x, end_y, end_z
def generate_spider(volume, cx, cy, cz, t):
# Spider walks in a circle
walk_speed = 0.8
circle_radius = 20
position_angle = t * walk_speed
spider_x = cx + int(np.cos(position_angle) * circle_radius)
spider_z = cz + int(np.sin(position_angle) * circle_radius)
spider_y = cy + int(np.sin(t * 3) * 2) # Subtle vertical movement
# Body rotation follows movement direction
body_rotation = position_angle + np.pi/2
# Generate body
generate_spider_body(volume, spider_x, spider_y, spider_z, body_rotation, t)
# Generate 8 legs (4 on each side)
leg_positions = [
(-0.5, 0.8), # Front legs
(-0.2, 0.4),
(0.2, -0.4),
(0.5, -0.8), # Back legs
]
for i, (front_back, spread) in enumerate(leg_positions):
walk_phase_offset = i * np.pi / 2 # Offset each leg's walk cycle
# Left side legs
leg_start_x = spider_x + int(front_back * BODY_LENGTH)
leg_start_y = spider_y
leg_start_z = spider_z - BODY_WIDTH
leg_angle = body_rotation - np.pi/2 + spread + np.pi/4
walk_phase = t * 4 + walk_phase_offset
generate_spider_leg(volume, leg_start_x, leg_start_y, leg_start_z,
leg_angle, walk_phase, "left")
# Right side legs
leg_start_z = spider_z + BODY_WIDTH
leg_angle = body_rotation + np.pi/2 - spread - np.pi/4
walk_phase = t * 4 + walk_phase_offset + np.pi # Opposite phase
generate_spider_leg(volume, leg_start_x, leg_start_y, leg_start_z,
leg_angle, walk_phase, "right")
def generate_scene(volume, t):
generate_spider(volume, CENTER_X, CENTER_Y + 10, CENTER_Z, t)
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
for frame in tqdm(range(FRAMES), desc="Generating spider"):
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
LEG_LENGTH
andLEG_SEGMENTS
for different spider proportions. - Change the
circle_radius
to make the spider walk in a larger or smaller circle. - Modify
walk_speed
to make the spider move faster or slower. - Add multiple spiders by calling
generate_spider()
with different positions. - Experiment with different body colors or add patterns to the spider's abdomen.