Files
retro-flight-sim/js/aircraft.js
T
mac-container-dev 76bad46d09 Initial commit: Retro 90s flight simulator MVP
Loading screen, main menu, 3D flight sim with CRT post-processing,
procedural terrain, airport with buildings, low-poly aircraft,
flight physics, HUD instruments, and sound.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 10:43:22 +00:00

220 lines
5.8 KiB
JavaScript

import * as THREE from 'three';
function flatMat(color, opts = {}) {
return new THREE.MeshLambertMaterial({ color, flatShading: true, ...opts });
}
export class Aircraft {
constructor() {
this.group = new THREE.Group();
this.propeller = null;
this.landingGear = [];
this.gearExtended = true;
this.build();
}
build() {
const bodyMat = flatMat(0x6688AA);
const whiteMat = flatMat(0xFFFFFF);
const redMat = flatMat(0xCC0000);
const cockpitMat = flatMat(0x333333, { transparent: true, opacity: 0.6 });
const darkMat = flatMat(0x333333);
const metalMat = flatMat(0x888888);
// Fuselage
const fuselage = new THREE.Mesh(
new THREE.CylinderGeometry(0.6, 0.45, 9, 6),
bodyMat
);
fuselage.rotation.x = Math.PI / 2;
this.group.add(fuselage);
// Nose cone
const nose = new THREE.Mesh(
new THREE.SphereGeometry(0.4, 6, 4, 0, Math.PI * 2, 0, Math.PI / 2),
bodyMat
);
nose.rotation.x = -Math.PI / 2;
nose.position.z = -4.5;
this.group.add(nose);
// Tail cone
const tailCone = new THREE.Mesh(
new THREE.CylinderGeometry(0.4, 0.15, 2.5, 6),
bodyMat
);
tailCone.rotation.x = Math.PI / 2;
tailCone.position.z = 5.5;
this.group.add(tailCone);
// Cockpit canopy
const canopy = new THREE.Mesh(
new THREE.SphereGeometry(0.55, 6, 4, 0, Math.PI * 2, 0, Math.PI / 2),
cockpitMat
);
canopy.position.set(0, 0.4, -1);
this.group.add(canopy);
// Wings
const wings = new THREE.Mesh(
new THREE.BoxGeometry(14, 0.2, 2),
bodyMat
);
wings.position.z = -0.5;
this.group.add(wings);
// Wingtips (red)
const leftTip = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.25, 1.6), redMat);
leftTip.position.set(-6.9, 0, -0.5);
this.group.add(leftTip);
const rightTip = leftTip.clone();
rightTip.position.x = 6.9;
this.group.add(rightTip);
// Ailerons (white strips on wing trailing edge)
const leftAileron = new THREE.Mesh(new THREE.BoxGeometry(2, 0.15, 0.4), whiteMat);
leftAileron.position.set(-5, -0.15, 0.3);
this.group.add(leftAileron);
const rightAileron = leftAileron.clone();
rightAileron.position.x = 5;
this.group.add(rightAileron);
// Vertical stabilizer
const vStab = new THREE.Mesh(
new THREE.BoxGeometry(0.15, 2.5, 1.5),
bodyMat
);
vStab.position.set(0, 1.2, 5.5);
this.group.add(vStab);
// Rudder (white)
const rudder = new THREE.Mesh(
new THREE.BoxGeometry(0.12, 1.8, 0.5),
whiteMat
);
rudder.position.set(0, 1.2, 6.3);
this.group.add(rudder);
// Red tail band
const tailBand = new THREE.Mesh(
new THREE.BoxGeometry(0.12, 0.6, 1.2),
redMat
);
tailBand.position.set(0, 1.8, 5.5);
this.group.add(tailBand);
// Horizontal stabilizer
const hStab = new THREE.Mesh(
new THREE.BoxGeometry(5, 0.15, 1.2),
bodyMat
);
hStab.position.set(0, 0.1, 5.5);
this.group.add(hStab);
// Elevators (white)
const leftElev = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.12, 0.4), whiteMat);
leftElev.position.set(-2.5, -0.05, 6);
this.group.add(leftElev);
const rightElev = leftElev.clone();
rightElev.position.x = 2.5;
this.group.add(rightElev);
// Engine cowling
const cowling = new THREE.Mesh(
new THREE.CylinderGeometry(0.45, 0.55, 1.5, 6),
darkMat
);
cowling.rotation.x = Math.PI / 2;
cowling.position.z = -5;
this.group.add(cowling);
// Propeller
this.propeller = new THREE.Group();
const blade1 = new THREE.Mesh(
new THREE.BoxGeometry(0.12, 3, 0.08),
metalMat
);
this.propeller.add(blade1);
const blade2 = new THREE.Mesh(
new THREE.BoxGeometry(3, 0.12, 0.08),
metalMat
);
this.propeller.add(blade2);
// Hub
const hub = new THREE.Mesh(
new THREE.SphereGeometry(0.15, 4, 4),
metalMat
);
this.propeller.add(hub);
this.propeller.position.z = -5.8;
this.group.add(this.propeller);
// Landing gear
const gearMat = flatMat(0x222222);
const wheelMat = flatMat(0x111111);
// Nose gear
const noseGearStrut = new THREE.Mesh(
new THREE.CylinderGeometry(0.06, 0.06, 2, 4),
gearMat
);
noseGearStrut.position.set(0, -1.5, -3.5);
this.group.add(noseGearStrut);
const noseWheel = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 4, 4),
wheelMat
);
noseWheel.position.set(0, -2.5, -3.5);
this.group.add(noseWheel);
this.landingGear.push(noseGearStrut, noseWheel);
// Left main gear
const leftGearStrut = new THREE.Mesh(
new THREE.CylinderGeometry(0.06, 0.06, 2, 4),
gearMat
);
leftGearStrut.position.set(-2, -1.5, -1);
this.group.add(leftGearStrut);
const leftWheel = new THREE.Mesh(
new THREE.SphereGeometry(0.25, 4, 4),
wheelMat
);
leftWheel.position.set(-2, -2.5, -1);
this.group.add(leftWheel);
this.landingGear.push(leftGearStrut, leftWheel);
// Right main gear
const rightGearStrut = leftGearStrut.clone();
rightGearStrut.position.x = 2;
this.group.add(rightGearStrut);
const rightWheel = leftWheel.clone();
rightWheel.position.x = 2;
this.group.add(rightWheel);
this.landingGear.push(rightGearStrut, rightWheel);
this.group.rotation.order = 'YXZ';
}
updatePropeller(thrust, delta) {
if (this.propeller) {
this.propeller.rotation.z += thrust * delta * 60;
}
}
retractGear(onGround) {
if (onGround && !this.gearExtended) {
this.landingGear.forEach(part => { part.visible = true; });
} else if (!onGround && this.gearExtended) {
this.landingGear.forEach(part => { part.visible = false; });
}
this.gearExtended = onGround;
}
getGroup() {
return this.group;
}
}