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