@threenative/core 0.3.2 → 0.3.4
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/capabilities.json +1891 -101
- package/dist/{assets-kyoF7JlJ.d.ts → assets-CqvE429w.d.ts} +52 -3
- package/dist/{audio-BFiGneTL.d.ts → audio-7i3Xl0l3.d.ts} +32 -1
- package/dist/{canvas-layer-BLVijiUJ.d.ts → canvas-layer-DDmC_VVF.d.ts} +1 -1
- package/dist/{game-XGrTzapq.d.ts → game-CljaDv4D.d.ts} +503 -11
- package/dist/{gpu-readback-D2iRvoe9.d.ts → gpu-readback-CqJEfWNQ.d.ts} +16 -6
- package/dist/hot.d.ts +5 -5
- package/dist/hot.js +20 -3
- package/dist/index.d.ts +1496 -43
- package/dist/index.js +8345 -753
- package/dist/playtest.d.ts +11 -5
- package/dist/playtest.js +96 -11
- package/dist/react.d.ts +2 -2
- package/dist/{renderer-C6hqZpoG.d.ts → renderer-CfsS2hxi.d.ts} +436 -14
- package/dist/ui-layer.d.ts +15 -7
- package/dist/ui-layer.js +2 -1
- package/dist/world.d.ts +900 -14
- package/dist/world.js +10876 -126
- package/gpl/fixtures/make_world_fixture.py +184 -0
- package/gpl/recipes/_common.py +11 -0
- package/gpl/recipes/decimate.py +11 -5
- package/gpl/recipes/export_world.py +682 -0
- package/mcp/blender-server.mjs +55 -1
- package/mcp/engine-server.mjs +84 -22
- package/mcp/servers.mjs +3 -3
- package/package.json +6 -6
- package/patches/three@0.185.1.patch +2228 -105
- package/scripts/apply-three-patch.mjs +61 -37
package/dist/index.d.ts
CHANGED
|
@@ -1,23 +1,222 @@
|
|
|
1
1
|
import * as three from 'three';
|
|
2
|
-
import {
|
|
3
|
-
export { I as IAssetLoader, a as IAssetLoaderOptions, c as createAssetLoader, r as reconcileMirroredClips } from './assets-
|
|
4
|
-
export { A as AudioBus, I as IAudioBusOptions, b as IAudioPlayOptions } from './audio-
|
|
5
|
-
export { C as CanvasLayer } from './canvas-layer-
|
|
6
|
-
import { a as IGamePluginRuntime, b as IGamePluginHooks } from './game-
|
|
7
|
-
export { A as AfterPhysicsCallback, C as ContextMenuPolicy, c as ICtx, I as IGame,
|
|
2
|
+
import { Object3D, AnimationClip, AnimationMixer, Camera, Vector3, Group, Matrix4, Mesh, BufferGeometry, InstancedMesh, Material, ColorRepresentation, Box3, Scene, DirectionalLight, HemisphereLight, Color, FogExp2, Sprite, DataTexture, CatmullRomCurve3, Texture } from 'three';
|
|
3
|
+
export { I as IAssetLoader, a as IAssetLoaderOptions, b as ITextureOptions, c as createAssetLoader, r as reconcileMirroredClips } from './assets-CqvE429w.js';
|
|
4
|
+
export { A as AudioBus, I as IAudioBusOptions, b as IAudioPlayOptions, r as resetAudioCueLedger } from './audio-7i3Xl0l3.js';
|
|
5
|
+
export { C as CanvasLayer } from './canvas-layer-DDmC_VVF.js';
|
|
6
|
+
import { a as IGamePluginRuntime, b as IGamePluginHooks } from './game-CljaDv4D.js';
|
|
7
|
+
export { A as AfterPhysicsCallback, C as ContextMenuPolicy, G as GEOMETRY_ASSET_KEY, c as GEOMETRY_CAPTURE_DEFAULT_LIMIT, d as GEOMETRY_CAPTURE_MAX_LIMIT, e as GEOMETRY_CAPTURE_SORTS, f as GEOMETRY_CAPTURE_TIMEOUT_MS, g as GEOMETRY_CAPTURE_WALK_CAP, h as GeometryCaptureSort, i as ICtx, I as IGame, j as IGameObservationContribution, k as IGameObservationSampleRequest, l as IGamePlatformSource, m as IGeometryCaptureAsset, n as IGeometryCaptureMesh, o as IGeometryCapturePass, p as IGeometryCaptureReport, q as IGeometryCaptureRequest, r as IGeometryCaptureRow, s as IInputAction, t as IInputGamepad, u as IPointerDragHandle, v as IPointerEvent3D, w as IPointerEvents3D, x as IPointerEvents3DOptions, y as IPointerEvents3DPicker, z as IPointerState, B as IRandom, D as IRawInputPointer, E as IRawInputPointerEdge, F as IRawInputState, H as IRaycastOptions, J as IScenePickerOptions, K as IThreeNativeAudioConfig, L as IThreeNativeAudioLoop, M as IThreeNativeAudioOverride, N as IThreeNativeAudioSpectrum, O as IThreeNativeBootSplash, P as IThreeNativeConfig, Q as IThreeNativeIconVariants, R as IThreeNativeLodConfig, S as IThreeNativeLodGenerationConfig, T as IThreeNativeLodOverride, U as IThreeNativeLodRuntimeConfig, V as IThreeNativeTexturesConfig, W as ITweenOptions, X as IWarmUpCacheOptions, Y as IWarmUpObservation, Z as IWarmUpOptions, _ as IWarmUpProgress, $ as IWarmUpRenderer, a0 as IWarmUpReport, a1 as InputBindings, a2 as InputMap, a3 as InputPlatformSource, a4 as PointerEvent3DListener, a5 as PointerEvent3DType, a6 as PointerEvents3D, a7 as Scene, a8 as SceneFrame, a9 as ScenePicker, aa as ScheduleHandle, ab as Scheduler, ac as ThreeNativeBackgroundMode, ad as ThreeNativeLodMinTrianglesScope, ae as ThreeNativeLodPreset, af as ThreeNativeOrientation, ag as ThreeNativeUiRenderer, ah as WarmUpCacheStatus, ai as WarmUpObservationStatus, aj as afterPhysics, ak as captureMouse, al as createRandom, am as defineGame, an as warmUpScene } from './game-CljaDv4D.js';
|
|
8
8
|
import * as three_webgpu from 'three/webgpu';
|
|
9
9
|
import { StorageTexture, ComputeNode, UniformNode, Node, TextureNode, NodeMaterial, StorageBufferNode, StructTypeNode, Data3DTexture, ShadowBaseNode, NodeBuilder, NodeFrame, SpriteNodeMaterial, StorageTextureNode } from 'three/webgpu';
|
|
10
|
-
import { I as IRendererLike } from './renderer-
|
|
11
|
-
export { D as DEFAULT_PIPELINE_CENSUS_LIMIT,
|
|
12
|
-
import { I as IComputeDriven$1, a as IGPUReadbackSample } from './gpu-readback-
|
|
13
|
-
export { C as ComputeDrivenRegistry, G as GPUReadback, b as IGPUReadbackOptions } from './gpu-readback-
|
|
10
|
+
import { I as IRendererLike, F as FramePassKind, a as IFrameBudgetWindow } from './renderer-CfsS2hxi.js';
|
|
11
|
+
export { D as DEFAULT_PIPELINE_CENSUS_LIMIT, b as DEFAULT_TARGET_FPS, c as FRAME_BUDGET_MARKER, d as FRAME_BUDGET_PHASES, e as FRAME_HITCH_MARKER, f as FrameBudget, g as FrameBudgetPhase, h as FrameCounters, i as ICounterDevice, j as IFrameBudgetOptions, k as IFrameBudgetPassSummary, l as IFrameBudgetSummary, m as IFrameCounters, n as IFramePhaseSample, o as IPipelineCensus, p as IPipelineCensusCounts, q as IPipelineCensusEvent, r as IPipelineCensusOptions, s as IPipelineProvenance, t as IPipelineShaderObservation, u as IPlatformInfo, v as IRenderChainApplied, w as IRenderChainBudgetWindow, x as IRenderChainDroppedStage, y as IRenderChainOptions, z as IRenderChainRenderer, A as IRenderChainRequest, B as IRenderChainStage, C as IRenderChainStageContext, E as IRenderChainVelocityMeasurement, G as IRenderChainVelocityReport, H as IRenderChainVelocityRequest, J as IRenderChainVelocityResult, K as IRenderPassSample, L as ITargetFps, M as IVelocityRenderPass, N as MAX_TARGET_FPS, P as PIPELINE_CENSUS_CAPABILITY, O as PIPELINE_CENSUS_VERSION, Q as PipelineCensus, R as PipelineCensusMode, S as PipelineCensusStatus, T as PlatformFormFactor, U as PlatformOS, V as PlatformRuntime, W as RENDER_CHAIN_MARKER, X as RENDER_CHAIN_STAGE_ORDER, Y as RENDER_CHAIN_TIERS, Z as RenderChain, _ as RenderChainSource, $ as RenderChainStageId, a0 as RenderChainStageName, a1 as RenderChainTier, a2 as RenderChainTierRequest, a3 as RenderChainVelocitySource, a4 as TargetFpsSource, a5 as VELOCITY_OUTPUT_NAME, a6 as VELOCITY_PREVIOUS_BONE_MATRICES, a7 as VELOCITY_PREVIOUS_INSTANCE_MATRICES, a8 as VELOCITY_PREVIOUS_WORLD_MATRIX, a9 as VelocityTracker, aa as counterDeviceOf, ab as createPipelineCensus, ac as ensureVelocityOutput, ad as getPlatform, ae as isMobile, af as isNative, ag as isTouchscreenAvailable, ah as isWeb, ai as prewarm, aj as readRenderChainObservation, ak as readRenderChainReport, al as readVelocityPreviousBoneMatrices, am as readVelocityPreviousMatrices, an as readVelocityPreviousWorldMatrix, ao as resolveTargetFps, ap as snapRefreshRate, aq as velocityTexture, ar as withVelocityContext } from './renderer-CfsS2hxi.js';
|
|
12
|
+
import { I as IComputeDriven$1, a as IGPUReadbackSample } from './gpu-readback-CqJEfWNQ.js';
|
|
13
|
+
export { C as ComputeDrivenRegistry, G as GPUReadback, b as IGPUReadbackOptions } from './gpu-readback-CqJEfWNQ.js';
|
|
14
|
+
import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
|
|
14
15
|
import { Node as Node$1 } from 'three/src/nodes/Nodes.js';
|
|
15
16
|
import 'zustand/vanilla';
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Nested cost spans across the render phase, default-off.
|
|
20
|
+
*
|
|
21
|
+
* The frame budget names six phases and one of them — `render` — is 16.1 ms of a 20.2 ms frame
|
|
22
|
+
* while every engine-owned thing inside it sums to under 1.5 ms. A phase that big with nothing
|
|
23
|
+
* inside it is not a measurement, it is a question, and four ranked optimisation options were
|
|
24
|
+
* priced against four different answers to it. This is the instrument that answers it: a span tree
|
|
25
|
+
* whose leaves are the parts of the render phase, with the leftover computed rather than assumed.
|
|
26
|
+
*
|
|
27
|
+
* Three properties make it trustworthy enough to price work against:
|
|
28
|
+
*
|
|
29
|
+
* - **The residual is arithmetic, not a category.** Every non-leaf reports its own duration minus
|
|
30
|
+
* the durations of the spans it contains, so an unmeasured part shows up as a number instead of
|
|
31
|
+
* being quietly filed under "other". A child that outlives its parent reports a negative
|
|
32
|
+
* residual rather than being clamped to zero, because a negative residual means the nesting is
|
|
33
|
+
* wrong and a clamped one means nothing at all.
|
|
34
|
+
* - **It is default-off and costs one guarded return when off.** `TN_FRAME_SPANS=1` installs it;
|
|
35
|
+
* unset, every call site is `if (recorder === undefined) return;` with no clock read.
|
|
36
|
+
* - **Nothing here decides anything.** It measures the path that exists. It does not select a
|
|
37
|
+
* renderer, a traversal strategy or a pass order, and no number it produces is evidence for
|
|
38
|
+
* owning the renderer.
|
|
39
|
+
*
|
|
40
|
+
* The ids are integers on the hot path — a span call must not allocate a string to be cheap enough
|
|
41
|
+
* to put around a per-draw call — and the names exist only in the report.
|
|
42
|
+
*/
|
|
43
|
+
/** Marker printed once per report window when spans are installed. */
|
|
44
|
+
declare const SPANS_MARKER = "TN_FRAME_SPANS";
|
|
45
|
+
/**
|
|
46
|
+
* The launch flag that installs the spans. Off by default: this is a diagnostic that adds work to
|
|
47
|
+
* the frame it measures, so a game must ask for it.
|
|
48
|
+
*/
|
|
49
|
+
declare const SPANS_FLAG = "TN_FRAME_SPANS";
|
|
50
|
+
/**
|
|
51
|
+
* Every span, in the order the report prints them. Integers, because `beginSpan` runs per draw.
|
|
52
|
+
*
|
|
53
|
+
* `pass` and its nested kinds are one id per render call three makes, not one per camera: three
|
|
54
|
+
* renders the main camera first and the shadow and reflection cameras from inside that call, so
|
|
55
|
+
* the nesting is the renderer's own and a flat list of passes would have to invent a relationship
|
|
56
|
+
* the measurement already has.
|
|
57
|
+
*/
|
|
58
|
+
declare const SPANS: {
|
|
59
|
+
/** Compute-driven render work dispatched by the engine before the world render. */
|
|
60
|
+
readonly compute: 0;
|
|
61
|
+
/** The engine's own `beforeRender` seam, where a game prepares the scene it is about to draw. */
|
|
62
|
+
readonly beforeRender: 1;
|
|
63
|
+
/** The scene-graph matrix walk. */
|
|
64
|
+
readonly sceneUpdate: 2;
|
|
65
|
+
/** The projection's reconcile, when one is installed. */
|
|
66
|
+
readonly reconcile: 3;
|
|
67
|
+
/** Virtual-geometry clustered mesh update. */
|
|
68
|
+
readonly clustered: 4;
|
|
69
|
+
/** Automatic discrete LOD selection. */
|
|
70
|
+
readonly lod: 5;
|
|
71
|
+
/** The projected-size cull, apply and restore together. */
|
|
72
|
+
readonly cull: 6;
|
|
73
|
+
/** The main camera's render call. */
|
|
74
|
+
readonly mainPass: 7;
|
|
75
|
+
/** A nested render call three names as a shadow map. */
|
|
76
|
+
readonly shadowPass: 8;
|
|
77
|
+
/** A nested render call three names as a reflector. */
|
|
78
|
+
readonly reflectionPass: 9;
|
|
79
|
+
/** Any other nested render call. */
|
|
80
|
+
readonly nestedPass: 10;
|
|
81
|
+
/** Render-list construction inside a render call. */
|
|
82
|
+
readonly projectObject: 11;
|
|
83
|
+
/** Render-list sort inside a render call. */
|
|
84
|
+
readonly sort: 12;
|
|
85
|
+
/** Per-draw submission inside a render call, accumulated across the draws of one pass. */
|
|
86
|
+
readonly draw: 13;
|
|
87
|
+
};
|
|
88
|
+
type SpanId = (typeof SPANS)[keyof typeof SPANS];
|
|
89
|
+
/** Names, indexed by id. The only place a span name is a string. */
|
|
90
|
+
declare const SPAN_NAMES: readonly string[];
|
|
91
|
+
declare const SPAN_COUNT: number;
|
|
92
|
+
/** One span's cost across a reported window. */
|
|
93
|
+
interface ISpanSummary {
|
|
94
|
+
/** Frames in the window that entered this span at least once. */
|
|
95
|
+
readonly frames: number;
|
|
96
|
+
/** Entries per frame, mean over the frames that entered it. */
|
|
97
|
+
readonly perFrame: number;
|
|
98
|
+
readonly mean: number;
|
|
99
|
+
readonly p50: number;
|
|
100
|
+
readonly p95: number;
|
|
101
|
+
readonly max: number;
|
|
102
|
+
/**
|
|
103
|
+
* This span's own time minus the time of the spans inside it, at p50.
|
|
104
|
+
*
|
|
105
|
+
* Negative when a child outlived its parent, which means the nesting is wrong. Never clamped: a
|
|
106
|
+
* clamped residual reads as a complete attribution, and this is the number that says whether the
|
|
107
|
+
* attribution is complete.
|
|
108
|
+
*/
|
|
109
|
+
readonly residualP50: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One reported window of the span tree.
|
|
113
|
+
*
|
|
114
|
+
* `residual` is the render phase's own cost with every top-level span subtracted — the number this
|
|
115
|
+
* instrument exists to shrink. `coverage` is its complement as a fraction of the phase, and a
|
|
116
|
+
* reader that sees 0.68 is being told that a third of the phase is still unmeasured.
|
|
117
|
+
*/
|
|
118
|
+
interface ISpanWindow {
|
|
119
|
+
readonly window: number;
|
|
120
|
+
readonly frames: number;
|
|
121
|
+
/** The render phase as the frame budget charged it, at p50. */
|
|
122
|
+
readonly renderMs: number;
|
|
123
|
+
readonly residualMs: number;
|
|
124
|
+
readonly coverage: number;
|
|
125
|
+
/** Frames whose span nesting exceeded `MAX_DEPTH`, whose residual is therefore incomplete. */
|
|
126
|
+
readonly overflowed: number;
|
|
127
|
+
readonly spans: Readonly<Record<string, ISpanSummary>>;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Accumulates a nested span tree, one frame at a time, and reports it windowed.
|
|
131
|
+
*
|
|
132
|
+
* The owner brackets a frame with `beginFrame` and `endFrame`; the render phase's own duration is
|
|
133
|
+
* handed in at the close rather than measured here, so the root residual is arithmetic against the
|
|
134
|
+
* number the frame budget already reports and cannot drift from it.
|
|
135
|
+
*
|
|
136
|
+
* Nesting is strict: `end(id)` must name the span `begin(id)` opened. A mismatch throws, because a
|
|
137
|
+
* tree whose parentage is wrong produces a residual that is confidently wrong, and that is worse
|
|
138
|
+
* than a loud failure in a mode a game opted into.
|
|
139
|
+
*/
|
|
140
|
+
declare class SpanRecorder {
|
|
141
|
+
#private;
|
|
142
|
+
readonly capacity: number;
|
|
143
|
+
constructor(capacity?: number);
|
|
144
|
+
/** Opens a span. Returns false when the tree is already at `MAX_DEPTH` and the span is dropped. */
|
|
145
|
+
begin(id: SpanId, now: number): boolean;
|
|
146
|
+
/** Closes the span `begin` opened. Throws when it names a different span. */
|
|
147
|
+
end(id: SpanId, now: number): void;
|
|
148
|
+
/**
|
|
149
|
+
* Adds `ms` to a span that is entered many times per frame — a per-draw call, or a call site that
|
|
150
|
+
* cannot be bracketed. Counts as one entry and attributes to the enclosing span exactly as a
|
|
151
|
+
* bracketed one would.
|
|
152
|
+
*/
|
|
153
|
+
add(id: SpanId, ms: number): void;
|
|
154
|
+
/** How many spans are currently open. Zero outside a frame's own work. */
|
|
155
|
+
get depth(): number;
|
|
156
|
+
/**
|
|
157
|
+
* Throws a half-open frame away without recording it.
|
|
158
|
+
*
|
|
159
|
+
* A frame that failed mid-render has a span stack that cannot be closed honestly, and recording
|
|
160
|
+
* it would put a fabricated zero in the window. The loop abandons it instead, so the window only
|
|
161
|
+
* ever reports frames whose spans all closed.
|
|
162
|
+
*/
|
|
163
|
+
abandonFrame(): void;
|
|
164
|
+
/** Records the frame's totals against the render phase the frame budget charged. */
|
|
165
|
+
endFrame(renderPhaseMs: number): void;
|
|
166
|
+
/** The window just closed, or `undefined` when no frame was recorded in it. */
|
|
167
|
+
window(): ISpanWindow | undefined;
|
|
168
|
+
}
|
|
169
|
+
/** Installs or removes the recorder every `beginSpan`/`endSpan` routes to. */
|
|
170
|
+
declare function setSpanRecorder(next: SpanRecorder | undefined): void;
|
|
171
|
+
declare function spanRecorder(): SpanRecorder | undefined;
|
|
172
|
+
/** The one clock the spans and the frame budget share, so their numbers are comparable. */
|
|
173
|
+
declare function spanNow(): number;
|
|
174
|
+
/**
|
|
175
|
+
* Opens a span. One guarded return when spans are off — no clock read, no allocation, no branch on
|
|
176
|
+
* anything but the recorder's own presence.
|
|
177
|
+
*/
|
|
178
|
+
declare function beginSpan(id: SpanId): void;
|
|
179
|
+
declare function endSpan(id: SpanId): void;
|
|
180
|
+
/** Adds an already-measured duration to a span entered many times per frame. */
|
|
181
|
+
declare function addSpan(id: SpanId, ms: number): void;
|
|
182
|
+
/**
|
|
183
|
+
* Whether `TN_FRAME_SPANS` asks for spans on this launch.
|
|
184
|
+
*
|
|
185
|
+
* Three ways in, because the three launches have three different seams and none of them is
|
|
186
|
+
* `import.meta.env` (Vite replaces that at build time, and a measurement flag must be settable
|
|
187
|
+
* without rebuilding the game): a native host forwards `TN_FRAME_SPANS` through `process.env`, a
|
|
188
|
+
* browser page carries `?tnFrameSpans=1` in its URL, and any harness can set
|
|
189
|
+
* `globalThis.__tnFrameSpans` before the game boots.
|
|
190
|
+
*/
|
|
191
|
+
declare function spansRequested(): boolean;
|
|
192
|
+
/** The window as the marker line prints it. */
|
|
193
|
+
declare function formatSpansWindow(window: ISpanWindow): string;
|
|
194
|
+
|
|
195
|
+
/** A stride measured at unit world scale, so uniformly scaled clones can share it. */
|
|
196
|
+
interface ISharedStride {
|
|
197
|
+
/** Metres of ground per clip-second at rate 1, per unit of uniform world scale. */
|
|
198
|
+
readonly groundSpeed: number;
|
|
199
|
+
readonly inPlace: boolean;
|
|
200
|
+
}
|
|
201
|
+
declare class RigPreparation {
|
|
202
|
+
#private;
|
|
203
|
+
/** The cached binding count for an equivalent preparation, or `undefined` on a miss. */
|
|
204
|
+
boundCount(source: Object3D, clip: AnimationClip): number | undefined;
|
|
205
|
+
rememberBound(source: Object3D, clip: AnimationClip, bound: number): void;
|
|
206
|
+
/** The cached unit-scale stride for an equivalent, fully covered clip, or `undefined`. */
|
|
207
|
+
stride(source: Object3D, clip: AnimationClip): ISharedStride | undefined;
|
|
208
|
+
rememberStride(source: Object3D, clip: AnimationClip, stride: ISharedStride): void;
|
|
209
|
+
}
|
|
210
|
+
|
|
17
211
|
interface IAnimationPlayerOptions {
|
|
18
212
|
readonly clips: readonly AnimationClip[];
|
|
19
213
|
readonly root: Object3D;
|
|
20
214
|
readonly requiredClips?: readonly string[] | Readonly<Record<string, string>>;
|
|
215
|
+
/**
|
|
216
|
+
* Shared preparation for clones of one source rig. Set by `SkeletalMesh3D`; a standalone
|
|
217
|
+
* player leaves it undefined and keeps the per-instance binding audit and stride sample.
|
|
218
|
+
*/
|
|
219
|
+
readonly preparation?: RigPreparation;
|
|
21
220
|
/**
|
|
22
221
|
* Match a travelling clip's playback rate to the ground the body actually covers.
|
|
23
222
|
*
|
|
@@ -192,6 +391,37 @@ declare class CameraShake {
|
|
|
192
391
|
update(dt: number): ICameraShakeOffset;
|
|
193
392
|
}
|
|
194
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Why a launch stopped, said where the player can read it.
|
|
396
|
+
*
|
|
397
|
+
* A launch has exactly two ways to go wrong quietly, and both were measured on a real game:
|
|
398
|
+
* progress stops moving (a decode that never returns, an asset that never settles) and the GPU
|
|
399
|
+
* device is lost (another process had taken 94% of VRAM). In both the loop keeps iterating, the
|
|
400
|
+
* loading layer keeps painting, and the only account of what happened is a line on stdout — which
|
|
401
|
+
* a player does not have, and which a bug report therefore never carries.
|
|
402
|
+
*
|
|
403
|
+
* So the failure is reported as text, once, through `onLaunchFailure` — and core stops there. Core
|
|
404
|
+
* cannot draw it: on the native host `document` is a Three.js compatibility stub whose
|
|
405
|
+
* `appendChild` is a no-op, so an overlay written here would be invisible on exactly the target
|
|
406
|
+
* that needs it. The page owns the presentation (and the copy-to-clipboard button players are
|
|
407
|
+
* asked for when they report a launch that hung); this owns the noticing and the wording.
|
|
408
|
+
*/
|
|
409
|
+
/** What failed. `stalled` is "no progress for a while"; `device-lost` is the GPU going away. */
|
|
410
|
+
type LaunchFailureKind = "stalled" | "device-lost";
|
|
411
|
+
interface ILaunchFailure {
|
|
412
|
+
readonly kind: LaunchFailureKind;
|
|
413
|
+
/** One human sentence, already naming the numbers — this is what the Copy button copies. */
|
|
414
|
+
readonly message: string;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Called for every launch failure the engine notices, with the message to show the player.
|
|
418
|
+
*
|
|
419
|
+
* @situation show the player why the game stopped loading instead of leaving the loading screen up
|
|
420
|
+
* @situation report a stalled launch or a lost GPU device in the game's own UI
|
|
421
|
+
* @example const off = onLaunchFailure((failure) => shell.loading({ failure: failure.message }));
|
|
422
|
+
*/
|
|
423
|
+
declare function onLaunchFailure(listener: (failure: ILaunchFailure) => void): () => void;
|
|
424
|
+
|
|
195
425
|
type AtmosphereRgb = readonly [number, number, number];
|
|
196
426
|
type AtmosphereVector = AtmosphereRgb | Readonly<{
|
|
197
427
|
x: number;
|
|
@@ -528,6 +758,15 @@ interface IInstancedBatchBuildOptions {
|
|
|
528
758
|
readonly parent?: Object3D;
|
|
529
759
|
/** Passed straight to the built mesh. Default `false`, as in Three.js. */
|
|
530
760
|
readonly receiveShadow?: boolean;
|
|
761
|
+
/**
|
|
762
|
+
* An existing mesh to refill instead of creating one: used when it draws this batch's geometry
|
|
763
|
+
* and material and holds at least this many instances. Reusing matters on WebGPU, where three
|
|
764
|
+
* keys an instanced mesh's compiled node program by the mesh itself — every new InstancedMesh
|
|
765
|
+
* rebuilds its shader, and a streamed world creating hundreds a cell stalls on it.
|
|
766
|
+
*/
|
|
767
|
+
readonly into?: InstancedMesh;
|
|
768
|
+
/** Instance slots to allocate when a new mesh is created, so later refills fit. Default: count. */
|
|
769
|
+
readonly capacity?: number;
|
|
531
770
|
}
|
|
532
771
|
/**
|
|
533
772
|
* Collapses many copies of one shape into a single draw, without knowing the count up front.
|
|
@@ -548,8 +787,22 @@ declare class InstancedBatch {
|
|
|
548
787
|
constructor(options: IInstancedBatchOptions);
|
|
549
788
|
/** How many instances have been placed so far. */
|
|
550
789
|
get count(): number;
|
|
790
|
+
/**
|
|
791
|
+
* Writes every placed matrix into `target` (a mesh's `instanceMatrix.array`), starting at instance
|
|
792
|
+
* `offset`, and returns how many were written. For a caller that packs several batches into one
|
|
793
|
+
* shared instance buffer instead of building a mesh per batch.
|
|
794
|
+
*/
|
|
795
|
+
writeMatrices(target: Float32Array, offset: number): number;
|
|
551
796
|
/** The built mesh, or `undefined` before {@link build} — never a guess. */
|
|
552
797
|
get mesh(): InstancedMesh | undefined;
|
|
798
|
+
/**
|
|
799
|
+
* True when this batch holds element-for-element the matrices of `other`.
|
|
800
|
+
*
|
|
801
|
+
* A refilter rebuilds a cell's batch from the same run in the same order, so "did the answer
|
|
802
|
+
* change" is exactly this question, and comparing is what lets the swap skip a rebuild that came
|
|
803
|
+
* back identical instead of writing, clearing and compacting records the buffer already holds.
|
|
804
|
+
*/
|
|
805
|
+
equals(other: InstancedBatch | undefined): boolean;
|
|
553
806
|
/**
|
|
554
807
|
* Records one instance from a matrix the game composed itself, and returns its instance index.
|
|
555
808
|
*
|
|
@@ -593,9 +846,47 @@ interface IMergePart {
|
|
|
593
846
|
interface IMergePartsOptions {
|
|
594
847
|
/** Named in the error when the merge is refused. Say what was being built. */
|
|
595
848
|
readonly label: string;
|
|
849
|
+
/**
|
|
850
|
+
* Channels to keep from each part besides `position`. Absent or empty keeps today's
|
|
851
|
+
* position-only merge, whose normals are recomputed from the merged result.
|
|
852
|
+
*
|
|
853
|
+
* `"normal"` keeps each part's authored normals, transformed by the part's placement matrix
|
|
854
|
+
* (the inverse-transpose normal matrix) and **never** recomputed. `"uv"` keeps each part's
|
|
855
|
+
* texture coordinates verbatim — the placement matrix moves position and normal, so UV values
|
|
856
|
+
* are retained unchanged. A part that does not carry a listed channel refuses the merge.
|
|
857
|
+
*/
|
|
858
|
+
readonly preserve?: readonly ("uv" | "normal")[];
|
|
859
|
+
}
|
|
860
|
+
interface IMergeByMaterialOptions {
|
|
861
|
+
/** Named in the error when a group's merge is refused. Say what was being built. */
|
|
862
|
+
readonly label: string;
|
|
863
|
+
/**
|
|
864
|
+
* Leaves one mesh out of its material's group and out of the result — a piece that moves at run
|
|
865
|
+
* time, or one a capture script addresses by name.
|
|
866
|
+
*/
|
|
867
|
+
readonly skip?: (mesh: Mesh) => boolean;
|
|
868
|
+
/**
|
|
869
|
+
* An `InstancedMesh` is baked into its material's group as one part per instance matrix while that
|
|
870
|
+
* group stays at or below this many triangles. Above it — or absent, which is the old behaviour —
|
|
871
|
+
* it comes back in the result untouched, because one instanced draw is already cheaper than the
|
|
872
|
+
* triangles it expands into.
|
|
873
|
+
*/
|
|
874
|
+
readonly expandInstancedUnderTriangles?: number;
|
|
875
|
+
/**
|
|
876
|
+
* Vertices one merged group may hold before the rest of its material's parts start a second mesh.
|
|
877
|
+
*
|
|
878
|
+
* One merged group is one upload on the frame that first draws it, and that upload is the frame's
|
|
879
|
+
* whole cost: measured in a browser, a streamed world created 826 GPU buffers totalling 228.8 MB
|
|
880
|
+
* out of `createAttribute`, 33 of them over 1 MB, and the first draw of the largest chunk meshes
|
|
881
|
+
* took up to 230 ms. Splitting a material group in traversal order — which keeps the pieces
|
|
882
|
+
* adjacent to the pieces they were placed beside — bounds each of those uploads instead of
|
|
883
|
+
* trading one draw for a single enormous buffer. See `CHUNK_MERGE_MAX_VERTICES`.
|
|
884
|
+
*/
|
|
885
|
+
readonly maxGroupVertices?: number;
|
|
596
886
|
}
|
|
597
887
|
/**
|
|
598
|
-
* Merge game-authored pieces into one buffer, keeping each piece's own colour
|
|
888
|
+
* Merge game-authored pieces into one buffer, keeping each piece's own colour and, when asked,
|
|
889
|
+
* its uv and authored normals.
|
|
599
890
|
*
|
|
600
891
|
* Two things go wrong every time an agent bakes a building, a ship or a character out of
|
|
601
892
|
* primitives, and neither is about how any of it looks. `mergeGeometries` returns `null` on
|
|
@@ -603,9 +894,72 @@ interface IMergePartsOptions {
|
|
|
603
894
|
* a hundred indexed primitives — is invisible until the whole scene is missing; and a merged
|
|
604
895
|
* buffer draws with one surface, so per-piece colour is gone unless every piece carries a flat
|
|
605
896
|
* `color` attribute written before the merge. Writing that attribute is mechanical. The colours
|
|
606
|
-
* are entirely the game's, one per part, and changing them changes nothing here.
|
|
897
|
+
* are entirely the game's, one per part, and changing them changes nothing here. By default the
|
|
898
|
+
* merged normals are recomputed from the merged buffer; `preserve` keeps the authored normals and
|
|
899
|
+
* texture coordinates instead so an imported model's shading survives the bake.
|
|
900
|
+
*
|
|
901
|
+
* A group whose parts are all indexed merges indexed — the sum of their vertex counts, not three
|
|
902
|
+
* times their triangles — and only a mixed group falls back to the de-indexed soup. One crossing
|
|
903
|
+
* 65,535 vertices gets a 32-bit index, because a 16-bit one cannot name the vertex.
|
|
607
904
|
*/
|
|
608
905
|
declare function mergeParts(parts: Iterable<IMergePart>, options: IMergePartsOptions): BufferGeometry;
|
|
906
|
+
/**
|
|
907
|
+
* Bake a hierarchy's static meshes into one mesh per material, transforms and all.
|
|
908
|
+
*
|
|
909
|
+
* A building or a ship is dozens of boxes and cylinders that never move relative to each other, and
|
|
910
|
+
* every one of them is a draw call. Grouping by material and merging each group is the ordinary
|
|
911
|
+
* fix, and the ordinary fix is thirty lines an agent rewrites in every game, each time slightly
|
|
912
|
+
* differently: walk the tree, group by material, bake `matrixWorld` into the vertices, hand the
|
|
913
|
+
* group to `mergeParts`, build a mesh on the game's own material. The parts here are the same
|
|
914
|
+
* `IMergePart` list, so a game that already merges by hand gets the same refusals — a group that
|
|
915
|
+
* cannot merge throws naming `label:material`, not silently vanishing.
|
|
916
|
+
*
|
|
917
|
+
* Nothing here decides how anything looks: the material is the game's own instance, the geometry is
|
|
918
|
+
* exactly what was authored, and the group split follows the materials the game already made.
|
|
919
|
+
*
|
|
920
|
+
* `normal` survives when every mesh in a group carries it and is recomputed otherwise. `uv` survives
|
|
921
|
+
* when any mesh carries it, so a group where only some do is the refusal `mergeParts` raises, never
|
|
922
|
+
* a texture silently left unmapped. Skinned meshes are left alone — their vertices are posed per
|
|
923
|
+
* frame — and an instanced one is left alone unless `expandInstancedUnderTriangles` names a cap its
|
|
924
|
+
* group fits under and its own shape is small enough to be worth repeating, which bakes every
|
|
925
|
+
* instance matrix into the merge.
|
|
926
|
+
*
|
|
927
|
+
* `maxGroupVertices` bounds a single group, and a material whose parts cross it is merged into
|
|
928
|
+
* several meshes in traversal order — more draws, none of them carrying a buffer big enough to stall
|
|
929
|
+
* the frame that first submits it.
|
|
930
|
+
*/
|
|
931
|
+
declare function mergeByMaterial(root: Object3D, options: IMergeByMaterialOptions): Mesh[];
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* A debug switch a game reads the same way on every platform it ships to.
|
|
935
|
+
*
|
|
936
|
+
* Two questions an agent answers differently in every game, both of which cost a rewrite: how do I
|
|
937
|
+
* turn this on for one run without changing the code, and how does a capture script reach the object
|
|
938
|
+
* it wants to look at. The answers here are one function each, and neither decides how anything
|
|
939
|
+
* looks.
|
|
940
|
+
*/
|
|
941
|
+
/**
|
|
942
|
+
* A debug switch read from the URL, or from the environment on a native launch.
|
|
943
|
+
*
|
|
944
|
+
* @situation read a debug toggle from the URL or an environment variable
|
|
945
|
+
*
|
|
946
|
+
* A browser asks with the query string — `?freeCam`, or `?freeCam=0` to force it off — and a native
|
|
947
|
+
* launch asks with `TN_DEBUG_FREE_CAM`, because a query string is not something a developer sets on
|
|
948
|
+
* a command line. `0` and `false` are off, so a saved URL that used to enable a switch still says
|
|
949
|
+
* "off" rather than quietly turning it back on.
|
|
950
|
+
*/
|
|
951
|
+
declare function debugFlag(name: string): boolean;
|
|
952
|
+
/**
|
|
953
|
+
* Publish one game object for a capture script or the console, in development builds only.
|
|
954
|
+
*
|
|
955
|
+
* @situation expose a game object to a capture script or the console in dev builds
|
|
956
|
+
*
|
|
957
|
+
* Playtest reads `__THREENATIVE__` in a dev build and is silent in a production one, so a game that
|
|
958
|
+
* publishes its player, its camera or a scene handle under `.debug` is reachable from the same
|
|
959
|
+
* place a production build is not. Other keys on the shared object are left alone; the dev surfaces
|
|
960
|
+
* the engine installs beside them keep working.
|
|
961
|
+
*/
|
|
962
|
+
declare function exposeDebug(name: string, value: unknown): void;
|
|
609
963
|
|
|
610
964
|
/**
|
|
611
965
|
* A mesh that draws only the clusters this camera can resolve.
|
|
@@ -686,8 +1040,9 @@ declare class ClusteredMesh extends Mesh {
|
|
|
686
1040
|
* Takes every clustered mesh and every clustered batch under `root` through this frame's cut.
|
|
687
1041
|
*
|
|
688
1042
|
* The engine calls this itself, once a frame, before the render — virtual geometry ships on and a
|
|
689
|
-
* game that has to remember to call something has not been given it.
|
|
690
|
-
*
|
|
1043
|
+
* game that has to remember to call something has not been given it. The first call censuses the
|
|
1044
|
+
* root once and subscribes to its graph events; later calls walk only the clustered meshes and
|
|
1045
|
+
* batch roots themselves, so a scene holding neither costs a map lookup.
|
|
691
1046
|
*
|
|
692
1047
|
* @returns triangles the clustered meshes and batches will submit.
|
|
693
1048
|
*/
|
|
@@ -772,6 +1127,428 @@ declare class ClusteredBatch {
|
|
|
772
1127
|
update(camera: Camera, viewportHeight: number): number;
|
|
773
1128
|
}
|
|
774
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* Pixels a one-world-unit error at `depth` covers, for this camera and viewport.
|
|
1132
|
+
*
|
|
1133
|
+
* Perspective divides the projected scale by the depth; orthographic has no depth term and uses the
|
|
1134
|
+
* frustum height instead. A non-positive depth or an unprojectable camera throws rather than
|
|
1135
|
+
* returning a plausible-looking wrong scale.
|
|
1136
|
+
*/
|
|
1137
|
+
declare function lodPixelScale(camera: Camera, viewportHeight: number, depth: number): number;
|
|
1138
|
+
/** The LOD0 geometry of a mesh under a discrete chain, or the mesh's own geometry otherwise. */
|
|
1139
|
+
declare function baseGeometryOf(mesh: Mesh): BufferGeometry;
|
|
1140
|
+
/**
|
|
1141
|
+
* Takes every discrete-LOD mesh under `root` through this frame's selection.
|
|
1142
|
+
*
|
|
1143
|
+
* The engine calls this itself, once a frame, after matrices are synced and before the render.
|
|
1144
|
+
* Multi-view callers that need the finest level across passes use {@link selectLodLevel} directly;
|
|
1145
|
+
* this entry point selects for the one camera it is handed — which is the main view, and is at
|
|
1146
|
+
* least as fine as a shadow pass of lower resolution needs.
|
|
1147
|
+
*
|
|
1148
|
+
* @returns triangles the managed meshes will submit this frame.
|
|
1149
|
+
*/
|
|
1150
|
+
declare function updateModelLods(root: {
|
|
1151
|
+
traverse(callback: (object: object) => void): void;
|
|
1152
|
+
}, camera: Camera, viewportHeight: number): number;
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* What the gate did on the last frame, in the shape a frame-budget window reports.
|
|
1156
|
+
*
|
|
1157
|
+
* `enabled: false` means the game declined the gate, and the counts are still measured — turning
|
|
1158
|
+
* the convention off must not turn its measurement off. Every exemption is named separately so an
|
|
1159
|
+
* override is visible rather than silent.
|
|
1160
|
+
*/
|
|
1161
|
+
interface IRenderCameraCullReport {
|
|
1162
|
+
readonly schemaVersion: 1;
|
|
1163
|
+
readonly enabled: boolean;
|
|
1164
|
+
/**
|
|
1165
|
+
* False when the camera has no perspective distance term (orthographic) or no viewport to
|
|
1166
|
+
* project into; the gate then leaves the whole scene drawn rather than guessing.
|
|
1167
|
+
*/
|
|
1168
|
+
readonly cameraResolved: boolean;
|
|
1169
|
+
readonly thresholdPixels: number;
|
|
1170
|
+
/** Renderables walked this frame. */
|
|
1171
|
+
readonly considered: number;
|
|
1172
|
+
/** Renderables hidden by this gate this frame. */
|
|
1173
|
+
readonly culled: number;
|
|
1174
|
+
readonly exemptCameraAttached: number;
|
|
1175
|
+
readonly exemptMarked: number;
|
|
1176
|
+
readonly exemptShadowCasters: number;
|
|
1177
|
+
readonly exemptWithoutBounds: number;
|
|
1178
|
+
/**
|
|
1179
|
+
* Objects whose position buffer is rewritten every frame, so the gate cannot trust a bound it
|
|
1180
|
+
* cannot afford to rescan. Kept drawn, like `frustumCulled = false`.
|
|
1181
|
+
*/
|
|
1182
|
+
readonly exemptDynamicBounds: number;
|
|
1183
|
+
/** Objects that already set `frustumCulled = false`, and so never had trustworthy bounds. */
|
|
1184
|
+
readonly exemptFrustumCulled: number;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Keep an object drawn regardless of how small the render camera resolves it.
|
|
1188
|
+
*
|
|
1189
|
+
* The named per-object override for the projected-size gate, which is on by default. Mark the
|
|
1190
|
+
* player's own cockpit, a nameplate, a quest marker, or anything a game never wants to pop out.
|
|
1191
|
+
* `alwaysRender(object, false)` removes the marker. The count of marked objects is reported beside
|
|
1192
|
+
* the cull, so an override is stated rather than hidden.
|
|
1193
|
+
*
|
|
1194
|
+
* @situation keep a small object drawn when the engine would skip it as too far to resolve
|
|
1195
|
+
* @situation stop my cockpit, marker or player model popping out at distance
|
|
1196
|
+
* @situation a tiny object disappeared at range and I need it always visible
|
|
1197
|
+
* @constraint the marker is per object and survives scene rebuilds only as long as the object does
|
|
1198
|
+
* @constraint disabling the gate (`renderer.minimumProjectedPixels: false`) keeps its measurement on
|
|
1199
|
+
* @example alwaysRender(ctx.camera.children[0]); // a camera-attached cockpit stays drawn
|
|
1200
|
+
*/
|
|
1201
|
+
declare function alwaysRender(object: Object3D, enabled?: boolean): void;
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* The per-frame world-matrix walk, with a hidden subtree left where it stands.
|
|
1205
|
+
*
|
|
1206
|
+
* `three`'s `Object3D.updateMatrixWorld` recurses into every child of every node whatever its
|
|
1207
|
+
* `visible` flag, multiplying a world matrix for each. That is the correct default for a library
|
|
1208
|
+
* that cannot know what a game is doing, and it is the wrong one for a renderer that is about to
|
|
1209
|
+
* skip exactly those subtrees: a full-detail body while its merged stand-in draws, a hidden LOD
|
|
1210
|
+
* level, a parked or hangared model each pay the walk and none of them can draw.
|
|
1211
|
+
*
|
|
1212
|
+
* Native flight in `sandbox/midway-open-pacific` profiled `updateMatrixWorld` plus
|
|
1213
|
+
* `multiplyMatrices` at **29 % of all JavaScript ticks**, and the same scene's hand-rolled
|
|
1214
|
+
* visible-only pass walked **779 nodes per frame instead of 9,903**, 3.1 ms down to 0.3 ms. That
|
|
1215
|
+
* pass was the game's; this is the engine's, so the next game does not write it again.
|
|
1216
|
+
*
|
|
1217
|
+
* The pass mirrors three exactly for a visible node: `matrixAutoUpdate` -> `updateMatrix()`, then
|
|
1218
|
+
* the `matrixWorldNeedsUpdate || force` recompute honouring `matrixWorldAutoUpdate` and a null
|
|
1219
|
+
* parent, then force the children. A node with `visible === false` still composes its **own**
|
|
1220
|
+
* matrix — that is what makes the next part cheap — and is not recursed into; when it was forced
|
|
1221
|
+
* (or carried a dirty flag) it is remembered, so the first frame it is visible again its whole
|
|
1222
|
+
* subtree is recomputed with `force = true`.
|
|
1223
|
+
*
|
|
1224
|
+
* Two things are never dropped by the pruning, because both are read while nothing above them is
|
|
1225
|
+
* visible:
|
|
1226
|
+
*
|
|
1227
|
+
* - a class that overrides `updateMatrixWorld` runs its own. `SkinnedMesh` refreshes
|
|
1228
|
+
* `bindMatrixInverse`, `Camera` its `matrixWorldInverse`; re-implementing only the base walk
|
|
1229
|
+
* left every deck-crew sailor that had moved since load drawing with a stale bind matrix and
|
|
1230
|
+
* vanishing from the frame. Their subtrees are small, so walking them whole costs nothing;
|
|
1231
|
+
* - a hidden node that holds a `Bone` is walked, because a visible `SkinnedMesh` draws with its
|
|
1232
|
+
* skeleton's matrices wherever the armature happens to sit. Whether bones sit below a node is
|
|
1233
|
+
* decided once and remembered — a rig is built whole and does not grow.
|
|
1234
|
+
*
|
|
1235
|
+
* A game that reads a **hidden** object's `matrixWorld` directly must not rely on this pass
|
|
1236
|
+
* having reached it: use `getWorldPosition`/`getWorldQuaternion`/`getWorldScale` (which update the
|
|
1237
|
+
* chain they need) or call `object.updateWorldMatrix(true, false)` first. The convention's named
|
|
1238
|
+
* override is `renderer.matrixWorld: "all"`, which visits every node exactly as three's own walk
|
|
1239
|
+
* does; `"visible"` is the default.
|
|
1240
|
+
*/
|
|
1241
|
+
/** How much of the scene graph the engine walks for world matrices each frame. */
|
|
1242
|
+
type MatrixWorldMode = "visible" | "all";
|
|
1243
|
+
/** What the pass did on the last frame, for the frame telemetry a window reports. */
|
|
1244
|
+
interface IMatrixWorldReport {
|
|
1245
|
+
readonly schemaVersion: 1;
|
|
1246
|
+
readonly mode: MatrixWorldMode;
|
|
1247
|
+
/**
|
|
1248
|
+
* Nodes the engine walked this frame, summed over every application (authored scene and a
|
|
1249
|
+
* projection mirror when one is in use). One number both ways, so `"all"` can be read as the
|
|
1250
|
+
* cost of the walk the convention just removed.
|
|
1251
|
+
*/
|
|
1252
|
+
readonly visited: number;
|
|
1253
|
+
}
|
|
1254
|
+
interface IMatrixWorldOptions {
|
|
1255
|
+
/** `"visible"` (default) skips a hidden subtree; `"all"` reproduces three's own walk. */
|
|
1256
|
+
readonly mode?: MatrixWorldMode;
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* One game's world-matrix walk, holding the state the pruning needs between frames.
|
|
1260
|
+
*
|
|
1261
|
+
* The stale set and the bone cache live on the instance rather than in module scope: two games,
|
|
1262
|
+
* two playtest scenarios or two roots in one process must not share "this subtree was skipped
|
|
1263
|
+
* while hidden", or one game's hidden node would be force-refreshed by the other's frame.
|
|
1264
|
+
*/
|
|
1265
|
+
declare class MatrixWorldPass {
|
|
1266
|
+
#private;
|
|
1267
|
+
constructor(options?: IMatrixWorldOptions);
|
|
1268
|
+
get mode(): MatrixWorldMode;
|
|
1269
|
+
/** What the current frame has walked so far, across every {@link apply} call. */
|
|
1270
|
+
get report(): IMatrixWorldReport;
|
|
1271
|
+
/** Starts a frame's count. The pass's state (stale set, bone cache) is untouched. */
|
|
1272
|
+
beginFrame(): void;
|
|
1273
|
+
/**
|
|
1274
|
+
* Walks `root`, mirroring three for every node the mode visits.
|
|
1275
|
+
*
|
|
1276
|
+
* Returns the nodes this call visited, so a caller can report one application's cost without
|
|
1277
|
+
* also reporting the frame's total.
|
|
1278
|
+
*/
|
|
1279
|
+
apply(root: Object3D, force?: boolean): number;
|
|
1280
|
+
/** Forgets what was skipped. A whole-scene swap has no hidden ancestors left to refresh. */
|
|
1281
|
+
dispose(): void;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Where the spans attach to three's own render path.
|
|
1286
|
+
*
|
|
1287
|
+
* Kept apart from the recorder because they are two different kinds of risk. The recorder is
|
|
1288
|
+
* arithmetic over a stack and is proven by unit tests; this file reaches into another library's
|
|
1289
|
+
* private methods, and every line of it is a bet about how three 0.185 is shaped. Reading them
|
|
1290
|
+
* separately is how the bet stays visible.
|
|
1291
|
+
*
|
|
1292
|
+
* Every wrapper is an own property on the instance, so it is the object the frame actually uses
|
|
1293
|
+
* and uninstalling restores the prototype's method. Three's private methods are read defensively:
|
|
1294
|
+
* a renderer whose internals have moved loses that span rather than throwing, and the span simply
|
|
1295
|
+
* does not appear in the report — an absent measurement, never a fabricated zero.
|
|
1296
|
+
*/
|
|
1297
|
+
|
|
1298
|
+
/** The slice of the raw three renderer the probes wrap. Structural, so a test can stand in a fake. */
|
|
1299
|
+
interface ISpanProbeTarget {
|
|
1300
|
+
render(scene: unknown, camera: unknown): unknown;
|
|
1301
|
+
_projectObject?(...args: unknown[]): void;
|
|
1302
|
+
_renderObjectDirect?(...args: unknown[]): void;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Wraps the renderer's own render path so each part of a frame lands in its own span.
|
|
1306
|
+
*
|
|
1307
|
+
* Every wrapper is an own property on the instance, so it is the object the frame actually uses and
|
|
1308
|
+
* uninstalling restores the prototype's method. Three's private methods are read defensively: a
|
|
1309
|
+
* renderer whose internals have moved loses that span rather than throwing, and the span simply
|
|
1310
|
+
* does not appear in the report — an absent measurement, never a fabricated zero.
|
|
1311
|
+
*
|
|
1312
|
+
* The recursion guards matter: `_projectObject` calls itself once per child, so only the outermost
|
|
1313
|
+
* call opens a span, and `render` is deliberately not guarded because a nested render *is* the
|
|
1314
|
+
* shadow and reflection passes.
|
|
1315
|
+
*/
|
|
1316
|
+
declare function installSpanProbes(target: ISpanProbeTarget, root: Object3D): () => void;
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* The engine telling the agent that built the scene what a human would otherwise find by playing.
|
|
1320
|
+
*
|
|
1321
|
+
* A scene reached 1,815 triangles per draw with its GPU ten times under budget and nobody knew,
|
|
1322
|
+
* because the census and the phase split are measured every frame and nothing reads them. The
|
|
1323
|
+
* frame already carries the shape: how many objects the cull considered, how many draws each pass
|
|
1324
|
+
* submitted, how many casters are exempt, and what share of the frame the GPU actually used. This
|
|
1325
|
+
* turns those numbers into a verdict, once per reported window.
|
|
1326
|
+
*
|
|
1327
|
+
* **The rule is derived, never a constant an author is told to revisit.** It fires when
|
|
1328
|
+
*
|
|
1329
|
+
* - the GPU used less than a third of the frame — the device is not the constraint, and
|
|
1330
|
+
* - the JS render phase alone is longer than the display's own period — so the scene cannot make
|
|
1331
|
+
* the display's rate even if everything else in the frame were free.
|
|
1332
|
+
*
|
|
1333
|
+
* Both numbers come from the frame meter. The display's period comes from the host's own
|
|
1334
|
+
* presentation cap where there is one, and otherwise from the frame rate the game itself declared;
|
|
1335
|
+
* no third source is invented, and a launch that can name neither gets no verdict rather than a
|
|
1336
|
+
* guessed one.
|
|
1337
|
+
*
|
|
1338
|
+
* It is silent on an honestly GPU-bound scene with the same draw count, which is the property that
|
|
1339
|
+
* makes it worth reading: a warning that fires on every heavy scene is a warning nobody reads.
|
|
1340
|
+
*/
|
|
1341
|
+
|
|
1342
|
+
/** Marker printed at most once per reported window. */
|
|
1343
|
+
declare const SCENE_WARNING_MARKER = "TN_SCENE_WARNING";
|
|
1344
|
+
/**
|
|
1345
|
+
* What the frame already knows about the scene's shape.
|
|
1346
|
+
*
|
|
1347
|
+
* Every field is a count the engine measures anyway — the projection's cull census and the render
|
|
1348
|
+
* pass budget — so the warning adds a reading, never a measurement.
|
|
1349
|
+
*/
|
|
1350
|
+
interface ISceneShape {
|
|
1351
|
+
/** Objects the projected-size cull looked at this window. */
|
|
1352
|
+
readonly objectsConsidered: number;
|
|
1353
|
+
/** Objects it hid. */
|
|
1354
|
+
readonly culled: number;
|
|
1355
|
+
/** Objects exempt from the gate because they cast shadows. */
|
|
1356
|
+
readonly shadowExemptCasters: number;
|
|
1357
|
+
/** Draws submitted per pass kind, from the render pass budget. */
|
|
1358
|
+
readonly draws: Readonly<Partial<Record<FramePassKind, number>>>;
|
|
1359
|
+
/** Triangles per draw across every pass; the number a merge or an atlas moves. */
|
|
1360
|
+
readonly trianglesPerDraw: number;
|
|
1361
|
+
}
|
|
1362
|
+
/** The verdict, and the shape behind it. */
|
|
1363
|
+
interface ISceneWarning {
|
|
1364
|
+
readonly window: number;
|
|
1365
|
+
/** The GPU's share of the frame, as a fraction. */
|
|
1366
|
+
readonly gpuShare: number;
|
|
1367
|
+
/** The JS render phase's mean, in milliseconds. */
|
|
1368
|
+
readonly renderMs: number;
|
|
1369
|
+
/** The period the display works at, in milliseconds, and where that number came from. */
|
|
1370
|
+
readonly displayPeriodMs: number;
|
|
1371
|
+
readonly displaySource: "host-cap" | "declared-target";
|
|
1372
|
+
/** The largest thing the CPU describes every frame, and its share of the terms compared. */
|
|
1373
|
+
readonly dominantTerm: string;
|
|
1374
|
+
readonly dominantShare: number;
|
|
1375
|
+
readonly shape: ISceneShape;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* The display's own period, in milliseconds, or `undefined` when nothing can say.
|
|
1379
|
+
*
|
|
1380
|
+
* The host's presentation cap first, because on a native launch that is literally the rate frames
|
|
1381
|
+
* reach the display at. A browser has no refresh-rate API, so the rate the game declared is the
|
|
1382
|
+
* only honest second source — and it is the same number the resolution scaler already judges
|
|
1383
|
+
* against, not a new one.
|
|
1384
|
+
*/
|
|
1385
|
+
declare function displayPeriodMs(declaredTargetFps: number | undefined): {
|
|
1386
|
+
ms: number;
|
|
1387
|
+
source: ISceneWarning["displaySource"];
|
|
1388
|
+
} | undefined;
|
|
1389
|
+
/**
|
|
1390
|
+
* The verdict for one reported window, or `undefined` when the frame does not earn one.
|
|
1391
|
+
*
|
|
1392
|
+
* Fails closed in both directions: a window whose GPU was never measured gets no verdict, because
|
|
1393
|
+
* "the GPU is idle" is a claim and an absent reading is not evidence for it; and a window with no
|
|
1394
|
+
* shape to report gets none either, because a warning that cannot say what to change is noise.
|
|
1395
|
+
*/
|
|
1396
|
+
declare function sceneWarning(window: IFrameBudgetWindow, shape: ISceneShape | undefined, declaredTargetFps: number | undefined): ISceneWarning | undefined;
|
|
1397
|
+
/**
|
|
1398
|
+
* The scene's shape for one window, from the counts the frame already reported.
|
|
1399
|
+
*
|
|
1400
|
+
* `undefined` when the window carries no pass split: without draws there is nothing to name, and
|
|
1401
|
+
* a verdict built on an absent census would be a sentence about a scene nobody measured.
|
|
1402
|
+
*/
|
|
1403
|
+
declare function describeSceneShape(window: IFrameBudgetWindow, cull: IRenderCameraCullReport | undefined): ISceneShape | undefined;
|
|
1404
|
+
/** The one-line summary a reader sees without opening a log viewer. */
|
|
1405
|
+
declare function describeSceneWarning(warning: ISceneWarning): string;
|
|
1406
|
+
/** The marker line. */
|
|
1407
|
+
declare function formatSceneWarning(warning: ISceneWarning): string;
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* Authored static subtrees: stop recomposing transforms that nobody moves.
|
|
1411
|
+
*
|
|
1412
|
+
* The matrix walk is 2.22 ms of the reference game's 16.1 ms render phase, and almost none of what
|
|
1413
|
+
* it recomputes changed since the previous frame — island geometry, deck fittings, static props and
|
|
1414
|
+
* terrain are composed from the same position, quaternion and scale every frame, then multiplied
|
|
1415
|
+
* into the same world matrix, for as long as the game runs.
|
|
1416
|
+
*
|
|
1417
|
+
* **What this deletes, exactly, so the claim can be checked.** Three's `updateMatrixWorld` does
|
|
1418
|
+
* three things per object: `updateMatrix()` composes the local matrix when `matrixAutoUpdate`,
|
|
1419
|
+
* the world matrix is multiplied out when `matrixWorldNeedsUpdate || force`, and the walk recurses
|
|
1420
|
+
* into every child — the recursion is unconditional in three 0.185, so a frozen subtree still gets
|
|
1421
|
+
* visited. Freezing removes the two composes and keeps the visit. That is the honest bound: this
|
|
1422
|
+
* deletes arithmetic, not traversal, and the traversal is a separate lever with a separate PRD.
|
|
1423
|
+
*
|
|
1424
|
+
* **Staticness is authored, never guessed.** A heuristic that decides an object has "not moved in
|
|
1425
|
+
* 60 frames" is a correctness bug with no reproduction, so nothing here watches gameplay. What the
|
|
1426
|
+
* engine does do is check the promise where it is cheap to check it: the root's own local transform
|
|
1427
|
+
* is compared against what was frozen, once per frame, and a root the author moved thaws and
|
|
1428
|
+
* refreezes itself. That is O(static roots), not O(objects), and it turns the most common way to
|
|
1429
|
+
* get this wrong into a non-event. Writes deeper inside a frozen subtree are the author's to
|
|
1430
|
+
* announce with `invalidateStatic`, and `TN_RENDERLIST_VALIDATE=1` is what proves they did.
|
|
1431
|
+
*/
|
|
1432
|
+
|
|
1433
|
+
/** Marker printed with the static census on each reported window. */
|
|
1434
|
+
declare const STATIC_TRANSFORM_MARKER = "TN_STATIC_TRANSFORMS";
|
|
1435
|
+
/**
|
|
1436
|
+
* Marks a subtree static: its transforms are composed once here and never again until something
|
|
1437
|
+
* changes them.
|
|
1438
|
+
*
|
|
1439
|
+
* Idempotent, and it answers the version the subtree is now at, so a caller can assert on the
|
|
1440
|
+
* contract rather than on the flag. Marking a root twice re-arms it, which is the same thing
|
|
1441
|
+
* `invalidateStatic` does and the reason a generator can emit the call unconditionally.
|
|
1442
|
+
*/
|
|
1443
|
+
declare function markStatic(root: Object3D): number;
|
|
1444
|
+
/**
|
|
1445
|
+
* Thaws a subtree: every object composes again from the next walk.
|
|
1446
|
+
*
|
|
1447
|
+
* The flags are restored to three's defaults rather than to whatever they were before, because a
|
|
1448
|
+
* game that had already turned `matrixAutoUpdate` off for its own reasons and then marked the
|
|
1449
|
+
* subtree static is asking for the same behaviour either way.
|
|
1450
|
+
*/
|
|
1451
|
+
declare function unmarkStatic(root: Object3D): void;
|
|
1452
|
+
/**
|
|
1453
|
+
* Announces that something inside a frozen subtree moved. The subtree recomposes once and refreezes
|
|
1454
|
+
* at its new transform.
|
|
1455
|
+
*
|
|
1456
|
+
* Takes the object that moved, or the root; either way the root that owns it is what re-arms, since
|
|
1457
|
+
* a world matrix deeper in the subtree is a product of everything above it.
|
|
1458
|
+
*/
|
|
1459
|
+
declare function invalidateStatic(object: Object3D): number | undefined;
|
|
1460
|
+
/** Whether this object is a frozen root. */
|
|
1461
|
+
declare function isStatic(root: Object3D): boolean;
|
|
1462
|
+
/** One window's census of what is frozen and what had to thaw. */
|
|
1463
|
+
interface IStaticTransformCensus {
|
|
1464
|
+
/** Subtrees currently frozen. */
|
|
1465
|
+
readonly roots: number;
|
|
1466
|
+
/** Objects inside them, which is the count that stopped recomposing. */
|
|
1467
|
+
readonly objects: number;
|
|
1468
|
+
/** Roots the per-frame check found moved, and re-armed, since the previous census. */
|
|
1469
|
+
readonly rearmed: number;
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Re-arms any frozen root whose authored transform has changed since it was frozen.
|
|
1473
|
+
*
|
|
1474
|
+
* Called once per render phase, before the walk. It reads 26 numbers per frozen root and writes
|
|
1475
|
+
* nothing when nothing moved, so a scene that stays still pays a comparison per subtree and no
|
|
1476
|
+
* allocation at all. It cannot see a write deeper inside the subtree — that is `invalidateStatic`'s
|
|
1477
|
+
* job and `TN_RENDERLIST_VALIDATE=1`'s proof — and it does not pretend to.
|
|
1478
|
+
*/
|
|
1479
|
+
declare function refreshStaticTransforms(): void;
|
|
1480
|
+
/** The census, and the re-arm counter it resets. */
|
|
1481
|
+
declare function staticTransformCensus(): IStaticTransformCensus;
|
|
1482
|
+
/** Drops every registration. A game that tore down its scene must not keep its roots alive. */
|
|
1483
|
+
declare function resetStaticTransforms(): void;
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* The parity oracle for anything that stops recomputing a transform.
|
|
1487
|
+
*
|
|
1488
|
+
* A cache that is right on the scene you tested and wrong on the one you did not is worse than no
|
|
1489
|
+
* cache, because it ships as a visual bug nobody can reproduce. This is the instrument that makes
|
|
1490
|
+
* the difference falsifiable: with `TN_RENDERLIST_VALIDATE=1`, every frame recomputes every world
|
|
1491
|
+
* matrix from the authored transforms, the long way, and compares it elementwise against the one
|
|
1492
|
+
* the frame is about to draw with. The first disagreement throws, naming the object and the
|
|
1493
|
+
* element, because a validation mode that logs and continues is a validation mode nobody reads.
|
|
1494
|
+
*
|
|
1495
|
+
* It is a validation mode, not a proof: it proves the frames it ran on. Run it on the scenes you
|
|
1496
|
+
* care about, in CI, for as many frames as you can afford.
|
|
1497
|
+
*
|
|
1498
|
+
* Off by default and expensive by construction — it does the work it is checking, twice.
|
|
1499
|
+
*/
|
|
1500
|
+
|
|
1501
|
+
/** Marker printed when the validator is installed, so a log says which mode produced it. */
|
|
1502
|
+
declare const RENDERLIST_VALIDATE_MARKER = "TN_RENDERLIST_VALIDATE";
|
|
1503
|
+
/** The launch flag. */
|
|
1504
|
+
declare const RENDERLIST_VALIDATE_FLAG = "TN_RENDERLIST_VALIDATE";
|
|
1505
|
+
/** Whether `TN_RENDERLIST_VALIDATE` asks for validation on this launch. */
|
|
1506
|
+
declare function renderListValidationRequested(): boolean;
|
|
1507
|
+
/** What one frame's check found. */
|
|
1508
|
+
interface IValidationReport {
|
|
1509
|
+
/** Objects whose world matrix was recomputed and compared. */
|
|
1510
|
+
readonly checked: number;
|
|
1511
|
+
/** Objects skipped because they own their own world matrix; reported, never counted as checked. */
|
|
1512
|
+
readonly gameOwned: number;
|
|
1513
|
+
/** Frames validated so far. */
|
|
1514
|
+
readonly frames: number;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Recomputes every world matrix under `root` and throws on the first that disagrees.
|
|
1518
|
+
*
|
|
1519
|
+
* The recomputation is deliberately independent of the flags a freeze sets: it composes the local
|
|
1520
|
+
* matrix from `position`/`quaternion`/`scale` when the object composes its own, uses the authored
|
|
1521
|
+
* `matrix` when it does not, and multiplies by the parent's *recomputed* world matrix rather than
|
|
1522
|
+
* the cached one — so an error at the top of a subtree cannot be hidden by a matching error
|
|
1523
|
+
* underneath it.
|
|
1524
|
+
*/
|
|
1525
|
+
declare function validateWorldMatrices(root: Object3D): {
|
|
1526
|
+
checked: number;
|
|
1527
|
+
gameOwned: number;
|
|
1528
|
+
};
|
|
1529
|
+
/**
|
|
1530
|
+
* The per-frame validator, installed when the flag asks for it.
|
|
1531
|
+
*
|
|
1532
|
+
* Holds a frame counter so the marker can say how much was proven, which is the difference between
|
|
1533
|
+
* "validation passed" and "validation passed on 1,800 frames of the reference game".
|
|
1534
|
+
*/
|
|
1535
|
+
declare class RenderListValidator {
|
|
1536
|
+
#private;
|
|
1537
|
+
/**
|
|
1538
|
+
* Validates one frame. Throws on the first divergence.
|
|
1539
|
+
*
|
|
1540
|
+
* `roots` is the thing being drawn plus every frozen subtree. Those are not the same object
|
|
1541
|
+
* when the engine's projection is collapsing the scene: the draw root is then the mirror, and
|
|
1542
|
+
* validating only it reported three objects checked and zero divergences on a scene with two
|
|
1543
|
+
* hundred frozen meshes, one of which really was stale. A freeze is authored on the authored
|
|
1544
|
+
* scene, so the authored roots are checked whether or not they are what reaches the GPU.
|
|
1545
|
+
*/
|
|
1546
|
+
frame(...roots: readonly Object3D[]): void;
|
|
1547
|
+
report(): IValidationReport;
|
|
1548
|
+
}
|
|
1549
|
+
/** The marker line for one reported window. */
|
|
1550
|
+
declare function formatValidationReport(report: IValidationReport): string;
|
|
1551
|
+
|
|
775
1552
|
/** The atlas has one copied edge texel on either side of each packed SH sub-volume. */
|
|
776
1553
|
declare const ATLAS_PADDING = 1;
|
|
777
1554
|
/** The machine-readable marker emitted whenever the probe state changes. */
|
|
@@ -952,6 +1729,8 @@ interface IClipWindow {
|
|
|
952
1729
|
readonly level: number;
|
|
953
1730
|
readonly extent: number;
|
|
954
1731
|
readonly pageWorldSize: number;
|
|
1732
|
+
/** Pages a window trails its followed centre by before it moves and re-renders. */
|
|
1733
|
+
readonly refreshPages: number;
|
|
955
1734
|
readonly minX: number;
|
|
956
1735
|
readonly minY: number;
|
|
957
1736
|
readonly maxX: number;
|
|
@@ -963,8 +1742,18 @@ interface IDirectionalClipmapOptions {
|
|
|
963
1742
|
/** Half-width of each level's window in world units, finest first. */
|
|
964
1743
|
readonly clipExtents: readonly number[];
|
|
965
1744
|
readonly pagesPerAxis: number;
|
|
966
|
-
/**
|
|
967
|
-
|
|
1745
|
+
/**
|
|
1746
|
+
* Fraction of an extent inside which a point selects that level, `(0, 1]`, default 0.9. One value
|
|
1747
|
+
* for every level, or one per level finest first, the last entry standing in for the rest.
|
|
1748
|
+
*/
|
|
1749
|
+
readonly selectionGuard?: number | readonly number[];
|
|
1750
|
+
/**
|
|
1751
|
+
* Fraction of an extent a level's window may trail its followed centre by before it re-renders,
|
|
1752
|
+
* `[0, 1)`, default 0.125. Rounded to a whole number of the level's texels, so `0` keeps the
|
|
1753
|
+
* old one-texel step. Larger steps re-render a level less often as the camera walks. One value
|
|
1754
|
+
* for every level, or one per level finest first, the last entry standing in for the rest.
|
|
1755
|
+
*/
|
|
1756
|
+
readonly refreshStep?: number | readonly number[];
|
|
968
1757
|
}
|
|
969
1758
|
/**
|
|
970
1759
|
* Camera-centred clip windows in a light-space basis, snapped to whole pages.
|
|
@@ -977,14 +1766,21 @@ declare class DirectionalClipmap {
|
|
|
977
1766
|
#private;
|
|
978
1767
|
readonly clipExtents: readonly number[];
|
|
979
1768
|
readonly pagesPerAxis: number;
|
|
980
|
-
|
|
1769
|
+
/**
|
|
1770
|
+
* Per level, finest first; the last entry stands in for every level past it. Mutable through
|
|
1771
|
+
* {@link setRefreshStep} so a level whose own render is expensive can be stepped further, which
|
|
1772
|
+
* is fewer grid positions for its window rather than a different grid.
|
|
1773
|
+
*/
|
|
1774
|
+
refreshStep: number[];
|
|
1775
|
+
/** Per level, finest first; the last entry stands in for every level past it. */
|
|
1776
|
+
readonly selectionGuard: readonly number[];
|
|
981
1777
|
readonly levelCount: number;
|
|
982
1778
|
basisU: IVector3Like;
|
|
983
1779
|
basisV: IVector3Like;
|
|
984
1780
|
basisW: IVector3Like;
|
|
985
1781
|
centerWorld: IVector3Like;
|
|
986
1782
|
centerLight: ILightSpacePoint;
|
|
987
|
-
constructor({ direction, clipExtents, pagesPerAxis, selectionGuard, }: IDirectionalClipmapOptions);
|
|
1783
|
+
constructor({ direction, clipExtents, pagesPerAxis, selectionGuard, refreshStep, }: IDirectionalClipmapOptions);
|
|
988
1784
|
/** Re-orient the basis; returns true when it changed enough that cached pages are stale. */
|
|
989
1785
|
setDirection(direction: IVector3Like): boolean;
|
|
990
1786
|
project(worldPoint: IVector3Like): ILightSpacePoint;
|
|
@@ -993,6 +1789,8 @@ declare class DirectionalClipmap {
|
|
|
993
1789
|
v: number;
|
|
994
1790
|
w?: number;
|
|
995
1791
|
}): IVector3Like;
|
|
1792
|
+
/** Re-step one level's window: the trail its followed centre may move before it re-renders. */
|
|
1793
|
+
setRefreshStep(level: number, step: number): void;
|
|
996
1794
|
updateCenter(worldPoint: IVector3Like): readonly IClipWindow[];
|
|
997
1795
|
getWindow(level: number): IClipWindow;
|
|
998
1796
|
pageWorldSize(level: number): number;
|
|
@@ -1065,12 +1863,130 @@ interface IVirtualShadowOptions {
|
|
|
1065
1863
|
/**
|
|
1066
1864
|
* Fraction of a level's extent inside which a fragment still selects that level, `(0, 1]`,
|
|
1067
1865
|
* default 0.9 — the outer ring falls through to the next level so the edge is never sampled.
|
|
1866
|
+
* `selectionGuard` minus `refreshStep`, since a window trails its centre by that much. One value
|
|
1867
|
+
* for every level, or one per level finest first, the last entry standing in for the rest.
|
|
1868
|
+
*/
|
|
1869
|
+
readonly selectionGuard?: number | readonly number[];
|
|
1870
|
+
/**
|
|
1871
|
+
* Fraction of a level's extent its window may trail the followed centre by before it re-renders,
|
|
1872
|
+
* `[0, selectionGuard)`, default 0.125 — `refreshStep: 0` keeps the old one-texel step. The step
|
|
1873
|
+
* is rounded to a whole number of that level's texels, so a walking camera re-renders a level
|
|
1874
|
+
* ~`1/refreshStep` times less often while the shadow texels stay on one fixed world grid. Costs
|
|
1875
|
+
* that much of `selectionGuard`, which it is already reduced by, so a window never reaches past
|
|
1876
|
+
* the trailing edge of a map rendered before the centre moved. One value for every level, or one
|
|
1877
|
+
* per level finest first, the last entry standing in for the rest — the fine level is the one a
|
|
1878
|
+
* walking camera re-renders most, so it is the one that wants the larger step.
|
|
1879
|
+
*/
|
|
1880
|
+
readonly refreshStep?: number | readonly number[];
|
|
1881
|
+
/**
|
|
1882
|
+
* Let a level buy itself a wider `refreshStep` out of what its own last render cost, default true.
|
|
1883
|
+
*
|
|
1884
|
+
* A level render is a whole scene draw, so the engine cannot know what one is worth until it has
|
|
1885
|
+
* paid for it: the node measures the draw it just took and smooths that reading, and a level whose
|
|
1886
|
+
* smoothed cost is a large share of the frame period is re-rendered less often. This is automatic
|
|
1887
|
+
* because the value it reacts to is a measurement, not a setting — a game sets nothing, and a
|
|
1888
|
+
* cheap level is never widened and behaves exactly as before. The widened trail is capped by the
|
|
1889
|
+
* window's own margin, so the camera stays inside the region that level serves and everything that
|
|
1890
|
+
* level serves stays inside the map; a level with no such margin is never widened at all.
|
|
1891
|
+
*
|
|
1892
|
+
* `false` puts every level back on the `refreshStep` it was given, which is also what a harness
|
|
1893
|
+
* that wants to count today's renders uses.
|
|
1894
|
+
*/
|
|
1895
|
+
readonly adaptiveRefresh?: boolean;
|
|
1896
|
+
/**
|
|
1897
|
+
* Fraction of the display period a level's smoothed render cost may take before its refresh trail
|
|
1898
|
+
* widens, `(0, 1]`, default 0.4.
|
|
1899
|
+
*
|
|
1900
|
+
* The period is the frame's own `deltaTime`, capped at the 60 fps frame (16.7 ms). The cap is what
|
|
1901
|
+
* keeps the budget honest: the frame that renders an expensive level is itself long because of that
|
|
1902
|
+
* render, so reading the share against its own inflated delta would count the cost twice and let
|
|
1903
|
+
* the very level that needs adapting pass its own test. At 60 Hz the cap is the frame itself; on a
|
|
1904
|
+
* 120 Hz panel the same render is twice the share it is at 60; below 60 the cap holds the budget
|
|
1905
|
+
* at the 60 fps frame rather than growing with the stall. Below the share nothing changes at all,
|
|
1906
|
+
* and above it the trail widens in proportion to the overshoot,
|
|
1907
|
+
* because that is the ratio between what the level costs and what a frame can afford to spend on
|
|
1908
|
+
* it — a level four times over the share refreshs about four times less often, until the cap
|
|
1909
|
+
* below it runs out.
|
|
1910
|
+
*/
|
|
1911
|
+
readonly expensiveRefreshShare?: number;
|
|
1912
|
+
/**
|
|
1913
|
+
* Raise a level's texel size gate while its own render is too expensive for the frame, default
|
|
1914
|
+
* true.
|
|
1915
|
+
*
|
|
1916
|
+
* The same measurement adaptive refresh reads — the smoothed cost of the level's own last render,
|
|
1917
|
+
* against the share of the frame it may take — drives a second, independent adaptation: a level
|
|
1918
|
+
* whose render is over budget sizes its gate up in steps of 1.5 until it is affordable again, up
|
|
1919
|
+
* to 8 times the configured gate, and halves it back toward 1 once the render is comfortably under
|
|
1920
|
+
* half the budget. The gate only ever judges a caster by its size (see `minCasterTexels`), so this
|
|
1921
|
+
* drops the tiniest props first and never a building, and a level that is cheap is never touched:
|
|
1922
|
+
* it stays at scale 1 and submits exactly what it did before.
|
|
1923
|
+
*
|
|
1924
|
+
* `false` pins every level at scale 1, which is also what a harness that wants today's draw
|
|
1925
|
+
* counts uses.
|
|
1926
|
+
*/
|
|
1927
|
+
readonly adaptiveCasterGate?: boolean;
|
|
1928
|
+
/**
|
|
1929
|
+
* How long a level waits after its own last render before an invalidation may re-render it, in
|
|
1930
|
+
* seconds. One value for every level, or one per level finest first, the last entry standing in
|
|
1931
|
+
* for the rest. Default `0.25 * extent / finestExtent` — 0.25 s, 1 s and 3.33 s for extents
|
|
1932
|
+
* 24 / 96 / 320 — and `0` renders on the very next frame, which is what the node did before.
|
|
1933
|
+
*
|
|
1934
|
+
* A streamed world invalidates its shadows on every residency update, and a cell admitted at the
|
|
1935
|
+
* ring edge lands in the coarsest window: the level that redraws for it is the one paying for the
|
|
1936
|
+
* whole ring's wide casters, ten times a second where movement alone asks for two. A level
|
|
1937
|
+
* waiting out its delay keeps the map and window it has, which is already right for every static
|
|
1938
|
+
* caster in it, so only a newly streamed caster is missing — the trade is that a far tree's
|
|
1939
|
+
* shadow can appear up to the delay late (3.33 s at extent 320, invisible on a level whose texel
|
|
1940
|
+
* is wider than the tree). A window that moved is never delayed: that is a different reason, and
|
|
1941
|
+
* it is not this option's.
|
|
1942
|
+
*/
|
|
1943
|
+
readonly invalidationDelay?: number | readonly number[];
|
|
1944
|
+
/**
|
|
1945
|
+
* How far behind the window centre each level camera sits, in world units. Default 200. An
|
|
1946
|
+
* explicit `lightDistance` *and* `depthRange` switch the level's light-space depth off the derived
|
|
1947
|
+
* span below and back onto this pair, so a game that knows its own world sizes can still say so.
|
|
1068
1948
|
*/
|
|
1069
|
-
readonly selectionGuard?: number;
|
|
1070
|
-
/** How far behind the window centre each level camera sits, in world units. Default 200. */
|
|
1071
1949
|
readonly lightDistance?: number;
|
|
1072
|
-
/**
|
|
1950
|
+
/**
|
|
1951
|
+
* Depth range each level camera covers past its centre, in world units. Default 400. Read only
|
|
1952
|
+
* together with `lightDistance`; on its own the span is still derived.
|
|
1953
|
+
*/
|
|
1073
1954
|
readonly depthRange?: number;
|
|
1955
|
+
/**
|
|
1956
|
+
* Texels of a level a caster must cover before it draws into that level, default 1.5. A level's
|
|
1957
|
+
* texel is `2 * extent / mapSize`, so the finest levels keep everything and the coarse ones keep
|
|
1958
|
+
* only what they can resolve: a fern is a whole number of texels in a 48 m window and a fraction
|
|
1959
|
+
* of one in a 640 m window, so it stops being drawn there and the shadow it casts is the ground
|
|
1960
|
+
* cover's own, not its silhouette's. Set 0 to draw every caster into every level.
|
|
1961
|
+
*/
|
|
1962
|
+
readonly minCasterTexels?: number;
|
|
1963
|
+
/**
|
|
1964
|
+
* Draw every level past the finest one with less geometry than the main pass would, default true.
|
|
1965
|
+
*
|
|
1966
|
+
* Two defaults, both Unreal's and both applied in one place — the traverse a level render already
|
|
1967
|
+
* makes over the casters in its window, where each of the two is a change to the object three
|
|
1968
|
+
* draws from and is put back the moment that render is over:
|
|
1969
|
+
*
|
|
1970
|
+
* 1. **Shadow LOD bias.** A mesh whose geometry carries a registered AutoLOD chain
|
|
1971
|
+
* (`lodChainOf`) is submitted with the chain's *coarsest* geometry. A level 2 window cannot
|
|
1972
|
+
* resolve a tree's needles, so drawing LOD0 there is a texel of needles per texel of shadow;
|
|
1973
|
+
* the coarse level's own texel is metres wide. Level 0 draws what the main pass draws.
|
|
1974
|
+
* 2. **Alpha-caster range.** An alpha-tested (`alphaTest > 0`) or transparent mesh casts into the
|
|
1975
|
+
* finest level only. Its cutout is its own texture: a coarse level either drops it — the
|
|
1976
|
+
* level's texel is wider than the card, so the fence is sub-texel — or keeps resolving a
|
|
1977
|
+
* texture it cannot afford. This is a per-primitive shadow cull distance, and the trade is
|
|
1978
|
+
* honest and one-sided: a fence's or a foliage card's shadow ends where the finest level's
|
|
1979
|
+
* window ends, and a wide level shows bare ground where it stood. Opaque casters — a merged
|
|
1980
|
+
* chunk's position-only proxy, a tree's silhouette — are drawn on every level as before.
|
|
1981
|
+
*
|
|
1982
|
+
* Neither is left changed: a hidden caster's `castShadow` and a coarse mesh's `geometry` are put
|
|
1983
|
+
* back before the next level, before the mover maps and before the main pass, which see the world
|
|
1984
|
+
* exactly as the game authored it. Mover maps keep full detail — a 256² map over the level's own
|
|
1985
|
+
* window, drawing only the tracked casters.
|
|
1986
|
+
* `false` puts every level back on stock full-detail draws, which is what the node did before
|
|
1987
|
+
* either default existed.
|
|
1988
|
+
*/
|
|
1989
|
+
readonly shadowLodBias?: boolean;
|
|
1074
1990
|
/** Print the `TN_VIRTUAL_SHADOW` line every `markerEvery` frames; `false` silences it. Default 300. */
|
|
1075
1991
|
readonly marker?: boolean | number;
|
|
1076
1992
|
}
|
|
@@ -1090,8 +2006,76 @@ interface IVirtualShadowStats {
|
|
|
1090
2006
|
readonly cached: number;
|
|
1091
2007
|
/** Levels rendered this frame, for any reason. */
|
|
1092
2008
|
readonly rendered: number;
|
|
2009
|
+
/**
|
|
2010
|
+
* Levels that wanted a render this frame and did not get one, because the node renders at most
|
|
2011
|
+
* one level per frame. They keep the map they already have and come due again next frame.
|
|
2012
|
+
*/
|
|
2013
|
+
readonly deferred: number;
|
|
2014
|
+
/**
|
|
2015
|
+
* Levels holding a redraw for an invalidation that is still inside its own `invalidationDelay`,
|
|
2016
|
+
* so this frame is not the frame they render on. They keep the map they have, which is right for
|
|
2017
|
+
* every static caster in it: only a caster streamed in since is missing, and the cumulative
|
|
2018
|
+
* `coalesced` below is what is being waited out.
|
|
2019
|
+
*/
|
|
2020
|
+
readonly held: number;
|
|
1093
2021
|
/** Fraction of levels served from cache over the node's lifetime. */
|
|
1094
2022
|
readonly reuseRatio: number;
|
|
2023
|
+
/**
|
|
2024
|
+
* Level renders over the node's lifetime, for any reason. `byMove` and `byInvalidation` are the two
|
|
2025
|
+
* the node has — a window that moved, and an invalidation that asked and waited out its delay — and
|
|
2026
|
+
* they add up to this exactly, because a render the single per-frame budget defers is counted
|
|
2027
|
+
* against the reason that queued it and not again when it is finally taken.
|
|
2028
|
+
*/
|
|
2029
|
+
readonly rendersTotal: number;
|
|
2030
|
+
/** Of those, the renders taken because a level's window moved. */
|
|
2031
|
+
readonly byMove: number;
|
|
2032
|
+
/** Of those, the renders taken because an invalidation asked and its level's delay had passed. */
|
|
2033
|
+
readonly byInvalidation: number;
|
|
2034
|
+
/**
|
|
2035
|
+
* Invalidation asks that cost no render of their own, over the node's lifetime: absorbed by a
|
|
2036
|
+
* render the level was taking anyway for its window, or merged into an ask already waiting out
|
|
2037
|
+
* that level's delay. The two of these are the shape of the win — with the default delays a walk
|
|
2038
|
+
* asks once a second and renders about that often, and every ask in between is counted here.
|
|
2039
|
+
*/
|
|
2040
|
+
readonly coalesced: number;
|
|
2041
|
+
/**
|
|
2042
|
+
* The same four counters per level, finest first: which window moved, which invalidation asked
|
|
2043
|
+
* for a redraw, which level took the frame's single render, and which wanted one and did not get
|
|
2044
|
+
* it. One entry per level per frame, so a harness reading this — or the `TN_VIRTUAL_SHADOW`
|
|
2045
|
+
* marker line, which carries it — can see which level a walk keeps re-rendering instead of only
|
|
2046
|
+
* how many.
|
|
2047
|
+
*/
|
|
2048
|
+
readonly perLevel: readonly IVirtualShadowLevelStat[];
|
|
2049
|
+
}
|
|
2050
|
+
/** One level's row of {@link IVirtualShadowStats}: 1 or 0 per counter, per frame. */
|
|
2051
|
+
interface IVirtualShadowLevelStat {
|
|
2052
|
+
/** The level's clip extent, in world units. */
|
|
2053
|
+
readonly extent: number;
|
|
2054
|
+
readonly deferred: number;
|
|
2055
|
+
readonly invalidated: number;
|
|
2056
|
+
readonly moved: number;
|
|
2057
|
+
readonly rendered: number;
|
|
2058
|
+
/** Caster meshes this level's chosen camera layers submit this frame. Zero if it did not render. */
|
|
2059
|
+
readonly draws: number;
|
|
2060
|
+
/** The same bill split by kind; the five parts sum to {@link draws}. */
|
|
2061
|
+
readonly drawsBy: IVirtualShadowDraws;
|
|
2062
|
+
/** The adaptive caster gate's scale on this level, 1 when it is not shedding casters. */
|
|
2063
|
+
readonly gateScale: number;
|
|
2064
|
+
/** Casters the gate hid on the render this level took this frame; zero if it did not render. */
|
|
2065
|
+
readonly gateHidden: number;
|
|
2066
|
+
}
|
|
2067
|
+
/** One level's caster draws, by the kind of mesh that submitted them. */
|
|
2068
|
+
interface IVirtualShadowDraws {
|
|
2069
|
+
/** Caster batch meshes on the cluster layer, when the cluster half is the level's choice. */
|
|
2070
|
+
readonly cluster: number;
|
|
2071
|
+
/** Key-wide caster meshes on the wide layer, when the wide half is the level's choice. */
|
|
2072
|
+
readonly wide: number;
|
|
2073
|
+
/** Small casters, submitted by the finest level and by any level rendering a prewarm. */
|
|
2074
|
+
readonly small: number;
|
|
2075
|
+
/** Merged per-chunk shadow proxies (`<name>-shadow`), drawn with the cluster half. */
|
|
2076
|
+
readonly chunkProxy: number;
|
|
2077
|
+
/** Everything else casting from layer 0 — terrain, props — which every level draws. */
|
|
2078
|
+
readonly layer0: number;
|
|
1095
2079
|
}
|
|
1096
2080
|
declare const VIRTUAL_SHADOW_MARKER = "TN_VIRTUAL_SHADOW";
|
|
1097
2081
|
/**
|
|
@@ -1099,6 +2083,21 @@ declare const VIRTUAL_SHADOW_MARKER = "TN_VIRTUAL_SHADOW";
|
|
|
1099
2083
|
* Keep it free of other uses; the main camera never needs it (tracked objects keep layer 0).
|
|
1100
2084
|
*/
|
|
1101
2085
|
declare const VIRTUAL_SHADOW_MOVER_LAYER = 29;
|
|
2086
|
+
/**
|
|
2087
|
+
* The object layer a shadow-only caster is put on, so each level's own shadow camera renders it and
|
|
2088
|
+
* the main camera never sees it. The level cameras have it enabled already; an object that exists
|
|
2089
|
+
* only as a shadow caster calls `object.layers.set(VIRTUAL_SHADOW_CASTER_LAYER)`.
|
|
2090
|
+
*/
|
|
2091
|
+
declare const VIRTUAL_SHADOW_CASTER_LAYER = 28;
|
|
2092
|
+
/**
|
|
2093
|
+
* The wide counterpart of {@link VIRTUAL_SHADOW_CASTER_LAYER}: one caster mesh per
|
|
2094
|
+
* `asset:level:part` holding every resident cell's records, for the levels whose window covers the
|
|
2095
|
+
* whole resident ring. A level renders exactly one of the two caster layers — whichever submits
|
|
2096
|
+
* fewer draws, counted off the meshes themselves, because a cluster per square is a draw per square
|
|
2097
|
+
* for the same pixels once the window holds the ring. `WorldCells` writes both halves of every key,
|
|
2098
|
+
* so whichever a level picks is there; the main camera renders neither.
|
|
2099
|
+
*/
|
|
2100
|
+
declare const VIRTUAL_SHADOW_WIDE_CASTER_LAYER = 27;
|
|
1102
2101
|
/**
|
|
1103
2102
|
* One directional shadow for a whole open world: camera-centred clip levels, each snapped to its
|
|
1104
2103
|
* own texel grid and re-rendered only when its window moves. Movers never touch that cache: a
|
|
@@ -1113,7 +2112,9 @@ declare const VIRTUAL_SHADOW_MOVER_LAYER = 29;
|
|
|
1113
2112
|
* map type and filter, and those source settings are mirrored into each stock level node before
|
|
1114
2113
|
* rendering. Per-level map sizes, cameras, `autoUpdate` and `needsUpdate` are owned by this node.
|
|
1115
2114
|
* Each level is rendered by the stock {@link ShadowNode} through the renderer's shadow-map type,
|
|
1116
|
-
* so the look is the same code path a plain shadow uses.
|
|
2115
|
+
* so the look is the same code path a plain shadow uses. At most one level renders per frame,
|
|
2116
|
+
* finest first — a level render is a whole scene draw, and a level passed over keeps the map and
|
|
2117
|
+
* window it has, so a fragment only ever samples a map that level drew.
|
|
1117
2118
|
*
|
|
1118
2119
|
* Ported from the virtual-shadow-map prototype's clipmap and invalidation; the sparse page atlas
|
|
1119
2120
|
* is deliberately not the first cut — a page needs the scene rendered once per page, and on a
|
|
@@ -1136,8 +2137,18 @@ declare const VIRTUAL_SHADOW_MOVER_LAYER = 29;
|
|
|
1136
2137
|
declare class VirtualShadowNode extends ShadowBaseNode {
|
|
1137
2138
|
#private;
|
|
1138
2139
|
static get type(): string;
|
|
1139
|
-
readonly options: Required<Omit<IVirtualShadowOptions, "marker">> & {
|
|
2140
|
+
readonly options: Required<Omit<IVirtualShadowOptions, "marker" | "refreshStep" | "selectionGuard" | "invalidationDelay">> & {
|
|
1140
2141
|
readonly markerEvery: number;
|
|
2142
|
+
/** Per level, finest first; the last entry stands in for every level past it. */
|
|
2143
|
+
readonly refreshStep: readonly number[];
|
|
2144
|
+
/** The per-level `selectionGuard` each level's own window is drawn with. */
|
|
2145
|
+
readonly selectionGuard: readonly number[];
|
|
2146
|
+
/**
|
|
2147
|
+
* Per level, finest first: the seconds a dirty level waits after its own last render before an
|
|
2148
|
+
* invalidation may re-render it. Already scaled by that level's extent, so the fine levels
|
|
2149
|
+
* answer a streamed caster promptly and the coarse ones let a burst merge into one render.
|
|
2150
|
+
*/
|
|
2151
|
+
readonly invalidationDelay: readonly number[];
|
|
1141
2152
|
};
|
|
1142
2153
|
readonly clipmap: DirectionalClipmap;
|
|
1143
2154
|
/**
|
|
@@ -1163,6 +2174,18 @@ declare class VirtualShadowNode extends ShadowBaseNode {
|
|
|
1163
2174
|
untrackCaster(objectOrId: Object3D | string): boolean;
|
|
1164
2175
|
/** Force every level to re-render on the next frame — a tree fell, a door opened. */
|
|
1165
2176
|
invalidateAll(): void;
|
|
2177
|
+
/**
|
|
2178
|
+
* Force only the levels whose current window covers `bounds`, on the next frame. A streamed world
|
|
2179
|
+
* that hands its shadow casters over has a moving near set, and a blanket `invalidateAll()` on
|
|
2180
|
+
* every refresh redrew all three levels for a change that lands in one corner of one of them. A
|
|
2181
|
+
* level whose window does not cover the region draws the same thing either way, so skipping it is
|
|
2182
|
+
* the same frame with fewer draws in it.
|
|
2183
|
+
*
|
|
2184
|
+
* `bounds` is `{ min: {x,y,z}, max: {x,y,z} }`, a plain object so a game can hand one over without
|
|
2185
|
+
* importing three. Regions are consumed by the next `updateBefore`; one that arrives before the
|
|
2186
|
+
* levels exist is dropped, because their first frame renders all of them anyway.
|
|
2187
|
+
*/
|
|
2188
|
+
invalidateRegion(bounds: IBoundsLike): void;
|
|
1166
2189
|
setup(builder: NodeBuilder): Node | null | undefined;
|
|
1167
2190
|
updateBefore(frame: NodeFrame): undefined;
|
|
1168
2191
|
dispose(): void;
|
|
@@ -1178,6 +2201,87 @@ declare class VirtualShadowNode extends ShadowBaseNode {
|
|
|
1178
2201
|
*/
|
|
1179
2202
|
declare function readVirtualShadowMarker(line: string): IVirtualShadowStats | undefined;
|
|
1180
2203
|
|
|
2204
|
+
/** Every value is the game's: this rig wires them and chooses none. */
|
|
2205
|
+
interface IDaylightOptions {
|
|
2206
|
+
/** The eye the sky, the sun's shadow windows and the haze centre on; usually the camera. */
|
|
2207
|
+
readonly follow: Object3D;
|
|
2208
|
+
/** Unit vector from the ground towards the sun. */
|
|
2209
|
+
readonly sunDirection: Vector3;
|
|
2210
|
+
readonly sunColor: Color;
|
|
2211
|
+
/** Irradiance in three's physical units; the same number a Blender sun strength carries. */
|
|
2212
|
+
readonly sunIntensity: number;
|
|
2213
|
+
/** Half-widths of the shadow windows in world units, finest first, strictly increasing. */
|
|
2214
|
+
readonly shadowExtents: readonly number[];
|
|
2215
|
+
/**
|
|
2216
|
+
* How far a window may trail the camera before it re-renders, as a fraction of its own extent:
|
|
2217
|
+
* one value for every level, or one per level finest first, the last entry standing in for the
|
|
2218
|
+
* rest. One for all of them spends the hysteresis the fine level can least afford on the coarse
|
|
2219
|
+
* one, which is the level a walking camera re-renders least. Default 0.125.
|
|
2220
|
+
*/
|
|
2221
|
+
readonly refreshStep?: number | readonly number[];
|
|
2222
|
+
/** Preetham sky parameters, as three's `SkyMesh` takes them. */
|
|
2223
|
+
readonly sky: {
|
|
2224
|
+
readonly turbidity: number;
|
|
2225
|
+
readonly rayleigh: number;
|
|
2226
|
+
readonly mieCoefficient: number;
|
|
2227
|
+
readonly mieDirectionalG: number;
|
|
2228
|
+
};
|
|
2229
|
+
/** Sky fill from above and bounce from below, as a hemisphere light. */
|
|
2230
|
+
readonly fill: {
|
|
2231
|
+
readonly sky: Color;
|
|
2232
|
+
readonly ground: Color;
|
|
2233
|
+
readonly intensity: number;
|
|
2234
|
+
};
|
|
2235
|
+
/** Exponential-squared haze in the sky's horizon colour, so distance fades into the sky. */
|
|
2236
|
+
readonly haze: {
|
|
2237
|
+
readonly color: Color;
|
|
2238
|
+
readonly density: number;
|
|
2239
|
+
};
|
|
2240
|
+
/** Linear exposure multiplier for the AgX tone curve (2^EV). */
|
|
2241
|
+
readonly exposure: number;
|
|
2242
|
+
/**
|
|
2243
|
+
* Edge of the sky box in world units. It must sit inside the camera's far plane with its corners
|
|
2244
|
+
* included (a box, so half-diagonal ≈ 0.87 × this).
|
|
2245
|
+
*/
|
|
2246
|
+
readonly skySize: number;
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* An outdoor daylight rig: a physical sky that stays on the eye, one sun whose open-world shadow
|
|
2250
|
+
* windows follow the eye (`VirtualShadowNode`), a hemisphere fill, sky-coloured distance haze and
|
|
2251
|
+
* the AgX tone curve at the game's exposure. It is plumbing, not a look: every colour, angle,
|
|
2252
|
+
* intensity and density is a required option, and the rig adds nothing a game did not ask for.
|
|
2253
|
+
*
|
|
2254
|
+
* Add it with `ctx.add(daylight)`: `attachRenderer` sets tone mapping, exposure and the shadow map
|
|
2255
|
+
* once, and `process` (render cadence) keeps the sky box, the sun and its target on the eye every
|
|
2256
|
+
* frame, so a 2 km map never walks out of its own sky or shadow.
|
|
2257
|
+
*
|
|
2258
|
+
* @situation daytime sky, sun and shadows for a large outdoor map
|
|
2259
|
+
* @situation distant terrain should fade into the sky instead of a coloured wall
|
|
2260
|
+
* @situation match a Blender look-dev scene's sun, sky and exposure in the game
|
|
2261
|
+
* @constraint every value is required; there is no default sun, sky, haze or exposure
|
|
2262
|
+
* @constraint `skySize` must keep the sky box's corners inside the camera's far plane
|
|
2263
|
+
* @constraint shadowExtents follow `VirtualShadowNode`: half-widths, finest first, strictly increasing
|
|
2264
|
+
* @override sky uniforms stay live on `daylight.sky`; the light and fill are `daylight.sun` and `daylight.fill`
|
|
2265
|
+
* @example
|
|
2266
|
+
* const daylight = new Daylight({ follow: ctx.camera, sunDirection, sunColor, sunIntensity: 4, shadowExtents: [24, 96, 320], sky: { turbidity: 3, rayleigh: 1.4, mieCoefficient: 0.004, mieDirectionalG: 0.8 }, fill: { sky, ground, intensity: 1.1 }, haze: { color: horizon, density: 0.0011 }, exposure: 2 ** -0.6, skySize: 1600 });
|
|
2267
|
+
* ctx.add(daylight);
|
|
2268
|
+
*/
|
|
2269
|
+
declare class Daylight extends Group implements IComputeDriven$1 {
|
|
2270
|
+
#private;
|
|
2271
|
+
readonly warmupNodes: readonly unknown[];
|
|
2272
|
+
readonly processCadence: "render";
|
|
2273
|
+
readonly sky: SkyMesh;
|
|
2274
|
+
readonly sun: DirectionalLight;
|
|
2275
|
+
readonly fill: HemisphereLight;
|
|
2276
|
+
constructor(options: IDaylightOptions);
|
|
2277
|
+
get released(): boolean;
|
|
2278
|
+
/** The haze this rig owns; `attachRenderer` puts it on the scene the rig was added to. */
|
|
2279
|
+
get haze(): FogExp2;
|
|
2280
|
+
attachRenderer(renderer: IRendererLike): void;
|
|
2281
|
+
process(): void;
|
|
2282
|
+
detach(): void;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
1181
2285
|
/**
|
|
1182
2286
|
* Two load-time mechanisms every streaming game writes by hand, and gets wrong in the same places.
|
|
1183
2287
|
*
|
|
@@ -1531,6 +2635,100 @@ declare class FluidField2D extends Group {
|
|
|
1531
2635
|
detach(): void;
|
|
1532
2636
|
}
|
|
1533
2637
|
|
|
2638
|
+
/**
|
|
2639
|
+
* A disturbance that propagates. `WaveField` in this same package evaluates a fixed analytic
|
|
2640
|
+
* swell: it is the sea a game always has, and nothing a game does changes it. This is the other
|
|
2641
|
+
* half — a square patch of surface that is flat until something hits it, carries the rings
|
|
2642
|
+
* outward at a real celerity, and forgets them again.
|
|
2643
|
+
*
|
|
2644
|
+
* The solve is the 2-D wave equation on a regular grid, plus an advected foam density. It owns no
|
|
2645
|
+
* Three.js object, no material and no colour: it reports height, foam and horizontal flow, and the
|
|
2646
|
+
* game decides what those look like. Add its height to an analytic swell; do not replace one.
|
|
2647
|
+
*
|
|
2648
|
+
* There is deliberately no obstacle or hull mask. A mask is only correct for a body that does not
|
|
2649
|
+
* move, and a game whose hulls move is better served drawing the displacement those hulls make
|
|
2650
|
+
* than re-rasterising a mask every time one of them advances a metre.
|
|
2651
|
+
*/
|
|
2652
|
+
interface IRippleFieldFlow {
|
|
2653
|
+
x: number;
|
|
2654
|
+
z: number;
|
|
2655
|
+
}
|
|
2656
|
+
interface IRippleFieldOptions {
|
|
2657
|
+
/** Cells per side. Cost is quadratic in this; 128 over a 400 m patch is ~3 m per cell. */
|
|
2658
|
+
readonly resolution: number;
|
|
2659
|
+
/** Width of the patch in metres. */
|
|
2660
|
+
readonly size: number;
|
|
2661
|
+
/** Wave celerity in metres per second. Sets both ring speed and the stable step. */
|
|
2662
|
+
readonly speed?: number;
|
|
2663
|
+
/** Bulk damping. Higher forgets a disturbance sooner. */
|
|
2664
|
+
readonly damping?: number;
|
|
2665
|
+
/** Seconds for undisturbed foam to halve. */
|
|
2666
|
+
readonly foamHalfLife?: number;
|
|
2667
|
+
/** Steady surface drift in metres per second, added to the solved flow. */
|
|
2668
|
+
readonly current?: IRippleFieldFlow;
|
|
2669
|
+
/**
|
|
2670
|
+
* Integration step in seconds. Defaults to 1/60 s, or the CFL limit for this cell size and
|
|
2671
|
+
* celerity where that is smaller.
|
|
2672
|
+
*/
|
|
2673
|
+
readonly step?: number;
|
|
2674
|
+
/** Substeps one `advance` will run before it drops the rest of the frame's time. */
|
|
2675
|
+
readonly maxSteps?: number;
|
|
2676
|
+
}
|
|
2677
|
+
declare class RippleField {
|
|
2678
|
+
#private;
|
|
2679
|
+
readonly resolution: number;
|
|
2680
|
+
readonly size: number;
|
|
2681
|
+
readonly dx: number;
|
|
2682
|
+
readonly speed: number;
|
|
2683
|
+
readonly damping: number;
|
|
2684
|
+
readonly step: number;
|
|
2685
|
+
readonly maxSteps: number;
|
|
2686
|
+
foamHalfLife: number;
|
|
2687
|
+
current: IRippleFieldFlow;
|
|
2688
|
+
/** Surface height per cell, row-major from the patch's -x/-z corner. Read-only to the game. */
|
|
2689
|
+
height: Float32Array;
|
|
2690
|
+
/** Foam density per cell in 0..1. */
|
|
2691
|
+
foam: Float32Array;
|
|
2692
|
+
/** Horizontal flow per cell, metres per second, excluding `current`. */
|
|
2693
|
+
readonly flowX: Float32Array;
|
|
2694
|
+
readonly flowZ: Float32Array;
|
|
2695
|
+
/** Patch centre in world metres, always snapped to a whole cell. */
|
|
2696
|
+
centerX: number;
|
|
2697
|
+
centerZ: number;
|
|
2698
|
+
/** Simulated seconds elapsed. */
|
|
2699
|
+
time: number;
|
|
2700
|
+
/** Bumped whenever a cell changes, so a texture upload can skip an unchanged frame. */
|
|
2701
|
+
version: number;
|
|
2702
|
+
constructor(options: IRippleFieldOptions);
|
|
2703
|
+
/** True while the point is inside the patch, `margin` metres in from its rim. */
|
|
2704
|
+
contains(x: number, z: number, margin?: number): boolean;
|
|
2705
|
+
/**
|
|
2706
|
+
* Move the patch so it covers the action. The grid shifts by whole cells and the newly exposed
|
|
2707
|
+
* band arrives flat, which is what the sponge rim had already damped it to.
|
|
2708
|
+
*/
|
|
2709
|
+
recenter(x: number, z: number): void;
|
|
2710
|
+
/**
|
|
2711
|
+
* Push the surface at a point: a Gaussian of the given radius, with the kernel corrected so the
|
|
2712
|
+
* disturbance injects no net volume. Without that correction every splash slowly raises the sea.
|
|
2713
|
+
* Returns false when the point is outside the patch.
|
|
2714
|
+
*/
|
|
2715
|
+
impulse(x: number, z: number, radius: number, amplitude: number, foam?: number): boolean;
|
|
2716
|
+
/** Lay down entrained air without claiming a pressure impulse happened. */
|
|
2717
|
+
depositFoam(x: number, z: number, radius: number, amount: number): boolean;
|
|
2718
|
+
/** Run whole fixed steps to consume `dt`, and report how many ran. */
|
|
2719
|
+
advance(dt: number): number;
|
|
2720
|
+
/** Disturbance height in metres at a world point, zero outside the patch. */
|
|
2721
|
+
heightAt(x: number, z: number): number;
|
|
2722
|
+
/** Foam density in 0..1 at a world point. */
|
|
2723
|
+
foamAt(x: number, z: number): number;
|
|
2724
|
+
/** Surface flow in metres per second at a world point, including `current`. */
|
|
2725
|
+
flowAt(x: number, z: number, out?: IRippleFieldFlow): IRippleFieldFlow;
|
|
2726
|
+
/** Total disturbance energy. Zero on an undisturbed patch; useful as a test oracle. */
|
|
2727
|
+
energy(): number;
|
|
2728
|
+
/** Flatten the patch and forget every disturbance. Position, options and time survive. */
|
|
2729
|
+
reset(): void;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
1534
2732
|
type WaveDirection = readonly [number, number] | {
|
|
1535
2733
|
readonly x: number;
|
|
1536
2734
|
readonly z?: number;
|
|
@@ -1597,7 +2795,21 @@ declare class WaveField {
|
|
|
1597
2795
|
constructor(options: IWaveFieldOptions);
|
|
1598
2796
|
/** Update the default graph clock. Explicit sample times remain available for fixed-step code. */
|
|
1599
2797
|
setTime(value: number): void;
|
|
2798
|
+
/**
|
|
2799
|
+
* Surface height and normal at a point. Allocates the result and its vector; a caller that wants
|
|
2800
|
+
* only the number should call `heightAt`, which is the same evaluation without either.
|
|
2801
|
+
*/
|
|
1600
2802
|
sample(x: number, z: number, time: number): IWaveFieldSample;
|
|
2803
|
+
/**
|
|
2804
|
+
* Surface height at a point, as a number.
|
|
2805
|
+
*
|
|
2806
|
+
* The scalar half of `sample`, and the same arithmetic in the same order, so the two agree
|
|
2807
|
+
* exactly. What it skips is everything only a normal needs: the domain warp's jacobian, the
|
|
2808
|
+
* cosine and slope of every wave, the normalisation, the result object and the `Vector3`. A
|
|
2809
|
+
* floating hull, a splash query or a whitewater height test asks this question thousands of times
|
|
2810
|
+
* a frame and throws the normal away every time.
|
|
2811
|
+
*/
|
|
2812
|
+
heightAt(x: number, z: number, time: number): number;
|
|
1601
2813
|
/** Surface height at a point, as a graph. The scalar half of what `sample` returns. */
|
|
1602
2814
|
heightNode(options?: IWaveFieldGraphOptions): Node<"float">;
|
|
1603
2815
|
/**
|
|
@@ -1617,17 +2829,53 @@ declare class WaveField {
|
|
|
1617
2829
|
displacementNode(timeNode?: three_webgpu.UniformNode<"float", number>): Node<"vec3">;
|
|
1618
2830
|
}
|
|
1619
2831
|
|
|
1620
|
-
/**
|
|
2832
|
+
/** What the mirrored pass costs: how big it is, how many of them, and how much of the world. */
|
|
1621
2833
|
interface IWaterReflectionOptions {
|
|
1622
2834
|
/**
|
|
1623
2835
|
* The mirrored pass's render target, as a fraction of the drawing buffer.
|
|
1624
2836
|
*
|
|
1625
|
-
*
|
|
1626
|
-
* whether a
|
|
2837
|
+
* How many *pixels* the second pass costs. Half is the usual answer. This is not on its own the
|
|
2838
|
+
* number that decides whether a surface is affordable, and a game that reads it that way will
|
|
2839
|
+
* measure no improvement and conclude its water is free: a scene with many objects is bound by
|
|
2840
|
+
* the draw calls the mirrored pass submits, not by its pixels, and those do not shrink with the
|
|
2841
|
+
* target. Measured on `sandbox/midway-open-pacific` at 1920x1080 on an nvidia/turing adapter with
|
|
2842
|
+
* 1,965 draws in the frame, halving this again — 0.5 to 0.25 — moved GPU p95 from 17.80 ms to
|
|
2843
|
+
* 17.58 ms. Removing the pass entirely moved it to 7.67 ms. `layers` is the number that mattered.
|
|
1627
2844
|
*/
|
|
1628
2845
|
readonly resolutionScale: number;
|
|
1629
2846
|
/** Whether this surface may appear in other reflectors' passes. Off is one pass; on is n². */
|
|
1630
2847
|
readonly bounces?: boolean;
|
|
2848
|
+
/**
|
|
2849
|
+
* Which layers the mirrored pass draws, as a three `Layers` mask. Omit to draw everything the
|
|
2850
|
+
* scene camera draws, which is the default and what a reflection means when nothing says
|
|
2851
|
+
* otherwise.
|
|
2852
|
+
*
|
|
2853
|
+
* This is how much *world* the second pass costs, and on a crowded scene it is the whole bill.
|
|
2854
|
+
* The mirrored pass is a second draw of everything, so a frame with sixty-eight aircraft in it
|
|
2855
|
+
* pays for sixty-eight aircraft twice — once where the player can see them and once in the water,
|
|
2856
|
+
* where they are a few pixels and half of them are behind the camera anyway. Put the big
|
|
2857
|
+
* silhouettes a player actually reads in the water on their own layer and name it here.
|
|
2858
|
+
*
|
|
2859
|
+
* The mask decides what appears in the mirror, so the game owns it: this only carries the number
|
|
2860
|
+
* through to the pass, and a game that omits it gets the whole world reflected as before.
|
|
2861
|
+
*/
|
|
2862
|
+
readonly layers?: number;
|
|
2863
|
+
/**
|
|
2864
|
+
* How often the mirrored pass redraws, in presented frames.
|
|
2865
|
+
*
|
|
2866
|
+
* The reflection is a second render of the world and on a crowded sea it is the largest single
|
|
2867
|
+
* item in the frame. It is also low-frequency: a swell, a hull and a wake read the same whether
|
|
2868
|
+
* the mirror was taken this frame or two frames ago, and at speed the eye cannot hold a reflected
|
|
2869
|
+
* silhouette still enough to notice it lag. `2` redraws every second frame and samples the
|
|
2870
|
+
* previous frame's target in between, halving the pass's cost; a higher number halves it again.
|
|
2871
|
+
*
|
|
2872
|
+
* Omit it — or pass `1` — and the pass redraws every frame, which is the shipping behaviour and
|
|
2873
|
+
* the default. The trade is temporal: while the camera moves, the reflection is up to
|
|
2874
|
+
* `refreshInterval - 1` frames behind the scene it mirrors. Fast camera motion can make a
|
|
2875
|
+
* reflected edge shimmer, and a moving object's reflection trails it. A still camera sees none of
|
|
2876
|
+
* that, so name it only where the measured pass dominates the frame.
|
|
2877
|
+
*/
|
|
2878
|
+
readonly refreshInterval?: number;
|
|
1631
2879
|
}
|
|
1632
2880
|
interface IWaterSurfaceOptions {
|
|
1633
2881
|
/** World-space height of the surface, in metres. The mirror plane, and where thickness is 0. */
|
|
@@ -1681,6 +2929,19 @@ declare class WaterSurface3D {
|
|
|
1681
2929
|
*/
|
|
1682
2930
|
readonly target: Object3D | undefined;
|
|
1683
2931
|
constructor(options: IWaterSurfaceOptions);
|
|
2932
|
+
/**
|
|
2933
|
+
* The camera the mirrored pass draws this surface with, for one scene camera.
|
|
2934
|
+
*
|
|
2935
|
+
* The pass mints one of these per scene camera, lazily, and reflects the camera through the
|
|
2936
|
+
* mirror plane each frame. It is exposed because `layers` is not the only thing a game may need
|
|
2937
|
+
* to say about the second draw — a near/far pair is the other — and because a reflection you
|
|
2938
|
+
* cannot inspect is a reflection you cannot cost. Undefined when this surface has no reflection.
|
|
2939
|
+
*/
|
|
2940
|
+
reflectionCameraFor(camera: Camera): Camera | undefined;
|
|
2941
|
+
/** What the mirrored pass draws, as the mask the game supplied. Undefined means everything. */
|
|
2942
|
+
readonly reflectionLayers: number | undefined;
|
|
2943
|
+
/** How often the mirrored pass redraws, in presented frames. `1` is every frame, the default. */
|
|
2944
|
+
readonly reflectionRefreshInterval: number;
|
|
1684
2945
|
/** The world-space height of the surface, in metres. */
|
|
1685
2946
|
get level(): number;
|
|
1686
2947
|
/** Move the surface — a tide, a sluice, a flooding room. The mirror plane follows. */
|
|
@@ -1872,6 +3133,213 @@ declare class GroundSnap {
|
|
|
1872
3133
|
audit(): number | null;
|
|
1873
3134
|
}
|
|
1874
3135
|
|
|
3136
|
+
/**
|
|
3137
|
+
* Force-integrated fixed-wing flight dynamics for a game-owned aircraft.
|
|
3138
|
+
*
|
|
3139
|
+
* The model integrates lift, drag, thrust and aerodynamic moments in SI units, `+Y` up, with the
|
|
3140
|
+
* nose down local `-Z`. Attitude is a quaternion, so there are no Euler clamps and no commanded
|
|
3141
|
+
* velocity vectors: the game writes controls, the model writes forces, and the aircraft keeps its
|
|
3142
|
+
* own momentum. Every number that decides how the aircraft looks or performs — mass, wing area,
|
|
3143
|
+
* engine power, inertia, control authority, wind, damage multipliers and payload — arrives from
|
|
3144
|
+
* the game. This module owns only the mechanism that turns those inputs into motion.
|
|
3145
|
+
*/
|
|
3146
|
+
interface IFlightVector3 {
|
|
3147
|
+
x: number;
|
|
3148
|
+
y: number;
|
|
3149
|
+
z: number;
|
|
3150
|
+
}
|
|
3151
|
+
interface IFlightQuaternion {
|
|
3152
|
+
x: number;
|
|
3153
|
+
y: number;
|
|
3154
|
+
z: number;
|
|
3155
|
+
w: number;
|
|
3156
|
+
}
|
|
3157
|
+
interface IFlightAxes {
|
|
3158
|
+
readonly r: IFlightVector3;
|
|
3159
|
+
readonly u: IFlightVector3;
|
|
3160
|
+
readonly f: IFlightVector3;
|
|
3161
|
+
}
|
|
3162
|
+
/** Per-aircraft constants. The game supplies these; the model ships no airframe of its own. */
|
|
3163
|
+
interface IAircraftAirframe {
|
|
3164
|
+
/** Empty mass, kg. */
|
|
3165
|
+
readonly dryMass: number;
|
|
3166
|
+
/** Mass of a full fuel load, kg. */
|
|
3167
|
+
readonly fuelMass: number;
|
|
3168
|
+
/** Reference wing area, m². */
|
|
3169
|
+
readonly wingArea: number;
|
|
3170
|
+
/** Wingspan, m. */
|
|
3171
|
+
readonly span: number;
|
|
3172
|
+
/** Mean aerodynamic chord, m. */
|
|
3173
|
+
readonly chord: number;
|
|
3174
|
+
/** Shaft power at full throttle, W. */
|
|
3175
|
+
readonly power: number;
|
|
3176
|
+
/** Propeller efficiency, 0–1. */
|
|
3177
|
+
readonly propEfficiency: number;
|
|
3178
|
+
/** Maximum static thrust, N. */
|
|
3179
|
+
readonly staticThrust: number;
|
|
3180
|
+
/** Pitch moment of inertia, kg·m². */
|
|
3181
|
+
readonly pitchInertia: number;
|
|
3182
|
+
/** Roll moment of inertia, kg·m². */
|
|
3183
|
+
readonly rollInertia: number;
|
|
3184
|
+
/** Yaw moment of inertia, kg·m². */
|
|
3185
|
+
readonly yawInertia: number;
|
|
3186
|
+
}
|
|
3187
|
+
/** Multipliers a game applies for damage, load or upgrades. 1 / 0 is the undamaged case. */
|
|
3188
|
+
interface IFlightModifiers {
|
|
3189
|
+
readonly power: number;
|
|
3190
|
+
readonly lift: number;
|
|
3191
|
+
readonly drag: number;
|
|
3192
|
+
readonly roll: number;
|
|
3193
|
+
readonly controls: number;
|
|
3194
|
+
}
|
|
3195
|
+
declare const NEUTRAL_FLIGHT_MODIFIERS: IFlightModifiers;
|
|
3196
|
+
/** One fixed step of pilot input. Every field is a dimensionless command in `[-1, 1]`. */
|
|
3197
|
+
interface IFlightControls {
|
|
3198
|
+
readonly turn?: number;
|
|
3199
|
+
readonly pitch?: number;
|
|
3200
|
+
readonly rudder?: number;
|
|
3201
|
+
readonly wheelBrake?: boolean;
|
|
3202
|
+
/** When true, stability assist is bypassed and the game commands the attitude directly. */
|
|
3203
|
+
readonly autopilot?: boolean;
|
|
3204
|
+
}
|
|
3205
|
+
/** The moving deck an aircraft launches from. */
|
|
3206
|
+
interface IFlightDeck {
|
|
3207
|
+
readonly x: number;
|
|
3208
|
+
readonly y?: number;
|
|
3209
|
+
readonly z: number;
|
|
3210
|
+
readonly heading: number;
|
|
3211
|
+
readonly speed: number;
|
|
3212
|
+
readonly length: number;
|
|
3213
|
+
readonly width: number;
|
|
3214
|
+
}
|
|
3215
|
+
interface IFlightState {
|
|
3216
|
+
x: number;
|
|
3217
|
+
y: number;
|
|
3218
|
+
z: number;
|
|
3219
|
+
vx: number;
|
|
3220
|
+
vy: number;
|
|
3221
|
+
vz: number;
|
|
3222
|
+
attitude?: IFlightQuaternion;
|
|
3223
|
+
heading: number;
|
|
3224
|
+
pitch: number;
|
|
3225
|
+
roll: number;
|
|
3226
|
+
rollRate: number;
|
|
3227
|
+
pitchRate: number;
|
|
3228
|
+
yawRate: number;
|
|
3229
|
+
speed: number;
|
|
3230
|
+
ias: number;
|
|
3231
|
+
groundSpeed: number;
|
|
3232
|
+
throttle: number;
|
|
3233
|
+
rpm: number;
|
|
3234
|
+
fuel: number;
|
|
3235
|
+
hp: number;
|
|
3236
|
+
engineCut: boolean;
|
|
3237
|
+
gear: boolean;
|
|
3238
|
+
brakes: boolean;
|
|
3239
|
+
gearPos: number;
|
|
3240
|
+
brakePos: number;
|
|
3241
|
+
flapPos: number;
|
|
3242
|
+
flaps: number;
|
|
3243
|
+
aileron: number;
|
|
3244
|
+
elevator: number;
|
|
3245
|
+
rudder: number;
|
|
3246
|
+
controlAileron: number;
|
|
3247
|
+
assist: boolean;
|
|
3248
|
+
trim: number;
|
|
3249
|
+
gforce: number;
|
|
3250
|
+
aoa: number;
|
|
3251
|
+
beta: number;
|
|
3252
|
+
stall: number;
|
|
3253
|
+
/** Mass of everything under the wings, kg. The game writes it as stores are released. */
|
|
3254
|
+
payloadMass: number;
|
|
3255
|
+
/** Extra flat-plate drag coefficient from external stores. */
|
|
3256
|
+
payloadDrag: number;
|
|
3257
|
+
flightTime: number;
|
|
3258
|
+
lift: number;
|
|
3259
|
+
drag: number;
|
|
3260
|
+
thrust: number;
|
|
3261
|
+
mass: number;
|
|
3262
|
+
deckSpeed?: number;
|
|
3263
|
+
deckLateral?: number;
|
|
3264
|
+
deckOffset?: number;
|
|
3265
|
+
chocks?: boolean;
|
|
3266
|
+
}
|
|
3267
|
+
interface IFlightEnvironment {
|
|
3268
|
+
readonly airframe: IAircraftAirframe;
|
|
3269
|
+
readonly wind?: IFlightVector3;
|
|
3270
|
+
readonly modifiers?: IFlightModifiers;
|
|
3271
|
+
readonly gravity?: number;
|
|
3272
|
+
/** Height of the carrier deck surface above the water, m. */
|
|
3273
|
+
readonly deckHeight?: number;
|
|
3274
|
+
}
|
|
3275
|
+
interface IFlightForces {
|
|
3276
|
+
readonly x: number;
|
|
3277
|
+
readonly y: number;
|
|
3278
|
+
readonly z: number;
|
|
3279
|
+
readonly normalLoad: number;
|
|
3280
|
+
readonly lift: number;
|
|
3281
|
+
readonly drag: number;
|
|
3282
|
+
readonly thrust: number;
|
|
3283
|
+
readonly alpha: number;
|
|
3284
|
+
readonly beta: number;
|
|
3285
|
+
readonly airspeed: number;
|
|
3286
|
+
readonly density: number;
|
|
3287
|
+
readonly mass: number;
|
|
3288
|
+
readonly qs: number;
|
|
3289
|
+
readonly axes: IFlightAxes;
|
|
3290
|
+
readonly cl: number;
|
|
3291
|
+
readonly cd: number;
|
|
3292
|
+
readonly stall: number;
|
|
3293
|
+
readonly critical: number;
|
|
3294
|
+
}
|
|
3295
|
+
/** ISA air density at altitude `y` metres, kg/m³. */
|
|
3296
|
+
declare function airDensity(y: number): number;
|
|
3297
|
+
/** Current mass of the aircraft from its empty mass, fuel load and game-written payload. */
|
|
3298
|
+
declare function aircraftMass(state: IFlightState, airframe: IAircraftAirframe): number;
|
|
3299
|
+
/** Lift and drag coefficients for an angle of attack and the deployed high-lift devices. */
|
|
3300
|
+
declare function aerodynamicCoefficients(alpha: number, flaps?: number, gear?: number, brakes?: number): {
|
|
3301
|
+
cl: number;
|
|
3302
|
+
cd: number;
|
|
3303
|
+
stall: number;
|
|
3304
|
+
critical: number;
|
|
3305
|
+
};
|
|
3306
|
+
/** Write a heading/pitch/roll pose into the body quaternion. Nose down local `-Z`. */
|
|
3307
|
+
declare function setAttitude(state: IFlightState, heading?: number, pitch?: number, roll?: number): IFlightQuaternion;
|
|
3308
|
+
/** Right, up and forward unit vectors of the body frame. */
|
|
3309
|
+
declare function attitudeAxes(state: IFlightState): IFlightAxes;
|
|
3310
|
+
/** Height of the gear contact point below the body origin at the current pitch. */
|
|
3311
|
+
declare function gearClearance(state: IFlightState): number;
|
|
3312
|
+
interface IFlightModelOptions<TState extends IFlightState = IFlightState> extends IFlightEnvironment {
|
|
3313
|
+
/** The game's own aircraft object, extended with whatever else the game needs on it. */
|
|
3314
|
+
readonly state: TState;
|
|
3315
|
+
}
|
|
3316
|
+
/**
|
|
3317
|
+
* One aircraft's dynamics: a thin owner of a game-authored state object plus its environment.
|
|
3318
|
+
*
|
|
3319
|
+
* The step record copies the options' own fields once, at construction — so `airframe`, `wind`,
|
|
3320
|
+
* `gravity` and `deckHeight` are read from the objects those fields reference, which stay live,
|
|
3321
|
+
* but replacing a field on the options object afterwards is not observed. Change a model's airframe
|
|
3322
|
+
* or deck by building it again, as a game that binds aircraft to carriers already does.
|
|
3323
|
+
*
|
|
3324
|
+
* @example
|
|
3325
|
+
* const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
|
|
3326
|
+
* model.setAttitude(0, 0.2, 0);
|
|
3327
|
+
* model.step(1 / 60, { turn: -1, pitch: 0.4 });
|
|
3328
|
+
*/
|
|
3329
|
+
declare class FlightModel<TState extends IFlightState = IFlightState> {
|
|
3330
|
+
#private;
|
|
3331
|
+
readonly state: TState;
|
|
3332
|
+
readonly environment: IFlightEnvironment;
|
|
3333
|
+
constructor(options: IFlightModelOptions<TState>);
|
|
3334
|
+
setAttitude(heading?: number, pitch?: number, roll?: number): void;
|
|
3335
|
+
reset(): void;
|
|
3336
|
+
axes(): IFlightAxes;
|
|
3337
|
+
gearClearance(): number;
|
|
3338
|
+
forces(modifiers?: IFlightModifiers): IFlightForces;
|
|
3339
|
+
step(dt: number, controls: IFlightControls, modifiers?: IFlightModifiers): void;
|
|
3340
|
+
stepDeck(deck: IFlightDeck, dt: number, controls: IFlightControls, modifiers?: IFlightModifiers): "liftoff" | "overrun" | null;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
1875
3343
|
type ThreePoseVector = readonly [number, number, number];
|
|
1876
3344
|
type ThreePoseQuaternion = readonly [number, number, number, number];
|
|
1877
3345
|
interface IThreePoseBounds {
|
|
@@ -1917,21 +3385,6 @@ declare function measureThreePose(object: Object3D, options?: IMeasureThreePoseO
|
|
|
1917
3385
|
*/
|
|
1918
3386
|
declare function posedBounds(root: Object3D, meshes?: readonly Object3D[]): IThreePoseBounds;
|
|
1919
3387
|
|
|
1920
|
-
type PlatformRuntime = "web" | "native";
|
|
1921
|
-
type PlatformOS = "android" | "ios" | "linux" | "macos" | "windows" | "unknown";
|
|
1922
|
-
type PlatformFormFactor = "mobile" | "desktop" | "unknown";
|
|
1923
|
-
interface IPlatformInfo {
|
|
1924
|
-
readonly runtime: PlatformRuntime;
|
|
1925
|
-
readonly os: PlatformOS;
|
|
1926
|
-
readonly formFactor: PlatformFormFactor;
|
|
1927
|
-
readonly maxTouchPoints: number;
|
|
1928
|
-
}
|
|
1929
|
-
declare function getPlatform(): Readonly<IPlatformInfo>;
|
|
1930
|
-
declare function isWeb(): boolean;
|
|
1931
|
-
declare function isNative(): boolean;
|
|
1932
|
-
declare function isMobile(): boolean;
|
|
1933
|
-
declare function isTouchscreenAvailable(): boolean;
|
|
1934
|
-
|
|
1935
3388
|
type ReplayPointer = readonly [number, number, number, number, number];
|
|
1936
3389
|
interface IReplayRecordingSample {
|
|
1937
3390
|
readonly keys: readonly string[];
|
|
@@ -2189,6 +3642,6 @@ declare function boneLengthDeviations(root: Object3D, bind: IBoneLengthSnapshot,
|
|
|
2189
3642
|
* unavoidable here — core is bundled for browsers and cannot read `package.json` at runtime — so
|
|
2190
3643
|
* the spec now asserts this equals the manifest instead of asserting a number somebody typed.
|
|
2191
3644
|
*/
|
|
2192
|
-
declare const version = "0.3.
|
|
3645
|
+
declare const version = "0.3.4";
|
|
2193
3646
|
|
|
2194
|
-
export { ATLAS_PADDING, ATMOSPHERE_LUT_RESOLUTIONS, AnimationPlayer, Atmosphere, type AtmosphereDirection, AtmosphereLuts, type AtmosphereRgb, Billboard3D, type BillboardLockAxis, CameraShake, type CameraShakeCurve, ClusteredBatch, ClusteredMesh, FluidField2D, GPUParticles3D, GPUSceneBVH, type GPUSceneBVHTraceFunction, GroundSnap, type IAddInSlicesOptions, type IAddInSlicesProgress, type IAddInSlicesReport, type IAnimationPlayOptions, type IAnimationPlayerOptions, type IAtmosphereLutResolution, type IAtmosphereLutResolutions, type IAtmosphereOptions, type IAtmosphereParameterPatch, type IAtmosphereParameters, type IAtmosphereScenePass, type IBillboard3DOptions, type IBoneContactReport, type IBoneLengthDeviation, type IBoneLengthDeviationReport, type IBoneLengthDeviationsOptions, type IBoneLengthSnapshot, type IBonePoseError, type ICameraShakeOffset, type ICameraShakeOptions, type IClipBindingReport, type IClipCoverageReport, type IClipPoseErrorOptions, type IClipPoseErrorReport, type IClipPoseSubject, type IClipTrackBinding, type IClusterTable, type IClusteredBatchBuildOptions, type IClusteredBatchOptions, type IClusteredMeshOptions, type IClusteredPlacement, IComputeDriven$1 as IComputeDriven, type IFluidFieldOptions, type IFluidFieldSampler, type IFluidFieldVector2, IGPUReadbackSample, type IGPUSceneBVHMaterialGroup, type IGPUSceneBVHOptions, IGamePluginHooks, IGamePluginRuntime, type IGroundSnapOptions, type IInstancedBatchBuildOptions, type IInstancedBatchOptions, type IInstancedPlacement, type ILoadAllOptions, type ILoadAllProgress, type IMeasureThreePoseOptions, type IMergePart, type IMergePartsOptions, type INormaliseToMetresOptions, type IPathFollow3DOptions, type IPathFollow3DProjection, type IPathFollow3DSample, type
|
|
3647
|
+
export { ATLAS_PADDING, ATMOSPHERE_LUT_RESOLUTIONS, AnimationPlayer, Atmosphere, type AtmosphereDirection, AtmosphereLuts, type AtmosphereRgb, Billboard3D, type BillboardLockAxis, CameraShake, type CameraShakeCurve, ClusteredBatch, ClusteredMesh, Daylight, FlightModel, FluidField2D, FramePassKind, GPUParticles3D, GPUSceneBVH, type GPUSceneBVHTraceFunction, GroundSnap, type IAddInSlicesOptions, type IAddInSlicesProgress, type IAddInSlicesReport, type IAircraftAirframe, type IAnimationPlayOptions, type IAnimationPlayerOptions, type IAtmosphereLutResolution, type IAtmosphereLutResolutions, type IAtmosphereOptions, type IAtmosphereParameterPatch, type IAtmosphereParameters, type IAtmosphereScenePass, type IBillboard3DOptions, type IBoneContactReport, type IBoneLengthDeviation, type IBoneLengthDeviationReport, type IBoneLengthDeviationsOptions, type IBoneLengthSnapshot, type IBonePoseError, type ICameraShakeOffset, type ICameraShakeOptions, type IClipBindingReport, type IClipCoverageReport, type IClipPoseErrorOptions, type IClipPoseErrorReport, type IClipPoseSubject, type IClipTrackBinding, type IClusterTable, type IClusteredBatchBuildOptions, type IClusteredBatchOptions, type IClusteredMeshOptions, type IClusteredPlacement, IComputeDriven$1 as IComputeDriven, type IDaylightOptions, type IFlightAxes, type IFlightControls, type IFlightDeck, type IFlightEnvironment, type IFlightForces, type IFlightModelOptions, type IFlightModifiers, type IFlightQuaternion, type IFlightState, type IFlightVector3, type IFluidFieldOptions, type IFluidFieldSampler, type IFluidFieldVector2, IFrameBudgetWindow, IGPUReadbackSample, type IGPUSceneBVHMaterialGroup, type IGPUSceneBVHOptions, IGamePluginHooks, IGamePluginRuntime, type IGroundSnapOptions, type IInstancedBatchBuildOptions, type IInstancedBatchOptions, type IInstancedPlacement, type ILaunchFailure, type ILoadAllOptions, type ILoadAllProgress, type IMatrixWorldReport, type IMeasureThreePoseOptions, type IMergeByMaterialOptions, type IMergePart, type IMergePartsOptions, type INormaliseToMetresOptions, type IPathFollow3DOptions, type IPathFollow3DProjection, type IPathFollow3DSample, type IProbeVolumeBakeProgress, type IProbeVolumeCoefficient, type IProbeVolumeObservation, type IProbeVolumeOptions, type IReplayOptions, type IReplayRecording, type IReplayRecordingSample, type IResolvedAtmosphereParameters, type IRippleFieldFlow, type IRippleFieldOptions, type ISceneShape, type ISceneWarning, type ISkeletalMesh3DOptions, type ISoftBody3DOptions, type ISoftBodyCollision, type ISolarPosition, type ISolarPositionInput, type ISpanProbeTarget, type ISpanSummary, type ISpanWindow, type ISpectralOceanCascade, type ISpectralOceanHeight, type ISpectralOceanOptions, type ISpriteAnimator3DOptions, type ISpriteFrame3D, type IStaticTransformCensus, type IStrideReport, type IThreePoseBounds, type IThreePoseMeasurement, type ITracerPool3DOptions, type ITracerSpawnOptions, type IValidationReport, type IVirtualShadowOptions, type IVirtualShadowStats, type IWaterReflectionOptions, type IWaterSurfaceOptions, type IWaveFieldDomainWarp, type IWaveFieldGraphOptions, type IWaveFieldOptions, type IWaveFieldSample, type IWaveFieldWave, InstancedBatch, LUT_RESOLUTIONS, type LaunchFailureKind, type MatrixWorldMode, MatrixWorldPass, NEUTRAL_FLIGHT_MODIFIERS, type NormaliseAxis, PROBE_VOLUME_MARKER, PathFollow3D, ProbeVolume, type ProbeVolumeDensity, RENDERLIST_VALIDATE_FLAG, RENDERLIST_VALIDATE_MARKER, type Recording, RenderListValidator, type ReplayPointer, RippleField, SCENE_WARNING_MARKER, SPANS, SPANS_FLAG, SPANS_MARKER, SPAN_COUNT, SPAN_NAMES, STATIC_TRANSFORM_MARKER, SkeletalMesh3D, SoftBody3D, type SpanId, SpanRecorder, SpectralOcean, SpriteAnimator3D, type SpritePlaybackMode, type ThreePoseQuaternion, type ThreePoseVector, TracerPool3D, VIRTUAL_SHADOW_CASTER_LAYER, VIRTUAL_SHADOW_MARKER, VIRTUAL_SHADOW_MOVER_LAYER, VIRTUAL_SHADOW_WIDE_CASTER_LAYER, VirtualShadowNode, WaterSurface3D, type WaveDirection, WaveField, addInSlices, addSpan, aerodynamicCoefficients, airDensity, aircraftMass, alwaysRender, attachToBone, attitudeAxes, baseGeometryOf, beginSpan, boneContact, boneLengthDeviations, boneLengths, bvhIntersectFirstHit, clipBoneCoverage, clipPoseError, clipTrackBindings, createReplayDriver, debugFlag, describeSceneShape, describeSceneWarning, directionFromSolarPosition, directionalTransmittance, displayPeriodMs, endSpan, exposeDebug, formatSceneWarning, formatSpansWindow, formatValidationReport, gearClearance, installSpanProbes, invalidateStatic, isStatic, loadAll, lodPixelScale, markStatic, measureThreePose, mergeByMaterial, mergeParts, normaliseToMetres, onLaunchFailure, parseReplayRecording, posedBounds, rayStruct, readProbeVolumeObservation, readVirtualShadowMarker, refreshStaticTransforms, renderListValidationRequested, replay, resetStaticTransforms, resolveAtmosphereLutResolutions, resolveAtmosphereParameters, sceneWarning, setAttitude, setSpanRecorder, skeletonBones, softCircleDataTexture, solarPosition, solarPositionAt, spanNow, spanRecorder, spansRequested, staticTransformCensus, unmarkStatic, updateAtmosphereParameters, updateClusteredMeshes, updateModelLods, validateWorldMatrices, version, zenithTransmittance };
|