@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,245 @@
1
+ import { createContext, use, useEffect } from 'react';
2
+ import { isLayerDef } from '../types/guards';
3
+ import { writeChanged } from '../update';
4
+ import { createLogger } from '../utils/logging';
5
+ import { culledBy } from './def-culling';
6
+ const logger = createLogger('layout');
7
+ /**
8
+ * The renderer's re-layout registry. Absent outside a renderer, where a layer simply registers
9
+ * nothing — which is what lets `Layer` be rendered bare in a spec.
10
+ */
11
+ export const LayoutTargetsContext = createContext(undefined);
12
+ /**
13
+ * Registers a layer's re-layout targets for as long as the layer is mounted: the defs it holds that
14
+ * declare an `onScale`, and the layer itself when it declares one.
15
+ *
16
+ * A child *layer* is never registered here even when it declares a handler — it registers itself, and
17
+ * it has to: a handler assigned to a layer after mount re-renders that layer alone, so a registration
18
+ * held by its parent would never learn of it. Self-registration also covers the root, which has no
19
+ * parent layer to be registered by.
20
+ *
21
+ * Called from the layer rather than from its containers or their instances, because this is where
22
+ * both halves are already at hand. It also means a def is laid out whether or not its instance has
23
+ * mounted — defs are the source of truth, and a staged mount picks up whatever they hold when it
24
+ * admits them.
25
+ *
26
+ * Outside a renderer there is no registry and this does nothing, which is what lets a `Layer` be
27
+ * rendered bare in a spec.
28
+ * @param layer - The layer registering; itself a target when it declares a handler, which is how a
29
+ * layer whose *membership* depends on the camera re-derives it.
30
+ * @param children - The layer's children snapshot.
31
+ */
32
+ export function useLayoutTargets(layer, children) {
33
+ const targets = use(LayoutTargetsContext);
34
+ useEffect(() => {
35
+ if (!targets)
36
+ return undefined;
37
+ const defs = children.filter((def) => (def.onScale !== undefined || def.onRoll !== undefined) && !isLayerDef(def));
38
+ if (layer.onScale || layer.onRoll)
39
+ defs.push(layer);
40
+ if (!defs.length)
41
+ return undefined;
42
+ return targets.register(defs);
43
+ }, [targets, layer, children]);
44
+ }
45
+ /**
46
+ * Builds a renderer's re-layout registry.
47
+ * @param invalidate - Requests a frame, for layouts that happen outside the frame loop.
48
+ * @param pixelSize - The pixel size defs are laid out at until the first frame moves it.
49
+ * @param roll - The roll defs are laid out at until the first frame moves it, in radians.
50
+ * @returns The registry.
51
+ */
52
+ export function createLayoutTargets(invalidate, pixelSize, roll = 0) {
53
+ const groups = new Set();
54
+ /**
55
+ * The pixel size each def was last laid out at — the whole of the registry's state, because the
56
+ * rule that lays a def out is the def's own. Keyed by def rather than by group, so a membership
57
+ * change re-lays out only the defs that joined.
58
+ *
59
+ * A def whose `onScale` is *replaced* after it was laid out is not noticed: the record says only
60
+ * "laid out at this size". Reassigning a handler is a thing nothing does — a def is built with its
61
+ * rule — and a caller that does it lays the def out itself.
62
+ */
63
+ const layouts = new WeakMap();
64
+ /**
65
+ * The roll each def's `onRoll` last ran at — the other axis's record, kept apart from
66
+ * {@link layouts} because the two move independently: a zoom leaves every roll record current,
67
+ * and a turn leaves every scale record current.
68
+ */
69
+ const rolls = new WeakMap();
70
+ /**
71
+ * The sweep's backlog: defs an `apply` marked stale and {@link LayoutTargets.drain} has not
72
+ * reached. **One queue for every axis** — which of a queued def's rules actually runs is decided
73
+ * at drain time by the per-axis records above, so a def stale on both axes is dequeued once and
74
+ * laid out on both, and a new axis adds a record and a rule, never another queue. `deferred`
75
+ * holds the ones a drain pushed behind the visible — hidden defs, and defs the frustum
76
+ * rejected — worked off with whatever budget the stale pass leaves. Sets, not arrays, because one
77
+ * def can be re-marked while queued and must not be laid out twice.
78
+ */
79
+ const pending = new Set();
80
+ const deferred = new Set();
81
+ let current = pixelSize;
82
+ let currentRoll = roll;
83
+ /**
84
+ * The view every rule is handed — one instance for the registry's lifetime, mutated as the axes
85
+ * move, which is what keeps a ten-thousand-label sweep allocation-free. `LayoutView` documents
86
+ * the flip side: valid during the call, so a rule destructures rather than retains it.
87
+ */
88
+ const view = { pixelSize: current, roll: currentRoll };
89
+ /**
90
+ * Runs a def's handler and writes the result.
91
+ *
92
+ * A leaf goes through the def's own registered sync — the same closure the React path runs after
93
+ * `update(def)` — so a handler mutating def fields needs no `update()` call of its own, and the two
94
+ * paths cannot disagree about what a def looks like. A def whose instance has not mounted yet syncs
95
+ * to nothing here and picks the mutation up when it does. A layer among the returned defs is
96
+ * reconciled instead, which is how a handler changes its own membership.
97
+ *
98
+ * A handler reporting nothing skips that write, which is the whole point of the return value: it is
99
+ * the half that costs. The def is still recorded as laid out, so a membership change does not run
100
+ * the handler over it again.
101
+ * @param def - The def to lay out.
102
+ * @returns Whether anything was written.
103
+ */
104
+ function layOut(def) {
105
+ // Hidden defs are laid out too. Whether a def draws at all can be exactly what its rule decides
106
+ // — a booth's dot appears only once the plan is far enough out for its label to stop fitting —
107
+ // so a registry that skipped them would never run the rule that would bring them back. Skipping
108
+ // the *waste* is the rule's own job instead: a handler with nothing worth drawing returns
109
+ // nothing, and the slot write is the half that costs.
110
+ if (!def.onScale || layouts.get(def) === current)
111
+ return false;
112
+ const changed = def.onScale(view);
113
+ // Recorded before the write, which runs the def's own sync and can reach back in here.
114
+ layouts.set(def, current);
115
+ if (!changed || changed.length === 0)
116
+ return false;
117
+ writeChanged(changed);
118
+ return true;
119
+ }
120
+ /**
121
+ * Runs a def's roll rule and writes the result — {@link layOut} on the other axis, with the same
122
+ * record-before-write ordering and the same "an empty return skips the write" contract.
123
+ * @param def - The def to lay out.
124
+ * @returns Whether anything was written.
125
+ */
126
+ function layOutRoll(def) {
127
+ if (!def.onRoll || rolls.get(def) === currentRoll)
128
+ return false;
129
+ const changed = def.onRoll(view);
130
+ rolls.set(def, currentRoll);
131
+ if (!changed || changed.length === 0)
132
+ return false;
133
+ writeChanged(changed);
134
+ return true;
135
+ }
136
+ /**
137
+ * Runs whatever of a def's rules are stale by their records — the one unit of work a dequeued def
138
+ * costs, scale before roll so a rule that turns a label re-fits it at the size just applied.
139
+ * @param def - The def to lay out.
140
+ * @returns Whether anything was written.
141
+ */
142
+ function layOutStale(def) {
143
+ const scaled = layOut(def);
144
+ const rolled = layOutRoll(def);
145
+ return scaled || rolled;
146
+ }
147
+ /**
148
+ * Marks stale defs for the sweep: the previous sweep's deferrals rejoin the queue — where the new
149
+ * frustum decides their priority afresh, and cross-axis work survives whichever axis moved — and
150
+ * every def carrying the moved axis's rule is (re-)queued.
151
+ * @param carriesRule - Whether a def declares the moved axis's rule.
152
+ */
153
+ function requeue(carriesRule) {
154
+ for (const def of deferred)
155
+ pending.add(def);
156
+ deferred.clear();
157
+ // Rule presence is read at marking time, so a rule cleared after registration — a hidden
158
+ // bookmark's — simply stops queueing, with nothing to unregister.
159
+ for (const defs of groups) {
160
+ for (const def of defs) {
161
+ if (carriesRule(def))
162
+ pending.add(def);
163
+ }
164
+ }
165
+ }
166
+ return {
167
+ get pixelSize() {
168
+ return current;
169
+ },
170
+ get roll() {
171
+ return currentRoll;
172
+ },
173
+ get hasWork() {
174
+ return pending.size > 0 || deferred.size > 0;
175
+ },
176
+ register(defs) {
177
+ groups.add(defs);
178
+ let laidOut = false;
179
+ for (const def of defs) {
180
+ if (layOutStale(def))
181
+ laidOut = true;
182
+ }
183
+ // Registration happens in an effect rather than inside the frame loop, so anything written here
184
+ // needs a frame asking for it.
185
+ if (laidOut)
186
+ invalidate();
187
+ return () => {
188
+ groups.delete(defs);
189
+ // A queued def whose layer is gone must not be laid out from the backlog: its instances are
190
+ // unmounting, and its handler may read state its owner already tore down.
191
+ for (const def of defs) {
192
+ pending.delete(def);
193
+ deferred.delete(def);
194
+ }
195
+ };
196
+ },
197
+ applyScale(nextPixelSize) {
198
+ current = nextPixelSize;
199
+ view.pixelSize = nextPixelSize;
200
+ requeue((def) => def.onScale !== undefined);
201
+ },
202
+ applyRoll(nextRoll) {
203
+ currentRoll = nextRoll;
204
+ view.roll = nextRoll;
205
+ requeue((def) => def.onRoll !== undefined);
206
+ },
207
+ drain(frustum, budgetMs = -1) {
208
+ if (pending.size === 0 && deferred.size === 0)
209
+ return false;
210
+ const start = performance.now();
211
+ const overBudget = () => budgetMs > 0 && performance.now() - start >= budgetMs;
212
+ let wrote = 0;
213
+ // Sets iterate in insertion order and tolerate deletion mid-walk, which is what makes the two
214
+ // passes below plain loops: the order moves arrived is the priority within a pass, and a
215
+ // processed def leaves its set before its rules run.
216
+ for (const def of pending) {
217
+ if (overBudget())
218
+ break;
219
+ pending.delete(def);
220
+ // Deferral is priority, never a skip: a hidden def, or one the camera cannot see, joins the
221
+ // second pass rather than being laid out now. `def.hidden` needs no frustum — hidden is not
222
+ // drawn wherever it sits.
223
+ if (def.hidden === true || (frustum !== undefined && culledBy(def, frustum))) {
224
+ deferred.add(def);
225
+ continue;
226
+ }
227
+ if (layOutStale(def))
228
+ wrote++;
229
+ }
230
+ for (const def of deferred) {
231
+ if (overBudget())
232
+ break;
233
+ deferred.delete(def);
234
+ if (layOutStale(def))
235
+ wrote++;
236
+ }
237
+ const remaining = pending.size + deferred.size;
238
+ if (wrote > 0) {
239
+ const took = (performance.now() - start).toFixed(2);
240
+ logger.debug(`laid out ${wrote} defs (${remaining} remaining) in ${took}ms`);
241
+ }
242
+ return remaining > 0;
243
+ },
244
+ };
245
+ }
@@ -0,0 +1,118 @@
1
+ import { type ThreeEvent } from '@react-three/fiber';
2
+ import { type Intersection, type Object3D, type Ray, type Raycaster } from 'three';
3
+ import { type PlanPoint, type Space } from '../space/Space';
4
+ import { type LayerDef, type RenderableDef } from '../types/defs';
5
+ import { type PointerHandlerName } from '../types/events';
6
+ /**
7
+ * Traces one mesh's intersection back to the def that drew it, or `undefined` when it cannot —
8
+ * a slot released between the raycast and the dispatch, or a mesh whose picking is not implemented.
9
+ */
10
+ export type HitResolver = (hit: Intersection) => RenderableDef | undefined;
11
+ /** The picking props {@link usePointerProps} builds for a layer's `<group>`. */
12
+ export interface PointerGroupProps {
13
+ /** Refuses the raycast traversal into the subtree while the layer is off screen. */
14
+ raycast(): false | undefined;
15
+ /** Dispatches a click in the layer's subtree; attached only when the def declares one. */
16
+ onClick?: (event: ThreeEvent<MouseEvent>) => void;
17
+ /** Dispatches a move over the layer's subtree; attached only when the def declares one. */
18
+ onPointerMove?: (event: ThreeEvent<MouseEvent>) => void;
19
+ /** Dispatches the pointer leaving a def; attached only when the def declares one. */
20
+ onPointerOut?: (event: ThreeEvent<MouseEvent>) => void;
21
+ }
22
+ /**
23
+ * The props that make a layer's group interactive: one dispatching handler per handler the def
24
+ * declares, and the raycast gate that un-picks the layer while it is off screen.
25
+ *
26
+ * Presence is the contract, in both directions. R3F raycasts only objects carrying handlers — and
27
+ * raycasts them with their whole subtree — so a handler is attached exactly when the def declares
28
+ * one and the layer is on screen: a layer wanting clicks alone must not be raycast on every pointer
29
+ * move, and a layer that is hidden, or under something hidden, must not be raycast at all. Which
30
+ * function a handler currently is stays out of it: dispatch looks the handler up by name on the def
31
+ * at event time, so swapping one takes effect with no render.
32
+ *
33
+ * Handler presence is read here as values (`layer.onClick !== undefined`) rather than through
34
+ * `layer` inside a callback: memoization compares what a render *read*, and a property reached only
35
+ * from inside a function is not that — a compiled cache keyed on the def object, whose identity
36
+ * never changes, would keep serving a group with no handler attached.
37
+ *
38
+ * The `raycast` gate covers the direction handler absence cannot: an interactive *ancestor* is
39
+ * raycast with everything beneath it, and `false` is three's own signal to stop descending —
40
+ * `Raycaster`'s `intersect()` keeps walking a subtree only while each object's `raycast` returns
41
+ * something other than `false`. Visibility is read at raycast time rather than swapped per render,
42
+ * since R3F ignores a prop set to `undefined` and a blocking version would stick.
43
+ * @param layer - The layer the group renders.
44
+ * @param visible - Whether the layer is on screen, ancestors included.
45
+ * @returns Props to spread onto the layer's `<group>`.
46
+ */
47
+ export declare function usePointerProps(layer: LayerDef, visible: boolean): PointerGroupProps;
48
+ /**
49
+ * Hands one of R3F's intersection events to a layer's handler, as the narrow event the public API
50
+ * describes: the def that was hit, and where the pointer sits on the plan plane.
51
+ *
52
+ * Two hits are dropped rather than reported. One the renderer cannot trace to a def — a released
53
+ * slot, or a mesh whose picking is not implemented — because a handler has nothing to act on without
54
+ * it. And a click that ends a drag: R3F fires `onClick` on release whenever the press landed on the
55
+ * same object, which on a pannable plan would select whatever the pan started over.
56
+ *
57
+ * The handler is called with no receiver, so a handler shared across layers reads `event.def` rather
58
+ * than the layer it was declared on.
59
+ * @param layer - The layer whose handler should run.
60
+ * @param name - Which handler to run.
61
+ * @param event - R3F's intersection event.
62
+ * @param space - The scene's space, which the event's world coordinates are reported through.
63
+ */
64
+ export declare function dispatchPointer(layer: LayerDef, name: PointerHandlerName, event: ThreeEvent<MouseEvent>, space: Space): void;
65
+ /**
66
+ * Registers how a mesh's intersections map back to defs, for as long as the mesh is mounted.
67
+ *
68
+ * Each container knows two things no one else does: which field of an intersection identifies its
69
+ * slot (`batchId` for a batched mesh, `instanceId` for an instanced one, the pack position for a line
70
+ * batch), and which def holds that slot. It supplies both here as one closure, so the dispatch path
71
+ * stays free of per-mesh-type knowledge.
72
+ * @param mesh - The mesh whose hits the resolver speaks for.
73
+ * @param resolve - Maps one of its intersections to a def.
74
+ * @returns An unregister function, safe to call after another resolver replaced this one.
75
+ */
76
+ export declare function registerHitResolver(mesh: Object3D, resolve: HitResolver): () => void;
77
+ /**
78
+ * The def an intersection hit.
79
+ *
80
+ * A mesh with no registered resolver reports nothing, which is how a primitive whose picking is not
81
+ * implemented behaves as if its layer were not interactive: the hit is dropped and no handler runs.
82
+ * @param hit - The intersection to trace.
83
+ * @returns The def, or `undefined` when the hit cannot be traced to one.
84
+ */
85
+ export declare function resolveHit(hit: Intersection): RenderableDef | undefined;
86
+ /**
87
+ * Where a missed click landed, in plan units.
88
+ *
89
+ * **A miss is not an empty canvas.** It means no handler took the click: a layer declaring no
90
+ * pointer handlers is never raycast by R3F in the first place, so the pointer may be sitting
91
+ * squarely on a wall, a model or a booth that simply does not listen. Searching the scene here is
92
+ * what makes the reported position agree with what the user is looking at — the same answer a hit
93
+ * event carries, arrived at a moment later and with no def to name.
94
+ *
95
+ * The search prunes hidden content for free: {@link usePointerProps} refuses the traversal into a
96
+ * hidden layer's subtree, which is three's own signal to stop descending.
97
+ * @param raycaster - The scene's raycaster, holding the ray R3F cast for this click.
98
+ * @param scene - The scene to search.
99
+ * @param space - The scene's space.
100
+ * @returns The nearest geometry under the pointer, or — where there is none — the floor beneath it.
101
+ */
102
+ export declare function missedPoint(raycaster: Raycaster, scene: Object3D, space: Space): PlanPoint;
103
+ /**
104
+ * Where a ray crosses the plan plane — the floor under the pointer, whatever is drawn above it.
105
+ *
106
+ * The fallback for a click that reached neither a handler nor any geometry at all: past the edge of
107
+ * the plan there is nothing to report but the ground the ray carries on to.
108
+ *
109
+ * Both the crossing and the fallback are taken through the scene's space, because R3F casts its rays
110
+ * in world coordinates and a scene fitted to the canvas draws a plan unit as some other number of
111
+ * those. Without it the reported position is the world one wearing plan units' name.
112
+ * @param ray - The ray the pointer cast, in world space.
113
+ * @param space - The scene's space.
114
+ * @returns The crossing, in plan units, or — for a ray running parallel to the plan, which crosses
115
+ * it nowhere — where the ray started.
116
+ */
117
+ export declare function planePoint(ray: Ray, space: Space): PlanPoint;
118
+ //# sourceMappingURL=pointer.d.ts.map
@@ -0,0 +1,168 @@
1
+ import { useSpace } from '../space/Space';
2
+ /**
3
+ * The props that make a layer's group interactive: one dispatching handler per handler the def
4
+ * declares, and the raycast gate that un-picks the layer while it is off screen.
5
+ *
6
+ * Presence is the contract, in both directions. R3F raycasts only objects carrying handlers — and
7
+ * raycasts them with their whole subtree — so a handler is attached exactly when the def declares
8
+ * one and the layer is on screen: a layer wanting clicks alone must not be raycast on every pointer
9
+ * move, and a layer that is hidden, or under something hidden, must not be raycast at all. Which
10
+ * function a handler currently is stays out of it: dispatch looks the handler up by name on the def
11
+ * at event time, so swapping one takes effect with no render.
12
+ *
13
+ * Handler presence is read here as values (`layer.onClick !== undefined`) rather than through
14
+ * `layer` inside a callback: memoization compares what a render *read*, and a property reached only
15
+ * from inside a function is not that — a compiled cache keyed on the def object, whose identity
16
+ * never changes, would keep serving a group with no handler attached.
17
+ *
18
+ * The `raycast` gate covers the direction handler absence cannot: an interactive *ancestor* is
19
+ * raycast with everything beneath it, and `false` is three's own signal to stop descending —
20
+ * `Raycaster`'s `intersect()` keeps walking a subtree only while each object's `raycast` returns
21
+ * something other than `false`. Visibility is read at raycast time rather than swapped per render,
22
+ * since R3F ignores a prop set to `undefined` and a blocking version would stick.
23
+ * @param layer - The layer the group renders.
24
+ * @param visible - Whether the layer is on screen, ancestors included.
25
+ * @returns Props to spread onto the layer's `<group>`.
26
+ */
27
+ export function usePointerProps(layer, visible) {
28
+ // Pointer positions are reported on the plan, and R3F casts its rays in world coordinates.
29
+ const space = useSpace();
30
+ const handlesClick = visible && layer.onClick !== undefined;
31
+ const handlesPointerMove = visible && layer.onPointerMove !== undefined;
32
+ const handlesPointerOut = visible && layer.onPointerOut !== undefined;
33
+ function handler(name) {
34
+ return (event) => dispatchPointer(layer, name, event, space);
35
+ }
36
+ return {
37
+ raycast: () => (visible ? undefined : false),
38
+ onClick: handlesClick ? handler('onClick') : undefined,
39
+ onPointerMove: handlesPointerMove ? handler('onPointerMove') : undefined,
40
+ onPointerOut: handlesPointerOut ? handler('onPointerOut') : undefined,
41
+ };
42
+ }
43
+ /**
44
+ * Hands one of R3F's intersection events to a layer's handler, as the narrow event the public API
45
+ * describes: the def that was hit, and where the pointer sits on the plan plane.
46
+ *
47
+ * Two hits are dropped rather than reported. One the renderer cannot trace to a def — a released
48
+ * slot, or a mesh whose picking is not implemented — because a handler has nothing to act on without
49
+ * it. And a click that ends a drag: R3F fires `onClick` on release whenever the press landed on the
50
+ * same object, which on a pannable plan would select whatever the pan started over.
51
+ *
52
+ * The handler is called with no receiver, so a handler shared across layers reads `event.def` rather
53
+ * than the layer it was declared on.
54
+ * @param layer - The layer whose handler should run.
55
+ * @param name - Which handler to run.
56
+ * @param event - R3F's intersection event.
57
+ * @param space - The scene's space, which the event's world coordinates are reported through.
58
+ */
59
+ export function dispatchPointer(layer, name, event, space) {
60
+ const handler = layer[name];
61
+ if (!handler)
62
+ return;
63
+ if (name === 'onClick' && isDrag(event))
64
+ return;
65
+ const def = resolveHit(event);
66
+ if (!def)
67
+ return;
68
+ handler({
69
+ def,
70
+ // The ray's own intersection with the geometry, which three computed against the real
71
+ // triangles — so an elevated def reports the point on *it*, elevation and all, rather than the
72
+ // ground the ray carries on to behind it. R3F reports it in world coordinates; the cast is the
73
+ // assertion of which space that is.
74
+ point: space.worldToPlan(event.point),
75
+ stopPropagation: () => event.stopPropagation(),
76
+ nativeEvent: event.nativeEvent,
77
+ });
78
+ }
79
+ /**
80
+ * Registers how a mesh's intersections map back to defs, for as long as the mesh is mounted.
81
+ *
82
+ * Each container knows two things no one else does: which field of an intersection identifies its
83
+ * slot (`batchId` for a batched mesh, `instanceId` for an instanced one, the pack position for a line
84
+ * batch), and which def holds that slot. It supplies both here as one closure, so the dispatch path
85
+ * stays free of per-mesh-type knowledge.
86
+ * @param mesh - The mesh whose hits the resolver speaks for.
87
+ * @param resolve - Maps one of its intersections to a def.
88
+ * @returns An unregister function, safe to call after another resolver replaced this one.
89
+ */
90
+ export function registerHitResolver(mesh, resolve) {
91
+ resolvers.set(mesh, resolve);
92
+ return () => {
93
+ if (resolvers.get(mesh) === resolve)
94
+ resolvers.delete(mesh);
95
+ };
96
+ }
97
+ /**
98
+ * The def an intersection hit.
99
+ *
100
+ * A mesh with no registered resolver reports nothing, which is how a primitive whose picking is not
101
+ * implemented behaves as if its layer were not interactive: the hit is dropped and no handler runs.
102
+ * @param hit - The intersection to trace.
103
+ * @returns The def, or `undefined` when the hit cannot be traced to one.
104
+ */
105
+ export function resolveHit(hit) {
106
+ return resolvers.get(hit.object)?.(hit);
107
+ }
108
+ /**
109
+ * Where a missed click landed, in plan units.
110
+ *
111
+ * **A miss is not an empty canvas.** It means no handler took the click: a layer declaring no
112
+ * pointer handlers is never raycast by R3F in the first place, so the pointer may be sitting
113
+ * squarely on a wall, a model or a booth that simply does not listen. Searching the scene here is
114
+ * what makes the reported position agree with what the user is looking at — the same answer a hit
115
+ * event carries, arrived at a moment later and with no def to name.
116
+ *
117
+ * The search prunes hidden content for free: {@link usePointerProps} refuses the traversal into a
118
+ * hidden layer's subtree, which is three's own signal to stop descending.
119
+ * @param raycaster - The scene's raycaster, holding the ray R3F cast for this click.
120
+ * @param scene - The scene to search.
121
+ * @param space - The scene's space.
122
+ * @returns The nearest geometry under the pointer, or — where there is none — the floor beneath it.
123
+ */
124
+ export function missedPoint(raycaster, scene, space) {
125
+ // Sorted nearest-first by three, so the first hit is the one the user is looking at.
126
+ const [nearest] = raycaster.intersectObject(scene, true);
127
+ if (nearest)
128
+ return space.worldToPlan(nearest.point);
129
+ return planePoint(raycaster.ray, space);
130
+ }
131
+ /**
132
+ * Where a ray crosses the plan plane — the floor under the pointer, whatever is drawn above it.
133
+ *
134
+ * The fallback for a click that reached neither a handler nor any geometry at all: past the edge of
135
+ * the plan there is nothing to report but the ground the ray carries on to.
136
+ *
137
+ * Both the crossing and the fallback are taken through the scene's space, because R3F casts its rays
138
+ * in world coordinates and a scene fitted to the canvas draws a plan unit as some other number of
139
+ * those. Without it the reported position is the world one wearing plan units' name.
140
+ * @param ray - The ray the pointer cast, in world space.
141
+ * @param space - The scene's space.
142
+ * @returns The crossing, in plan units, or — for a ray running parallel to the plan, which crosses
143
+ * it nowhere — where the ray started.
144
+ */
145
+ export function planePoint(ray, space) {
146
+ return space.rayToPlan(ray) ?? space.worldToPlan(ray.origin);
147
+ }
148
+ /**
149
+ * Whether a press-and-release traveled far enough to be a drag rather than a click.
150
+ *
151
+ * R3F's `delta` is the pointer's travel since the press, rounded to whole pixels.
152
+ * @param event - The click event to judge.
153
+ * @returns Whether to treat it as a drag.
154
+ */
155
+ function isDrag(event) {
156
+ return event.delta * event.delta > DRAG_SLOP_SQ_PX;
157
+ }
158
+ /**
159
+ * Squared pointer travel, in px², above which a release is a drag and not a click — so ~3.9 px of
160
+ * slop. Squared because that is how the imperative renderer's `dragThreshold` compared it, and this
161
+ * reproduces its feel exactly; a threshold in whole pixels would be four times looser.
162
+ */
163
+ const DRAG_SLOP_SQ_PX = 15;
164
+ /**
165
+ * Mesh-to-def resolution for every mounted container, keyed weakly by mesh identity — so nothing has
166
+ * to be cleaned up per renderer, and two renderers holding the same defs never collide.
167
+ */
168
+ const resolvers = new WeakMap();
@@ -0,0 +1,56 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * What a host that owns the canvas hands over — a MapLibre custom layer in practice. `Renderer`
4
+ * carries all three as optional props ({@link RendererProps} extends the partial of this), and
5
+ * `gl` arriving is what selects external mode, exactly as it selected the imperative renderer's.
6
+ */
7
+ export interface ExternalCanvasProps {
8
+ /**
9
+ * The host's WebGL context. Rendering shares the host's context and canvas (`gl.canvas`) inside
10
+ * the host's own frame loop.
11
+ */
12
+ gl: WebGLRenderingContext | WebGL2RenderingContext;
13
+ /**
14
+ * Asks the host for a frame — `map.triggerRepaint()` under MapLibre. The demand loop's other
15
+ * half: the host owns every frame, so anything that would invalidate the renderer's own canvas —
16
+ * an `update()`, a texture upload, a re-layout backlog — asks the host instead.
17
+ */
18
+ requestFrame: () => void;
19
+ /**
20
+ * Subscribes the renderer's per-frame draw to the host's render hook. The host calls the
21
+ * subscribed function once per frame it draws — from a MapLibre custom layer's `render` — with
22
+ * its combined view-projection matrix, sixteen elements column-major.
23
+ * @param draw - Draws one frame with the host's matrix.
24
+ * @returns An unsubscribe function.
25
+ */
26
+ onFrame: (draw: (projectionMatrix: ArrayLike<number>) => void) => () => void;
27
+ }
28
+ /**
29
+ * `<Canvas>`'s stand-in for a host that already owns the canvas: everything the Canvas component
30
+ * does — boot an R3F root, configure the renderer, the events, the size — minus the one thing it
31
+ * cannot share, creating the canvas element. It knows nothing of the scene: `children` render into
32
+ * the root verbatim, so `Renderer` composes one tree and only the mount differs.
33
+ *
34
+ * The bootstrap is imperative because the host's contract is: there is no React tree to mount into
35
+ * until a context exists. R3F's `<Canvas>` cannot serve here — it creates and owns its own canvas
36
+ * element inside a wrapper it renders, and has no way to adopt a foreign one — so this takes R3F's
37
+ * own escape hatch for exactly that, `createRoot(canvas)`, which is all `<Canvas>` wraps anyway.
38
+ * It is configured once per context, while the tree re-renders through props like any other.
39
+ *
40
+ * The camera half is the mount's as well, because the host owns it: one bare camera carries
41
+ * whatever matrix the host handed the frame hook, decomposed into a rigid view and a projection so
42
+ * that everything reading a camera's transform — culling, raycasts, lighting — works unchanged.
43
+ * There is no viewbox fit, no controls and no camera commands; the tree above simply mounts none
44
+ * of them.
45
+ *
46
+ * The demand loop inverts. R3F's own `invalidate` is a no-op under `frameloop: 'never'`, so the
47
+ * store's copy is replaced with the host's `requestFrame` — and every host frame redraws in full,
48
+ * as a shared canvas requires, starting from `resetState()` so three's GL-state tracking never
49
+ * trusts what the host's own drawing left behind.
50
+ * @param props - The host's hooks, and the tree to render into the root.
51
+ * @returns Nothing; the tree renders on the host's canvas, not in this one.
52
+ */
53
+ export declare function ExternalCanvas(props: ExternalCanvasProps & {
54
+ children?: ReactNode;
55
+ }): null;
56
+ //# sourceMappingURL=ExternalCanvas.d.ts.map