tosijs-3d 0.6.2 → 0.7.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/b3d-aircraft.d.ts +79 -2
- package/dist/b3d-aircraft.d.ts.map +1 -1
- package/dist/b3d-aircraft.js +349 -17
- package/dist/b3d-aircraft.js.map +1 -1
- package/dist/b3d-hud.d.ts +11 -0
- package/dist/b3d-hud.d.ts.map +1 -1
- package/dist/b3d-hud.js +20 -0
- package/dist/b3d-hud.js.map +1 -1
- package/dist/b3d-library.d.ts.map +1 -1
- package/dist/b3d-library.js +20 -6
- package/dist/b3d-library.js.map +1 -1
- package/dist/b3d-patch.d.ts +137 -0
- package/dist/b3d-patch.d.ts.map +1 -0
- package/dist/b3d-patch.js +665 -0
- package/dist/b3d-patch.js.map +1 -0
- package/dist/b3d-terrain.d.ts +63 -3
- package/dist/b3d-terrain.d.ts.map +1 -1
- package/dist/b3d-terrain.js +177 -11
- package/dist/b3d-terrain.js.map +1 -1
- package/dist/b3d-utils.d.ts +13 -0
- package/dist/b3d-utils.d.ts.map +1 -1
- package/dist/b3d-utils.js +42 -0
- package/dist/b3d-utils.js.map +1 -1
- package/dist/biome-plugin.d.ts +26 -0
- package/dist/biome-plugin.d.ts.map +1 -1
- package/dist/biome-plugin.js +42 -8
- package/dist/biome-plugin.js.map +1 -1
- package/dist/carve.d.ts +89 -0
- package/dist/carve.d.ts.map +1 -0
- package/dist/carve.js +223 -0
- package/dist/carve.js.map +1 -0
- package/dist/control-input.d.ts +5 -0
- package/dist/control-input.d.ts.map +1 -1
- package/dist/control-input.js +5 -0
- package/dist/control-input.js.map +1 -1
- package/dist/fly-by-wire.d.ts +45 -0
- package/dist/fly-by-wire.d.ts.map +1 -1
- package/dist/fly-by-wire.js +116 -8
- package/dist/fly-by-wire.js.map +1 -1
- package/dist/glass-gamepad.d.ts +21 -0
- package/dist/glass-gamepad.d.ts.map +1 -1
- package/dist/glass-gamepad.js +70 -0
- package/dist/glass-gamepad.js.map +1 -1
- package/dist/hud-math.d.ts +13 -0
- package/dist/hud-math.d.ts.map +1 -1
- package/dist/hud-math.js +40 -0
- package/dist/hud-math.js.map +1 -1
- package/dist/hud.d.ts +24 -0
- package/dist/hud.d.ts.map +1 -1
- package/dist/hud.js +62 -2
- package/dist/hud.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/landform.d.ts +92 -0
- package/dist/landform.d.ts.map +1 -1
- package/dist/landform.js +110 -0
- package/dist/landform.js.map +1 -1
- package/dist/patch-field.d.ts +38 -0
- package/dist/patch-field.d.ts.map +1 -0
- package/dist/patch-field.js +92 -0
- package/dist/patch-field.js.map +1 -0
- package/dist/sdf-lattice.d.ts +69 -0
- package/dist/sdf-lattice.d.ts.map +1 -0
- package/dist/sdf-lattice.js +254 -0
- package/dist/sdf-lattice.js.map +1 -0
- package/dist/terrain-grid.d.ts +73 -0
- package/dist/terrain-grid.d.ts.map +1 -1
- package/dist/terrain-grid.js +151 -1
- package/dist/terrain-grid.js.map +1 -1
- package/dist/tosi-b3d.d.ts +17 -0
- package/dist/tosi-b3d.d.ts.map +1 -1
- package/dist/tosi-b3d.js +29 -0
- package/dist/tosi-b3d.js.map +1 -1
- package/dist/virtual-gamepad.d.ts.map +1 -1
- package/dist/virtual-gamepad.js +11 -4
- package/dist/virtual-gamepad.js.map +1 -1
- package/package.json +1 -1
package/dist/b3d-aircraft.d.ts
CHANGED
|
@@ -12,13 +12,40 @@ export declare class B3dAircraft extends B3dControllable {
|
|
|
12
12
|
player: boolean;
|
|
13
13
|
enterable: boolean;
|
|
14
14
|
ceiling: number;
|
|
15
|
-
|
|
15
|
+
hudChaseOff: boolean;
|
|
16
16
|
hudSize: number;
|
|
17
17
|
hudForward: number;
|
|
18
18
|
maxSpeed: number;
|
|
19
19
|
afterburnerSpeed: number;
|
|
20
20
|
acceleration: number;
|
|
21
21
|
vtolSpeed: number;
|
|
22
|
+
/** How fast the craft may back up in hover (units/s). */
|
|
23
|
+
reverseSpeed: number;
|
|
24
|
+
/** How fast the trigger moves the throttle lever (setting/sec). */
|
|
25
|
+
throttleRate: number;
|
|
26
|
+
/**
|
|
27
|
+
* AUTO LANDING GEAR. `'on'` finds the model's gear-retract AnimationGroups
|
|
28
|
+
* by name and runs them with height above ground: up on climb-out, down on
|
|
29
|
+
* approach. `'off'` leaves the gear to you (call `setGear`).
|
|
30
|
+
*/
|
|
31
|
+
autoGear: "on" | "off";
|
|
32
|
+
/** Height above ground (m) at which the gear retracts. It extends again at
|
|
33
|
+
* 60% of this — hysteresis, so a bumpy approach doesn't cycle it. */
|
|
34
|
+
gearAltitude: number;
|
|
35
|
+
/** Optional sound for the gear cycle (URL). Played spatially at the
|
|
36
|
+
* airframe, once per transition. */
|
|
37
|
+
gearSound: string;
|
|
38
|
+
/** Volume for `gearSound`. */
|
|
39
|
+
gearVolume: number;
|
|
40
|
+
/** Seconds for a full gear cycle. */
|
|
41
|
+
gearTime: number;
|
|
42
|
+
/** How far the LOOK stick can swing the view (degrees each way). */
|
|
43
|
+
lookRange: number;
|
|
44
|
+
/** Look slew rate (degrees/sec at full deflection). */
|
|
45
|
+
lookRate: number;
|
|
46
|
+
/** How fast the view springs back to centre when the stick is released
|
|
47
|
+
* (fraction of the remaining offset per second). */
|
|
48
|
+
lookReturn: number;
|
|
22
49
|
hoverCeiling: number;
|
|
23
50
|
groundY: number;
|
|
24
51
|
crashSpeed: number;
|
|
@@ -77,7 +104,7 @@ export declare class B3dAircraft extends B3dControllable {
|
|
|
77
104
|
private fbw;
|
|
78
105
|
private fbwSeeded;
|
|
79
106
|
ceiling: number;
|
|
80
|
-
|
|
107
|
+
hudChaseOff: boolean;
|
|
81
108
|
reticle: string;
|
|
82
109
|
reticleRange: number;
|
|
83
110
|
private _hud;
|
|
@@ -87,9 +114,16 @@ export declare class B3dAircraft extends B3dControllable {
|
|
|
87
114
|
private meshNode;
|
|
88
115
|
private _chasePivot;
|
|
89
116
|
private _chaseLookPitch;
|
|
117
|
+
/** Where the LOOK stick has swung the view (radians), and how fast it
|
|
118
|
+
* springs back when released. */
|
|
119
|
+
private _lookYaw;
|
|
120
|
+
private _lookPitch;
|
|
90
121
|
private meshesToDispose;
|
|
91
122
|
private _lastGroundDist;
|
|
92
123
|
private _groundNormal;
|
|
124
|
+
/** True while the airframe is in open air INSIDE the ground (a bore/cavern):
|
|
125
|
+
* heightfield assumptions are suspended for the frame. */
|
|
126
|
+
private _inCavity;
|
|
93
127
|
private _worldVel;
|
|
94
128
|
private _prevPos;
|
|
95
129
|
private _prevPosValid;
|
|
@@ -130,6 +164,49 @@ export declare class B3dAircraft extends B3dControllable {
|
|
|
130
164
|
private get gunWarhead();
|
|
131
165
|
/** Nearest destroyable within `range` and inside the forward cone (or null). */
|
|
132
166
|
private acquireTarget;
|
|
167
|
+
/** The model's gear-retract animations, found by name (see `_findGear`). */
|
|
168
|
+
private _gearGroups;
|
|
169
|
+
/** true = retracted (or retracting). Starts DOWN: you spawn on the ground. */
|
|
170
|
+
private _gearUp;
|
|
171
|
+
private _gearSound;
|
|
172
|
+
/** 0 = down, 1 = up — scrubbed toward `_gearTarget`. */
|
|
173
|
+
private _gearPos;
|
|
174
|
+
private _gearTarget;
|
|
175
|
+
/**
|
|
176
|
+
* Find the gear animations on a freshly-loaded model, by NAME.
|
|
177
|
+
*
|
|
178
|
+
* Convention over configuration: a group whose name mentions "gear" and
|
|
179
|
+
* "retract" (or "up") is a landing-gear animation, so the scout's
|
|
180
|
+
* "Main Gear (L) Retract" / "Nose Gear Retract" are picked up with no
|
|
181
|
+
* authoring beyond what's already in the GLB. Library instances carry their
|
|
182
|
+
* groups on `metadata.animationGroups` (renamed per instance), so several
|
|
183
|
+
* aircraft each animate their own gear.
|
|
184
|
+
*/
|
|
185
|
+
private _findGear;
|
|
186
|
+
/**
|
|
187
|
+
* Raise or lower the gear. Public so an AI pilot, a cutscene or a key bind
|
|
188
|
+
* can call it; `autoGear` drives it from altitude otherwise.
|
|
189
|
+
*/
|
|
190
|
+
setGear(up: boolean): void;
|
|
191
|
+
/**
|
|
192
|
+
* The gear is SCRUBBED, not played.
|
|
193
|
+
*
|
|
194
|
+
* Letting the AnimationGroup play itself looked obvious and doesn't work:
|
|
195
|
+
* glTF animations arrive with a cyclic loop mode, so a group told to stop at
|
|
196
|
+
* the end can snap back to frame 0 — the gear cycles and then vanishes
|
|
197
|
+
* (exactly what Tonio saw) — and reverse playback via `start(from > to)` is
|
|
198
|
+
* unreliable across Babylon versions. Advancing a normalised position and
|
|
199
|
+
* calling `goToFrame` sidesteps all of it: no loop mode, no end-of-group
|
|
200
|
+
* behaviour, no second animation to author for the reverse, and an
|
|
201
|
+
* interrupted cycle simply turns around from wherever it had got to.
|
|
202
|
+
*/
|
|
203
|
+
private _scrubGear;
|
|
204
|
+
/** True while the gear is up (or on its way). */
|
|
205
|
+
get gearUp(): boolean;
|
|
206
|
+
private _playGearSound;
|
|
207
|
+
/** Drive the gear from height above ground, with hysteresis so a bumpy
|
|
208
|
+
* approach or a hill passing underneath doesn't cycle it. */
|
|
209
|
+
private _updateGear;
|
|
133
210
|
/** Distance from the aircraft origin down to the nearest ground: the lower of
|
|
134
211
|
* any terrain collider the raycast hits and the configured ground plane. */
|
|
135
212
|
private groundDistance;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"b3d-aircraft.d.ts","sourceRoot":"","sources":["../src/b3d-aircraft.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"b3d-aircraft.d.ts","sourceRoot":"","sources":["../src/b3d-aircraft.ts"],"names":[],"mappings":"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"}
|
package/dist/b3d-aircraft.js
CHANGED
|
@@ -22,7 +22,8 @@ const MAX_ALT = 300 // the aircraft's ceiling (its `ceiling`, default 300)
|
|
|
22
22
|
|
|
23
23
|
// A FACTORY — so a respawn is a genuinely new aircraft (with its radar), not a reset. The sim
|
|
24
24
|
// really emits a death and a spawn, which is the stream a narrative driver reads (see b3d-death).
|
|
25
|
-
// The HUD
|
|
25
|
+
// The HUD shows in BOTH views: in-scene on the canopy in cockpit, flat overlay in chase
|
|
26
|
+
// (minus the artificial horizon, which only tells the truth from inside the aircraft).
|
|
26
27
|
const plane = () => b3dAircraft(
|
|
27
28
|
{ library: 'vehicles', meshName: 'scout', player: true, y: 0, vtolSpeed: 6, maxSpeed: 55 },
|
|
28
29
|
b3dRadar({ range: RADAR_RANGE, coneDeg: 90, lockTime: 1.2, maxLocks: 2 }),
|
|
@@ -175,10 +176,19 @@ lift/throttle (the dual-purpose axis above), right stick Y = camera zoom.
|
|
|
175
176
|
| `afterburnerSpeed` | `75` | Speed ceiling while the throttle is held past `maxSpeed`; releasing bleeds back to `maxSpeed`. ≤ `maxSpeed` disables afterburner. |
|
|
176
177
|
| `acceleration` | `12` | Throttle / lean authority (speed change rate) |
|
|
177
178
|
| `vtolSpeed` | `6` | Forward ground speed splitting hover (below) from plane (above). 0 = pure aeroplane, no hover regime. |
|
|
178
|
-
| `
|
|
179
|
+
| `lookRange` | `120` | How far the right stick can swing the view (degrees each way) |
|
|
180
|
+
| `lookRate` | `150` | Look slew rate (degrees/sec at full stick) |
|
|
181
|
+
| `lookReturn` | `4` | How fast the view springs back to centre when released |
|
|
182
|
+
| `autoGear` | `'on'` | Find the model's gear-retract animations by NAME and run them from height above ground. `'off'` = manual (`setGear(up)`) |
|
|
183
|
+
| `gearAltitude` | `40` | Height above ground (m) at which the gear retracts; it extends again at 60% of this (hysteresis) |
|
|
184
|
+
| `gearTime` | `2.5` | Seconds for a full gear cycle |
|
|
185
|
+
| `gearSound` | `''` | Optional URL played spatially at the airframe on each gear transition |
|
|
186
|
+
| `gearVolume` | `0.6` | Volume for `gearSound` |
|
|
187
|
+
| `afterburnerSpeed` (behaviour) | — | Reached only while the trigger is HELD past the detent at full lever; release and you settle back to `maxSpeed` (military) |
|
|
188
|
+
| `hoverCeiling` | `140` | Height above ground above which the trigger is forward thrust regardless of speed (take off vertically, then fly) and the brake can't stall you below `vtolSpeed`. Below it, slowing to a hover gives the vertical trigger back for a vertical landing. 0 = off. |
|
|
179
189
|
| `groundY` | `0` | Assumed ground-plane height (a floor in addition to any terrain colliders) |
|
|
180
190
|
| `crashSpeed` | `8` | Vertical impact speed (m/s) above which a ground contact is a crash |
|
|
181
|
-
| `
|
|
191
|
+
| `hudChaseOff` | `false` | Hide the HUD entirely in chase view. By default chase shows the HUD **without the artificial horizon** (which would contradict the real one behind the aircraft); cockpit shows everything, in-scene |
|
|
182
192
|
| `hudSize` | `0.7` | In-cockpit HUD plane size (metres) |
|
|
183
193
|
| `hudForward` | `1.6` | How far ahead of the pilot's eye the HUD floats (metres) |
|
|
184
194
|
| `weapons` | `'on'` | `'off'` disarms all weapons |
|
|
@@ -208,6 +218,52 @@ Shells inherit the airframe's velocity, so your own motion leads the shot. Any
|
|
|
208
218
|
`fireGuns()`, `dropBomb()`, and `fireMissile()` are also callable directly (e.g. for
|
|
209
219
|
an AI pilot). Set `weapons="off"` to disarm.
|
|
210
220
|
|
|
221
|
+
## The throttle is a LEVER, and the HUD marks where it's taking you
|
|
222
|
+
|
|
223
|
+
Full lever = **military** thrust, the fastest speed you can simply leave set.
|
|
224
|
+
**Afterburner is held, not parked**: it lights only while the trigger is past
|
|
225
|
+
a detent with the lever already at full, and letting go settles you back to
|
|
226
|
+
military speed rather than cruising in reheat.
|
|
227
|
+
|
|
228
|
+
Because a lever commands an *equilibrium*, the needle is always travelling
|
|
229
|
+
toward a number rather than sitting on one — which looks like a fault if the
|
|
230
|
+
gauge doesn't show the target. The HUD therefore marks it (`setMeterMarks`),
|
|
231
|
+
and the same mechanism marks **sea level** on the altimeter when you drop
|
|
232
|
+
below it and **the ground beneath you** when that's higher: an altimeter reads
|
|
233
|
+
height above datum, which is the wrong number exactly when terrain is the
|
|
234
|
+
thing about to hit you.
|
|
235
|
+
|
|
236
|
+
## The right stick is the CAMERA
|
|
237
|
+
|
|
238
|
+
It swings the view and **springs back** when you let go: in chase it orbits the
|
|
239
|
+
aircraft, in the cockpit it turns the pilot's head. Held, it slews; released,
|
|
240
|
+
it returns — so you can glance at what you're about to hit without leaving the
|
|
241
|
+
camera somewhere awkward, and without a second control to re-centre.
|
|
242
|
+
|
|
243
|
+
It used to be an aux roll axis, which was near-useless: the left stick already
|
|
244
|
+
banks, and bank-to-turn means a second roll input fights it. Looking around is
|
|
245
|
+
what the spare stick is actually for.
|
|
246
|
+
|
|
247
|
+
## Landing gear, found by name
|
|
248
|
+
|
|
249
|
+
If the model carries AnimationGroups whose names mention **gear** and
|
|
250
|
+
**retract** (the scout's `Main Gear (L) Retract`, `Nose Gear Retract`, …), the
|
|
251
|
+
aircraft finds them and runs them with height above ground — up on climb-out,
|
|
252
|
+
down on approach, with hysteresis so a bumpy approach doesn't cycle them.
|
|
253
|
+
Nothing to wire: it's convention over configuration, and a model without such
|
|
254
|
+
animations simply has no gear to work.
|
|
255
|
+
|
|
256
|
+
The animation is **scrubbed, not played** — a normalised position is advanced
|
|
257
|
+
each frame and pushed with `goToFrame`. Playing the group looked simpler and
|
|
258
|
+
failed: glTF animations arrive with a cyclic loop mode, so a group told to stop
|
|
259
|
+
at the end can snap back to frame 0 (the gear cycles, then vanishes), and
|
|
260
|
+
reversing via `start(from > to)` is unreliable. Scrubbing also means an
|
|
261
|
+
interrupted cycle turns around from wherever it got to, and the SAME animation
|
|
262
|
+
serves both directions — there's no second one to author or keep in sync.
|
|
263
|
+
|
|
264
|
+
`gearSound` plays spatially at the airframe on each transition;
|
|
265
|
+
`setGear(up)` and `gearUp` are public for an AI pilot or a key bind.
|
|
266
|
+
|
|
211
267
|
## API (read-only properties for HUD binding)
|
|
212
268
|
|
|
213
269
|
- `airspeed: number` — current forward speed (m/s)
|
|
@@ -226,7 +282,7 @@ import * as BABYLON from '@babylonjs/core';
|
|
|
226
282
|
import { canonicalize, applyCenterOfGravity } from './model-transform';
|
|
227
283
|
import { B3dControllable } from './b3d-controllable';
|
|
228
284
|
import { aircraftMapping } from './virtual-gamepad';
|
|
229
|
-
import { flyByWireStep, targetVelocity, chaseVelocity, } from './fly-by-wire';
|
|
285
|
+
import { equilibriumSpeed, flyByWireStep, targetVelocity, chaseVelocity, } from './fly-by-wire';
|
|
230
286
|
import { placeOnSurface, boundingBottomOffset, isOff } from './b3d-utils';
|
|
231
287
|
import { spawnProjectile, spawnMissile } from './b3d-launcher';
|
|
232
288
|
// Small gap kept between the model's belly and the ground.
|
|
@@ -278,7 +334,8 @@ export class B3dAircraft extends B3dControllable {
|
|
|
278
334
|
// a linked HUD's altitude gauge.
|
|
279
335
|
ceiling: 300,
|
|
280
336
|
// Show the HUD in the chase view too (default: cockpit view only).
|
|
281
|
-
|
|
337
|
+
// Chase view shows the HUD MINUS the horizon; set to hide it entirely.
|
|
338
|
+
hudChaseOff: false,
|
|
282
339
|
// In-cockpit HUD plane placement (see b3d-hud attachInScene): its size in metres
|
|
283
340
|
// and how far ahead of the pilot's eye it floats. Tune to taste per airframe.
|
|
284
341
|
hudSize: 0.7,
|
|
@@ -292,11 +349,44 @@ export class B3dAircraft extends B3dControllable {
|
|
|
292
349
|
// up/down) and above which it flies like a plane (triggers = throttle). Set
|
|
293
350
|
// to 0 for a pure aeroplane with no hover regime.
|
|
294
351
|
vtolSpeed: 6,
|
|
352
|
+
/** How fast the craft may back up in hover (units/s). */
|
|
353
|
+
reverseSpeed: 5,
|
|
354
|
+
/** How fast the trigger moves the throttle lever (setting/sec). */
|
|
355
|
+
throttleRate: 0.8,
|
|
356
|
+
/**
|
|
357
|
+
* AUTO LANDING GEAR. `'on'` finds the model's gear-retract AnimationGroups
|
|
358
|
+
* by name and runs them with height above ground: up on climb-out, down on
|
|
359
|
+
* approach. `'off'` leaves the gear to you (call `setGear`).
|
|
360
|
+
*/
|
|
361
|
+
autoGear: 'on',
|
|
362
|
+
/** Height above ground (m) at which the gear retracts. It extends again at
|
|
363
|
+
* 60% of this — hysteresis, so a bumpy approach doesn't cycle it. */
|
|
364
|
+
gearAltitude: 40,
|
|
365
|
+
/** Optional sound for the gear cycle (URL). Played spatially at the
|
|
366
|
+
* airframe, once per transition. */
|
|
367
|
+
gearSound: '',
|
|
368
|
+
/** Volume for `gearSound`. */
|
|
369
|
+
gearVolume: 0.6,
|
|
370
|
+
/** Seconds for a full gear cycle. */
|
|
371
|
+
gearTime: 2.5,
|
|
372
|
+
/** How far the LOOK stick can swing the view (degrees each way). */
|
|
373
|
+
lookRange: 120,
|
|
374
|
+
/** Look slew rate (degrees/sec at full deflection). */
|
|
375
|
+
lookRate: 150,
|
|
376
|
+
/** How fast the view springs back to centre when the stick is released
|
|
377
|
+
* (fraction of the remaining offset per second). */
|
|
378
|
+
lookReturn: 4,
|
|
295
379
|
// Height above ground above which the trigger is forward thrust regardless of
|
|
296
380
|
// speed (take off vertically, then fly) AND the brake can't stall you below
|
|
297
381
|
// vtolSpeed. Below it, slowing to a hover gives the vertical trigger back for a
|
|
298
382
|
// vertical landing. 0 = altitude gate off (regime is speed-only).
|
|
299
|
-
|
|
383
|
+
// 50m put the hover regime out of reach in normal flight over broken
|
|
384
|
+
// ground: you had to descend into a valley before the brake could stall
|
|
385
|
+
// you, which reads as "the brake doesn't work" (Tonio diagnosed it as
|
|
386
|
+
// altitude-driven). 140m keeps the intent — you can't decelerate into a
|
|
387
|
+
// hover from cruise ALTITUDE — while making hovering reachable wherever
|
|
388
|
+
// you'd actually want it.
|
|
389
|
+
hoverCeiling: 140,
|
|
300
390
|
// Assumed ground-plane height (used as a floor in addition to any terrain
|
|
301
391
|
// colliders the downward raycast hits).
|
|
302
392
|
groundY: 0,
|
|
@@ -370,7 +460,11 @@ export class B3dAircraft extends B3dControllable {
|
|
|
370
460
|
// dragging the camera with it. The airframe's small attitude jitter, amplified by the ~5m chase
|
|
371
461
|
// lever arm, was the whole reason the chase was jittery while the pivot-adjacent cockpit wasn't.
|
|
372
462
|
_chasePivot = null;
|
|
373
|
-
_chaseLookPitch = 0;
|
|
463
|
+
_chaseLookPitch = 0;
|
|
464
|
+
/** Where the LOOK stick has swung the view (radians), and how fast it
|
|
465
|
+
* springs back when released. */
|
|
466
|
+
_lookYaw = 0;
|
|
467
|
+
_lookPitch = 0; // fixed look-down angle of the chase camera
|
|
374
468
|
meshesToDispose = [];
|
|
375
469
|
// Ground sampling is ONE raycast per frame, taken after the move and cached: the
|
|
376
470
|
// pre-move regime height reuses last frame's value (one-frame stale, like the
|
|
@@ -379,6 +473,9 @@ export class B3dAircraft extends B3dControllable {
|
|
|
379
473
|
// Set, and a child-mesh array three times a frame.
|
|
380
474
|
_lastGroundDist = Infinity;
|
|
381
475
|
_groundNormal = new BABYLON.Vector3(0, 1, 0);
|
|
476
|
+
/** True while the airframe is in open air INSIDE the ground (a bore/cavern):
|
|
477
|
+
* heightfield assumptions are suspended for the frame. */
|
|
478
|
+
_inCavity = false;
|
|
382
479
|
// TRUE world velocity, tracked from frame displacement. this.velocity is
|
|
383
480
|
// only the hover/ground integrator and reads ZERO in wing-borne flight
|
|
384
481
|
// (the fbw path moves the node directly) — weapons inheriting it left
|
|
@@ -444,10 +541,13 @@ export class B3dAircraft extends B3dControllable {
|
|
|
444
541
|
// that lands banked settles flat.
|
|
445
542
|
const cmd = {
|
|
446
543
|
pitch: this.grounded ? 0 : input.pitch,
|
|
447
|
-
// Left stick X
|
|
544
|
+
// Left stick X banks, and bank turns you. `strafe` is still summed in so
|
|
545
|
+
// an ALTERNATIVE mapping can offer a dedicated roll axis — but the
|
|
546
|
+
// aircraft preset no longer puts one on the right stick, which now looks
|
|
547
|
+
// around instead.
|
|
448
548
|
roll: this.grounded
|
|
449
549
|
? 0
|
|
450
|
-
: Math.max(-1, Math.min(1, input.turn + input.strafe)),
|
|
550
|
+
: Math.max(-1, Math.min(1, input.turn + (input.strafe ?? 0))),
|
|
451
551
|
lift: input.lift, // trigger axis: + up/faster, − down/slower
|
|
452
552
|
};
|
|
453
553
|
const cfg = {
|
|
@@ -465,6 +565,16 @@ export class B3dAircraft extends B3dControllable {
|
|
|
465
565
|
// gets you over vtolSpeed and into forward flight quickly (shallow, not a dive).
|
|
466
566
|
leanAccel: attrs.acceleration * 2,
|
|
467
567
|
hoverDamp: HOVER_DAMP,
|
|
568
|
+
// Hover: the LEAN reverses you (nose up), the brake half of the trigger
|
|
569
|
+
// sheds speed to a stop, and the throttle half stays purely vertical —
|
|
570
|
+
// so slowing down never fights altitude.
|
|
571
|
+
hoverBrake: attrs.acceleration * 1.5,
|
|
572
|
+
// Plane mode: the trigger moves a THROTTLE LEVER and speed settles where
|
|
573
|
+
// thrust meets drag, so a climb costs speed and a dive gives it back
|
|
574
|
+
// without touching the setting. Past idle the trigger is an airbrake.
|
|
575
|
+
throttleRate: attrs.throttleRate,
|
|
576
|
+
brakeAccel: attrs.acceleration * 1.2,
|
|
577
|
+
reverseSpeed: attrs.reverseSpeed,
|
|
468
578
|
climbRate: attrs.maxSpeed * 0.3,
|
|
469
579
|
offLevelSink: attrs.maxSpeed * 0.12,
|
|
470
580
|
diveBoost: attrs.maxSpeed * 0.4,
|
|
@@ -476,7 +586,18 @@ export class B3dAircraft extends B3dControllable {
|
|
|
476
586
|
const fwdSpeed = Math.hypot(vel.x, vel.z);
|
|
477
587
|
// Reuse last frame's post-move ground distance (refined after the move below).
|
|
478
588
|
// One frame stale, but so is `grounded`, and regime selection is tolerant.
|
|
479
|
-
|
|
589
|
+
//
|
|
590
|
+
// INSIDE A TUNNEL, clearance stops meaning what it means outside. The
|
|
591
|
+
// regime uses height-above-ground to know you're taking off or landing —
|
|
592
|
+
// but in a bore you are DELIBERATELY three metres off the floor at speed,
|
|
593
|
+
// and letting that read as "about to land" drops you out of wing-borne
|
|
594
|
+
// flight in the one place you most need control. So underground the
|
|
595
|
+
// regime is decided by SPEED alone: report a clearance that can't trip the
|
|
596
|
+
// hover threshold, and let the floor still govern the clamp and the
|
|
597
|
+
// landing gate (which run on the real distance below).
|
|
598
|
+
const heightAboveGround = this._inCavity
|
|
599
|
+
? Number.POSITIVE_INFINITY
|
|
600
|
+
: this._lastGroundDist - this.groundClearance;
|
|
480
601
|
flyByWireStep(this.fbw, cmd, fwdSpeed, heightAboveGround, cfg, dt, this.grounded);
|
|
481
602
|
// Realise the attitude as a quaternion. Babylon's +pitch(X) drops the nose
|
|
482
603
|
// and +roll(Z) banks left, so negate both (our state: +pitch = nose up,
|
|
@@ -492,8 +613,12 @@ export class B3dAircraft extends B3dControllable {
|
|
|
492
613
|
// Read-only flight state for the HUD / XR rig.
|
|
493
614
|
this.airspeed = this.fbw.speed;
|
|
494
615
|
this.altitude = node.position.y;
|
|
495
|
-
|
|
496
|
-
|
|
616
|
+
// 0..100% spans the WHOLE envelope, afterburner included. Dividing by
|
|
617
|
+
// maxSpeed made the meter read 134% in a cruise the aircraft is perfectly
|
|
618
|
+
// happy in — so "it went to maximum" was the gauge pegging, not the
|
|
619
|
+
// aircraft misbehaving (Tonio, 2026-08-13).
|
|
620
|
+
const topSpeed = Math.max(attrs.maxSpeed, attrs.afterburnerSpeed);
|
|
621
|
+
this.throttleLevel = topSpeed > 0 ? this.fbw.speed / topSpeed : 0;
|
|
497
622
|
// "In VTOL" = slow (below vtolSpeed), regardless of altitude — above the hover
|
|
498
623
|
// ceiling you can be stalled but the thrust still goes forward.
|
|
499
624
|
this.vtolActive = attrs.vtolSpeed > 0 && fwdSpeed < attrs.vtolSpeed;
|
|
@@ -521,12 +646,42 @@ export class B3dAircraft extends B3dControllable {
|
|
|
521
646
|
// chase view only when `hud-chase` opts in.
|
|
522
647
|
const inCockpit = this.cameraView === 'cockpit';
|
|
523
648
|
this._hud.setInSceneVisible?.(inCockpit);
|
|
524
|
-
|
|
525
|
-
|
|
649
|
+
// The HUD is useful from ANY view — speed, altitude, radar, warnings are
|
|
650
|
+
// true wherever the camera is. The horizon is the exception: outside the
|
|
651
|
+
// cockpit it contradicts the real horizon behind the aircraft, so it's
|
|
652
|
+
// dropped in chase and the rest stays. (`hudChase: false` still forces
|
|
653
|
+
// the old cockpit-only behaviour for anyone who wants a clean chase.)
|
|
654
|
+
const chaseHud = !attrs.hudChaseOff;
|
|
655
|
+
const showHud = inCockpit || chaseHud;
|
|
656
|
+
this._hud.setVisible(!inCockpit && chaseHud);
|
|
657
|
+
this._hud.setHorizonVisible?.(inCockpit);
|
|
526
658
|
if (showHud) {
|
|
527
659
|
const RAD = 180 / Math.PI;
|
|
528
|
-
this._hud.setMeter('speed', attrs.maxSpeed
|
|
660
|
+
this._hud.setMeter('speed', Math.max(attrs.maxSpeed, attrs.afterburnerSpeed) > 0
|
|
661
|
+
? this.fbw.speed / Math.max(attrs.maxSpeed, attrs.afterburnerSpeed)
|
|
662
|
+
: 0);
|
|
529
663
|
this._hud.setMeter('altitude', this.altitude / attrs.ceiling);
|
|
664
|
+
// REFERENCE MARKS — what a bare fill can't say.
|
|
665
|
+
//
|
|
666
|
+
// speed: with a throttle lever the needle is always TRAVELLING toward
|
|
667
|
+
// an equilibrium, so mark where it's going; without it, speed rising
|
|
668
|
+
// after you release the trigger reads as a fault rather than as the
|
|
669
|
+
// aircraft doing what you asked (Tonio).
|
|
670
|
+
//
|
|
671
|
+
// altitude: sea level once you're below it, and the GROUND beneath you
|
|
672
|
+
// whenever that's above sea level — the altimeter reads height above
|
|
673
|
+
// datum, which is the wrong number precisely when terrain is the thing
|
|
674
|
+
// about to hit you.
|
|
675
|
+
this._hud.setMeterMarks?.('speed', [
|
|
676
|
+
equilibriumSpeed(cfg, this.fbw.throttle ?? 0, this.fbw.afterburner ?? 0) / topSpeed,
|
|
677
|
+
]);
|
|
678
|
+
const altMarks = [];
|
|
679
|
+
const groundY = node.position.y - this._lastGroundDist;
|
|
680
|
+
if (groundY > 0)
|
|
681
|
+
altMarks.push(groundY / attrs.ceiling);
|
|
682
|
+
if (this.altitude < 0)
|
|
683
|
+
altMarks.push(0);
|
|
684
|
+
this._hud.setMeterMarks?.('altitude', altMarks);
|
|
530
685
|
// Nose-up should slide the horizon down — flip here if it reads inverted.
|
|
531
686
|
this._hud.setHorizon(this.fbw.pitch * RAD, this.fbw.bank * RAD);
|
|
532
687
|
// Warnings on the graphical HUD: PULL UP flashes the bottom arc (ground
|
|
@@ -583,6 +738,10 @@ export class B3dAircraft extends B3dControllable {
|
|
|
583
738
|
// wheels — kill the downward bounce and apply rolling resistance so you can
|
|
584
739
|
// land, roll to a stop, and accelerate to take off again. (First cut — tune
|
|
585
740
|
// GROUND_FRICTION / GROUND_TOUCH; the model's own ground tweaks are separate.)
|
|
741
|
+
// Inside a cavity (a bore, a cavern) the heightfield rules are suspended —
|
|
742
|
+
// see groundDistance. Computed once per frame, before anything consults it.
|
|
743
|
+
this._inCavity =
|
|
744
|
+
this.owner?.insideCavity(node.position.x, node.position.y, node.position.z) ?? false;
|
|
586
745
|
const groundDist = this.groundDistance(node); // the ONE raycast this frame
|
|
587
746
|
this._lastGroundDist = groundDist;
|
|
588
747
|
const wasGrounded = this.grounded;
|
|
@@ -626,6 +785,8 @@ export class B3dAircraft extends B3dControllable {
|
|
|
626
785
|
// Read-only flight state (airspeed/altitude/throttle/vtol) is set in the
|
|
627
786
|
// fly-by-wire block above. Just refresh the ground-proximity pull-up warning.
|
|
628
787
|
this.altitude = node.position.y;
|
|
788
|
+
this._updateGear(groundDist);
|
|
789
|
+
this._scrubGear(dt);
|
|
629
790
|
this.updatePullUp(node, groundDist);
|
|
630
791
|
// Drive the chase pivot LAST — after position is integrated AND ground-clamped, so it uses
|
|
631
792
|
// THIS frame's final position. (Sampling it earlier, before the move, left the chase a frame
|
|
@@ -634,15 +795,60 @@ export class B3dAircraft extends B3dControllable {
|
|
|
634
795
|
// level (steady); the Manta bank goes in the camera's own quaternion (a parented FreeCamera
|
|
635
796
|
// ignores parent-roll and upVector for the view). It re-derives from the airframe each frame,
|
|
636
797
|
// so a floating-origin rebase is absorbed for free — deliberately NOT origin-registered.
|
|
798
|
+
// LOOK: the right stick swings the view and SPRINGS BACK when released, so
|
|
799
|
+
// you can glance at what you're about to hit without leaving the camera
|
|
800
|
+
// somewhere awkward. Held, it's a slew; released, it returns — which is
|
|
801
|
+
// why it's integrated rather than mapped straight to an angle.
|
|
802
|
+
{
|
|
803
|
+
const attrs2 = this;
|
|
804
|
+
const range = (attrs2.lookRange * Math.PI) / 180;
|
|
805
|
+
const rate = (attrs2.lookRate * Math.PI) / 180;
|
|
806
|
+
const lx = input.lookX ?? 0;
|
|
807
|
+
const ly = input.lookY ?? 0;
|
|
808
|
+
const active = Math.abs(lx) > 0.08 || Math.abs(ly) > 0.08;
|
|
809
|
+
if (active) {
|
|
810
|
+
const yaw = this._lookYaw + lx * rate * dt;
|
|
811
|
+
const pitch = this._lookPitch + ly * rate * dt;
|
|
812
|
+
this._lookYaw = yaw < -range ? -range : yaw > range ? range : yaw;
|
|
813
|
+
const pr = range * 0.5;
|
|
814
|
+
this._lookPitch = pitch < -pr ? -pr : pitch > pr ? pr : pitch;
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
const k = Math.exp(-attrs2.lookReturn * dt); // frame-rate independent
|
|
818
|
+
this._lookYaw *= k;
|
|
819
|
+
this._lookPitch *= k;
|
|
820
|
+
if (Math.abs(this._lookYaw) < 1e-4)
|
|
821
|
+
this._lookYaw = 0;
|
|
822
|
+
if (Math.abs(this._lookPitch) < 1e-4)
|
|
823
|
+
this._lookPitch = 0;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
// COCKPIT: the same look turns the pilot's HEAD — the camera is parented to
|
|
827
|
+
// the airframe, so a local rotation is exactly "look left without turning".
|
|
828
|
+
if (this.cockpitCamera != null) {
|
|
829
|
+
this.cockpitCamera.rotation.set(-this._lookPitch, this._lookYaw, 0);
|
|
830
|
+
}
|
|
637
831
|
if (this._chasePivot != null) {
|
|
638
832
|
node.computeWorldMatrix(true); // refresh: position moved since the attitude pass
|
|
639
833
|
this._chasePivot.position.copyFrom(node.absolutePosition);
|
|
640
834
|
if (this._chasePivot.rotationQuaternion == null) {
|
|
641
835
|
this._chasePivot.rotationQuaternion = new BABYLON.Quaternion();
|
|
642
836
|
}
|
|
643
|
-
|
|
837
|
+
/*
|
|
838
|
+
ORBIT, not tilt. Both look axes go on the PIVOT, so the camera swings
|
|
839
|
+
around the aircraft on an arc — push up and it rises and looks DOWN at
|
|
840
|
+
the target, which is what "move the camera around the target" means.
|
|
841
|
+
|
|
842
|
+
It keeps aiming at the aircraft for free: the camera sits at a fixed
|
|
843
|
+
local offset behind and above the pivot, so its local aim is constant no
|
|
844
|
+
matter how the pivot is rotated. Putting the pitch on the CAMERA instead
|
|
845
|
+
(what this did first) only tilted the view off the target — the camera
|
|
846
|
+
stayed exactly where it was.
|
|
847
|
+
*/
|
|
848
|
+
BABYLON.Quaternion.RotationYawPitchRollToRef(this.fbw.heading + this._lookYaw, this._lookPitch, 0, this._chasePivot.rotationQuaternion);
|
|
644
849
|
if (this.chaseCamera?.rotationQuaternion != null) {
|
|
645
|
-
BABYLON.Quaternion.RotationYawPitchRollToRef(0, this._chaseLookPitch,
|
|
850
|
+
BABYLON.Quaternion.RotationYawPitchRollToRef(0, this._chaseLookPitch, // aim only — the ORBIT lives on the pivot
|
|
851
|
+
-this.fbw.bank * CHASE_BANK_FOLLOW, // airframe roll sign convention
|
|
646
852
|
this.chaseCamera.rotationQuaternion);
|
|
647
853
|
}
|
|
648
854
|
}
|
|
@@ -865,10 +1071,119 @@ export class B3dAircraft extends B3dControllable {
|
|
|
865
1071
|
}
|
|
866
1072
|
return best;
|
|
867
1073
|
}
|
|
1074
|
+
/** The model's gear-retract animations, found by name (see `_findGear`). */
|
|
1075
|
+
_gearGroups = [];
|
|
1076
|
+
/** true = retracted (or retracting). Starts DOWN: you spawn on the ground. */
|
|
1077
|
+
_gearUp = false;
|
|
1078
|
+
_gearSound = null;
|
|
1079
|
+
/** 0 = down, 1 = up — scrubbed toward `_gearTarget`. */
|
|
1080
|
+
_gearPos = 0;
|
|
1081
|
+
_gearTarget = 0;
|
|
1082
|
+
/**
|
|
1083
|
+
* Find the gear animations on a freshly-loaded model, by NAME.
|
|
1084
|
+
*
|
|
1085
|
+
* Convention over configuration: a group whose name mentions "gear" and
|
|
1086
|
+
* "retract" (or "up") is a landing-gear animation, so the scout's
|
|
1087
|
+
* "Main Gear (L) Retract" / "Nose Gear Retract" are picked up with no
|
|
1088
|
+
* authoring beyond what's already in the GLB. Library instances carry their
|
|
1089
|
+
* groups on `metadata.animationGroups` (renamed per instance), so several
|
|
1090
|
+
* aircraft each animate their own gear.
|
|
1091
|
+
*/
|
|
1092
|
+
_findGear(node) {
|
|
1093
|
+
const groups = node.metadata?.animationGroups ?? [];
|
|
1094
|
+
this._gearGroups = groups.filter((g) => {
|
|
1095
|
+
const n = g.name.toLowerCase();
|
|
1096
|
+
return n.includes('gear') && (n.includes('retract') || n.includes('up'));
|
|
1097
|
+
});
|
|
1098
|
+
// Started-then-paused so `goToFrame` has an effect: we never let the group
|
|
1099
|
+
// PLAY. See `setGear` for why.
|
|
1100
|
+
for (const g of this._gearGroups) {
|
|
1101
|
+
g.start(false);
|
|
1102
|
+
g.pause();
|
|
1103
|
+
g.goToFrame(g.from); // gear DOWN is frame 0, by the same convention
|
|
1104
|
+
}
|
|
1105
|
+
this._gearPos = 0;
|
|
1106
|
+
this._gearTarget = 0;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Raise or lower the gear. Public so an AI pilot, a cutscene or a key bind
|
|
1110
|
+
* can call it; `autoGear` drives it from altitude otherwise.
|
|
1111
|
+
*/
|
|
1112
|
+
setGear(up) {
|
|
1113
|
+
if (up === this._gearUp || this._gearGroups.length === 0)
|
|
1114
|
+
return;
|
|
1115
|
+
this._gearUp = up;
|
|
1116
|
+
this._gearTarget = up ? 1 : 0;
|
|
1117
|
+
this._playGearSound();
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* The gear is SCRUBBED, not played.
|
|
1121
|
+
*
|
|
1122
|
+
* Letting the AnimationGroup play itself looked obvious and doesn't work:
|
|
1123
|
+
* glTF animations arrive with a cyclic loop mode, so a group told to stop at
|
|
1124
|
+
* the end can snap back to frame 0 — the gear cycles and then vanishes
|
|
1125
|
+
* (exactly what Tonio saw) — and reverse playback via `start(from > to)` is
|
|
1126
|
+
* unreliable across Babylon versions. Advancing a normalised position and
|
|
1127
|
+
* calling `goToFrame` sidesteps all of it: no loop mode, no end-of-group
|
|
1128
|
+
* behaviour, no second animation to author for the reverse, and an
|
|
1129
|
+
* interrupted cycle simply turns around from wherever it had got to.
|
|
1130
|
+
*/
|
|
1131
|
+
_scrubGear(dt) {
|
|
1132
|
+
if (this._gearGroups.length === 0)
|
|
1133
|
+
return;
|
|
1134
|
+
const target = this._gearTarget;
|
|
1135
|
+
if (this._gearPos === target)
|
|
1136
|
+
return;
|
|
1137
|
+
const step = dt / Math.max(0.1, this.gearTime);
|
|
1138
|
+
this._gearPos =
|
|
1139
|
+
target > this._gearPos
|
|
1140
|
+
? Math.min(target, this._gearPos + step)
|
|
1141
|
+
: Math.max(target, this._gearPos - step);
|
|
1142
|
+
for (const g of this._gearGroups) {
|
|
1143
|
+
g.goToFrame(g.from + (g.to - g.from) * this._gearPos);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
/** True while the gear is up (or on its way). */
|
|
1147
|
+
get gearUp() {
|
|
1148
|
+
return this._gearUp;
|
|
1149
|
+
}
|
|
1150
|
+
_playGearSound() {
|
|
1151
|
+
const attrs = this;
|
|
1152
|
+
if (!attrs.gearSound || this.owner == null)
|
|
1153
|
+
return;
|
|
1154
|
+
if (this._gearSound == null) {
|
|
1155
|
+
this._gearSound = new BABYLON.Sound('gear', attrs.gearSound, this.owner.scene, null, { spatialSound: true, volume: attrs.gearVolume, autoplay: false });
|
|
1156
|
+
if (this.meshNode instanceof BABYLON.AbstractMesh) {
|
|
1157
|
+
this._gearSound.attachToMesh(this.meshNode);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
this._gearSound.play();
|
|
1161
|
+
}
|
|
1162
|
+
/** Drive the gear from height above ground, with hysteresis so a bumpy
|
|
1163
|
+
* approach or a hill passing underneath doesn't cycle it. */
|
|
1164
|
+
_updateGear(groundDist) {
|
|
1165
|
+
if (this._gearGroups.length === 0)
|
|
1166
|
+
return;
|
|
1167
|
+
if (isOff(this.autoGear))
|
|
1168
|
+
return;
|
|
1169
|
+
const up = Math.max(1, this.gearAltitude);
|
|
1170
|
+
const down = up * 0.6;
|
|
1171
|
+
const agl = groundDist - this.groundClearance;
|
|
1172
|
+
if (!this._gearUp && agl > up)
|
|
1173
|
+
this.setGear(true);
|
|
1174
|
+
else if (this._gearUp && agl < down)
|
|
1175
|
+
this.setGear(false);
|
|
1176
|
+
}
|
|
868
1177
|
/** Distance from the aircraft origin down to the nearest ground: the lower of
|
|
869
1178
|
* any terrain collider the raycast hits and the configured ground plane. */
|
|
870
1179
|
groundDistance(node) {
|
|
871
1180
|
const terrain = this.raycastGround(node);
|
|
1181
|
+
// INSIDE a cavity the flat `groundY` floor is a lie: fly into a bore whose
|
|
1182
|
+
// floor is 20m down and the plane term reads −20, so the clamp shoves you
|
|
1183
|
+
// up through the tunnel roof and back into daylight. Underground, only the
|
|
1184
|
+
// ray — the actual surface under you — gets a vote.
|
|
1185
|
+
if (this._inCavity)
|
|
1186
|
+
return terrain;
|
|
872
1187
|
const plane = node.position.y - (this.groundY ?? 0);
|
|
873
1188
|
return Math.min(terrain, plane);
|
|
874
1189
|
}
|
|
@@ -922,6 +1237,13 @@ export class B3dAircraft extends B3dControllable {
|
|
|
922
1237
|
return Infinity;
|
|
923
1238
|
}
|
|
924
1239
|
updatePullUp(node, groundDist) {
|
|
1240
|
+
// No PULL UP inside a tunnel: the ground being 3m below is the POINT of
|
|
1241
|
+
// flying through a bore, and a warning that's always on is a warning
|
|
1242
|
+
// nobody reads when it matters.
|
|
1243
|
+
if (this._inCavity) {
|
|
1244
|
+
this.pullUp = false;
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
925
1247
|
// Warn if projected altitude in PULL_UP_SECONDS is below 10m
|
|
926
1248
|
const futureY = groundDist < Infinity
|
|
927
1249
|
? groundDist + this.velocity.y * PULL_UP_SECONDS
|
|
@@ -956,6 +1278,15 @@ export class B3dAircraft extends B3dControllable {
|
|
|
956
1278
|
now produce the same identity-frame control node.
|
|
957
1279
|
*/
|
|
958
1280
|
const control = canonicalize(root, scene, `aircraft-${this.instanceId}`);
|
|
1281
|
+
// Expose the container's AnimationGroups where the library path puts
|
|
1282
|
+
// them, so gear detection (and anything else reading them) works the
|
|
1283
|
+
// same whichever way the model was loaded.
|
|
1284
|
+
if (container.animationGroups.length > 0) {
|
|
1285
|
+
control.metadata = {
|
|
1286
|
+
...(control.metadata ?? {}),
|
|
1287
|
+
animationGroups: container.animationGroups,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
959
1290
|
this.setupMesh(control, owner);
|
|
960
1291
|
this.meshesToDispose = [control];
|
|
961
1292
|
});
|
|
@@ -1007,6 +1338,7 @@ export class B3dAircraft extends B3dControllable {
|
|
|
1007
1338
|
// With one declared, attitude changes rotate about the CoG while
|
|
1008
1339
|
// `position` keeps meaning the stance point (parking is unchanged).
|
|
1009
1340
|
applyCenterOfGravity(root);
|
|
1341
|
+
this._findGear(root);
|
|
1010
1342
|
if (root instanceof BABYLON.Mesh) {
|
|
1011
1343
|
this.mesh = root;
|
|
1012
1344
|
root.ellipsoid = new BABYLON.Vector3(1, 0.5, 2);
|