Creating Metal Plates Animation
Creating 3D Voxel Metal Plates Animation
This guide walks you through how to generate a looping 3D voxel animation of metal plates using SpatialStudio.
The script creates industrial-looking metal plates that shift, rotate, and gleam 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 6 metal plates, each with:
- A rectangular metallic body with rivets
- Realistic rust and wear patterns
- Reflective highlights that shimmer
-
Animates them sliding and rotating for 8 seconds at 30 FPS
-
Outputs the file
metal_plates.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
). -
Metal plate body Plates are drawn as rectangular slabs with noise-based surface texturing for realistic metal appearance.
-
Rivets and details Each plate gets circular rivets and bolts positioned along the edges for industrial authenticity.
-
Surface effects Rust spots, scratches, and metallic highlights are procedurally generated to simulate weathered metal.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, making plates slide and rotate smoothly in a loop. -
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 metal_plates.py
and run:
python metal_plates.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/metal_plates.splv"
# Metal plate settings
PLATE_COUNT = 6
PLATE_WIDTH = 20
PLATE_HEIGHT = 15
PLATE_THICKNESS = 4
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_metal_texture(x, y, z, base_color, t):
# Add metallic noise and surface variation
noise = np.sin(x*0.1) * np.cos(y*0.15) * np.sin(z*0.12 + t*0.3)
brightness = 1.0 + noise * 0.2
# Add rust spots occasionally
rust_factor = np.sin(x*0.07 + y*0.08 + t*0.1) * np.cos(z*0.06)
if rust_factor > 0.7:
rust_color = (139, 69, 19) # Rusty brown
mix = 0.3
final_color = tuple(int(base_color[i] * (1-mix) + rust_color[i] * mix) for i in range(3))
else:
final_color = tuple(min(255, int(c * brightness)) for c in base_color)
return final_color
def generate_plate_body(volume, cx, cy, cz, width, height, thickness, color, rotation, t):
cos_r, sin_r = np.cos(rotation), np.sin(rotation)
for dx in range(-width//2, width//2 + 1):
for dy in range(-height//2, height//2 + 1):
for dz in range(-thickness//2, thickness//2 + 1):
# Rotate the plate
rx = int(dx * cos_r - dz * sin_r)
rz = int(dx * sin_r + dz * cos_r)
final_x, final_y, final_z = cx + rx, cy + dy, cz + rz
metal_color = generate_metal_texture(final_x, final_y, final_z, color, t)
add_voxel(volume, final_x, final_y, final_z, metal_color)
def generate_rivets(volume, cx, cy, cz, width, height, thickness, rotation):
rivet_color = (105, 105, 105) # Dark gray
cos_r, sin_r = np.cos(rotation), np.sin(rotation)
# Place rivets at corners and edges
rivet_positions = [
(-width//2 + 2, -height//2 + 2, thickness//2 + 1),
(width//2 - 2, -height//2 + 2, thickness//2 + 1),
(-width//2 + 2, height//2 - 2, thickness//2 + 1),
(width//2 - 2, height//2 - 2, thickness//2 + 1),
]
for dx, dy, dz in rivet_positions:
# Rotate rivet position
rx = int(dx * cos_r - dz * sin_r)
rz = int(dx * sin_r + dz * cos_r)
# Draw small cylindrical rivet
for r_dx in range(-1, 2):
for r_dy in range(-1, 2):
if r_dx*r_dx + r_dy*r_dy <= 1:
add_voxel(volume, cx + rx + r_dx, cy + dy + r_dy, cz + rz, rivet_color)
def generate_plate_cluster(volume, cx, cy, cz, t):
metal_colors = [
(169, 169, 169), # Dark gray
(192, 192, 192), # Silver
(105, 105, 105), # Dim gray
(128, 128, 128), # Gray
(160, 160, 160), # Light gray
(112, 128, 144), # Slate gray
]
for i in range(PLATE_COUNT):
# Arrange plates in a shifting formation
layer = i // 2
offset = i % 2
px = cx + int((layer - 1) * 25 + 10 * np.sin(t*0.8 + i*0.5))
py = cy + int(offset * 30 - 15 + 5 * np.cos(t*1.2 + i*0.3))
pz = cz + int(layer * 8 + 6 * np.sin(t*0.6 + i*0.7))
# Each plate rotates slowly
rotation = t * 0.4 + i * 0.8
color = metal_colors[i % len(metal_colors)]
width = PLATE_WIDTH + int(5 * np.sin(t*0.5 + i))
height = PLATE_HEIGHT + int(3 * np.cos(t*0.7 + i))
generate_plate_body(volume, px, py, pz, width, height, PLATE_THICKNESS, color, rotation, t)
generate_rivets(volume, px, py, pz, width, height, PLATE_THICKNESS, rotation)
def generate_metallic_highlights(volume, cx, cy, cz, t):
highlight_color = (255, 255, 255)
for i in range(PLATE_COUNT):
layer = i // 2
offset = i % 2
px = cx + int((layer - 1) * 25 + 10 * np.sin(t*0.8 + i*0.5))
py = cy + int(offset * 30 - 15 + 5 * np.cos(t*1.2 + i*0.3))
pz = cz + int(layer * 8 + 6 * np.sin(t*0.6 + i*0.7))
rotation = t * 0.4 + i * 0.8
cos_r, sin_r = np.cos(rotation), np.sin(rotation)
# Add moving highlights on the surface
highlight_x = int(8 * np.cos(t*2.0 + i*0.4))
highlight_y = int(4 * np.sin(t*1.8 + i*0.6))
highlight_z = PLATE_THICKNESS//2 + 2
# Rotate highlight position
rhx = int(highlight_x * cos_r - highlight_z * sin_r)
rhz = int(highlight_x * sin_r + highlight_z * cos_r)
# Draw small highlight cluster
for dx in range(-2, 3):
for dy in range(-1, 2):
if dx*dx + dy*dy <= 4:
add_voxel(volume, px + rhx + dx, py + highlight_y + dy, pz + rhz, highlight_color)
def generate_scene(volume, t):
generate_plate_cluster(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_metallic_highlights(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 metal plates"):
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
PLATE_COUNT
to add more metal plates. - Edit
metal_colors
for different metallic finishes. - Adjust
PLATE_THICKNESS
to make thicker or thinner plates. - Add sparks or welding effects by creating bright particle clusters.
- Experiment with different rust patterns by modifying the
rust_factor
calculation.