Creating Solar System Animation
Solar System - 3D Voxel Animation Tutorial
This guide walks you through how to generate a looping 3D voxel animation of a solar system using SpatialStudio.
The script creates a miniature solar system with planets orbiting around a glowing sun 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 a central sun with a glowing effect
- Creates 6 planets of different sizes, each with:
- Unique orbital paths and speeds
- Realistic colors and textures
- Some planets have small moons
- Animates orbital motion for 12 seconds at 30 FPS
- Outputs the file
solar_system.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
). -
Central sun A bright yellow-orange sphere at the center with animated glow effects and surface detail.
-
Planetary orbits Each planet follows circular orbital paths at different distances and speeds using trigonometric functions.
-
Planet rendering Planets are drawn as spheres with color variations and surface textures using noise functions.
-
Moons Some planets have smaller orbiting bodies that follow sub-orbital paths.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, ensuring all orbital motions 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 solar_system.py
and run:
python solar_system.py
Full Script
import numpy as np
from spatialstudio import splv
from tqdm import tqdm
# Scene setup
SIZE, FPS, SECONDS = 128, 30, 12
FRAMES = FPS * SECONDS
CENTER_X = CENTER_Y = CENTER_Z = SIZE // 2
OUT_PATH = "../outputs/solar_system.splv"
# Solar system settings
SUN_RADIUS = 8
PLANET_COUNT = 6
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_sun(volume, cx, cy, cz, t):
sun_color = (255, 200, 50)
glow_color = (255, 150, 0)
# Main sun body with animated surface
for dx in range(-SUN_RADIUS, SUN_RADIUS+1):
for dy in range(-SUN_RADIUS, SUN_RADIUS+1):
for dz in range(-SUN_RADIUS, SUN_RADIUS+1):
dist = np.sqrt(dx*dx + dy*dy + dz*dz)
if dist <= SUN_RADIUS:
# Animated surface detail
surface_noise = np.sin(dx*0.4 + t*2) + np.cos(dy*0.3 + t*1.5) + np.sin(dz*0.5 + t*2.5)
brightness = 1.0 + surface_noise * 0.15
final_color = tuple(min(255, int(c * brightness)) for c in sun_color)
add_voxel(volume, cx+dx, cy+dy, cz+dz, final_color)
# Glow effect
glow_radius = SUN_RADIUS + 3
for dx in range(-glow_radius, glow_radius+1):
for dy in range(-glow_radius, glow_radius+1):
for dz in range(-glow_radius, glow_radius+1):
dist = np.sqrt(dx*dx + dy*dy + dz*dz)
if SUN_RADIUS < dist <= glow_radius:
glow_intensity = 1.0 - (dist - SUN_RADIUS) / 3.0
if np.random.random() < glow_intensity * 0.3:
add_voxel(volume, cx+dx, cy+dy, cz+dz, glow_color)
def generate_planet(volume, cx, cy, cz, radius, color, t, planet_id):
for dx in range(-radius, radius+1):
for dy in range(-radius, radius+1):
for dz in range(-radius, radius+1):
dist = np.sqrt(dx*dx + dy*dy + dz*dz)
if dist <= radius:
# Planet surface texture
texture = np.sin(dx*0.5 + planet_id) + np.cos(dy*0.4 + planet_id*2) + np.sin(dz*0.6 + t*0.5)
brightness = 1.0 + texture * 0.2
final_color = tuple(min(255, max(50, int(c * brightness))) for c in color)
add_voxel(volume, cx+dx, cy+dy, cz+dz, final_color)
def generate_moon(volume, cx, cy, cz, t, planet_angle, moon_orbit_radius):
moon_radius = 2
moon_color = (180, 180, 180)
# Moon orbits around planet position
moon_angle = t * 4.0 # Moons orbit faster
moon_x = cx + int(moon_orbit_radius * np.cos(moon_angle))
moon_z = cz + int(moon_orbit_radius * np.sin(moon_angle))
for dx in range(-moon_radius, moon_radius+1):
for dy in range(-moon_radius, moon_radius+1):
for dz in range(-moon_radius, moon_radius+1):
if dx*dx + dy*dy + dz*dz <= moon_radius*moon_radius:
add_voxel(volume, moon_x+dx, cy+dy, moon_z+dz, moon_color)
def generate_planets(volume, cx, cy, cz, t):
# Planet data: (orbit_radius, planet_radius, color, orbital_speed, has_moon, moon_orbit_radius)
planets = [
(20, 3, (255, 100, 100), 1.2, False, 0), # Mercury-like (red)
(28, 4, (255, 200, 100), 1.0, False, 0), # Venus-like (yellow)
(36, 4, (100, 150, 255), 0.8, True, 8), # Earth-like (blue) with moon
(45, 3, (255, 100, 50), 0.6, True, 6), # Mars-like (red-orange) with moon
(55, 6, (200, 150, 100), 0.4, True, 12), # Jupiter-like (brown) with moon
(65, 5, (150, 150, 200), 0.3, False, 0), # Saturn-like (pale blue)
]
for i, (orbit_radius, planet_radius, color, speed, has_moon, moon_orbit_radius) in enumerate(planets):
# Calculate planet position
angle = t * speed + i * np.pi / 3 # Offset each planet's starting position
px = cx + int(orbit_radius * np.cos(angle))
pz = cz + int(orbit_radius * np.sin(angle))
py = cy + int(3 * np.sin(t * 0.5 + i)) # Slight vertical oscillation
# Generate planet
generate_planet(volume, px, py, pz, planet_radius, color, t, i)
# Generate moon if planet has one
if has_moon:
generate_moon(volume, px, py, pz, t, angle, moon_orbit_radius)
def generate_orbital_trails(volume, cx, cy, cz, t):
# Optional: Add faint orbital trail particles
trail_color = (50, 50, 80)
orbit_radii = [20, 28, 36, 45, 55, 65]
for radius in orbit_radii:
for angle_step in range(0, 360, 15):
angle = np.radians(angle_step)
tx = cx + int(radius * np.cos(angle))
tz = cz + int(radius * np.sin(angle))
if np.random.random() < 0.1: # Sparse trail effect
add_voxel(volume, tx, cy, tz, trail_color)
def generate_scene(volume, t):
# Generate sun at center
generate_sun(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
# Generate orbiting planets
generate_planets(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
# Generate orbital trails (optional visual effect)
generate_orbital_trails(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 solar system"):
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
- Modify the
planets
array to add more celestial bodies or change their properties - Adjust orbital speeds to create different animation rhythms
- Add asteroid belts by generating small particles between planet orbits
- Experiment with different planet colors and textures
- Create elliptical orbits by modifying the trigonometric calculations
- Add Saturn-like rings around larger planets