Added the gallery
This commit is contained in:
1 parent
d44f159c98
commit
8861858f0d
8 files changed
+482
-11
No files matched your search
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Writes examples/scenes/<model>.toml for the NASA models fetch-models.sh downloads.
|
||||
"""Writes examples/scenes/<model>.toml for the NASA models fetch-models.sh downloads,
|
||||
and two scenes with Earth, the Moon and a spacecraft together (below):
|
||||
examples/scenes/earthrise.toml from lunar orbit and examples/scenes/moonrise.toml
|
||||
from geostationary height.
|
||||
|
||||
Every scene has the same composition: the spacecraft in an isometric view (the
|
||||
camera looks along its body (+1, +1, +1) diagonal, body +Z toward image up) with
|
||||
@@ -174,8 +177,284 @@ attitude = {fmt(q, 9)}
|
||||
"""
|
||||
|
||||
|
||||
# Earthrise: from 1000 km above the Moon, Earth just above the curved lunar horizon
|
||||
# through the example camera's 50 mm lens, Landsat 8 standing in for a lunar orbiter
|
||||
# in the foreground.
|
||||
RM = 1737400.0
|
||||
MOON_DISTANCE = 3.844e8
|
||||
EARTHRISE_ALTITUDE = 1000e3
|
||||
EARTHRISE_FOCAL = 0.05
|
||||
EARTHRISE_GROUND = (15.0, 25.0) # Africa, geocentric degrees, on Earth's lit half
|
||||
# (degrees above the lunar limb, degrees right of Earth) of the image center and of
|
||||
# the satellite's center, Earth's center above the limb, and the satellite's distance.
|
||||
EARTHRISE_CENTER = (2.0, -3.0)
|
||||
EARTHRISE_EARTH = 2.6
|
||||
EARTHRISE_SATELLITE = (2.4, -6.0)
|
||||
EARTHRISE_RANGE = 500.0
|
||||
EARTHRISE_EXPOSURE = 2e-4 # s
|
||||
|
||||
|
||||
def earthrise():
|
||||
file, scale, center = "landsat8.glb", 1.0, MODELS[0][3]
|
||||
moon = [0.0, MOON_DISTANCE, 0.0]
|
||||
to_earth = norm([-x for x in moon])
|
||||
orbit = RM + EARTHRISE_ALTITUDE
|
||||
dip = math.acos(RM / orbit)
|
||||
|
||||
# The observer over the Moon, tilted from the Earth-facing point along the lunar
|
||||
# equator (toward +X; toward +Z it would look over the pole, where the map has no
|
||||
# data) until Earth's center, as seen from there, stands EARTHRISE_EARTH above the
|
||||
# limb.
|
||||
want = math.radians(EARTHRISE_EARTH) - dip
|
||||
theta = want
|
||||
for _ in range(20):
|
||||
u = norm([math.sin(theta) * to_earth[i] + math.cos(theta) * (i == 0) for i in range(3)])
|
||||
camera = [moon[i] + orbit * u[i] for i in range(3)]
|
||||
earth_dir = norm([-x for x in camera])
|
||||
theta += want - math.asin(dot(earth_dir, u))
|
||||
up = u
|
||||
ahead = norm([earth_dir[i] - dot(earth_dir, up) * up[i] for i in range(3)])
|
||||
right = cross(ahead, up)
|
||||
|
||||
def direction(above_limb, rightward):
|
||||
el, az = math.radians(above_limb) - dip, math.radians(rightward)
|
||||
return norm([math.cos(el) * math.cos(az) * ahead[i] + math.sin(el) * up[i]
|
||||
+ math.cos(el) * math.sin(az) * right[i] for i in range(3)])
|
||||
|
||||
boresight = direction(*EARTHRISE_CENTER)
|
||||
look = [camera[i] + MOON_DISTANCE * boresight[i] for i in range(3)]
|
||||
to_satellite = direction(*EARTHRISE_SATELLITE)
|
||||
attitude = triad([1, 1, 1], [0, 0, 1], [-x for x in to_satellite], up)
|
||||
spot = [camera[i] + EARTHRISE_RANGE * to_satellite[i] for i in range(3)]
|
||||
offset = mul(attitude, center)
|
||||
position = [spot[i] - offset[i] for i in range(3)]
|
||||
|
||||
# The lunar ground at the limb toward Earth.
|
||||
reach = math.sqrt(orbit ** 2 - RM ** 2)
|
||||
limb = direction(0.0, 0.0)
|
||||
ground = norm([camera[i] + reach * limb[i] - moon[i] for i in range(3)])
|
||||
|
||||
# The Sun: Earth and the Moon are the subject, so the direction that best lights
|
||||
# both, the product of Earth's lit fraction as seen and the Sun's height over the
|
||||
# lunar ground at the limb, with the satellite out of the Moon's shadow.
|
||||
to_camera = norm(camera) # from Earth's center
|
||||
best = None
|
||||
for az in range(0, 360, 2):
|
||||
for el in range(-88, 90, 2):
|
||||
a, e = math.radians(az), math.radians(el)
|
||||
sun = [math.cos(e) * math.cos(a), math.cos(e) * math.sin(a), math.sin(e)]
|
||||
phase = math.degrees(math.acos(dot(sun, to_camera)))
|
||||
if dot(sun, [-x for x in up]) > math.cos(math.asin(RM / orbit)):
|
||||
continue
|
||||
score = (1 + math.cos(math.radians(phase))) / 2 * max(dot(ground, sun), 0.0)
|
||||
if best is None or score > best[0]:
|
||||
best = (score, sun, phase)
|
||||
_, sun, phase = best
|
||||
|
||||
# Earth turned with its axis along image up, and EARTHRISE_GROUND as near as that
|
||||
# allows to midway across its lit half as seen.
|
||||
across = norm([sun[i] - dot(sun, to_camera) * to_camera[i] for i in range(3)])
|
||||
shown = norm([to_camera[i] + across[i] for i in range(3)])
|
||||
north = norm([up[i] - dot(up, to_camera) * to_camera[i] for i in range(3)])
|
||||
lat, lon = (math.radians(x) for x in EARTHRISE_GROUND)
|
||||
fixed = [math.cos(lat) * math.cos(lon), math.cos(lat) * math.sin(lon), math.sin(lat)]
|
||||
earth = triad([0, 0, 1], fixed, north, shown)
|
||||
|
||||
fmt = lambda v, d=2: "[" + ", ".join(f"{x:.{d}f}" for x in v) + "]"
|
||||
return f"""# Earthrise from lunar orbit: 1000 km above the Moon, Earth partly lit (phase
|
||||
# {phase:.0f} degrees) just above the curved lunar horizon, and Landsat 8 standing in
|
||||
# for a lunar orbiter {EARTHRISE_RANGE:.0f} m off in the foreground. Generated by
|
||||
# scripts/make-example-scenes.py; the model comes from scripts/fetch-models.sh.
|
||||
#
|
||||
# resolved render --scene examples/scenes/earthrise.toml --lighting L1 --preview earthrise.png
|
||||
#
|
||||
# At lighting L1 Earth shows Africa on its lit half, the Moon's ground its reflectance
|
||||
# map, and the satellite is lit by the Sun and, faintly, by Earth and the Moon. The
|
||||
# Moon has no atmosphere, so its horizon is a sharp edge, as in the Apollo photographs.
|
||||
|
||||
sun = {fmt([AU * x for x in sun], 0)}
|
||||
moon = {fmt(moon)}
|
||||
# Earth-fixed to inertial: north up, 15 N 25 E on the lit side.
|
||||
earth_attitude = {fmt(quaternion(earth), 9)}
|
||||
|
||||
# Exposed so Earth, the Moon and the satellite stay short of saturation.
|
||||
[camera]
|
||||
focal_length = {EARTHRISE_FOCAL}
|
||||
exposure = {EARTHRISE_EXPOSURE}
|
||||
|
||||
[observer]
|
||||
position = {fmt(camera)}
|
||||
look_at = {fmt(look)}
|
||||
up = {fmt(up, 6)}
|
||||
|
||||
# At lighting L1, Earth's surface shows July (from scripts/fetch-earth.sh).
|
||||
[appearance]
|
||||
earth_month = 7
|
||||
|
||||
[[target]]
|
||||
model = "../models/nasa/{file}"
|
||||
scale = {scale}
|
||||
position = {fmt(position, 4)}
|
||||
attitude = {fmt(quaternion(attitude), 9)}
|
||||
"""
|
||||
|
||||
|
||||
# Moonrise from geostationary height: looking toward Earth's limb through a 100 mm lens,
|
||||
# Australia filling the lower half of the frame, the Moon rising over the limb, and the
|
||||
# SSL-1300 in the foreground.
|
||||
MOONRISE_ORBIT = GEO
|
||||
MOONRISE_FOCAL = 0.1
|
||||
MOONRISE_GROUND = (-25.0, 134.0) # central Australia, geocentric degrees
|
||||
# The image center's height above the limb (degrees). Then, as (degrees right of the
|
||||
# image center, degrees above the limb at that column): the ground at the bottom of the
|
||||
# frame, the Moon's center and the satellite's; and the satellite's distance (m).
|
||||
MOONRISE_CENTER = 0.15
|
||||
MOONRISE_SWATH = (0.0, -1.4)
|
||||
MOONRISE_MOON = (1.7, 0.2)
|
||||
MOONRISE_SATELLITE = (-1.6, 0.8)
|
||||
MOONRISE_RANGE = 1100.0
|
||||
MOONRISE_EXPOSURE = 2e-4 # s
|
||||
|
||||
|
||||
def moonrise():
|
||||
file, scale, center = "ssl1300.glb", 0.1, MODELS[3][3]
|
||||
up, east = [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]
|
||||
b = RE * (1 - 1 / 298.257223563)
|
||||
lat = math.radians(MOONRISE_GROUND[0])
|
||||
radius_here = RE * b / math.hypot(b * math.cos(lat), RE * math.sin(lat))
|
||||
dip = math.acos(radius_here / MOONRISE_ORBIT)
|
||||
camera = [MOONRISE_ORBIT, 0.0, 0.0]
|
||||
|
||||
def hits(d, earth=None):
|
||||
"""How far along d the line of sight meets Earth, or None: the WGS84 ellipsoid
|
||||
turned by `earth` (Earth-fixed to inertial), or without it a sphere of the
|
||||
ellipsoid's radius at MOONRISE_GROUND."""
|
||||
if earth is None:
|
||||
o, v, k = camera, d, 1.0
|
||||
else:
|
||||
back = transpose(earth)
|
||||
stretch = [1.0, 1.0, RE / b]
|
||||
o = [x * f for x, f in zip(mul(back, camera), stretch)]
|
||||
v = [x * f for x, f in zip(mul(back, d), stretch)]
|
||||
k = 1.0
|
||||
r = radius_here if earth is None else RE
|
||||
aa, bb, cc = dot(v, v), dot(o, v), dot(o, o) - r * r
|
||||
disc = bb * bb - aa * cc
|
||||
return (-bb - math.sqrt(disc)) / aa * k if disc >= 0 else None
|
||||
|
||||
# The camera's axes: looking east and MOONRISE_CENTER above the limb, up toward the
|
||||
# local vertical.
|
||||
el = math.radians(MOONRISE_CENTER) - dip
|
||||
forward = norm([math.cos(el) * east[i] + math.sin(el) * up[i] for i in range(3)])
|
||||
image_up = norm([up[i] - dot(up, forward) * forward[i] for i in range(3)])
|
||||
image_right = cross(forward, image_up)
|
||||
|
||||
def ray(x, y):
|
||||
"""The line of sight x degrees right of the image center and y degrees up."""
|
||||
tx, ty = math.tan(math.radians(x)), math.tan(math.radians(y))
|
||||
return norm([forward[i] + tx * image_right[i] + ty * image_up[i] for i in range(3)])
|
||||
|
||||
def limb(x, earth=None):
|
||||
"""Degrees up from the image center to the limb in column x."""
|
||||
lo, hi = -10.0, 10.0 # on Earth, in space
|
||||
for _ in range(60):
|
||||
mid = (lo + hi) / 2
|
||||
lo, hi = (mid, hi) if hits(ray(x, mid), earth) is not None else (lo, mid)
|
||||
return lo
|
||||
|
||||
def place(x, above, earth=None):
|
||||
return ray(x, limb(x, earth) + above)
|
||||
|
||||
look = [camera[i] + MOON_DISTANCE * forward[i] for i in range(3)]
|
||||
to_satellite = place(*MOONRISE_SATELLITE)
|
||||
attitude = triad([1, 1, 1], [0, 0, 1], [-x for x in to_satellite], image_up)
|
||||
spot = [camera[i] + MOONRISE_RANGE * to_satellite[i] for i in range(3)]
|
||||
offset = mul(attitude, center)
|
||||
position = [spot[i] - offset[i] for i in range(3)]
|
||||
|
||||
# The ground at the bottom of the frame, turned to MOONRISE_GROUND with north
|
||||
# along the line of sight across it (toward the limb, image up).
|
||||
swath = place(*MOONRISE_SWATH)
|
||||
ground = norm([camera[i] + hits(swath) * swath[i] for i in range(3)])
|
||||
north = norm([swath[i] - dot(swath, ground) * ground[i] for i in range(3)])
|
||||
lon = math.radians(MOONRISE_GROUND[1])
|
||||
fixed = [math.cos(lat) * math.cos(lon), math.cos(lat) * math.sin(lon), math.sin(lat)]
|
||||
earth = triad(fixed, [0, 0, 1], ground, north)
|
||||
|
||||
# The Moon on its line of sight, MOON_DISTANCE from Earth's center, placed against
|
||||
# the ellipsoid's limb.
|
||||
to_moon = place(*MOONRISE_MOON, earth)
|
||||
od = dot(camera, to_moon)
|
||||
reach = -od + math.sqrt(od * od - dot(camera, camera) + MOON_DISTANCE ** 2)
|
||||
moon = [camera[i] + reach * to_moon[i] for i in range(3)]
|
||||
|
||||
# The Sun: Earth and the Moon are the subject, so the direction that best lights
|
||||
# both, the product of the Sun's height over that ground and the Moon's lit
|
||||
# fraction, with the satellite and the Moon out of Earth's shadow.
|
||||
from_moon = norm([camera[i] - moon[i] for i in range(3)])
|
||||
moon_dir = norm(moon)
|
||||
best = None
|
||||
for az in range(0, 360, 2):
|
||||
for el in range(-88, 90, 2):
|
||||
a, e = math.radians(az), math.radians(el)
|
||||
sun = [math.cos(e) * math.cos(a), math.cos(e) * math.sin(a), math.sin(e)]
|
||||
phase = math.degrees(math.acos(dot(sun, from_moon)))
|
||||
if dot(sun, [-x for x in norm(camera)]) > math.cos(math.asin(RE / GEO)):
|
||||
continue
|
||||
if dot(sun, [-x for x in moon_dir]) > math.cos(math.radians(3)):
|
||||
continue
|
||||
score = (1 + math.cos(math.radians(phase))) / 2 * max(dot(ground, sun), 0.0)
|
||||
if best is None or score > best[0]:
|
||||
best = (score, sun, phase)
|
||||
_, sun, phase = best
|
||||
|
||||
fmt = lambda v, d=2: "[" + ", ".join(f"{x:.{d}f}" for x in v) + "]"
|
||||
return f"""# Moonrise from geostationary height: looking toward Earth's limb through a 100 mm
|
||||
# lens, Australia filling the lower half of the frame, the Moon (phase {phase:.0f} degrees)
|
||||
# rising over the limb, and an SSL-1300 communications satellite {MOONRISE_RANGE / 1000:.1f} km off in
|
||||
# the foreground. Generated by scripts/make-example-scenes.py; the model comes from
|
||||
# scripts/fetch-models.sh.
|
||||
#
|
||||
# resolved render --scene examples/scenes/moonrise.toml --lighting L1 --preview moonrise.png
|
||||
#
|
||||
# At lighting L1 Earth's surface and the Moon's come from their maps. There is no
|
||||
# atmosphere yet, so the limb is a sharp edge rather than a thin glowing band.
|
||||
|
||||
sun = {fmt([AU * x for x in sun], 0)}
|
||||
moon = {fmt(moon)}
|
||||
# Earth-fixed to inertial: 25 S 134 E at the bottom of the frame, looking north.
|
||||
earth_attitude = {fmt(quaternion(earth), 9)}
|
||||
|
||||
# Exposed so Earth, the Moon and the satellite stay short of saturation.
|
||||
[camera]
|
||||
focal_length = {MOONRISE_FOCAL}
|
||||
exposure = {MOONRISE_EXPOSURE}
|
||||
|
||||
[observer]
|
||||
position = {fmt(camera)}
|
||||
look_at = {fmt(look)}
|
||||
up = [1, 0, 0]
|
||||
|
||||
# At lighting L1, Earth's surface shows July (from scripts/fetch-earth.sh).
|
||||
[appearance]
|
||||
earth_month = 7
|
||||
|
||||
[[target]]
|
||||
model = "../models/nasa/{file}"
|
||||
scale = {scale}
|
||||
position = {fmt(position, 4)}
|
||||
attitude = {fmt(quaternion(attitude), 9)}
|
||||
"""
|
||||
|
||||
|
||||
out = pathlib.Path(__file__).resolve().parent.parent / "examples" / "scenes"
|
||||
for model in MODELS:
|
||||
path = out / f"{model[0]}.toml"
|
||||
path.write_text(scene(*model))
|
||||
print(f"wrote {path}")
|
||||
path = out / "earthrise.toml"
|
||||
path.write_text(earthrise())
|
||||
print(f"wrote {path}")
|
||||
path = out / "moonrise.toml"
|
||||
path.write_text(moonrise())
|
||||
print(f"wrote {path}")
|
||||
Reference in new issue
Block a user