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
@@ -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()
|
||||
Reference in new issue
Block a user