-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFixedWingOperations.ts
More file actions
125 lines (113 loc) · 3.45 KB
/
Copy pathFixedWingOperations.ts
File metadata and controls
125 lines (113 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) 2025-2026 Matthew Kissinger
import * as THREE from 'three';
import type { FixedWingControlPhase } from './FixedWingControlLaw';
import type { FixedWingConfig } from './FixedWingConfigs';
import type { FixedWingFlightSnapshot } from './FixedWingTypes';
export type FixedWingOperationState =
| 'parked'
| 'stopped'
| 'taxi'
| 'lineup'
| 'takeoff_roll'
| 'rotation'
| 'initial_climb'
| 'cruise'
| 'orbit_hold'
| 'approach'
| 'rollout';
export interface FixedWingRunwayStartWorld {
id: string;
position: THREE.Vector3;
heading: number;
holdShortPosition?: THREE.Vector3;
shortFinalDistance: number;
shortFinalAltitude: number;
}
export interface FixedWingSpawnMetadata {
standId: string;
taxiRoute: THREE.Vector3[];
runwayStart?: FixedWingRunwayStartWorld;
}
interface FixedWingExitStatus {
canExit: boolean;
message?: string;
}
const MIN_SAFE_GROUNDED_ALTITUDE = 0.6;
export function deriveFixedWingOperationState(
snapshot: Pick<FixedWingFlightSnapshot, 'phase' | 'airspeed' | 'altitudeAGL' | 'weightOnWheels'>,
controlPhase: FixedWingControlPhase,
config: FixedWingConfig,
options?: {
orbitHoldEnabled?: boolean;
lineupActive?: boolean;
},
): FixedWingOperationState {
if (options?.orbitHoldEnabled && !snapshot.weightOnWheels) {
return 'orbit_hold';
}
if (controlPhase === 'landing_rollout') {
if (snapshot.airspeed <= config.operation.stoppedSpeedMax) {
return 'stopped';
}
return 'rollout';
}
if (snapshot.weightOnWheels) {
if (options?.lineupActive && snapshot.airspeed <= config.operation.taxiSpeedMax) {
return 'lineup';
}
if (snapshot.phase === 'parked') {
return 'parked';
}
if (snapshot.airspeed <= config.operation.stoppedSpeedMax) {
return 'stopped';
}
}
switch (controlPhase) {
case 'taxi':
return snapshot.airspeed <= config.operation.stoppedSpeedMax ? 'stopped' : 'taxi';
case 'takeoff_roll':
return 'takeoff_roll';
case 'rotation':
return 'rotation';
case 'initial_climb':
return 'initial_climb';
case 'approach':
return 'approach';
case 'flight':
default:
return snapshot.weightOnWheels ? 'taxi' : 'cruise';
}
}
export function getFixedWingExitStatus(
snapshot: Pick<FixedWingFlightSnapshot, 'weightOnWheels' | 'airspeed' | 'altitudeAGL'>,
config: FixedWingConfig,
): FixedWingExitStatus {
if (!snapshot.weightOnWheels) {
return { canExit: false, message: 'Aircraft must be on the ground before exit.' };
}
if (snapshot.altitudeAGL > MIN_SAFE_GROUNDED_ALTITUDE) {
return { canExit: false, message: 'Aircraft is not settled on the ground yet.' };
}
if (snapshot.airspeed > config.operation.exitSpeedMax) {
return {
canExit: false,
message: `Slow below ${Math.round(config.operation.exitSpeedMax)} m/s before exit.`,
};
}
return { canExit: true };
}
export function buildOrbitAnchorFromHeading(
position: THREE.Vector3,
headingRad: number,
radius: number,
direction: -1 | 1,
): { centerX: number; centerZ: number } {
const rotation = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), headingRad);
const leftVector = new THREE.Vector3(-1, 0, 0).applyQuaternion(rotation);
const lateralScale = direction === -1 ? 1 : -1;
return {
centerX: position.x + leftVector.x * radius * lateralScale,
centerZ: position.z + leftVector.z * radius * lateralScale,
};
}