Creating Phoenix Animation
Phoenix - 3D Voxel Animation Learning Example
This guide walks you through how to generate a looping 3D voxel animation of a phoenix using SpatialStudio.
The script creates a majestic phoenix with fiery wings, a glowing body, and trailing flames 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 phoenix with:
- A bird-shaped body with gradient colors
- Animated wings that flap realistically
- Trailing fire particles behind it
- A glowing aura effect
- Animates the phoenix soaring in a circular flight pattern for 8 seconds at 30 FPS
- Outputs the file
phoenix.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
). -
Phoenix body The phoenix is built from multiple parts: head, neck, body, and tail using ellipsoid shapes with fire-colored gradients.
-
Wing animation Wings are drawn as curved surfaces that rotate up and down with sine wave functions to create realistic flapping motion.
-
Fire trail Particles spawn behind the phoenix and fade over time, creating a trailing flame effect with orange-to-red gradients.
-
Flight path The phoenix follows a circular path while gently rising and falling, using trigonometric functions for smooth motion.
-
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 phoenix.py
and run:
python phoenix.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/phoenix.splv"
# Phoenix settings
PHOENIX_SIZE = 12
WING_SPAN = 20
FLIGHT_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 get_fire_color(intensity):
"""Generate fire colors from white-hot to deep red"""
if intensity > 0.8:
return (255, 255, int(200 + intensity * 55)) # White-yellow
elif intensity > 0.6:
return (255, int(150 + intensity * 105), 0) # Orange-yellow
elif intensity > 0.3:
return (255, int(intensity * 150), 0) # Orange-red
else:
return (int(100 + intensity * 155), 0, 0) # Deep red
def generate_phoenix_body(volume, cx, cy, cz, t):
# Head
for dx in range(-3, 4):
for dy in range(-3, 4):
for dz in range(-3, 4):
if dx*dx + dy*dy + dz*dz <= 9:
intensity = 0.9 + 0.1 * np.sin(t * 3)
add_voxel(volume, cx+dx, cy+dy-8, cz+dz, get_fire_color(intensity))
# Neck
for i in range(8):
thickness = 2 + int(i * 0.3)
y_offset = -8 + i
for dx in range(-thickness, thickness+1):
for dz in range(-thickness, thickness+1):
if dx*dx + dz*dz <= thickness*thickness:
intensity = 0.8 + 0.2 * np.sin(t * 2 + i * 0.3)
add_voxel(volume, cx+dx, cy+y_offset, cz+dz, get_fire_color(intensity))
# Main body
for dx in range(-6, 7):
for dy in range(-4, 5):
for dz in range(-8, 9):
if (dx*dx/36 + dy*dy/16 + dz*dz/64) <= 1:
intensity = 0.7 + 0.3 * np.sin(t * 1.5 + dx * 0.1 + dz * 0.1)
add_voxel(volume, cx+dx, cy+dy, cz+dz, get_fire_color(intensity))
# Tail feathers
tail_length = 15
for i in range(tail_length):
p = i / tail_length
tail_sway = np.sin(t * 2 + i * 0.2) * 3
tail_width = int((1 - p) * 4)
for dx in range(-tail_width, tail_width+1):
for dy in range(-1, 2):
tail_x = cx + dx + int(tail_sway)
tail_y = cy + dy
tail_z = cz + 8 + i
intensity = 0.8 - p * 0.5 + 0.2 * np.sin(t * 3 + i * 0.1)
add_voxel(volume, tail_x, tail_y, tail_z, get_fire_color(intensity))
def generate_wings(volume, cx, cy, cz, t):
wing_beat = np.sin(t * 8) * 0.5 # Fast wing flapping
for side in [-1, 1]: # Left and right wings
for i in range(WING_SPAN):
wing_curve = np.sin(i * 0.2) * 6
wing_height = wing_beat * (WING_SPAN - i) * 0.3
for j in range(8): # Wing depth
p = j / 8
feather_intensity = 0.6 + 0.4 * (1 - p) + 0.2 * np.sin(t * 4 + i * 0.1)
wing_x = cx + side * (5 + i)
wing_y = cy + int(wing_height + wing_curve)
wing_z = cz - 4 + j
# Wing thickness
for dy in range(-2, 3):
add_voxel(volume, wing_x, wing_y + dy, wing_z, get_fire_color(feather_intensity))
def generate_fire_trail(volume, cx, cy, cz, t, trail_particles):
# Add new particle
trail_particles.append({
'x': cx + np.random.randint(-2, 3),
'y': cy + np.random.randint(-2, 3),
'z': cz + np.random.randint(8, 12),
'life': 1.0,
'vx': np.random.uniform(-0.5, 0.5),
'vy': np.random.uniform(-0.5, 0.5),
'vz': np.random.uniform(0.5, 1.5)
})
# Update and render existing particles
for particle in trail_particles[:]:
if particle['life'] <= 0:
trail_particles.remove(particle)
continue
particle['x'] += particle['vx']
particle['y'] += particle['vy']
particle['z'] += particle['vz']
particle['life'] -= 0.05
intensity = particle['life'] * 0.8
px, py, pz = int(particle['x']), int(particle['y']), int(particle['z'])
# Draw particle with some spread
for dx in range(-1, 2):
for dy in range(-1, 2):
for dz in range(-1, 2):
if np.random.random() < intensity:
add_voxel(volume, px+dx, py+dy, pz+dz, get_fire_color(intensity))
def generate_aura(volume, cx, cy, cz, t):
aura_radius = 15 + int(3 * np.sin(t * 2))
for dx in range(-aura_radius, aura_radius+1):
for dy in range(-aura_radius, aura_radius+1):
for dz in range(-aura_radius, aura_radius+1):
distance = np.sqrt(dx*dx + dy*dy + dz*dz)
if aura_radius - 3 < distance < aura_radius:
if np.random.random() < 0.1: # Sparse glow effect
intensity = 0.3 + 0.2 * np.sin(t * 4 + distance * 0.1)
add_voxel(volume, cx+dx, cy+dy, cz+dz, get_fire_color(intensity))
def generate_phoenix(volume, t, trail_particles):
# Phoenix flight path
flight_angle = t * 0.5
phoenix_x = CENTER_X + int(FLIGHT_RADIUS * np.cos(flight_angle))
phoenix_y = CENTER_Y + int(8 * np.sin(t * 0.8)) # Gentle up-down motion
phoenix_z = CENTER_Z + int(FLIGHT_RADIUS * np.sin(flight_angle))
generate_aura(volume, phoenix_x, phoenix_y, phoenix_z, t)
generate_phoenix_body(volume, phoenix_x, phoenix_y, phoenix_z, t)
generate_wings(volume, phoenix_x, phoenix_y, phoenix_z, t)
generate_fire_trail(volume, phoenix_x, phoenix_y, phoenix_z, t, trail_particles)
# Initialize encoder and trail particles
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
trail_particles = []
for frame in tqdm(range(FRAMES), desc="Generating phoenix"):
volume = np.zeros((SIZE, SIZE, SIZE, 4), dtype=np.uint8)
t = (frame / FRAMES) * 2*np.pi
generate_phoenix(volume, t, trail_particles)
enc.encode(splv.Frame(volume, lrAxis="x", udAxis="y", fbAxis="z"))
enc.finish()
print(f"Created {OUT_PATH}")
Next steps
- Adjust
WING_SPAN
to make the phoenix larger or smaller. - Modify
FLIGHT_RADIUS
to change the flight path size. - Experiment with
get_fire_color()
function for different flame colors (blue phoenix, ice phoenix, etc.). - Add multiple phoenixes by calling
generate_phoenix()
with different positions. - Increase
PHOENIX_SIZE
for a more detailed bird model.