airliner mesh is generated in-repo: tools/plane_gen.py writes vendor/plane3d.js (480 tris, CC0, same unit extent as the old Grab3D derivative); attribution updated
This commit is contained in:
1 parent
499c00d8b1
commit
113e4c522d
5 files changed
+211
-12
No files matched your search
@@ -23,7 +23,8 @@ dropped into an iframe or kiosk browser and left running.
|
|||||||
- **Realistic night sky**: ~8700 stars from the Yale Bright Star Catalogue
|
- **Realistic night sky**: ~8700 stars from the Yale Bright Star Catalogue
|
||||||
(points sized by magnitude, coloured by temperature), rotated at the sidereal
|
(points sized by magnitude, coloured by temperature), rotated at the sidereal
|
||||||
rate for the site's latitude, a Milky Way band and a phase-correct moon.
|
rate for the site's latitude, a Milky Way band and a phase-correct moon.
|
||||||
- **Aircraft**: low-poly jets (CC0 model) on curved routes between real
|
- **Aircraft**: low-poly jets (own CC0 mesh, generated by `tools/plane_gen.py`)
|
||||||
|
on curved routes between real
|
||||||
airfields, with a destination rolled at every take-off, sun-lit silhouettes
|
airfields, with a destination rolled at every take-off, sun-lit silhouettes
|
||||||
and contrails by day, strobing position lights by night.
|
and contrails by day, strobing position lights by night.
|
||||||
- **Night lights**: vehicle lights moving along the real road network (Overpass)
|
- **Night lights**: vehicle lights moving along the real road network (Overpass)
|
||||||
@@ -237,6 +238,8 @@ window.TRANSLATIONS['Sunrise']['xx'] = 'MyTranslation';
|
|||||||
├── ash.css default colour theme
|
├── ash.css default colour theme
|
||||||
├── gruvbox.css alternative colour theme
|
├── gruvbox.css alternative colour theme
|
||||||
├── nord.css alternative colour theme
|
├── nord.css alternative colour theme
|
||||||
|
├── tools/
|
||||||
|
│ └── plane_gen.py generates vendor/plane3d.js (the airliner mesh)
|
||||||
├── vendor/
|
├── vendor/
|
||||||
│ ├── clouds3d.js WebGL scene renderer (sky, clouds, terrain, lights)
|
│ ├── clouds3d.js WebGL scene renderer (sky, clouds, terrain, lights)
|
||||||
│ ├── suncalc.js sun/moon positions - ISC (SunCalc by mourner)
|
│ ├── suncalc.js sun/moon positions - ISC (SunCalc by mourner)
|
||||||
@@ -255,7 +258,8 @@ window.TRANSLATIONS['Sunrise']['xx'] = 'MyTranslation';
|
|||||||
|
|
||||||
- **Background application code**: MIT (see `LICENSE`)
|
- **Background application code**: MIT (see `LICENSE`)
|
||||||
- **SunCalc** (vendored): ISC - <https://github.com/mourner/suncalc>
|
- **SunCalc** (vendored): ISC - <https://github.com/mourner/suncalc>
|
||||||
- **Aircraft model** (`vendor/plane3d.js`): CC0
|
- **Aircraft model** (`vendor/plane3d.js`): own geometry, generated by
|
||||||
|
`tools/plane_gen.py`, CC0
|
||||||
- **Open-Meteo** (weather API): CC BY 4.0 - <https://open-meteo.com/>
|
- **Open-Meteo** (weather API): CC BY 4.0 - <https://open-meteo.com/>
|
||||||
- **ESRI** basemaps (`World_Imagery`, `World_Street_Map`) served through the
|
- **ESRI** basemaps (`World_Imagery`, `World_Street_Map`) served through the
|
||||||
bundled proxy: Esri, its licensors and contributors -
|
bundled proxy: Esri, its licensors and contributors -
|
||||||
|
|||||||
@@ -1623,8 +1623,6 @@ window.weatherThemes = {
|
|||||||
lines.push('Terrain: Copernicus DEM via Mapzen Terrarium');
|
lines.push('Terrain: Copernicus DEM via Mapzen Terrarium');
|
||||||
if (t.carLights !== false || t.cityLights !== false) lines.push('Roads: \u00a9 OpenStreetMap contributors (ODbL)');
|
if (t.carLights !== false || t.cityLights !== false) lines.push('Roads: \u00a9 OpenStreetMap contributors (ODbL)');
|
||||||
}
|
}
|
||||||
var fc = cfg.flights;
|
|
||||||
if (!fc || fc.enabled !== false) lines.push('Airliner model: Grab3D (CC0)');
|
|
||||||
lines.push('Stars: Yale Bright Star Catalogue');
|
lines.push('Stars: Yale Bright Star Catalogue');
|
||||||
lines.push('Weather: Open-Meteo');
|
lines.push('Weather: Open-Meteo');
|
||||||
el.innerHTML = lines.join('<br>');
|
el.innerHTML = lines.join('<br>');
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate vendor/plane3d.js - the low-poly airliner used by the flight layer.
|
||||||
|
|
||||||
|
The mesh is built here from primitives (no source model, no textures), so the
|
||||||
|
asset is CC0 by construction and reproducible:
|
||||||
|
|
||||||
|
python3 tools/plane_gen.py # writes vendor/plane3d.js
|
||||||
|
|
||||||
|
Output format (exactly what clouds3d.js expects):
|
||||||
|
window.__planeVerts = '<base64>' of non-indexed triangles, interleaved per
|
||||||
|
vertex: pos(3), normal(3), colIdx(1), float32 little endian.
|
||||||
|
colIdx: 0 = body (light albedo), 1 = glazing (dark albedo).
|
||||||
|
Convention: forward = +Z, up = +Y, nose-to-tail ~2.0 (the renderer scales
|
||||||
|
the model with uS and assumes that ~2.0 span, see PLANE_VERT_SRC).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
BODY, GLASS = 0.0, 1.0
|
||||||
|
SIDES = 10
|
||||||
|
TRIS = []
|
||||||
|
|
||||||
|
|
||||||
|
def add_tri(a, b, c, col, ref):
|
||||||
|
"""Store one triangle; its flat normal is oriented away from ref."""
|
||||||
|
ux, uy, uz = b[0] - a[0], b[1] - a[1], b[2] - a[2]
|
||||||
|
vx, vy, vz = c[0] - a[0], c[1] - a[1], c[2] - a[2]
|
||||||
|
nx, ny, nz = uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx
|
||||||
|
ln = math.sqrt(nx * nx + ny * ny + nz * nz)
|
||||||
|
if ln < 1e-9:
|
||||||
|
return
|
||||||
|
nx, ny, nz = nx / ln, ny / ln, nz / ln
|
||||||
|
mx = (a[0] + b[0] + c[0]) / 3.0 - ref[0]
|
||||||
|
my = (a[1] + b[1] + c[1]) / 3.0 - ref[1]
|
||||||
|
mz = (a[2] + b[2] + c[2]) / 3.0 - ref[2]
|
||||||
|
if nx * mx + ny * my + nz * mz < 0:
|
||||||
|
nx, ny, nz = -nx, -ny, -nz
|
||||||
|
TRIS.append((a, b, c, col, (nx, ny, nz)))
|
||||||
|
|
||||||
|
|
||||||
|
def add_quad(a, b, c, d, col, ref):
|
||||||
|
add_tri(a, b, c, col, ref)
|
||||||
|
add_tri(a, c, d, col, ref)
|
||||||
|
|
||||||
|
|
||||||
|
def ring(z, r, cy, cx=0.0, phase=0.0):
|
||||||
|
pts = []
|
||||||
|
for i in range(SIDES):
|
||||||
|
ang = phase + 2.0 * math.pi * i / SIDES
|
||||||
|
pts.append((cx + r * math.sin(ang), cy + r * math.cos(ang), z))
|
||||||
|
return pts
|
||||||
|
|
||||||
|
|
||||||
|
def add_cap(r, ref, col=BODY):
|
||||||
|
cx = sum(p[0] for p in r) / len(r)
|
||||||
|
cy = sum(p[1] for p in r) / len(r)
|
||||||
|
cz = sum(p[2] for p in r) / len(r)
|
||||||
|
for i in range(len(r)):
|
||||||
|
add_tri((cx, cy, cz), r[i], r[(i + 1) % len(r)], col, ref)
|
||||||
|
|
||||||
|
|
||||||
|
def tube(stations, cy, ref, cx=0.0, col=BODY):
|
||||||
|
"""stations: [(z, radius), ...] from one end to the other, caps closed."""
|
||||||
|
rings = [ring(z, r, cy, cx) for (z, r) in stations]
|
||||||
|
for k in range(len(rings) - 1):
|
||||||
|
a, b = rings[k], rings[k + 1]
|
||||||
|
for i in range(SIDES):
|
||||||
|
j = (i + 1) % SIDES
|
||||||
|
add_quad(a[i], a[j], b[j], b[i], col, ref)
|
||||||
|
add_cap(rings[0], ref, col)
|
||||||
|
add_cap(rings[-1], ref, col)
|
||||||
|
|
||||||
|
|
||||||
|
def slab(root_le, root_te, tip_le, tip_te, half_t):
|
||||||
|
"""Tapered wing/stabilizer: planform corners extruded by +-half_t in y."""
|
||||||
|
def off(p, dy):
|
||||||
|
return (p[0], p[1] + dy, p[2])
|
||||||
|
tlr, ttr = off(root_le, half_t), off(root_te, half_t)
|
||||||
|
tlt, ttt = off(tip_le, half_t), off(tip_te, half_t)
|
||||||
|
blr, btr = off(root_le, -half_t), off(root_te, -half_t)
|
||||||
|
blt, btt = off(tip_le, -half_t), off(tip_te, -half_t)
|
||||||
|
ref = ((root_le[0] + tip_le[0]) / 2.0, root_le[1] - 0.25,
|
||||||
|
(root_le[2] + tip_le[2]) / 2.0)
|
||||||
|
add_quad(tlr, tlt, ttt, ttr, BODY, ref) # top skin
|
||||||
|
add_quad(blr, btr, btt, blt, BODY, ref) # bottom skin
|
||||||
|
add_quad(tlr, blr, blt, tlt, BODY, ref) # leading edge
|
||||||
|
add_quad(ttr, ttt, btt, btr, BODY, ref) # trailing edge
|
||||||
|
add_quad(tlt, ttt, btt, blt, BODY, ref) # tip
|
||||||
|
|
||||||
|
|
||||||
|
def plate(outline, x, thickness, col=BODY):
|
||||||
|
"""Thin plate: outline is a list of (z, y), extruded around x."""
|
||||||
|
a = [(x - thickness / 2.0, p[1], p[0]) for p in outline]
|
||||||
|
b = [(x + thickness / 2.0, p[1], p[0]) for p in outline]
|
||||||
|
ym = sum(p[1] for p in outline) / len(outline)
|
||||||
|
zm = sum(p[0] for p in outline) / len(outline)
|
||||||
|
for i in range(1, len(a) - 1):
|
||||||
|
add_tri(a[0], a[i], a[i + 1], col, (x - 1.0, ym, zm))
|
||||||
|
add_tri(b[0], b[i + 1], b[i], col, (x + 1.0, ym, zm))
|
||||||
|
for i in range(len(a)):
|
||||||
|
j = (i + 1) % len(a)
|
||||||
|
add_quad(a[i], a[j], b[j], b[i], col, (x, ym, zm))
|
||||||
|
|
||||||
|
|
||||||
|
def window_band(ang_mid, cy, r, z0, z1, step, h, col=GLASS):
|
||||||
|
"""Glazing quads on the fuselage skin around the section angle ang_mid."""
|
||||||
|
x = r * 1.02 * math.sin(ang_mid)
|
||||||
|
ym = cy + r * 1.02 * math.cos(ang_mid)
|
||||||
|
y_lo, y_hi = ym - h / 2.0, ym + h / 2.0
|
||||||
|
ref = (0.0, cy, 0.5 * (z0 + z1))
|
||||||
|
z = z0
|
||||||
|
while z + step <= z1 + 1e-9:
|
||||||
|
za, zb = z + step * 0.18, z + step * 0.82
|
||||||
|
add_quad((x, y_hi, za), (x, y_hi, zb), (x, y_lo, zb), (x, y_lo, za),
|
||||||
|
col, ref)
|
||||||
|
z += step
|
||||||
|
|
||||||
|
|
||||||
|
def build():
|
||||||
|
cy = 0.115
|
||||||
|
|
||||||
|
# fuselage: nose cone, parallel section, tail cone
|
||||||
|
tube([(0.97, 0.020), (0.88, 0.075), (0.78, 0.105), (0.70, 0.115),
|
||||||
|
(-0.55, 0.115), (-0.75, 0.095), (-0.90, 0.055), (-1.00, 0.015)],
|
||||||
|
cy, (0.0, cy, 0.0))
|
||||||
|
|
||||||
|
# wings: swept and tapered, slight dihedral (root chord 0.56, tip 0.18)
|
||||||
|
for s in (1.0, -1.0):
|
||||||
|
slab((s * 0.09, 0.055, 0.30), (s * 0.09, 0.055, -0.26),
|
||||||
|
(s * 0.91, 0.125, -0.28), (s * 0.91, 0.125, -0.46), 0.016)
|
||||||
|
# winglet: small plate standing up at the tip
|
||||||
|
plate([(-0.28, 0.125), (-0.46, 0.125), (-0.46, 0.235), (-0.28, 0.215)],
|
||||||
|
s * 0.912, 0.014)
|
||||||
|
|
||||||
|
# horizontal stabilizers
|
||||||
|
for s in (1.0, -1.0):
|
||||||
|
slab((s * 0.07, 0.135, -0.66), (s * 0.07, 0.135, -0.94),
|
||||||
|
(s * 0.42, 0.165, -0.86), (s * 0.42, 0.165, -1.00), 0.011)
|
||||||
|
|
||||||
|
# vertical fin (outline in the (z, y) plane)
|
||||||
|
plate([(-0.48, 0.20), (-0.60, 0.60), (-0.86, 0.60), (-1.00, 0.26)],
|
||||||
|
0.0, 0.026)
|
||||||
|
|
||||||
|
# engine nacelles under the wings, with a thin pylon each
|
||||||
|
for s in (1.0, -1.0):
|
||||||
|
ex, ey = s * 0.36, -0.055
|
||||||
|
tube([(0.24, 0.030), (0.18, 0.072), (-0.06, 0.075), (-0.14, 0.045)],
|
||||||
|
ey, (ex, ey, 0.05), cx=ex)
|
||||||
|
plate([(0.16, 0.015), (-0.02, 0.015), (-0.02, 0.075), (0.16, 0.075)],
|
||||||
|
ex, 0.030)
|
||||||
|
|
||||||
|
# window bands on both flanks, cockpit glazing on the upper flanks
|
||||||
|
d = math.radians
|
||||||
|
window_band(d(90), cy, 0.115, -0.42, 0.58, 0.075, 0.048)
|
||||||
|
window_band(d(270), cy, 0.115, -0.42, 0.58, 0.075, 0.048)
|
||||||
|
window_band(d(54), cy, 0.115, 0.64, 0.82, 0.09, 0.055)
|
||||||
|
window_band(d(306), cy, 0.115, 0.64, 0.82, 0.09, 0.055)
|
||||||
|
|
||||||
|
|
||||||
|
def emit():
|
||||||
|
out = bytearray()
|
||||||
|
for (a, b, c, col, n) in TRIS:
|
||||||
|
for p in (a, b, c):
|
||||||
|
out += struct.pack('<fffffff',
|
||||||
|
p[0], p[1], p[2], n[0], n[1], n[2], col)
|
||||||
|
b64 = base64.b64encode(bytes(out)).decode('ascii')
|
||||||
|
head = ("""/* Airliner low-poly mesh (%d triangles, flat shaded, no textures).
|
||||||
|
* Generated procedurally by tools/plane_gen.py - original geometry, CC0 1.0.
|
||||||
|
* Non-indexed interleaved: pos(3) nrm(3) colIdx(1); unit extent 1.0,
|
||||||
|
* forward = +Z, up = +Y, nose-to-tail ~2.0 (see PLANE_VERT_SRC in clouds3d.js).
|
||||||
|
* colIdx: 0 = body, 1 = glazing.
|
||||||
|
*/
|
||||||
|
window.__planeVerts = '""" % len(TRIS))
|
||||||
|
path = os.path.join(ROOT, 'vendor', 'plane3d.js')
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(head + b64 + "';\n")
|
||||||
|
|
||||||
|
pts = [p for t in TRIS for p in t[:3]]
|
||||||
|
xs = [p[0] for p in pts]; ys = [p[1] for p in pts]; zs = [p[2] for p in pts]
|
||||||
|
glass = sum(1 for t in TRIS if t[3] == GLASS)
|
||||||
|
print('wrote %s' % path)
|
||||||
|
print('triangles %d (glazing %d), bytes %d' % (len(TRIS), glass, len(out)))
|
||||||
|
print('x %.3f..%.3f y %.3f..%.3f z %.3f..%.3f'
|
||||||
|
% (min(xs), max(xs), min(ys), max(ys), min(zs), max(zs)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
build()
|
||||||
|
emit()
|
||||||
Vendored
+3
-4
@@ -8,10 +8,9 @@ stars.json - star catalogue derived from the Yale Bright Star Catalogue
|
|||||||
(5th ed., public catalogue data). Generated for this project; see the
|
(5th ed., public catalogue data). Generated for this project; see the
|
||||||
real-sky notes in clouds3d.js.
|
real-sky notes in clouds3d.js.
|
||||||
|
|
||||||
plane3d.js - low-poly airliner mesh (599 triangles, flattened, no textures)
|
plane3d.js - low-poly airliner mesh (480 triangles, flat shaded, no textures)
|
||||||
Derived from 'Passenger Airplane' by Grab3D (https://grab3d.com), CC0 1.0
|
Original geometry generated by tools/plane_gen.py in this repository; offered
|
||||||
(public domain dedication). Decimated and repacked for this project; the
|
under CC0 1.0 like the rest of the project's own assets.
|
||||||
derived mesh is offered under CC0 as well.
|
|
||||||
|
|
||||||
clouds3d.js - original code for this project (same license as the
|
clouds3d.js - original code for this project (same license as the
|
||||||
repository root LICENSE).
|
repository root LICENSE).
|
||||||
|
|||||||
Vendored
+7
-4
@@ -1,4 +1,7 @@
|
|||||||
/* Airliner low-poly mesh (599 tris). Derived from 'Passenger Airplane' by Grab3D,
|
/* Airliner low-poly mesh (480 triangles, flat shaded, no textures).
|
||||||
* CC0 (https://grab3d.com) - decimated and flattened for distant viewing.
|
* Generated procedurally by tools/plane_gen.py - original geometry, CC0 1.0.
|
||||||
* Non-indexed interleaved: pos(3) nrm(3) colIdx(1); unit extent 1.0. */
|
* Non-indexed interleaved: pos(3) nrm(3) colIdx(1); unit extent 1.0,
|
||||||
window.__planeVerts = '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
Line truncated
|
* forward = +Z, up = +Y, nose-to-tail ~2.0 (see PLANE_VERT_SRC in clouds3d.js).
|
||||||
|
* colIdx: 0 = body, 1 = glazing.
|
||||||
|
*/
|
||||||
|
window.__planeVerts = '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 truncated
|
||||||
Reference in new issue
Block a user