Creating Snake Animation
Snake - 3D Voxel Animation Learning Example
This guide walks you through how to generate a looping 3D voxel animation of a snake using SpatialStudio.
The script creates a slithering snake that moves through a cubic 3D space with realistic body motion, then saves the animation to a .splv
file.
What this script does
- Creates a 3D scene of size 128×128×128
- Spawns 1 animated snake with:
- A segmented body that follows a curved path
- Realistic slithering motion with sine-wave undulation
- Gradient coloring from head to tail
- Smooth body segments that follow the head
- Animates the snake moving in a figure-8 pattern for 8 seconds at 30 FPS
- Outputs the file
snake.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
). -
Snake body segments The snake consists of multiple spherical segments that follow a calculated path, each slightly smaller than the previous.
-
Path calculation The snake follows a parametric figure-8 curve in 3D space, with additional vertical undulation for realistic motion.
-
Body following Each segment follows the position of the segment in front of it with a time delay, creating natural snake-like movement.
-
Color gradient The snake uses a green-to-yellow gradient from head to tail, making it easy to see the direction of movement.
-
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 snake.py
and run:
python snake.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/snake.splv"
# Snake settings
SNAKE_LENGTH = 25
SEGMENT_SIZE = 3
SEGMENT_SPACING = 2.5
PATH_RADIUS = 30
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 calculate_snake_position(t, segment_index):
# Figure-8 path with time delay for each segment
delayed_t = t - (segment_index * 0.1)
# Figure-8 parametric equations
x = PATH_RADIUS * np.sin(delayed_t)
z = PATH_RADIUS * np.sin(delayed_t) * np.cos(delayed_t)
# Vertical undulation for 3D movement
y = 10 * np.sin(delayed_t * 2.0 + segment_index * 0.3)
return CENTER_X + int(x), CENTER_Y + int(y), CENTER_Z + int(z)
def get_segment_color(segment_index):
# Gradient from bright green (head) to yellow (tail)
progress = segment_index / SNAKE_LENGTH
red = int(255 * progress)
green = 255
blue = int(50 * (1 - progress))
return (red, green, blue)
def generate_snake_segment(volume, cx, cy, cz, color, size):
for dx in range(-size, size+1):
for dy in range(-size, size+1):
for dz in range(-size, size+1):
distance = np.sqrt(dx*dx + dy*dy + dz*dz)
if distance <= size:
# Add some texture variation
brightness = 1.0 - (distance / size) * 0.3
final_color = tuple(min(255, int(c * brightness)) for c in color)
add_voxel(volume, cx+dx, cy+dy, cz+dz, final_color)
def generate_snake_head(volume, cx, cy, cz, t):
# Head is slightly larger and has eyes
head_color = (0, 255, 0) # Bright green
head_size = SEGMENT_SIZE + 1
# Generate head body
generate_snake_segment(volume, cx, cy, cz, head_color, head_size)
# Add eyes
eye_color = (255, 255, 255) # White eyes
eye_offset = 2
add_voxel(volume, cx-eye_offset, cy+eye_offset, cz+eye_offset, eye_color)
add_voxel(volume, cx+eye_offset, cy+eye_offset, cz+eye_offset, eye_color)
# Add pupils
pupil_color = (0, 0, 0) # Black pupils
add_voxel(volume, cx-eye_offset, cy+eye_offset, cz+eye_offset+1, pupil_color)
add_voxel(volume, cx+eye_offset, cy+eye_offset, cz+eye_offset+1, pupil_color)
def generate_snake(volume, t):
snake_positions = []
# Calculate all segment positions
for i in range(SNAKE_LENGTH):
x, y, z = calculate_snake_position(t, i)
snake_positions.append((x, y, z))
# Draw snake segments from tail to head
for i in range(SNAKE_LENGTH-1, -1, -1):
x, y, z = snake_positions[i]
if i == 0: # Head
generate_snake_head(volume, x, y, z, t)
else: # Body segments
color = get_segment_color(i)
# Segments get slightly smaller towards the tail
size = max(1, SEGMENT_SIZE - int(i * 0.05))
generate_snake_segment(volume, x, y, z, color, size)
def add_ground_texture(volume):
# Add some ground voxels for reference
ground_color = (101, 67, 33) # Brown ground
for x in range(0, SIZE, 8):
for z in range(0, SIZE, 8):
if np.random.random() > 0.7: # Sparse ground texture
add_voxel(volume, x, 5, z, ground_color)
def generate_scene(volume, t):
add_ground_texture(volume)
generate_snake(volume, t)
# Initialize encoder
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
# Generate animation frames
for frame in tqdm(range(FRAMES), desc="Generating snake"):
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
SNAKE_LENGTH
to make the snake longer or shorter. - Modify
PATH_RADIUS
to make the snake's movement path larger or smaller. - Edit the
get_segment_color()
function for different color schemes. - Adjust
SEGMENT_SPACING
to make the snake more or less compressed. - Try different path equations in
calculate_snake_position()
for unique movement patterns (spiral, sine wave, etc.). - Add obstacles or terrain for the snake to navigate around.