- InputMap-Actions mit physischen KEY_*-Codes (shortcut_keys.gd, idempotent) — DE-Layout/Windows-unabhängig - Freelook: RMT halten = Umsehen+Flug (WASD/EQ, Shift x3, Alt x0.3, Rad = Grundtempo dauerhaft), Shift+F toggle; Orbit bleibt Default, Flug-Exit ohne Kamera-Sprung (FlyMath pure) - F zentriert Auswahl (SelectionMath.focus_center); Esc = pure Prioritätskette (ShortcutPolicy) - Strg+LMB + Baum SELECT_MULTI: Mehrfachauswahl synchron Szene↔Baum; Entf/Strg+D wirken auf die ganze Auswahl (EIN Undo-Schritt) - docs/tasten.md = deutsche Source of truth (Karten 2/3 ergänzen) - Tests: test_shortcuts.gd NEU 70 Checks, Suite 571 grün, Builder-Export 21/0, Parse-Check grün
70 lines
No EOL
2.9 KiB
GDScript
70 lines
No EOL
2.9 KiB
GDScript
extends RefCounted
|
||
class_name FlyMath
|
||
## Freelook-Flug-Mathe (Karte t_c46d31c2, Karte 1/3) — bewusst pure Funktionen
|
||
## (kein Knoten, keine Kamera): speed_for/wheel_speed/move_delta/forward- und
|
||
## Euler-Umrechnung. tests/test_shortcuts.gd prüft headless; orbit_camera.gd
|
||
## ist nur der dünne Anwender.
|
||
##
|
||
## Euler-Konvention identisch zur Orbit-Kamera (_apply in orbit_camera.gd):
|
||
## Blickrichtung forward(yaw, pitch) =
|
||
## (−sin(yaw)·cos(pitch), −sin(pitch), −cos(yaw)·cos(pitch))
|
||
## pitch positiv = Kamera schaut von OBEN herab (Orbit-Pitch 5…85°),
|
||
## im Flug erlaubt: −85…+85° (auch von unten hochschauen).
|
||
|
||
const BASE_SPEED_MPS := 30.0 # Grundgeschwindigkeit (Mausrad ändert sie dauerhaft)
|
||
const FAST_MULT := 3.0 # Umschalt = schneller
|
||
const SLOW_MULT := 0.3 # Alt = langsamer
|
||
const WHEEL_MULT := 1.15 # Mausrad-Tick: +15 % / −13 %
|
||
const SPEED_MIN_MPS := 2.0
|
||
const SPEED_MAX_MPS := 300.0
|
||
|
||
const PITCH_MAX_DEG := 85.0 # Flug-Blick grenzwertig (±) — kein Gimbal-Kippen
|
||
|
||
|
||
## Flug-Tempo: Umschalt dominiert, wenn (unüblicherweise) beide Modifier unten sind.
|
||
static func speed_for(base_mps: float, fast: bool, slow: bool) -> float:
|
||
if fast:
|
||
return base_mps * FAST_MULT
|
||
if slow:
|
||
return base_mps * SLOW_MULT
|
||
return base_mps
|
||
|
||
|
||
## Mausrad-Tick: Grundgeschwindigkeit DAUERHAFT ändern (nicht nur solange
|
||
## gedrückt) — up=true schneller, down=false langsamer, immer geklemmt.
|
||
static func wheel_speed(base_mps: float, up: bool) -> float:
|
||
var next := base_mps * WHEEL_MULT if up else base_mps / WHEEL_MULT
|
||
return clampf(next, SPEED_MIN_MPS, SPEED_MAX_MPS)
|
||
|
||
|
||
## Bewegungs-Delta für einen Frame. forward = BLICKRICHTUNG (3D, mit Pitch —
|
||
## W/S fliegen dorthin, wo man hinsieht), right = horizontal-rechts, und
|
||
## vertikal ist Welt-UP (E/Q heben/senken unabhängig vom Blick).
|
||
## Diagonal-Eingaben werden normalisiert (kein √2-Tempo). Rückgabe: Meter.
|
||
static func move_delta(forward: Vector3, right: Vector3, fwd_input: float,
|
||
side_input: float, vert_input: float, speed_mps: float, delta_s: float) -> Vector3:
|
||
var dir := forward * fwd_input + right * side_input + Vector3.UP * vert_input
|
||
if dir.length_squared() < 0.0000001:
|
||
return Vector3.ZERO
|
||
return dir.normalized() * speed_mps * delta_s
|
||
|
||
|
||
## Blickrichtung → (yaw_deg, pitch_deg) in der Orbit-Konvention (siehe oben).
|
||
static func euler_from_forward(forward: Vector3) -> Vector2:
|
||
var f := forward
|
||
if f.length_squared() < 0.0000001:
|
||
return Vector2(0.0, 0.0)
|
||
f = f.normalized()
|
||
var pitch := rad_to_deg(asin(clampf(-f.y, -1.0, 1.0)))
|
||
var yaw := rad_to_deg(atan2(-f.x, -f.z))
|
||
return Vector2(yaw, pitch)
|
||
|
||
|
||
## (yaw_deg, pitch_deg) → Blickrichtung (Inverse von euler_from_forward).
|
||
static func forward_from_euler(yaw_deg: float, pitch_deg: float) -> Vector3:
|
||
var yaw := deg_to_rad(yaw_deg)
|
||
var pitch := deg_to_rad(clampf(pitch_deg, -PITCH_MAX_DEG, PITCH_MAX_DEG))
|
||
return Vector3(
|
||
-sin(yaw) * cos(pitch),
|
||
-sin(pitch),
|
||
-cos(yaw) * cos(pitch)) |