""" Track 03: NOTHING - Sphere Explosion/Fragmentation into Void """ import bpy import bmesh import math import random import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from utils import * def create_fragments(count=60, sphere_radius=2.0): """Create fragment particles from a sphere.""" fragments = [] mat = create_emission_material("FragmentMat", COLORS["nothing"], strength=1.0) for i in range(count): # Random position within sphere theta = random.uniform(0, 2 * math.pi) phi = random.uniform(0, math.pi) r = random.uniform(0, sphere_radius * 0.9) x = r * math.sin(phi) * math.cos(theta) y = r * math.sin(phi) * math.sin(theta) z = r * math.cos(phi) size = random.uniform(0.15, 0.4) bpy.ops.mesh.primitive_ico_sphere_add( radius=size, subdivisions=1, location=(x, y, z) ) frag = bpy.context.active_object frag.name = f"Fragment_{i:03d}" frag.data.materials.append(mat) # Store explosion direction if r > 0.1: direction = (x/r, y/r, z/r) else: direction = (random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)) norm = math.sqrt(sum(d*d for d in direction)) direction = tuple(d/norm for d in direction) fragments.append((frag, (x, y, z), direction)) return fragments def create_nothing_animation(): """Create the full Track 03 animation.""" clear_scene() setup_scene(background_color=(0.04, 0.04, 0.04, 1.0)) # Near black # Create camera camera = create_camera(location=(0, -10, 4), rotation=(1.15, 0, 0)) animate_camera_orbit(camera, center=(0, 0, 0), radius=10, height=4, start_frame=1, end_frame=TOTAL_FRAMES, revolutions=0.3) # Create initial sphere sphere = create_sphere(location=(0, 0, 0), radius=2.0, segments=32, rings=16, name="MainSphere") sphere_mat = create_emission_material("SphereMat", COLORS["nothing"], strength=1.2) sphere.data.materials.append(sphere_mat) # Animate sphere appearance keyframe_scale(sphere, 1, 0.01) keyframe_scale(sphere, 60, 1.0) keyframe_scale(sphere, 90, 1.0) # Explosion at frame 120 explosion_frame = 120 # Sphere disappears at explosion keyframe_scale(sphere, explosion_frame - 1, 1.0) keyframe_scale(sphere, explosion_frame, 0.01) # Create fragments fragments = create_fragments(count=60, sphere_radius=2.0) # Animate fragments for frag, start_pos, direction in fragments: # Start invisible keyframe_scale(frag, 1, 0.01) keyframe_scale(frag, explosion_frame - 1, 0.01) # Appear at explosion keyframe_scale(frag, explosion_frame, 1.0) keyframe_location(frag, explosion_frame, start_pos) # Drift outward into void drift_distance = random.uniform(6, 12) end_pos = tuple(start_pos[i] + direction[i] * drift_distance for i in range(3)) # Animate drift keyframe_location(frag, TOTAL_FRAMES, end_pos) # Fade out (scale down) keyframe_scale(frag, TOTAL_FRAMES - 60, 0.6) keyframe_scale(frag, TOTAL_FRAMES, 0.1) # Set linear interpolation for drift if frag.animation_data: for fc in frag.animation_data.action.fcurves: for kf in fc.keyframe_points: kf.interpolation = 'LINEAR' # Add some ambient dust particles dust = [] dust_mat = create_emission_material("DustMat", COLORS["nothing"], strength=0.3) for i in range(40): pos = [random.uniform(-6, 6) for _ in range(3)] bpy.ops.mesh.primitive_ico_sphere_add(radius=0.05, subdivisions=0, location=pos) d = bpy.context.active_object d.name = f"Dust_{i:03d}" d.data.materials.append(dust_mat) # Slow random drift end_pos = [pos[j] + random.uniform(-2, 2) for j in range(3)] keyframe_location(d, 1, pos) keyframe_location(d, TOTAL_FRAMES, end_pos) dust.append(d) return sphere, fragments, dust if __name__ == "__main__": create_nothing_animation() output_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) bpy.ops.wm.save_as_mainfile(filepath=os.path.join(output_dir, "exports", "track03_nothing.blend")) bpy.ops.export_scene.gltf( filepath=os.path.join(output_dir, "exports", "track03_nothing.gltf"), export_animations=True, export_format='GLTF_SEPARATE' ) bpy.ops.wm.alembic_export( filepath=os.path.join(output_dir, "exports", "track03_nothing.abc"), start=1, end=TOTAL_FRAMES ) print("Track 03 - Nothing: Export complete!")