76bad46d09
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>
220 lines
5.8 KiB
JavaScript
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;
|
|
}
|
|
}
|