@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.
Files changed (280) hide show
  1. package/README.md +145 -0
  2. package/dist/Renderer.d.ts +102 -0
  3. package/dist/Renderer.js +96 -0
  4. package/dist/controls/CameraControls.d.ts +108 -0
  5. package/dist/controls/CameraControls.js +203 -0
  6. package/dist/controls/Controls.d.ts +53 -0
  7. package/dist/controls/Controls.js +54 -0
  8. package/dist/controls/camera-api.d.ts +226 -0
  9. package/dist/controls/camera-api.js +179 -0
  10. package/dist/controls/gestures.d.ts +71 -0
  11. package/dist/controls/gestures.js +98 -0
  12. package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
  13. package/dist/controls/handlers/DoubleTapZoom.js +82 -0
  14. package/dist/controls/handlers/Inertia.d.ts +60 -0
  15. package/dist/controls/handlers/Inertia.js +222 -0
  16. package/dist/controls/handlers/Pan.d.ts +23 -0
  17. package/dist/controls/handlers/Pan.js +212 -0
  18. package/dist/controls/handlers/Pitch.d.ts +19 -0
  19. package/dist/controls/handlers/Pitch.js +200 -0
  20. package/dist/controls/handlers/Roll.d.ts +30 -0
  21. package/dist/controls/handlers/Roll.js +196 -0
  22. package/dist/controls/handlers/Zoom.d.ts +28 -0
  23. package/dist/controls/handlers/Zoom.js +34 -0
  24. package/dist/controls/hooks.d.ts +136 -0
  25. package/dist/controls/hooks.js +200 -0
  26. package/dist/controls/index.d.ts +9 -0
  27. package/dist/controls/index.js +7 -0
  28. package/dist/controls/zoom-to.d.ts +89 -0
  29. package/dist/controls/zoom-to.js +91 -0
  30. package/dist/core/InstanceUniforms.d.ts +108 -0
  31. package/dist/core/InstanceUniforms.js +298 -0
  32. package/dist/core/SquareDataTexture.d.ts +277 -0
  33. package/dist/core/SquareDataTexture.js +476 -0
  34. package/dist/core/batch/BatchedMesh.d.ts +74 -0
  35. package/dist/core/batch/BatchedMesh.js +155 -0
  36. package/dist/core/instanced/FrustumCulling.d.ts +141 -0
  37. package/dist/core/instanced/FrustumCulling.js +246 -0
  38. package/dist/core/instanced/InstancedMesh.d.ts +302 -0
  39. package/dist/core/instanced/InstancedMesh.js +497 -0
  40. package/dist/core/instanced/Instances.d.ts +119 -0
  41. package/dist/core/instanced/Instances.js +230 -0
  42. package/dist/core/instanced/Raycasting.d.ts +118 -0
  43. package/dist/core/instanced/Raycasting.js +134 -0
  44. package/dist/core/instanced/instanced-material.d.ts +77 -0
  45. package/dist/core/instanced/instanced-material.js +228 -0
  46. package/dist/core/lines/BatchedLines.d.ts +137 -0
  47. package/dist/core/lines/BatchedLines.js +215 -0
  48. package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
  49. package/dist/core/lines/FrustumCullingLines.js +85 -0
  50. package/dist/core/lines/RaycastingLines.d.ts +45 -0
  51. package/dist/core/lines/RaycastingLines.js +91 -0
  52. package/dist/core/lines/lines-material.d.ts +47 -0
  53. package/dist/core/lines/lines-material.js +124 -0
  54. package/dist/core/shader-source.d.ts +70 -0
  55. package/dist/core/shader-source.js +110 -0
  56. package/dist/core/sprites/BatchedSprites.d.ts +115 -0
  57. package/dist/core/sprites/BatchedSprites.js +173 -0
  58. package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
  59. package/dist/core/sprites/RaycastingSprites.js +71 -0
  60. package/dist/core/sprites/sprite-material.d.ts +59 -0
  61. package/dist/core/sprites/sprite-material.js +192 -0
  62. package/dist/core/text/BatchedText.d.ts +233 -0
  63. package/dist/core/text/BatchedText.js +473 -0
  64. package/dist/core/text/MemberTexture.d.ts +90 -0
  65. package/dist/core/text/MemberTexture.js +289 -0
  66. package/dist/core/text/Text.d.ts +88 -0
  67. package/dist/core/text/Text.js +230 -0
  68. package/dist/core/text/batched-text-material.d.ts +56 -0
  69. package/dist/core/text/batched-text-material.js +123 -0
  70. package/dist/core/text/glyph-packing.d.ts +17 -0
  71. package/dist/core/text/glyph-packing.js +61 -0
  72. package/dist/core/typed-array.d.ts +16 -0
  73. package/dist/core/typed-array.js +20 -0
  74. package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
  75. package/dist/debug/components/DebugPanelProvider.js +50 -0
  76. package/dist/debug/components/DebugTools.d.ts +22 -0
  77. package/dist/debug/components/DebugTools.js +28 -0
  78. package/dist/debug/components/Gui.d.ts +22 -0
  79. package/dist/debug/components/Gui.js +42 -0
  80. package/dist/debug/components/MemoryInfo.d.ts +22 -0
  81. package/dist/debug/components/MemoryInfo.js +42 -0
  82. package/dist/debug/components/Panel.d.ts +31 -0
  83. package/dist/debug/components/Panel.js +104 -0
  84. package/dist/debug/components/Stats.d.ts +9 -0
  85. package/dist/debug/components/Stats.js +24 -0
  86. package/dist/debug/debug-chunk.d.ts +26 -0
  87. package/dist/debug/debug-chunk.js +32 -0
  88. package/dist/debug/debug-context.d.ts +41 -0
  89. package/dist/debug/debug-context.js +59 -0
  90. package/dist/debug/index.d.ts +41 -0
  91. package/dist/debug/index.js +43 -0
  92. package/dist/events/LayoutDriver.d.ts +36 -0
  93. package/dist/events/LayoutDriver.js +192 -0
  94. package/dist/events/PointerMissed.d.ts +23 -0
  95. package/dist/events/PointerMissed.js +58 -0
  96. package/dist/events/def-culling.d.ts +46 -0
  97. package/dist/events/def-culling.js +69 -0
  98. package/dist/events/layout-targets.d.ts +151 -0
  99. package/dist/events/layout-targets.js +397 -0
  100. package/dist/events/placement/box.d.ts +59 -0
  101. package/dist/events/placement/box.js +96 -0
  102. package/dist/events/placement/grid.d.ts +161 -0
  103. package/dist/events/placement/grid.js +235 -0
  104. package/dist/events/placement/index.d.ts +2 -0
  105. package/dist/events/placement/index.js +1 -0
  106. package/dist/events/placement/pass.d.ts +70 -0
  107. package/dist/events/placement/pass.js +617 -0
  108. package/dist/events/pointer.d.ts +194 -0
  109. package/dist/events/pointer.js +356 -0
  110. package/dist/external/ExternalCanvas.d.ts +56 -0
  111. package/dist/external/ExternalCanvas.js +177 -0
  112. package/dist/external/external-camera.d.ts +18 -0
  113. package/dist/external/external-camera.js +103 -0
  114. package/dist/external/external-events.d.ts +14 -0
  115. package/dist/external/external-events.js +33 -0
  116. package/dist/index.d.ts +13 -0
  117. package/dist/index.js +14 -0
  118. package/dist/objects/Background.d.ts +31 -0
  119. package/dist/objects/Background.js +37 -0
  120. package/dist/objects/containers/SlotContainer.d.ts +312 -0
  121. package/dist/objects/containers/SlotContainer.js +311 -0
  122. package/dist/objects/containers/hooks.d.ts +61 -0
  123. package/dist/objects/containers/hooks.js +196 -0
  124. package/dist/objects/containers/index.d.ts +6 -0
  125. package/dist/objects/containers/index.js +4 -0
  126. package/dist/objects/containers/keys.d.ts +10 -0
  127. package/dist/objects/containers/keys.js +18 -0
  128. package/dist/objects/containers/mesh-writes.d.ts +106 -0
  129. package/dist/objects/containers/mesh-writes.js +97 -0
  130. package/dist/objects/containers/props.d.ts +33 -0
  131. package/dist/objects/containers/props.js +1 -0
  132. package/dist/objects/images/ImageInstance.d.ts +29 -0
  133. package/dist/objects/images/ImageInstance.js +152 -0
  134. package/dist/objects/images/Images.d.ts +49 -0
  135. package/dist/objects/images/Images.js +394 -0
  136. package/dist/objects/images/atlas-packer.d.ts +129 -0
  137. package/dist/objects/images/atlas-packer.js +287 -0
  138. package/dist/objects/images/image-container.d.ts +74 -0
  139. package/dist/objects/images/image-container.js +100 -0
  140. package/dist/objects/images/index.d.ts +2 -0
  141. package/dist/objects/images/index.js +1 -0
  142. package/dist/objects/images/pack-sources.d.ts +190 -0
  143. package/dist/objects/images/pack-sources.js +272 -0
  144. package/dist/objects/images/texture-budget.d.ts +120 -0
  145. package/dist/objects/images/texture-budget.js +215 -0
  146. package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
  147. package/dist/objects/images/worker/atlas-worker.js +123 -0
  148. package/dist/objects/images/worker/client.d.ts +62 -0
  149. package/dist/objects/images/worker/client.js +249 -0
  150. package/dist/objects/images/worker/index.d.ts +10 -0
  151. package/dist/objects/images/worker/index.js +1 -0
  152. package/dist/objects/layer/Layer.d.ts +28 -0
  153. package/dist/objects/layer/Layer.js +134 -0
  154. package/dist/objects/layer/index.d.ts +2 -0
  155. package/dist/objects/layer/index.js +1 -0
  156. package/dist/objects/layer/layer-context.d.ts +74 -0
  157. package/dist/objects/layer/layer-context.js +82 -0
  158. package/dist/objects/layer/mount-scheduler.d.ts +131 -0
  159. package/dist/objects/layer/mount-scheduler.js +289 -0
  160. package/dist/objects/layer/visibility-gui.d.ts +19 -0
  161. package/dist/objects/layer/visibility-gui.js +93 -0
  162. package/dist/objects/lighting/Lighting.d.ts +92 -0
  163. package/dist/objects/lighting/Lighting.js +384 -0
  164. package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
  165. package/dist/objects/lighting/OcclusionPasses.js +271 -0
  166. package/dist/objects/lighting/index.d.ts +11 -0
  167. package/dist/objects/lighting/index.js +10 -0
  168. package/dist/objects/lighting/lighting-gui.d.ts +24 -0
  169. package/dist/objects/lighting/lighting-gui.js +114 -0
  170. package/dist/objects/lighting/occluder.d.ts +34 -0
  171. package/dist/objects/lighting/occluder.js +62 -0
  172. package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
  173. package/dist/objects/lighting/occlusion-targets.js +276 -0
  174. package/dist/objects/lines/LineInstance.d.ts +21 -0
  175. package/dist/objects/lines/LineInstance.js +73 -0
  176. package/dist/objects/lines/Lines.d.ts +17 -0
  177. package/dist/objects/lines/Lines.js +31 -0
  178. package/dist/objects/lines/index.d.ts +2 -0
  179. package/dist/objects/lines/index.js +1 -0
  180. package/dist/objects/lines/line-container.d.ts +35 -0
  181. package/dist/objects/lines/line-container.js +40 -0
  182. package/dist/objects/materials/ColorMaterial.d.ts +92 -0
  183. package/dist/objects/materials/ColorMaterial.js +124 -0
  184. package/dist/objects/materials/LineMaterial.d.ts +44 -0
  185. package/dist/objects/materials/LineMaterial.js +74 -0
  186. package/dist/objects/materials/ModelMaterial.d.ts +53 -0
  187. package/dist/objects/materials/ModelMaterial.js +111 -0
  188. package/dist/objects/materials/TextureMaterial.d.ts +33 -0
  189. package/dist/objects/materials/TextureMaterial.js +48 -0
  190. package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
  191. package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
  192. package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
  193. package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
  194. package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
  195. package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
  196. package/dist/objects/materials/effects/ao/index.d.ts +129 -0
  197. package/dist/objects/materials/effects/ao/index.js +72 -0
  198. package/dist/objects/materials/effects/color-dim.d.ts +115 -0
  199. package/dist/objects/materials/effects/color-dim.js +206 -0
  200. package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
  201. package/dist/objects/materials/effects/contact-shadow.js +167 -0
  202. package/dist/objects/materials/effects/effects-context.d.ts +53 -0
  203. package/dist/objects/materials/effects/effects-context.js +24 -0
  204. package/dist/objects/materials/shared.d.ts +24 -0
  205. package/dist/objects/materials/shared.js +72 -0
  206. package/dist/objects/models/ModelInstance.d.ts +22 -0
  207. package/dist/objects/models/ModelInstance.js +33 -0
  208. package/dist/objects/models/Models.d.ts +22 -0
  209. package/dist/objects/models/Models.js +186 -0
  210. package/dist/objects/models/gltf-cache.d.ts +17 -0
  211. package/dist/objects/models/gltf-cache.js +50 -0
  212. package/dist/objects/models/index.d.ts +2 -0
  213. package/dist/objects/models/index.js +1 -0
  214. package/dist/objects/models/model-asset.d.ts +48 -0
  215. package/dist/objects/models/model-asset.js +77 -0
  216. package/dist/objects/models/model-container.d.ts +123 -0
  217. package/dist/objects/models/model-container.js +207 -0
  218. package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
  219. package/dist/objects/shapes/ShapeInstance.js +39 -0
  220. package/dist/objects/shapes/Shapes.d.ts +21 -0
  221. package/dist/objects/shapes/Shapes.js +131 -0
  222. package/dist/objects/shapes/index.d.ts +2 -0
  223. package/dist/objects/shapes/index.js +1 -0
  224. package/dist/objects/shapes/shape-container.d.ts +141 -0
  225. package/dist/objects/shapes/shape-container.js +242 -0
  226. package/dist/objects/shapes/shape-geometry.d.ts +27 -0
  227. package/dist/objects/shapes/shape-geometry.js +94 -0
  228. package/dist/objects/text/TextInstance.d.ts +22 -0
  229. package/dist/objects/text/TextInstance.js +95 -0
  230. package/dist/objects/text/Texts.d.ts +30 -0
  231. package/dist/objects/text/Texts.js +43 -0
  232. package/dist/objects/text/index.d.ts +2 -0
  233. package/dist/objects/text/index.js +1 -0
  234. package/dist/objects/text/text-container.d.ts +121 -0
  235. package/dist/objects/text/text-container.js +229 -0
  236. package/dist/objects/text/text-layout.d.ts +92 -0
  237. package/dist/objects/text/text-layout.js +180 -0
  238. package/dist/space/Camera.d.ts +103 -0
  239. package/dist/space/Camera.js +321 -0
  240. package/dist/space/Scene.d.ts +47 -0
  241. package/dist/space/Scene.js +127 -0
  242. package/dist/space/Space.d.ts +325 -0
  243. package/dist/space/Space.js +359 -0
  244. package/dist/space/VisibleRect.d.ts +45 -0
  245. package/dist/space/VisibleRect.js +129 -0
  246. package/dist/space/camera-handle.d.ts +67 -0
  247. package/dist/space/camera-handle.js +63 -0
  248. package/dist/space/camera-kind.d.ts +26 -0
  249. package/dist/space/camera-kind.js +30 -0
  250. package/dist/space/canvas-size.d.ts +24 -0
  251. package/dist/space/canvas-size.js +20 -0
  252. package/dist/space/clipping.d.ts +69 -0
  253. package/dist/space/clipping.js +130 -0
  254. package/dist/space/layout-measure.d.ts +67 -0
  255. package/dist/space/layout-measure.js +175 -0
  256. package/dist/space/viewport.d.ts +112 -0
  257. package/dist/space/viewport.js +67 -0
  258. package/dist/types/defs.d.ts +234 -0
  259. package/dist/types/defs.js +1 -0
  260. package/dist/types/events.d.ts +198 -0
  261. package/dist/types/events.js +1 -0
  262. package/dist/types/guards.d.ts +21 -0
  263. package/dist/types/guards.js +52 -0
  264. package/dist/update/index.d.ts +3 -0
  265. package/dist/update/index.js +2 -0
  266. package/dist/update/slot-writers.d.ts +30 -0
  267. package/dist/update/slot-writers.js +76 -0
  268. package/dist/update/store.d.ts +41 -0
  269. package/dist/update/store.js +104 -0
  270. package/dist/utils/arrays.d.ts +13 -0
  271. package/dist/utils/arrays.js +20 -0
  272. package/dist/utils/colors.d.ts +19 -0
  273. package/dist/utils/colors.js +27 -0
  274. package/dist/utils/hot-state.d.ts +41 -0
  275. package/dist/utils/hot-state.js +37 -0
  276. package/dist/utils/logging.d.ts +29 -0
  277. package/dist/utils/logging.js +55 -0
  278. package/dist/utils/math.d.ts +52 -0
  279. package/dist/utils/math.js +76 -0
  280. package/package.json +79 -0
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @expofp/renderer
2
+
3
+ Interactive floor plan renderer built on [react-three-fiber](https://r3f.docs.pmnd.rs/).
4
+
5
+ ## Architecture
6
+
7
+ - **`Renderer`** (`src/Renderer.tsx`) — the public entry: mounts the R3F
8
+ `Canvas` and the scene (`controls/`, `lighting/`, `batch/`). Pass `debug` to
9
+ enable the debug overlay.
10
+ - **Scene topics** live in their own folders (`batch/`, `controls/`,
11
+ `lighting/`, …); add new rendering features as sibling topic folders.
12
+ - **`debug/`** — the debug overlay (lil-gui panel + stats-gl overlay), loaded
13
+ through a single dynamic `import()` in `Renderer`. Everything under `debug/`
14
+ (and its heavier deps, lil-gui + stats-gl) is code-split into one lazily
15
+ loaded chunk that is **never fetched when `debug` is off**. Add new debug
16
+ pieces to `debug/index.tsx` so they ride along in the same chunk.
17
+
18
+ ## Navigation
19
+
20
+ `RendererProps` carries two different kinds of thing:
21
+
22
+ - **Camera limits** — `minZoom` / `maxZoom` / `minPitch` / `maxPitch`, declared as
23
+ `CameraProps` in `src/space/Camera.tsx`. They constrain the camera itself, so
24
+ they hold for a programmatic move as much as for a dragged one. `Camera` writes
25
+ them and defaults them, beside the clipping planes derived from the same
26
+ numbers; `maxPitch` both defaults to and is capped at `MAX_PITCH` (degrees, in
27
+ `clipping.ts`), the angle every depth calculation in the renderer is sized
28
+ against.
29
+ - **`controls`** — what a _user_ may do. Each handler takes `false`, `true` or
30
+ omitted for its defaults, or an options object.
31
+
32
+ ```tsx
33
+ <Renderer
34
+ scene={scene}
35
+ minZoom={0.1}
36
+ maxZoom={100}
37
+ minPitch={0}
38
+ maxPitch={85}
39
+ controls={{
40
+ pan: { inertia: { durationMs: 600 } },
41
+ zoom: true,
42
+ roll: { threshold: 25 },
43
+ pitch: false,
44
+ }}
45
+ />
46
+ ```
47
+
48
+ Pan and zoom default on; **roll and pitch default on only in a development
49
+ build**, so this package's Storybook and dev server have them and a production
50
+ plan does not until a caller asks. An explicit value always wins.
51
+
52
+ Each handler is a component under `src/controls/handlers/`, mirroring the
53
+ imperative renderer's `ui/handlers/`, and owns its own config type and defaults.
54
+ Mounting is enabling: a handler claims its mouse and touch actions on mount and
55
+ gives them back on unmount, and the controls are built with every action switched
56
+ off. To add a gesture, write a sibling component and a field on `ControlsConfig`.
57
+
58
+ `Camera` writes the limits in two effects — the pitch limits, and the framing
59
+ (distance limits, far plane, and the dolly that preserves zoom across a resize),
60
+ which is one transaction because the dolly must follow the limits.
61
+
62
+ The controls are reached two ways and no other: `useCameraControls()` reads
63
+ them, and `useControlsEffect(effect)` runs an effect against them once they
64
+ exist, handing them over as an argument. The React Compiler rejects assigning to
65
+ a _field_ of a hook's return value — a method call on it, or passing it to a
66
+ function that does the assigning, is fine — so a component with a field to write
67
+ either routes it through a helper or uses `useControlsEffect`. Two-finger
68
+ gestures come off `useGesture(type, handler)`.
69
+
70
+ ## Adding GUI controls from a component
71
+
72
+ `debug/gui-context.ts` exposes `useGui()`. It returns the active lil-gui
73
+ instance, or `null` when the overlay is disabled or still loading — so guard on a
74
+ non-null value. `useGui` is imported from the tiny `gui-context` module (a
75
+ type-only lil-gui import), so consuming it does **not** pull lil-gui into the
76
+ main chunk.
77
+
78
+ ```tsx
79
+ function MyThing() {
80
+ const gui = useGui();
81
+ useEffect(() => {
82
+ if (!gui) return;
83
+ const folder = gui.addFolder('My thing');
84
+ folder.add(/* … */);
85
+ return () => folder.destroy();
86
+ }, [gui]);
87
+ // …
88
+ }
89
+ ```
90
+
91
+ ## Recommended extensions
92
+
93
+ Two browser extensions make what the renderer draws inspectable:
94
+
95
+ - **[Three.js DevTools](https://chromewebstore.google.com/detail/threejs-devtools/jechbjkglifdaldbdbigibihfaclnkbo)**
96
+ adds a DevTools tab showing the live scene tree — the quickest way to see how
97
+ Defs translate into three.js objects. Hovering a mesh there highlights its
98
+ geometry on the plan.
99
+ - **[SpectorJS](https://spector.babylonjs.com/)** lists every WebGL draw call of
100
+ a captured frame, with the intermediate canvas state between them. It takes a
101
+ little coaxing: tick **Show offscreen canvas(es)** to see the canvases React
102
+ renders (this works inconsistently — closing and reopening the extension a
103
+ couple of times usually gets there), pick the canvas from the **Choose
104
+ canvas…** dropdown by matching its dimensions, then press the red **Record**
105
+ button. In debug mode the capture opens immediately, because the scene
106
+ re-renders every frame; without it you have to trigger a frame first, most
107
+ easily by panning or zooming slightly.
108
+
109
+ ## Running unit tests
110
+
111
+ Run `nx test @expofp/renderer` to execute the unit tests via [Vitest](https://vitest.dev/).
112
+
113
+ ## Limits of the published package
114
+
115
+ **`dist` is not supported outside this repository. It does not even resolve.**
116
+ `dist/objects/images/worker/client.js` holds
117
+ `import AtlasWorker from './atlas-worker?worker&inline'`, and `dist/index.js`
118
+ reaches that file. The `?worker&inline` specifier is a Vite instruction, so
119
+ only Vite can answer it. Any other consumer — a different bundler, plain Node,
120
+ a browser loading the files as they are — fails to resolve the import and stops
121
+ there. Nothing in the package loads, so there is no smaller part of it to fall
122
+ back on.
123
+
124
+ This package has no bundler. Its `build` target is plain `tsc`, so `dist` is
125
+ file-per-module TypeScript output and nothing else runs over it. `tsc` copies
126
+ the specifier through unchanged, which is where the broken import comes from.
127
+ The same absence of a bundler means the React Compiler never runs on `dist`
128
+ either, so the components there ship unoptimized.
129
+
130
+ Nothing in this repository is affected, because nothing in this repository
131
+ consumes `dist`. The renderer is compiled from its own source instead.
132
+ Floorplan's browser build reaches `src` through the `@expofp/source` condition
133
+ in `vite.config.mts`, so Vite answers the specifier and inlines the worker
134
+ script into the bundle; floorplan also applies its own React Compiler pass.
135
+ This package's own unit tests and Storybook import `src` by relative path, so
136
+ Vite answers the specifier for them too: the Storybook run starts the real
137
+ worker, and the unit tests drive the protocol against a fake one. Wayfinding
138
+ imports types only, which are erased at emit. The offline CLI copies
139
+ floorplan's browser bundle rather than importing anything.
140
+
141
+ To lift the limit, give this package a Vite library build in the shape
142
+ floorplan already uses. Keep `build` (tsc) for the types. Add `build-browser`
143
+ (Vite) for the bundle. Put a `browser` condition in the exports map, above
144
+ `import`. That answers the worker specifier and runs the React Compiler, so
145
+ both problems above go with it.
@@ -0,0 +1,102 @@
1
+ import { type Box } from '@expofp/geometry';
2
+ import { type HTMLAttributes, type ReactNode, type Ref } from 'react';
3
+ import { type ControlsConfig } from './controls';
4
+ import { type ExternalCanvasProps } from './external/ExternalCanvas';
5
+ import { type LightingProps } from './objects/lighting';
6
+ import { type CameraProps } from './space/Camera';
7
+ import { type SceneDef } from './types/defs';
8
+ import { type PointerMissedEventData } from './types/events';
9
+ export { type ExternalCanvasProps } from './external/ExternalCanvas';
10
+ /**
11
+ * Props for {@link Renderer}. One surface for both mounts: the external trio rides along optional
12
+ * (the partial of {@link ExternalCanvasProps}, the way the camera limits ride along from
13
+ * {@link CameraProps}), and **`gl` arriving is what selects external mode** — exactly as it
14
+ * selected the imperative renderer's. With `gl` set, everything canvas- and camera-shaped is
15
+ * ignored: the host owns the canvas, the view and the frames, so the DOM attributes, `ref`,
16
+ * the camera limits, `controls` and `visibleRect` have nothing to configure.
17
+ */
18
+ export interface RendererProps extends HTMLAttributes<HTMLDivElement>, CameraProps, Partial<ExternalCanvasProps> {
19
+ /** Ref to the underlying WebGL canvas element. Internal mode — the external canvas is the host's. */
20
+ ref?: Ref<HTMLCanvasElement>;
21
+ /**
22
+ * When `true`, enables the debug UI overlay — in both modes; the GUI, the readout panels and the
23
+ * frame stats attach to the document beside whichever canvas is drawing. The one figure external
24
+ * mode cannot always show is GPU memory: it needs the context created *after* the debug chunk has
25
+ * instrumented context creation, which a host arranges by awaiting `prepareDebug()` before
26
+ * building its map. Without that the memory panel shows and stays empty — visibly wrong rather
27
+ * than silently absent.
28
+ */
29
+ debug?: boolean;
30
+ /** The scene to render. */
31
+ scene: SceneDef;
32
+ /**
33
+ * Components rendered inside the canvas, under the scene's space and camera. This is where a
34
+ * caller puts anything that has to reach the renderer from within — camera commands, event
35
+ * bridges — since those hooks only work below a mounted canvas.
36
+ */
37
+ children?: ReactNode;
38
+ /**
39
+ * Soft cap, in megabytes, on the GPU memory the image atlases may consume. When the atlases would
40
+ * exceed it, every image source is downscaled by one shared linear factor — preserving their
41
+ * relative sizes, with no priority — until they fit. Canvas sources are counted at full size but
42
+ * never scaled (their pixels are the caller's), so a limit smaller than the canvases alone need is
43
+ * simply not met. May be changed at runtime: the resizable atlases re-derive their textures live,
44
+ * with no repack. Omitted (or `0`) means no cap, and the budget stands down entirely.
45
+ */
46
+ memoryLimit?: number;
47
+ /**
48
+ * Called for a click that reached no layer handler — empty background, or a def in a layer that
49
+ * declares none, since such a layer is never picked. The event reports where the click landed on
50
+ * the plan, which a click on nothing otherwise could not tell you.
51
+ *
52
+ * Clicks that *do* reach a layer go to that layer's `onClick` instead, so a caller that wants every
53
+ * click regardless (say, to publish plan coordinates) wires the same function in both places.
54
+ */
55
+ onPointerMissed?: (event: PointerMissedEventData) => void;
56
+ /**
57
+ * What the user may do with the plan by mouse and touch — pan, zoom, roll, pitch.
58
+ *
59
+ * The other half of navigation from the limits this extends {@link CameraProps} for: a caller
60
+ * switches a gesture off without changing where the camera may go, and constrains the camera
61
+ * without taking a gesture away.
62
+ */
63
+ controls?: ControlsConfig;
64
+ /**
65
+ * The scene's lighting, and the darkening that rides with it: the rig's contrast and three
66
+ * colors, and a `shadow` object holding the shade both darkening terms blend toward and each
67
+ * term's own numbers. See {@link LightingProps}.
68
+ *
69
+ * Every value is optional and every omitted one keeps the design's own default, so a caller sets
70
+ * the one number a plan needs and nothing else. Where the key light points is not here: that is
71
+ * the design's decision, and the debug GUI keeps it.
72
+ *
73
+ * Changing a value moves a mounted scene with no remount and no shader recompile. It lands on a
74
+ * uniform or on a field the frame reads, and asks for a redraw.
75
+ */
76
+ lighting?: LightingProps;
77
+ /**
78
+ * The part of the canvas the plan should be framed inside, in css pixels from its top-left
79
+ * corner — for a host whose own header, sheet or panel covers the rest.
80
+ *
81
+ * **The plan is still drawn across the whole canvas**: nothing is clipped, and the strips behind
82
+ * that UI show more of the plan than they would otherwise rather than being cropped away. What
83
+ * changes is where the viewbox lands, which is guaranteed to be inside this rect.
84
+ *
85
+ * Distinct from `zoomTo`'s `within`, which is about a single command's framing and moves nothing.
86
+ * Ignored for a scene that carries a `staticTransform`, where the host places the plan itself.
87
+ */
88
+ visibleRect?: Box;
89
+ }
90
+ /**
91
+ * R3F floorplan renderer. Composes **one** scene tree — space, camera, layers, debug overlay, the
92
+ * caller's children — and mounts it on one of two canvases: its own (an R3F `<Canvas>`, wrapped in
93
+ * the debug loader) or a host's, when the host's `gl` arrives and {@link ExternalCanvas} adopts its
94
+ * canvas, camera and frame loop instead. The tree is the same in both; what external mode drops is
95
+ * exactly what the host owns — the managed camera, the controls, the `visibleRect` framing — and
96
+ * the tree simply mounts none of them there.
97
+ * @param props - Renderer props; in internal mode, keys other than the renderer's own pass through
98
+ * to the underlying canvas.
99
+ * @returns The rendered canvas and scene.
100
+ */
101
+ export declare function Renderer(props: RendererProps): ReactNode;
102
+ //# sourceMappingURL=Renderer.d.ts.map
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StrictMode, Suspense, useEffect, useRef, } from 'react';
3
+ import { Controls } from './controls';
4
+ import { CanvasWithDebug, DebugOverlay } from './debug';
5
+ import { PointerMissed } from './events/PointerMissed';
6
+ import { ExternalCanvas } from './external/ExternalCanvas';
7
+ import { Scene } from './space/Scene';
8
+ import { createLogger } from './utils/logging';
9
+ const logger = createLogger('renderer');
10
+ /**
11
+ * R3F floorplan renderer. Composes **one** scene tree — space, camera, layers, debug overlay, the
12
+ * caller's children — and mounts it on one of two canvases: its own (an R3F `<Canvas>`, wrapped in
13
+ * the debug loader) or a host's, when the host's `gl` arrives and {@link ExternalCanvas} adopts its
14
+ * canvas, camera and frame loop instead. The tree is the same in both; what external mode drops is
15
+ * exactly what the host owns — the managed camera, the controls, the `visibleRect` framing — and
16
+ * the tree simply mounts none of them there.
17
+ * @param props - Renderer props; in internal mode, keys other than the renderer's own pass through
18
+ * to the underlying canvas.
19
+ * @returns The rendered canvas and scene.
20
+ */
21
+ export function Renderer(props) {
22
+ // Every prop of the renderer's own is pulled out here rather than read off `props` below, so none
23
+ // of them ride along in `canvasProps` and land on the canvas's wrapper element as unknown DOM
24
+ // attributes — which is what React warns about, and the reason every member of `CameraProps` has
25
+ // to be named here as well as reassembled below. They are passed on untouched: what an omitted
26
+ // one means is `Camera`'s to say, and it is the only place that says it.
27
+ const { debug, scene, memoryLimit, minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime, controls, lighting, visibleRect, onPointerMissed, gl, requestFrame, onFrame, children, ...canvasProps } = props;
28
+ const cameraProps = { minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime };
29
+ const external = gl !== undefined;
30
+ // The context this mount created, released here rather than left to R3F. `<Canvas>`'s teardown
31
+ // (`unmountComponentAtNode`) defers `forceContextLoss()` behind a 500 ms `setTimeout`, inside a
32
+ // `try` that disconnects events first — so a host that unmounts the renderer holds a full
33
+ // viewport of GPU memory for half a second after it thought the surface was gone, and holds it
34
+ // forever if anything ahead of the loss in that block throws. An SDK `destroy()` that returns
35
+ // while the plan still owns a WebGL2 context is the same bug on a timer, and a plan swap pays it
36
+ // as a real transient: the incoming plan's context is created while the outgoing one still
37
+ // holds its own. R3F's deferred pass still runs afterwards and is unharmed — its remaining work
38
+ // (`renderLists.dispose()`, `dispose(scene)`) is no-ops on a lost context.
39
+ //
40
+ // Safe as a plain unmount effect only because `<StrictMode>` starts *below* here, wrapping the
41
+ // canvas rather than this component: nothing simulates a remount at this level, so the cleanup
42
+ // runs when the renderer is actually going away. External mode never fills the ref — the host
43
+ // owns that context, and `ExternalCanvas` neuters `forceContextLoss` for the same reason.
44
+ const rendererRef = useRef(undefined);
45
+ useEffect(() => {
46
+ return () => {
47
+ rendererRef.current?.forceContextLoss();
48
+ rendererRef.current = undefined;
49
+ };
50
+ }, []);
51
+ // The one tree, whichever canvas it lands on. The mount decides nothing about it; the tree
52
+ // leaves out what external mode has no object for — the controls here, the camera inside Scene.
53
+ const tree = (_jsx(DebugOverlay, { enabled: debug, children: _jsxs(Scene, { sceneDef: scene, external: external, memoryLimit: memoryLimit, lighting: lighting, visibleRect: visibleRect, ...cameraProps, children: [!external && _jsx(Controls, { config: controls }), _jsx(PointerMissed, { onPointerMissed: onPointerMissed }), children] }) }));
54
+ // The host's context arriving is what selects external mode. The mount swap is an element-type
55
+ // change, so flipping modes at runtime remounts — like `debug`, both are fixed for a renderer's
56
+ // lifetime in practice.
57
+ if (gl) {
58
+ if (!requestFrame || !onFrame) {
59
+ logger.error('external mode needs requestFrame and onFrame alongside gl; nothing will render');
60
+ return null;
61
+ }
62
+ // No StrictMode here: ExternalCanvas wraps the tree it renders into the foreign root itself,
63
+ // and its own bootstrap effect must run once per context, not be double-invoked into churn.
64
+ return (_jsx(ExternalCanvas, { gl: gl, requestFrame: requestFrame, onFrame: onFrame, children: tree }));
65
+ }
66
+ return (_jsx(StrictMode, { children: _jsx(Suspense, { fallback: null, children: _jsx(CanvasWithDebug, { ...canvasProps,
67
+ // No `dpr`: R3F's default caps the buffer at 2x, and that is a performance choice rather
68
+ // than a size one. Every pixel-authored size is derived from CSS units and the pixel size
69
+ // the renderer publishes (also CSS), so a 3x canvas rasterized by a caller still draws at
70
+ // the size it asked for while the buffer stays at 2x. A 3x buffer is 2.25 times the area
71
+ // of a 2x one, on exactly the devices that can least afford it.
72
+ debugEnabled: debug,
73
+ // R3F force-writes ACES filmic tone mapping onto a fresh renderer unless `flat` is set,
74
+ // which lands it on three's own default, `NoToneMapping`, instead. This renderer does not
75
+ // tone map: `Lighting` keeps the rig's light sum in range by construction, rather than
76
+ // compressing an unbounded one back into range afterward.
77
+ flat: true,
78
+ // three leaves the stencil buffer off by default. Line layers need one: it is how they keep
79
+ // translucent lines from darkening where they overlap (see `LineMaterial`), and without the
80
+ // buffer the stencil settings silently do nothing. In the MapLibre embed the context is the
81
+ // map's own, which already has one.
82
+ gl: { stencil: true },
83
+ // three reads a program's info log straight after linking it, and that read blocks until
84
+ // the driver has finished linking. Left to itself the driver links in the background,
85
+ // because `KHR_parallel_shader_compile` is there, so the check is the whole of what makes
86
+ // a compile synchronous: 17 programs cost 64 ms of main thread on a large scene, in
87
+ // blocks of up to 12 ms, and they land in the frames where the layers are still mounting.
88
+ // Debug keeps the check, because a broken shader has to report itself somewhere.
89
+ //
90
+ // The root state is also this mount's one handle on the renderer, which the unmount
91
+ // effect above needs to release its context.
92
+ onCreated: (state) => {
93
+ state.gl.debug.checkShaderErrors = debug ?? false;
94
+ rendererRef.current = state.gl;
95
+ }, frameloop: debug ? 'always' : 'demand', resize: { debounce: 50 }, children: tree }) }) }));
96
+ }
@@ -0,0 +1,108 @@
1
+ import { type NonFunctionKeys } from '@react-three/fiber/dist/declarations/src/core/utils';
2
+ import { MapControls as CameraControlsImpl, type SmoothTimeOption } from 'camera-controls';
3
+ import { type Ref } from 'react';
4
+ import { type OrthographicCamera, type PerspectiveCamera } from 'three';
5
+ /** Props for {@link CameraControls}: camera-controls element props plus lifecycle callbacks. */
6
+ export type CameraControlsProps = Partial<Omit<Pick<CameraControlsImpl, NonFunctionKeys<CameraControlsImpl>>, 'smoothTime' | 'controlSmoothTime'>> & {
7
+ /** Ref to hold the camera-controls instance */
8
+ ref?: Ref<CameraControlsImpl>;
9
+ /** Camera to control; defaults to R3F's default camera. */
10
+ camera?: PerspectiveCamera | OrthographicCamera;
11
+ /** Element input listeners attach to; defaults to the R3F event target. */
12
+ domElement?: HTMLElement;
13
+ /** When set, registers these as R3F's default controls (`state.controls`). */
14
+ makeDefault?: boolean;
15
+ /** Called when a user control interaction begins. */
16
+ onControlStart?: (e?: {
17
+ type: 'controlstart';
18
+ }) => void;
19
+ /** Called on each frame of a control interaction. */
20
+ onControl?: (e?: {
21
+ type: 'control';
22
+ }) => void;
23
+ /** Called when a user control interaction ends. */
24
+ onControlEnd?: (e?: {
25
+ type: 'controlend';
26
+ }) => void;
27
+ /** Called when a programmatic camera transition starts. */
28
+ onTransitionStart?: (e?: {
29
+ type: 'transitionstart';
30
+ }) => void;
31
+ /** Called whenever the camera updates. */
32
+ onUpdate?: (e?: {
33
+ type: 'update';
34
+ }) => void;
35
+ /** Called when the controls wake (begin needing frame updates). */
36
+ onWake?: (e?: {
37
+ type: 'wake';
38
+ }) => void;
39
+ /** Called when the camera comes to rest. */
40
+ onRest?: (e?: {
41
+ type: 'rest';
42
+ }) => void;
43
+ /** Called when the controls sleep (stop needing frame updates). */
44
+ onSleep?: (e?: {
45
+ type: 'sleep';
46
+ }) => void;
47
+ /** Whether to keep R3F pointer events enabled during control interaction. */
48
+ events?: boolean;
49
+ /** When set, drops render resolution (`performance.regress`) during interaction. */
50
+ regress?: boolean;
51
+ /** Approximate time in seconds to reach the target. A smaller value will reach the target faster. */
52
+ smoothTime?: SmoothTimeOption;
53
+ /** The smoothTime used while the user is actively controlling the camera */
54
+ controlSmoothTime?: SmoothTimeOption;
55
+ };
56
+ /**
57
+ * R3F wrapper around the `camera-controls` library. Builds a controls instance
58
+ * for the active camera, wires its events to R3F's invalidate/regress loop and
59
+ * the optional callbacks, optionally makes it the default controls, and
60
+ * publishes the live camera state (position, target, spherical, clipping planes)
61
+ * to the debug panel as it changes.
62
+ * @param props - Controls configuration and event callbacks.
63
+ * @returns The controls instance as an R3F `<primitive>`.
64
+ */
65
+ export declare function CameraControls(props: CameraControlsProps): import("react").JSX.Element;
66
+ /**
67
+ * The controls class, under the name the rest of this package calls it by.
68
+ *
69
+ * A **type** and not a value: the only code that needs the class itself is right here, and a type
70
+ * survives an import cycle where a class would not — so a handler this module's own parent loads
71
+ * can name it freely. Everything else the library offers arrives through
72
+ * {@link CameraControlsAction}.
73
+ */
74
+ export { type CameraControlsImpl };
75
+ /**
76
+ * The mouse and touch actions a handler claims, from the library's own enum.
77
+ *
78
+ * Beside the component rather than in a module of its own: a constant is the one thing Fast Refresh
79
+ * tolerates next to a component, and this one never changes.
80
+ */
81
+ export declare const CameraControlsAction: Readonly<{
82
+ readonly NONE: 0;
83
+ readonly ROTATE_AZIMUTH: 1;
84
+ readonly ROTATE_POLAR: 2;
85
+ readonly ROTATE: number;
86
+ readonly TRUCK: 4;
87
+ readonly SCREEN_PAN: 8;
88
+ readonly OFFSET: 16;
89
+ readonly DOLLY: 32;
90
+ readonly ZOOM: 64;
91
+ readonly TOUCH_ROTATE_AZIMUTH: 128;
92
+ readonly TOUCH_ROTATE_POLAR: 256;
93
+ readonly TOUCH_ROTATE: number;
94
+ readonly TOUCH_TRUCK: 512;
95
+ readonly TOUCH_SCREEN_PAN: 1024;
96
+ readonly TOUCH_OFFSET: 2048;
97
+ readonly TOUCH_DOLLY: 4096;
98
+ readonly TOUCH_ZOOM: 8192;
99
+ readonly TOUCH_DOLLY_TRUCK: number;
100
+ readonly TOUCH_DOLLY_SCREEN_PAN: number;
101
+ readonly TOUCH_DOLLY_OFFSET: number;
102
+ readonly TOUCH_DOLLY_ROTATE: number;
103
+ readonly TOUCH_ZOOM_TRUCK: number;
104
+ readonly TOUCH_ZOOM_OFFSET: number;
105
+ readonly TOUCH_ZOOM_SCREEN_PAN: number;
106
+ readonly TOUCH_ZOOM_ROTATE: number;
107
+ }>;
108
+ //# sourceMappingURL=CameraControls.d.ts.map
@@ -0,0 +1,203 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { radToDeg } from '@expofp/geometry';
3
+ import { extend, useFrame, useThree } from '@react-three/fiber';
4
+ import { MapControls as CameraControlsImpl } from 'camera-controls';
5
+ import { useEffect, useState } from 'react';
6
+ import { Box3, MathUtils, Matrix4, Quaternion, Raycaster, Sphere, Spherical, Vector2, Vector3, Vector4, } from 'three';
7
+ import { useDebugPanel } from '../debug/debug-context';
8
+ // Install the subset of THREE that camera-controls uses and register it with
9
+ // R3F, once at module load — before any controls are constructed during render,
10
+ // so the `new CameraControlsImpl(...)` below never runs uninstalled. Importing
11
+ // only the classes it needs keeps the bundle tree-shakeable.
12
+ // See https://github.com/yomotsu/camera-controls#important
13
+ const subsetOfThree = {
14
+ Box3,
15
+ MathUtils: { clamp: MathUtils.clamp },
16
+ Matrix4,
17
+ Quaternion,
18
+ Raycaster,
19
+ Sphere,
20
+ Spherical,
21
+ Vector2,
22
+ Vector3,
23
+ Vector4,
24
+ };
25
+ CameraControlsImpl.install({ THREE: subsetOfThree });
26
+ extend({ CameraControlsImpl });
27
+ /**
28
+ * R3F wrapper around the `camera-controls` library. Builds a controls instance
29
+ * for the active camera, wires its events to R3F's invalidate/regress loop and
30
+ * the optional callbacks, optionally makes it the default controls, and
31
+ * publishes the live camera state (position, target, spherical, clipping planes)
32
+ * to the debug panel as it changes.
33
+ * @param props - Controls configuration and event callbacks.
34
+ * @returns The controls instance as an R3F `<primitive>`.
35
+ */
36
+ export function CameraControls(props) {
37
+ const { ref, camera, domElement, makeDefault, onControlStart, onControl, onControlEnd, onTransitionStart, onUpdate, onWake, onRest, onSleep, regress, ...restProps } = props;
38
+ const defaultCamera = useThree((state) => state.camera);
39
+ const gl = useThree((state) => state.gl);
40
+ const invalidate = useThree((state) => state.invalidate);
41
+ const set = useThree((state) => state.set);
42
+ const get = useThree((state) => state.get);
43
+ const performance = useThree((state) => state.performance);
44
+ const setDebugPanel = useDebugPanel();
45
+ const explCamera = camera ?? defaultCamera;
46
+ // The canvas itself, not `state.events.connected`. R3F connects its pointer events to the canvas
47
+ // unless a caller names an `eventSource`, which this renderer never does — and `connected` is
48
+ // `false` until it has, so consulting it only adds a window where there is no element to attach
49
+ // to. A caller wanting a different one passes `domElement`.
50
+ const explDomElement = domElement ?? gl.domElement;
51
+ // Hold the instance in state, not a memo: React Fast Refresh treats every
52
+ // dependency list as changed when a module is edited, so `useMemo` (and the
53
+ // React Compiler's memo cache) would hand back a fresh instance on each save,
54
+ // snapping the camera back to its default target. State survives a refresh, so
55
+ // the instance is rebuilt only when the camera it drives actually changes.
56
+ const [controls, setControls] = useState(() => createControls(explCamera));
57
+ const [controlsCamera, setControlsCamera] = useState(explCamera);
58
+ if (controlsCamera !== explCamera) {
59
+ setControlsCamera(explCamera);
60
+ setControls((controls) => Object.assign(controls, { camera: explCamera }));
61
+ }
62
+ useFrame((_state, delta) => {
63
+ const updated = controls.update(Math.min(delta, MAX_FRAME_DELTA));
64
+ if (updated || gl.info.render.frame == 0) {
65
+ const position = explCamera.position.toArray().map((value) => +value.toFixed(2));
66
+ const target = controls
67
+ .getTarget(_target)
68
+ .toArray()
69
+ .map((value) => +value.toFixed(2));
70
+ const { theta, phi, radius } = controls.getSpherical(_spherical);
71
+ const spherical = [radToDeg(theta), radToDeg(phi), radius].map((value) => +value.toFixed(2));
72
+ const clippingPlanes = [explCamera.near, explCamera.far].map((value) => +value.toFixed(2));
73
+ setDebugPanel('camera', { position, target, spherical, clippingPlanes });
74
+ }
75
+ }, -1);
76
+ useEffect(() => {
77
+ controls.connect(explDomElement);
78
+ // Android WebViews send no pointercancel for a system gesture — the back-navigation swipe — so
79
+ // a control interaction the user has already left would go on animating. touchcancel is what
80
+ // they do send, and dropping the interaction on it is what the imperative renderer did.
81
+ const cancel = () => controls.cancel();
82
+ explDomElement.addEventListener('touchcancel', cancel);
83
+ return () => {
84
+ explDomElement.removeEventListener('touchcancel', cancel);
85
+ controls.disconnect();
86
+ };
87
+ }, [explDomElement, controls]);
88
+ useEffect(() => {
89
+ function invalidateAndRegress() {
90
+ invalidate();
91
+ if (regress)
92
+ performance.regress();
93
+ }
94
+ const handleControlStart = (e) => {
95
+ invalidateAndRegress();
96
+ onControlStart?.(e);
97
+ };
98
+ const handleControl = (e) => {
99
+ invalidateAndRegress();
100
+ onControl?.(e);
101
+ };
102
+ const handleControlEnd = (e) => {
103
+ onControlEnd?.(e);
104
+ };
105
+ const handleTransitionStart = (e) => {
106
+ invalidateAndRegress();
107
+ onTransitionStart?.(e);
108
+ };
109
+ const handleUpdate = (e) => {
110
+ invalidateAndRegress();
111
+ onUpdate?.(e);
112
+ };
113
+ const handleWake = (e) => {
114
+ invalidateAndRegress();
115
+ onWake?.(e);
116
+ };
117
+ const handleRest = (e) => {
118
+ onRest?.(e);
119
+ };
120
+ const handleSleep = (e) => {
121
+ onSleep?.(e);
122
+ };
123
+ controls.addEventListener('controlstart', handleControlStart);
124
+ controls.addEventListener('control', handleControl);
125
+ controls.addEventListener('controlend', handleControlEnd);
126
+ controls.addEventListener('transitionstart', handleTransitionStart);
127
+ controls.addEventListener('update', handleUpdate);
128
+ controls.addEventListener('wake', handleWake);
129
+ controls.addEventListener('rest', handleRest);
130
+ controls.addEventListener('sleep', handleSleep);
131
+ return () => {
132
+ controls.removeEventListener('controlstart', handleControlStart);
133
+ controls.removeEventListener('control', handleControl);
134
+ controls.removeEventListener('controlend', handleControlEnd);
135
+ controls.removeEventListener('transitionstart', handleTransitionStart);
136
+ controls.removeEventListener('update', handleUpdate);
137
+ controls.removeEventListener('wake', handleWake);
138
+ controls.removeEventListener('rest', handleRest);
139
+ controls.removeEventListener('sleep', handleSleep);
140
+ };
141
+ }, [
142
+ controls,
143
+ invalidate,
144
+ regress,
145
+ performance,
146
+ onControlStart,
147
+ onControl,
148
+ onControlEnd,
149
+ onTransitionStart,
150
+ onUpdate,
151
+ onWake,
152
+ onRest,
153
+ onSleep,
154
+ ]);
155
+ useEffect(() => {
156
+ if (makeDefault) {
157
+ const old = get().controls;
158
+ set({ controls: controls });
159
+ return () => set({ controls: old });
160
+ }
161
+ return;
162
+ }, [makeDefault, controls, set, get]);
163
+ return _jsx("primitive", { ref: ref, object: controls, ...restProps });
164
+ }
165
+ /**
166
+ * Builds a controls instance with every mouse and touch action switched off.
167
+ *
168
+ * **The handler components own the actions**, each setting its own on mount and clearing it on
169
+ * unmount, so the library's defaults — orbit on the left button, truck on the right, dolly on the
170
+ * middle and the wheel — would be a second author writing the same fields. Zeroed here at
171
+ * construction rather than through a prop or an effect, both of which run *after* a handler's:
172
+ * R3F re-applies a prop whenever its identity changes, and a parent's effect runs after its
173
+ * children's, so either would undo whatever had just been set.
174
+ *
175
+ * This is where the imperative renderer did it too, in its `CameraController` constructor.
176
+ * @param camera - The camera the controls drive.
177
+ * @returns The controls, inert until a handler claims an action.
178
+ */
179
+ function createControls(camera) {
180
+ const controls = new CameraControlsImpl(camera);
181
+ const { NONE } = CameraControlsAction;
182
+ controls.mouseButtons = { left: NONE, middle: NONE, right: NONE, wheel: NONE };
183
+ controls.touches = { one: NONE, two: NONE, three: NONE };
184
+ return controls;
185
+ }
186
+ /**
187
+ * The mouse and touch actions a handler claims, from the library's own enum.
188
+ *
189
+ * Beside the component rather than in a module of its own: a constant is the one thing Fast Refresh
190
+ * tolerates next to a component, and this one never changes.
191
+ */
192
+ export const CameraControlsAction = CameraControlsImpl.ACTION;
193
+ /**
194
+ * The longest step, in seconds, one frame may advance the controls' easing by.
195
+ *
196
+ * Under a demand frameloop the first frame after an idle spell carries the whole spell as its
197
+ * delta, and camera-controls does not clamp it: a move started by that frame would reach its
198
+ * target in one step. A ceiling rather than a fixed step, so a device drawing at 30 fps still
199
+ * eases in real time and only the idle gap is cut short.
200
+ */
201
+ const MAX_FRAME_DELTA = 1 / 30;
202
+ const _target = new Vector3();
203
+ const _spherical = new Spherical();