feat(city): add landing gear, cockpit glass, and winglets to aircraft
Implement proper ground clearance and landing gear for all aircraft types (airliner, regional, prop). Add cockpit glass at the nose, winglets at wing tips, and configurable propeller blade length. Wheels now touch the runway surface (y=0) with the fuselage lifted by clearance values.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
+61
-4
@@ -2602,9 +2602,11 @@ function runwayMarkings(designators) {
|
|||||||
function buildAircraft(kind, livery) {
|
function buildAircraft(kind, livery) {
|
||||||
const g = new THREE.Group();
|
const g = new THREE.Group();
|
||||||
const dims = {
|
const dims = {
|
||||||
airliner: { L: 16, r: 0.95, span: 15, chord: 2.6, tail: 3.6, fin: 2.8, engines: true },
|
// clearance: fuselage centreline height — the wheels (bottom at
|
||||||
regional: { L: 12, r: 0.75, span: 11.6, chord: 2.1, tail: 2.8, fin: 2.2, engines: true },
|
// y≈0) carry the airframe above the pavement instead of dragging.
|
||||||
prop: { L: 9.5, r: 0.65, span: 12, chord: 1.9, tail: 2.4, fin: 1.9, engines: false, prop: true },
|
airliner: { L: 16, r: 0.95, span: 15, chord: 2.6, tail: 3.6, fin: 2.8, engines: true, clearance: 1.75, wheel: 0.34 },
|
||||||
|
regional: { L: 12, r: 0.75, span: 11.6, chord: 2.1, tail: 2.8, fin: 2.2, engines: true, clearance: 1.4, wheel: 0.3 },
|
||||||
|
prop: { L: 9.5, r: 0.65, span: 12, chord: 1.9, tail: 2.4, fin: 1.9, engines: false, prop: true, blade: 2.6, clearance: 1.5, wheel: 0.28 },
|
||||||
};
|
};
|
||||||
const d = dims[kind] || dims.airliner;
|
const d = dims[kind] || dims.airliner;
|
||||||
const bodyMat = new THREE.MeshStandardMaterial({ color: 0xf3f5f8, roughness: 0.35, metalness: 0.45 });
|
const bodyMat = new THREE.MeshStandardMaterial({ color: 0xf3f5f8, roughness: 0.35, metalness: 0.45 });
|
||||||
@@ -2660,8 +2662,9 @@ function buildAircraft(kind, livery) {
|
|||||||
|
|
||||||
if (d.prop) {
|
if (d.prop) {
|
||||||
const prop = new THREE.Group();
|
const prop = new THREE.Group();
|
||||||
|
const bladeLen = d.blade ?? 3.4;
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const blade = new THREE.Mesh(new THREE.BoxGeometry(0.12, 3.4, 0.16), darkMat);
|
const blade = new THREE.Mesh(new THREE.BoxGeometry(0.12, bladeLen, 0.16), darkMat);
|
||||||
blade.rotation.x = (i * Math.PI * 2) / 3;
|
blade.rotation.x = (i * Math.PI * 2) / 3;
|
||||||
prop.add(blade);
|
prop.add(blade);
|
||||||
}
|
}
|
||||||
@@ -2670,6 +2673,19 @@ function buildAircraft(kind, livery) {
|
|||||||
g.userData.prop = prop;
|
g.userData.prop = prop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cockpit glass at the nose (rider sits above the nose gear).
|
||||||
|
const glassMat = new THREE.MeshStandardMaterial({ color: 0x0e1620, roughness: 0.2, metalness: 0.8 });
|
||||||
|
const cockpit = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.4, 0.62), glassMat);
|
||||||
|
cockpit.position.set(d.L * 0.44, d.r * 0.5, 0);
|
||||||
|
g.add(cockpit);
|
||||||
|
|
||||||
|
// Winglets at the tips (airline livery accent).
|
||||||
|
for (const side of [1, -1]) {
|
||||||
|
const winglet = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.55, 0.12), accentMat);
|
||||||
|
winglet.position.set(d.L * 0.04, 0.1, side * (d.span / 2));
|
||||||
|
g.add(winglet);
|
||||||
|
}
|
||||||
|
|
||||||
const beacon = new THREE.Mesh(
|
const beacon = new THREE.Mesh(
|
||||||
new THREE.SphereGeometry(0.2, 8, 8),
|
new THREE.SphereGeometry(0.2, 8, 8),
|
||||||
new THREE.MeshStandardMaterial({ color: 0xff3333, emissive: 0xff3333, emissiveIntensity: 2 })
|
new THREE.MeshStandardMaterial({ color: 0xff3333, emissive: 0xff3333, emissiveIntensity: 2 })
|
||||||
@@ -2677,7 +2693,34 @@ function buildAircraft(kind, livery) {
|
|||||||
beacon.position.set(d.L * 0.08, d.r + 0.3, 0);
|
beacon.position.set(d.L * 0.08, d.r + 0.3, 0);
|
||||||
g.add(beacon);
|
g.add(beacon);
|
||||||
g.userData.beacon = beacon;
|
g.userData.beacon = beacon;
|
||||||
|
|
||||||
|
// Ride on the landing gear: lift the whole airframe so the wheels
|
||||||
|
// (added next, ground-based) carry it above the pavement.
|
||||||
|
for (const child of g.children) {
|
||||||
|
child.position.y += d.clearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Landing gear: nose leg + two mains; wheel bottoms touch y=0 ----
|
||||||
|
const gearMat = new THREE.MeshStandardMaterial({ color: 0x2a2e36, roughness: 0.45, metalness: 0.6 });
|
||||||
|
const addGear = (gx, gz, wr, strutR) => {
|
||||||
|
const strutBottom = wr * 0.5;
|
||||||
|
const top = d.clearance - d.r * 0.35;
|
||||||
|
const strutH = top - strutBottom;
|
||||||
|
const strut = new THREE.Mesh(new THREE.CylinderGeometry(strutR, strutR * 1.2, strutH, 8), gearMat);
|
||||||
|
strut.position.set(gx, strutBottom + strutH / 2, gz);
|
||||||
|
g.add(strut);
|
||||||
|
const wheel = new THREE.Mesh(new THREE.CylinderGeometry(wr, wr, wr * 0.75, 12), gearMat);
|
||||||
|
wheel.rotation.x = Math.PI / 2; // axle along Z — rolls along the runway
|
||||||
|
// Centre at half the tyre thickness → the bottom sits exactly on y=0.
|
||||||
|
wheel.position.set(gx, wr * 0.375, gz);
|
||||||
|
g.add(wheel);
|
||||||
|
};
|
||||||
|
addGear(d.L * 0.4, 0, d.wheel * 0.85, 0.06); // nose gear
|
||||||
|
addGear(d.L * 0.06, d.span * 0.2, d.wheel, 0.085); // main right
|
||||||
|
addGear(d.L * 0.06, -d.span * 0.2, d.wheel, 0.085); // main left
|
||||||
|
|
||||||
g.userData.kind = kind;
|
g.userData.kind = kind;
|
||||||
|
g.userData.gear = { kind: 'wheels', clearance: d.clearance };
|
||||||
g.traverse(o => { if (o.isMesh) o.castShadow = true; });
|
g.traverse(o => { if (o.isMesh) o.castShadow = true; });
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
@@ -2753,6 +2796,12 @@ function buildHelicopter(livery) {
|
|||||||
beacon.position.set(0, 2.0, 0);
|
beacon.position.set(0, 2.0, 0);
|
||||||
g.add(beacon);
|
g.add(beacon);
|
||||||
g.userData.beacon = beacon;
|
g.userData.beacon = beacon;
|
||||||
|
|
||||||
|
// Ride on the skids: lift the fuselage so the skid tubes rest on y=0.
|
||||||
|
for (const child of g.children) {
|
||||||
|
child.position.y += 1.4;
|
||||||
|
}
|
||||||
|
g.userData.gear = { kind: 'skids', clearance: 1.4 };
|
||||||
g.userData.kind = 'helicopter';
|
g.userData.kind = 'helicopter';
|
||||||
g.traverse(o => { if (o.isMesh) o.castShadow = true; });
|
g.traverse(o => { if (o.isMesh) o.castShadow = true; });
|
||||||
return g;
|
return g;
|
||||||
@@ -3280,6 +3329,14 @@ function initAirplanes(cfg) {
|
|||||||
takeoffs: flights.takeoffs,
|
takeoffs: flights.takeoffs,
|
||||||
landings: flights.landings,
|
landings: flights.landings,
|
||||||
states: aircraft.map(a => a.callsign + ':' + a.state),
|
states: aircraft.map(a => a.callsign + ':' + a.state),
|
||||||
|
gear: aircraft.map(a => ({
|
||||||
|
kind: a.mesh.userData.kind,
|
||||||
|
gear: a.mesh.userData.gear?.kind || 'none',
|
||||||
|
state: a.state,
|
||||||
|
x: +a.p[0].toFixed(1),
|
||||||
|
y: +a.p[1].toFixed(2),
|
||||||
|
z: +a.p[2].toFixed(1),
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
update(dt) {
|
update(dt) {
|
||||||
|
|||||||
Reference in New Issue
Block a user