Creating Fireworks Animation
3D Voxel Fireworks Animation Tutorial
This guide walks you through how to generate a looping 3D voxel animation of fireworks using SpatialStudio.
The script creates spectacular fireworks that burst, sparkle, and fade 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 multiple fireworks, each with:
- A bright explosive burst pattern
- Colorful particles that spread outward
- Sparkling trails that fade over time
- Glowing core effects
- Animates them exploding and fading for 10 seconds at 30 FPS
- Outputs the file
fireworks.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
). -
Firework lifecycle Each firework has launch, burst, expansion, and fade phases with realistic timing.
-
Particle system Hundreds of particles spread outward from burst points with gravity and air resistance.
-
Trail effects Particles leave glowing trails that gradually fade, creating streaking light effects.
-
Color palettes Different fireworks use distinct color schemes (red/gold, blue/white, green/yellow, etc.).
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, with staggered firework timing for continuous action. -
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 fireworks.py
and run:
python fireworks.py
Full Script
import numpy as np
from spatialstudio import splv
from tqdm import tqdm
# Scene setup
SIZE, FPS, SECONDS = 128, 30, 10
FRAMES = FPS * SECONDS
CENTER_X = CENTER_Y = CENTER_Z = SIZE // 2
OUT_PATH = "../outputs/fireworks.splv"
# Fireworks settings
FIREWORK_COUNT = 6
PARTICLE_COUNT = 150
BURST_SPEED = 0.8
GRAVITY = 0.02
FADE_SPEED = 0.95
class Firework:
def __init__(self, x, y, z, color_palette, start_time):
self.center = np.array([x, y, z])
self.colors = color_palette
self.start_time = start_time
self.particles = []
self.burst_created = False
def create_burst(self):
self.particles = []
for _ in range(PARTICLE_COUNT):
# Random spherical distribution
theta = np.random.uniform(0, 2*np.pi)
phi = np.random.uniform(0, np.pi)
speed = np.random.uniform(0.5, BURST_SPEED)
velocity = np.array([
speed * np.sin(phi) * np.cos(theta),
speed * np.sin(phi) * np.sin(theta),
speed * np.cos(phi)
])
color = self.colors[np.random.randint(len(self.colors))]
self.particles.append({
'pos': self.center.copy(),
'vel': velocity,
'color': color,
'life': 1.0,
'trail': []
})
self.burst_created = True
def add_voxel(volume, x, y, z, color, alpha=255):
x, y, z = int(x), int(y), int(z)
if 0 <= x < SIZE and 0 <= y < SIZE and 0 <= z < SIZE:
volume[x, y, z, :3] = color
volume[x, y, z, 3] = alpha
def update_firework(firework, dt):
if not firework.burst_created:
firework.create_burst()
return
for particle in firework.particles:
if particle['life'] > 0:
# Add current position to trail
particle['trail'].append(particle['pos'].copy())
if len(particle['trail']) > 8: # Limit trail length
particle['trail'].pop(0)
# Update physics
particle['pos'] += particle['vel']
particle['vel'][1] -= GRAVITY # Apply gravity (downward)
particle['vel'] *= 0.98 # Air resistance
particle['life'] *= FADE_SPEED
def render_firework(volume, firework):
for particle in firework.particles:
if particle['life'] > 0.01:
# Render particle
brightness = particle['life']
color = tuple(int(c * brightness) for c in particle['color'])
alpha = int(255 * brightness)
x, y, z = particle['pos']
add_voxel(volume, x, y, z, color, alpha)
# Render glowing core (brighter center)
if brightness > 0.5:
core_color = tuple(min(255, int(c * 1.3)) for c in color)
add_voxel(volume, x, y, z, core_color, 255)
# Render trail
for i, trail_pos in enumerate(particle['trail']):
trail_brightness = brightness * (i + 1) / len(particle['trail']) * 0.3
if trail_brightness > 0.05:
trail_color = tuple(int(c * trail_brightness) for c in particle['color'])
trail_alpha = int(255 * trail_brightness)
tx, ty, tz = trail_pos
add_voxel(volume, tx, ty, tz, trail_color, trail_alpha)
def generate_sparkles(volume, t):
# Add random sparkles in the air
sparkle_count = int(20 + 10 * np.sin(t * 2))
for _ in range(sparkle_count):
x = np.random.randint(SIZE)
y = np.random.randint(SIZE)
z = np.random.randint(SIZE)
if np.random.random() < 0.1: # 10% chance for each position
color = (255, 255, 200) # Golden sparkle
add_voxel(volume, x, y, z, color, 150)
def generate_scene(volume, t):
# Color palettes for different fireworks
color_palettes = [
[(255, 50, 50), (255, 150, 50), (255, 255, 100)], # Red/Orange/Yellow
[(50, 100, 255), (150, 200, 255), (255, 255, 255)], # Blue/White
[(50, 255, 50), (150, 255, 100), (255, 255, 150)], # Green/Yellow
[(255, 50, 255), (200, 100, 255), (255, 150, 255)], # Purple/Magenta
[(255, 100, 50), (255, 200, 100), (255, 255, 200)], # Orange/Gold
[(100, 255, 255), (200, 255, 255), (255, 255, 255)] # Cyan/White
]
# Create fireworks at different times and positions
fireworks = []
for i in range(FIREWORK_COUNT):
start_time = (i * 2.0) % 6.0 # Stagger start times
current_time = (t / (2*np.pi)) * 6.0
if current_time >= start_time and (current_time - start_time) < 3.0:
# Position fireworks around the scene
angle = (i / FIREWORK_COUNT) * 2*np.pi
radius = 20 + 15*np.sin(i * 0.7)
fx = CENTER_X + int(radius * np.cos(angle))
fy = CENTER_Y + int(10 * np.sin(t*0.5 + i))
fz = CENTER_Z + int(radius * np.sin(angle))
firework = Firework(fx, fy, fz, color_palettes[i], start_time)
dt = current_time - start_time
# Update firework multiple times to catch up
for _ in range(int(dt * 10)):
update_firework(firework, 0.1)
render_firework(volume, firework)
# Add ambient sparkles
generate_sparkles(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 fireworks"):
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}")
Customization options
- More fireworks: Increase
FIREWORK_COUNT
for a busier sky - Bigger explosions: Increase
PARTICLE_COUNT
for denser bursts - Custom colors: Edit
color_palettes
to create your own color schemes - Timing: Adjust stagger timing in the loop for different pacing
- Physics: Modify
GRAVITY
andBURST_SPEED
for different effects - Scene size: Change
SIZE
for larger or smaller viewing area
Next steps
- Try adding ground-level launching effects
- Experiment with different particle shapes (stars, hearts, etc.)
- Add sound synchronization timing markers
- Create themed color palettes (patriotic, Christmas, etc.)
- Implement different burst patterns (ring, heart, smiley face)