Creating Robot Animation
Robot - 3D Voxel Animation Learning Example
This guide walks you through how to generate a looping 3D voxel animation of a robot using SpatialStudio.
The script creates an articulated robot that walks, moves its arms, and features glowing eyes 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 walking robot with:
- A rectangular body and head
- Articulated arms and legs that move
- Glowing blue eyes that pulse
- Metallic gray coloring with highlights
- Animates a walking cycle for 8 seconds at 30 FPS
- Outputs the file
robot.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
). -
Robot body The robot is constructed from rectangular voxel segments (torso, head, limbs) with metallic shading.
-
Walking animation Arms and legs swing back and forth in opposite phases to simulate natural walking motion.
-
Glowing eyes Bright blue voxels with pulsing intensity create the robot's animated eyes.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, ensuring the walking 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 robot.py
and run:
python robot.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/robot.splv"
# Robot settings
ROBOT_HEIGHT = 40
TORSO_WIDTH = 12
TORSO_HEIGHT = 16
HEAD_SIZE = 8
LIMB_LENGTH = 14
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 add_voxel_box(volume, cx, cy, cz, width, height, depth, color):
for dx in range(-width//2, width//2 + 1):
for dy in range(-height//2, height//2 + 1):
for dz in range(-depth//2, depth//2 + 1):
# Add metallic shading effect
brightness = 1.0
if abs(dx) == width//2 or abs(dy) == height//2:
brightness = 0.7 # Darker edges
elif dx == -width//2 + 1 and dy == height//2 - 1:
brightness = 1.3 # Highlight
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_robot_torso(volume, cx, cy, cz):
# Main body - dark metallic gray
body_color = (100, 100, 120)
add_voxel_box(volume, cx, cy, cz, TORSO_WIDTH, TORSO_HEIGHT, 8, body_color)
# Chest panel - lighter gray
panel_color = (140, 140, 160)
add_voxel_box(volume, cx, cy+2, cz+3, 8, 6, 2, panel_color)
def generate_robot_head(volume, cx, cy, cz, t):
# Head - lighter metallic
head_color = (120, 120, 140)
head_y = cy + TORSO_HEIGHT//2 + HEAD_SIZE//2 + 2
add_voxel_box(volume, cx, head_y, cz, HEAD_SIZE, HEAD_SIZE, HEAD_SIZE, head_color)
# Glowing eyes with pulse effect
eye_brightness = 0.8 + 0.4 * np.sin(t * 3.0)
eye_color = tuple(int(c * eye_brightness) for c in (0, 150, 255))
eye_y = head_y + 1
eye_z = cz + HEAD_SIZE//2 - 1
# Left eye
add_voxel(volume, cx - 2, eye_y, eye_z, eye_color)
add_voxel(volume, cx - 2, eye_y+1, eye_z, eye_color)
# Right eye
add_voxel(volume, cx + 2, eye_y, eye_z, eye_color)
add_voxel(volume, cx + 2, eye_y+1, eye_z, eye_color)
def generate_robot_limb(volume, start_x, start_y, start_z, length, angle, color, direction='down'):
# Generate an articulated limb (arm or leg)
segment_length = length // 2
# Upper segment
if direction == 'down':
mid_x = start_x + int(segment_length * np.sin(angle) * 0.5)
mid_y = start_y - segment_length
mid_z = start_z
else: # arm direction
mid_x = start_x + int(segment_length * np.cos(angle))
mid_y = start_y + int(segment_length * np.sin(angle) * 0.3)
mid_z = start_z
# Draw upper segment
steps = max(abs(mid_x - start_x), abs(mid_y - start_y), abs(mid_z - start_z))
if steps > 0:
for i in range(steps + 1):
progress = i / steps
x = int(start_x + (mid_x - start_x) * progress)
y = int(start_y + (mid_y - start_y) * progress)
z = int(start_z + (mid_z - start_z) * progress)
add_voxel_box(volume, x, y, z, 3, 3, 3, color)
# Lower segment
if direction == 'down':
end_x = mid_x + int(segment_length * np.sin(-angle) * 0.3)
end_y = mid_y - segment_length
end_z = mid_z
else:
end_x = mid_x + int(segment_length * np.cos(angle + np.pi/6))
end_y = mid_y + int(segment_length * np.sin(angle + np.pi/6) * 0.5)
end_z = mid_z
# Draw lower segment
steps = max(abs(end_x - mid_x), abs(end_y - mid_y), abs(end_z - mid_z))
if steps > 0:
for i in range(steps + 1):
progress = i / steps
x = int(mid_x + (end_x - mid_x) * progress)
y = int(mid_y + (end_y - mid_y) * progress)
z = int(mid_z + (end_z - mid_z) * progress)
add_voxel_box(volume, x, y, z, 3, 3, 3, color)
def generate_robot_legs(volume, cx, cy, cz, t):
leg_color = (80, 80, 100)
base_y = cy - TORSO_HEIGHT//2 - 2
# Walking motion - legs alternate
left_angle = np.sin(t * 2.0) * 0.4
right_angle = np.sin(t * 2.0 + np.pi) * 0.4
# Left leg
generate_robot_limb(volume, cx - 4, base_y, cz, LIMB_LENGTH, left_angle, leg_color, 'down')
# Right leg
generate_robot_limb(volume, cx + 4, base_y, cz, LIMB_LENGTH, right_angle, leg_color, 'down')
def generate_robot_arms(volume, cx, cy, cz, t):
arm_color = (90, 90, 110)
base_y = cy + 4
# Arm swinging - opposite to legs for natural walking
left_angle = np.sin(t * 2.0 + np.pi) * 0.5
right_angle = np.sin(t * 2.0) * 0.5
# Left arm
generate_robot_limb(volume, cx - TORSO_WIDTH//2 - 2, base_y, cz, LIMB_LENGTH, left_angle, arm_color, 'arm')
# Right arm
generate_robot_limb(volume, cx + TORSO_WIDTH//2 + 2, base_y, cz, LIMB_LENGTH, right_angle, arm_color, 'arm')
def generate_robot(volume, cx, cy, cz, t):
# Add slight bobbing motion to entire robot
bob_offset = int(2 * np.sin(t * 4.0))
robot_y = cy + bob_offset
# Generate robot parts
generate_robot_torso(volume, cx, robot_y, cz)
generate_robot_head(volume, cx, robot_y, cz, t)
generate_robot_legs(volume, cx, robot_y, cz, t)
generate_robot_arms(volume, cx, robot_y, cz, t)
def generate_scene(volume, t):
generate_robot(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 robot"):
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
- Modify
ROBOT_HEIGHT
andTORSO_WIDTH
to change the robot's proportions. - Adjust the walking speed by changing the multiplier in
t * 2.0
. - Add more details like antennas or chest lights.
- Experiment with different metallic colors in the color variables.
- Make the robot move forward by adding
+ int(t*10)
to the X position.