Files
retro-flight-sim/js/airport.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

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;
}
}