🎈 Floating Balloons
This guide walks you through how to generate a looping 3D voxel animation of balloons using SpatialStudio.
The script creates colorful balloons that float, sway, and shine 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 8 balloons, each with:
- A round voxel body
- A swaying string
- White highlights to look glossy
-
Animates them floating in place for 8 seconds at 30 FPS
-
Outputs the file
balloons.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
). -
Balloon body Balloons are drawn as spheres with subtle sine-wave variations for texture.
-
String Each balloon gets a wavy brown string that swings naturally.
-
Highlights White voxels are added to the upper side of balloons to simulate light reflection.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, ensuring the 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 balloons.py
and run:
python balloons.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/balloons.splv"
# Balloon settings
BALLOON_COUNT = 8
BALLOON_RADIUS = 8
STRING_LENGTH = 25
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_balloon_body(volume, cx, cy, cz, color, size, t):
for dx in range(-size, size+1):
for dy in range(-size, size+1):
for dz in range(-size, size+1):
if np.sqrt(dx*dx + dy*dy + dz*dz) <= size:
surface = int(np.sin(dx*0.3 + dy*0.2 + dz*0.1 + t*0.5) * 2)
brightness = 1.0 + surface * 0.1
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_string(volume, cx, cy, cz, t):
string_color = (139, 69, 19)
sway = np.sin(t * 2.0 + cx * 0.01) * 3
for i in range(STRING_LENGTH):
p = i / STRING_LENGTH
x = cx + int(sway * p)
y = cy - BALLOON_RADIUS - i
z = cz + int(np.sin(t * 1.5 + i*0.2) * p)
add_voxel(volume, x, y, z, string_color)
def generate_cluster(volume, cx, cy, cz, t):
colors = [
(255,0,0), (255,165,0), (255,255,0), (0,255,0),
(0,0,255), (138,43,226), (255,20,147), (255,105,180),
]
for i in range(BALLOON_COUNT):
angle = (i / BALLOON_COUNT) * 2*np.pi
radius = 15 + 8*np.sin(i * 0.5)
fx = int(radius * np.cos(angle + t*0.3))
fz = int(radius * np.sin(angle + t*0.2))
fy = int(5 * np.sin(t*1.5 + i*0.4))
bx, by, bz = cx+fx, cy+fy, cz+fz
size = max(5, BALLOON_RADIUS + int(2*np.sin(t*0.8 + i*0.3)))
color = colors[i % len(colors)]
generate_balloon_body(volume, bx, by, bz, color, size, t)
generate_string(volume, bx, by, bz, t)
def generate_highlights(volume, cx, cy, cz, t):
for i in range(BALLOON_COUNT):
angle = (i / BALLOON_COUNT) * 2*np.pi
radius = 15 + 8*np.sin(i * 0.5)
fx = int(radius * np.cos(angle + t*0.3))
fz = int(radius * np.sin(angle + t*0.2))
fy = int(5 * np.sin(t*1.5 + i*0.4))
bx, by, bz = cx+fx, cy+fy, cz+fz
for dx in range(-2, 3):
for dy in range(-2, 3):
for dz in range(-2, 3):
if dx*dx + dy*dy + dz*dz <= 4:
add_voxel(volume, bx-3+dx, by+3+dy, bz-2+dz, (255,255,255))
def generate_scene(volume, t):
generate_cluster(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_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 balloons"):
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
BALLOON_COUNT
to spawn more balloons. - Edit
colors
for a new palette. - Make balloons rise slowly by adding
+ int(t*5)
to the Y position.