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>
224 lines
5.8 KiB
JavaScript
224 lines
5.8 KiB
JavaScript
import * as THREE from 'three';
|
|
|
|
const EGA_COLORS = {
|
|
BLACK: 0x000000,
|
|
BLUE: 0x0000AA,
|
|
GREEN: 0x00AA00,
|
|
CYAN: 0x00AAAA,
|
|
RED: 0xAA0000,
|
|
MAGENTA: 0xAA00AA,
|
|
BROWN: 0xAA5500,
|
|
LIGHT_GRAY: 0xAAAAAA,
|
|
DARK_GRAY: 0x555555,
|
|
LIGHT_BLUE: 0x5555FF,
|
|
LIGHT_GREEN: 0x55FF55,
|
|
LIGHT_CYAN: 0x55FFFF,
|
|
LIGHT_RED: 0xFF5555,
|
|
YELLOW: 0xFFFF55,
|
|
WHITE: 0xFFFFFF,
|
|
};
|
|
|
|
function flatMat(color) {
|
|
return new THREE.MeshLambertMaterial({ color, flatShading: true });
|
|
}
|
|
|
|
export class Airport {
|
|
constructor() {
|
|
this.group = new THREE.Group();
|
|
}
|
|
|
|
createRunwayTexture() {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = 512;
|
|
canvas.height = 512;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx.fillStyle = '#444444';
|
|
ctx.fillRect(0, 0, 512, 512);
|
|
|
|
// Edge lines
|
|
ctx.fillStyle = '#FFFFFF';
|
|
ctx.fillRect(18, 0, 4, 512);
|
|
ctx.fillRect(490, 0, 4, 512);
|
|
|
|
// Center dashed line
|
|
for (let y = 0; y < 512; y += 40) {
|
|
ctx.fillRect(254, y, 4, 20);
|
|
}
|
|
|
|
// Threshold markings
|
|
for (let x = 30; x < 482; x += 28) {
|
|
ctx.fillRect(x, 0, 14, 35);
|
|
ctx.fillRect(x, 477, 14, 35);
|
|
}
|
|
|
|
// Touchdown zone markers
|
|
for (let x = 60; x < 160; x += 40) {
|
|
ctx.fillRect(x, 80, 10, 60);
|
|
ctx.fillRect(x, 372, 10, 60);
|
|
}
|
|
|
|
const texture = new THREE.CanvasTexture(canvas);
|
|
texture.magFilter = THREE.NearestFilter;
|
|
texture.minFilter = THREE.NearestFilter;
|
|
return texture;
|
|
}
|
|
|
|
buildRunway() {
|
|
const runwayLength = 180;
|
|
const runwayWidth = 30;
|
|
|
|
const geometry = new THREE.PlaneGeometry(runwayWidth, runwayLength);
|
|
geometry.rotateX(-Math.PI / 2);
|
|
|
|
const texture = this.createRunwayTexture();
|
|
|
|
const material = new THREE.MeshLambertMaterial({
|
|
map: texture,
|
|
flatShading: true,
|
|
});
|
|
|
|
const runway = new THREE.Mesh(geometry, material);
|
|
runway.position.y = 0.1;
|
|
this.group.add(runway);
|
|
|
|
// Taxiway
|
|
const taxiGeo = new THREE.PlaneGeometry(8, 120);
|
|
taxiGeo.rotateX(-Math.PI / 2);
|
|
const taxiMat = flatMat(0x555555);
|
|
const taxi = new THREE.Mesh(taxiGeo, taxiMat);
|
|
taxi.position.set(0, 0.15, -70);
|
|
this.group.add(taxi);
|
|
|
|
// Apron
|
|
const apronGeo = new THREE.PlaneGeometry(60, 40);
|
|
apronGeo.rotateX(-Math.PI / 2);
|
|
const apron = new THREE.Mesh(apronGeo, taxiMat);
|
|
apron.position.set(0, 0.15, -110);
|
|
this.group.add(apron);
|
|
}
|
|
|
|
buildControlTower() {
|
|
const tower = new THREE.Group();
|
|
|
|
// Base cylinder (octagonal for low-poly look)
|
|
const baseGeo = new THREE.CylinderGeometry(5, 6, 20, 8);
|
|
const baseMat = flatMat(0xAAAAAA);
|
|
const base = new THREE.Mesh(baseGeo, baseMat);
|
|
base.position.y = 10;
|
|
tower.add(base);
|
|
|
|
// Cab (wider top)
|
|
const cabGeo = new THREE.CylinderGeometry(7, 5, 6, 8);
|
|
const cab = new THREE.Mesh(cabGeo, baseMat);
|
|
cab.position.y = 23;
|
|
tower.add(cab);
|
|
|
|
// Windows (dark band around cab)
|
|
const winGeo = new THREE.CylinderGeometry(6.5, 5.5, 3, 8);
|
|
const winMat = flatMat(0x0000AA);
|
|
const windows = new THREE.Mesh(winGeo, winMat);
|
|
windows.position.y = 23;
|
|
tower.add(windows);
|
|
|
|
// Roof
|
|
const roofGeo = new THREE.ConeGeometry(7.5, 3, 8);
|
|
const roofMat = flatMat(0x555555);
|
|
const roof = new THREE.Mesh(roofGeo, roofMat);
|
|
roof.position.y = 27.5;
|
|
tower.add(roof);
|
|
|
|
// Antenna
|
|
const antGeo = new THREE.CylinderGeometry(0.1, 0.1, 5, 4);
|
|
const antenna = new THREE.Mesh(antGeo, flatMat(0xAA0000));
|
|
antenna.position.y = 31;
|
|
tower.add(antenna);
|
|
|
|
tower.position.set(0, 0, -130);
|
|
this.group.add(tower);
|
|
}
|
|
|
|
buildHangar(index) {
|
|
const hangar = new THREE.Group();
|
|
|
|
// Body
|
|
const bodyGeo = new THREE.BoxGeometry(25, 10, 18);
|
|
const bodyMat = flatMat(0x888888);
|
|
const body = new THREE.Mesh(bodyGeo, bodyMat);
|
|
body.position.y = 5;
|
|
hangar.add(body);
|
|
|
|
// Door
|
|
const doorGeo = new THREE.BoxGeometry(14, 8, 0.5);
|
|
const doorMat = flatMat(0x555555);
|
|
const door = new THREE.Mesh(doorGeo, doorMat);
|
|
door.position.set(0, 4, 9);
|
|
hangar.add(door);
|
|
|
|
// Pitched roof
|
|
const roofShape = new THREE.Shape();
|
|
roofShape.moveTo(-13, 0);
|
|
roofShape.lineTo(0, 6);
|
|
roofShape.lineTo(13, 0);
|
|
roofShape.lineTo(-13, 0);
|
|
|
|
const extrudeSettings = { depth: 20, bevelEnabled: false };
|
|
const roofGeo = new THREE.ExtrudeGeometry(roofShape, extrudeSettings);
|
|
const roofMat = flatMat(0xAA4444);
|
|
const roof = new THREE.Mesh(roofGeo, roofMat);
|
|
roof.position.set(0, 10, -10);
|
|
hangar.add(roof);
|
|
|
|
const xOffset = (index - 1) * 40;
|
|
hangar.position.set(xOffset, 0, -160);
|
|
this.group.add(hangar);
|
|
}
|
|
|
|
buildFuelTruck() {
|
|
const truck = new THREE.Group();
|
|
|
|
// Body
|
|
const bodyGeo = new THREE.BoxGeometry(2, 2, 4);
|
|
const bodyMat = flatMat(0xAA5500);
|
|
const body = new THREE.Mesh(bodyGeo, bodyMat);
|
|
body.position.y = 1;
|
|
truck.add(body);
|
|
|
|
// Cabin
|
|
const cabGeo = new THREE.BoxGeometry(2, 1.5, 1.5);
|
|
const cabMat = flatMat(0xAAAAAA);
|
|
const cab = new THREE.Mesh(cabGeo, cabMat);
|
|
cab.position.set(0, 2.25, -1.5);
|
|
truck.add(cab);
|
|
|
|
truck.position.set(20, 0, -90);
|
|
this.group.add(truck);
|
|
}
|
|
|
|
build() {
|
|
this.buildRunway();
|
|
this.buildControlTower();
|
|
for (let i = 0; i < 3; i++) {
|
|
this.buildHangar(i);
|
|
}
|
|
this.buildFuelTruck();
|
|
|
|
// Small windsock near runway
|
|
const poleGeo = new THREE.CylinderGeometry(0.1, 0.1, 5, 4);
|
|
const pole = new THREE.Mesh(poleGeo, flatMat(0xAAAAAA));
|
|
pole.position.set(-20, 2.5, 10);
|
|
this.group.add(pole);
|
|
|
|
const sockGeo = new THREE.ConeGeometry(0.8, 3, 6);
|
|
const sockMat = flatMat(0xFF5555);
|
|
const sock = new THREE.Mesh(sockGeo, sockMat);
|
|
sock.rotation.z = Math.PI / 2;
|
|
sock.position.set(-21.5, 4.5, 10);
|
|
this.group.add(sock);
|
|
}
|
|
|
|
getGroup() {
|
|
return this.group;
|
|
}
|
|
}
|