Klima 3D: Runway senkrecht zum Vulkan + Plane bleibt sichtbar + korrekte Höhe
- Runway-Ausrichtung: coastRotation(airport.x, airport.z) statt
atan2(-dz, dx). Runway läuft jetzt parallel zur Küste an der
Airport-Position, also im rechten Winkel zur Radialen Richtung
Vulkan. Flug geht parallel am Vulkan vorbei, nicht durch ihn.
Greift in finalizeBuy und im syncMeasureMeshes-Force-Override.
- RUNWAY_Y von 0.50 auf 0.78 korrigiert: Plane-Räder stehen jetzt
exakt auf der Asphalt-Oberkante (airport.y + 0.56 Runway-Top +
0.22 Wheel-Offset = 0.78). Versank vorher sichtbar in der Bahn.
- Plane-Zyklus: neue Phasen, damit das Flugzeug nach der Landung
sichtbar bleibt statt zu verschwinden.
0-14: Parken am -X-Ende, Nase +X (sichtbar)
14-18: Takeoff +X
18-26: Climb-Out +X, Banking am Ende
26-76: unsichtbar (50 s Flugrunde)
76-84: Approach aus +X-Ferne, Nase -X
84-88: Landing-Roll +X → -X
88-92: Wenden am -X-Ende (Yaw-Interpolation π → 0)
92-110: Parken am -X-Ende, Nase +X (sichtbar, steht bereit)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+38
-35
@@ -1540,14 +1540,11 @@ function buyMeasure(id) {
|
||||
|
||||
function finalizeBuy(id, hitPos) {
|
||||
let rotation = +placementRotation.toFixed(3);
|
||||
// Flughafen-Ausrichtung: Runway (lokales +X = Start-Richtung) zeigt
|
||||
// IMMER vom Vulkan weg, damit Starts und Landungen in den offenen Ozean
|
||||
// gehen und nicht in die Bergflanke. Überschreibt die zufällige
|
||||
// placementRotation für Airports.
|
||||
// Flughafen-Ausrichtung: Runway parallel zur Insel-Küste an der Airport-
|
||||
// Position (coastRotation). So fliegen Start und Landung immer parallel
|
||||
// am Vulkan vorbei statt direkt auf ihn zu oder von ihm weg.
|
||||
if (id === 'airport') {
|
||||
const dx = hitPos.x - VOLCANO_X;
|
||||
const dz = hitPos.z - VOLCANO_Z;
|
||||
rotation = +Math.atan2(-dz, dx).toFixed(3);
|
||||
rotation = +coastRotation(hitPos.x, hitPos.z).toFixed(3);
|
||||
}
|
||||
const zone = MEASURE_ZONES_3D[id];
|
||||
const zoneId = zone ? zone.kind : 'land';
|
||||
@@ -3117,12 +3114,11 @@ function syncMeasureMeshes() {
|
||||
const p = inst.pos || { x: 0, y: 0, z: 0 };
|
||||
mesh.position.set(p.x, p.y, p.z);
|
||||
if (typeof inst.rotation === 'number') mesh.rotation.y = inst.rotation;
|
||||
// Airport: Runway-Ausrichtung IMMER vom Vulkan weg erzwingen
|
||||
// (greift auch bei alten Saves mit zufälliger Rotation).
|
||||
// Airport: Runway-Ausrichtung parallel zur Küste an der Airport-
|
||||
// Position erzwingen (greift auch bei alten Saves). Flug geht damit
|
||||
// seitlich am Vulkan vorbei statt durch ihn.
|
||||
if (id === 'airport') {
|
||||
const dx = p.x - VOLCANO_X;
|
||||
const dz = p.z - VOLCANO_Z;
|
||||
mesh.rotation.y = Math.atan2(-dz, dx);
|
||||
mesh.rotation.y = coastRotation(p.x, p.z);
|
||||
inst.rotation = mesh.rotation.y;
|
||||
}
|
||||
measuresGroup.add(mesh);
|
||||
@@ -4094,56 +4090,63 @@ function updateSinglePlane(v, tNow, airportMesh) {
|
||||
const plane = v.plane;
|
||||
const rotY = airportMesh.rotation.y;
|
||||
const RUNWAY_END = 2.2;
|
||||
const RUNWAY_Y = 0.50;
|
||||
// Runway-Oberkante = airport.y + 0.56 (Scale 2, Plateau + Runway-Mesh).
|
||||
// Plane-Räder sitzen bei -0.22 unter plane.position.y → Parkhöhe 0.78,
|
||||
// damit die Räder exakt auf dem Asphalt stehen.
|
||||
const RUNWAY_Y = 0.78;
|
||||
|
||||
const tPhase = (tNow + v.phaseOffset) % PLANE_CYCLE;
|
||||
plane.visible = true;
|
||||
const prop = plane.userData.propRef;
|
||||
|
||||
// place: Nase zeigt IMMER in Airport-lokales +X (= Start- und Lande-
|
||||
// Richtung, vom Vulkan weg). Banking via roll (rotation.z).
|
||||
function place(lx, ly, lz, pitch, roll) {
|
||||
// Nose-Richtung über extraYaw: 0 = Airport-lokales +X (Takeoff), π = -X
|
||||
// (Landung/Anflug aus der anderen Richtung). Roll via rotation.z.
|
||||
function place(lx, ly, lz, pitch, roll, yaw) {
|
||||
const w = airportLocalToWorld(airportMesh, lx, ly, lz);
|
||||
plane.position.set(w.x, w.y, w.z);
|
||||
plane.rotation.set(pitch || 0, rotY, roll || 0);
|
||||
plane.rotation.set(pitch || 0, rotY + (yaw || 0), roll || 0);
|
||||
}
|
||||
|
||||
if (tPhase < 14) {
|
||||
// Parken am Runway-Anfang (-X-Ende), Nase zeigt +X
|
||||
place(-(RUNWAY_END - 0.3), RUNWAY_Y, 0, 0, 0);
|
||||
// Parken am Runway-Anfang (-X-Ende), Nase +X, bereit für Takeoff
|
||||
place(-(RUNWAY_END - 0.3), RUNWAY_Y, 0, 0, 0, 0);
|
||||
} else if (tPhase < 18) {
|
||||
// Takeoff-Roll: -X → +X, am Ende leicht abheben
|
||||
// Takeoff-Roll: -X → +X, leicht abheben am Ende
|
||||
const t = (tPhase - 14) / 4;
|
||||
const lx = -(RUNWAY_END - 0.3) + t * (2 * RUNWAY_END - 0.6);
|
||||
const ly = RUNWAY_Y + (t > 0.75 ? (t - 0.75) * 0.6 : 0);
|
||||
place(lx, ly, 0, -t * 0.2, 0);
|
||||
place(lx, ly, 0, -t * 0.2, 0, 0);
|
||||
} else if (tPhase < 26) {
|
||||
// Climb-Out: weiter +X, steigt auf, ab t=0.5 bankt es leicht für die
|
||||
// Kurve (rotation.z Roll, sichtbar seitwärts kippend vorm Abtauchen)
|
||||
// Climb-Out: weiter +X, steigt auf, Banking am Ende in die Kurve
|
||||
const t = (tPhase - 18) / 8;
|
||||
const lx = (RUNWAY_END - 0.3) + t * 18;
|
||||
const ly = RUNWAY_Y + 0.15 + t * 4.0;
|
||||
const roll = t > 0.5 ? (t - 0.5) * 0.9 : 0;
|
||||
place(lx, ly, 0, -0.28, roll);
|
||||
place(lx, ly, 0, -0.28, roll, 0);
|
||||
} else if (tPhase < 76) {
|
||||
// Unterwegs — unsichtbar (fliegt irgendwo in der Ferne den Bogen)
|
||||
// Unterwegs — unsichtbar (fliegt irgendwo den Bogen)
|
||||
plane.visible = false;
|
||||
} else if (tPhase < 84) {
|
||||
// Approach: von -X-Ferne zurück zum Landeanflug, Banking löst sich
|
||||
// langsam auf (Kurveneinleitung rückwärts interpretiert)
|
||||
// Approach: aus +X-Ferne kommend, Nase zeigt -X (Landerichtung),
|
||||
// Banking löst sich auf.
|
||||
const t = (tPhase - 76) / 8;
|
||||
const lx = -22 + t * (22 - (RUNWAY_END + 0.3)); // -22 → -2.5
|
||||
const lx = 22 - t * (22 - (RUNWAY_END + 0.3)); // +22 → +2.5
|
||||
const ly = 4.2 - t * 3.7;
|
||||
const roll = t < 0.5 ? -(0.5 - t) * 0.9 : 0; // Linkskurven-Roll raus
|
||||
place(lx, ly, 0, 0.15 - t * 0.15, roll);
|
||||
const roll = t < 0.5 ? -(0.5 - t) * 0.9 : 0;
|
||||
place(lx, ly, 0, 0.15 - t * 0.15, roll, Math.PI);
|
||||
} else if (tPhase < 88) {
|
||||
// Landing-Roll: -X-Ende → +X-Ende (selbe Richtung wie Takeoff)
|
||||
// Landing-Roll: +X → -X, Nase -X (Bewegung und Nase passen)
|
||||
const t = (tPhase - 84) / 4;
|
||||
const lx = -(RUNWAY_END - 0.3) + t * (2 * RUNWAY_END - 0.6);
|
||||
place(lx, RUNWAY_Y, 0, 0, 0);
|
||||
const lx = (RUNWAY_END - 0.3) - t * (2 * RUNWAY_END - 0.6);
|
||||
place(lx, RUNWAY_Y, 0, 0, 0, Math.PI);
|
||||
} else if (tPhase < 92) {
|
||||
// Wenden am -X-Ende: Nase-Rotation interpoliert von π zurück auf 0
|
||||
const t = (tPhase - 88) / 4;
|
||||
const yaw = Math.PI * (1 - t);
|
||||
place(-(RUNWAY_END - 0.3), RUNWAY_Y, 0, 0, 0, yaw);
|
||||
} else {
|
||||
// Taxi zurück zur Parkposition — unsichtbar (Teleport OK, kurz)
|
||||
plane.visible = false;
|
||||
// Parken am -X-Ende, Nase +X — bleibt bis zum nächsten Zyklus
|
||||
place(-(RUNWAY_END - 0.3), RUNWAY_Y, 0, 0, 0, 0);
|
||||
}
|
||||
if (prop && plane.visible) prop.rotation.x = tPhase * 22;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user