@expofp/renderer 3.2.5 → 3.27.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.
Files changed (228) hide show
  1. package/README.md +111 -0
  2. package/dist/Renderer.d.ts +88 -0
  3. package/dist/Renderer.js +53 -0
  4. package/dist/controls/CameraControls.d.ts +102 -0
  5. package/dist/controls/CameraControls.js +239 -0
  6. package/dist/controls/camera-api.d.ts +206 -0
  7. package/dist/controls/camera-api.js +181 -0
  8. package/dist/controls/camera-move.d.ts +13 -0
  9. package/dist/controls/camera-move.js +18 -0
  10. package/dist/controls/camera-state.d.ts +40 -0
  11. package/dist/controls/camera-state.js +65 -0
  12. package/dist/controls/gestures.d.ts +70 -0
  13. package/dist/controls/gestures.js +84 -0
  14. package/dist/controls/handlers/Inertia.d.ts +111 -0
  15. package/dist/controls/handlers/Inertia.js +215 -0
  16. package/dist/controls/handlers/Pan.d.ts +23 -0
  17. package/dist/controls/handlers/Pan.js +32 -0
  18. package/dist/controls/handlers/Pitch.d.ts +94 -0
  19. package/dist/controls/handlers/Pitch.js +191 -0
  20. package/dist/controls/handlers/Roll.d.ts +103 -0
  21. package/dist/controls/handlers/Roll.js +182 -0
  22. package/dist/controls/handlers/Zoom.d.ts +15 -0
  23. package/dist/controls/handlers/Zoom.js +26 -0
  24. package/dist/controls/index.d.ts +56 -0
  25. package/dist/controls/index.js +58 -0
  26. package/dist/controls/zoom-to.d.ts +89 -0
  27. package/dist/controls/zoom-to.js +91 -0
  28. package/dist/core/InstanceUniforms.d.ts +93 -0
  29. package/dist/core/InstanceUniforms.js +266 -0
  30. package/dist/core/SquareDataTexture.d.ts +277 -0
  31. package/dist/core/SquareDataTexture.js +476 -0
  32. package/dist/core/batch/BatchedMesh.d.ts +74 -0
  33. package/dist/core/batch/BatchedMesh.js +149 -0
  34. package/dist/core/instanced/FrustumCulling.d.ts +141 -0
  35. package/dist/core/instanced/FrustumCulling.js +246 -0
  36. package/dist/core/instanced/InstancedMesh.d.ts +302 -0
  37. package/dist/core/instanced/InstancedMesh.js +497 -0
  38. package/dist/core/instanced/Instances.d.ts +119 -0
  39. package/dist/core/instanced/Instances.js +230 -0
  40. package/dist/core/instanced/Raycasting.d.ts +118 -0
  41. package/dist/core/instanced/Raycasting.js +134 -0
  42. package/dist/core/instanced/instanced-material.d.ts +77 -0
  43. package/dist/core/instanced/instanced-material.js +224 -0
  44. package/dist/core/lines/BatchedLines.d.ts +135 -0
  45. package/dist/core/lines/BatchedLines.js +213 -0
  46. package/dist/core/lines/FrustumCulling.d.ts +43 -0
  47. package/dist/core/lines/FrustumCulling.js +85 -0
  48. package/dist/core/lines/Raycasting.d.ts +44 -0
  49. package/dist/core/lines/Raycasting.js +91 -0
  50. package/dist/core/lines/lines-material.d.ts +47 -0
  51. package/dist/core/lines/lines-material.js +115 -0
  52. package/dist/core/shader-source.d.ts +50 -0
  53. package/dist/core/shader-source.js +84 -0
  54. package/dist/core/text/BatchedText.d.ts +227 -0
  55. package/dist/core/text/BatchedText.js +467 -0
  56. package/dist/core/text/MemberTexture.d.ts +90 -0
  57. package/dist/core/text/MemberTexture.js +194 -0
  58. package/dist/core/text/Text.d.ts +73 -0
  59. package/dist/core/text/Text.js +215 -0
  60. package/dist/core/text/batched-text-material.d.ts +56 -0
  61. package/dist/core/text/batched-text-material.js +121 -0
  62. package/dist/core/text/glyph-packing.d.ts +17 -0
  63. package/dist/core/text/glyph-packing.js +61 -0
  64. package/dist/core/typed-array.d.ts +16 -0
  65. package/dist/core/typed-array.js +20 -0
  66. package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
  67. package/dist/debug/components/DebugPanelProvider.js +38 -0
  68. package/dist/debug/components/DebugTools.d.ts +22 -0
  69. package/dist/debug/components/DebugTools.js +28 -0
  70. package/dist/debug/components/Gui.d.ts +20 -0
  71. package/dist/debug/components/Gui.js +32 -0
  72. package/dist/debug/components/MemoryInfo.d.ts +22 -0
  73. package/dist/debug/components/MemoryInfo.js +42 -0
  74. package/dist/debug/components/Panel.d.ts +31 -0
  75. package/dist/debug/components/Panel.js +98 -0
  76. package/dist/debug/components/Stats.d.ts +9 -0
  77. package/dist/debug/components/Stats.js +24 -0
  78. package/dist/debug/debug-context.d.ts +41 -0
  79. package/dist/debug/debug-context.js +59 -0
  80. package/dist/debug/index.d.ts +51 -0
  81. package/dist/debug/index.js +64 -0
  82. package/dist/events/LayoutDriver.d.ts +31 -0
  83. package/dist/events/LayoutDriver.js +88 -0
  84. package/dist/events/PointerMissed.d.ts +23 -0
  85. package/dist/events/PointerMissed.js +58 -0
  86. package/dist/events/def-culling.d.ts +46 -0
  87. package/dist/events/def-culling.js +69 -0
  88. package/dist/events/layout-targets.d.ts +104 -0
  89. package/dist/events/layout-targets.js +245 -0
  90. package/dist/events/pointer.d.ts +118 -0
  91. package/dist/events/pointer.js +168 -0
  92. package/dist/external/ExternalCanvas.d.ts +56 -0
  93. package/dist/external/ExternalCanvas.js +178 -0
  94. package/dist/external/external-camera.d.ts +17 -0
  95. package/dist/external/external-camera.js +82 -0
  96. package/dist/external/external-events.d.ts +14 -0
  97. package/dist/external/external-events.js +33 -0
  98. package/dist/index.d.ts +11 -783
  99. package/dist/index.js +12 -8031
  100. package/dist/objects/Background.d.ts +31 -0
  101. package/dist/objects/Background.js +37 -0
  102. package/dist/objects/Lighting.d.ts +20 -0
  103. package/dist/objects/Lighting.js +85 -0
  104. package/dist/objects/containers/SlotContainer.d.ts +266 -0
  105. package/dist/objects/containers/SlotContainer.js +251 -0
  106. package/dist/objects/containers/hooks.d.ts +46 -0
  107. package/dist/objects/containers/hooks.js +118 -0
  108. package/dist/objects/containers/index.d.ts +6 -0
  109. package/dist/objects/containers/index.js +4 -0
  110. package/dist/objects/containers/keys.d.ts +10 -0
  111. package/dist/objects/containers/keys.js +18 -0
  112. package/dist/objects/containers/mesh-writes.d.ts +106 -0
  113. package/dist/objects/containers/mesh-writes.js +97 -0
  114. package/dist/objects/containers/props.d.ts +23 -0
  115. package/dist/objects/containers/props.js +1 -0
  116. package/dist/objects/images/ImageInstance.d.ts +29 -0
  117. package/dist/objects/images/ImageInstance.js +83 -0
  118. package/dist/objects/images/Images.d.ts +37 -0
  119. package/dist/objects/images/Images.js +204 -0
  120. package/dist/objects/images/atlas-packer.d.ts +128 -0
  121. package/dist/objects/images/atlas-packer.js +315 -0
  122. package/dist/objects/images/image-container.d.ts +51 -0
  123. package/dist/objects/images/image-container.js +70 -0
  124. package/dist/objects/images/index.d.ts +2 -0
  125. package/dist/objects/images/index.js +1 -0
  126. package/dist/objects/images/texture-budget.d.ts +120 -0
  127. package/dist/objects/images/texture-budget.js +215 -0
  128. package/dist/objects/layer/Layer.d.ts +27 -0
  129. package/dist/objects/layer/Layer.js +114 -0
  130. package/dist/objects/layer/index.d.ts +2 -0
  131. package/dist/objects/layer/index.js +1 -0
  132. package/dist/objects/layer/mount-scheduler.d.ts +102 -0
  133. package/dist/objects/layer/mount-scheduler.js +145 -0
  134. package/dist/objects/layer/visibility-gui.d.ts +19 -0
  135. package/dist/objects/layer/visibility-gui.js +93 -0
  136. package/dist/objects/lines/LineInstance.d.ts +21 -0
  137. package/dist/objects/lines/LineInstance.js +73 -0
  138. package/dist/objects/lines/Lines.d.ts +17 -0
  139. package/dist/objects/lines/Lines.js +29 -0
  140. package/dist/objects/lines/index.d.ts +2 -0
  141. package/dist/objects/lines/index.js +1 -0
  142. package/dist/objects/lines/line-container.d.ts +35 -0
  143. package/dist/objects/lines/line-container.js +40 -0
  144. package/dist/objects/materials/ColorMaterial.d.ts +85 -0
  145. package/dist/objects/materials/ColorMaterial.js +89 -0
  146. package/dist/objects/materials/LineMaterial.d.ts +44 -0
  147. package/dist/objects/materials/LineMaterial.js +72 -0
  148. package/dist/objects/materials/ModelMaterial.d.ts +41 -0
  149. package/dist/objects/materials/ModelMaterial.js +84 -0
  150. package/dist/objects/materials/TextureMaterial.d.ts +30 -0
  151. package/dist/objects/materials/TextureMaterial.js +29 -0
  152. package/dist/objects/materials/dim.d.ts +123 -0
  153. package/dist/objects/materials/dim.js +213 -0
  154. package/dist/objects/materials/shared.d.ts +6 -0
  155. package/dist/objects/materials/shared.js +51 -0
  156. package/dist/objects/models/ModelInstance.d.ts +22 -0
  157. package/dist/objects/models/ModelInstance.js +33 -0
  158. package/dist/objects/models/Models.d.ts +22 -0
  159. package/dist/objects/models/Models.js +177 -0
  160. package/dist/objects/models/gltf-cache.d.ts +17 -0
  161. package/dist/objects/models/gltf-cache.js +50 -0
  162. package/dist/objects/models/index.d.ts +2 -0
  163. package/dist/objects/models/index.js +1 -0
  164. package/dist/objects/models/model-asset.d.ts +48 -0
  165. package/dist/objects/models/model-asset.js +77 -0
  166. package/dist/objects/models/model-container.d.ts +139 -0
  167. package/dist/objects/models/model-container.js +227 -0
  168. package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
  169. package/dist/objects/shapes/ShapeInstance.js +40 -0
  170. package/dist/objects/shapes/Shapes.d.ts +21 -0
  171. package/dist/objects/shapes/Shapes.js +120 -0
  172. package/dist/objects/shapes/index.d.ts +2 -0
  173. package/dist/objects/shapes/index.js +1 -0
  174. package/dist/objects/shapes/shape-container.d.ts +165 -0
  175. package/dist/objects/shapes/shape-container.js +272 -0
  176. package/dist/objects/shapes/shape-geometry.d.ts +27 -0
  177. package/dist/objects/shapes/shape-geometry.js +94 -0
  178. package/dist/objects/text/TextInstance.d.ts +22 -0
  179. package/dist/objects/text/TextInstance.js +95 -0
  180. package/dist/objects/text/Texts.d.ts +30 -0
  181. package/dist/objects/text/Texts.js +41 -0
  182. package/dist/objects/text/index.d.ts +2 -0
  183. package/dist/objects/text/index.js +1 -0
  184. package/dist/objects/text/text-container.d.ts +105 -0
  185. package/dist/objects/text/text-container.js +206 -0
  186. package/dist/objects/text/text-layout.d.ts +83 -0
  187. package/dist/objects/text/text-layout.js +154 -0
  188. package/dist/space/Camera.d.ts +103 -0
  189. package/dist/space/Camera.js +321 -0
  190. package/dist/space/Scene.d.ts +44 -0
  191. package/dist/space/Scene.js +112 -0
  192. package/dist/space/Space.d.ts +288 -0
  193. package/dist/space/Space.js +329 -0
  194. package/dist/space/VisibleRect.d.ts +72 -0
  195. package/dist/space/VisibleRect.js +129 -0
  196. package/dist/space/camera-handle.d.ts +67 -0
  197. package/dist/space/camera-handle.js +63 -0
  198. package/dist/space/camera-kind.d.ts +26 -0
  199. package/dist/space/camera-kind.js +30 -0
  200. package/dist/space/canvas-size.d.ts +24 -0
  201. package/dist/space/canvas-size.js +20 -0
  202. package/dist/space/clipping.d.ts +55 -0
  203. package/dist/space/clipping.js +108 -0
  204. package/dist/space/layout-measure.d.ts +65 -0
  205. package/dist/space/layout-measure.js +173 -0
  206. package/dist/space/viewport.d.ts +112 -0
  207. package/dist/space/viewport.js +67 -0
  208. package/dist/types/defs.d.ts +169 -0
  209. package/dist/types/defs.js +1 -0
  210. package/dist/types/events.d.ts +183 -0
  211. package/dist/types/events.js +1 -0
  212. package/dist/types/guards.d.ts +14 -0
  213. package/dist/types/guards.js +42 -0
  214. package/dist/update/index.d.ts +3 -0
  215. package/dist/update/index.js +2 -0
  216. package/dist/update/slot-writers.d.ts +30 -0
  217. package/dist/update/slot-writers.js +72 -0
  218. package/dist/update/store.d.ts +41 -0
  219. package/dist/update/store.js +98 -0
  220. package/dist/utils/arrays.d.ts +13 -0
  221. package/dist/utils/arrays.js +20 -0
  222. package/dist/utils/colors.d.ts +19 -0
  223. package/dist/utils/colors.js +27 -0
  224. package/dist/utils/logging.d.ts +26 -0
  225. package/dist/utils/logging.js +47 -0
  226. package/dist/utils/math.d.ts +24 -0
  227. package/dist/utils/math.js +27 -0
  228. package/package.json +67 -19
@@ -0,0 +1,51 @@
1
+ import { type CanvasProps } from '@react-three/fiber';
2
+ import { type ReactNode } from 'react';
3
+ /**
4
+ * Loads the debug chunk ahead of time — for a host that creates its own WebGL context.
5
+ *
6
+ * The chunk's evaluation is what instruments context creation for the GPU-memory panel
7
+ * (webgl-memory patches `getContext` as it loads), so a context created before it exists can never
8
+ * report memory figures. The renderer's own mount orders this itself; a MapLibre host does it by
9
+ * awaiting this before constructing the map, when debug is on. Idempotent, and the only eager cost
10
+ * is the call: the debug code still lives in its own lazy chunk.
11
+ */
12
+ export declare function prepareDebug(): Promise<void>;
13
+ /** Props for {@link CanvasWithDebug}: the Canvas props plus the debug toggle. */
14
+ export interface CanvasWithDebugProps extends CanvasProps {
15
+ /** When falsy, the Canvas mounts untouched and no debug code is loaded. */
16
+ debugEnabled?: boolean;
17
+ }
18
+ /**
19
+ * The renderer's own Canvas with the debug ordering folded in: when `debugEnabled`, it suspends —
20
+ * into the boundary its parent provides — until the debug chunk has loaded, so the chunk's
21
+ * webgl-memory patch lands *before* the Canvas creates its context and the context is born
22
+ * instrumented. That ordering is this component's whole job, and it has to sit above `<Canvas>`:
23
+ * children mount only after the root is configured, which is after the context exists, so nothing
24
+ * inside the tree could order it. The overlay itself is {@link DebugOverlay}'s, inside the tree,
25
+ * the same in both mounts.
26
+ *
27
+ * Turning `debugEnabled` on at runtime is not supported: React keeps a suspending, already-mounted
28
+ * Canvas alive through the fallback, so the context is never recreated and the patch arrives too
29
+ * late for it. `debug` is derived from a module constant fixed for the session, so this never
30
+ * happens in practice.
31
+ * @param props - `debugEnabled` plus the Canvas props; `children` is the scene.
32
+ * @returns The Canvas, its context creation deferred behind the debug chunk when enabled.
33
+ */
34
+ export declare function CanvasWithDebug(props: CanvasWithDebugProps): ReactNode;
35
+ /** Props for {@link DebugOverlay}: the toggle and the tree it may instrument. */
36
+ export interface DebugOverlayProps {
37
+ /** When falsy, the children render untouched and no debug code is loaded. */
38
+ enabled?: boolean;
39
+ /** The scene tree. */
40
+ children?: ReactNode;
41
+ }
42
+ /**
43
+ * Mounts the debug overlay around the scene tree when enabled — the same element in both mounts,
44
+ * which is what makes `debug` a shared prop: the GUI, the panels and the stats attach to the
45
+ * document, not to whichever canvas is drawing. Lazy behind the shared debug chunk; disabled, it
46
+ * is the children and nothing else.
47
+ * @param props - The toggle and the tree.
48
+ * @returns The tree, instrumented when enabled.
49
+ */
50
+ export declare function DebugOverlay(props: DebugOverlayProps): ReactNode;
51
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Canvas } from '@react-three/fiber';
3
+ import { lazy, Suspense, use } from 'react';
4
+ /**
5
+ * The one debug chunk, shared by every consumer: the internal mount's context-patch gate, the
6
+ * overlay in both mounts' trees, and {@link prepareDebug}. Memoized so `use()` sees a stable
7
+ * promise and the chunk is fetched once.
8
+ */
9
+ let debugChunk;
10
+ function loadDebugChunk() {
11
+ debugChunk ??= import('./components/DebugTools');
12
+ return debugChunk;
13
+ }
14
+ /**
15
+ * Loads the debug chunk ahead of time — for a host that creates its own WebGL context.
16
+ *
17
+ * The chunk's evaluation is what instruments context creation for the GPU-memory panel
18
+ * (webgl-memory patches `getContext` as it loads), so a context created before it exists can never
19
+ * report memory figures. The renderer's own mount orders this itself; a MapLibre host does it by
20
+ * awaiting this before constructing the map, when debug is on. Idempotent, and the only eager cost
21
+ * is the call: the debug code still lives in its own lazy chunk.
22
+ */
23
+ export async function prepareDebug() {
24
+ await loadDebugChunk();
25
+ }
26
+ const DebugTools = lazy(() => import('./components/DebugTools').then((module) => ({ default: module.DebugTools })));
27
+ /**
28
+ * The renderer's own Canvas with the debug ordering folded in: when `debugEnabled`, it suspends —
29
+ * into the boundary its parent provides — until the debug chunk has loaded, so the chunk's
30
+ * webgl-memory patch lands *before* the Canvas creates its context and the context is born
31
+ * instrumented. That ordering is this component's whole job, and it has to sit above `<Canvas>`:
32
+ * children mount only after the root is configured, which is after the context exists, so nothing
33
+ * inside the tree could order it. The overlay itself is {@link DebugOverlay}'s, inside the tree,
34
+ * the same in both mounts.
35
+ *
36
+ * Turning `debugEnabled` on at runtime is not supported: React keeps a suspending, already-mounted
37
+ * Canvas alive through the fallback, so the context is never recreated and the patch arrives too
38
+ * late for it. `debug` is derived from a module constant fixed for the session, so this never
39
+ * happens in practice.
40
+ * @param props - `debugEnabled` plus the Canvas props; `children` is the scene.
41
+ * @returns The Canvas, its context creation deferred behind the debug chunk when enabled.
42
+ */
43
+ export function CanvasWithDebug(props) {
44
+ const { debugEnabled, children, ...canvasProps } = props;
45
+ // `use` is built for conditional calls, and the condition is the point: an undebugged mount
46
+ // never touches the chunk.
47
+ if (debugEnabled)
48
+ use(loadDebugChunk());
49
+ return _jsx(Canvas, { ...canvasProps, children: children });
50
+ }
51
+ /**
52
+ * Mounts the debug overlay around the scene tree when enabled — the same element in both mounts,
53
+ * which is what makes `debug` a shared prop: the GUI, the panels and the stats attach to the
54
+ * document, not to whichever canvas is drawing. Lazy behind the shared debug chunk; disabled, it
55
+ * is the children and nothing else.
56
+ * @param props - The toggle and the tree.
57
+ * @returns The tree, instrumented when enabled.
58
+ */
59
+ export function DebugOverlay(props) {
60
+ const { enabled, children } = props;
61
+ if (!enabled)
62
+ return children;
63
+ return (_jsx(Suspense, { fallback: null, children: _jsx(DebugTools, { children: children }) }));
64
+ }
@@ -0,0 +1,31 @@
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 starts a **sweep**, never a synchronous pass: each frame drains the registry's backlog for
18
+ * at most {@link LAYOUT_BUDGET_MS}, defs inside the camera's frustum first, and asks for another
19
+ * frame while any backlog remains — so a plan of ten thousand labels re-lays out over a handful of
20
+ * frames instead of stalling one, and what the user is looking at is what lands first. The layouts
21
+ * stay inside the frame callback, before three's own render, so their attribute writes upload in
22
+ * the very frame that draws them.
23
+ *
24
+ * No React is involved in a re-layout. The handlers mutate def fields and the defs' own sync closures
25
+ * write them, so a zoom does not re-render a single component — which is what makes it affordable for
26
+ * every label in a plan to re-lay out while the camera moves.
27
+ * @param props - The scene to provide the registry to.
28
+ * @returns The scene, under the registry.
29
+ */
30
+ export declare function LayoutDriver(props: LayoutDriverProps): import("react").JSX.Element;
31
+ //# sourceMappingURL=LayoutDriver.d.ts.map
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useFrame, useThree } from '@react-three/fiber';
3
+ import { useState } from 'react';
4
+ import { Frustum } from 'three';
5
+ import { useCameraControls } from '../controls';
6
+ import { measurePixelSize, measureRoll, pixelSizeMoved } from '../space/layout-measure';
7
+ import { useSpace } from '../space/Space';
8
+ import { createLogger } from '../utils/logging';
9
+ import { planFrustum } from './def-culling';
10
+ import { createLayoutTargets, LayoutTargetsContext } from './layout-targets';
11
+ const logger = createLogger('layout');
12
+ /**
13
+ * How much of a frame one drain may spend running layout rules, in milliseconds — the same budget
14
+ * the legacy renderer's `UpdatesSystem` drained its update queue with. Small enough to leave a
15
+ * 60fps frame its render; the backlog it leaves is carried into the next frame, visible defs first.
16
+ */
17
+ const LAYOUT_BUDGET_MS = 3;
18
+ /**
19
+ * Drives camera-driven re-layout: it owns the renderer's {@link LayoutTargets} and watches both
20
+ * layout axes every frame — the pixel size, swept past an epsilon, and the roll, swept on any
21
+ * change — running each registered def's stale rules and writing back whatever they report changed.
22
+ *
23
+ * It runs at the default frame priority, after the controls have updated the camera at `-1`, so a
24
+ * frame lays out against the camera it is about to draw. And it runs only on frames something asked
25
+ * for: under the on-demand loop the camera's own movement is what requests them, so a still plan
26
+ * costs nothing at all.
27
+ *
28
+ * A move starts a **sweep**, never a synchronous pass: each frame drains the registry's backlog for
29
+ * at most {@link LAYOUT_BUDGET_MS}, defs inside the camera's frustum first, and asks for another
30
+ * frame while any backlog remains — so a plan of ten thousand labels re-lays out over a handful of
31
+ * frames instead of stalling one, and what the user is looking at is what lands first. The layouts
32
+ * stay inside the frame callback, before three's own render, so their attribute writes upload in
33
+ * the very frame that draws them.
34
+ *
35
+ * No React is involved in a re-layout. The handlers mutate def fields and the defs' own sync closures
36
+ * write them, so a zoom does not re-render a single component — which is what makes it affordable for
37
+ * every label in a plan to re-lay out while the camera moves.
38
+ * @param props - The scene to provide the registry to.
39
+ * @returns The scene, under the registry.
40
+ */
41
+ export function LayoutDriver(props) {
42
+ const { children } = props;
43
+ const camera = useThree((state) => state.camera);
44
+ const gl = useThree((state) => state.gl);
45
+ const invalidate = useThree((state) => state.invalidate);
46
+ const controls = useCameraControls();
47
+ const space = useSpace();
48
+ // Seeded from the camera as it stands, so defs registering before the first frame are laid out at
49
+ // the size they will be drawn at rather than at a placeholder. The measurement asks nothing of
50
+ // the controls — absent at this point whatever the mount order, since they publish themselves in
51
+ // an effect and this runs during render — which is also what lets this component be rendered,
52
+ // and tested, without any controls at all.
53
+ const [targets] = useState(() => createLayoutTargets(invalidate, measurePixelSize(camera, space, gl.domElement.width) ?? 1, measureRoll(camera, space, gl.domElement.width, gl.domElement.height) ?? 0));
54
+ useFrame((state) => {
55
+ const camera = state.camera;
56
+ const { width, height } = state.gl.domElement;
57
+ // One call for every camera — the renderer's own and external mode's folded matrix alike. The
58
+ // controls are handed over rather than branched on here: with them the reading is the cheap
59
+ // arithmetic the imperative renderer used, and without them it is measured off the matrices,
60
+ // which both cameras carry.
61
+ const pixelSize = measurePixelSize(camera, space, width, controls);
62
+ // The epsilon compares against the sweep's target size, so a camera that stopped mid-sweep does
63
+ // not re-mark the same sweep on every one of the frames that finish it.
64
+ if (pixelSize !== undefined && pixelSizeMoved(pixelSize, targets.pixelSize)) {
65
+ logger.debug(`pxToSvgScale ${+pixelSize.toFixed(3)}`);
66
+ targets.applyScale(pixelSize);
67
+ }
68
+ // Roll has no epsilon — the imperative renderer swept its roll handlers on any bearing change
69
+ // at all, and the rules themselves step in quarter turns, so almost every layout is a cheap
70
+ // no-write. Which of the two ways it is reached — the controls' own angle, or a measurement off
71
+ // the camera for external mode and bare scenes — is `measureRoll`'s to choose.
72
+ const roll = measureRoll(camera, space, width, height, controls);
73
+ if (roll !== undefined && roll !== targets.roll)
74
+ targets.applyRoll(roll);
75
+ if (!targets.hasWork)
76
+ return;
77
+ // Priority, never correctness: the frustum only decides who waits, and it is rebuilt each drain
78
+ // so a camera that pans mid-sweep re-prioritizes what is left.
79
+ const frustum = planFrustum(state.camera, space.matrix, _frustum);
80
+ // The writes self-invalidate through the defs' slot writers; this asks for the *extra* frame a
81
+ // leftover backlog needs, which is what keeps the demand loop ticking until the sweep is done.
82
+ if (targets.drain(frustum, LAYOUT_BUDGET_MS))
83
+ invalidate();
84
+ });
85
+ return _jsx(LayoutTargetsContext, { value: targets, children: children });
86
+ }
87
+ // Scratch for the drain's frustum, filled and consumed within one frame callback.
88
+ const _frustum = new Frustum();
@@ -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
@@ -0,0 +1,69 @@
1
+ import { Frustum, Matrix4, Sphere, Vector3 } from 'three';
2
+ import { isImageDef, isTextDef } from '../types/guards';
3
+ /**
4
+ * Encloses a def's bounds in a sphere the frustum can test, rotation-invariant by construction.
5
+ *
6
+ * Ported from the imperative renderer's `computeBoundingSphere` (fp-engine
7
+ * `renderer/src/util/geometry.ts`), which the legacy `UpdatesSystem` culled its update queue with.
8
+ * The sphere is centered at `bounds.center` — the rotation pivot — with a radius reaching the
9
+ * furthest corner of the rendered content, so no rotation can swing the content outside it. An
10
+ * `origin` (an image's anchor) shifts which part of the content hangs off the pivot, which widens
11
+ * the radius but never moves the center.
12
+ * @param bounds - The def's bounds.
13
+ * @param origin - The image anchor in `[0..1, 0..1]`, when the def has one; centered otherwise.
14
+ * @param out - Receives the result.
15
+ * @returns The sphere, in plan space.
16
+ */
17
+ export function boundingSphereOf(bounds, origin, out) {
18
+ const originX = origin?.[0] ?? 0.5;
19
+ const originY = origin?.[1] ?? 0.5;
20
+ const reachX = Math.max(originX, 1 - originX) * bounds.size.x;
21
+ const reachY = Math.max(originY, 1 - originY) * bounds.size.y;
22
+ _center.set(bounds.center.x, bounds.center.y, bounds.elevation);
23
+ out.set(_center, Math.hypot(reachX, reachY));
24
+ return out;
25
+ }
26
+ /**
27
+ * Whether a def is entirely off screen, for the drain that lays visible defs out first.
28
+ *
29
+ * Only text and image defs are tested — the def kinds with one cheap `bounds` enclosure, and the
30
+ * bulk of any plan's re-layout targets (labels, dots, icons, markers). Everything else answers
31
+ * "not culled" and takes the priority pass, exactly as the legacy `UpdatesSystem` treated it.
32
+ * A `true` is a **priority** signal, never a correctness one: a culled def must still be laid out,
33
+ * just after the ones the camera can see.
34
+ * @param def - The def to test.
35
+ * @param frustum - The camera's frustum, in plan space — see {@link planFrustum}.
36
+ * @returns Whether the def can wait.
37
+ */
38
+ export function culledBy(def, frustum) {
39
+ if (isTextDef(def)) {
40
+ return !frustum.intersectsSphere(boundingSphereOf(def.bounds, undefined, _sphere));
41
+ }
42
+ if (isImageDef(def)) {
43
+ return !frustum.intersectsSphere(boundingSphereOf(def.bounds, def.origin, _sphere));
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * The camera's frustum brought into plan space, where def bounds live.
49
+ *
50
+ * The same composition the imperative renderer's `computeFrustumInModelSpace` used, with the
51
+ * scene's plan → world matrix standing in for the scene's `matrixWorld`: projecting through
52
+ * `projection · viewInverse · plan` makes the frustum's planes answer for plan coordinates
53
+ * directly, so a def is tested where it is authored and nothing per-def is transformed.
54
+ * @param camera - The camera in use.
55
+ * @param planMatrix - The plan → world matrix, from the scene's `Space`.
56
+ * @param out - A frustum to write into. Omit it and each call allocates, which is what makes the
57
+ * result safe to keep.
58
+ * @returns The frustum.
59
+ */
60
+ export function planFrustum(camera, planMatrix, out = new Frustum()) {
61
+ _frustumMatrix
62
+ .multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
63
+ .multiply(planMatrix);
64
+ return out.setFromProjectionMatrix(_frustumMatrix, camera.coordinateSystem);
65
+ }
66
+ // Scratch, each filled and consumed within one synchronous call.
67
+ const _center = new Vector3();
68
+ const _sphere = new Sphere();
69
+ const _frustumMatrix = new Matrix4();
@@ -0,0 +1,104 @@
1
+ import { type Frustum } from 'three';
2
+ import { type LayerDef, type RenderableDef } from '../types/defs';
3
+ /**
4
+ * The defs a renderer re-lays out when the camera moves: the pixel size and the roll are the two
5
+ * layout axes.
6
+ *
7
+ * Every def here declares its own `onScale` or `onRoll` — the registry never holds a handler, only
8
+ * the value each def's rule last ran at, per axis. One registry per renderer, reached through {@link LayoutTargetsContext},
9
+ * so two renderers showing the same defs at different zooms do not lay each other's out.
10
+ *
11
+ * A pixel-size move is a **sweep**, not a synchronous pass: {@link applyScale} marks every registered def
12
+ * stale and {@link drain} works the backlog off a frame's budget at a time, defs the camera can see
13
+ * first. The shape is the legacy renderer's `UpdatesSystem` (fp-engine `core/updates.ts`), whose
14
+ * pending/deferred queues and per-frame time budget this registry inherits — with the queue's unit
15
+ * changed from "def whose fields moved" to "def whose layout rule must re-run".
16
+ */
17
+ export interface LayoutTargets {
18
+ /**
19
+ * The pixel size the registered defs are laid out at — from the moment {@link applyScale} marks a
20
+ * sweep, the size defs are being *brought* to, which is what the driver's epsilon must compare
21
+ * against so a camera that stopped moving does not re-mark the same sweep every frame.
22
+ */
23
+ readonly pixelSize: number;
24
+ /**
25
+ * The roll the registered defs are laid out at, in radians clockwise from north — the second
26
+ * layout axis, compared the way {@link pixelSize} is: against the sweep's target, not the last
27
+ * drained value.
28
+ */
29
+ readonly roll: number;
30
+ /** Whether a sweep still has defs to lay out — what makes a caller keep frames coming. */
31
+ readonly hasWork: boolean;
32
+ /**
33
+ * Registers defs for re-layout, and lays out the ones that are not current — so a def is current
34
+ * from the moment it is registered, and a membership change re-lays out only what it added.
35
+ * @param defs - The defs to lay out; every one of them declares an `onScale` or an `onRoll`.
36
+ * @returns An unregister function; it also drops whatever of these defs a sweep still queued.
37
+ */
38
+ register(defs: readonly RenderableDef[]): () => void;
39
+ /**
40
+ * Starts a sweep: records the new pixel size and marks every registered def stale. Nothing is
41
+ * laid out here — that is {@link drain}'s job — so the frame that noticed the move pays only the
42
+ * marking. Applying again mid-sweep re-marks at the newer size, and a def the earlier sweep had
43
+ * not reached is simply laid out once, at that newer size: the backlog coalesces by construction.
44
+ * @param pixelSize - The new pixel size.
45
+ */
46
+ applyScale(pixelSize: number): void;
47
+ /**
48
+ * Starts a roll sweep — {@link applyScale}'s counterpart on the other axis, marking only the defs that
49
+ * declare an `onRoll`. The two axes coalesce independently, and one {@link drain} works both off.
50
+ * @param roll - The new roll, in radians clockwise from north.
51
+ */
52
+ applyRoll(roll: number): void;
53
+ /**
54
+ * Works off the sweep within a frame's budget, and says whether any backlog remains — the
55
+ * caller's cue to ask for another frame.
56
+ *
57
+ * Two passes, in the legacy `UpdatesSystem`'s shape: the stale defs first, except that a hidden
58
+ * def or one the frustum rejects is *deferred* rather than laid out; then the deferred ones, with
59
+ * whatever budget is left. Priority is all deferral buys — every def is still laid out before the
60
+ * sweep ends, because whether a def draws can be exactly what its rule decides. The budget is
61
+ * checked before each def, so even a blown budget lays at least one out and a sweep can never
62
+ * stall.
63
+ * @param frustum - The camera's frustum in plan space, when the caller has one; without it only
64
+ * hidden defs are deferred.
65
+ * @param budgetMs - How much of the frame a call may spend. Non-positive means no limit.
66
+ * @returns Whether defs remain for a later drain.
67
+ */
68
+ drain(frustum?: Frustum, budgetMs?: number): boolean;
69
+ }
70
+ /**
71
+ * The renderer's re-layout registry. Absent outside a renderer, where a layer simply registers
72
+ * nothing — which is what lets `Layer` be rendered bare in a spec.
73
+ */
74
+ export declare const LayoutTargetsContext: import("react").Context<LayoutTargets | undefined>;
75
+ /**
76
+ * Registers a layer's re-layout targets for as long as the layer is mounted: the defs it holds that
77
+ * declare an `onScale`, and the layer itself when it declares one.
78
+ *
79
+ * A child *layer* is never registered here even when it declares a handler — it registers itself, and
80
+ * it has to: a handler assigned to a layer after mount re-renders that layer alone, so a registration
81
+ * held by its parent would never learn of it. Self-registration also covers the root, which has no
82
+ * parent layer to be registered by.
83
+ *
84
+ * Called from the layer rather than from its containers or their instances, because this is where
85
+ * both halves are already at hand. It also means a def is laid out whether or not its instance has
86
+ * mounted — defs are the source of truth, and a staged mount picks up whatever they hold when it
87
+ * admits them.
88
+ *
89
+ * Outside a renderer there is no registry and this does nothing, which is what lets a `Layer` be
90
+ * rendered bare in a spec.
91
+ * @param layer - The layer registering; itself a target when it declares a handler, which is how a
92
+ * layer whose *membership* depends on the camera re-derives it.
93
+ * @param children - The layer's children snapshot.
94
+ */
95
+ export declare function useLayoutTargets(layer: LayerDef, children: readonly RenderableDef[]): void;
96
+ /**
97
+ * Builds a renderer's re-layout registry.
98
+ * @param invalidate - Requests a frame, for layouts that happen outside the frame loop.
99
+ * @param pixelSize - The pixel size defs are laid out at until the first frame moves it.
100
+ * @param roll - The roll defs are laid out at until the first frame moves it, in radians.
101
+ * @returns The registry.
102
+ */
103
+ export declare function createLayoutTargets(invalidate: () => void, pixelSize: number, roll?: number): LayoutTargets;
104
+ //# sourceMappingURL=layout-targets.d.ts.map