Creating Solar System Narration.Txt Animation
Solar System Narration - 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, complete with moons and asteroid belts, all 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 animated solar flares
- Generates 8 planets with realistic orbital mechanics:
- Different sizes, colors, and orbital speeds
- Unique textures and surface details
- Some planets include orbiting moons
- Adds a rotating asteroid belt between Mars and Jupiter
- Creates twinkling stars in the background
- Animates everything for 12 seconds at 30 FPS with realistic orbital periods
- Outputs the file
solar_system_narration.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 with animated surface activity and glowing corona effects.
-
Planetary orbits Each planet follows an elliptical path around the sun with different orbital radii and speeds based on real solar system ratios.
-
Planet surfaces Planets are rendered as spheres with unique colors and procedural surface textures using noise functions.
-
Moons and asteroids Smaller objects orbit around planets, while a dense asteroid belt rotates between the inner and outer planets.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, with each celestial body having its own orbital period multiplier. -
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_narration.py
and run:
python solar_system_narration.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_narration.splv"
# Solar system settings
SUN_RADIUS = 6
PLANET_COUNT = 8
ASTEROID_COUNT = 150
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)
corona_color = (255, 150, 0)
# Main sun body
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 activity
flare = np.sin(dx*0.5 + t*3) * np.cos(dy*0.3 + t*2) * np.sin(dz*0.4 + t*4)
brightness = 1.0 + flare * 0.3
final_color = tuple(min(255, int(c * brightness)) for c in sun_color)
add_voxel(volume, cx+dx, cy+dy, cz+dz, final_color)
elif dist <= SUN_RADIUS + 2:
# Corona effect
intensity = max(0, 1.0 - (dist - SUN_RADIUS) / 2.0)
if np.random.random() < intensity * 0.3:
add_voxel(volume, cx+dx, cy+dy, cz+dz, corona_color)
def generate_planet(volume, cx, cy, cz, radius, color, t, planet_id):
# Planet surface with texture
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:
# Procedural surface texture
noise = np.sin(dx*0.8 + planet_id) * np.cos(dy*0.6 + planet_id) * np.sin(dz*0.7 + planet_id)
brightness = 0.8 + noise * 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_planets(volume, cx, cy, cz, t):
# Planet data: (name, distance, size, color, orbital_speed, has_moon)
planets = [
("Mercury", 15, 2, (169, 169, 169), 4.0, False),
("Venus", 20, 3, (255, 198, 73), 1.6, False),
("Earth", 25, 3, (100, 149, 237), 1.0, True),
("Mars", 32, 2, (205, 92, 92), 0.5, True),
("Jupiter", 45, 5, (255, 140, 0), 0.08, True),
("Saturn", 55, 4, (250, 222, 164), 0.03, True),
("Uranus", 65, 3, (79, 208, 231), 0.01, False),
("Neptune", 72, 3, (70, 130, 180), 0.006, False)
]
for i, (name, distance, size, color, speed, has_moon) in enumerate(planets):
# Calculate planet position
angle = t * speed + i * 0.5
px = cx + int(distance * np.cos(angle))
pz = cz + int(distance * np.sin(angle))
py = cy + int(2 * np.sin(angle * 0.3)) # Slight vertical variation
generate_planet(volume, px, py, pz, size, color, t, i)
# Add moon if planet has one
if has_moon:
moon_angle = t * 8.0 + i
moon_distance = size + 6
mx = px + int(moon_distance * np.cos(moon_angle))
mz = pz + int(moon_distance * np.sin(moon_angle))
generate_planet(volume, mx, py, mz, 1, (200, 200, 200), t, i + 100)
def generate_asteroid_belt(volume, cx, cy, cz, t):
asteroid_color = (139, 69, 19)
for i in range(ASTEROID_COUNT):
# Random but consistent positioning using i as seed
np.random.seed(i)
base_distance = 38 + np.random.random() * 8 # Between Mars and Jupiter
angle_offset = np.random.random() * 2 * np.pi
height_offset = (np.random.random() - 0.5) * 6
angle = t * 0.2 + angle_offset
ax = cx + int(base_distance * np.cos(angle))
az = cz + int(base_distance * np.sin(angle))
ay = cy + int(height_offset)
add_voxel(volume, ax, ay, az, asteroid_color)
def generate_stars(volume, t):
star_color = (255, 255, 255)
# Fixed star positions that twinkle
np.random.seed(42) # Fixed seed for consistent star positions
for i in range(200):
x = np.random.randint(0, SIZE)
y = np.random.randint(0, SIZE)
z = np.random.randint(0, SIZE)
# Only place stars far from center
if np.sqrt((x-CENTER_X)**2 + (y-CENTER_Y)**2 + (z-CENTER_Z)**2) > 80:
# Twinkling effect
if np.sin(t * 5 + i * 0.1) > 0.7:
add_voxel(volume, x, y, z, star_color)
def generate_scene(volume, t):
generate_stars(volume, t)
generate_sun(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_planets(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_asteroid_belt(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}")
Customization ideas
- Adjust orbital speeds: Modify the
speed
values in the planets list to make orbits faster or slower - Add more moons: Set
has_moon=True
for more planets or create multiple moons per planet - Change planet sizes: Edit the
size
parameter to create gas giants or smaller rocky planets - Modify asteroid density: Increase
ASTEROID_COUNT
for a thicker asteroid belt - Add comet trails: Create elongated objects with trailing particles
- Experiment with colors: Try different planet color schemes or add atmospheric effects
Educational notes
This animation demonstrates several astronomical concepts:
- Orbital mechanics: Inner planets orbit faster than outer planets (Kepler's laws)
- Scale relationships: While not to real scale, the relative sizes and distances give a sense of the solar system's structure
- Asteroid belt: Shows the rocky debris field between Mars and Jupiter
- Planetary moons: Illustrates how natural satellites orbit their parent planets
- Solar activity: The animated sun surface represents solar flares and coronal activity
Next steps
- Try adding Saturn's rings by creating a flat disk of particles around Saturn
- Implement realistic orbital ellipses instead of perfect circles
- Add Pluto and other dwarf planets in the outer solar system
- Create planetary rotation so planets spin as they orbit
- Experiment with lighting effects to show the day/night cycle on planets