@threenative/core 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/capabilities.json +1881 -131
- package/dist/assets-kyoF7JlJ.d.ts +103 -0
- package/dist/{audio-Dp2mXpD3.d.ts → audio-BFiGneTL.d.ts} +62 -0
- package/dist/canvas-layer-BLVijiUJ.d.ts +62 -0
- package/dist/{game-CYIaKhgl.d.ts → game-XGrTzapq.d.ts} +350 -164
- package/dist/gpu-readback-D2iRvoe9.d.ts +112 -0
- package/dist/hot.d.ts +5 -3
- package/dist/hot.js +5 -1
- package/dist/index.d.ts +813 -143
- package/dist/index.js +4548 -941
- package/dist/net.d.ts +65 -0
- package/dist/net.js +643 -0
- package/dist/playtest.d.ts +29 -5
- package/dist/playtest.js +181 -35
- package/dist/react.d.ts +4 -2
- package/dist/{canvas-layer-CtrZHgIh.d.ts → renderer-C6hqZpoG.d.ts} +237 -75
- package/dist/world.d.ts +203 -4
- package/dist/world.js +2536 -25
- package/gpl/LICENSE.GPL +117 -0
- package/gpl/convert.py +192 -0
- package/gpl/recipes/_common.py +169 -0
- package/gpl/recipes/bake_ao.py +111 -0
- package/gpl/recipes/decimate.py +64 -0
- package/gpl/recipes/retarget.py +131 -0
- package/gpl/recipes/unwrap.py +71 -0
- package/mcp/blender-server.mjs +632 -0
- package/mcp/blender.mjs +27 -0
- package/mcp/engine-server.mjs +271 -23
- package/mcp/engine.mjs +15 -7
- package/mcp/install.d.mts +37 -0
- package/mcp/install.mjs +94 -26
- package/mcp/servers.d.mts +34 -0
- package/mcp/servers.mjs +110 -9
- package/package.json +36 -8
- package/patches/three@0.185.1.patch +249 -14
- package/scripts/ensure-mcp.mjs +20 -13
- package/scripts/bundle-engine-mcp.mjs +0 -15
- package/scripts/generate-version.mjs +0 -13
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import * as three from 'three';
|
|
2
|
-
import { AnimationMixer,
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
import {
|
|
7
|
-
export {
|
|
2
|
+
import { AnimationMixer, Object3D, AnimationClip, Camera, Vector3, Group, Matrix4, Mesh, BufferGeometry, Material, InstancedMesh, ColorRepresentation, Box3, Scene, DirectionalLight, Sprite, DataTexture, CatmullRomCurve3, Texture } from 'three';
|
|
3
|
+
export { I as IAssetLoader, a as IAssetLoaderOptions, c as createAssetLoader, r as reconcileMirroredClips } from './assets-kyoF7JlJ.js';
|
|
4
|
+
export { A as AudioBus, I as IAudioBusOptions, b as IAudioPlayOptions } from './audio-BFiGneTL.js';
|
|
5
|
+
export { C as CanvasLayer } from './canvas-layer-BLVijiUJ.js';
|
|
6
|
+
import { a as IGamePluginRuntime, b as IGamePluginHooks } from './game-XGrTzapq.js';
|
|
7
|
+
export { A as AfterPhysicsCallback, C as ContextMenuPolicy, c as ICtx, I as IGame, d as IGameObservationContribution, e as IGameObservationSampleRequest, f as IGamePlatformSource, g as IInputAction, h as IInputGamepad, i as IPointerDragHandle, j as IPointerEvent3D, k as IPointerEvents3D, l as IPointerEvents3DOptions, m as IPointerEvents3DPicker, n as IPointerState, o as IRandom, p as IRawInputPointer, q as IRawInputPointerEdge, r as IRawInputState, s as IRaycastOptions, t as IScenePickerOptions, u as IThreeNativeAudioConfig, v as IThreeNativeAudioLoop, w as IThreeNativeAudioOverride, x as IThreeNativeAudioSpectrum, y as IThreeNativeBootSplash, z as IThreeNativeConfig, B as IThreeNativeIconVariants, D as IThreeNativeTexturesConfig, E as ITweenOptions, F as IWarmUpCacheOptions, G as IWarmUpObservation, H as IWarmUpOptions, J as IWarmUpProgress, K as IWarmUpRenderer, L as IWarmUpReport, M as InputBindings, N as InputPlatformSource, P as PointerEvent3DListener, O as PointerEvent3DType, Q as PointerEvents3D, S as Scene, R as SceneFrame, T as ScenePicker, U as ScheduleHandle, V as Scheduler, W as ThreeNativeBackgroundMode, X as ThreeNativeOrientation, Y as ThreeNativeUiRenderer, Z as WarmUpCacheStatus, _ as WarmUpObservationStatus, $ as afterPhysics, a0 as createRandom, a1 as defineGame, a2 as warmUpScene } from './game-XGrTzapq.js';
|
|
8
8
|
import * as three_webgpu from 'three/webgpu';
|
|
9
|
-
import { StorageTexture, ComputeNode, UniformNode, Node, TextureNode, NodeMaterial, StorageBufferNode, StructTypeNode, Data3DTexture, SpriteNodeMaterial, StorageTextureNode } from 'three/webgpu';
|
|
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-C6hqZpoG.js';
|
|
11
|
+
export { D as DEFAULT_PIPELINE_CENSUS_LIMIT, F as FRAME_BUDGET_MARKER, a as FRAME_BUDGET_PHASES, b as FRAME_HITCH_MARKER, c as FrameBudget, d as FrameBudgetPhase, e as IFrameBudgetOptions, f as IFrameBudgetSummary, g as IFrameBudgetWindow, h as IFramePhaseSample, i as IPipelineCensus, j as IPipelineCensusCounts, k as IPipelineCensusEvent, l as IPipelineCensusOptions, m as IPipelineProvenance, n as IPipelineShaderObservation, o as IRenderChainApplied, p as IRenderChainBudgetWindow, q as IRenderChainDroppedStage, r as IRenderChainOptions, s as IRenderChainRenderer, t as IRenderChainRequest, u as IRenderChainStage, v as IRenderChainStageContext, w as IRenderChainVelocityMeasurement, x as IRenderChainVelocityReport, y as IRenderChainVelocityRequest, z as IRenderChainVelocityResult, A as IVelocityRenderPass, P as PIPELINE_CENSUS_CAPABILITY, B as PIPELINE_CENSUS_VERSION, C as PipelineCensus, E as PipelineCensusMode, G as PipelineCensusStatus, R as RENDER_CHAIN_MARKER, H as RENDER_CHAIN_STAGE_ORDER, J as RENDER_CHAIN_TIERS, K as RenderChain, L as RenderChainSource, M as RenderChainStageId, N as RenderChainStageName, O as RenderChainTier, Q as RenderChainTierRequest, S as RenderChainVelocitySource, V as VELOCITY_OUTPUT_NAME, T as VELOCITY_PREVIOUS_BONE_MATRICES, U as VELOCITY_PREVIOUS_INSTANCE_MATRICES, W as VELOCITY_PREVIOUS_WORLD_MATRIX, X as VelocityTracker, Y as createPipelineCensus, Z as ensureVelocityOutput, _ as prewarm, $ as readRenderChainObservation, a0 as readRenderChainReport, a1 as readVelocityPreviousBoneMatrices, a2 as readVelocityPreviousMatrices, a3 as readVelocityPreviousWorldMatrix, a4 as velocityTexture, a5 as withVelocityContext } from './renderer-C6hqZpoG.js';
|
|
12
|
+
import { I as IComputeDriven$1, a as IGPUReadbackSample } from './gpu-readback-D2iRvoe9.js';
|
|
13
|
+
export { C as ComputeDrivenRegistry, G as GPUReadback, b as IGPUReadbackOptions } from './gpu-readback-D2iRvoe9.js';
|
|
10
14
|
import { Node as Node$1 } from 'three/src/nodes/Nodes.js';
|
|
11
15
|
import 'zustand/vanilla';
|
|
12
16
|
|
|
13
17
|
interface IAnimationPlayerOptions {
|
|
14
18
|
readonly clips: readonly AnimationClip[];
|
|
15
19
|
readonly root: Object3D;
|
|
20
|
+
readonly requiredClips?: readonly string[] | Readonly<Record<string, string>>;
|
|
16
21
|
/**
|
|
17
22
|
* Match a travelling clip's playback rate to the ground the body actually covers.
|
|
18
23
|
*
|
|
@@ -49,6 +54,15 @@ interface IStrideReport {
|
|
|
49
54
|
readonly synced: boolean;
|
|
50
55
|
/** True when a rate was measured and deliberately not applied. */
|
|
51
56
|
readonly overridden: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* True when the clip carries no root motion and its stride was read off the feet instead.
|
|
59
|
+
*
|
|
60
|
+
* `synced: false, overridden: false` is what an idle reports, so without this a game whose walk
|
|
61
|
+
* cycle is silently going unmatched cannot tell itself apart from one with nothing to match. An
|
|
62
|
+
* in-place clip that also yields no foot plant reports `inPlace: true` with a zero
|
|
63
|
+
* `clipGroundSpeed`, which names the asset as the thing to fix.
|
|
64
|
+
*/
|
|
65
|
+
readonly inPlace: boolean;
|
|
52
66
|
}
|
|
53
67
|
interface IAnimationPlayOptions {
|
|
54
68
|
readonly fade?: number;
|
|
@@ -61,6 +75,7 @@ interface IAnimationPlayOptions {
|
|
|
61
75
|
declare class AnimationPlayer {
|
|
62
76
|
#private;
|
|
63
77
|
readonly mixer: AnimationMixer;
|
|
78
|
+
readonly root: Object3D;
|
|
64
79
|
constructor(options: IAnimationPlayerOptions);
|
|
65
80
|
get current(): string | undefined;
|
|
66
81
|
get advancedFrames(): number;
|
|
@@ -82,6 +97,33 @@ declare class AnimationPlayer {
|
|
|
82
97
|
dispose(): void;
|
|
83
98
|
}
|
|
84
99
|
|
|
100
|
+
type NormaliseAxis = "height" | "longest";
|
|
101
|
+
interface INormaliseToMetresOptions {
|
|
102
|
+
readonly metres: number;
|
|
103
|
+
readonly axis: NormaliseAxis;
|
|
104
|
+
/** Crown bone to use for a skinned height measurement. Defaults to a named/highest bone. */
|
|
105
|
+
readonly top?: Object3D | string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Scale an asset to a real-world size and return the factor applied.
|
|
109
|
+
*
|
|
110
|
+
* Height for a skinned asset comes from its crown bone, not its bind-pose Box3. Longest-axis
|
|
111
|
+
* normalization remains a geometry measurement because it is used for rigid props and weapons.
|
|
112
|
+
*/
|
|
113
|
+
declare function normaliseToMetres(object: Object3D, options: INormaliseToMetresOptions): number;
|
|
114
|
+
|
|
115
|
+
interface ISkeletalMesh3DOptions {
|
|
116
|
+
readonly source: Object3D;
|
|
117
|
+
readonly clips?: readonly AnimationClip[];
|
|
118
|
+
readonly requiredClips?: readonly string[] | Readonly<Record<string, string>>;
|
|
119
|
+
readonly size?: INormaliseToMetresOptions;
|
|
120
|
+
readonly strideRoot?: Object3D;
|
|
121
|
+
readonly strideSync?: boolean;
|
|
122
|
+
}
|
|
123
|
+
declare class SkeletalMesh3D extends AnimationPlayer {
|
|
124
|
+
constructor(options: ISkeletalMesh3DOptions);
|
|
125
|
+
}
|
|
126
|
+
|
|
85
127
|
type BillboardLockAxis = "x" | "y" | "z";
|
|
86
128
|
interface IBillboard3DOptions {
|
|
87
129
|
/** Camera whose view direction or position the object follows. */
|
|
@@ -216,6 +258,7 @@ declare function zenithTransmittance(parameters: IAtmosphereParameters | IResolv
|
|
|
216
258
|
declare function directionalTransmittance(parameters: IAtmosphereParameters | IResolvedAtmosphereParameters, direction: Vector3): Vector3;
|
|
217
259
|
/** Calculate solar elevation and azimuth from time, latitude, and longitude.
|
|
218
260
|
* @situation move a sun across a real day at a game's latitude and longitude
|
|
261
|
+
* @situation run a day and night cycle over the game's sky
|
|
219
262
|
* @constraint dates are interpreted as UTC unless utcOffset is supplied; no fixed sun direction is assumed
|
|
220
263
|
* @constraint pass a mutable { azimuth, elevation } target to reuse the result object in a steady frame loop
|
|
221
264
|
* @example const sun = solarPosition({ date, latitude: 49.28, longitude: -123.12, utcOffset: -8 });
|
|
@@ -277,7 +320,7 @@ declare class AtmosphereLuts {
|
|
|
277
320
|
type IAtmosphereParametersLike = IResolvedAtmosphereParameters | IAtmosphereParameters;
|
|
278
321
|
|
|
279
322
|
/** The structural contract consumed by the compute registry from PRD-242. */
|
|
280
|
-
interface IComputeDriven
|
|
323
|
+
interface IComputeDriven {
|
|
281
324
|
readonly warmupNodes: readonly unknown[];
|
|
282
325
|
attachRenderer(renderer: IRendererLike): void;
|
|
283
326
|
readonly processCadence?: "fixed" | "render";
|
|
@@ -299,12 +342,13 @@ type AtmosphereDirection = Vector3 | readonly [number, number, number] | Node<"v
|
|
|
299
342
|
* those, and the same object remains useful when a game supplies a completely different look.
|
|
300
343
|
* @situation render a sunrise that changes as time and place change
|
|
301
344
|
* @situation add distance haze from the depth of a scene pass
|
|
345
|
+
* @alias bright sky saturated green platforms
|
|
302
346
|
* @constraint supply rayleigh, mie, ozone, planetRadius, and atmosphereRadius; there is no Earth fallback
|
|
303
347
|
* @constraint the game creates the sky object, surface, and sun from the returned nodes
|
|
304
348
|
* @example const atmosphere = new Atmosphere({ rayleigh, mie, ozone, planetRadius, atmosphereRadius });
|
|
305
349
|
* ctx.add(atmosphere);
|
|
306
350
|
*/
|
|
307
|
-
declare class Atmosphere extends Group implements IComputeDriven
|
|
351
|
+
declare class Atmosphere extends Group implements IComputeDriven {
|
|
308
352
|
#private;
|
|
309
353
|
readonly luts: AtmosphereLuts;
|
|
310
354
|
constructor(options: IAtmosphereOptions);
|
|
@@ -341,114 +385,6 @@ declare class Atmosphere extends Group implements IComputeDriven$1 {
|
|
|
341
385
|
*/
|
|
342
386
|
declare function solarPositionAt(date: Date | string, latitude: number, longitude: number): ISolarPosition;
|
|
343
387
|
|
|
344
|
-
/**
|
|
345
|
-
* The lifecycle contract for a game-owned GPU simulation.
|
|
346
|
-
*
|
|
347
|
-
* A compute-driven object owns its kernels, buffers, and appearance. The framework only attaches
|
|
348
|
-
* the active renderer, warms the kernels before the world is shown, dispatches process calls at
|
|
349
|
-
* the object's declared cadence, and releases the object when its scene ends.
|
|
350
|
-
*/
|
|
351
|
-
interface IComputeDriven {
|
|
352
|
-
/** Kernels to compile before the world is shown. Read once, at attach. */
|
|
353
|
-
readonly warmupNodes: readonly unknown[];
|
|
354
|
-
attachRenderer(renderer: IRendererLike): void;
|
|
355
|
-
/**
|
|
356
|
-
* The loop phase that dispatches `process`. Defaults to fixed-step; render cadence preserves the
|
|
357
|
-
* existing behavior of consumers whose simulation is intentionally tied to presentation.
|
|
358
|
-
*/
|
|
359
|
-
readonly processCadence?: "fixed" | "render";
|
|
360
|
-
/** Dispatched once per fixed step, in scene-add order unless render cadence is declared. */
|
|
361
|
-
process(renderer: IRendererLike): void;
|
|
362
|
-
detach(): void;
|
|
363
|
-
readonly released: boolean;
|
|
364
|
-
}
|
|
365
|
-
/** The ordered registry used by the game loop for all compute-driven scene objects. */
|
|
366
|
-
declare class ComputeDrivenRegistry {
|
|
367
|
-
#private;
|
|
368
|
-
get size(): number;
|
|
369
|
-
/** Attach and remember one object. Re-adding the same object is idempotent. */
|
|
370
|
-
add(object: Object3D & IComputeDriven, renderer: IRendererLike): void;
|
|
371
|
-
/** Release one object without disturbing the order of the remaining objects. */
|
|
372
|
-
remove(driven: IComputeDriven): void;
|
|
373
|
-
/** Kernels in the same order as their objects were added to the scene. */
|
|
374
|
-
get warmupNodes(): readonly unknown[];
|
|
375
|
-
/** Dispatch fixed-step objects once; detached scene children are released before dispatch. */
|
|
376
|
-
process(renderer: IRendererLike): void;
|
|
377
|
-
/** Dispatch render-cadence objects once; detached scene children are released before dispatch. */
|
|
378
|
-
processRender(renderer: IRendererLike): void;
|
|
379
|
-
/** Release every registered object, continuing after a failure so no resource is stranded. */
|
|
380
|
-
clear(): void;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
interface IGPUReadbackOptions {
|
|
384
|
-
/** The GPU storage attribute to copy — a TSL storage node's `.value`. */
|
|
385
|
-
readonly attribute: unknown;
|
|
386
|
-
/**
|
|
387
|
-
* Frames between readback requests. `1` asks every frame; larger values throttle.
|
|
388
|
-
*
|
|
389
|
-
* A copy off the GPU costs a queue submission and a mapped buffer, so a game reading a field it
|
|
390
|
-
* only consults for physics asks for it every few frames and pays the staleness instead.
|
|
391
|
-
*/
|
|
392
|
-
readonly everyFrames: number;
|
|
393
|
-
}
|
|
394
|
-
/** A landed copy of GPU memory, with the age of the frame that produced it. */
|
|
395
|
-
interface IGPUReadbackSample {
|
|
396
|
-
readonly data: Float32Array;
|
|
397
|
-
/**
|
|
398
|
-
* Frames between the frame whose GPU state these bytes hold and the frame reading them.
|
|
399
|
-
*
|
|
400
|
-
* This is the number a caller must not be allowed to ignore. A buoyancy solver that treats a
|
|
401
|
-
* 4-frame-old surface as this frame's surface floats a hull through the water it is drawn on,
|
|
402
|
-
* and nothing in the frame says so.
|
|
403
|
-
*/
|
|
404
|
-
readonly staleFrames: number;
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* A throttled, fire-and-forget copy of a GPU buffer into CPU memory.
|
|
408
|
-
*
|
|
409
|
-
* Every sample it hands back carries its own age. That is the whole point: the copy is
|
|
410
|
-
* asynchronous, so the bytes are always some frames behind the GPU, and a class that hid that
|
|
411
|
-
* would let a caller mistake stale data for live data with no way to find out.
|
|
412
|
-
*
|
|
413
|
-
* `request()` never awaits and never blocks the frame. One copy is in flight at a time; requests
|
|
414
|
-
* made while one is pending are dropped rather than queued, because a backlog of copies of a field
|
|
415
|
-
* that has already moved on is latency with no information in it.
|
|
416
|
-
*/
|
|
417
|
-
declare class GPUReadback {
|
|
418
|
-
#private;
|
|
419
|
-
readonly everyFrames: number;
|
|
420
|
-
constructor(options: IGPUReadbackOptions);
|
|
421
|
-
get released(): boolean;
|
|
422
|
-
/** True while a copy is in flight. A game that wants to pace its own work can read it. */
|
|
423
|
-
get pending(): boolean;
|
|
424
|
-
/** The newest landed bytes, or `undefined` before the first copy lands. */
|
|
425
|
-
get data(): Float32Array | undefined;
|
|
426
|
-
/**
|
|
427
|
-
* How many frames old the landed bytes are.
|
|
428
|
-
*
|
|
429
|
-
* Before anything has landed this is the number of frames since construction, which grows
|
|
430
|
-
* without bound on purpose: "no data yet" and "data from frame zero" must not read the same.
|
|
431
|
-
*/
|
|
432
|
-
get staleFrames(): number;
|
|
433
|
-
/** The newest bytes with their age attached, or `undefined` before the first copy lands. */
|
|
434
|
-
get sample(): IGPUReadbackSample | undefined;
|
|
435
|
-
/** Requests, landings and failures, for a report that has to say why a sample is old. */
|
|
436
|
-
get stats(): {
|
|
437
|
-
readonly requests: number;
|
|
438
|
-
readonly lands: number;
|
|
439
|
-
readonly failures: number;
|
|
440
|
-
};
|
|
441
|
-
/**
|
|
442
|
-
* Advances the frame clock and starts a copy when the throttle allows one.
|
|
443
|
-
*
|
|
444
|
-
* Safe to call every frame. It returns before the GPU has answered — awaiting it is the one
|
|
445
|
-
* thing that would turn this class into the stall it exists to avoid.
|
|
446
|
-
*/
|
|
447
|
-
request(renderer: IRendererLike): void;
|
|
448
|
-
/** Drops the attribute reference and the landed bytes. Further requests throw. */
|
|
449
|
-
dispose(): void;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
388
|
interface IClothTopologyOptions {
|
|
453
389
|
/** Original geometry vertex indices that never move. Duplicate positions pin together. */
|
|
454
390
|
readonly pinned: readonly number[];
|
|
@@ -493,7 +429,7 @@ interface ISoftBody3DOptions extends IClothTopologyOptions {
|
|
|
493
429
|
* @override readbackEveryFrames enables an explicitly stale CPU position sample; zero disables it
|
|
494
430
|
* @example const flag = new SoftBody3D(flagMesh, { pinned: topEdge, stiffness: 35, damping: 1.8, gravity: [0, -9.81, 0], wind: [1.5, 0, 0.4] });
|
|
495
431
|
*/
|
|
496
|
-
declare class SoftBody3D extends Mesh<BufferGeometry, NodeMaterial> implements IComputeDriven {
|
|
432
|
+
declare class SoftBody3D extends Mesh<BufferGeometry, NodeMaterial> implements IComputeDriven$1 {
|
|
497
433
|
#private;
|
|
498
434
|
readonly processCadence: "fixed";
|
|
499
435
|
readonly warmupNodes: readonly ComputeNode[];
|
|
@@ -542,7 +478,7 @@ declare const rayStruct: StructTypeNode;
|
|
|
542
478
|
* exports through the core entry point and uses these four named nodes in its own `src/render/`
|
|
543
479
|
* kernel. The snapshot is static until `rebuild()`.
|
|
544
480
|
*/
|
|
545
|
-
declare class GPUSceneBVH extends Group implements IComputeDriven {
|
|
481
|
+
declare class GPUSceneBVH extends Group implements IComputeDriven$1 {
|
|
546
482
|
#private;
|
|
547
483
|
readonly indices: StorageBufferNode<"uvec3">;
|
|
548
484
|
readonly nodes: StorageBufferNode<"struct">;
|
|
@@ -640,6 +576,37 @@ declare class InstancedBatch {
|
|
|
640
576
|
build(options?: IInstancedBatchBuildOptions): InstancedMesh | undefined;
|
|
641
577
|
}
|
|
642
578
|
|
|
579
|
+
/**
|
|
580
|
+
* One piece on its way into a merged buffer.
|
|
581
|
+
*
|
|
582
|
+
* A `Mesh` is already one of these — pass the meshes straight in and their own transforms are
|
|
583
|
+
* used. Every value here is the game's: the shape, where it sits, and what colour it is.
|
|
584
|
+
*/
|
|
585
|
+
interface IMergePart {
|
|
586
|
+
/** The piece's own colour, written flat across its vertices. Omit it on every part for none. */
|
|
587
|
+
readonly color?: ColorRepresentation;
|
|
588
|
+
/** The shape. It is cloned before anything is done to it, so the game's copy is untouched. */
|
|
589
|
+
readonly geometry: BufferGeometry;
|
|
590
|
+
/** Where the piece sits. A `Mesh` brings its own; identity when there is none. */
|
|
591
|
+
readonly matrix?: Matrix4;
|
|
592
|
+
}
|
|
593
|
+
interface IMergePartsOptions {
|
|
594
|
+
/** Named in the error when the merge is refused. Say what was being built. */
|
|
595
|
+
readonly label: string;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Merge game-authored pieces into one buffer, keeping each piece's own colour.
|
|
599
|
+
*
|
|
600
|
+
* Two things go wrong every time an agent bakes a building, a ship or a character out of
|
|
601
|
+
* primitives, and neither is about how any of it looks. `mergeGeometries` returns `null` on
|
|
602
|
+
* mismatched inputs instead of throwing, and the usual mismatch — one non-indexed extrusion among
|
|
603
|
+
* a hundred indexed primitives — is invisible until the whole scene is missing; and a merged
|
|
604
|
+
* buffer draws with one surface, so per-piece colour is gone unless every piece carries a flat
|
|
605
|
+
* `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.
|
|
607
|
+
*/
|
|
608
|
+
declare function mergeParts(parts: Iterable<IMergePart>, options: IMergePartsOptions): BufferGeometry;
|
|
609
|
+
|
|
643
610
|
/**
|
|
644
611
|
* A mesh that draws only the clusters this camera can resolve.
|
|
645
612
|
*
|
|
@@ -809,7 +776,7 @@ declare class ClusteredBatch {
|
|
|
809
776
|
declare const ATLAS_PADDING = 1;
|
|
810
777
|
/** The machine-readable marker emitted whenever the probe state changes. */
|
|
811
778
|
declare const PROBE_VOLUME_MARKER = "TN_PROBE_VOLUME";
|
|
812
|
-
type IVector3Like = {
|
|
779
|
+
type IVector3Like$1 = {
|
|
813
780
|
readonly x: number;
|
|
814
781
|
readonly y: number;
|
|
815
782
|
readonly z: number;
|
|
@@ -822,12 +789,12 @@ interface IProbeVolumeCoefficient {
|
|
|
822
789
|
readonly b: number;
|
|
823
790
|
}
|
|
824
791
|
/** Probe density in probes per world unit, either isotropic or per-axis. */
|
|
825
|
-
type ProbeVolumeDensity = number | readonly [number, number, number] | IVector3Like;
|
|
792
|
+
type ProbeVolumeDensity = number | readonly [number, number, number] | IVector3Like$1;
|
|
826
793
|
interface IProbeVolumeOptions {
|
|
827
794
|
/** World-space bounds; the volume does not move with the object after construction. */
|
|
828
795
|
readonly bounds: Box3 | {
|
|
829
|
-
readonly min: IVector3Like;
|
|
830
|
-
readonly max: IVector3Like;
|
|
796
|
+
readonly min: IVector3Like$1;
|
|
797
|
+
readonly max: IVector3Like$1;
|
|
831
798
|
};
|
|
832
799
|
/** Probe spacing expressed as probes per world unit. */
|
|
833
800
|
readonly density: ProbeVolumeDensity;
|
|
@@ -891,7 +858,7 @@ declare function readProbeVolumeObservation(value: unknown): IProbeVolumeObserva
|
|
|
891
858
|
* Bakes are static-lighting-first and explicit. Call `requestBake(scene)` after lights and static
|
|
892
859
|
* geometry are authored; a completed bake is reused until the game requests another one.
|
|
893
860
|
*/
|
|
894
|
-
declare class ProbeVolume extends Object3D implements IComputeDriven {
|
|
861
|
+
declare class ProbeVolume extends Object3D implements IComputeDriven$1 {
|
|
895
862
|
#private;
|
|
896
863
|
readonly isProbeVolume = true;
|
|
897
864
|
readonly processCadence: "render";
|
|
@@ -943,6 +910,434 @@ declare class ProbeVolume extends Object3D implements IComputeDriven {
|
|
|
943
910
|
probePosition(ix: number, iy: number, iz: number, target?: Vector3): Vector3;
|
|
944
911
|
}
|
|
945
912
|
|
|
913
|
+
/**
|
|
914
|
+
* Renderer-independent bookkeeping for virtual shadow pages.
|
|
915
|
+
*
|
|
916
|
+
* Four pieces, none of which know a GPU exists: stable virtual page addresses and a bounded
|
|
917
|
+
* physical page pool with LRU eviction (`PhysicalPagePool`), camera-centred clip windows snapped
|
|
918
|
+
* to whole pages in a light-space basis (`DirectionalClipmap`), the pages a frame needs from
|
|
919
|
+
* receiver points and visible caster bounds (`ReceiverDemandPass`), and the pages a moving caster
|
|
920
|
+
* dirties (`ShadowInvalidationTracker`). `VirtualShadowNode` in `virtual-shadow.ts` is the only
|
|
921
|
+
* consumer; everything here is pure so its rules are provable in a node-environment spec.
|
|
922
|
+
*
|
|
923
|
+
* Fail closed: malformed input throws at the boundary instead of producing a page that samples
|
|
924
|
+
* nothing.
|
|
925
|
+
*/
|
|
926
|
+
/** A plain `{x, y, z}` triple; three's `Vector3` satisfies it without a copy. */
|
|
927
|
+
interface IVector3Like {
|
|
928
|
+
readonly x: number;
|
|
929
|
+
readonly y: number;
|
|
930
|
+
readonly z: number;
|
|
931
|
+
}
|
|
932
|
+
/** An axis-aligned world-space box as two corners. */
|
|
933
|
+
interface IBoundsLike {
|
|
934
|
+
readonly min: IVector3Like;
|
|
935
|
+
readonly max: IVector3Like;
|
|
936
|
+
}
|
|
937
|
+
/** A point in the light-space basis: `u` and `v` across the light, `w` toward it. */
|
|
938
|
+
interface ILightSpacePoint {
|
|
939
|
+
readonly u: number;
|
|
940
|
+
readonly v: number;
|
|
941
|
+
readonly w: number;
|
|
942
|
+
}
|
|
943
|
+
/** The address of one virtual page: a clip level and integer page coordinates. */
|
|
944
|
+
interface IVirtualPageAddress {
|
|
945
|
+
readonly level: number;
|
|
946
|
+
readonly x: number;
|
|
947
|
+
readonly y: number;
|
|
948
|
+
readonly key: string;
|
|
949
|
+
}
|
|
950
|
+
/** One clip level's window of `pagesPerAxis²` virtual pages around the centre. */
|
|
951
|
+
interface IClipWindow {
|
|
952
|
+
readonly level: number;
|
|
953
|
+
readonly extent: number;
|
|
954
|
+
readonly pageWorldSize: number;
|
|
955
|
+
readonly minX: number;
|
|
956
|
+
readonly minY: number;
|
|
957
|
+
readonly maxX: number;
|
|
958
|
+
readonly maxY: number;
|
|
959
|
+
}
|
|
960
|
+
interface IDirectionalClipmapOptions {
|
|
961
|
+
/** Unit-free direction *toward* the source; normalised here. */
|
|
962
|
+
readonly direction: IVector3Like;
|
|
963
|
+
/** Half-width of each level's window in world units, finest first. */
|
|
964
|
+
readonly clipExtents: readonly number[];
|
|
965
|
+
readonly pagesPerAxis: number;
|
|
966
|
+
/** Fraction of an extent inside which a point selects that level; `(0, 1]`. */
|
|
967
|
+
readonly selectionGuard?: number;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Camera-centred clip windows in a light-space basis, snapped to whole pages.
|
|
971
|
+
*
|
|
972
|
+
* The basis is right-handed (`U × V = W`). A page camera placed along `+W` with `up = V` then
|
|
973
|
+
* renders screen X = `V × W` = `+U`, which is the axis the sampler reads the atlas along. The
|
|
974
|
+
* prototype this was ported from built `V = U × W` and every page came out mirrored.
|
|
975
|
+
*/
|
|
976
|
+
declare class DirectionalClipmap {
|
|
977
|
+
#private;
|
|
978
|
+
readonly clipExtents: readonly number[];
|
|
979
|
+
readonly pagesPerAxis: number;
|
|
980
|
+
readonly selectionGuard: number;
|
|
981
|
+
readonly levelCount: number;
|
|
982
|
+
basisU: IVector3Like;
|
|
983
|
+
basisV: IVector3Like;
|
|
984
|
+
basisW: IVector3Like;
|
|
985
|
+
centerWorld: IVector3Like;
|
|
986
|
+
centerLight: ILightSpacePoint;
|
|
987
|
+
constructor({ direction, clipExtents, pagesPerAxis, selectionGuard, }: IDirectionalClipmapOptions);
|
|
988
|
+
/** Re-orient the basis; returns true when it changed enough that cached pages are stale. */
|
|
989
|
+
setDirection(direction: IVector3Like): boolean;
|
|
990
|
+
project(worldPoint: IVector3Like): ILightSpacePoint;
|
|
991
|
+
unproject({ u, v, w }: {
|
|
992
|
+
u: number;
|
|
993
|
+
v: number;
|
|
994
|
+
w?: number;
|
|
995
|
+
}): IVector3Like;
|
|
996
|
+
updateCenter(worldPoint: IVector3Like): readonly IClipWindow[];
|
|
997
|
+
getWindow(level: number): IClipWindow;
|
|
998
|
+
pageWorldSize(level: number): number;
|
|
999
|
+
containsPage(level: number, x: number, y: number): boolean;
|
|
1000
|
+
worldToPage(worldPoint: IVector3Like, level: number): IVirtualPageAddress;
|
|
1001
|
+
/** The finest level whose guarded extent contains the point, else the coarsest. */
|
|
1002
|
+
selectLevel(worldPoint: IVector3Like): number;
|
|
1003
|
+
/** Light-space extent and world centre (at `w = 0`) of one page. */
|
|
1004
|
+
pageBounds(level: number, x: number, y: number): {
|
|
1005
|
+
level: number;
|
|
1006
|
+
x: number;
|
|
1007
|
+
y: number;
|
|
1008
|
+
minU: number;
|
|
1009
|
+
minV: number;
|
|
1010
|
+
maxU: number;
|
|
1011
|
+
maxV: number;
|
|
1012
|
+
centerU: number;
|
|
1013
|
+
centerV: number;
|
|
1014
|
+
pageWorldSize: number;
|
|
1015
|
+
centerWorld: IVector3Like;
|
|
1016
|
+
};
|
|
1017
|
+
boundsToPageRange(bounds: IBoundsLike, level: number): {
|
|
1018
|
+
minX: number;
|
|
1019
|
+
maxX: number;
|
|
1020
|
+
minY: number;
|
|
1021
|
+
maxY: number;
|
|
1022
|
+
};
|
|
1023
|
+
boundsToPageKeys(bounds: IBoundsLike, level: number): string[];
|
|
1024
|
+
windowPages(level: number): IVirtualPageAddress[];
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Remembers each tracked caster's last bounds and dirties every page the old and new bounds
|
|
1028
|
+
* cover on every level when they change. Unchanged bounds dirty nothing.
|
|
1029
|
+
*/
|
|
1030
|
+
declare class ShadowInvalidationTracker {
|
|
1031
|
+
#private;
|
|
1032
|
+
readonly clipmap: DirectionalClipmap;
|
|
1033
|
+
constructor(clipmap: DirectionalClipmap);
|
|
1034
|
+
get trackedCount(): number;
|
|
1035
|
+
/** Record a caster's current bounds; returns true when pages were dirtied. */
|
|
1036
|
+
update(id: number | string, bounds: IBoundsLike): boolean;
|
|
1037
|
+
has(id: number | string): boolean;
|
|
1038
|
+
remove(id: number | string): boolean;
|
|
1039
|
+
/** Dirty the current coverage of every tracked caster; returns the pending key count. */
|
|
1040
|
+
invalidateAll(): number;
|
|
1041
|
+
/** Drop trackers whose id is not in `liveIds`, dirtying the pages they covered. */
|
|
1042
|
+
prune(liveIds: ReadonlySet<number | string>): number;
|
|
1043
|
+
consumeInvalidatedKeys(): Set<string>;
|
|
1044
|
+
clear(): void;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Options for {@link VirtualShadowNode}. Every value is a mechanism parameter; the light's own
|
|
1049
|
+
* `shadow` keeps bias, normalBias, intensity, map type and filter, exactly as with a stock shadow.
|
|
1050
|
+
*/
|
|
1051
|
+
interface IVirtualShadowOptions {
|
|
1052
|
+
/**
|
|
1053
|
+
* Half-width of each clip level's window in world units, finest first and strictly
|
|
1054
|
+
* increasing. Default `[16, 48, 144]`: three windows, each three times wider than the last.
|
|
1055
|
+
*/
|
|
1056
|
+
readonly clipExtents?: readonly number[];
|
|
1057
|
+
/** Texels per level edge. Default: the light's `shadow.mapSize.width`. */
|
|
1058
|
+
readonly mapSize?: number;
|
|
1059
|
+
/**
|
|
1060
|
+
* Texels per edge of each level's mover map — the map tracked casters draw into every frame.
|
|
1061
|
+
* Default: half of `mapSize`, never below 256. Movers are few and close, so half the texels
|
|
1062
|
+
* over the same window reads as the same shadow at a quarter of the fill.
|
|
1063
|
+
*/
|
|
1064
|
+
readonly moverMapSize?: number;
|
|
1065
|
+
/**
|
|
1066
|
+
* Fraction of a level's extent inside which a fragment still selects that level, `(0, 1]`,
|
|
1067
|
+
* default 0.9 — the outer ring falls through to the next level so the edge is never sampled.
|
|
1068
|
+
*/
|
|
1069
|
+
readonly selectionGuard?: number;
|
|
1070
|
+
/** How far behind the window centre each level camera sits, in world units. Default 200. */
|
|
1071
|
+
readonly lightDistance?: number;
|
|
1072
|
+
/** Depth range each level camera covers past its centre, in world units. Default 400. */
|
|
1073
|
+
readonly depthRange?: number;
|
|
1074
|
+
/** Print the `TN_VIRTUAL_SHADOW` line every `markerEvery` frames; `false` silences it. Default 300. */
|
|
1075
|
+
readonly marker?: boolean | number;
|
|
1076
|
+
}
|
|
1077
|
+
/** Per-frame counters, readable any time through {@link VirtualShadowNode.stats}. */
|
|
1078
|
+
interface IVirtualShadowStats {
|
|
1079
|
+
readonly frame: number;
|
|
1080
|
+
readonly levels: number;
|
|
1081
|
+
/** Levels whose window moved this frame and were re-rendered. */
|
|
1082
|
+
readonly moved: number;
|
|
1083
|
+
/** Levels re-rendered because `invalidateAll()` or explicit tracker invalidation asked for it. */
|
|
1084
|
+
readonly invalidated: number;
|
|
1085
|
+
/** Tracked casters, as of this frame. */
|
|
1086
|
+
readonly movers: number;
|
|
1087
|
+
/** Mover maps rendered this frame: one per level when at least one caster is tracked. */
|
|
1088
|
+
readonly moverRenders: number;
|
|
1089
|
+
/** Levels served from their cached map this frame. */
|
|
1090
|
+
readonly cached: number;
|
|
1091
|
+
/** Levels rendered this frame, for any reason. */
|
|
1092
|
+
readonly rendered: number;
|
|
1093
|
+
/** Fraction of levels served from cache over the node's lifetime. */
|
|
1094
|
+
readonly reuseRatio: number;
|
|
1095
|
+
}
|
|
1096
|
+
declare const VIRTUAL_SHADOW_MARKER = "TN_VIRTUAL_SHADOW";
|
|
1097
|
+
/**
|
|
1098
|
+
* The object layer tracked casters are enabled on, so each level's mover camera sees only them.
|
|
1099
|
+
* Keep it free of other uses; the main camera never needs it (tracked objects keep layer 0).
|
|
1100
|
+
*/
|
|
1101
|
+
declare const VIRTUAL_SHADOW_MOVER_LAYER = 29;
|
|
1102
|
+
/**
|
|
1103
|
+
* One directional shadow for a whole open world: camera-centred clip levels, each snapped to its
|
|
1104
|
+
* own texel grid and re-rendered only when its window moves. Movers never touch that cache: a
|
|
1105
|
+
* tracked caster draws into a second, per-level mover map every frame, and a fragment takes the
|
|
1106
|
+
* darker of the two — so a walking stag costs one small render of itself, not a render of the wood.
|
|
1107
|
+
*
|
|
1108
|
+
* Plugs into three's own slot, so every material in the scene receives it with no other change:
|
|
1109
|
+
* `light.shadow.shadowNode = new VirtualShadowNode(light, { clipExtents: [16, 48, 144] })`.
|
|
1110
|
+
*
|
|
1111
|
+
* What it owns is mechanism: level windows, texel snapping, per-level caching, invalidation,
|
|
1112
|
+
* level selection and the statistics. The light's `shadow` keeps bias, normal bias, intensity,
|
|
1113
|
+
* map type and filter, and those source settings are mirrored into each stock level node before
|
|
1114
|
+
* rendering. Per-level map sizes, cameras, `autoUpdate` and `needsUpdate` are owned by this node.
|
|
1115
|
+
* 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.
|
|
1117
|
+
*
|
|
1118
|
+
* Ported from the virtual-shadow-map prototype's clipmap and invalidation; the sparse page atlas
|
|
1119
|
+
* is deliberately not the first cut — a page needs the scene rendered once per page, and on a
|
|
1120
|
+
* forest of hundreds of instanced meshes three level renders are cheaper than twenty-four page
|
|
1121
|
+
* renders. The page pool and demand pass in `virtual-shadow-pages.ts` stay ready for it.
|
|
1122
|
+
*
|
|
1123
|
+
* @situation crisp shadows close to the player across a large outdoor level
|
|
1124
|
+
* @situation shadow map too coarse over a big terrain
|
|
1125
|
+
* @situation one directional light shadow for a whole open world
|
|
1126
|
+
* @situation shadows shimmer when the camera moves
|
|
1127
|
+
* @constraint the light must be a DirectionalLight with `castShadow` and a target in the scene
|
|
1128
|
+
* @constraint clipExtents are half-widths in world units, finest first, strictly increasing
|
|
1129
|
+
* @constraint call `trackCaster(object)` for movers; it enables layer `VIRTUAL_SHADOW_MOVER_LAYER` on the object and its descendants, tracking or untracking refreshes cached levels once, and subsequent mover movement refreshes only when a window moves
|
|
1130
|
+
* @override bias, biasNode, normalBias, intensity, radius, blurSamples, mapType and filterNode stay on `light.shadow`; mapSize and the other options here have defaults
|
|
1131
|
+
* @example
|
|
1132
|
+
* const sun = new DirectionalLight(0xffffff, 3);
|
|
1133
|
+
* sun.castShadow = true;
|
|
1134
|
+
* sun.shadow.shadowNode = new VirtualShadowNode(sun, { clipExtents: [12, 40, 120] });
|
|
1135
|
+
*/
|
|
1136
|
+
declare class VirtualShadowNode extends ShadowBaseNode {
|
|
1137
|
+
#private;
|
|
1138
|
+
static get type(): string;
|
|
1139
|
+
readonly options: Required<Omit<IVirtualShadowOptions, "marker">> & {
|
|
1140
|
+
readonly markerEvery: number;
|
|
1141
|
+
};
|
|
1142
|
+
readonly clipmap: DirectionalClipmap;
|
|
1143
|
+
/**
|
|
1144
|
+
* Compatibility handle for explicit page invalidation. Automatic caster motion uses mover maps;
|
|
1145
|
+
* callers that already update this tracker still invalidate the affected cached levels.
|
|
1146
|
+
*/
|
|
1147
|
+
readonly tracker: ShadowInvalidationTracker;
|
|
1148
|
+
constructor(light: DirectionalLight, options?: IVirtualShadowOptions);
|
|
1149
|
+
/** The per-frame counters, as of the last `updateBefore`. */
|
|
1150
|
+
get stats(): IVirtualShadowStats;
|
|
1151
|
+
/** The stock shadow nodes behind each level, for diagnostics. */
|
|
1152
|
+
get levelNodes(): readonly Node[];
|
|
1153
|
+
/** The stock shadow nodes behind each level's mover map, for diagnostics. */
|
|
1154
|
+
get moverNodes(): readonly Node[];
|
|
1155
|
+
/** The placeholder lights, one per level; exposed for tests and debug views. */
|
|
1156
|
+
get levelLights(): readonly Object3D[];
|
|
1157
|
+
/**
|
|
1158
|
+
* Make an object a mover: it leaves the cached level maps and draws into every level's mover
|
|
1159
|
+
* map each frame, so its shadow follows it without a level render. Static geometry never
|
|
1160
|
+
* needs this — a level re-renders whenever its window moves anyway.
|
|
1161
|
+
*/
|
|
1162
|
+
trackCaster(object: Object3D): string;
|
|
1163
|
+
untrackCaster(objectOrId: Object3D | string): boolean;
|
|
1164
|
+
/** Force every level to re-render on the next frame — a tree fell, a door opened. */
|
|
1165
|
+
invalidateAll(): void;
|
|
1166
|
+
setup(builder: NodeBuilder): Node | null | undefined;
|
|
1167
|
+
updateBefore(frame: NodeFrame): undefined;
|
|
1168
|
+
dispose(): void;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Parse a `TN_VIRTUAL_SHADOW` console line back into its complete stats, or `undefined`.
|
|
1172
|
+
*
|
|
1173
|
+
* @situation inspect virtual shadow cache and mover counters from a renderer log
|
|
1174
|
+
* @constraint non-marker lines and markers with incomplete or non-numeric stats return `undefined`
|
|
1175
|
+
* @example
|
|
1176
|
+
* const stats = readVirtualShadowMarker(line);
|
|
1177
|
+
* if (stats !== undefined) console.log(stats.reuseRatio);
|
|
1178
|
+
*/
|
|
1179
|
+
declare function readVirtualShadowMarker(line: string): IVirtualShadowStats | undefined;
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Two load-time mechanisms every streaming game writes by hand, and gets wrong in the same places.
|
|
1183
|
+
*
|
|
1184
|
+
* A game that shows a loading curtain and builds its world behind it has two loops that decide how
|
|
1185
|
+
* long the curtain stays up, and neither is about how anything looks: the loop that **fetches** the
|
|
1186
|
+
* models and the loop that **attaches** the finished objects to the scene. Written naively, the
|
|
1187
|
+
* first is serial and the second is one-object-per-frame, and both are slow for reasons that have
|
|
1188
|
+
* nothing to do with the game.
|
|
1189
|
+
*
|
|
1190
|
+
* Measured in a real game (a 190 m valley, ~70 GLBs, ~400 attached objects), on the same build and
|
|
1191
|
+
* the same content:
|
|
1192
|
+
*
|
|
1193
|
+
* | Attach loop | Detail tier |
|
|
1194
|
+
* | ------------ | ----------- |
|
|
1195
|
+
* | 1 per frame | 16.5 s |
|
|
1196
|
+
* | 6 per frame | 11.26 s |
|
|
1197
|
+
* | 24 per frame | 9.39 s |
|
|
1198
|
+
* | 256 per frame| **6.89 s** |
|
|
1199
|
+
* | all at once | 7.18 s |
|
|
1200
|
+
*
|
|
1201
|
+
* and the fetch loop: 52 models one at a time took **38.4 s**; six at a time took **8.8 s**.
|
|
1202
|
+
*
|
|
1203
|
+
* Neither of these decides how anything looks. `addInSlices` never creates an object and never
|
|
1204
|
+
* chooses where it goes — it is handed a list and the game's own `add`. `loadAll` never chooses
|
|
1205
|
+
* what to load — it is handed a list and the game's own `load`. Geometry, material, colour,
|
|
1206
|
+
* texture, curve and timing stay with the game in both, and a game can change its appearance
|
|
1207
|
+
* completely without editing either.
|
|
1208
|
+
*
|
|
1209
|
+
* What they are not is code a game can get right portably by itself, which is why they live here.
|
|
1210
|
+
* Yielding correctly is a platform seam: awaiting `requestAnimationFrame` deadlocks a host whose
|
|
1211
|
+
* frames are pumped by the code doing the waiting, racing it against a timer reorders the frame
|
|
1212
|
+
* sequence a harness observes, and a hard-coded `setTimeout(16)` is neither a frame on the native
|
|
1213
|
+
* runtime nor a frame on a machine that cannot hold 60. See `yieldToHost` in `warmup.ts` for the
|
|
1214
|
+
* two wrong versions that came before the one both of these use.
|
|
1215
|
+
*/
|
|
1216
|
+
/** How far a sliced attachment has got, reported once per slice. */
|
|
1217
|
+
interface IAddInSlicesProgress {
|
|
1218
|
+
/** Objects attached so far. Never greater than `total`. */
|
|
1219
|
+
readonly added: number;
|
|
1220
|
+
/** Objects the run was given. Known before the first slice. */
|
|
1221
|
+
readonly total: number;
|
|
1222
|
+
}
|
|
1223
|
+
interface IAddInSlicesOptions {
|
|
1224
|
+
/**
|
|
1225
|
+
* Objects attached between presented frames. Default 256.
|
|
1226
|
+
*
|
|
1227
|
+
* The default is large because the reason it used to be small has been taken over by something
|
|
1228
|
+
* better. One per frame was right when nothing else compiled the world: the renderer built a few
|
|
1229
|
+
* newly visible pipelines per presented frame instead of all of them inside one multi-second
|
|
1230
|
+
* frame. It is not right once the framework warms the held scene (`warmUpScene`) — the compile is
|
|
1231
|
+
* then paid either way and the slice only chooses where, so a small slice buys nothing and costs
|
|
1232
|
+
* one present per object.
|
|
1233
|
+
*
|
|
1234
|
+
* 256 is where the measured curve flattens without giving up the yields entirely: over a few
|
|
1235
|
+
* hundred objects it still presents a handful of frames, so a browser watchdog cannot see a hung
|
|
1236
|
+
* page, and it costs nothing against attaching everything in one go.
|
|
1237
|
+
*/
|
|
1238
|
+
readonly sliceSize?: number;
|
|
1239
|
+
/** Called once per slice, and once more for a partial final slice. */
|
|
1240
|
+
readonly onProgress?: (progress: IAddInSlicesProgress) => void;
|
|
1241
|
+
/**
|
|
1242
|
+
* How the run hands the loop back to the host between slices. Defaults to yielding one
|
|
1243
|
+
* macrotask, which is one native-runtime loop iteration and therefore one presented frame.
|
|
1244
|
+
*/
|
|
1245
|
+
readonly yieldFrame?: () => Promise<void>;
|
|
1246
|
+
/**
|
|
1247
|
+
* Asked before every object; a false answer stops the run.
|
|
1248
|
+
*
|
|
1249
|
+
* This is how a scene that can be torn down mid-attach stays correct. A generation invalidated
|
|
1250
|
+
* by a restart, a scene change, or an HMR reload must stop attaching into a graph that is no
|
|
1251
|
+
* longer current, and it must do so **without throwing** — a throw here is indistinguishable
|
|
1252
|
+
* from a real attachment failure at the catch site, and games end up reporting a teardown as an
|
|
1253
|
+
* asset error. The stop is reported instead, as `stopped` on the report.
|
|
1254
|
+
*/
|
|
1255
|
+
readonly while?: () => boolean;
|
|
1256
|
+
/** Silences the `TN_ADD_SLICES` marker. Never silences the report. Default true (on). */
|
|
1257
|
+
readonly marker?: boolean;
|
|
1258
|
+
}
|
|
1259
|
+
/** What a sliced attachment did, so a caller can report it rather than assume it. */
|
|
1260
|
+
interface IAddInSlicesReport {
|
|
1261
|
+
/** Objects actually handed to `add`. */
|
|
1262
|
+
readonly added: number;
|
|
1263
|
+
/** Objects the run was given. */
|
|
1264
|
+
readonly total: number;
|
|
1265
|
+
/** Slices the work was cut into, and therefore the frames the loop got to present, plus one. */
|
|
1266
|
+
readonly slices: number;
|
|
1267
|
+
/** Wall-clock milliseconds the run took, attaching and yielding together. */
|
|
1268
|
+
readonly elapsedMs: number;
|
|
1269
|
+
/** The slice size actually used — the default, or the one the game chose. */
|
|
1270
|
+
readonly sliceSize: number;
|
|
1271
|
+
/** Whether that slice size came from the game rather than from the default. */
|
|
1272
|
+
readonly sliceSizeOverridden: boolean;
|
|
1273
|
+
/** True when `while` ended the run early. `added` is then less than `total`. */
|
|
1274
|
+
readonly stopped: boolean;
|
|
1275
|
+
}
|
|
1276
|
+
/** How far a bounded-concurrency load has got, reported once per settled item. */
|
|
1277
|
+
interface ILoadAllProgress {
|
|
1278
|
+
/** Loads that have resolved. */
|
|
1279
|
+
readonly settled: number;
|
|
1280
|
+
/** Loads the run was given. Known before the first one starts. */
|
|
1281
|
+
readonly total: number;
|
|
1282
|
+
}
|
|
1283
|
+
interface ILoadAllOptions {
|
|
1284
|
+
/**
|
|
1285
|
+
* Loads in flight at once. Default 6.
|
|
1286
|
+
*
|
|
1287
|
+
* Bounded rather than unbounded, because a full-width fan-out coalesces its completion callbacks
|
|
1288
|
+
* into 100-200 ms tasks and freezes the loading screen it is filling — which reads as the hang
|
|
1289
|
+
* the concurrency was added to remove. Six is also the per-host connection limit a browser
|
|
1290
|
+
* applies to HTTP/1.1, so a larger number frequently buys queueing rather than parallelism.
|
|
1291
|
+
*/
|
|
1292
|
+
readonly concurrency?: number;
|
|
1293
|
+
/** Called after each load settles. */
|
|
1294
|
+
readonly onProgress?: (progress: ILoadAllProgress) => void;
|
|
1295
|
+
/**
|
|
1296
|
+
* How the run hands the loop back to the host after each settled load. Defaults to yielding one
|
|
1297
|
+
* macrotask, which is what keeps a progress bar moving while the lanes are busy.
|
|
1298
|
+
*/
|
|
1299
|
+
readonly yieldFrame?: () => Promise<void>;
|
|
1300
|
+
/** Silences the `TN_LOAD_ALL` marker. Never silences `onProgress`. Default true (on). */
|
|
1301
|
+
readonly marker?: boolean;
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Attach a built list of objects to the scene in slices, presenting a frame between each.
|
|
1305
|
+
*
|
|
1306
|
+
* The objects are already built and already the game's; this decides only *when* each one joins
|
|
1307
|
+
* the graph. Order is the list's order, and there is no option to change it — an attach loop that
|
|
1308
|
+
* reordered its input would change which object a positional lookup finds.
|
|
1309
|
+
*
|
|
1310
|
+
* @situation add hundreds of built objects to the scene without one multi-second frame
|
|
1311
|
+
* @situation stream a detail tier in behind a loading curtain without the page looking hung
|
|
1312
|
+
* @example
|
|
1313
|
+
* const report = await addInSlices(detailObjects, (object) => ctx.add(object), {
|
|
1314
|
+
* onProgress: ({ added, total }) => setProgress(added / total),
|
|
1315
|
+
* while: () => generation.live,
|
|
1316
|
+
* });
|
|
1317
|
+
*/
|
|
1318
|
+
declare function addInSlices<T>(objects: Iterable<T>, add: (object: T, index: number) => void, options?: IAddInSlicesOptions): Promise<IAddInSlicesReport>;
|
|
1319
|
+
/**
|
|
1320
|
+
* Load a list with bounded concurrency, and hand the results back **in the input's order**.
|
|
1321
|
+
*
|
|
1322
|
+
* The ordering is the whole point and has no override. `Promise.all` already keeps order but runs
|
|
1323
|
+
* everything at once; a hand-rolled worker pool bounds the lanes but pushes results as they land,
|
|
1324
|
+
* so the array comes back in completion order — whatever the network happened to return. A game
|
|
1325
|
+
* that picks from that list positionally then places a different model in the same spot on every
|
|
1326
|
+
* load, and its world is never the same twice. That defect shipped in a real game and is why this
|
|
1327
|
+
* writes each result to its item's own index and never appends.
|
|
1328
|
+
*
|
|
1329
|
+
* Fails closed like `Promise.all`: the first rejection rejects the call, and no lane starts a load
|
|
1330
|
+
* it had not already begun.
|
|
1331
|
+
*
|
|
1332
|
+
* @situation load many models or textures in parallel instead of one at a time
|
|
1333
|
+
* @situation keep a loading screen moving while a list of assets downloads
|
|
1334
|
+
* @example
|
|
1335
|
+
* const species = await loadAll(names, (name) => ctx.assets.model(`flora/${name}.glb`), {
|
|
1336
|
+
* onProgress: ({ settled, total }) => setProgress(settled / total),
|
|
1337
|
+
* });
|
|
1338
|
+
*/
|
|
1339
|
+
declare function loadAll<TIn, TOut>(items: readonly TIn[], load: (item: TIn, index: number) => Promise<TOut>, options?: ILoadAllOptions): Promise<TOut[]>;
|
|
1340
|
+
|
|
946
1341
|
/** One band of the spectrum, drawn on its own patch. */
|
|
947
1342
|
interface ISpectralOceanCascade {
|
|
948
1343
|
/** The world-space edge length, in metres, this cascade's grid tiles across. */
|
|
@@ -1021,7 +1416,7 @@ interface ISpectralOceanHeight {
|
|
|
1021
1416
|
* needs the height to be exact wants an analytic field instead — that is a different contract, and
|
|
1022
1417
|
* the reason this class has a different name rather than a flag.
|
|
1023
1418
|
*/
|
|
1024
|
-
declare class SpectralOcean extends Object3D implements IComputeDriven {
|
|
1419
|
+
declare class SpectralOcean extends Object3D implements IComputeDriven$1 {
|
|
1025
1420
|
#private;
|
|
1026
1421
|
readonly resolution: number;
|
|
1027
1422
|
readonly cascades: readonly ISpectralOceanCascade[];
|
|
@@ -1064,7 +1459,7 @@ interface IGPUParticles3DOptions {
|
|
|
1064
1459
|
readonly start: (buffers: IGPUParticles3DBuffers) => ComputeNode;
|
|
1065
1460
|
readonly process: (buffers: IGPUParticles3DBuffers) => ComputeNode;
|
|
1066
1461
|
}
|
|
1067
|
-
declare class GPUParticles3D extends Sprite implements IComputeDriven {
|
|
1462
|
+
declare class GPUParticles3D extends Sprite implements IComputeDriven$1 {
|
|
1068
1463
|
#private;
|
|
1069
1464
|
readonly amount: number;
|
|
1070
1465
|
readonly buffers: IGPUParticles3DBuffers;
|
|
@@ -1148,6 +1543,17 @@ interface IWaveFieldWave {
|
|
|
1148
1543
|
readonly speed: number;
|
|
1149
1544
|
readonly phase?: number;
|
|
1150
1545
|
readonly steepness?: number;
|
|
1546
|
+
/**
|
|
1547
|
+
* Mark this wave as detail: the graph fades it out with the `fade` node passed to
|
|
1548
|
+
* `heightNode` / `normalNode`, and leaves it at full amplitude everywhere else.
|
|
1549
|
+
*
|
|
1550
|
+
* A wave shorter than the distance one screen pixel covers cannot be resolved, and what it
|
|
1551
|
+
* produces instead is a crawling moire that reads as a repeating pattern. Fading it is the fix.
|
|
1552
|
+
* `sample` on the CPU has no camera and therefore no fade, so CPU and GPU height differ by at
|
|
1553
|
+
* most the summed amplitude of the detail waves — keep them small, or float things on the
|
|
1554
|
+
* non-detail waves alone.
|
|
1555
|
+
*/
|
|
1556
|
+
readonly detail?: boolean;
|
|
1151
1557
|
}
|
|
1152
1558
|
interface IWaveFieldDomainWarp {
|
|
1153
1559
|
readonly direction?: WaveDirection;
|
|
@@ -1162,6 +1568,18 @@ interface IWaveFieldOptions {
|
|
|
1162
1568
|
readonly waves: readonly IWaveFieldWave[];
|
|
1163
1569
|
readonly domainWarp?: readonly IWaveFieldDomainWarp[];
|
|
1164
1570
|
}
|
|
1571
|
+
/** Where, when, and how finely to evaluate the field in a graph. */
|
|
1572
|
+
interface IWaveFieldGraphOptions {
|
|
1573
|
+
/**
|
|
1574
|
+
* The horizontal point to evaluate at, in whatever space the caller wants the answer in.
|
|
1575
|
+
* Defaults to this vertex's local x and z.
|
|
1576
|
+
*/
|
|
1577
|
+
readonly point?: Node<"vec2">;
|
|
1578
|
+
/** The clock. Defaults to the field's own `time` uniform. */
|
|
1579
|
+
readonly time?: Node<"float">;
|
|
1580
|
+
/** A 0..1 multiplier on every wave marked `detail`. Omitted, detail waves stay at full size. */
|
|
1581
|
+
readonly fade?: Node<"float">;
|
|
1582
|
+
}
|
|
1165
1583
|
interface IWaveFieldSample {
|
|
1166
1584
|
readonly height: number;
|
|
1167
1585
|
readonly normal: Vector3;
|
|
@@ -1180,8 +1598,120 @@ declare class WaveField {
|
|
|
1180
1598
|
/** Update the default graph clock. Explicit sample times remain available for fixed-step code. */
|
|
1181
1599
|
setTime(value: number): void;
|
|
1182
1600
|
sample(x: number, z: number, time: number): IWaveFieldSample;
|
|
1601
|
+
/** Surface height at a point, as a graph. The scalar half of what `sample` returns. */
|
|
1602
|
+
heightNode(options?: IWaveFieldGraphOptions): Node<"float">;
|
|
1603
|
+
/**
|
|
1604
|
+
* The analytic surface normal at a point, as a graph — the same value `sample` returns, and the
|
|
1605
|
+
* reason a water material needs no hand-written ripple pattern.
|
|
1606
|
+
*
|
|
1607
|
+
* Differencing the height, or stamping a normal map over the surface, is what puts visible
|
|
1608
|
+
* repeats in water: both quantise a field that has none. This differentiates the wave sum
|
|
1609
|
+
* itself, so the normal repeats only where the waves do, which for wavelengths with no common
|
|
1610
|
+
* multiple is nowhere.
|
|
1611
|
+
*
|
|
1612
|
+
* Evaluate it per fragment — pass `point` in world XZ — and the ripples survive at any distance
|
|
1613
|
+
* from the camera, at the cost of the wave sum running per pixel rather than per vertex.
|
|
1614
|
+
*/
|
|
1615
|
+
normalNode(options?: IWaveFieldGraphOptions): Node<"vec3">;
|
|
1183
1616
|
/** Return a TSL node that displaces local vertices using the same packed values as `sample`. */
|
|
1184
|
-
displacementNode(timeNode?: three_webgpu.UniformNode<"float", number>):
|
|
1617
|
+
displacementNode(timeNode?: three_webgpu.UniformNode<"float", number>): Node<"vec3">;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/** How the mirrored pass is sized. Both numbers are cost, not appearance. */
|
|
1621
|
+
interface IWaterReflectionOptions {
|
|
1622
|
+
/**
|
|
1623
|
+
* The mirrored pass's render target, as a fraction of the drawing buffer.
|
|
1624
|
+
*
|
|
1625
|
+
* A reflection is a second draw of the whole world, so this is the one number that decides
|
|
1626
|
+
* whether a water surface is affordable. Half is the usual answer.
|
|
1627
|
+
*/
|
|
1628
|
+
readonly resolutionScale: number;
|
|
1629
|
+
/** Whether this surface may appear in other reflectors' passes. Off is one pass; on is n². */
|
|
1630
|
+
readonly bounces?: boolean;
|
|
1631
|
+
}
|
|
1632
|
+
interface IWaterSurfaceOptions {
|
|
1633
|
+
/** World-space height of the surface, in metres. The mirror plane, and where thickness is 0. */
|
|
1634
|
+
readonly level: number;
|
|
1635
|
+
/**
|
|
1636
|
+
* Thickness readings saturate here, in metres.
|
|
1637
|
+
*
|
|
1638
|
+
* A required number because it is the range of the instrument, not a taste: sky behind the
|
|
1639
|
+
* surface has no depth at all, and something has to be reported for it.
|
|
1640
|
+
*/
|
|
1641
|
+
readonly maxThickness: number;
|
|
1642
|
+
/** Omit for a surface that reflects nothing; `reflectionAt` then throws rather than lying. */
|
|
1643
|
+
readonly reflection?: IWaterReflectionOptions;
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* What a horizontal water surface can see: the world mirrored in it, the world beneath it, and
|
|
1647
|
+
* how much water stands between the two.
|
|
1648
|
+
*
|
|
1649
|
+
* This is the render plumbing a water material needs and cannot write portably — a mirrored
|
|
1650
|
+
* camera and its render target, the frame's own colour read back as the light coming up through
|
|
1651
|
+
* the surface, and the scene's depth turned into **metres of water under this pixel**. It decides
|
|
1652
|
+
* nothing about how any of that looks: no colour, no absorption tint, no fresnel weighting, no
|
|
1653
|
+
* glint. Those are the game's, composed from the nodes below in its own `src/render/` material.
|
|
1654
|
+
*
|
|
1655
|
+
* The depth reading is the part worth having. `linearDepth` answers in a normalised 0..1 that
|
|
1656
|
+
* changes meaning with every camera near/far pair, so a material that subtracts two of them gets
|
|
1657
|
+
* a number in no unit at all, and its shoreline moves when the camera's far plane does.
|
|
1658
|
+
* `thicknessAt` returns metres, and metres survive the camera changing.
|
|
1659
|
+
*
|
|
1660
|
+
* ```js
|
|
1661
|
+
* const surface = new WaterSurface3D({ level: 0, maxThickness: 4, reflection: { resolutionScale: 0.5 } });
|
|
1662
|
+
* const offset = normal.xz.mul(0.02);
|
|
1663
|
+
* material.colorNode = mix(
|
|
1664
|
+
* surface.refractionAt(offset).mul(siltTint), // the game's colours,
|
|
1665
|
+
* surface.reflectionAt(offset), // composed by the game,
|
|
1666
|
+
* fresnel, // with the game's own fresnel.
|
|
1667
|
+
* );
|
|
1668
|
+
* material.opacityNode = surface.thicknessAt().div(surface.maxThickness);
|
|
1669
|
+
* ```
|
|
1670
|
+
*/
|
|
1671
|
+
declare class WaterSurface3D {
|
|
1672
|
+
#private;
|
|
1673
|
+
readonly maxThickness: number;
|
|
1674
|
+
/**
|
|
1675
|
+
* The object whose plane is mirrored, kept outside the scene graph on purpose.
|
|
1676
|
+
*
|
|
1677
|
+
* A water level is a fact about the world, not about the mesh that happens to draw it: parent
|
|
1678
|
+
* the mirror to the surface mesh, as three's own examples do, and a non-uniform scale anywhere
|
|
1679
|
+
* up that mesh's ancestry skews the plane the reflection is taken about. This one is placed
|
|
1680
|
+
* from `level` and nothing else, so it stays level.
|
|
1681
|
+
*/
|
|
1682
|
+
readonly target: Object3D | undefined;
|
|
1683
|
+
constructor(options: IWaterSurfaceOptions);
|
|
1684
|
+
/** The world-space height of the surface, in metres. */
|
|
1685
|
+
get level(): number;
|
|
1686
|
+
/** Move the surface — a tide, a sluice, a flooding room. The mirror plane follows. */
|
|
1687
|
+
setLevel(value: number): void;
|
|
1688
|
+
get released(): boolean;
|
|
1689
|
+
/**
|
|
1690
|
+
* The world mirrored in the surface, at an optional screen-space offset.
|
|
1691
|
+
*
|
|
1692
|
+
* The offset is where a game spends its surface normal: a still mirror takes none, and ripples
|
|
1693
|
+
* are the normal's horizontal part scaled by however far the game wants the reflection to slide.
|
|
1694
|
+
*/
|
|
1695
|
+
reflectionAt(offset?: Node<"vec2">): Node<"vec3">;
|
|
1696
|
+
/**
|
|
1697
|
+
* The frame beneath the surface — everything already drawn this frame, read at an offset.
|
|
1698
|
+
*
|
|
1699
|
+
* An offset that lands on something **in front of** the water is refused and the fragment falls
|
|
1700
|
+
* back to a straight read. Without that, a rock standing in the shallows smears across the
|
|
1701
|
+
* water in front of it: the classic refraction bleed, and the reason a hand-rolled offset looks
|
|
1702
|
+
* wrong the first time every game writes one.
|
|
1703
|
+
*/
|
|
1704
|
+
refractionAt(offset?: Node<"vec2">): Node<"vec3">;
|
|
1705
|
+
/**
|
|
1706
|
+
* Metres of water between this fragment and whatever is drawn behind it, clamped to
|
|
1707
|
+
* `maxThickness`. Zero exactly where the bed meets the surface, which is the shoreline.
|
|
1708
|
+
*
|
|
1709
|
+
* Sky behind the surface has no depth and reads as `maxThickness`, not as zero: an unbounded
|
|
1710
|
+
* horizon is deep water, not dry land.
|
|
1711
|
+
*/
|
|
1712
|
+
thicknessAt(offset?: Node<"vec2">): Node<"float">;
|
|
1713
|
+
/** Drop the mirrored pass and its render target. */
|
|
1714
|
+
dispose(): void;
|
|
1185
1715
|
}
|
|
1186
1716
|
|
|
1187
1717
|
/**
|
|
@@ -1491,25 +2021,165 @@ declare class SpriteAnimator3D {
|
|
|
1491
2021
|
setFrame(index: number): this;
|
|
1492
2022
|
}
|
|
1493
2023
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
2024
|
+
/** Names of every bone in traversal order. Empty for an unskinned model. */
|
|
2025
|
+
declare function skeletonBones(root: Object3D): readonly string[];
|
|
2026
|
+
/** Parent a child to a named bone while preserving the child's authored world scale. */
|
|
2027
|
+
declare function attachToBone(root: Object3D, boneName: string, child: Object3D): Object3D;
|
|
2028
|
+
/** How far a bone sits from the object it is supposed to be touching. */
|
|
2029
|
+
interface IBoneContactReport {
|
|
2030
|
+
readonly bone: string;
|
|
2031
|
+
readonly target: string;
|
|
2032
|
+
/** Metres from the bone to the nearest point of the target's world bounds. Zero when inside. */
|
|
2033
|
+
readonly distance: number;
|
|
2034
|
+
readonly inside: boolean;
|
|
2035
|
+
readonly bonePosition: ThreePoseVector;
|
|
2036
|
+
readonly targetPoint: ThreePoseVector;
|
|
1500
2037
|
}
|
|
1501
2038
|
/**
|
|
1502
|
-
*
|
|
2039
|
+
* Measure whether a named bone reaches a game object, in metres.
|
|
1503
2040
|
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
2041
|
+
* This turns "his hands are on the keyboard" and "he is sitting on the chair" into numbers a
|
|
2042
|
+
* scenario can assert. It walks the target's vertices through `measureThreePose`, so call it on
|
|
2043
|
+
* a check or a debug sample rather than every frame.
|
|
1506
2044
|
*/
|
|
1507
|
-
declare function
|
|
2045
|
+
declare function boneContact(root: Object3D, boneName: string, target: Object3D): IBoneContactReport;
|
|
1508
2046
|
|
|
1509
|
-
/**
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2047
|
+
/** One track of a clip, and whether it resolves to a real property on a root. */
|
|
2048
|
+
interface IClipTrackBinding {
|
|
2049
|
+
readonly track: string;
|
|
2050
|
+
/** The node the track names, or `null` when nothing under the root carries that name. */
|
|
2051
|
+
readonly node: string | null;
|
|
2052
|
+
readonly bound: boolean;
|
|
2053
|
+
/** Three.js's own reason the track binds nothing. `null` when it binds. */
|
|
2054
|
+
readonly reason: string | null;
|
|
2055
|
+
}
|
|
2056
|
+
interface IClipBindingReport {
|
|
2057
|
+
readonly clip: string;
|
|
2058
|
+
readonly tracks: number;
|
|
2059
|
+
readonly bound: number;
|
|
2060
|
+
/** Every track that drives nothing, in clip order. */
|
|
2061
|
+
readonly unbound: readonly IClipTrackBinding[];
|
|
2062
|
+
}
|
|
2063
|
+
interface IClipCoverageReport {
|
|
2064
|
+
readonly clip: string;
|
|
2065
|
+
readonly bones: number;
|
|
2066
|
+
readonly driven: readonly string[];
|
|
2067
|
+
/** Bones no bound track touches. They hold whatever the previous clip left them in. */
|
|
2068
|
+
readonly undriven: readonly string[];
|
|
2069
|
+
}
|
|
2070
|
+
interface IClipPoseSubject {
|
|
2071
|
+
readonly root: Object3D;
|
|
2072
|
+
readonly clip: AnimationClip;
|
|
2073
|
+
}
|
|
2074
|
+
interface IClipPoseErrorOptions {
|
|
2075
|
+
/** Measured bone name to reference bone name. Defaults to the names the two rigs share. */
|
|
2076
|
+
readonly bones?: Readonly<Record<string, string>>;
|
|
2077
|
+
/** Poses compared across the clip. Defaults to 8. */
|
|
2078
|
+
readonly samples?: number;
|
|
2079
|
+
}
|
|
2080
|
+
interface IBonePoseError {
|
|
2081
|
+
readonly bone: string;
|
|
2082
|
+
readonly reference: string;
|
|
2083
|
+
readonly meanDegrees: number;
|
|
2084
|
+
readonly maxDegrees: number;
|
|
2085
|
+
readonly maxAtSeconds: number;
|
|
2086
|
+
}
|
|
2087
|
+
interface IClipPoseErrorReport {
|
|
2088
|
+
readonly clip: string;
|
|
2089
|
+
readonly referenceClip: string;
|
|
2090
|
+
readonly samples: number;
|
|
2091
|
+
readonly meanDegrees: number;
|
|
2092
|
+
readonly maxDegrees: number;
|
|
2093
|
+
/** Every compared bone, worst mean first. */
|
|
2094
|
+
readonly bones: readonly IBonePoseError[];
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Report which of a clip's tracks resolve to a real property on `root`.
|
|
2098
|
+
*
|
|
2099
|
+
* A retarget that writes the wrong glTF target path produces tracks named `<bone>.undefined`:
|
|
2100
|
+
* they load without error, bind nothing, and the character plays its bind pose.
|
|
2101
|
+
*/
|
|
2102
|
+
declare function clipTrackBindings(root: Object3D, clip: AnimationClip): IClipBindingReport;
|
|
2103
|
+
/**
|
|
2104
|
+
* Report which bones of `root` a clip does not drive.
|
|
2105
|
+
*
|
|
2106
|
+
* An undriven bone keeps whatever the previous clip left it in, so a rig whose clips cover 22 of
|
|
2107
|
+
* 65 bones carries the last walk cycle's hand shape into every pose that follows.
|
|
2108
|
+
*/
|
|
2109
|
+
declare function clipBoneCoverage(root: Object3D, clip: AnimationClip): IClipCoverageReport;
|
|
2110
|
+
/**
|
|
2111
|
+
* Score how far a clip's pose is from the same pose on a reference rig, in degrees.
|
|
2112
|
+
*
|
|
2113
|
+
* Each bone is compared as its world rotation *relative to its own rig's bind pose*, as a whole
|
|
2114
|
+
* quaternion. The delta makes the two rigs' bind conventions cancel, so rigs whose arms sit 90
|
|
2115
|
+
* degrees apart at rest still score zero when the retarget is right; the whole quaternion makes
|
|
2116
|
+
* twist count, which a bone-direction check cannot see — a forearm rolled about its own axis
|
|
2117
|
+
* points exactly where it should while the skin between elbow and wrist tears into a smear.
|
|
2118
|
+
*
|
|
2119
|
+
* Both rigs are driven and then restored to the transforms they arrived with.
|
|
2120
|
+
*/
|
|
2121
|
+
declare function clipPoseError(subject: IClipPoseSubject, reference: IClipPoseSubject, options?: IClipPoseErrorOptions): IClipPoseErrorReport;
|
|
2122
|
+
|
|
2123
|
+
/**
|
|
2124
|
+
* One rig's parent→child bone distances, captured at one moment.
|
|
2125
|
+
*
|
|
2126
|
+
* A rigid skeleton preserves every parent→child distance under any pose — that is what makes
|
|
2127
|
+
* bone length the one number that names a broken pose without a screenshot (PRD-324).
|
|
2128
|
+
*/
|
|
2129
|
+
interface IBoneLengthSnapshot {
|
|
2130
|
+
readonly bones: number;
|
|
2131
|
+
/** Bone name → world distance to its parent bone, in metres. Bones without a bone parent are absent. */
|
|
2132
|
+
readonly lengths: Readonly<Record<string, number>>;
|
|
2133
|
+
}
|
|
2134
|
+
/** One bone whose parent→child distance changed between the snapshot and now. */
|
|
2135
|
+
interface IBoneLengthDeviation {
|
|
2136
|
+
readonly bone: string;
|
|
2137
|
+
/** The distance at the snapshot, in metres. */
|
|
2138
|
+
readonly bindLength: number;
|
|
2139
|
+
/** The distance now, in metres. */
|
|
2140
|
+
readonly posedLength: number;
|
|
2141
|
+
/** `posedLength / bindLength`; `Infinity` when the bind distance was zero. */
|
|
2142
|
+
readonly ratio: number;
|
|
2143
|
+
/** `|posedLength − bindLength|`, in metres. */
|
|
2144
|
+
readonly delta: number;
|
|
2145
|
+
}
|
|
2146
|
+
interface IBoneLengthDeviationReport {
|
|
2147
|
+
readonly bones: number;
|
|
2148
|
+
/** Bones actually compared: named in both the snapshot and now. */
|
|
2149
|
+
readonly compared: number;
|
|
2150
|
+
/** The largest relative change across compared bones. Zero for a rigid pose. */
|
|
2151
|
+
readonly maxDeviation: number;
|
|
2152
|
+
/** The worst deviating bone, or `null` when the pose is rigid. */
|
|
2153
|
+
readonly worst: IBoneLengthDeviation | null;
|
|
2154
|
+
/** Every bone past `tolerance`, worst first. */
|
|
2155
|
+
readonly deviations: readonly IBoneLengthDeviation[];
|
|
2156
|
+
/** True when no bone moved past `tolerance` relative to its bind distance. */
|
|
2157
|
+
readonly rigid: boolean;
|
|
2158
|
+
}
|
|
2159
|
+
interface IBoneLengthDeviationsOptions {
|
|
2160
|
+
/**
|
|
2161
|
+
* Allowed relative change per bone before it is named. Default `0.01` — one per cent of its
|
|
2162
|
+
* own bind length, which float noise sits far below and a pose defect sits far above.
|
|
2163
|
+
*/
|
|
2164
|
+
readonly tolerance?: number;
|
|
2165
|
+
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Measure every parent→child bone distance, in world space, as the rig stands right now.
|
|
2168
|
+
*
|
|
2169
|
+
* World distances, so a uniform ancestor scale is part of the number: capture the baseline and
|
|
2170
|
+
* the comparison under the same ancestor transform (in the usual shape, both after the rig's
|
|
2171
|
+
* normalisation) and the scale cancels in the comparison.
|
|
2172
|
+
*/
|
|
2173
|
+
declare function boneLengths(root: Object3D): IBoneLengthSnapshot;
|
|
2174
|
+
/**
|
|
2175
|
+
* Compare a rig's parent→child bone distances now against a captured snapshot.
|
|
2176
|
+
*
|
|
2177
|
+
* A rigid skeleton preserves every parent→child distance under any pose, so any named bone is
|
|
2178
|
+
* a defect with an address: something moved a bone away from its parent — a position or scale
|
|
2179
|
+
* track in the wrong space, a bind mismatch, a clipped hierarchy — and no screenshot needs to
|
|
2180
|
+
* be squinted at to say which one (PRD-324).
|
|
2181
|
+
*/
|
|
2182
|
+
declare function boneLengthDeviations(root: Object3D, bind: IBoneLengthSnapshot, options?: IBoneLengthDeviationsOptions): IBoneLengthDeviationReport;
|
|
1513
2183
|
|
|
1514
2184
|
/**
|
|
1515
2185
|
* The version this library reports.
|
|
@@ -1519,6 +2189,6 @@ declare function attachToBone(root: Object3D, boneName: string, child: Object3D)
|
|
|
1519
2189
|
* unavoidable here — core is bundled for browsers and cannot read `package.json` at runtime — so
|
|
1520
2190
|
* the spec now asserts this equals the manifest instead of asserting a number somebody typed.
|
|
1521
2191
|
*/
|
|
1522
|
-
declare const version = "0.3.
|
|
2192
|
+
declare const version = "0.3.1";
|
|
1523
2193
|
|
|
1524
|
-
export { ATLAS_PADDING, ATMOSPHERE_LUT_RESOLUTIONS, AnimationPlayer, Atmosphere, type AtmosphereDirection, AtmosphereLuts, type AtmosphereRgb, Billboard3D, type BillboardLockAxis, CameraShake, type CameraShakeCurve, ClusteredBatch, ClusteredMesh,
|
|
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 IPlatformInfo, type IProbeVolumeBakeProgress, type IProbeVolumeCoefficient, type IProbeVolumeObservation, type IProbeVolumeOptions, type IReplayOptions, type IReplayRecording, type IReplayRecordingSample, type IResolvedAtmosphereParameters, type ISkeletalMesh3DOptions, type ISoftBody3DOptions, type ISoftBodyCollision, type ISolarPosition, type ISolarPositionInput, type ISpectralOceanCascade, type ISpectralOceanHeight, type ISpectralOceanOptions, type ISpriteAnimator3DOptions, type ISpriteFrame3D, type IStrideReport, type IThreePoseBounds, type IThreePoseMeasurement, type ITracerPool3DOptions, type ITracerSpawnOptions, type IVirtualShadowOptions, type IVirtualShadowStats, type IWaterReflectionOptions, type IWaterSurfaceOptions, type IWaveFieldDomainWarp, type IWaveFieldGraphOptions, type IWaveFieldOptions, type IWaveFieldSample, type IWaveFieldWave, InstancedBatch, LUT_RESOLUTIONS, type NormaliseAxis, PROBE_VOLUME_MARKER, PathFollow3D, type PlatformFormFactor, type PlatformOS, type PlatformRuntime, ProbeVolume, type ProbeVolumeDensity, type Recording, type ReplayPointer, SkeletalMesh3D, SoftBody3D, SpectralOcean, SpriteAnimator3D, type SpritePlaybackMode, type ThreePoseQuaternion, type ThreePoseVector, TracerPool3D, VIRTUAL_SHADOW_MARKER, VIRTUAL_SHADOW_MOVER_LAYER, VirtualShadowNode, WaterSurface3D, type WaveDirection, WaveField, addInSlices, attachToBone, boneContact, boneLengthDeviations, boneLengths, bvhIntersectFirstHit, clipBoneCoverage, clipPoseError, clipTrackBindings, createReplayDriver, directionFromSolarPosition, directionalTransmittance, getPlatform, isMobile, isNative, isTouchscreenAvailable, isWeb, loadAll, measureThreePose, mergeParts, normaliseToMetres, parseReplayRecording, posedBounds, rayStruct, readProbeVolumeObservation, readVirtualShadowMarker, replay, resolveAtmosphereLutResolutions, resolveAtmosphereParameters, skeletonBones, softCircleDataTexture, solarPosition, solarPositionAt, updateAtmosphereParameters, updateClusteredMeshes, version, zenithTransmittance };
|