Creating Spring Animation
Spring Animation - 3D Voxel Learning Example
This guide walks you through how to generate a looping 3D voxel animation of a spring using SpatialStudio.
The script creates a colorful metallic spring that bounces, compresses, and extends 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
-
Generates a metallic spring with:
- Helical coil structure made of connected voxel segments
- Realistic compression and extension motion
- Metallic sheen with highlights and shadows
- Dynamic bounce animation
-
Animates the spring compressing and extending for 6 seconds at 30 FPS
-
Outputs the file
spring.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
). -
Helical geometry The spring is constructed using parametric equations that create a 3D helix with varying pitch.
-
Compression animation The spring's height and coil spacing change over time using sine waves to create realistic bouncing motion.
-
Metallic appearance Multiple shades of gray and silver create depth, with white highlights simulating metal reflections.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, making the compression and extension loop 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 spring.py
and run:
python spring.py
Full Script
import numpy as np
from spatialstudio import splv
from tqdm import tqdm
# Scene setup
SIZE, FPS, SECONDS = 128, 30, 6
FRAMES = FPS * SECONDS
CENTER_X = CENTER_Y = CENTER_Z = SIZE // 2
OUT_PATH = "../outputs/spring.splv"
# Spring settings
SPRING_RADIUS = 15
SPRING_HEIGHT_BASE = 50
COIL_THICKNESS = 3
COIL_COUNT = 8
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 get_spring_colors():
return {
'base': (120, 120, 120), # Base metal color
'dark': (80, 80, 80), # Shadow areas
'bright': (180, 180, 180), # Lit areas
'highlight': (255, 255, 255) # Metallic shine
}
def generate_spring_coil(volume, cx, cy, cz, compression_factor, t):
colors = get_spring_colors()
# Calculate spring height with compression
current_height = SPRING_HEIGHT_BASE * compression_factor
# Generate points along the helical path
steps = int(COIL_COUNT * 50) # Points per full spring
for i in range(steps):
# Parametric helix equations
theta = (i / steps) * COIL_COUNT * 2 * np.pi
height_progress = i / steps
# Spring position along helix
x = cx + SPRING_RADIUS * np.cos(theta)
z = cz + SPRING_RADIUS * np.sin(theta)
y = cy - current_height/2 + height_progress * current_height
# Add thickness to the coil
for dx in range(-COIL_THICKNESS, COIL_THICKNESS+1):
for dy in range(-COIL_THICKNESS//2, COIL_THICKNESS//2+1):
for dz in range(-COIL_THICKNESS, COIL_THICKNESS+1):
if dx*dx + dy*dy + dz*dz <= COIL_THICKNESS*COIL_THICKNESS:
# Calculate lighting based on position
light_factor = (np.cos(theta) + 1) / 2 # Simple lighting
# Choose color based on lighting and position
if light_factor > 0.8:
color = colors['highlight']
elif light_factor > 0.6:
color = colors['bright']
elif light_factor > 0.3:
color = colors['base']
else:
color = colors['dark']
final_x = int(x + dx)
final_y = int(y + dy)
final_z = int(z + dz)
add_voxel(volume, final_x, final_y, final_z, color)
def generate_spring_ends(volume, cx, cy, cz, compression_factor, t):
"""Generate flat end caps for the spring"""
colors = get_spring_colors()
current_height = SPRING_HEIGHT_BASE * compression_factor
# Top and bottom end positions
top_y = int(cy + current_height/2)
bottom_y = int(cy - current_height/2)
# Draw circular end caps
for dx in range(-SPRING_RADIUS-2, SPRING_RADIUS+3):
for dz in range(-SPRING_RADIUS-2, SPRING_RADIUS+3):
distance = np.sqrt(dx*dx + dz*dz)
if distance <= SPRING_RADIUS + 2:
# Add end caps with some thickness
for dy in range(-1, 2):
if distance < SPRING_RADIUS - 2:
color = colors['base']
else:
color = colors['dark']
add_voxel(volume, cx+dx, top_y+dy, cz+dz, color)
add_voxel(volume, cx+dx, bottom_y+dy, cz+dz, color)
def generate_scene(volume, t):
# Create bouncing compression effect
# Spring compresses and extends in a realistic bouncing motion
base_compression = 0.7 + 0.3 * np.sin(t * 3.0) # Main bounce
secondary_bounce = 0.05 * np.sin(t * 12.0) # Secondary oscillation
compression_factor = base_compression + secondary_bounce
# Clamp compression to reasonable values
compression_factor = max(0.4, min(1.2, compression_factor))
# Generate the spring
generate_spring_coil(volume, CENTER_X, CENTER_Y, CENTER_Z, compression_factor, t)
generate_spring_ends(volume, CENTER_X, CENTER_Y, CENTER_Z, compression_factor, t)
# Add some floating particles for effect (optional)
generate_bounce_particles(volume, CENTER_X, CENTER_Y, CENTER_Z, compression_factor, t)
def generate_bounce_particles(volume, cx, cy, cz, compression_factor, t):
"""Add small particles that bounce off the spring"""
colors = get_spring_colors()
# Only show particles when spring is highly compressed
if compression_factor < 0.6:
particle_count = 5
for i in range(particle_count):
angle = (i / particle_count) * 2 * np.pi + t * 2
radius = 25 + 10 * np.sin(t * 4 + i)
px = cx + int(radius * np.cos(angle))
pz = cz + int(radius * np.sin(angle))
py = cy + int(20 * np.sin(t * 6 + i * 0.5))
# Small particle
for dx in range(-1, 2):
for dy in range(-1, 2):
for dz in range(-1, 2):
if dx*dx + dy*dy + dz*dz <= 1:
add_voxel(volume, px+dx, py+dy, pz+dz, colors['bright'])
# 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 spring"):
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
COIL_COUNT
to create springs with more or fewer coils - Modify the
compression_factor
calculation for different bounce patterns - Change the metallic colors in
get_spring_colors()
for gold, copper, or other materials - Add
+ int(t*10)
to the Y position to make the spring bounce vertically through space - Experiment with
SPRING_RADIUS
andCOIL_THICKNESS
for different spring proportions