@expofp/renderer 0.0.0-experimental.2d6c681
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 +145 -0
- package/dist/Renderer.d.ts +102 -0
- package/dist/Renderer.js +96 -0
- package/dist/controls/CameraControls.d.ts +108 -0
- package/dist/controls/CameraControls.js +203 -0
- package/dist/controls/Controls.d.ts +53 -0
- package/dist/controls/Controls.js +54 -0
- package/dist/controls/camera-api.d.ts +226 -0
- package/dist/controls/camera-api.js +179 -0
- package/dist/controls/gestures.d.ts +71 -0
- package/dist/controls/gestures.js +98 -0
- package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
- package/dist/controls/handlers/DoubleTapZoom.js +82 -0
- package/dist/controls/handlers/Inertia.d.ts +60 -0
- package/dist/controls/handlers/Inertia.js +222 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +212 -0
- package/dist/controls/handlers/Pitch.d.ts +19 -0
- package/dist/controls/handlers/Pitch.js +200 -0
- package/dist/controls/handlers/Roll.d.ts +30 -0
- package/dist/controls/handlers/Roll.js +196 -0
- package/dist/controls/handlers/Zoom.d.ts +28 -0
- package/dist/controls/handlers/Zoom.js +34 -0
- package/dist/controls/hooks.d.ts +136 -0
- package/dist/controls/hooks.js +200 -0
- package/dist/controls/index.d.ts +9 -0
- package/dist/controls/index.js +7 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +108 -0
- package/dist/core/InstanceUniforms.js +298 -0
- package/dist/core/SquareDataTexture.d.ts +277 -0
- package/dist/core/SquareDataTexture.js +476 -0
- package/dist/core/batch/BatchedMesh.d.ts +74 -0
- package/dist/core/batch/BatchedMesh.js +155 -0
- package/dist/core/instanced/FrustumCulling.d.ts +141 -0
- package/dist/core/instanced/FrustumCulling.js +246 -0
- package/dist/core/instanced/InstancedMesh.d.ts +302 -0
- package/dist/core/instanced/InstancedMesh.js +497 -0
- package/dist/core/instanced/Instances.d.ts +119 -0
- package/dist/core/instanced/Instances.js +230 -0
- package/dist/core/instanced/Raycasting.d.ts +118 -0
- package/dist/core/instanced/Raycasting.js +134 -0
- package/dist/core/instanced/instanced-material.d.ts +77 -0
- package/dist/core/instanced/instanced-material.js +228 -0
- package/dist/core/lines/BatchedLines.d.ts +137 -0
- package/dist/core/lines/BatchedLines.js +215 -0
- package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
- package/dist/core/lines/FrustumCullingLines.js +85 -0
- package/dist/core/lines/RaycastingLines.d.ts +45 -0
- package/dist/core/lines/RaycastingLines.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +124 -0
- package/dist/core/shader-source.d.ts +70 -0
- package/dist/core/shader-source.js +110 -0
- package/dist/core/sprites/BatchedSprites.d.ts +115 -0
- package/dist/core/sprites/BatchedSprites.js +173 -0
- package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
- package/dist/core/sprites/RaycastingSprites.js +71 -0
- package/dist/core/sprites/sprite-material.d.ts +59 -0
- package/dist/core/sprites/sprite-material.js +192 -0
- package/dist/core/text/BatchedText.d.ts +233 -0
- package/dist/core/text/BatchedText.js +473 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +289 -0
- package/dist/core/text/Text.d.ts +88 -0
- package/dist/core/text/Text.js +230 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +123 -0
- package/dist/core/text/glyph-packing.d.ts +17 -0
- package/dist/core/text/glyph-packing.js +61 -0
- package/dist/core/typed-array.d.ts +16 -0
- package/dist/core/typed-array.js +20 -0
- package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
- package/dist/debug/components/DebugPanelProvider.js +50 -0
- package/dist/debug/components/DebugTools.d.ts +22 -0
- package/dist/debug/components/DebugTools.js +28 -0
- package/dist/debug/components/Gui.d.ts +22 -0
- package/dist/debug/components/Gui.js +42 -0
- package/dist/debug/components/MemoryInfo.d.ts +22 -0
- package/dist/debug/components/MemoryInfo.js +42 -0
- package/dist/debug/components/Panel.d.ts +31 -0
- package/dist/debug/components/Panel.js +104 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-chunk.d.ts +26 -0
- package/dist/debug/debug-chunk.js +32 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +41 -0
- package/dist/debug/index.js +43 -0
- package/dist/events/LayoutDriver.d.ts +36 -0
- package/dist/events/LayoutDriver.js +192 -0
- package/dist/events/PointerMissed.d.ts +23 -0
- package/dist/events/PointerMissed.js +58 -0
- package/dist/events/def-culling.d.ts +46 -0
- package/dist/events/def-culling.js +69 -0
- package/dist/events/layout-targets.d.ts +151 -0
- package/dist/events/layout-targets.js +397 -0
- package/dist/events/placement/box.d.ts +59 -0
- package/dist/events/placement/box.js +96 -0
- package/dist/events/placement/grid.d.ts +161 -0
- package/dist/events/placement/grid.js +235 -0
- package/dist/events/placement/index.d.ts +2 -0
- package/dist/events/placement/index.js +1 -0
- package/dist/events/placement/pass.d.ts +70 -0
- package/dist/events/placement/pass.js +617 -0
- package/dist/events/pointer.d.ts +194 -0
- package/dist/events/pointer.js +356 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +177 -0
- package/dist/external/external-camera.d.ts +18 -0
- package/dist/external/external-camera.js +103 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/containers/SlotContainer.d.ts +312 -0
- package/dist/objects/containers/SlotContainer.js +311 -0
- package/dist/objects/containers/hooks.d.ts +61 -0
- package/dist/objects/containers/hooks.js +196 -0
- package/dist/objects/containers/index.d.ts +6 -0
- package/dist/objects/containers/index.js +4 -0
- package/dist/objects/containers/keys.d.ts +10 -0
- package/dist/objects/containers/keys.js +18 -0
- package/dist/objects/containers/mesh-writes.d.ts +106 -0
- package/dist/objects/containers/mesh-writes.js +97 -0
- package/dist/objects/containers/props.d.ts +33 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +152 -0
- package/dist/objects/images/Images.d.ts +49 -0
- package/dist/objects/images/Images.js +394 -0
- package/dist/objects/images/atlas-packer.d.ts +129 -0
- package/dist/objects/images/atlas-packer.js +287 -0
- package/dist/objects/images/image-container.d.ts +74 -0
- package/dist/objects/images/image-container.js +100 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/pack-sources.d.ts +190 -0
- package/dist/objects/images/pack-sources.js +272 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
- package/dist/objects/images/worker/atlas-worker.js +123 -0
- package/dist/objects/images/worker/client.d.ts +62 -0
- package/dist/objects/images/worker/client.js +249 -0
- package/dist/objects/images/worker/index.d.ts +10 -0
- package/dist/objects/images/worker/index.js +1 -0
- package/dist/objects/layer/Layer.d.ts +28 -0
- package/dist/objects/layer/Layer.js +134 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/layer-context.d.ts +74 -0
- package/dist/objects/layer/layer-context.js +82 -0
- package/dist/objects/layer/mount-scheduler.d.ts +131 -0
- package/dist/objects/layer/mount-scheduler.js +289 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -0
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +384 -0
- package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
- package/dist/objects/lighting/OcclusionPasses.js +271 -0
- package/dist/objects/lighting/index.d.ts +11 -0
- package/dist/objects/lighting/index.js +10 -0
- package/dist/objects/lighting/lighting-gui.d.ts +24 -0
- package/dist/objects/lighting/lighting-gui.js +114 -0
- package/dist/objects/lighting/occluder.d.ts +34 -0
- package/dist/objects/lighting/occluder.js +62 -0
- package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
- package/dist/objects/lighting/occlusion-targets.js +276 -0
- package/dist/objects/lines/LineInstance.d.ts +21 -0
- package/dist/objects/lines/LineInstance.js +73 -0
- package/dist/objects/lines/Lines.d.ts +17 -0
- package/dist/objects/lines/Lines.js +31 -0
- package/dist/objects/lines/index.d.ts +2 -0
- package/dist/objects/lines/index.js +1 -0
- package/dist/objects/lines/line-container.d.ts +35 -0
- package/dist/objects/lines/line-container.js +40 -0
- package/dist/objects/materials/ColorMaterial.d.ts +92 -0
- package/dist/objects/materials/ColorMaterial.js +124 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +74 -0
- package/dist/objects/materials/ModelMaterial.d.ts +53 -0
- package/dist/objects/materials/ModelMaterial.js +111 -0
- package/dist/objects/materials/TextureMaterial.d.ts +33 -0
- package/dist/objects/materials/TextureMaterial.js +48 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
- package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
- package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
- package/dist/objects/materials/effects/ao/index.d.ts +129 -0
- package/dist/objects/materials/effects/ao/index.js +72 -0
- package/dist/objects/materials/effects/color-dim.d.ts +115 -0
- package/dist/objects/materials/effects/color-dim.js +206 -0
- package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
- package/dist/objects/materials/effects/contact-shadow.js +167 -0
- package/dist/objects/materials/effects/effects-context.d.ts +53 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/materials/shared.d.ts +24 -0
- package/dist/objects/materials/shared.js +72 -0
- package/dist/objects/models/ModelInstance.d.ts +22 -0
- package/dist/objects/models/ModelInstance.js +33 -0
- package/dist/objects/models/Models.d.ts +22 -0
- package/dist/objects/models/Models.js +186 -0
- package/dist/objects/models/gltf-cache.d.ts +17 -0
- package/dist/objects/models/gltf-cache.js +50 -0
- package/dist/objects/models/index.d.ts +2 -0
- package/dist/objects/models/index.js +1 -0
- package/dist/objects/models/model-asset.d.ts +48 -0
- package/dist/objects/models/model-asset.js +77 -0
- package/dist/objects/models/model-container.d.ts +123 -0
- package/dist/objects/models/model-container.js +207 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +39 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +131 -0
- package/dist/objects/shapes/index.d.ts +2 -0
- package/dist/objects/shapes/index.js +1 -0
- package/dist/objects/shapes/shape-container.d.ts +141 -0
- package/dist/objects/shapes/shape-container.js +242 -0
- package/dist/objects/shapes/shape-geometry.d.ts +27 -0
- package/dist/objects/shapes/shape-geometry.js +94 -0
- package/dist/objects/text/TextInstance.d.ts +22 -0
- package/dist/objects/text/TextInstance.js +95 -0
- package/dist/objects/text/Texts.d.ts +30 -0
- package/dist/objects/text/Texts.js +43 -0
- package/dist/objects/text/index.d.ts +2 -0
- package/dist/objects/text/index.js +1 -0
- package/dist/objects/text/text-container.d.ts +121 -0
- package/dist/objects/text/text-container.js +229 -0
- package/dist/objects/text/text-layout.d.ts +92 -0
- package/dist/objects/text/text-layout.js +180 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +47 -0
- package/dist/space/Scene.js +127 -0
- package/dist/space/Space.d.ts +325 -0
- package/dist/space/Space.js +359 -0
- package/dist/space/VisibleRect.d.ts +45 -0
- package/dist/space/VisibleRect.js +129 -0
- package/dist/space/camera-handle.d.ts +67 -0
- package/dist/space/camera-handle.js +63 -0
- package/dist/space/camera-kind.d.ts +26 -0
- package/dist/space/camera-kind.js +30 -0
- package/dist/space/canvas-size.d.ts +24 -0
- package/dist/space/canvas-size.js +20 -0
- package/dist/space/clipping.d.ts +69 -0
- package/dist/space/clipping.js +130 -0
- package/dist/space/layout-measure.d.ts +67 -0
- package/dist/space/layout-measure.js +175 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +234 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +198 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +21 -0
- package/dist/types/guards.js +52 -0
- package/dist/update/index.d.ts +3 -0
- package/dist/update/index.js +2 -0
- package/dist/update/slot-writers.d.ts +30 -0
- package/dist/update/slot-writers.js +76 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +104 -0
- package/dist/utils/arrays.d.ts +13 -0
- package/dist/utils/arrays.js +20 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +27 -0
- package/dist/utils/hot-state.d.ts +41 -0
- package/dist/utils/hot-state.js +37 -0
- package/dist/utils/logging.d.ts +29 -0
- package/dist/utils/logging.js +55 -0
- package/dist/utils/math.d.ts +52 -0
- package/dist/utils/math.js +76 -0
- package/package.json +79 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { addAfterEffect, useThree } from '@react-three/fiber';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import StatsGl from 'stats-gl';
|
|
4
|
+
/**
|
|
5
|
+
* Performance-stats overlay (frame rate, GPU timing, refresh rate) attached to
|
|
6
|
+
* the document body and refreshed after every rendered frame.
|
|
7
|
+
*
|
|
8
|
+
* The overlay is mounted imperatively, so the component renders nothing.
|
|
9
|
+
* @returns `null`.
|
|
10
|
+
*/
|
|
11
|
+
export function Stats() {
|
|
12
|
+
const gl = useThree((state) => state.gl);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const stats = new StatsGl({ trackGPU: true, trackHz: true });
|
|
15
|
+
void stats.init(gl);
|
|
16
|
+
document.body.appendChild(stats.domElement);
|
|
17
|
+
const removeAfterEffect = addAfterEffect(() => stats.update());
|
|
18
|
+
return () => {
|
|
19
|
+
stats.dispose();
|
|
20
|
+
removeAfterEffect();
|
|
21
|
+
};
|
|
22
|
+
}, [gl]);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one debug chunk, and the two ways into it.
|
|
3
|
+
*
|
|
4
|
+
* Beside `index.tsx` rather than in it, because Fast Refresh swaps a module in place only when every
|
|
5
|
+
* value it exports is a component. `prepareDebug` is this package's public API and cannot stop being
|
|
6
|
+
* exported, so it moves instead.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Imports the debug chunk, memoized so `use()` sees a stable promise and it is fetched once.
|
|
10
|
+
*
|
|
11
|
+
* Shared by every consumer: the internal mount's context-patch gate, the overlay in both mounts'
|
|
12
|
+
* trees, and {@link prepareDebug}.
|
|
13
|
+
* @returns The import promise, the same one each call.
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadDebugChunk(): Promise<unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Loads the debug chunk ahead of time — for a host that creates its own WebGL context.
|
|
18
|
+
*
|
|
19
|
+
* The chunk's evaluation is what instruments context creation for the GPU-memory panel
|
|
20
|
+
* (webgl-memory patches `getContext` as it loads), so a context created before it exists can never
|
|
21
|
+
* report memory figures. The renderer's own mount orders this itself; a MapLibre host does it by
|
|
22
|
+
* awaiting this before constructing the map, when debug is on. Idempotent, and the only eager cost
|
|
23
|
+
* is the call: the debug code still lives in its own lazy chunk.
|
|
24
|
+
*/
|
|
25
|
+
export declare function prepareDebug(): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=debug-chunk.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one debug chunk, and the two ways into it.
|
|
3
|
+
*
|
|
4
|
+
* Beside `index.tsx` rather than in it, because Fast Refresh swaps a module in place only when every
|
|
5
|
+
* value it exports is a component. `prepareDebug` is this package's public API and cannot stop being
|
|
6
|
+
* exported, so it moves instead.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Imports the debug chunk, memoized so `use()` sees a stable promise and it is fetched once.
|
|
10
|
+
*
|
|
11
|
+
* Shared by every consumer: the internal mount's context-patch gate, the overlay in both mounts'
|
|
12
|
+
* trees, and {@link prepareDebug}.
|
|
13
|
+
* @returns The import promise, the same one each call.
|
|
14
|
+
*/
|
|
15
|
+
export function loadDebugChunk() {
|
|
16
|
+
debugChunk ??= import('./components/DebugTools');
|
|
17
|
+
return debugChunk;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Loads the debug chunk ahead of time — for a host that creates its own WebGL context.
|
|
21
|
+
*
|
|
22
|
+
* The chunk's evaluation is what instruments context creation for the GPU-memory panel
|
|
23
|
+
* (webgl-memory patches `getContext` as it loads), so a context created before it exists can never
|
|
24
|
+
* report memory figures. The renderer's own mount orders this itself; a MapLibre host does it by
|
|
25
|
+
* awaiting this before constructing the map, when debug is on. Idempotent, and the only eager cost
|
|
26
|
+
* is the call: the debug code still lives in its own lazy chunk.
|
|
27
|
+
*/
|
|
28
|
+
export async function prepareDebug() {
|
|
29
|
+
await loadDebugChunk();
|
|
30
|
+
}
|
|
31
|
+
/** The memoized import, set by the first {@link loadDebugChunk} call. */
|
|
32
|
+
let debugChunk;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { GUI } from 'lil-gui';
|
|
2
|
+
/**
|
|
3
|
+
* Context carrying the active debug GUI, or `null` when the debug UI is
|
|
4
|
+
* disabled or has not finished loading. Isolated in its own module (with a
|
|
5
|
+
* type-only GUI import) so components can read the GUI without pulling the
|
|
6
|
+
* debug UI bundle into the main chunk.
|
|
7
|
+
*/
|
|
8
|
+
export declare const GuiContext: import("react").Context<GUI | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Reads the active debug GUI from context.
|
|
11
|
+
*
|
|
12
|
+
* Returns `null` while the debug UI is off or still loading, so guard on a
|
|
13
|
+
* non-null value before adding folders or controllers.
|
|
14
|
+
* @returns The current GUI instance, or `null`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const useGui: () => GUI | null;
|
|
17
|
+
/** Write API for the debug panel: components publish key/value entries that the panel combines into one object and renders. */
|
|
18
|
+
export interface DebugPanel {
|
|
19
|
+
/** Adds or updates the entry under `key`. */
|
|
20
|
+
set(key: string, value: unknown): void;
|
|
21
|
+
/** Removes the entry under `key`. */
|
|
22
|
+
remove(key: string): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Context carrying the debug panel's write API, or `null` when the debug UI is
|
|
26
|
+
* disabled. Kept separate from {@link GuiContext} so panel updates never
|
|
27
|
+
* re-render GUI consumers and vice versa.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DebugPanelContext: import("react").Context<DebugPanel | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Returns a setter that publishes a key/value entry to the debug panel.
|
|
32
|
+
*
|
|
33
|
+
* The returned `set(key, value)` is stable and safe to call from anywhere,
|
|
34
|
+
* including a per-frame loop — it never re-renders the calling tree, and the
|
|
35
|
+
* panel itself re-renders only when its combined, serialized value changes.
|
|
36
|
+
* Every key a component writes is removed automatically when it unmounts.
|
|
37
|
+
* Outside the debug provider the setter is a no-op.
|
|
38
|
+
* @returns A `set(key, value)` function for the debug panel.
|
|
39
|
+
*/
|
|
40
|
+
export declare function useDebugPanel(): (key: string, value: unknown) => void;
|
|
41
|
+
//# sourceMappingURL=debug-context.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createContext, use, useEffect, useRef } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Context carrying the active debug GUI, or `null` when the debug UI is
|
|
4
|
+
* disabled or has not finished loading. Isolated in its own module (with a
|
|
5
|
+
* type-only GUI import) so components can read the GUI without pulling the
|
|
6
|
+
* debug UI bundle into the main chunk.
|
|
7
|
+
*/
|
|
8
|
+
export const GuiContext = createContext(null);
|
|
9
|
+
/**
|
|
10
|
+
* Reads the active debug GUI from context.
|
|
11
|
+
*
|
|
12
|
+
* Returns `null` while the debug UI is off or still loading, so guard on a
|
|
13
|
+
* non-null value before adding folders or controllers.
|
|
14
|
+
* @returns The current GUI instance, or `null`.
|
|
15
|
+
*/
|
|
16
|
+
export const useGui = () => use(GuiContext);
|
|
17
|
+
/**
|
|
18
|
+
* Context carrying the debug panel's write API, or `null` when the debug UI is
|
|
19
|
+
* disabled. Kept separate from {@link GuiContext} so panel updates never
|
|
20
|
+
* re-render GUI consumers and vice versa.
|
|
21
|
+
*/
|
|
22
|
+
export const DebugPanelContext = createContext(null);
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
24
|
+
const noop = () => { };
|
|
25
|
+
/**
|
|
26
|
+
* Returns a setter that publishes a key/value entry to the debug panel.
|
|
27
|
+
*
|
|
28
|
+
* The returned `set(key, value)` is stable and safe to call from anywhere,
|
|
29
|
+
* including a per-frame loop — it never re-renders the calling tree, and the
|
|
30
|
+
* panel itself re-renders only when its combined, serialized value changes.
|
|
31
|
+
* Every key a component writes is removed automatically when it unmounts.
|
|
32
|
+
* Outside the debug provider the setter is a no-op.
|
|
33
|
+
* @returns A `set(key, value)` function for the debug panel.
|
|
34
|
+
*/
|
|
35
|
+
export function useDebugPanel() {
|
|
36
|
+
const panel = use(DebugPanelContext);
|
|
37
|
+
// One stable Set per consumer, tracking every key it has published. The
|
|
38
|
+
// compiler memoizes the allocation, so subsequent renders don't churn it; even
|
|
39
|
+
// without the compiler, useRef discards the extra `new Set()` after mount.
|
|
40
|
+
const keysRef = useRef(new Set());
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
// Outside the debug provider there is nothing to clean up.
|
|
43
|
+
if (!panel)
|
|
44
|
+
return;
|
|
45
|
+
// Capture the (stable) Set locally so the cleanup closure doesn't read a ref
|
|
46
|
+
// in teardown. Every key this component published is removed on unmount.
|
|
47
|
+
const tracked = keysRef.current;
|
|
48
|
+
return () => tracked.forEach((key) => panel.remove(key));
|
|
49
|
+
}, [panel]);
|
|
50
|
+
// Non-debug consumers share one stable no-op — no per-consumer closure.
|
|
51
|
+
if (!panel)
|
|
52
|
+
return noop;
|
|
53
|
+
// Stable identity comes from the React Compiler — no hand-written useCallback —
|
|
54
|
+
// so callers can put `set` in effect deps without churn.
|
|
55
|
+
return (key, value) => {
|
|
56
|
+
keysRef.current.add(key);
|
|
57
|
+
panel.set(key, value);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CanvasProps } from '@react-three/fiber';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
/** Props for {@link CanvasWithDebug}: the Canvas props plus the debug toggle. */
|
|
4
|
+
export interface CanvasWithDebugProps extends CanvasProps {
|
|
5
|
+
/** When falsy, the Canvas mounts untouched and no debug code is loaded. */
|
|
6
|
+
debugEnabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The renderer's own Canvas with the debug ordering folded in: when `debugEnabled`, it suspends —
|
|
10
|
+
* into the boundary its parent provides — until the debug chunk has loaded, so the chunk's
|
|
11
|
+
* webgl-memory patch lands *before* the Canvas creates its context and the context is born
|
|
12
|
+
* instrumented. That ordering is this component's whole job, and it has to sit above `<Canvas>`:
|
|
13
|
+
* children mount only after the root is configured, which is after the context exists, so nothing
|
|
14
|
+
* inside the tree could order it. The overlay itself is {@link DebugOverlay}'s, inside the tree,
|
|
15
|
+
* the same in both mounts.
|
|
16
|
+
*
|
|
17
|
+
* Turning `debugEnabled` on at runtime is not supported: React keeps a suspending, already-mounted
|
|
18
|
+
* Canvas alive through the fallback, so the context is never recreated and the patch arrives too
|
|
19
|
+
* late for it. `debug` is derived from a module constant fixed for the session, so this never
|
|
20
|
+
* happens in practice.
|
|
21
|
+
* @param props - `debugEnabled` plus the Canvas props; `children` is the scene.
|
|
22
|
+
* @returns The Canvas, its context creation deferred behind the debug chunk when enabled.
|
|
23
|
+
*/
|
|
24
|
+
export declare function CanvasWithDebug(props: CanvasWithDebugProps): ReactNode;
|
|
25
|
+
/** Props for {@link DebugOverlay}: the toggle and the tree it may instrument. */
|
|
26
|
+
export interface DebugOverlayProps {
|
|
27
|
+
/** When falsy, the children render untouched and no debug code is loaded. */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
/** The scene tree. */
|
|
30
|
+
children?: ReactNode;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Mounts the debug overlay around the scene tree when enabled — the same element in both mounts,
|
|
34
|
+
* which is what makes `debug` a shared prop: the GUI, the panels and the stats attach to the
|
|
35
|
+
* document, not to whichever canvas is drawing. Lazy behind the shared debug chunk; disabled, it
|
|
36
|
+
* is the children and nothing else.
|
|
37
|
+
* @param props - The toggle and the tree.
|
|
38
|
+
* @returns The tree, instrumented when enabled.
|
|
39
|
+
*/
|
|
40
|
+
export declare function DebugOverlay(props: DebugOverlayProps): ReactNode;
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Canvas } from '@react-three/fiber';
|
|
3
|
+
import { lazy, Suspense, use } from 'react';
|
|
4
|
+
import { loadDebugChunk } from './debug-chunk';
|
|
5
|
+
const DebugTools = lazy(() => import('./components/DebugTools').then((module) => ({ default: module.DebugTools })));
|
|
6
|
+
/**
|
|
7
|
+
* The renderer's own Canvas with the debug ordering folded in: when `debugEnabled`, it suspends —
|
|
8
|
+
* into the boundary its parent provides — until the debug chunk has loaded, so the chunk's
|
|
9
|
+
* webgl-memory patch lands *before* the Canvas creates its context and the context is born
|
|
10
|
+
* instrumented. That ordering is this component's whole job, and it has to sit above `<Canvas>`:
|
|
11
|
+
* children mount only after the root is configured, which is after the context exists, so nothing
|
|
12
|
+
* inside the tree could order it. The overlay itself is {@link DebugOverlay}'s, inside the tree,
|
|
13
|
+
* the same in both mounts.
|
|
14
|
+
*
|
|
15
|
+
* Turning `debugEnabled` on at runtime is not supported: React keeps a suspending, already-mounted
|
|
16
|
+
* Canvas alive through the fallback, so the context is never recreated and the patch arrives too
|
|
17
|
+
* late for it. `debug` is derived from a module constant fixed for the session, so this never
|
|
18
|
+
* happens in practice.
|
|
19
|
+
* @param props - `debugEnabled` plus the Canvas props; `children` is the scene.
|
|
20
|
+
* @returns The Canvas, its context creation deferred behind the debug chunk when enabled.
|
|
21
|
+
*/
|
|
22
|
+
export function CanvasWithDebug(props) {
|
|
23
|
+
const { debugEnabled, children, ...canvasProps } = props;
|
|
24
|
+
// `use` is built for conditional calls, and the condition is the point: an undebugged mount
|
|
25
|
+
// never touches the chunk.
|
|
26
|
+
if (debugEnabled)
|
|
27
|
+
use(loadDebugChunk());
|
|
28
|
+
return _jsx(Canvas, { ...canvasProps, children: children });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Mounts the debug overlay around the scene tree when enabled — the same element in both mounts,
|
|
32
|
+
* which is what makes `debug` a shared prop: the GUI, the panels and the stats attach to the
|
|
33
|
+
* document, not to whichever canvas is drawing. Lazy behind the shared debug chunk; disabled, it
|
|
34
|
+
* is the children and nothing else.
|
|
35
|
+
* @param props - The toggle and the tree.
|
|
36
|
+
* @returns The tree, instrumented when enabled.
|
|
37
|
+
*/
|
|
38
|
+
export function DebugOverlay(props) {
|
|
39
|
+
const { enabled, children } = props;
|
|
40
|
+
if (!enabled)
|
|
41
|
+
return children;
|
|
42
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(DebugTools, { children: children }) }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** Props for {@link LayoutDriver}. */
|
|
3
|
+
export interface LayoutDriverProps {
|
|
4
|
+
/** The scene whose defs register as re-layout targets. */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Drives camera-driven re-layout: it owns the renderer's {@link LayoutTargets} and watches both
|
|
9
|
+
* layout axes every frame — the pixel size, swept past an epsilon, and the roll, swept on any
|
|
10
|
+
* change — running each registered def's stale rules and writing back whatever they report changed.
|
|
11
|
+
*
|
|
12
|
+
* It runs at the default frame priority, after the controls have updated the camera at `-1`, so a
|
|
13
|
+
* frame lays out against the camera it is about to draw. And it runs only on frames something asked
|
|
14
|
+
* for: under the on-demand loop the camera's own movement is what requests them, so a still plan
|
|
15
|
+
* costs nothing at all.
|
|
16
|
+
*
|
|
17
|
+
* A move runs every stale rule at once — the cheap half — and starts a **sweep** of the writes: each
|
|
18
|
+
* frame drains the registry's write queue for at most {@link LAYOUT_BUDGET_MS}, visibility flips
|
|
19
|
+
* first and then defs inside the camera's frustum, and asks for another frame while any remain — so
|
|
20
|
+
* a plan of ten thousand labels re-writes over a handful of frames instead of stalling one, and what
|
|
21
|
+
* the user sees change lands first. The writes stay inside the frame callback, before three's own
|
|
22
|
+
* render, so they upload in the very frame that draws them.
|
|
23
|
+
*
|
|
24
|
+
* It also drives the registry's placement pass, which depends on the whole camera: a pan, a pitch
|
|
25
|
+
* or a resize moves every screen box while the pixel size and roll sit still. The pass runs before
|
|
26
|
+
* the drain and before three's render, so a verdict is written in the frame that draws it.
|
|
27
|
+
* {@link runPlacement} decides when it runs.
|
|
28
|
+
*
|
|
29
|
+
* No React is involved in a re-layout. The handlers mutate def fields and the defs' own sync closures
|
|
30
|
+
* write them, so a zoom does not re-render a single component — which is what makes it affordable for
|
|
31
|
+
* every label in a plan to re-lay out while the camera moves.
|
|
32
|
+
* @param props - The scene to provide the registry to.
|
|
33
|
+
* @returns The scene, under the registry.
|
|
34
|
+
*/
|
|
35
|
+
export declare function LayoutDriver(props: LayoutDriverProps): import("react").JSX.Element;
|
|
36
|
+
//# sourceMappingURL=LayoutDriver.d.ts.map
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Point } from '@expofp/geometry';
|
|
3
|
+
import { useFrame, useThree } from '@react-three/fiber';
|
|
4
|
+
import { useRef, useState } from 'react';
|
|
5
|
+
import { Frustum, Matrix4, Vector2, Vector4 } from 'three';
|
|
6
|
+
import { useCameraControls } from '../controls/hooks';
|
|
7
|
+
import { measurePixelSize, measureRoll, pixelSizeMoved } from '../space/layout-measure';
|
|
8
|
+
import { useSpace } from '../space/Space';
|
|
9
|
+
import { planFrustum } from './def-culling';
|
|
10
|
+
import { createLayoutTargets, LayoutTargetsContext } from './layout-targets';
|
|
11
|
+
/**
|
|
12
|
+
* Drives camera-driven re-layout: it owns the renderer's {@link LayoutTargets} and watches both
|
|
13
|
+
* layout axes every frame — the pixel size, swept past an epsilon, and the roll, swept on any
|
|
14
|
+
* change — running each registered def's stale rules and writing back whatever they report changed.
|
|
15
|
+
*
|
|
16
|
+
* It runs at the default frame priority, after the controls have updated the camera at `-1`, so a
|
|
17
|
+
* frame lays out against the camera it is about to draw. And it runs only on frames something asked
|
|
18
|
+
* for: under the on-demand loop the camera's own movement is what requests them, so a still plan
|
|
19
|
+
* costs nothing at all.
|
|
20
|
+
*
|
|
21
|
+
* A move runs every stale rule at once — the cheap half — and starts a **sweep** of the writes: each
|
|
22
|
+
* frame drains the registry's write queue for at most {@link LAYOUT_BUDGET_MS}, visibility flips
|
|
23
|
+
* first and then defs inside the camera's frustum, and asks for another frame while any remain — so
|
|
24
|
+
* a plan of ten thousand labels re-writes over a handful of frames instead of stalling one, and what
|
|
25
|
+
* the user sees change lands first. The writes stay inside the frame callback, before three's own
|
|
26
|
+
* render, so they upload in the very frame that draws them.
|
|
27
|
+
*
|
|
28
|
+
* It also drives the registry's placement pass, which depends on the whole camera: a pan, a pitch
|
|
29
|
+
* or a resize moves every screen box while the pixel size and roll sit still. The pass runs before
|
|
30
|
+
* the drain and before three's render, so a verdict is written in the frame that draws it.
|
|
31
|
+
* {@link runPlacement} decides when it runs.
|
|
32
|
+
*
|
|
33
|
+
* No React is involved in a re-layout. The handlers mutate def fields and the defs' own sync closures
|
|
34
|
+
* write them, so a zoom does not re-render a single component — which is what makes it affordable for
|
|
35
|
+
* every label in a plan to re-lay out while the camera moves.
|
|
36
|
+
* @param props - The scene to provide the registry to.
|
|
37
|
+
* @returns The scene, under the registry.
|
|
38
|
+
*/
|
|
39
|
+
export function LayoutDriver(props) {
|
|
40
|
+
const { children } = props;
|
|
41
|
+
const camera = useThree((state) => state.camera);
|
|
42
|
+
const gl = useThree((state) => state.gl);
|
|
43
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
44
|
+
const controls = useCameraControls();
|
|
45
|
+
const space = useSpace();
|
|
46
|
+
// Seeded from the camera as it stands, so defs registering before the first frame are laid out at
|
|
47
|
+
// the size they will be drawn at rather than at a placeholder. The measurement asks nothing of
|
|
48
|
+
// the controls — absent at this point whatever the mount order, since they publish themselves in
|
|
49
|
+
// an effect and this runs during render — which is also what lets this component be rendered,
|
|
50
|
+
// and tested, without any controls at all.
|
|
51
|
+
const [targets] = useState(() => createLayoutTargets(invalidate, measurePixelSize(camera, space, gl.domElement.width / gl.getPixelRatio()) ?? 1, measureRoll(camera, space, gl.domElement.width / gl.getPixelRatio(), gl.domElement.height / gl.getPixelRatio()) ?? 0));
|
|
52
|
+
// Frame state for the placement debounce. Nothing renders on it.
|
|
53
|
+
const placementRef = useRef({
|
|
54
|
+
seen: new Matrix4(),
|
|
55
|
+
size: new Vector2(),
|
|
56
|
+
frameAt: -Infinity,
|
|
57
|
+
changedAt: -Infinity,
|
|
58
|
+
placedAt: -Infinity,
|
|
59
|
+
});
|
|
60
|
+
useFrame((state) => {
|
|
61
|
+
const { camera, gl, viewport } = state;
|
|
62
|
+
const { width, height } = gl.domElement;
|
|
63
|
+
// Everything the renderer publishes is in CSS pixels, so the device ratio stops here.
|
|
64
|
+
const cssWidth = width / viewport.dpr;
|
|
65
|
+
const cssHeight = height / viewport.dpr;
|
|
66
|
+
// 1. The pixel size, swept past an epsilon against the sweep's own target.
|
|
67
|
+
const pixelSize = measurePixelSize(camera, space, cssWidth, controls);
|
|
68
|
+
if (pixelSize !== undefined && pixelSizeMoved(pixelSize, targets.pixelSize)) {
|
|
69
|
+
targets.applyScale(pixelSize);
|
|
70
|
+
}
|
|
71
|
+
// 2. The roll, swept on any change: the rules step in quarter turns, so most are no-writes.
|
|
72
|
+
const roll = measureRoll(camera, space, cssWidth, cssHeight, controls);
|
|
73
|
+
if (roll !== undefined && roll !== targets.roll)
|
|
74
|
+
targets.applyRoll(roll);
|
|
75
|
+
// 3. The placement pass, ahead of the drain so its verdicts are written this frame.
|
|
76
|
+
const view = {
|
|
77
|
+
space,
|
|
78
|
+
camera,
|
|
79
|
+
pixelSize: targets.pixelSize,
|
|
80
|
+
width: cssWidth,
|
|
81
|
+
height: cssHeight,
|
|
82
|
+
};
|
|
83
|
+
if (runPlacement(targets, placementRef.current, view, performance.now()))
|
|
84
|
+
invalidate();
|
|
85
|
+
// 4. The writes, inside the frustum first, on a budget. A leftover backlog asks for a frame.
|
|
86
|
+
if (!targets.hasWork)
|
|
87
|
+
return;
|
|
88
|
+
const frustum = planFrustum(camera, space.matrix, _frustum);
|
|
89
|
+
if (targets.drain(frustum, LAYOUT_BUDGET_MS))
|
|
90
|
+
invalidate();
|
|
91
|
+
});
|
|
92
|
+
return _jsx(LayoutTargetsContext, { value: targets, children: children });
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Runs the placement pass once the plan has not visibly moved on screen for
|
|
96
|
+
* {@link PLACEMENT_DEBOUNCE_MS}, and at once when a participant registers, leaves or moves. Every
|
|
97
|
+
* pass reports.
|
|
98
|
+
*
|
|
99
|
+
* Visibly means faster than {@link SETTLE_SPEED_PX_PER_S}. The controls' easing tail, which
|
|
100
|
+
* creeps for hundreds of milliseconds after the plan looks still, therefore does not hold the pass.
|
|
101
|
+
* @param targets - The registry.
|
|
102
|
+
* @param state - The debounce state, updated here.
|
|
103
|
+
* @param context - The pass's context.
|
|
104
|
+
* @param now - The clock, in milliseconds.
|
|
105
|
+
* @returns Whether the driver must ask for a frame: a pass is waiting out the debounce.
|
|
106
|
+
*/
|
|
107
|
+
function runPlacement(targets, state, context, now) {
|
|
108
|
+
const { space, camera, width, height } = context;
|
|
109
|
+
const step = Math.min(now - state.frameAt, MAX_STEP_MS);
|
|
110
|
+
const motion = screenMotion(space, camera, state.seen, width, height);
|
|
111
|
+
const resized = state.size.x !== width || state.size.y !== height;
|
|
112
|
+
if (resized || motion > (SETTLE_SPEED_PX_PER_S * step) / 1000)
|
|
113
|
+
state.changedAt = now;
|
|
114
|
+
state.seen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
115
|
+
state.size.set(width, height);
|
|
116
|
+
state.frameAt = now;
|
|
117
|
+
const pending = state.changedAt > state.placedAt;
|
|
118
|
+
const owed = targets.needsPlacement || targets.placementDrifted;
|
|
119
|
+
if (!owed && !(pending && now - state.changedAt >= PLACEMENT_DEBOUNCE_MS))
|
|
120
|
+
return pending;
|
|
121
|
+
state.placedAt = now;
|
|
122
|
+
targets.applyPlacement(context);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* How far the plan moved on screen between the last frame's camera and this one, in CSS pixels:
|
|
127
|
+
* the most that any of a grid of points across the canvas moved. Each point is cast onto the plan
|
|
128
|
+
* through this frame's camera and projected back through the last frame's, so a pan, a zoom, a
|
|
129
|
+
* turn and a tilt all count, by however far they move what is on screen. A point that misses the
|
|
130
|
+
* plan, above the horizon, is skipped, as is one the last frame had behind the camera.
|
|
131
|
+
* @param space - The scene's space.
|
|
132
|
+
* @param camera - This frame's camera, its matrices up to date.
|
|
133
|
+
* @param previous - `projection * viewInverse` as of the last frame.
|
|
134
|
+
* @param cssWidth - The canvas width in CSS pixels.
|
|
135
|
+
* @param cssHeight - The canvas height in CSS pixels.
|
|
136
|
+
* @returns The motion in CSS pixels: 0 for a camera that did not move, and `Infinity` when no
|
|
137
|
+
* point lands on the plan, so a camera that sees none of it settles only by standing still.
|
|
138
|
+
*/
|
|
139
|
+
function screenMotion(space, camera, previous, cssWidth, cssHeight) {
|
|
140
|
+
_motionNow.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
141
|
+
if (_motionNow.equals(previous))
|
|
142
|
+
return 0;
|
|
143
|
+
let motion = -1;
|
|
144
|
+
for (const x of MOTION_SAMPLES) {
|
|
145
|
+
for (const y of MOTION_SAMPLES) {
|
|
146
|
+
const plan = space.ndcToPlan(_motionNdc.set(x, y), camera, _motionPlan);
|
|
147
|
+
if (!plan)
|
|
148
|
+
continue;
|
|
149
|
+
_motionClip.set(plan.x, plan.y, 0, 1).applyMatrix4(space.matrix).applyMatrix4(previous);
|
|
150
|
+
if (_motionClip.w <= 0)
|
|
151
|
+
continue;
|
|
152
|
+
const dx = (_motionClip.x / _motionClip.w - x) * (cssWidth / 2);
|
|
153
|
+
const dy = (_motionClip.y / _motionClip.w - y) * (cssHeight / 2);
|
|
154
|
+
motion = Math.max(motion, Math.hypot(dx, dy));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return motion < 0 ? Infinity : motion;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* How much of a frame one drain may spend writing laid-out defs into their slots, in milliseconds —
|
|
161
|
+
* the same budget the legacy renderer's `UpdatesSystem` drained its update queue with. Small enough
|
|
162
|
+
* to leave a 60fps frame its render; the backlog it leaves is carried into the next frame.
|
|
163
|
+
*/
|
|
164
|
+
const LAYOUT_BUDGET_MS = 3;
|
|
165
|
+
/**
|
|
166
|
+
* How long the view must stay unchanged before the placement pass runs, in milliseconds. Long
|
|
167
|
+
* enough that a drag or a run of wheel events reports once, not on every event.
|
|
168
|
+
*/
|
|
169
|
+
const PLACEMENT_DEBOUNCE_MS = 60;
|
|
170
|
+
/**
|
|
171
|
+
* How fast the plan may move on screen, in CSS pixels a second, and still count as still: one CSS
|
|
172
|
+
* pixel a frame at 60 Hz. Each frame is held against its own time step, so the rule is the same at
|
|
173
|
+
* any frame rate.
|
|
174
|
+
*/
|
|
175
|
+
const SETTLE_SPEED_PX_PER_S = 60;
|
|
176
|
+
/**
|
|
177
|
+
* The longest time step a frame's motion is measured over, in milliseconds. The first frame after an
|
|
178
|
+
* idle loop would otherwise read any jump as slow.
|
|
179
|
+
*/
|
|
180
|
+
const MAX_STEP_MS = 1000 / 30;
|
|
181
|
+
/**
|
|
182
|
+
* Where {@link screenMotion} samples the canvas, in normalized device coordinates along each axis:
|
|
183
|
+
* a 3 by 3 grid reaching nearly to the edges, where a zoom or a turn moves the plan most.
|
|
184
|
+
*/
|
|
185
|
+
const MOTION_SAMPLES = [-0.96, 0, 0.96];
|
|
186
|
+
// Scratch for the drain's frustum, filled and consumed within one frame callback.
|
|
187
|
+
const _frustum = new Frustum();
|
|
188
|
+
// Scratch for screenMotion, filled and consumed within one call.
|
|
189
|
+
const _motionNow = new Matrix4();
|
|
190
|
+
const _motionNdc = new Vector2();
|
|
191
|
+
const _motionPlan = new Point();
|
|
192
|
+
const _motionClip = new Vector4();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type PointerMissedEventData } from '../types/events';
|
|
2
|
+
/** Props for {@link PointerMissed}. */
|
|
3
|
+
export interface PointerMissedProps {
|
|
4
|
+
/** Called for a click that reached no layer handler. */
|
|
5
|
+
onPointerMissed?: (event: PointerMissedEventData) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Publishes the renderer's missed-click handler, wrapped so it reports a plan position rather than
|
|
9
|
+
* the bare DOM event R3F hands out.
|
|
10
|
+
*
|
|
11
|
+
* Two reasons this is a component inside the canvas rather than the `onPointerMissed` prop `<Canvas>`
|
|
12
|
+
* already accepts. The position is only computable here: R3F sets up the pointer ray before deciding
|
|
13
|
+
* a click reached no handler, so the ray is still current when this runs and the scene can be
|
|
14
|
+
* searched along it — which is what a miss needs, since reaching no *handler* is not the same as
|
|
15
|
+
* striking nothing. And the canvas prop is effectively write-once —
|
|
16
|
+
* R3F copies it into its store under `if (!state.onPointerMissed)`, so a caller swapping the handler
|
|
17
|
+
* would keep firing the first one. Setting the store directly re-publishes on every change and clears
|
|
18
|
+
* the slot on unmount.
|
|
19
|
+
* @param props - The handler to publish.
|
|
20
|
+
* @returns Nothing; this renders no scene node.
|
|
21
|
+
*/
|
|
22
|
+
export declare function PointerMissed(props: PointerMissedProps): null;
|
|
23
|
+
//# sourceMappingURL=PointerMissed.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber';
|
|
2
|
+
import { useEffect, useEffectEvent } from 'react';
|
|
3
|
+
import { useSpace } from '../space/Space';
|
|
4
|
+
import { missedPoint } from './pointer';
|
|
5
|
+
/**
|
|
6
|
+
* Publishes the renderer's missed-click handler, wrapped so it reports a plan position rather than
|
|
7
|
+
* the bare DOM event R3F hands out.
|
|
8
|
+
*
|
|
9
|
+
* Two reasons this is a component inside the canvas rather than the `onPointerMissed` prop `<Canvas>`
|
|
10
|
+
* already accepts. The position is only computable here: R3F sets up the pointer ray before deciding
|
|
11
|
+
* a click reached no handler, so the ray is still current when this runs and the scene can be
|
|
12
|
+
* searched along it — which is what a miss needs, since reaching no *handler* is not the same as
|
|
13
|
+
* striking nothing. And the canvas prop is effectively write-once —
|
|
14
|
+
* R3F copies it into its store under `if (!state.onPointerMissed)`, so a caller swapping the handler
|
|
15
|
+
* would keep firing the first one. Setting the store directly re-publishes on every change and clears
|
|
16
|
+
* the slot on unmount.
|
|
17
|
+
* @param props - The handler to publish.
|
|
18
|
+
* @returns Nothing; this renders no scene node.
|
|
19
|
+
*/
|
|
20
|
+
export function PointerMissed(props) {
|
|
21
|
+
const { onPointerMissed } = props;
|
|
22
|
+
const raycaster = useThree((state) => state.raycaster);
|
|
23
|
+
const scene = useThree((state) => state.scene);
|
|
24
|
+
const set = useThree((state) => state.set);
|
|
25
|
+
const space = useSpace();
|
|
26
|
+
/**
|
|
27
|
+
* Reports a missed click at the pointer's plan position.
|
|
28
|
+
*
|
|
29
|
+
* An effect event rather than a closure the publish effect rebuilds: the handler, the scene and
|
|
30
|
+
* the space are read fresh when a click arrives, so none of them is a dependency of the effect
|
|
31
|
+
* below. The space is the one that matters — a resize refits it on every step, and a publish that
|
|
32
|
+
* depended on it wrote the store twice per step, notifying every `useThree` subscriber in the
|
|
33
|
+
* renderer each time.
|
|
34
|
+
* **Clicks only, where R3F counts three events as one.** It dispatches this for `contextmenu` and
|
|
35
|
+
* `dblclick` as readily as for `click`, and a browser fires `contextmenu` on the *press* rather
|
|
36
|
+
* than the release — so a right-drag, which is how a pitch gesture begins, would report a miss
|
|
37
|
+
* before the gesture had moved at all, and a double-click would report a third on top of the two
|
|
38
|
+
* clicks that composed it. Only a real click has missed anything.
|
|
39
|
+
* @param nativeEvent - The DOM event; anything but a click is not one.
|
|
40
|
+
*/
|
|
41
|
+
const handleMissed = useEffectEvent((nativeEvent) => {
|
|
42
|
+
if (nativeEvent.type !== 'click')
|
|
43
|
+
return;
|
|
44
|
+
onPointerMissed?.({
|
|
45
|
+
point: missedPoint(raycaster, scene, space),
|
|
46
|
+
nativeEvent,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
// Republished only when the caller's handler appears or disappears; which function it currently
|
|
50
|
+
// is stays out of the dependencies, like the space, because the effect event reads it at click
|
|
51
|
+
// time.
|
|
52
|
+
const handles = onPointerMissed !== undefined;
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
set({ onPointerMissed: handles ? handleMissed : undefined });
|
|
55
|
+
return () => set({ onPointerMissed: undefined });
|
|
56
|
+
}, [handles, set]);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Rect } from '@expofp/geometry';
|
|
2
|
+
import { type Camera, Frustum, Matrix4, Sphere } from 'three';
|
|
3
|
+
import { type RenderableDef } from '../types/defs';
|
|
4
|
+
/**
|
|
5
|
+
* Encloses a def's bounds in a sphere the frustum can test, rotation-invariant by construction.
|
|
6
|
+
*
|
|
7
|
+
* Ported from the imperative renderer's `computeBoundingSphere` (fp-engine
|
|
8
|
+
* `renderer/src/util/geometry.ts`), which the legacy `UpdatesSystem` culled its update queue with.
|
|
9
|
+
* The sphere is centered at `bounds.center` — the rotation pivot — with a radius reaching the
|
|
10
|
+
* furthest corner of the rendered content, so no rotation can swing the content outside it. An
|
|
11
|
+
* `origin` (an image's anchor) shifts which part of the content hangs off the pivot, which widens
|
|
12
|
+
* the radius but never moves the center.
|
|
13
|
+
* @param bounds - The def's bounds.
|
|
14
|
+
* @param origin - The image anchor in `[0..1, 0..1]`, when the def has one; centered otherwise.
|
|
15
|
+
* @param out - Receives the result.
|
|
16
|
+
* @returns The sphere, in plan space.
|
|
17
|
+
*/
|
|
18
|
+
export declare function boundingSphereOf(bounds: Rect, origin: [number, number] | undefined, out: Sphere): Sphere;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a def is entirely off screen, for the drain that lays visible defs out first.
|
|
21
|
+
*
|
|
22
|
+
* Only text and image defs are tested — the def kinds with one cheap `bounds` enclosure, and the
|
|
23
|
+
* bulk of any plan's re-layout targets (labels, dots, icons, markers). Everything else answers
|
|
24
|
+
* "not culled" and takes the priority pass, exactly as the legacy `UpdatesSystem` treated it.
|
|
25
|
+
* A `true` is a **priority** signal, never a correctness one: a culled def must still be laid out,
|
|
26
|
+
* just after the ones the camera can see.
|
|
27
|
+
* @param def - The def to test.
|
|
28
|
+
* @param frustum - The camera's frustum, in plan space — see {@link planFrustum}.
|
|
29
|
+
* @returns Whether the def can wait.
|
|
30
|
+
*/
|
|
31
|
+
export declare function culledBy(def: RenderableDef, frustum: Frustum): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The camera's frustum brought into plan space, where def bounds live.
|
|
34
|
+
*
|
|
35
|
+
* The same composition the imperative renderer's `computeFrustumInModelSpace` used, with the
|
|
36
|
+
* scene's plan → world matrix standing in for the scene's `matrixWorld`: projecting through
|
|
37
|
+
* `projection · viewInverse · plan` makes the frustum's planes answer for plan coordinates
|
|
38
|
+
* directly, so a def is tested where it is authored and nothing per-def is transformed.
|
|
39
|
+
* @param camera - The camera in use.
|
|
40
|
+
* @param planMatrix - The plan → world matrix, from the scene's `Space`.
|
|
41
|
+
* @param out - A frustum to write into. Omit it and each call allocates, which is what makes the
|
|
42
|
+
* result safe to keep.
|
|
43
|
+
* @returns The frustum.
|
|
44
|
+
*/
|
|
45
|
+
export declare function planFrustum(camera: Camera, planMatrix: Matrix4, out?: Frustum): Frustum;
|
|
46
|
+
//# sourceMappingURL=def-culling.d.ts.map
|