diff --git a/.gitignore b/.gitignore index e7b0893..e838828 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,8 @@ __pycache__/ .matplotlib/ .venv/ .DS_Store +*.blend *.blend1 +# Local debug / probe frames (never products) +output/**/*_smoke_* +output/**/overlay_probe.png diff --git a/README.md b/README.md index 7874b7f..239d7fc 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ SOLSYS/ │ ├── solar_system_animator.py # SolarSystemAnimator, renderAllAnimations │ ├── camera_controller.py # CameraController │ ├── animation_styles.py # Light/dark styles and timing constants +│ ├── blender_body_sprites.py # Blender spin-loop frames for cinematic Earth/Moon │ └── scenes/ │ ├── inner_system.py # Fixed 2D inner-system scene │ ├── zoom_tour.py # Staged 3D Oort → inner zoom @@ -84,6 +85,7 @@ SOLSYS/ ├── tests/ # Unit tests (stdlib unittest) │ ├── test_frame_transform.py # Sol ↔ α Cen frame transform │ ├── test_sol_centauri_cinematic.py # Sol → α Cen cinematic helpers +│ ├── test_blender_body_sprites.py # Blender sprites + cinematic billboards │ └── test_blender_pipeline.py # Blender export / ingest scaffold │ ├── solsys/ # Shared libraries (no plotting) @@ -111,7 +113,8 @@ SOLSYS/ │ ├── 2d/ # inner_solar_system_{light,dark}.gif │ ├── 3d/ # solar_system_{light,dark}.gif │ ├── alpha_centauri/ # ab / system_wide / proxima_planets GIFs -│ ├── sol_centauri/ # sol_centauri_cinematic_{light,dark}.gif +│ ├── sol_centauri/ # classic cinematic GIFs +│ │ └── blender/ # cinematic with textured Earth/Moon │ ├── barnards_star/ # barnards_star_planets_{light,dark}.gif │ ├── trappist_1/ # trappist_1_planets_{light,dark}.gif │ ├── tabbys_star/ # tabbys_star_dust_{light,dark}.gif @@ -305,6 +308,7 @@ Or render by product: .venv/bin/python render.py animate --dimension 3d .venv/bin/python render.py animate --system alpha_centauri .venv/bin/python render.py animate --system sol_centauri +.venv/bin/python render.py animate --system sol_centauri --blender-bodies .venv/bin/python render.py animate --system barnards_star .venv/bin/python render.py animate --system trappist_1 .venv/bin/python render.py animate --system tabbys_star @@ -314,12 +318,27 @@ Or render by product: .venv/bin/python render.py static --dimension 2d .venv/bin/python render.py neighborhood --ly 10 .venv/bin/python render.py blender --body Earth -.venv/bin/python render.py blender --body Moon --flyby # Moon close-up GIFs .venv/bin/python render.py blender --body Earth --load # optional debug ingest .venv/bin/python render.py blender --body Earth --flyby # light/dark close-up GIFs +.venv/bin/python render.py blender --body Moon --flyby # Moon close-up GIFs +.venv/bin/python render.py blender --body Earth --spin # RGBA day/night loop for cinematic +.venv/bin/python render.py blender --body Moon --spin # Moon spin loop for cinematic +.venv/bin/python render.py blender --pipeline # Earth+Moon spins + blender cinematic +``` + +Blender close-ups: `render.py blender` exports Keplerian body-scene JSON (`#11`). `render.py blender --flyby` renders a body-centered close-up via EEVEE (`#12`/`#36`/`#41`) to `output/animate/blender/{planets,moons}//` (`*_flyby_{light,dark}.gif`). `render.py blender --spin` writes a fixed-camera transparent PNG day/night loop (`*_spin_/frame_*.png`) for cinematic reuse. Texture packs live under `data/textures/bodies/`. + +Blender → cinematic workflow: + +```bash +.venv/bin/python render.py blender --body Earth --spin +.venv/bin/python render.py blender --body Moon --spin +.venv/bin/python render.py animate --system sol_centauri --blender-bodies +# or one-shot: +.venv/bin/python render.py blender --pipeline ``` -Blender close-ups: `render.py blender` exports Keplerian body-scene JSON (`#11`). `render.py blender --flyby` renders a body-centered close-up via EEVEE (`#12`/`#36`/`#41`) to `output/animate/blender/{planets,moons}//` (`*_flyby_{light,dark}.gif`). Texture packs live under `data/textures/bodies/`. +Sol → Centauri cinematic with `--blender-bodies` (issue #42) keeps the same camera tour but composites those Blender spin frames at Earth/Moon **each `FuncAnimation` frame** (not GIF concat). Outputs go under `output/animate/sol_centauri/blender/` (classic dotted GIFs stay in `output/animate/sol_centauri/`). Alpha Centauri (issue #1) is one `system_id` covering A, B, and Proxima. Animations render to `output/animate/alpha_centauri/`: @@ -327,17 +346,20 @@ Alpha Centauri (issue #1) is one `system_id` covering A, B, and Proxima. Animati - `alpha_centauri_system_{light,dark}.gif` — wide triple with Proxima (~8.7 kau) - `proxima_planets_{light,dark}.gif` — confirmed Proxima planets (via shared `exoplanet_system` animator) -Sol → Alpha Centauri cinematic (issue #10) flies from our solar system to the A–B close-up, zooms back out, then finishes on Proxima and its planets — all in Sol XYZ via `SolCentauriFrameTransform`. Animations render to `output/animate/sol_centauri/`: +Sol → Alpha Centauri cinematic (issue #10) flies from our solar system to the A–B close-up, zooms back out, then finishes on Proxima and its planets — all in Sol XYZ via `SolCentauriFrameTransform`: -- `sol_centauri_cinematic_{light,dark}.gif` — Sol → AB → wide → Proxima planets +- `output/animate/sol_centauri/sol_centauri_cinematic_{light,dark}.gif` — classic scatter-dot bodies +- `output/animate/sol_centauri/blender/sol_centauri_cinematic_blender_{light,dark}.gif` — Blender spin billboards on Earth/Moon (`--blender-bodies` or `blender --pipeline`) -Earth Blender close-up (issues #12 / #36) is a body-centered EEVEE view of Earth with NASA Blue Marble texture (light/dark). Render with `render.py blender --body Earth --flyby` to `output/animate/blender/planets/earth/`: +Earth Blender close-up (issues #12 / #36) is a body-centered EEVEE view of Earth with NASA Blue Marble texture (light/dark). Render with `render.py blender --body Earth --flyby` / `--spin` to `output/animate/blender/planets/earth/`: - `earth_flyby_{light,dark}.gif` — textured sphere, clouds, thin atmosphere limb, elevated turntable camera + spin +- `earth_spin_{light,dark}/frame_*.png` — fixed-camera RGBA day/night loop for the cinematic (`--spin`) Moon Blender close-up (issue #41) uses the shared pack path with an LRO LROC color map (airless — no atmosphere/clouds) under `output/animate/blender/moons/moon/`: - `moon_flyby_{light,dark}.gif` — textured lunar sphere, elevated turntable camera + spin +- `moon_spin_{light,dark}/frame_*.png` — fixed-camera RGBA spin loop for the cinematic (`--spin`) Barnard's Star (issue #15) is a nearby M dwarf (~6 ly) with four confirmed sub-Earth planets. Animations render via `exoplanet_system` to `output/animate/barnards_star/`: diff --git a/animate/blender_body_sprites.py b/animate/blender_body_sprites.py new file mode 100644 index 0000000..7fbca83 --- /dev/null +++ b/animate/blender_body_sprites.py @@ -0,0 +1,118 @@ +"""Load Blender spin-loop RGBA frames for the Sol→Centauri cinematic. + +Assets come from ``render.py blender --spin`` (fixed camera, full rotation, +transparent PNGs). The cinematic indexes into that loop each ``update()`` — +rendered together, not GIF-concatenated afterward. + +Earth: free spin by animation frame. +Moon: tidally locked — spin index follows orbital mean anomaly (one turn / orbit). +""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +from PIL import Image + +from animate.scenes.blender.flyby_scene import spinFramesDirectory + +DEFAULT_DISPLAY_RESOLUTION = 384 + + +def loadSpinLoopFrames( + bodyName: str, + theme: str, + *, + outputDirectory: Path | str = 'output/animate/blender', +) -> list[np.ndarray] | None: + """Load persistent ``*_spin_/frame_*.png`` as float RGBA arrays.""" + directory = spinFramesDirectory(bodyName, theme, outputDirectory=outputDirectory) + paths = sorted(directory.glob('frame_*.png')) + if not paths: + return None + + frames: list[np.ndarray] = [] + for path in paths: + with Image.open(path) as image: + frames.append(np.asarray(image.convert('RGBA'), dtype=np.float32) / 255.0) + return frames + + +def _resizeRgba(rgba: np.ndarray, size: int) -> np.ndarray: + if rgba.shape[0] == size and rgba.shape[1] == size: + return rgba + image = Image.fromarray((np.clip(rgba, 0.0, 1.0) * 255.0).astype(np.uint8), mode='RGBA') + image = image.resize((size, size), Image.Resampling.LANCZOS) + return np.asarray(image, dtype=np.float32) / 255.0 + + +def tidalLockFrameIndex(orbitalPhaseRad: float, frameCount: int) -> int: + """Map orbital mean anomaly → spin-loop index (synchronous rotation).""" + return int(np.floor(tidalLockFramePosition(orbitalPhaseRad, frameCount))) % frameCount + + +def tidalLockFramePosition(orbitalPhaseRad: float, frameCount: int) -> float: + """Fractional spin-loop position for smooth tidally locked blending.""" + if frameCount <= 0: + raise ValueError('frameCount must be positive') + turns = float(orbitalPhaseRad) / (2.0 * np.pi) + return turns * frameCount + + +def _blendSpinFrames(frames: list[np.ndarray], position: float) -> np.ndarray: + """Linear blend between neighboring loop frames (seamless at wrap).""" + count = len(frames) + wrapped = position % count + index0 = int(np.floor(wrapped)) % count + index1 = (index0 + 1) % count + blend = float(wrapped - np.floor(wrapped)) + if blend <= 1e-6: + return frames[index0] + if blend >= 1.0 - 1e-6: + return frames[index1] + return frames[index0] * (1.0 - blend) + frames[index1] * blend + + +class BlenderBodySpriteAtlas: + """Theme-scoped Earth/Moon Blender spin loops for in-animation billboards.""" + + def __init__(self, theme: str, *, outputDirectory: Path | str = 'output/animate/blender'): + self.theme = theme + self.earth = loadSpinLoopFrames('Earth', theme, outputDirectory=outputDirectory) + self.moon = loadSpinLoopFrames('Moon', theme, outputDirectory=outputDirectory) + + @property + def hasEarth(self) -> bool: + return bool(self.earth) + + @property + def hasMoon(self) -> bool: + return bool(self.moon) + + def earthFrame( + self, frame: int, *, resolution: int = DEFAULT_DISPLAY_RESOLUTION + ) -> np.ndarray | None: + if not self.earth: + return None + # Fractional position so Earth also eases between PNG samples. + position = float(frame) % len(self.earth) + rgba = _blendSpinFrames(self.earth, position) + return _resizeRgba(rgba, resolution) + + def moonFrame( + self, + frame: int, + *, + orbitalPhaseRad: float | None = None, + resolution: int = DEFAULT_DISPLAY_RESOLUTION, + ) -> np.ndarray | None: + """Moon disk. Prefer ``orbitalPhaseRad`` for tidal lock; ``frame`` is fallback.""" + if not self.moon: + return None + if orbitalPhaseRad is None: + position = float(frame) % len(self.moon) + else: + position = tidalLockFramePosition(orbitalPhaseRad, len(self.moon)) + rgba = _blendSpinFrames(self.moon, position) + return _resizeRgba(rgba, resolution) diff --git a/animate/scenes/blender/README.md b/animate/scenes/blender/README.md index fd3ea15..c349c98 100644 --- a/animate/scenes/blender/README.md +++ b/animate/scenes/blender/README.md @@ -50,6 +50,31 @@ Earth uses NASA Blue Marble color + cloud maps under `data/textures/bodies/earth .venv/bin/python render.py blender --body Moon --flyby ``` +Fixed-camera day/night spin loops (RGBA PNGs for cinematic reuse): + +```bash +.venv/bin/python render.py blender --body Earth --spin +.venv/bin/python render.py blender --body Moon --spin +``` + +End-to-end (Earth + Moon spins, then Sol→Centauri cinematic compositing those frames): + +```bash +.venv/bin/python render.py blender --pipeline +``` + +Products land in: + +```text +output/animate/blender/{planets,moons}// + *_flyby_{light,dark}.gif # gallery close-ups + *_spin_{light,dark}/frame_*.png # cinematic spin loops (transparent) + *_spin_{light,dark}.gif # preview of the spin loop +output/animate/sol_centauri/blender/ # cinematic with Earth/Moon spin billboards +``` + +Local debug leftovers (`.blend`, `*_smoke_*`, `overlay_probe.png`) are gitignored / not products. + ### Debug ingest (orbit view, not the flyby) ```bash diff --git a/animate/scenes/blender/flyby_camera.py b/animate/scenes/blender/flyby_camera.py index 441ae92..586e46c 100644 --- a/animate/scenes/blender/flyby_camera.py +++ b/animate/scenes/blender/flyby_camera.py @@ -55,3 +55,34 @@ def buildFlybyCameraPath( ) ) return tuple(samples) + + +def buildSpinCameraPath( + displayRadiusAu: float, + frameCount: int = 48, + *, + elevationDeg: float = 18.0, + azimuthDeg: float = 35.0, +) -> tuple[FlybyCameraSample, ...]: + """Fixed camera + full 360° body spin (seamless loop for cinematic reuse).""" + if frameCount < 2: + raise ValueError('frameCount must be >= 2') + cameraAu = flybyCameraLocation( + 0, + 2, + displayRadiusAu, + elevationDeg=elevationDeg, + azimuthStartDeg=azimuthDeg, + azimuthSweepDeg=0.0, + ) + samples: list[FlybyCameraSample] = [] + for frame in range(frameCount): + # Omit 360° on the last frame so index 0 and N line up when looping. + samples.append( + FlybyCameraSample( + frame=frame, + cameraAu=cameraAu, + bodyRotationDeg=(frame / frameCount) * 360.0, + ) + ) + return tuple(samples) diff --git a/animate/scenes/blender/flyby_scene.py b/animate/scenes/blender/flyby_scene.py index 31a1f8a..2c47b9b 100644 --- a/animate/scenes/blender/flyby_scene.py +++ b/animate/scenes/blender/flyby_scene.py @@ -13,8 +13,13 @@ from animate.scenes.blender.body_appearance import appearanceForCatalogName from animate.scenes.blender.body_scene import buildBodyScene -from animate.scenes.blender.export_body import DEFAULT_OUTPUT_DIRECTORY, bodyStem, exportBodyScene -from animate.scenes.blender.flyby_camera import buildFlybyCameraPath +from animate.scenes.blender.export_body import ( + DEFAULT_OUTPUT_DIRECTORY, + bodyOutputDirectory, + bodyStem, + exportBodyScene, +) +from animate.scenes.blender.flyby_camera import buildFlybyCameraPath, buildSpinCameraPath from animate.scenes.blender.render_flyby import JOB_SCHEMA_ID FLYBY_EXTENSION_POINT = 'animate.scenes.blender.flyby_scene.renderPlanetFlyby' @@ -24,6 +29,9 @@ DEFAULT_FLYBY_FRAMES = 72 DEFAULT_FLYBY_RESOLUTION = 640 DEFAULT_FLYBY_FPS = 18 +DEFAULT_SPIN_FRAMES = 48 +DEFAULT_SPIN_RESOLUTION = 512 +DEFAULT_SPIN_FPS = 20 def preparePlanetFlybyExport( @@ -40,24 +48,26 @@ def preparePlanetFlybyExport( ) -def buildFlybyJob( - bodyName: str = 'Earth', +def _bodyJobSkeleton( + bodyName: str, *, - theme: Theme = 'dark', - frameCount: int = DEFAULT_FLYBY_FRAMES, - resolution: int = DEFAULT_FLYBY_RESOLUTION, - fps: int = DEFAULT_FLYBY_FPS, + theme: Theme, + frameCount: int, + resolution: int, + fps: int, framesDirectory: Path | str, + cameraPath, + filmTransparent: bool = False, + mode: str = 'flyby', ) -> dict: - """Build a Blender flyby job dict from PlanetCatalog/MoonCatalog + camera path.""" if theme not in ('light', 'dark'): raise ValueError(f'theme must be light or dark, got {theme!r}') bodyScene = buildBodyScene(bodyName, frameCount=max(frameCount, 2)) - cameraPath = buildFlybyCameraPath(bodyScene.body.displayRadiusAu, frameCount=frameCount) appearance = appearanceForCatalogName(bodyScene.body.name) job: dict = { 'schema': JOB_SCHEMA_ID, 'theme': theme, + 'mode': mode, 'body': { 'name': bodyScene.body.name, 'kind': bodyScene.body.kind, @@ -78,12 +88,78 @@ def buildFlybyJob( 'outputDirectory': str(Path(framesDirectory)), 'resolution': resolution, 'fps': fps, + 'filmTransparent': filmTransparent, } if appearance is not None: job['appearance'] = appearance.toJobDict() return job +def buildFlybyJob( + bodyName: str = 'Earth', + *, + theme: Theme = 'dark', + frameCount: int = DEFAULT_FLYBY_FRAMES, + resolution: int = DEFAULT_FLYBY_RESOLUTION, + fps: int = DEFAULT_FLYBY_FPS, + framesDirectory: Path | str, +) -> dict: + """Build a Blender flyby job dict from PlanetCatalog/MoonCatalog + camera path.""" + bodyScene = buildBodyScene(bodyName, frameCount=max(frameCount, 2)) + cameraPath = buildFlybyCameraPath(bodyScene.body.displayRadiusAu, frameCount=frameCount) + return _bodyJobSkeleton( + bodyName, + theme=theme, + frameCount=frameCount, + resolution=resolution, + fps=fps, + framesDirectory=framesDirectory, + cameraPath=cameraPath, + filmTransparent=False, + mode='flyby', + ) + + +def buildSpinJob( + bodyName: str = 'Earth', + *, + theme: Theme = 'dark', + frameCount: int = DEFAULT_SPIN_FRAMES, + resolution: int = DEFAULT_SPIN_RESOLUTION, + fps: int = DEFAULT_SPIN_FPS, + framesDirectory: Path | str, +) -> dict: + """Fixed-camera full-rotation job (transparent PNGs for cinematic reuse).""" + bodyScene = buildBodyScene(bodyName, frameCount=max(frameCount, 2)) + cameraPath = buildSpinCameraPath(bodyScene.body.displayRadiusAu, frameCount=frameCount) + return _bodyJobSkeleton( + bodyName, + theme=theme, + frameCount=frameCount, + resolution=resolution, + fps=fps, + framesDirectory=framesDirectory, + cameraPath=cameraPath, + filmTransparent=True, + mode='spin', + ) + + +def spinFramesDirectory( + bodyName: str, + theme: Theme, + *, + outputDirectory: Path | str = DEFAULT_OUTPUT_DIRECTORY, +) -> Path: + """Persistent PNG loop: ``…/planets/earth/earth_spin_dark/frame_*.png``.""" + bodyScene = buildBodyScene(bodyName, frameCount=2) + stem = bodyStem(bodyName) + return ( + bodyOutputDirectory(bodyScene.body.kind, bodyName, root=outputDirectory) + / f'{stem}_spin_{theme}' + ) + + def writeFlybyJob(job: dict, path: Path | str) -> Path: outputPath = Path(path) outputPath.parent.mkdir(parents=True, exist_ok=True) @@ -185,3 +261,55 @@ def renderPlanetFlyby( written.append(gifPath) print(f'Wrote flyby GIF → {gifPath}') return tuple(written) + + +def renderPlanetSpin( + bodyName: str = 'Earth', + *, + theme: Theme | Literal['all'] = 'all', + frameCount: int = DEFAULT_SPIN_FRAMES, + resolution: int = DEFAULT_SPIN_RESOLUTION, + fps: int = DEFAULT_SPIN_FPS, + outputDirectory: Path | str = DEFAULT_OUTPUT_DIRECTORY, +) -> tuple[Path, ...]: + """Render fixed-camera RGBA spin loops for cinematic body billboards.""" + themes: tuple[Theme, ...] + if theme == 'all': + themes = ('light', 'dark') + elif theme in ('light', 'dark'): + themes = (theme,) + else: + raise ValueError(f'theme must be light, dark, or all, got {theme!r}') + + outputRoot = Path(outputDirectory) + outputRoot.mkdir(parents=True, exist_ok=True) + bodyDirectory = preparePlanetFlybyExport(bodyName, outputDirectory=outputRoot).parent + stem = bodyStem(bodyName) + written: list[Path] = [] + + for themeName in themes: + framesDirectory = spinFramesDirectory(bodyName, themeName, outputDirectory=outputRoot) + if framesDirectory.exists(): + shutil.rmtree(framesDirectory) + framesDirectory.mkdir(parents=True, exist_ok=True) + job = buildSpinJob( + bodyName, + theme=themeName, + frameCount=frameCount, + resolution=resolution, + fps=fps, + framesDirectory=framesDirectory, + ) + jobPath = bodyDirectory / f'{stem}_spin_{themeName}_job.json' + writeFlybyJob(job, jobPath) + _runBlenderFlybyJob(jobPath) + framePaths = sorted(framesDirectory.glob('frame_*.png')) + if not framePaths: + raise RuntimeError(f'No spin frames rendered for theme={themeName}') + # Preview GIF (RGB) for the gallery; cinematic loads the RGBA PNGs. + gifPath = bodyDirectory / f'{stem}_spin_{themeName}.gif' + assembleGifFromPngs(framePaths, gifPath, fps=fps) + written.append(framesDirectory) + print(f'Wrote spin loop → {framesDirectory} ({len(framePaths)} frames)') + print(f'Wrote spin preview GIF → {gifPath}') + return tuple(written) diff --git a/animate/scenes/blender/render_flyby.py b/animate/scenes/blender/render_flyby.py index cc38949..3d2130c 100644 --- a/animate/scenes/blender/render_flyby.py +++ b/animate/scenes/blender/render_flyby.py @@ -62,45 +62,185 @@ def _clearSceneObjects(bpy: Any) -> None: def _assignSphericalUvs(builder: Any) -> None: - """Equirectangular UVs from vertex directions (shared by planets/moons/asteroids).""" + """Equirectangular UVs from vertex directions, seam-safe per face. + + Loops are per-corner, so faces that straddle U=0/1 get their low-U corners + shifted by +1. That stops interpolation from smearing across the whole map + (the vertical “date line” artifact on Earth). + """ uvLayer = builder.loops.layers.uv.new('UVMap') for face in builder.faces: for loop in face.loops: direction = loop.vert.co.normalized() - u = 0.5 + math.atan2(direction.y, direction.x) / (2.0 * math.pi) + u = (0.5 + math.atan2(direction.y, direction.x) / (2.0 * math.pi)) % 1.0 v = 0.5 + math.asin(max(-1.0, min(1.0, direction.z))) / math.pi loop[uvLayer].uv = (u, v) + us = [loop[uvLayer].uv[0] for loop in face.loops] + if max(us) - min(us) > 0.5: + for loop in face.loops: + u, v = loop[uvLayer].uv + if u < 0.5: + loop[uvLayer].uv = (u + 1.0, v) -def _createUvSphere(bpy: Any, name: str, radius: float) -> Any: +def _createBodySphere(bpy: Any, name: str, radius: float) -> Any: + """Icosphere body mesh — UV spheres keep a geometric meridian crease. + + Equirectangular maps are sampled via Environment Texture (object direction), + so we do not need UV-sphere topology. UVs are still assigned as a fallback + for Image Texture paths. + """ import bmesh # type: ignore[import-not-found] mesh = bpy.data.meshes.new(f'{name}Mesh') builder = bmesh.new() - bmesh.ops.create_uvsphere(builder, u_segments=96, v_segments=48, radius=radius) + # subdivisions=5 ≈ 20k tris — smooth limb without a date-line edge. + bmesh.ops.create_icosphere(builder, subdivisions=5, radius=radius) bmesh.ops.recalc_face_normals(builder, faces=builder.faces) _assignSphericalUvs(builder) builder.to_mesh(mesh) builder.free() for polygon in mesh.polygons: polygon.use_smooth = True + if hasattr(mesh, 'use_auto_smooth'): + mesh.use_auto_smooth = False obj = bpy.data.objects.new(name, mesh) bpy.context.scene.collection.objects.link(obj) return obj -def _loadImageTexture(bpy: Any, nodeTree: Any, imagePath: Path, *, label: str) -> Any | None: +def _createUvSphere(bpy: Any, name: str, radius: float) -> Any: + """Backward-compatible alias — prefer ``_createBodySphere``.""" + return _createBodySphere(bpy, name, radius) + + +def _ensureEquirectangularVector(bpy: Any, nodeTree: Any) -> Any: + """Per-fragment equirectangular UV from object normals (no mesh UV seam). + + Mesh UV unwraps smear across U=0/1; computing atan2/asin in the shader from + the interpolated object normal avoids the date-line streak. + """ + for node in nodeTree.nodes: + if node.get('solsys_equirect_uv'): + return node.outputs['Vector'] + + texCoord = nodeTree.nodes.new('ShaderNodeTexCoord') + texCoord.location = (-1600, 200) + + normalize = nodeTree.nodes.new('ShaderNodeVectorMath') + normalize.operation = 'NORMALIZE' + normalize.location = (-1420, 200) + nodeTree.links.new(texCoord.outputs['Normal'], normalize.inputs[0]) + + separate = nodeTree.nodes.new('ShaderNodeSeparateXYZ') + separate.location = (-1240, 200) + nodeTree.links.new(normalize.outputs['Vector'], separate.inputs['Vector']) + + # u = atan2(y, x) / (2π) + 0.5 + arctan2 = nodeTree.nodes.new('ShaderNodeMath') + arctan2.operation = 'ARCTAN2' + arctan2.location = (-1060, 260) + nodeTree.links.new(separate.outputs['Y'], arctan2.inputs[0]) + nodeTree.links.new(separate.outputs['X'], arctan2.inputs[1]) + + divU = nodeTree.nodes.new('ShaderNodeMath') + divU.operation = 'DIVIDE' + divU.location = (-880, 260) + divU.inputs[1].default_value = 2.0 * math.pi + nodeTree.links.new(arctan2.outputs['Value'], divU.inputs[0]) + + addU = nodeTree.nodes.new('ShaderNodeMath') + addU.operation = 'ADD' + addU.location = (-700, 260) + addU.inputs[1].default_value = 0.5 + nodeTree.links.new(divU.outputs['Value'], addU.inputs[0]) + + # v = asin(z) / π + 0.5 + arcsin = nodeTree.nodes.new('ShaderNodeMath') + arcsin.operation = 'ARCSINE' + arcsin.location = (-1060, 80) + nodeTree.links.new(separate.outputs['Z'], arcsin.inputs[0]) + + divV = nodeTree.nodes.new('ShaderNodeMath') + divV.operation = 'DIVIDE' + divV.location = (-880, 80) + divV.inputs[1].default_value = math.pi + nodeTree.links.new(arcsin.outputs['Value'], divV.inputs[0]) + + addV = nodeTree.nodes.new('ShaderNodeMath') + addV.operation = 'ADD' + addV.location = (-700, 80) + addV.inputs[1].default_value = 0.5 + nodeTree.links.new(divV.outputs['Value'], addV.inputs[0]) + + combine = nodeTree.nodes.new('ShaderNodeCombineXYZ') + combine.location = (-520, 180) + combine['solsys_equirect_uv'] = True + nodeTree.links.new(addU.outputs['Value'], combine.inputs['X']) + nodeTree.links.new(addV.outputs['Value'], combine.inputs['Y']) + return combine.outputs['Vector'] + + +def _featherEquirectEdges(imagePath: Path, *, blendPx: int = 8) -> Path: + """Cross-fade left/right edges so the 0°/360° wrap is invisible.""" + seamlessPath = imagePath.with_name(f'{imagePath.stem}_seamless{imagePath.suffix}') + if seamlessPath.is_file() and seamlessPath.stat().st_mtime >= imagePath.stat().st_mtime: + return seamlessPath + try: + import numpy as np + from PIL import Image + except ImportError: + return imagePath + with Image.open(imagePath) as image: + array = np.asarray(image.convert('RGBA'), dtype=np.float32) + width = array.shape[1] + blendPx = max(1, min(blendPx, width // 4)) + for index in range(blendPx): + blend = (index + 0.5) / blendPx + left = array[:, index] + right = array[:, width - blendPx + index] + mixed = right * (1.0 - blend) + left * blend + array[:, index] = mixed + array[:, width - blendPx + index] = mixed + Image.fromarray(np.clip(array, 0, 255).astype(np.uint8), mode='RGBA').save(seamlessPath) + return seamlessPath + + +def _loadImageTexture( + bpy: Any, + nodeTree: Any, + imagePath: Path, + *, + label: str, + equirectangular: bool = False, +) -> Any | None: + """Load a body map. Equirectangular mode uses shader UVs (no mesh seam).""" if not imagePath.is_file(): print(f'Warning: texture missing ({label}): {imagePath}', file=sys.stderr) return None - image = bpy.data.images.load(str(imagePath), check_existing=True) + loadPath = _featherEquirectEdges(imagePath) if equirectangular else imagePath + image = bpy.data.images.load(str(loadPath), check_existing=True) textureNode = nodeTree.nodes.new('ShaderNodeTexImage') textureNode.image = image textureNode.label = label - textureNode.interpolation = 'Smart' + # Closest avoids mip-seam streaks where atan2 U wraps 0↔1 across neighbors. + textureNode.interpolation = 'Closest' if equirectangular else 'Cubic' + textureNode.extension = 'REPEAT' + if equirectangular: + textureNode.projection = 'FLAT' + vector = _ensureEquirectangularVector(bpy, nodeTree) + nodeTree.links.new(vector, textureNode.inputs['Vector']) return textureNode +def _loadBodyColorTexture(bpy: Any, nodeTree: Any, colorPath: Path) -> Any | None: + """Seam-safe equirect color map; plain UV image as last resort.""" + colorNode = _loadImageTexture(bpy, nodeTree, colorPath, label='color', equirectangular=True) + if colorNode is not None: + return colorNode + return _loadImageTexture(bpy, nodeTree, colorPath, label='color', equirectangular=False) + + def _mixCloudLayer( bpy: Any, nodeTree: Any, @@ -111,7 +251,7 @@ def _mixCloudLayer( Keep coverage modest so continents/oceans stay readable (not a snowball). """ - cloudsNode = _loadImageTexture(bpy, nodeTree, cloudsPath, label='clouds') + cloudsNode = _loadImageTexture(bpy, nodeTree, cloudsPath, label='clouds', equirectangular=True) if cloudsNode is None: return surfaceColorSocket cloudsNode.location = (-620, -40) @@ -187,7 +327,7 @@ def _applyBodyMaterial( if not colorPath: return - colorNode = _loadImageTexture(bpy, nodeTree, Path(colorPath), label='color') + colorNode = _loadBodyColorTexture(bpy, nodeTree, Path(colorPath)) if colorNode is None: return colorNode.location = (-820, 200) @@ -461,11 +601,12 @@ def applyFlybyJobInBlender(job: dict[str, Any]) -> Path: scene.render.resolution_y = resolution scene.render.resolution_percentage = 100 scene.render.fps = int(job.get('fps', 18)) + filmTransparent = bool(job.get('filmTransparent', False)) scene.render.image_settings.file_format = 'PNG' - scene.render.image_settings.color_mode = 'RGB' + scene.render.image_settings.color_mode = 'RGBA' if filmTransparent else 'RGB' scene.render.filepath = str(outputDirectory / 'frame_') scene.render.use_file_extension = True - scene.render.film_transparent = False + scene.render.film_transparent = filmTransparent # Light theme was reading muddy under Filmic — lift exposure for day-side punch. viewSettings = getattr(scene, 'view_settings', None) if viewSettings is not None: diff --git a/animate/scenes/sol_centauri_cinematic.py b/animate/scenes/sol_centauri_cinematic.py index bbfedbd..32ca19c 100644 --- a/animate/scenes/sol_centauri_cinematic.py +++ b/animate/scenes/sol_centauri_cinematic.py @@ -8,6 +8,7 @@ import numpy as np import pandas as pd from matplotlib.animation import FuncAnimation, PillowWriter +from mpl_toolkits.mplot3d import proj3d from solsys.motion import AnimatedAsteroidPopulation, AsteroidPopulationCounts from solsys.motion.mean_anomaly import planetMeanAnomalyRad from solsys.physics import ( @@ -27,6 +28,7 @@ ) from animate.animation_styles import ASTEROID_RENDER_STYLES +from animate.blender_body_sprites import BlenderBodySpriteAtlas from animate.scenes.exoplanet_system import bodyPositionInOrbitalPlane, orbitPathInOrbitalPlane DEFAULT_FIGURE_SIZE_INCHES = (12.0, 12.0) @@ -62,8 +64,15 @@ 'D': '#D8E6FF', # white dwarfs (class strings often start with D) } # Sol opening: Earth+Moon → near-Sun → inner → tighter outer linger, then interstellar pullback. -# Moon display radius ≈ 0.128 AU (50× exaggerated); keep it nearly filling the frame. -SOL_EARTH_HALF_AU = 0.14 +# Moon display radius ≈ 0.128 AU (50× exaggerated); leave a little margin for the label. +SOL_EARTH_HALF_AU = 0.16 +# Blender open: Earth-only frame (Moon orbit ≈ 0.128 AU stays off-screen). +SOL_EARTH_CLOSE_HALF_AU = 0.042 +# Fixed world radii for textured globes (must NOT scale up with camera half-width). +EARTH_GLOBE_RADIUS_AU = SOL_EARTH_HALF_AU * 0.18 +# Luna uses bodyScale 0.35 against this base in the billboard path. +# Hide Luna until the camera is wide enough that its exaggerated orbit fits. +SOL_MOON_REVEAL_HALF_AU = 0.11 SOL_NEAR_SUN_HALF_AU = 2.4 SOL_INNER_HALF_AU = 6.5 # Linger on the main belt / Jupiter's orbit (same idea as the Kuiper hold). @@ -118,6 +127,11 @@ PULLBACK_WEIGHTS = (1.4, 2.0, 2.4, 2.8, 3.0, 3.0, 2.6, 2.0, 1.6, 1.3, 1.0) # Timeline: Earth → belt linger → outer linger → Oort pullback. SOL_EARTH_DWELL_END = 0.03 +# Blender open: hold Earth-close for ~2 day/night spins (48 PNG samples/turn), +# then ease out to Earth+Moon before the existing Sol dive. +SOL_EARTH_SPIN_HOLD_END = 0.055 +SOL_EARTH_MOON_REVEAL_END = 0.085 +SOL_EARTH_BLENDER_DWELL_END = 0.10 SOL_BELT_ARRIVE = 0.16 SOL_BELT_HOLD_END = 0.28 SOL_OUTER_ARRIVE = 0.40 @@ -136,8 +150,11 @@ SOL_ELEVATION_DEG = 28.0 # Top-down open so the Moon orbit fills the viewport (3D foreshortening shrinks it). EARTH_OPEN_ELEVATION_DEG = 62.0 +# Blender Earth-close: slightly flatter so the globe fills the frame cleanly. +EARTH_CLOSE_ELEVATION_DEG = 48.0 PROXIMA_ELEVATION_DEG = 58.0 OUTPUT_DIRECTORY = 'output/animate/sol_centauri' +BLENDER_OUTPUT_DIRECTORY = 'output/animate/sol_centauri/blender' SOL_PLANET_NAMES = ( 'Mercury', @@ -392,6 +409,8 @@ def __init__( figureSizeInches: tuple[float, float] = DEFAULT_FIGURE_SIZE_INCHES, dpi: int = DEFAULT_DPI, starsCsvPath: str = 'data/nearby_stars_30.csv', + *, + useBlenderBodies: bool = False, ): if system.systemId != 'alpha_centauri': raise ValueError(f'Expected alpha_centauri, got {system.systemId!r}') @@ -400,6 +419,7 @@ def __init__( self.figureSizeInches = figureSizeInches self.dpi = dpi self.animationFrames = ANIMATION_FRAMES + self.useBlenderBodies = useBlenderBodies self.constants = AstronomicalConstants() self.orbitCalculator = OrbitCalculator() self.planetCatalog = PlanetCatalog(self.constants) @@ -474,6 +494,27 @@ def __init__( self._viewHalfWidthAu = self.solEarthHalfWidthAu self.figure = plt.figure(figsize=figureSizeInches, dpi=dpi, layout='none') self.axes = self.figure.add_axes((0.0, 0.0, 1.0, 1.0), projection='3d') + # 2D overlay for Blender spin-loop frames (drawn after the 3D camera is set). + self.bodyOverlay = self.figure.add_axes((0.0, 0.0, 1.0, 1.0), facecolor='none', zorder=20) + self.bodyOverlay.set_axis_off() + self.bodyOverlay.patch.set_alpha(0.0) + self.bodyOverlay.set_xlim(0.0, 1.0) + self.bodyOverlay.set_ylim(0.0, 1.0) + # (name, center, frame, halfWidth, openCloseup, bodyScale, orbitalPhaseRad|None) + self._pendingBlenderBodies: list[ + tuple[str, np.ndarray, int, float, bool, float, float | None] + ] = [] + # (name, center, fontsize, bodyScale) + self._pendingBlenderLabels: list[tuple[str, np.ndarray, float, float]] = [] + self.blenderSprites: BlenderBodySpriteAtlas | None = None + if self.useBlenderBodies: + themeName = 'dark' if self.isDark else 'light' + self.blenderSprites = BlenderBodySpriteAtlas(themeName) + print( + 'Blender spin loops: ' + f'Earth={"on" if self.blenderSprites.hasEarth else "missing"} ' + f'Moon={"on" if self.blenderSprites.hasMoon else "missing"}' + ) def _requireOrbit(self, role: str) -> StellarOrbit: for orbit in self.system.stellarOrbits: @@ -598,10 +639,27 @@ def _abLegHalfWidthAu(self, focus: np.ndarray, abProgress: float) -> float: dive = segmentProgress(abProgress, AB_CRUISE_END, 1.0) return stagedLogDive(cruiseEndHalf, self.abHalfWidthAu, AB_DIVE_WAYPOINTS_AU, dive) + def _earthDwellEnd(self) -> float: + """Timeline fraction when the Earth(-Moon) open ends and the Sol dive begins.""" + return SOL_EARTH_BLENDER_DWELL_END if self.useBlenderBodies else SOL_EARTH_DWELL_END + def _solOpeningHalfWidthAu(self, linear: float) -> float: """Earth → belt/Jupiter linger → outer/Kuiper linger.""" + dwellEnd = self._earthDwellEnd() + if self.useBlenderBodies and linear < dwellEnd: + # Earth-only day/night hold → reveal Luna → short Earth+Moon beat. + if linear < SOL_EARTH_SPIN_HOLD_END: + return SOL_EARTH_CLOSE_HALF_AU + if linear < SOL_EARTH_MOON_REVEAL_END: + reveal = segmentProgress(linear, SOL_EARTH_SPIN_HOLD_END, SOL_EARTH_MOON_REVEAL_END) + return logLerp( + SOL_EARTH_CLOSE_HALF_AU, + self.solEarthHalfWidthAu, + smootherstep(reveal), + ) + return self.solEarthHalfWidthAu if linear < SOL_BELT_ARRIVE: - dive = segmentProgress(linear, SOL_EARTH_DWELL_END, SOL_BELT_ARRIVE) + dive = segmentProgress(linear, dwellEnd, SOL_BELT_ARRIVE) if dive >= 1.0 - 1e-9: return SOL_BELT_LINGER_HALF_AU return stagedLogDive( @@ -746,6 +804,13 @@ def _cameraState(self, frame: int) -> tuple[np.ndarray, float]: def update(self, frame: int): self.axes.clear() + self.bodyOverlay.clear() + self.bodyOverlay.set_axis_off() + self.bodyOverlay.patch.set_alpha(0.0) + self.bodyOverlay.set_xlim(0.0, 1.0) + self.bodyOverlay.set_ylim(0.0, 1.0) + self._pendingBlenderBodies = [] + self._pendingBlenderLabels = [] for textArtist in list(self.figure.texts): textArtist.remove() focus, halfWidthAu = self._cameraState(frame) @@ -761,6 +826,9 @@ def update(self, frame: int): self._drawAlphaCentauri(frame, halfWidthAu, abProgress, linear) self._drawProxima(frame, halfWidthAu, linear) self._applyAxes(focus, halfWidthAu, abProgress, proximaProgress, linear) + # Globes need a finished 3D projection — paint them into this frame now. + self._flushBlenderBodyOverlays(halfWidthAu) + self._flushBlenderBodyLabels(halfWidthAu) return [] def _inView(self, position: np.ndarray, margin: float = 0.95) -> bool: @@ -778,6 +846,7 @@ def _label3d( color: str, fontsize: float = 8, alpha: float = 1.0, + clipOn: bool = True, ) -> None: if not self._inView(position): return @@ -789,7 +858,7 @@ def _label3d( color=color, fontsize=fontsize, alpha=alpha, - clip_on=True, + clip_on=clipOn, ) def _drawPath( @@ -930,24 +999,47 @@ def _drawOneSolPlanet(self, name: str, frame: int, halfWidthAu: float) -> None: return earthOpen = halfWidthAu <= SOL_EARTH_HALF_AU * 1.5 plutoOuter = name == 'Pluto' and halfWidthAu >= 20.0 - self.axes.scatter( - [position[0]], - [position[1]], - [position[2]], - color=planet.color, - s=self._solPlanetMarkerSize(name, halfWidthAu), - depthshade=True, - zorder=6 if plutoOuter else 5, + queuedBlenderEarth = False + blenderEarthAvailable = ( + name == 'Earth' + and self.useBlenderBodies + and self.blenderSprites is not None + and self.blenderSprites.hasEarth ) + if ( + blenderEarthAvailable + and self._blenderBillboardRadiusAu(halfWidthAu, openCloseup=earthOpen, bodyScale=1.0) + is not None + ): + self._pendingBlenderBodies.append( + ('Earth', position.copy(), frame, halfWidthAu, earthOpen, 1.0, None) + ) + queuedBlenderEarth = True + # Blender mode: never fall back to the catalog-blue Earth scatter marker. + if not queuedBlenderEarth and not blenderEarthAvailable: + self.axes.scatter( + [position[0]], + [position[1]], + [position[2]], + color=planet.color, + s=self._solPlanetMarkerSize(name, halfWidthAu), + depthshade=True, + zorder=6 if plutoOuter else 5, + ) if name in LABELED_SOL_PLANETS or ( halfWidthAu < SOL_INNER_HALF_AU and name in ('Mercury', 'Venus', 'Mars') ): - self._label3d( - position, - f' {name}', - color=self.labelColor, - fontsize=(12 if earthOpen and name == 'Earth' else (10 if plutoOuter else 8)), - ) + fontsize = 12 if earthOpen and name == 'Earth' else (10 if plutoOuter else 8) + if name == 'Earth' and queuedBlenderEarth: + # Overlay text above the spin billboard — axes labels sit under the disk. + self._pendingBlenderLabels.append((name, position.copy(), float(fontsize), 1.0)) + else: + self._label3d( + position, + f' {name}', + color=self.labelColor, + fontsize=fontsize, + ) def _drawSolMoons(self, frame: int, halfWidthAu: float) -> None: moonScale = self.moonCatalog.displayScaleForCameraAu(halfWidthAu) @@ -967,6 +1059,9 @@ def _drawOneSolMoon( moonScale: float, halfWidthAu: float, ) -> None: + # Earth-only open: keep Luna off-screen until the reveal zoom. + if moon.name == 'Moon' and self.useBlenderBodies and halfWidthAu < SOL_MOON_REVEAL_HALF_AU: + return orbitRadiusAu = self.moonCatalog.displayOrbitRadiusAu(moon, moonScale) ring = np.linspace(0.0, 2.0 * np.pi, 64) moonOpen = moon.name == 'Moon' and halfWidthAu <= SOL_EARTH_HALF_AU * 2.2 @@ -979,7 +1074,7 @@ def _drawOneSolMoon( alpha=0.75 if moonOpen else (0.55 if moon.name == 'Moon' else 0.3), ) moonMeanAnomaly = planetMeanAnomalyRad( - moon.orbitalPeriodDays, 1, self._solMotionDays(frame) + moon.orbitalPeriodDays, 1, self._lunarMotionDays(moon, frame, halfWidthAu) ) moonX, moonY, moonZ = self.moonCatalog.heliocentricPosition( planetPosition[0], @@ -990,23 +1085,214 @@ def _drawOneSolMoon( moonScale, ) moonPosition = np.array([float(moonX), float(moonY), float(moonZ)], dtype=float) - self.axes.scatter( - [moonPosition[0]], - [moonPosition[1]], - [moonPosition[2]], - color=moon.color, - s=self.moonCatalog.markerSize3d( - moon, 600 if moonOpen else (900 if moon.name == 'Moon' else 1400) - ), - depthshade=True, - zorder=6, + queuedBlenderMoon = False + blenderMoonAvailable = ( + moon.name == 'Moon' + and self.useBlenderBodies + and self.blenderSprites is not None + and self.blenderSprites.hasMoon ) - if moon.name == 'Moon' or halfWidthAu < 4.0: + if ( + blenderMoonAvailable + and self._blenderBillboardRadiusAu(halfWidthAu, openCloseup=moonOpen, bodyScale=0.35) + is not None + ): + self._pendingBlenderBodies.append( + ( + 'Moon', + moonPosition.copy(), + frame, + halfWidthAu, + moonOpen, + 0.35, + float(moonMeanAnomaly), + ) + ) + queuedBlenderMoon = True + # Blender mode: never fall back to the catalog-gray Luna scatter marker. + if not queuedBlenderMoon and not blenderMoonAvailable: + self.axes.scatter( + [moonPosition[0]], + [moonPosition[1]], + [moonPosition[2]], + color=moon.color, + s=self.moonCatalog.markerSize3d( + moon, 600 if moonOpen else (900 if moon.name == 'Moon' else 1400) + ), + depthshade=True, + zorder=6, + ) + # Luna: only label in the Earth–Moon open — drop it once the camera + # reaches the inner system (Earth keeps its planet label as usual). + if moon.name == 'Moon': + if moonOpen: + if queuedBlenderMoon: + # Overlay text above the spin billboard so the leading "M" isn't covered. + self._pendingBlenderLabels.append((moon.name, moonPosition.copy(), 11.0, 0.35)) + else: + self._label3d( + moonPosition, + f' {moon.name}', + color=self.labelColor, + fontsize=11, + ) + elif halfWidthAu < 4.0: self._label3d( moonPosition, f' {moon.name}', color=self.labelColor, - fontsize=11 if moonOpen else (9 if moon.name == 'Moon' else 7), + fontsize=7, + ) + + def _lunarMotionDays(self, moon, frame: int, halfWidthAu: float) -> float: + """Sol motion clock is too fast for a readable Earth–Moon opening orbit.""" + days = self._solMotionDays(frame) + if moon.name != 'Moon' or not self.useBlenderBodies: + return days + # At open (~0.05 d/frame) Luna crawls; blend back to the sol clock by ~2 AU. + openHalf = SOL_EARTH_HALF_AU + leaveHalf = 2.0 + if halfWidthAu >= leaveHalf: + return days + blend = smootherstep((halfWidthAu - openHalf) / max(leaveHalf - openHalf, 1e-6)) + openScale = 0.015 # ≈0.05 d/frame vs sol's 3.5 d/frame near Earth + return days * (openScale + (1.0 - openScale) * blend) + + def _blenderBillboardRadiusAu( + self, + halfWidthAu: float, + *, + openCloseup: bool, + bodyScale: float, + ) -> float | None: + """Fixed world-space globe radius; None → omit the body (no catalog-color dot). + + Radius is anchored to the Earth-open framing so zoom-out shrinks the + body on screen instead of growing it inside the Moon's orbit. + """ + del openCloseup # size is world-fixed; closeup only affects texture resolution + radiusAu = EARTH_GLOBE_RADIUS_AU * bodyScale + # Keep textured disks through the Sol zoom-out; drop only once the + # camera is well past the outer system (never swap to catalog blue/gray). + if halfWidthAu > 100.0: + return None + return radiusAu + + def _flushBlenderBodyOverlays(self, halfWidthAu: float) -> None: + """Project queued bodies and paint Blender spin-loop frames into this frame.""" + if not self._pendingBlenderBodies or self.blenderSprites is None: + return + for ( + catalogName, + center, + frame, + bodyHalfWidth, + openCloseup, + bodyScale, + orbitalPhaseRad, + ) in self._pendingBlenderBodies: + radiusAu = self._blenderBillboardRadiusAu( + bodyHalfWidth, openCloseup=openCloseup, bodyScale=bodyScale + ) + if radiusAu is None: + continue + fracRadius = self._blenderBillboardFracRadius(halfWidthAu, bodyScale) + if fracRadius is None: + continue + resolution = 384 if openCloseup else (64 if fracRadius <= 0.01 else 128) + if catalogName == 'Earth': + disk = self.blenderSprites.earthFrame(frame, resolution=resolution) + else: + disk = self.blenderSprites.moonFrame( + frame, + orbitalPhaseRad=orbitalPhaseRad, + resolution=resolution, + ) + if disk is None: + continue + x2, y2, _ = proj3d.proj_transform( + float(center[0]), + float(center[1]), + float(center[2]), + self.axes.get_proj(), + ) + display = self.axes.transData.transform((x2, y2)) + frac = self.figure.transFigure.inverted().transform(display) + # PillowWriter quantizes better from 8-bit RGBA than float arrays. + diskU8 = (np.clip(disk, 0.0, 1.0) * 255.0).astype(np.uint8) + self.bodyOverlay.imshow( + diskU8, + extent=( + frac[0] - fracRadius, + frac[0] + fracRadius, + frac[1] - fracRadius, + frac[1] + fracRadius, + ), + origin='upper', + interpolation='bilinear', + zorder=5, + clip_on=False, + ) + # imshow(extent=...) expands data limits; keep 0–1 so label coords stay figure-like. + self.bodyOverlay.set_xlim(0.0, 1.0) + self.bodyOverlay.set_ylim(0.0, 1.0) + + def _blenderBillboardFracRadius(self, halfWidthAu: float, bodyScale: float) -> float | None: + """On-screen disk radius in figure fraction (matches overlay paint).""" + radiusAu = self._blenderBillboardRadiusAu( + halfWidthAu, openCloseup=False, bodyScale=bodyScale + ) + if radiusAu is None: + return None + # Same floor as _flushBlenderBodyOverlays so labels track the painted disk. + return max(float(radiusAu / max(2.0 * halfWidthAu, 1e-9)), 0.0035) + + def _blenderBodyLabelPad(self, fracRadius: float) -> float: + """Gap from disk edge to label; shrinks with Earth as the camera pulls back.""" + # Close-up (~0.09): ~0.03. Inner-Sol floor (0.0035): ~0.0045 — not a fixed + # 3% figure gap that leaves Earth floating alone on zoom-out. + return max(0.004, min(0.03, fracRadius * 0.45 + 0.003)) + + def _flushBlenderBodyLabels(self, halfWidthAu: float) -> None: + """Draw deferred body labels above spin billboards (figure fraction).""" + if not self._pendingBlenderLabels: + return + self.bodyOverlay.set_xlim(0.0, 1.0) + self.bodyOverlay.set_ylim(0.0, 1.0) + viewHalf = float(halfWidthAu) if halfWidthAu > 0.0 else self._viewHalfWidthAu + for name, center, fontsize, bodyScale in self._pendingBlenderLabels: + x2, y2, _ = proj3d.proj_transform( + float(center[0]), + float(center[1]), + float(center[2]), + self.axes.get_proj(), + ) + display = self.axes.transData.transform((x2, y2)) + frac = self.figure.transFigure.inverted().transform(display) + fracRadius = self._blenderBillboardFracRadius(viewHalf, bodyScale) or 0.0035 + pad = self._blenderBodyLabelPad(fracRadius) + # Overlay text above the imshow disk. Figure-level text paints under this + # axes (zorder 20) and gets covered by the globe. + if name == 'Earth': + textX = min(frac[0] + fracRadius + pad, 0.94) + textY = frac[1] + va = 'center' + else: + # Moon sits near the right edge; lower-right keeps the leading "M". + moonPad = max(0.004, min(0.016, pad * 0.55)) + textX = min(frac[0] + fracRadius + moonPad, 0.90) + textY = max(frac[1] - fracRadius - moonPad, 0.08) + va = 'top' + self.bodyOverlay.text( + textX, + textY, + name, + color=self.labelColor, + fontsize=fontsize, + ha='left', + va=va, + clip_on=False, + zorder=10, ) def _drawSolAsteroidPopulations(self, frame: int, halfWidthAu: float) -> None: @@ -1535,6 +1821,8 @@ def _solCaption(self, halfWidthAu: float, linear: float) -> tuple[str, str] | No if linear >= PULLBACK_END: return None if halfWidthAu <= SOL_EARTH_HALF_AU * 2.2: + if self.useBlenderBodies and halfWidthAu < SOL_MOON_REVEAL_HALF_AU: + return ('Earth', 'A couple of day–night cycles before we find the Moon') return ('Earth and the Moon', 'Starting close to home before we pull back') if halfWidthAu <= SOL_NEAR_SUN_HALF_AU: return ('Inner solar system', 'Pulling back past Venus and Mars') @@ -1613,14 +1901,24 @@ def _applyAxes( if linear < PULLBACK_END: if halfWidthAu <= SOL_NEAR_SUN_HALF_AU: - # Ease from top-down Earth open down to the usual Sol angle. - tilt = smootherstep( - (halfWidthAu - SOL_EARTH_HALF_AU) - / max(SOL_NEAR_SUN_HALF_AU - SOL_EARTH_HALF_AU, 1e-6) - ) - elev = EARTH_OPEN_ELEVATION_DEG + tilt * ( - SOL_ELEVATION_DEG - EARTH_OPEN_ELEVATION_DEG - ) + if self.useBlenderBodies and halfWidthAu < self.solEarthHalfWidthAu - 1e-9: + # Earth-close → Earth+Moon open angle as Luna comes into frame. + closeTilt = smootherstep( + (halfWidthAu - SOL_EARTH_CLOSE_HALF_AU) + / max(self.solEarthHalfWidthAu - SOL_EARTH_CLOSE_HALF_AU, 1e-6) + ) + elev = EARTH_CLOSE_ELEVATION_DEG + closeTilt * ( + EARTH_OPEN_ELEVATION_DEG - EARTH_CLOSE_ELEVATION_DEG + ) + else: + # Ease from top-down Earth open down to the usual Sol angle. + tilt = smootherstep( + (halfWidthAu - SOL_EARTH_HALF_AU) + / max(SOL_NEAR_SUN_HALF_AU - SOL_EARTH_HALF_AU, 1e-6) + ) + elev = EARTH_OPEN_ELEVATION_DEG + tilt * ( + SOL_ELEVATION_DEG - EARTH_OPEN_ELEVATION_DEG + ) else: elev = SOL_ELEVATION_DEG azim = self.solAzimuthDeg @@ -1679,11 +1977,19 @@ def renderSolCentauriCinematicAnimations( figureSizeInches: tuple[float, float] = DEFAULT_FIGURE_SIZE_INCHES, dpi: int = DEFAULT_DPI, starsCsvPath: str = 'data/nearby_stars_30.csv', + *, + useBlenderBodies: bool = False, ) -> None: catalog = SystemCatalog(starsCsvPath=starsCsvPath) system = catalog.load('alpha_centauri') + if useBlenderBodies: + outputDirectory = BLENDER_OUTPUT_DIRECTORY + stem = 'sol_centauri_cinematic_blender' + else: + outputDirectory = OUTPUT_DIRECTORY + stem = 'sol_centauri_cinematic' for themeName, styleName in (('light', 'default'), ('dark', 'dark_background')): - outputPath = f'{OUTPUT_DIRECTORY}/sol_centauri_cinematic_{themeName}.gif' + outputPath = f'{outputDirectory}/{stem}_{themeName}.gif' print(f'Rendering {outputPath}...') animator = SolCentauriCinematicAnimator( system, @@ -1691,6 +1997,7 @@ def renderSolCentauriCinematicAnimations( figureSizeInches=figureSizeInches, dpi=dpi, starsCsvPath=starsCsvPath, + useBlenderBodies=useBlenderBodies, ) animator.saveGif(outputPath) print('Sol ↔ Centauri cinematic animations completed!') diff --git a/data/textures/bodies/earth/clouds_seamless.png b/data/textures/bodies/earth/clouds_seamless.png new file mode 100644 index 0000000..c93287a Binary files /dev/null and b/data/textures/bodies/earth/clouds_seamless.png differ diff --git a/data/textures/bodies/earth/color_seamless.png b/data/textures/bodies/earth/color_seamless.png new file mode 100644 index 0000000..2642db0 Binary files /dev/null and b/data/textures/bodies/earth/color_seamless.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark.gif b/output/animate/blender/moons/moon/moon_spin_dark.gif new file mode 100644 index 0000000..4e05005 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark.gif differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0000.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0000.png new file mode 100644 index 0000000..27c43c2 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0000.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0001.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0001.png new file mode 100644 index 0000000..47663bb Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0001.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0002.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0002.png new file mode 100644 index 0000000..be13dd9 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0002.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0003.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0003.png new file mode 100644 index 0000000..14dab9e Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0003.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0004.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0004.png new file mode 100644 index 0000000..62a12d6 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0004.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0005.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0005.png new file mode 100644 index 0000000..1a2e7b2 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0005.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0006.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0006.png new file mode 100644 index 0000000..99d030d Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0006.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0007.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0007.png new file mode 100644 index 0000000..a678a8e Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0007.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0008.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0008.png new file mode 100644 index 0000000..5acc030 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0008.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0009.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0009.png new file mode 100644 index 0000000..890c7c2 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0009.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0010.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0010.png new file mode 100644 index 0000000..93978cf Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0010.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0011.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0011.png new file mode 100644 index 0000000..bdbd0ad Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0011.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0012.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0012.png new file mode 100644 index 0000000..7a728c3 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0012.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0013.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0013.png new file mode 100644 index 0000000..a7c9d61 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0013.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0014.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0014.png new file mode 100644 index 0000000..b13d3df Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0014.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0015.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0015.png new file mode 100644 index 0000000..5968818 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0015.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0016.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0016.png new file mode 100644 index 0000000..dac2a47 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0016.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0017.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0017.png new file mode 100644 index 0000000..e11508f Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0017.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0018.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0018.png new file mode 100644 index 0000000..7f2de17 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0018.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0019.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0019.png new file mode 100644 index 0000000..7049f0b Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0019.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0020.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0020.png new file mode 100644 index 0000000..47bfeff Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0020.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0021.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0021.png new file mode 100644 index 0000000..3cd5ee8 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0021.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0022.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0022.png new file mode 100644 index 0000000..de667c0 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0022.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0023.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0023.png new file mode 100644 index 0000000..0848338 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0023.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0024.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0024.png new file mode 100644 index 0000000..127130e Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0024.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0025.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0025.png new file mode 100644 index 0000000..8cd47eb Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0025.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0026.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0026.png new file mode 100644 index 0000000..1a04db4 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0026.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0027.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0027.png new file mode 100644 index 0000000..1ecfb1a Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0027.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0028.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0028.png new file mode 100644 index 0000000..b324e74 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0028.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0029.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0029.png new file mode 100644 index 0000000..3c49b41 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0029.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0030.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0030.png new file mode 100644 index 0000000..6979e6b Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0030.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0031.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0031.png new file mode 100644 index 0000000..f029896 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0031.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0032.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0032.png new file mode 100644 index 0000000..c59ba2e Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0032.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0033.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0033.png new file mode 100644 index 0000000..0686585 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0033.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0034.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0034.png new file mode 100644 index 0000000..1424a4a Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0034.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0035.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0035.png new file mode 100644 index 0000000..b9ea315 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0035.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0036.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0036.png new file mode 100644 index 0000000..b1508c2 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0036.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0037.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0037.png new file mode 100644 index 0000000..70c2aef Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0037.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0038.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0038.png new file mode 100644 index 0000000..c851b56 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0038.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0039.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0039.png new file mode 100644 index 0000000..cc25ce7 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0039.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0040.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0040.png new file mode 100644 index 0000000..bcaf3c0 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0040.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0041.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0041.png new file mode 100644 index 0000000..69e8c6c Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0041.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0042.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0042.png new file mode 100644 index 0000000..1c2b706 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0042.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0043.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0043.png new file mode 100644 index 0000000..48eae09 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0043.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0044.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0044.png new file mode 100644 index 0000000..991635a Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0044.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0045.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0045.png new file mode 100644 index 0000000..440e105 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0045.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0046.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0046.png new file mode 100644 index 0000000..b576d58 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0046.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark/frame_0047.png b/output/animate/blender/moons/moon/moon_spin_dark/frame_0047.png new file mode 100644 index 0000000..390a660 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_dark/frame_0047.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_dark_job.json b/output/animate/blender/moons/moon/moon_spin_dark_job.json new file mode 100644 index 0000000..1c16a62 --- /dev/null +++ b/output/animate/blender/moons/moon/moon_spin_dark_job.json @@ -0,0 +1,466 @@ +{ + "schema": "solsys.blender_flyby_job/v1", + "theme": "dark", + "mode": "spin", + "body": { + "name": "Moon", + "kind": "moon", + "systemId": "sol", + "diameterKm": 3474, + "color": "lightgray", + "colorRgba": [ + 0.75, + 0.75, + 0.75, + 1.0 + ], + "displayRadiusAu": 0.009288902265104232 + }, + "frames": [ + { + "frame": 0, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 0.0 + }, + { + "frame": 1, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 7.5 + }, + { + "frame": 2, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 15.0 + }, + { + "frame": 3, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 22.5 + }, + { + "frame": 4, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 30.0 + }, + { + "frame": 5, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 37.5 + }, + { + "frame": 6, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 45.0 + }, + { + "frame": 7, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 52.5 + }, + { + "frame": 8, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 60.0 + }, + { + "frame": 9, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 67.5 + }, + { + "frame": 10, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 75.0 + }, + { + "frame": 11, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 82.5 + }, + { + "frame": 12, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 90.0 + }, + { + "frame": 13, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 97.5 + }, + { + "frame": 14, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 105.0 + }, + { + "frame": 15, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 112.5 + }, + { + "frame": 16, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 120.0 + }, + { + "frame": 17, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 127.5 + }, + { + "frame": 18, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 135.0 + }, + { + "frame": 19, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 142.5 + }, + { + "frame": 20, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 150.0 + }, + { + "frame": 21, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 157.5 + }, + { + "frame": 22, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 165.0 + }, + { + "frame": 23, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 172.5 + }, + { + "frame": 24, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 180.0 + }, + { + "frame": 25, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 187.5 + }, + { + "frame": 26, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 195.0 + }, + { + "frame": 27, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 202.5 + }, + { + "frame": 28, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 210.0 + }, + { + "frame": 29, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 217.5 + }, + { + "frame": 30, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 225.0 + }, + { + "frame": 31, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 232.5 + }, + { + "frame": 32, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 240.0 + }, + { + "frame": 33, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 247.5 + }, + { + "frame": 34, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 255.0 + }, + { + "frame": 35, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 262.5 + }, + { + "frame": 36, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 270.0 + }, + { + "frame": 37, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 277.5 + }, + { + "frame": 38, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 285.0 + }, + { + "frame": 39, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 292.5 + }, + { + "frame": 40, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 300.0 + }, + { + "frame": 41, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 307.5 + }, + { + "frame": 42, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 315.0 + }, + { + "frame": 43, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 322.5 + }, + { + "frame": 44, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 330.0 + }, + { + "frame": 45, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 337.5 + }, + { + "frame": 46, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 345.0 + }, + { + "frame": 47, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 352.5 + } + ], + "outputDirectory": "output/animate/blender/moons/moon/moon_spin_dark", + "resolution": 512, + "fps": 20, + "filmTransparent": true, + "appearance": { + "bodyId": "moon", + "kind": "moon", + "textures": { + "color": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/moon/color.png" + }, + "roughness": 0.82, + "specular": 0.04 + } +} diff --git a/output/animate/blender/moons/moon/moon_spin_light.gif b/output/animate/blender/moons/moon/moon_spin_light.gif new file mode 100644 index 0000000..73f6bb1 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light.gif differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0000.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0000.png new file mode 100644 index 0000000..f7f0af0 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0000.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0001.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0001.png new file mode 100644 index 0000000..df770c0 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0001.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0002.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0002.png new file mode 100644 index 0000000..19d2a4a Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0002.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0003.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0003.png new file mode 100644 index 0000000..42f7b7a Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0003.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0004.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0004.png new file mode 100644 index 0000000..a8f4d26 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0004.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0005.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0005.png new file mode 100644 index 0000000..202a084 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0005.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0006.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0006.png new file mode 100644 index 0000000..776cc72 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0006.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0007.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0007.png new file mode 100644 index 0000000..4d08d35 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0007.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0008.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0008.png new file mode 100644 index 0000000..13de17d Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0008.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0009.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0009.png new file mode 100644 index 0000000..db8f070 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0009.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0010.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0010.png new file mode 100644 index 0000000..3f50ff1 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0010.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0011.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0011.png new file mode 100644 index 0000000..87d7aa1 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0011.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0012.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0012.png new file mode 100644 index 0000000..2992980 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0012.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0013.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0013.png new file mode 100644 index 0000000..31bb6c1 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0013.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0014.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0014.png new file mode 100644 index 0000000..2045934 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0014.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0015.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0015.png new file mode 100644 index 0000000..e6c44b9 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0015.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0016.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0016.png new file mode 100644 index 0000000..aab4c24 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0016.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0017.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0017.png new file mode 100644 index 0000000..979e11b Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0017.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0018.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0018.png new file mode 100644 index 0000000..6d9597e Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0018.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0019.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0019.png new file mode 100644 index 0000000..d9ecb1a Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0019.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0020.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0020.png new file mode 100644 index 0000000..e0a5fc4 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0020.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0021.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0021.png new file mode 100644 index 0000000..91f2133 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0021.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0022.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0022.png new file mode 100644 index 0000000..bc15695 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0022.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0023.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0023.png new file mode 100644 index 0000000..2d70b11 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0023.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0024.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0024.png new file mode 100644 index 0000000..ae4bcf8 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0024.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0025.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0025.png new file mode 100644 index 0000000..e369a5d Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0025.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0026.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0026.png new file mode 100644 index 0000000..c7e7c91 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0026.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0027.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0027.png new file mode 100644 index 0000000..00b8123 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0027.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0028.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0028.png new file mode 100644 index 0000000..5e6a950 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0028.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0029.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0029.png new file mode 100644 index 0000000..99fab76 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0029.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0030.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0030.png new file mode 100644 index 0000000..45bf72f Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0030.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0031.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0031.png new file mode 100644 index 0000000..5cd639b Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0031.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0032.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0032.png new file mode 100644 index 0000000..fce0971 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0032.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0033.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0033.png new file mode 100644 index 0000000..e4e87ea Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0033.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0034.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0034.png new file mode 100644 index 0000000..221f6cd Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0034.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0035.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0035.png new file mode 100644 index 0000000..569ab4d Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0035.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0036.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0036.png new file mode 100644 index 0000000..0db4349 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0036.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0037.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0037.png new file mode 100644 index 0000000..6d6220f Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0037.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0038.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0038.png new file mode 100644 index 0000000..59c5644 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0038.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0039.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0039.png new file mode 100644 index 0000000..dbc1337 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0039.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0040.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0040.png new file mode 100644 index 0000000..1775084 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0040.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0041.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0041.png new file mode 100644 index 0000000..1941b1c Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0041.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0042.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0042.png new file mode 100644 index 0000000..40e5b50 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0042.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0043.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0043.png new file mode 100644 index 0000000..7170cb2 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0043.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0044.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0044.png new file mode 100644 index 0000000..0eb2883 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0044.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0045.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0045.png new file mode 100644 index 0000000..ba1047f Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0045.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0046.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0046.png new file mode 100644 index 0000000..beb4cbc Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0046.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light/frame_0047.png b/output/animate/blender/moons/moon/moon_spin_light/frame_0047.png new file mode 100644 index 0000000..297ae2b Binary files /dev/null and b/output/animate/blender/moons/moon/moon_spin_light/frame_0047.png differ diff --git a/output/animate/blender/moons/moon/moon_spin_light_job.json b/output/animate/blender/moons/moon/moon_spin_light_job.json new file mode 100644 index 0000000..a4128f7 --- /dev/null +++ b/output/animate/blender/moons/moon/moon_spin_light_job.json @@ -0,0 +1,466 @@ +{ + "schema": "solsys.blender_flyby_job/v1", + "theme": "light", + "mode": "spin", + "body": { + "name": "Moon", + "kind": "moon", + "systemId": "sol", + "diameterKm": 3474, + "color": "lightgray", + "colorRgba": [ + 0.75, + 0.75, + 0.75, + 1.0 + ], + "displayRadiusAu": 0.009288902265104232 + }, + "frames": [ + { + "frame": 0, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 0.0 + }, + { + "frame": 1, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 7.5 + }, + { + "frame": 2, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 15.0 + }, + { + "frame": 3, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 22.5 + }, + { + "frame": 4, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 30.0 + }, + { + "frame": 5, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 37.5 + }, + { + "frame": 6, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 45.0 + }, + { + "frame": 7, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 52.5 + }, + { + "frame": 8, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 60.0 + }, + { + "frame": 9, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 67.5 + }, + { + "frame": 10, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 75.0 + }, + { + "frame": 11, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 82.5 + }, + { + "frame": 12, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 90.0 + }, + { + "frame": 13, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 97.5 + }, + { + "frame": 14, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 105.0 + }, + { + "frame": 15, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 112.5 + }, + { + "frame": 16, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 120.0 + }, + { + "frame": 17, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 127.5 + }, + { + "frame": 18, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 135.0 + }, + { + "frame": 19, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 142.5 + }, + { + "frame": 20, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 150.0 + }, + { + "frame": 21, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 157.5 + }, + { + "frame": 22, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 165.0 + }, + { + "frame": 23, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 172.5 + }, + { + "frame": 24, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 180.0 + }, + { + "frame": 25, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 187.5 + }, + { + "frame": 26, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 195.0 + }, + { + "frame": 27, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 202.5 + }, + { + "frame": 28, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 210.0 + }, + { + "frame": 29, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 217.5 + }, + { + "frame": 30, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 225.0 + }, + { + "frame": 31, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 232.5 + }, + { + "frame": 32, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 240.0 + }, + { + "frame": 33, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 247.5 + }, + { + "frame": 34, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 255.0 + }, + { + "frame": 35, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 262.5 + }, + { + "frame": 36, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 270.0 + }, + { + "frame": 37, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 277.5 + }, + { + "frame": 38, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 285.0 + }, + { + "frame": 39, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 292.5 + }, + { + "frame": 40, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 300.0 + }, + { + "frame": 41, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 307.5 + }, + { + "frame": 42, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 315.0 + }, + { + "frame": 43, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 322.5 + }, + { + "frame": 44, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 330.0 + }, + { + "frame": 45, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 337.5 + }, + { + "frame": 46, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 345.0 + }, + { + "frame": 47, + "cameraAu": [ + 0.03184108916015677, + 0.022295370654749246, + 0.012629886099622662 + ], + "bodyRotationDeg": 352.5 + } + ], + "outputDirectory": "output/animate/blender/moons/moon/moon_spin_light", + "resolution": 512, + "fps": 20, + "filmTransparent": true, + "appearance": { + "bodyId": "moon", + "kind": "moon", + "textures": { + "color": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/moon/color.png" + }, + "roughness": 0.82, + "specular": 0.04 + } +} diff --git a/output/animate/blender/planets/earth/earth_spin_dark.gif b/output/animate/blender/planets/earth/earth_spin_dark.gif new file mode 100644 index 0000000..b16af6a Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark.gif differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0000.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0000.png new file mode 100644 index 0000000..a3c753d Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0000.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0001.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0001.png new file mode 100644 index 0000000..8326c8e Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0001.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0002.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0002.png new file mode 100644 index 0000000..1ff9534 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0002.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0003.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0003.png new file mode 100644 index 0000000..4c5d17e Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0003.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0004.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0004.png new file mode 100644 index 0000000..5a078d6 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0004.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0005.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0005.png new file mode 100644 index 0000000..ced927b Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0005.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0006.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0006.png new file mode 100644 index 0000000..228f631 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0006.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0007.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0007.png new file mode 100644 index 0000000..8769604 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0007.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0008.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0008.png new file mode 100644 index 0000000..1211d24 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0008.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0009.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0009.png new file mode 100644 index 0000000..7b8cec7 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0009.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0010.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0010.png new file mode 100644 index 0000000..ffc942a Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0010.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0011.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0011.png new file mode 100644 index 0000000..ec28966 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0011.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0012.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0012.png new file mode 100644 index 0000000..5e7993e Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0012.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0013.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0013.png new file mode 100644 index 0000000..c5634fd Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0013.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0014.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0014.png new file mode 100644 index 0000000..8283440 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0014.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0015.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0015.png new file mode 100644 index 0000000..f11ed3c Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0015.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0016.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0016.png new file mode 100644 index 0000000..e7dcb05 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0016.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0017.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0017.png new file mode 100644 index 0000000..d44438c Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0017.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0018.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0018.png new file mode 100644 index 0000000..d3495dc Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0018.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0019.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0019.png new file mode 100644 index 0000000..7978e74 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0019.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0020.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0020.png new file mode 100644 index 0000000..a5b9436 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0020.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0021.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0021.png new file mode 100644 index 0000000..4ce6520 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0021.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0022.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0022.png new file mode 100644 index 0000000..051d069 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0022.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0023.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0023.png new file mode 100644 index 0000000..c2e33ee Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0023.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0024.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0024.png new file mode 100644 index 0000000..04073cc Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0024.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0025.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0025.png new file mode 100644 index 0000000..11d99e7 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0025.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0026.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0026.png new file mode 100644 index 0000000..d93a5c9 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0026.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0027.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0027.png new file mode 100644 index 0000000..c6a76e2 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0027.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0028.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0028.png new file mode 100644 index 0000000..4a82144 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0028.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0029.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0029.png new file mode 100644 index 0000000..5924993 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0029.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0030.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0030.png new file mode 100644 index 0000000..705dbf6 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0030.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0031.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0031.png new file mode 100644 index 0000000..4eafeb9 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0031.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0032.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0032.png new file mode 100644 index 0000000..4d7984b Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0032.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0033.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0033.png new file mode 100644 index 0000000..6fe5ba5 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0033.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0034.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0034.png new file mode 100644 index 0000000..30f9c6e Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0034.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0035.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0035.png new file mode 100644 index 0000000..f29c2d2 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0035.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0036.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0036.png new file mode 100644 index 0000000..de5b7f3 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0036.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0037.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0037.png new file mode 100644 index 0000000..c56fc11 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0037.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0038.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0038.png new file mode 100644 index 0000000..8ec5b23 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0038.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0039.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0039.png new file mode 100644 index 0000000..be39766 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0039.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0040.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0040.png new file mode 100644 index 0000000..226ebe3 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0040.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0041.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0041.png new file mode 100644 index 0000000..a679083 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0041.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0042.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0042.png new file mode 100644 index 0000000..1819590 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0042.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0043.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0043.png new file mode 100644 index 0000000..fcd8569 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0043.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0044.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0044.png new file mode 100644 index 0000000..8538ef4 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0044.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0045.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0045.png new file mode 100644 index 0000000..97dbaff Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0045.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0046.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0046.png new file mode 100644 index 0000000..15a4ef1 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0046.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark/frame_0047.png b/output/animate/blender/planets/earth/earth_spin_dark/frame_0047.png new file mode 100644 index 0000000..566c7bf Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_dark/frame_0047.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_dark_job.json b/output/animate/blender/planets/earth/earth_spin_dark_job.json new file mode 100644 index 0000000..03aa9c0 --- /dev/null +++ b/output/animate/blender/planets/earth/earth_spin_dark_job.json @@ -0,0 +1,479 @@ +{ + "schema": "solsys.blender_flyby_job/v1", + "theme": "dark", + "mode": "spin", + "body": { + "name": "Earth", + "kind": "planet", + "systemId": "sol", + "diameterKm": 12742, + "color": "blue", + "colorRgba": [ + 0.25, + 0.45, + 0.95, + 1.0 + ], + "displayRadiusAu": 0.034070003644777816 + }, + "frames": [ + { + "frame": 0, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 0.0 + }, + { + "frame": 1, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 7.5 + }, + { + "frame": 2, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 15.0 + }, + { + "frame": 3, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 22.5 + }, + { + "frame": 4, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 30.0 + }, + { + "frame": 5, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 37.5 + }, + { + "frame": 6, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 45.0 + }, + { + "frame": 7, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 52.5 + }, + { + "frame": 8, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 60.0 + }, + { + "frame": 9, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 67.5 + }, + { + "frame": 10, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 75.0 + }, + { + "frame": 11, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 82.5 + }, + { + "frame": 12, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 90.0 + }, + { + "frame": 13, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 97.5 + }, + { + "frame": 14, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 105.0 + }, + { + "frame": 15, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 112.5 + }, + { + "frame": 16, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 120.0 + }, + { + "frame": 17, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 127.5 + }, + { + "frame": 18, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 135.0 + }, + { + "frame": 19, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 142.5 + }, + { + "frame": 20, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 150.0 + }, + { + "frame": 21, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 157.5 + }, + { + "frame": 22, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 165.0 + }, + { + "frame": 23, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 172.5 + }, + { + "frame": 24, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 180.0 + }, + { + "frame": 25, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 187.5 + }, + { + "frame": 26, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 195.0 + }, + { + "frame": 27, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 202.5 + }, + { + "frame": 28, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 210.0 + }, + { + "frame": 29, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 217.5 + }, + { + "frame": 30, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 225.0 + }, + { + "frame": 31, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 232.5 + }, + { + "frame": 32, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 240.0 + }, + { + "frame": 33, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 247.5 + }, + { + "frame": 34, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 255.0 + }, + { + "frame": 35, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 262.5 + }, + { + "frame": 36, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 270.0 + }, + { + "frame": 37, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 277.5 + }, + { + "frame": 38, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 285.0 + }, + { + "frame": 39, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 292.5 + }, + { + "frame": 40, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 300.0 + }, + { + "frame": 41, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 307.5 + }, + { + "frame": 42, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 315.0 + }, + { + "frame": 43, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 322.5 + }, + { + "frame": 44, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 330.0 + }, + { + "frame": 45, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 337.5 + }, + { + "frame": 46, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 345.0 + }, + { + "frame": 47, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 352.5 + } + ], + "outputDirectory": "output/animate/blender/planets/earth/earth_spin_dark", + "resolution": 512, + "fps": 20, + "filmTransparent": true, + "appearance": { + "bodyId": "earth", + "kind": "planet", + "textures": { + "color": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/earth/color.png", + "clouds": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/earth/clouds.png" + }, + "roughness": 0.58, + "specular": 0.18, + "atmosphere": { + "enabled": true, + "scale": 1.038, + "colorRgba": [ + 0.4, + 0.68, + 1.0, + 1.0 + ], + "strength": 0.85, + "fresnelBlend": 0.14 + } + } +} diff --git a/output/animate/blender/planets/earth/earth_spin_light.gif b/output/animate/blender/planets/earth/earth_spin_light.gif new file mode 100644 index 0000000..6d91256 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light.gif differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0000.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0000.png new file mode 100644 index 0000000..110f16d Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0000.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0001.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0001.png new file mode 100644 index 0000000..bdaebd9 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0001.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0002.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0002.png new file mode 100644 index 0000000..842ab3e Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0002.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0003.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0003.png new file mode 100644 index 0000000..9d1e2a9 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0003.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0004.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0004.png new file mode 100644 index 0000000..98be32d Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0004.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0005.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0005.png new file mode 100644 index 0000000..d0d236b Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0005.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0006.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0006.png new file mode 100644 index 0000000..83bef4a Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0006.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0007.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0007.png new file mode 100644 index 0000000..ccb85f0 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0007.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0008.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0008.png new file mode 100644 index 0000000..e734f90 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0008.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0009.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0009.png new file mode 100644 index 0000000..01d7123 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0009.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0010.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0010.png new file mode 100644 index 0000000..da623c4 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0010.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0011.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0011.png new file mode 100644 index 0000000..a2e58d2 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0011.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0012.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0012.png new file mode 100644 index 0000000..56fd8e2 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0012.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0013.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0013.png new file mode 100644 index 0000000..44fd12f Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0013.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0014.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0014.png new file mode 100644 index 0000000..753278c Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0014.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0015.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0015.png new file mode 100644 index 0000000..b3046ce Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0015.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0016.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0016.png new file mode 100644 index 0000000..9737abf Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0016.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0017.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0017.png new file mode 100644 index 0000000..97c4694 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0017.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0018.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0018.png new file mode 100644 index 0000000..291b1b9 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0018.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0019.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0019.png new file mode 100644 index 0000000..6d6d3f3 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0019.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0020.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0020.png new file mode 100644 index 0000000..aafb080 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0020.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0021.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0021.png new file mode 100644 index 0000000..fbf2d4b Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0021.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0022.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0022.png new file mode 100644 index 0000000..9f8d243 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0022.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0023.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0023.png new file mode 100644 index 0000000..e322468 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0023.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0024.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0024.png new file mode 100644 index 0000000..e9a6892 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0024.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0025.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0025.png new file mode 100644 index 0000000..7b434e5 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0025.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0026.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0026.png new file mode 100644 index 0000000..12af1ff Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0026.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0027.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0027.png new file mode 100644 index 0000000..bb2ecb9 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0027.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0028.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0028.png new file mode 100644 index 0000000..d36ef2b Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0028.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0029.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0029.png new file mode 100644 index 0000000..352c402 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0029.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0030.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0030.png new file mode 100644 index 0000000..b8741e3 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0030.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0031.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0031.png new file mode 100644 index 0000000..1617c42 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0031.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0032.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0032.png new file mode 100644 index 0000000..b276a73 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0032.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0033.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0033.png new file mode 100644 index 0000000..906d4fe Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0033.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0034.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0034.png new file mode 100644 index 0000000..80f5f15 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0034.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0035.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0035.png new file mode 100644 index 0000000..0faa556 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0035.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0036.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0036.png new file mode 100644 index 0000000..7398421 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0036.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0037.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0037.png new file mode 100644 index 0000000..58e7125 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0037.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0038.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0038.png new file mode 100644 index 0000000..870e199 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0038.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0039.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0039.png new file mode 100644 index 0000000..abe8990 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0039.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0040.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0040.png new file mode 100644 index 0000000..b5fc630 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0040.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0041.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0041.png new file mode 100644 index 0000000..520f6bb Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0041.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0042.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0042.png new file mode 100644 index 0000000..cabf10f Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0042.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0043.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0043.png new file mode 100644 index 0000000..7b54061 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0043.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0044.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0044.png new file mode 100644 index 0000000..087cc22 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0044.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0045.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0045.png new file mode 100644 index 0000000..da47ca2 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0045.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0046.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0046.png new file mode 100644 index 0000000..77ece14 Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0046.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light/frame_0047.png b/output/animate/blender/planets/earth/earth_spin_light/frame_0047.png new file mode 100644 index 0000000..bc9943f Binary files /dev/null and b/output/animate/blender/planets/earth/earth_spin_light/frame_0047.png differ diff --git a/output/animate/blender/planets/earth/earth_spin_light_job.json b/output/animate/blender/planets/earth/earth_spin_light_job.json new file mode 100644 index 0000000..dda8220 --- /dev/null +++ b/output/animate/blender/planets/earth/earth_spin_light_job.json @@ -0,0 +1,479 @@ +{ + "schema": "solsys.blender_flyby_job/v1", + "theme": "light", + "mode": "spin", + "body": { + "name": "Earth", + "kind": "planet", + "systemId": "sol", + "diameterKm": 12742, + "color": "blue", + "colorRgba": [ + 0.25, + 0.45, + 0.95, + 1.0 + ], + "displayRadiusAu": 0.034070003644777816 + }, + "frames": [ + { + "frame": 0, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 0.0 + }, + { + "frame": 1, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 7.5 + }, + { + "frame": 2, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 15.0 + }, + { + "frame": 3, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 22.5 + }, + { + "frame": 4, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 30.0 + }, + { + "frame": 5, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 37.5 + }, + { + "frame": 6, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 45.0 + }, + { + "frame": 7, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 52.5 + }, + { + "frame": 8, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 60.0 + }, + { + "frame": 9, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 67.5 + }, + { + "frame": 10, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 75.0 + }, + { + "frame": 11, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 82.5 + }, + { + "frame": 12, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 90.0 + }, + { + "frame": 13, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 97.5 + }, + { + "frame": 14, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 105.0 + }, + { + "frame": 15, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 112.5 + }, + { + "frame": 16, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 120.0 + }, + { + "frame": 17, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 127.5 + }, + { + "frame": 18, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 135.0 + }, + { + "frame": 19, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 142.5 + }, + { + "frame": 20, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 150.0 + }, + { + "frame": 21, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 157.5 + }, + { + "frame": 22, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 165.0 + }, + { + "frame": 23, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 172.5 + }, + { + "frame": 24, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 180.0 + }, + { + "frame": 25, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 187.5 + }, + { + "frame": 26, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 195.0 + }, + { + "frame": 27, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 202.5 + }, + { + "frame": 28, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 210.0 + }, + { + "frame": 29, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 217.5 + }, + { + "frame": 30, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 225.0 + }, + { + "frame": 31, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 232.5 + }, + { + "frame": 32, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 240.0 + }, + { + "frame": 33, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 247.5 + }, + { + "frame": 34, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 255.0 + }, + { + "frame": 35, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 262.5 + }, + { + "frame": 36, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 270.0 + }, + { + "frame": 37, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 277.5 + }, + { + "frame": 38, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 285.0 + }, + { + "frame": 39, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 292.5 + }, + { + "frame": 40, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 300.0 + }, + { + "frame": 41, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 307.5 + }, + { + "frame": 42, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 315.0 + }, + { + "frame": 43, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 322.5 + }, + { + "frame": 44, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 330.0 + }, + { + "frame": 45, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 337.5 + }, + { + "frame": 46, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 345.0 + }, + { + "frame": 47, + "cameraAu": [ + 0.11678732241759286, + 0.08177536352412634, + 0.046324124548472075 + ], + "bodyRotationDeg": 352.5 + } + ], + "outputDirectory": "output/animate/blender/planets/earth/earth_spin_light", + "resolution": 512, + "fps": 20, + "filmTransparent": true, + "appearance": { + "bodyId": "earth", + "kind": "planet", + "textures": { + "color": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/earth/color.png", + "clouds": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/earth/clouds.png" + }, + "roughness": 0.58, + "specular": 0.18, + "atmosphere": { + "enabled": true, + "scale": 1.038, + "colorRgba": [ + 0.4, + 0.68, + 1.0, + 1.0 + ], + "strength": 0.85, + "fresnelBlend": 0.14 + } + } +} diff --git a/output/animate/sol_centauri/blender/sol_centauri_cinematic_blender_dark.gif b/output/animate/sol_centauri/blender/sol_centauri_cinematic_blender_dark.gif new file mode 100644 index 0000000..e2bb4b6 Binary files /dev/null and b/output/animate/sol_centauri/blender/sol_centauri_cinematic_blender_dark.gif differ diff --git a/output/animate/sol_centauri/blender/sol_centauri_cinematic_blender_light.gif b/output/animate/sol_centauri/blender/sol_centauri_cinematic_blender_light.gif new file mode 100644 index 0000000..b472f47 Binary files /dev/null and b/output/animate/sol_centauri/blender/sol_centauri_cinematic_blender_light.gif differ diff --git a/render.py b/render.py index 66eec02..f00bb63 100644 --- a/render.py +++ b/render.py @@ -13,7 +13,7 @@ from animate.scenes.alpha_centauri import renderAlphaCentauriAnimations from animate.scenes.barnards_star import renderBarnardsStarAnimations from animate.scenes.blender.export_body import exportBodyScene -from animate.scenes.blender.flyby_scene import renderPlanetFlyby +from animate.scenes.blender.flyby_scene import renderPlanetFlyby, renderPlanetSpin from animate.scenes.interstellar_objects import renderInterstellarObjectAnimations from animate.scenes.sol_centauri_cinematic import renderSolCentauriCinematicAnimations from animate.scenes.tabbys_star import renderTabbysStarAnimations @@ -132,6 +132,14 @@ def buildParser() -> argparse.ArgumentParser: default='data/nearby_stars_30.csv', help='Path to nearby-stars CSV (for multi-star systems)', ) + animateParser.add_argument( + '--blender-bodies', + action='store_true', + help=( + 'For --system sol_centauri: replace Earth/Moon dots with Blender spin-loop ' + 'frames drawn each cinematic frame (requires prior: blender --spin)' + ), + ) allParser = subparsers.add_parser( 'all', @@ -206,11 +214,27 @@ def buildParser() -> argparse.ArgumentParser: action='store_true', help='Render polished light/dark flyby GIFs via Blender (requires blender on PATH)', ) + blenderParser.add_argument( + '--spin', + action='store_true', + help=( + 'Render fixed-camera RGBA day/night spin loops for cinematic reuse ' + '(requires blender on PATH)' + ), + ) blenderParser.add_argument( '--theme', choices=('light', 'dark', 'all'), default='all', - help='Flyby theme when using --flyby (default: all)', + help='Theme when using --flyby / --spin (default: all)', + ) + blenderParser.add_argument( + '--pipeline', + action='store_true', + help=( + 'End-to-end: Earth+Moon Blender spin loops, then Sol→Centauri cinematic ' + 'with --blender-bodies. Ignores --body.' + ), ) return parser @@ -238,11 +262,43 @@ def _runBlenderLoad(scenePath: Path) -> None: raise SystemExit(f'Blender ingest failed with exit code {completed.returncode}') +def _runBlenderCinematicPipeline( + *, + theme: str, + frames: int, + outputDirectory: str, + starsCsvPath: str, +) -> None: + """Earth+Moon spin loops, then Sol→Centauri cinematic compositing those frames.""" + spinFrames = 48 if frames == 120 else frames + for bodyName in ('Earth', 'Moon'): + print(f'[pipeline] Blender spin loop → {bodyName}') + paths = renderPlanetSpin( + bodyName, + theme=theme, + frameCount=spinFrames, + outputDirectory=outputDirectory, + ) + for path in paths: + print(f'Spin ready → {path}') + + print('[pipeline] Sol→Centauri cinematic with Blender spin billboards') + renderSolCentauriCinematicAnimations( + figureSizeInches=ANIMATE_FIGURE_SIZE_INCHES, + dpi=ANIMATE_DPI_CINEMATIC, + starsCsvPath=starsCsvPath, + useBlenderBodies=True, + ) + print('[pipeline] done') + + def _renderAnimations( systemChoice: str, dimensions: tuple[Dimension, ...], starsCsvPath: str, objectChoice: str, + *, + useBlenderBodies: bool = False, ) -> None: if systemChoice in ('sol', 'all'): for dimension in dimensions: @@ -263,6 +319,7 @@ def _renderAnimations( figureSizeInches=ANIMATE_FIGURE_SIZE_INCHES, dpi=ANIMATE_DPI_CINEMATIC, starsCsvPath=starsCsvPath, + useBlenderBodies=useBlenderBodies, ) if systemChoice in ('barnards_star', 'all'): renderBarnardsStarAnimations( @@ -297,6 +354,63 @@ def _renderAnimations( ) +def _handleBlenderCommand(args: argparse.Namespace) -> None: + if args.pipeline: + _runBlenderCinematicPipeline( + theme=args.theme, + frames=args.frames, + outputDirectory=args.output_dir, + starsCsvPath='data/nearby_stars_30.csv', + ) + return + + if args.spin: + spinFrames = 48 if args.frames == 120 else args.frames + paths = renderPlanetSpin( + args.body, + theme=args.theme, + frameCount=spinFrames, + outputDirectory=args.output_dir, + ) + for path in paths: + print(f'Spin ready → {path}') + return + + if args.flyby: + flybyFrames = 72 if args.frames == 120 else args.frames + gifPaths = renderPlanetFlyby( + args.body, + theme=args.theme, + frameCount=flybyFrames, + outputDirectory=args.output_dir, + ) + for gifPath in gifPaths: + print(f'Flyby ready → {gifPath}') + return + + scenePath = exportBodyScene( + args.body, + frameCount=args.frames, + outputDirectory=args.output_dir, + ) + print(f'Exported Blender body scene → {scenePath}') + if args.load: + _runBlenderLoad(scenePath) + return + print( + 'Next: dry-run validate with\n' + f' {sys.executable} {BLENDER_LOAD_SCRIPT} {scenePath}\n' + 'or ingest with\n' + f' blender --background --python {BLENDER_LOAD_SCRIPT} -- {scenePath}\n' + 'or render flybys with\n' + f' {sys.executable} render.py blender --body {args.body} --flyby\n' + 'or render spin loops with\n' + f' {sys.executable} render.py blender --body {args.body} --spin\n' + 'or run the full Earth/Moon → cinematic pipeline with\n' + f' {sys.executable} render.py blender --pipeline' + ) + + def main(argv: list[str] | None = None) -> None: parser = buildParser() args = parser.parse_args(argv) @@ -313,35 +427,7 @@ def main(argv: list[str] | None = None) -> None: return if args.command == 'blender': - if args.flyby: - flybyFrames = 72 if args.frames == 120 else args.frames - gifPaths = renderPlanetFlyby( - args.body, - theme=args.theme, - frameCount=flybyFrames, - outputDirectory=args.output_dir, - ) - for gifPath in gifPaths: - print(f'Flyby ready → {gifPath}') - return - - scenePath = exportBodyScene( - args.body, - frameCount=args.frames, - outputDirectory=args.output_dir, - ) - print(f'Exported Blender body scene → {scenePath}') - if args.load: - _runBlenderLoad(scenePath) - else: - print( - 'Next: dry-run validate with\n' - f' {sys.executable} {BLENDER_LOAD_SCRIPT} {scenePath}\n' - 'or ingest with\n' - f' blender --background --python {BLENDER_LOAD_SCRIPT} -- {scenePath}\n' - 'or render flybys with\n' - f' {sys.executable} render.py blender --body {args.body} --flyby' - ) + _handleBlenderCommand(args) return dimensions = _parseDimensions(args.dimension) @@ -354,6 +440,7 @@ def main(argv: list[str] | None = None) -> None: dimensions, starsCsvPath=getattr(args, 'stars', 'data/nearby_stars_30.csv'), objectChoice=getattr(args, 'object', 'all'), + useBlenderBodies=bool(getattr(args, 'blender_bodies', False)), ) if args.command in ('static', 'all'): diff --git a/tests/test_blender_body_sprites.py b/tests/test_blender_body_sprites.py new file mode 100644 index 0000000..444ece4 --- /dev/null +++ b/tests/test_blender_body_sprites.py @@ -0,0 +1,182 @@ +"""Tests for Blender spin-loop frames in the Sol→Centauri cinematic.""" + +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +import numpy as np +from animate.blender_body_sprites import ( + BlenderBodySpriteAtlas, + _blendSpinFrames, + loadSpinLoopFrames, + tidalLockFrameIndex, + tidalLockFramePosition, +) +from animate.scenes.blender.flyby_camera import buildSpinCameraPath +from animate.scenes.blender.flyby_scene import buildSpinJob, spinFramesDirectory +from animate.scenes.sol_centauri_cinematic import SolCentauriCinematicAnimator +from PIL import Image +from solsys.physics.catalogs.system_catalog import SystemCatalog, defaultDataPaths + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +class SpinCameraTests(unittest.TestCase): + def test_spin_path_is_fixed_camera_full_turn(self) -> None: + path = buildSpinCameraPath(0.01, frameCount=8) + self.assertEqual(len(path), 8) + self.assertEqual(path[0].cameraAu, path[-1].cameraAu) + self.assertAlmostEqual(path[0].bodyRotationDeg, 0.0) + self.assertAlmostEqual(path[4].bodyRotationDeg, 180.0) + self.assertLess(path[-1].bodyRotationDeg, 360.0) + + def test_spin_job_requests_transparent_film(self) -> None: + with tempfile.TemporaryDirectory() as temporaryName: + temporary = Path(temporaryName) + job = buildSpinJob('Earth', theme='dark', framesDirectory=temporary / 'frames') + self.assertEqual(job['mode'], 'spin') + self.assertTrue(job['filmTransparent']) + self.assertEqual(job['frames'][0]['cameraAu'], job['frames'][-1]['cameraAu']) + + +class TidalLockTests(unittest.TestCase): + def test_tidal_lock_index_tracks_orbital_phase(self) -> None: + self.assertEqual(tidalLockFrameIndex(0.0, 48), 0) + self.assertEqual(tidalLockFrameIndex(np.pi, 48), 24) + self.assertEqual(tidalLockFrameIndex(2.0 * np.pi * 0.999, 48), 47) + # Full orbit wraps to the same face. + self.assertEqual(tidalLockFrameIndex(2.0 * np.pi, 48), 0) + self.assertAlmostEqual(tidalLockFramePosition(np.pi, 48), 24.0, places=9) + + def test_blend_spin_frames_is_smooth_between_neighbors(self) -> None: + frames = [ + np.zeros((2, 2, 4), dtype=np.float32), + np.ones((2, 2, 4), dtype=np.float32), + ] + mid = _blendSpinFrames(frames, 0.5) + np.testing.assert_allclose(mid, 0.5) + + def test_moon_frame_uses_orbital_phase_not_wall_clock(self) -> None: + with tempfile.TemporaryDirectory() as temporaryName: + temporary = Path(temporaryName) + moonDir = spinFramesDirectory('Moon', 'dark', outputDirectory=temporary) + moonDir.mkdir(parents=True) + for index in range(8): + Image.new('RGBA', (16, 16), (index * 30, 160, 160, 220)).save( + moonDir / f'frame_{index:04d}.png' + ) + earthDir = spinFramesDirectory('Earth', 'dark', outputDirectory=temporary) + earthDir.mkdir(parents=True) + Image.new('RGBA', (16, 16), (20, 90, 180, 230)).save(earthDir / 'frame_0000.png') + atlas = BlenderBodySpriteAtlas('dark', outputDirectory=temporary) + a = atlas.moonFrame(0, orbitalPhaseRad=0.0, resolution=16) + b = atlas.moonFrame(100, orbitalPhaseRad=0.0, resolution=16) + c = atlas.moonFrame(0, orbitalPhaseRad=np.pi, resolution=16) + assert a is not None and b is not None and c is not None + np.testing.assert_allclose(a, b) + self.assertFalse(np.allclose(a, c)) + + +class SpinAtlasTests(unittest.TestCase): + def test_load_spin_frames_from_png_loop(self) -> None: + with tempfile.TemporaryDirectory() as temporaryName: + temporary = Path(temporaryName) + directory = spinFramesDirectory('Earth', 'dark', outputDirectory=temporary) + directory.mkdir(parents=True) + for index in range(4): + Image.new('RGBA', (32, 32), (10, 80, 180, 200)).save( + directory / f'frame_{index:04d}.png' + ) + frames = loadSpinLoopFrames('Earth', 'dark', outputDirectory=temporary) + assert frames is not None + self.assertEqual(len(frames), 4) + self.assertEqual(frames[0].shape, (32, 32, 4)) + + moonDir = spinFramesDirectory('Moon', 'dark', outputDirectory=temporary) + moonDir.mkdir(parents=True) + Image.new('RGBA', (32, 32), (160, 160, 160, 220)).save(moonDir / 'frame_0000.png') + atlas = BlenderBodySpriteAtlas('dark', outputDirectory=temporary) + self.assertTrue(atlas.hasEarth) + self.assertTrue(atlas.hasMoon) + frame = atlas.earthFrame(5, resolution=16) + assert frame is not None + self.assertEqual(frame.shape, (16, 16, 4)) + + +class BlenderBodyOverlayTests(unittest.TestCase): + def setUp(self) -> None: + paths = defaultDataPaths(REPO_ROOT) + self.system = SystemCatalog(**paths).load('alpha_centauri') + self.starsCsvPath = paths['starsCsvPath'] + + def tearDown(self) -> None: + import matplotlib.pyplot as plt + + for figure in list(plt.get_fignums()): + plt.close(figure) + + def test_flag_off_skips_atlas(self) -> None: + animator = SolCentauriCinematicAnimator( + self.system, + starsCsvPath=self.starsCsvPath, + useBlenderBodies=False, + ) + self.assertIsNone(animator.blenderSprites) + + def test_billboard_radius_is_world_fixed_then_drops(self) -> None: + animator = SolCentauriCinematicAnimator( + self.system, + starsCsvPath=self.starsCsvPath, + useBlenderBodies=False, + ) + close = animator._blenderBillboardRadiusAu(0.14, openCloseup=True, bodyScale=1.0) + mid = animator._blenderBillboardRadiusAu(1.5, openCloseup=False, bodyScale=1.0) + outerSol = animator._blenderBillboardRadiusAu(42.0, openCloseup=False, bodyScale=1.0) + far = animator._blenderBillboardRadiusAu(120.0, openCloseup=False, bodyScale=1.0) + self.assertIsNotNone(close) + self.assertIsNotNone(mid) + self.assertIsNotNone(outerSol) + self.assertIsNone(far) + assert close is not None and mid is not None and outerSol is not None + self.assertAlmostEqual(close, mid, places=9) + self.assertAlmostEqual(close, outerSol, places=9) + + def test_earth_label_pad_shrinks_on_inner_sol_zoom_out(self) -> None: + animator = SolCentauriCinematicAnimator( + self.system, + starsCsvPath=self.starsCsvPath, + useBlenderBodies=False, + ) + closeFrac = animator._blenderBillboardFracRadius(0.16, 1.0) + innerFrac = animator._blenderBillboardFracRadius(6.5, 1.0) + self.assertIsNotNone(closeFrac) + self.assertIsNotNone(innerFrac) + assert closeFrac is not None and innerFrac is not None + closePad = animator._blenderBodyLabelPad(closeFrac) + innerPad = animator._blenderBodyLabelPad(innerFrac) + self.assertAlmostEqual(closePad, 0.03, places=3) + self.assertLess(innerPad, 0.01) + self.assertLess(innerFrac + innerPad, closeFrac * 0.35) + + def test_update_paints_overlay_when_spin_assets_present(self) -> None: + with tempfile.TemporaryDirectory() as temporaryName: + temporary = Path(temporaryName) + for body in ('Earth', 'Moon'): + directory = spinFramesDirectory(body, 'light', outputDirectory=temporary) + directory.mkdir(parents=True) + Image.new('RGBA', (48, 48), (20, 90, 180, 230)).save(directory / 'frame_0000.png') + animator = SolCentauriCinematicAnimator( + self.system, + style='default', + starsCsvPath=self.starsCsvPath, + useBlenderBodies=True, + ) + animator.blenderSprites = BlenderBodySpriteAtlas('light', outputDirectory=temporary) + animator.update(0) + self.assertGreaterEqual(len(animator.bodyOverlay.get_images()), 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_sol_centauri_cinematic.py b/tests/test_sol_centauri_cinematic.py index dadf777..3481131 100644 --- a/tests/test_sol_centauri_cinematic.py +++ b/tests/test_sol_centauri_cinematic.py @@ -6,21 +6,25 @@ from pathlib import Path import numpy as np +from animate.animation_styles import ASTEROID_RENDER_STYLES from animate.scenes.sol_centauri_cinematic import ( AB_HOLD_END, AB_TRAVEL_END, ANIMATION_SPEED_AB, ANIMATION_SPEED_SOL_NEAR, + PROXIMA_TRAVEL_END, PULLBACK_END, SOL_BELT_ARRIVE, SOL_BELT_HOLD_END, SOL_BELT_LINGER_HALF_AU, + SOL_EARTH_CLOSE_HALF_AU, SOL_EARTH_HALF_AU, + SOL_EARTH_MOON_REVEAL_END, + SOL_EARTH_SPIN_HOLD_END, SOL_HALF_WIDTH_AU, SOL_HOLD_END, SOL_INNER_HALF_AU, SOL_OUTER_ARRIVE, - PROXIMA_TRAVEL_END, WIDE_OUT_ARRIVE, WIDE_OUT_END, SolCentauriCinematicAnimator, @@ -31,7 +35,6 @@ spectralClassColor, travelProgress, ) -from animate.animation_styles import ASTEROID_RENDER_STYLES from solsys.physics.catalogs.system_catalog import SystemCatalog, defaultDataPaths from solsys.physics.frame_transform import SolCentauriFrameTransform @@ -85,6 +88,29 @@ def test_camera_starts_on_earth_moon(self) -> None: np.testing.assert_allclose(focus, earth, atol=1e-9) self.assertAlmostEqual(halfWidth, SOL_EARTH_HALF_AU, places=6) + def test_blender_camera_starts_earth_close_then_reveals_moon(self) -> None: + paths = defaultDataPaths(REPO_ROOT) + animator = SolCentauriCinematicAnimator( + self.system, + starsCsvPath=paths['starsCsvPath'], + useBlenderBodies=True, + ) + try: + focus, halfWidth = animator._cameraState(0) + earth = animator._planetPositionAu('Earth', 0) + np.testing.assert_allclose(focus, earth, atol=1e-9) + self.assertAlmostEqual(halfWidth, SOL_EARTH_CLOSE_HALF_AU, places=6) + holdFrame = int(SOL_EARTH_SPIN_HOLD_END * (animator.animationFrames - 1)) - 1 + _, holdHalf = animator._cameraState(holdFrame) + self.assertAlmostEqual(holdHalf, SOL_EARTH_CLOSE_HALF_AU, places=6) + revealFrame = int(SOL_EARTH_MOON_REVEAL_END * (animator.animationFrames - 1)) + _, revealHalf = animator._cameraState(revealFrame) + self.assertAlmostEqual(revealHalf, SOL_EARTH_HALF_AU, places=6) + finally: + import matplotlib.pyplot as plt + + plt.close(animator.figure) + def test_camera_ends_at_proxima(self) -> None: from animate.scenes.sol_centauri_cinematic import PROXIMA_INNER_HALF_AU