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>
This commit is contained in:
+223
@@ -0,0 +1,223 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user