@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,65 @@
1
+ import { type Camera } from 'three';
2
+ import { type CameraControlsImpl } from '../controls/CameraControls';
3
+ import { type Space } from './Space';
4
+ /**
5
+ * How much of the plan one device pixel spans, in plan units — what everything sized in pixels is
6
+ * authored against, and what the imperative renderer published as `ptScale`.
7
+ *
8
+ * **One number for a view that has many.** Perspective foreshortens a pitched plan, so no single
9
+ * factor is true across the whole screen. The one the plan is laid out against is the value where
10
+ * the camera is aimed ({@link aimedAt}), which is why the reading does not move with pitch — only
11
+ * with dolly.
12
+ *
13
+ * **Derived where the controls can say, measured where they cannot.** They hold the distance to
14
+ * that point, and the projection says how much world a screen spans at it: the imperative
15
+ * renderer's own arithmetic, for free. Without them — external mode, a bare scene — the screen row
16
+ * through the aim is taken onto the plan and divided by the buffer's width, which asks nothing of
17
+ * the camera but its matrices. That row is an iso-depth line wherever the screen's x axis lies
18
+ * parallel to the plan, true for this renderer's camera and a map host's alike, so the average
19
+ * along it is exact rather than approximate.
20
+ *
21
+ * Neither path knows a `visibleRect` exists: its framing rides in `projectionMatrix`, which one
22
+ * reads `m00` out of and the other projects through.
23
+ *
24
+ * **Device pixels, not css pixels** — a plan at dpr 2 reports half the pixel size of the same plan
25
+ * at dpr 1, as the imperative renderer did measuring `canvas.height`.
26
+ * @param camera - The camera in use.
27
+ * @param space - The scene's space.
28
+ * @param bufferWidth - The drawing buffer's width in device pixels.
29
+ * @param controls - The camera controls, when mounted; they own the distance to the target.
30
+ * @returns Plan units per device pixel, or `undefined` while the camera misses the plan or the
31
+ * buffer has no width.
32
+ */
33
+ export declare function measurePixelSize(camera: Camera, space: Space, bufferWidth: number, controls?: CameraControlsImpl): number | undefined;
34
+ /**
35
+ * Which way the plan is turned, in radians clockwise from north — {@link measurePixelSize}'s
36
+ * counterpart on the roll axis, split the same way.
37
+ *
38
+ * The controls hold an azimuth, so where they exist that is the whole answer. Without them it is
39
+ * the screen direction of the plan's own x axis at {@link aimedAt}, read off the matrix that takes
40
+ * the plan to clip space: the plan's x axis is that matrix's first column, and a screen direction is
41
+ * what its clip image becomes once the perspective divide is differentiated through. Under an
42
+ * external host this composes the host's bearing with the static transform's own rotation, which is
43
+ * the venue-relative angle the imperative path fed its roll handlers by hand.
44
+ *
45
+ * A direction rather than the angle between two projected points is what removes the probe the
46
+ * imperative renderer used, and with it the question of how far apart to place it.
47
+ * @param camera - The camera in use.
48
+ * @param space - The scene's space.
49
+ * @param bufferWidth - The drawing buffer's width in device pixels.
50
+ * @param bufferHeight - Its height, which weights the two NDC axes back into pixels.
51
+ * @param controls - The camera controls, when mounted; they hold the angle outright.
52
+ * @returns The roll in `[0, 2π)`, or `undefined` while the camera misses the plan.
53
+ */
54
+ export declare function measureRoll(camera: Camera, space: Space, bufferWidth: number, bufferHeight: number, controls?: CameraControlsImpl): number | undefined;
55
+ /**
56
+ * Whether a new pixel size differs enough from the last one to re-lay out on.
57
+ *
58
+ * Absolute rather than relative, and the same `1e-4` the imperative renderer compared with, so the
59
+ * handlers ported onto this API keep firing at the zoom steps they were tuned against.
60
+ * @param pixelSize - The new value.
61
+ * @param previous - The value last laid out at.
62
+ * @returns Whether the change is worth acting on.
63
+ */
64
+ export declare function pixelSizeMoved(pixelSize: number, previous: number): boolean;
65
+ //# sourceMappingURL=layout-measure.d.ts.map
@@ -0,0 +1,173 @@
1
+ import { Point } from '@expofp/geometry';
2
+ import { Matrix4, Ray, Vector2, Vector4 } from 'three';
3
+ import { bearing } from '../controls/CameraControls';
4
+ import { euclideanModulo } from '../utils/math';
5
+ import { isOrthographic, isPerspective } from './camera-kind';
6
+ /**
7
+ * How much of the plan one device pixel spans, in plan units — what everything sized in pixels is
8
+ * authored against, and what the imperative renderer published as `ptScale`.
9
+ *
10
+ * **One number for a view that has many.** Perspective foreshortens a pitched plan, so no single
11
+ * factor is true across the whole screen. The one the plan is laid out against is the value where
12
+ * the camera is aimed ({@link aimedAt}), which is why the reading does not move with pitch — only
13
+ * with dolly.
14
+ *
15
+ * **Derived where the controls can say, measured where they cannot.** They hold the distance to
16
+ * that point, and the projection says how much world a screen spans at it: the imperative
17
+ * renderer's own arithmetic, for free. Without them — external mode, a bare scene — the screen row
18
+ * through the aim is taken onto the plan and divided by the buffer's width, which asks nothing of
19
+ * the camera but its matrices. That row is an iso-depth line wherever the screen's x axis lies
20
+ * parallel to the plan, true for this renderer's camera and a map host's alike, so the average
21
+ * along it is exact rather than approximate.
22
+ *
23
+ * Neither path knows a `visibleRect` exists: its framing rides in `projectionMatrix`, which one
24
+ * reads `m00` out of and the other projects through.
25
+ *
26
+ * **Device pixels, not css pixels** — a plan at dpr 2 reports half the pixel size of the same plan
27
+ * at dpr 1, as the imperative renderer did measuring `canvas.height`.
28
+ * @param camera - The camera in use.
29
+ * @param space - The scene's space.
30
+ * @param bufferWidth - The drawing buffer's width in device pixels.
31
+ * @param controls - The camera controls, when mounted; they own the distance to the target.
32
+ * @returns Plan units per device pixel, or `undefined` while the camera misses the plan or the
33
+ * buffer has no width.
34
+ */
35
+ export function measurePixelSize(camera, space, bufferWidth, controls) {
36
+ if (!(bufferWidth > 0) || !(space.scale > 0))
37
+ return undefined;
38
+ if (controls) {
39
+ const worldWidth = visibleWorldWidth(camera, controls.distance);
40
+ if (!(worldWidth > 0))
41
+ return undefined;
42
+ // The camera measures the world, and only the fit says how much plan that world is.
43
+ return worldWidth / bufferWidth / space.scale;
44
+ }
45
+ const aim = aimedAt(camera, space);
46
+ if (!aim)
47
+ return undefined;
48
+ // Only the row's height is wanted, so `_ndc` is free to be re-aimed along it below.
49
+ const rowY = space.planToNdc(aim, camera, _ndc)?.y;
50
+ if (rowY === undefined)
51
+ return undefined;
52
+ const left = space.ndcToPlan(_ndc.set(-1, rowY), camera, _left);
53
+ const right = space.ndcToPlan(_ndc.set(1, rowY), camera, _right);
54
+ if (!left || !right)
55
+ return undefined;
56
+ const planLength = Math.hypot(right.x - left.x, right.y - left.y);
57
+ if (!(planLength > 0))
58
+ return undefined;
59
+ return planLength / bufferWidth;
60
+ }
61
+ /**
62
+ * Which way the plan is turned, in radians clockwise from north — {@link measurePixelSize}'s
63
+ * counterpart on the roll axis, split the same way.
64
+ *
65
+ * The controls hold an azimuth, so where they exist that is the whole answer. Without them it is
66
+ * the screen direction of the plan's own x axis at {@link aimedAt}, read off the matrix that takes
67
+ * the plan to clip space: the plan's x axis is that matrix's first column, and a screen direction is
68
+ * what its clip image becomes once the perspective divide is differentiated through. Under an
69
+ * external host this composes the host's bearing with the static transform's own rotation, which is
70
+ * the venue-relative angle the imperative path fed its roll handlers by hand.
71
+ *
72
+ * A direction rather than the angle between two projected points is what removes the probe the
73
+ * imperative renderer used, and with it the question of how far apart to place it.
74
+ * @param camera - The camera in use.
75
+ * @param space - The scene's space.
76
+ * @param bufferWidth - The drawing buffer's width in device pixels.
77
+ * @param bufferHeight - Its height, which weights the two NDC axes back into pixels.
78
+ * @param controls - The camera controls, when mounted; they hold the angle outright.
79
+ * @returns The roll in `[0, 2π)`, or `undefined` while the camera misses the plan.
80
+ */
81
+ export function measureRoll(camera, space, bufferWidth, bufferHeight, controls) {
82
+ if (controls)
83
+ return bearing(controls);
84
+ const aim = aimedAt(camera, space);
85
+ if (!aim)
86
+ return undefined;
87
+ _planToClip
88
+ .multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
89
+ .multiply(space.matrix);
90
+ // Column-major, so elements 0 to 3 are the first column: the plan's x axis as a clip direction.
91
+ const column = _planToClip.elements;
92
+ const clip = _clip.set(aim.x, aim.y, 0, 1).applyMatrix4(_planToClip);
93
+ // d(ndc)/dx by the quotient rule, less its `1/w²` — positive, so it moves no direction. NDC is
94
+ // aspect-distorted, so each axis is weighted back into pixels; canvas y grows downward where NDC
95
+ // y grows up.
96
+ const dx = (column[0] * clip.w - clip.x * column[3]) * bufferWidth;
97
+ const dy = -(column[1] * clip.w - clip.y * column[3]) * bufferHeight;
98
+ if (dx === 0 && dy === 0)
99
+ return undefined;
100
+ // The plan turned clockwise by θ shows its east axis at screen angle -θ.
101
+ return euclideanModulo(-Math.atan2(dy, dx), 2 * Math.PI);
102
+ }
103
+ /**
104
+ * Whether a new pixel size differs enough from the last one to re-lay out on.
105
+ *
106
+ * Absolute rather than relative, and the same `1e-4` the imperative renderer compared with, so the
107
+ * handlers ported onto this API keep firing at the zoom steps they were tuned against.
108
+ * @param pixelSize - The new value.
109
+ * @param previous - The value last laid out at.
110
+ * @returns Whether the change is worth acting on.
111
+ */
112
+ export function pixelSizeMoved(pixelSize, previous) {
113
+ return Math.abs(pixelSize - previous) >= PIXEL_SIZE_EPSILON;
114
+ }
115
+ /**
116
+ * Where the camera is aimed on the plan: the point its own axis crosses, which is what the controls
117
+ * would call their target and what the derived path's distance is measured to.
118
+ *
119
+ * Read off the camera's world matrix rather than asked of anything that might not be there —
120
+ * external mode's camera carries a rigid one recovered from the host's folded matrix, so its eye
121
+ * and direction are as true as a camera three placed itself.
122
+ * @param camera - The camera in use.
123
+ * @param space - The scene's space.
124
+ * @returns The plan point, or `undefined` when the camera looks along the plan and never meets it.
125
+ */
126
+ function aimedAt(camera, space) {
127
+ _axis.origin.setFromMatrixPosition(camera.matrixWorld);
128
+ camera.getWorldDirection(_axis.direction);
129
+ return space.rayToPlan(_axis, _aim);
130
+ }
131
+ /**
132
+ * The world-space width one screen covers at a distance.
133
+ *
134
+ * Read off the projection matrix rather than rebuilt from `fov` and `zoom`: `m00` is `2·near /
135
+ * (right − left)` under perspective and `2 / (right − left)` under orthographic, so it holds for a
136
+ * projection nobody derived from a symmetric frustum — the off-centre, expanded one a `visibleRect`
137
+ * installs, which leaves `fov` untouched.
138
+ *
139
+ * Width where the imperative renderer took height, so both of {@link measurePixelSize}'s paths
140
+ * divide by the one buffer dimension they are given; for a projection whose aspect is the buffer's
141
+ * the two agree exactly.
142
+ * @param camera - The camera in use.
143
+ * @param distance - Distance from the camera to the point it is aimed at, in world units.
144
+ * @returns The width in world units, or `NaN` for a camera that projects neither way.
145
+ */
146
+ function visibleWorldWidth(camera, distance) {
147
+ // Column-major: element 0 is row 0 of column 0.
148
+ const m00 = camera.projectionMatrix.elements[0];
149
+ if (!(m00 > 0))
150
+ return NaN;
151
+ // A perspective frustum widens with distance; an orthographic one covers the same width wherever
152
+ // it sits, which is the whole difference between the two.
153
+ if (isPerspective(camera))
154
+ return (2 * distance) / m00;
155
+ if (isOrthographic(camera))
156
+ return 2 / m00;
157
+ return NaN;
158
+ }
159
+ /**
160
+ * The change in {@link measurePixelSize} below which nothing is re-laid out. Not configurable: it
161
+ * is the granularity every handler's thresholds are expressed against, so a scene tuning it would
162
+ * be tuning every handler at once.
163
+ */
164
+ const PIXEL_SIZE_EPSILON = 1e-4;
165
+ // Scratch, each filled and consumed within one call — and the two readings never overlap, so the
166
+ // aim they share is one point rather than two.
167
+ const _axis = new Ray();
168
+ const _aim = new Point();
169
+ const _left = new Point();
170
+ const _right = new Point();
171
+ const _ndc = new Vector2();
172
+ const _planToClip = new Matrix4();
173
+ const _clip = new Vector4();
@@ -0,0 +1,112 @@
1
+ import { type Camera as ThreeCamera } from 'three';
2
+ import { type CanvasPoint, type CanvasPointLike, type PlanPoint, type PlanPointLike, type Space } from './Space';
3
+ /**
4
+ * Where things are: the two directions between the plan's own coordinates and the canvas the user
5
+ * points at, and the one question that rides on them.
6
+ *
7
+ * The counterpart to `CameraAPI`, and the division is the obvious one — that moves the camera, this
8
+ * reads where the camera has left things. Nothing here changes anything, so nothing here returns a
9
+ * promise: every method answers for the plan as it stands at the moment of asking.
10
+ *
11
+ * Both conversions can fail, and both failures are real rather than defensive: a tilted camera looks
12
+ * past the plan entirely above the horizon, and a plan point can sit behind the camera. `undefined`
13
+ * says so in each direction, rather than a number that would read as a position.
14
+ *
15
+ * This is the surface that replaces the imperative renderer's `viewport`, whose `canvasToSvg`
16
+ * covered only the first of these — and returned one point per loaded scene, leaving every caller
17
+ * to pick the default one out of an array.
18
+ */
19
+ export interface ViewportAPI {
20
+ /**
21
+ * Where a point on the canvas lands on the plan.
22
+ *
23
+ * **The plan plane, not the geometry drawn on it.** A click on the roof of a raised model reports
24
+ * the ground point the ray carries on to, which is the floor coordinate this answers for; what the
25
+ * ray actually struck is a pointer event's business, and `PointerEventData.point` reports that.
26
+ * @param point - The canvas position, in css pixels from the canvas's top-left corner.
27
+ * @param out - A point to write into, for a caller converting enough of them to care. Omit it and
28
+ * each call allocates, which is what makes the result safe to keep.
29
+ * @returns The plan position, or `undefined` when the camera never meets the plan there.
30
+ */
31
+ canvasToPlan(point: CanvasPointLike, out?: PlanPoint): PlanPoint | undefined;
32
+ /**
33
+ * Where a point on the plan lands on the canvas.
34
+ *
35
+ * **Unclamped:** a point off the side of the plan reports the position it really has, outside the
36
+ * canvas, which is what makes it usable for placing something that may scroll into view.
37
+ * @param point - The plan position; a missing `z` is read as `0`, so an elevated point projects
38
+ * from the height it is drawn at.
39
+ * @param out - A point to write into, for a caller converting enough of them to care. Omit it and
40
+ * each call allocates, which is what makes the result safe to keep.
41
+ * @returns The canvas position in css pixels, or `undefined` when the point is behind the camera.
42
+ */
43
+ planToCanvas(point: PlanPointLike, out?: CanvasPoint): CanvasPoint | undefined;
44
+ /**
45
+ * Whether a point on the plan is on screen.
46
+ *
47
+ * **The whole canvas counts**, including any part of it a host's own UI covers: a `visibleRect`
48
+ * expands the frustum rather than cropping it, so the plan really is drawn edge to edge and a
49
+ * point behind a bottom sheet is drawn as much as one beside it. A caller asking the narrower
50
+ * question — can the *user* see it — tests {@link planToCanvas} against its own region.
51
+ * @param point - The plan position; a missing `z` is read as `0`.
52
+ * @returns Whether it is drawn on the canvas.
53
+ */
54
+ isVisible(point: PlanPointLike): boolean;
55
+ }
56
+ /**
57
+ * What {@link Viewport} needs from the scene it reads.
58
+ *
59
+ * Handed over whole rather than reached for, as `CameraContext` is, and the store getter is here for
60
+ * the same reason: R3F may swap the camera out from under a held reference, and every conversion
61
+ * below has to run through the one the canvas is currently drawn with.
62
+ */
63
+ interface ViewportContext {
64
+ /** Reads the canvas state, which is where the camera in use is found. */
65
+ get: () => {
66
+ camera: ThreeCamera;
67
+ };
68
+ /** The scene's space, which owns the mapping every conversion here composes. */
69
+ space: Space;
70
+ }
71
+ /**
72
+ * The scene's coordinate conversions.
73
+ *
74
+ * A hook for the same reason `useCameraAPI` is one: it resolves only inside a component mounted
75
+ * under `<Renderer>`, so "the plan cannot be measured before the scene exists" is enforced by where
76
+ * the code can live rather than by a null check every caller has to remember. A host that needs to
77
+ * convert coordinates from outside React publishes this object from an effect.
78
+ *
79
+ * **Nothing here subscribes to the canvas size**, and it does not need to: `Space` is refitted in
80
+ * place on every resize, so the object below reads the current fit through the same reference for as
81
+ * long as the scene lives, and the camera is read at the moment of asking. That is the reading
82
+ * discipline `Space` documents — and the reason it is safe here is that no render output is derived
83
+ * from these answers.
84
+ *
85
+ * **The returned object is new on every render, deliberately**, as `useCameraAPI`'s is: this module
86
+ * memoizes nothing, so a consumer that cannot re-subscribe cheaply holds it in a ref and pays that
87
+ * cost where the cost is understood.
88
+ * @returns The conversions.
89
+ */
90
+ export declare function useViewport(): ViewportAPI;
91
+ /**
92
+ * The coordinate conversions, over whatever the scene currently holds.
93
+ *
94
+ * A class rather than an object literal because it is the renderer's published surface, and built
95
+ * fresh rather than mutated so that nothing here is state — every answer is composed from the space
96
+ * and the camera as they stand when the method is called.
97
+ */
98
+ export declare class Viewport implements ViewportAPI {
99
+ private readonly context;
100
+ /**
101
+ * @param context - What the scene holds; the instance is rebuilt whenever it moves.
102
+ */
103
+ constructor(context: ViewportContext);
104
+ /** @inheritdoc */
105
+ canvasToPlan(point: CanvasPointLike, out?: PlanPoint): PlanPoint | undefined;
106
+ /** @inheritdoc */
107
+ planToCanvas(point: PlanPointLike, out?: CanvasPoint): CanvasPoint | undefined;
108
+ /** @inheritdoc */
109
+ isVisible(point: PlanPointLike): boolean;
110
+ }
111
+ export {};
112
+ //# sourceMappingURL=viewport.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { useThree } from '@react-three/fiber';
2
+ import { Vector2 } from 'three';
3
+ import { useSpace, } from './Space';
4
+ /**
5
+ * The scene's coordinate conversions.
6
+ *
7
+ * A hook for the same reason `useCameraAPI` is one: it resolves only inside a component mounted
8
+ * under `<Renderer>`, so "the plan cannot be measured before the scene exists" is enforced by where
9
+ * the code can live rather than by a null check every caller has to remember. A host that needs to
10
+ * convert coordinates from outside React publishes this object from an effect.
11
+ *
12
+ * **Nothing here subscribes to the canvas size**, and it does not need to: `Space` is refitted in
13
+ * place on every resize, so the object below reads the current fit through the same reference for as
14
+ * long as the scene lives, and the camera is read at the moment of asking. That is the reading
15
+ * discipline `Space` documents — and the reason it is safe here is that no render output is derived
16
+ * from these answers.
17
+ *
18
+ * **The returned object is new on every render, deliberately**, as `useCameraAPI`'s is: this module
19
+ * memoizes nothing, so a consumer that cannot re-subscribe cheaply holds it in a ref and pays that
20
+ * cost where the cost is understood.
21
+ * @returns The conversions.
22
+ */
23
+ export function useViewport() {
24
+ const get = useThree((state) => state.get);
25
+ const space = useSpace();
26
+ return new Viewport({ get, space });
27
+ }
28
+ /**
29
+ * The coordinate conversions, over whatever the scene currently holds.
30
+ *
31
+ * A class rather than an object literal because it is the renderer's published surface, and built
32
+ * fresh rather than mutated so that nothing here is state — every answer is composed from the space
33
+ * and the camera as they stand when the method is called.
34
+ */
35
+ export class Viewport {
36
+ context;
37
+ /**
38
+ * @param context - What the scene holds; the instance is rebuilt whenever it moves.
39
+ */
40
+ constructor(context) {
41
+ this.context = context;
42
+ }
43
+ /** @inheritdoc */
44
+ canvasToPlan(point, out) {
45
+ const { space, get } = this.context;
46
+ return space.canvasToPlan(point, get().camera, out);
47
+ }
48
+ /** @inheritdoc */
49
+ planToCanvas(point, out) {
50
+ const { space, get } = this.context;
51
+ // Never a shared scratch when the caller named none: this is public, and a caller that kept the
52
+ // point it was handed must never find it moved under them. An `out` they passed is theirs, and
53
+ // the aliasing is then a choice they made rather than one made for them.
54
+ return space.planToCanvas(point, get().camera, out);
55
+ }
56
+ /** @inheritdoc */
57
+ isVisible(point) {
58
+ const { space, get } = this.context;
59
+ // Tested in normalized device coordinates rather than in canvas pixels, which is the same test
60
+ // — `ndcToCanvas` maps `[-1, 1]` onto the canvas and nothing else — arrived at without the
61
+ // canvas size, and so with nothing to disagree with when a resize is halfway through.
62
+ const ndc = space.planToNdc(point, get().camera, _ndc);
63
+ return !!ndc && Math.abs(ndc.x) <= 1 && Math.abs(ndc.y) <= 1;
64
+ }
65
+ }
66
+ /** Where {@link Viewport.isVisible} projects to; it never escapes the method. */
67
+ const _ndc = new Vector2();
@@ -0,0 +1,169 @@
1
+ import { type Box, type LineLike, type Rect, type Shape } from '@expofp/geometry';
2
+ import { type LayoutHandlers, type PointerHandlers } from './events';
3
+ /** A color as a css string or a packed numeric value. */
4
+ export type ColorInput = string | number;
5
+ /** Any image-like source that can be uploaded as a texture. */
6
+ export type ImageSource = HTMLImageElement | HTMLCanvasElement | ImageBitmap | OffscreenCanvas;
7
+ /** Scene definition */
8
+ export interface SceneDef {
9
+ /** Root layer of the scene tree */
10
+ rootLayer: LayerDef;
11
+ /** Background color in css format */
12
+ background?: ColorInput;
13
+ /** SVG viewbox */
14
+ viewbox: Box;
15
+ /** Static transform matrix */
16
+ staticTransform?: number[];
17
+ }
18
+ /** Shared properties for all renderable defs */
19
+ export interface SharedDef extends LayoutHandlers {
20
+ /** Whether the def is hidden */
21
+ hidden?: boolean;
22
+ /**
23
+ * How strongly the def is dimmed — desaturated toward gray and darkened — from `0` (undimmed) to
24
+ * `1` (fully dimmed), interpolating in between.
25
+ *
26
+ * `undefined` inherits from the nearest ancestor that sets one, and only `undefined` inherits: a
27
+ * def's own `0` un-dims it inside a dimmed layer, and its own `1` dims it inside an undimmed one.
28
+ * A layer and a single def carry the same field with the same meaning, so dimming a whole floor
29
+ * and un-dimming one booth on it are the same operation at two levels. The root defaults to `0`.
30
+ *
31
+ * Values outside `[0, 1]` are clamped. Being a number rather than a flag is what lets a dim be
32
+ * animated — easing a layer's value from `0` to `1` costs one uniform per layer per frame,
33
+ * whatever the layer holds.
34
+ */
35
+ dim?: number;
36
+ }
37
+ /** Any def that can be rendered into a scene. */
38
+ export type RenderableDef = LayerDef | ShapeDef | ImageDef | TextDef | LineDef | ModelDef;
39
+ /**
40
+ * An array of a single {@link RenderableDef} kind (never a mixed union). The conditional
41
+ * distributes over {@link RenderableDef}, so even the default is a union of single-kind arrays —
42
+ * a mixed array is unrepresentable, which is what lets a first-element guard speak for the whole
43
+ * collection. `Readonly<RenderableDefCollection<T>>` distributes the same way, into readonly
44
+ * single-kind arrays.
45
+ */
46
+ export type RenderableDefCollection<T = RenderableDef> = T extends RenderableDef ? T[] : never;
47
+ /**
48
+ * Layer definition.
49
+ *
50
+ * A layer is also where interaction lives: the handlers it inherits from {@link PointerHandlers} are
51
+ * what make its subtree pickable, and each event names the def it hit. Layout is the other way round
52
+ * — a layer carries its own {@link LayoutHandlers} through {@link SharedDef}, like any other def, and
53
+ * that is how a layer whose contents depend on the camera re-derives them.
54
+ */
55
+ export interface LayerDef<T extends RenderableDef = RenderableDef> extends SharedDef, PointerHandlers<T> {
56
+ /** Layer's name */
57
+ readonly name: string;
58
+ /** Collection of layer's children. Mixing different types of children is not supported. */
59
+ children: RenderableDefCollection<T>;
60
+ }
61
+ /** Shape definition. */
62
+ export interface ShapeDef extends SharedDef {
63
+ /**
64
+ * Shape to render. To change it, **replace** this with a new `Shape` and call `update(def)`: the
65
+ * new object re-claims the instance against fresh geometry, so even the shape *type* may change
66
+ * (rect ↔ polygon). Mutating the existing shape in place is more limited — a rect's or box's
67
+ * transform (center, size, rotation) is re-read every update, but a polygon's vertices and indices
68
+ * are cached by shape identity and will not rebuild in place.
69
+ */
70
+ shape: Shape;
71
+ /** Shape's color in css format */
72
+ color: ColorInput;
73
+ }
74
+ /** Image definition */
75
+ export interface ImageDef extends SharedDef {
76
+ /**
77
+ * Image source. Write-once — a def's pixels are fixed for its lifetime: the atlas packs and
78
+ * uploads them at claim time and never re-reads the source, so a new image is a new def, not a
79
+ * reassignment. `readonly` makes that the type's guarantee (see {@link SceneDef.memoryLimit},
80
+ * which relies on a pack's byte totals staying stable until a repack).
81
+ */
82
+ readonly source: ImageSource;
83
+ /** Image's bounds */
84
+ bounds: Rect;
85
+ /**
86
+ * Image's anchor point. Defines the point on the image that is used as an origin point for transformations.
87
+ * [0, 0] is the top-left corner, [0.5, 0.5] is the center (default), [1, 1] is the bottom-right corner.
88
+ */
89
+ origin?: [number, number];
90
+ }
91
+ /** Text definition */
92
+ export interface TextDef extends SharedDef {
93
+ /**
94
+ * Array of text lines, drawn as a block stacked inside {@link bounds}.
95
+ *
96
+ * Updatable in both directions: a line's own fields are re-read on every `update(def)`, and
97
+ * changing the array's *length* re-claims the def's batch members against the new count. (The
98
+ * imperative renderer allocated members once and so required this to be fixed; React reconciles
99
+ * it instead.) A line whose `fontSize` is `0` is not drawn.
100
+ */
101
+ lines: TextLineDef[];
102
+ /** Text's bounds */
103
+ bounds: Rect;
104
+ /** Text's padding [horizontal, vertical] */
105
+ padding: [number, number];
106
+ /** {@link TextAlignment} object describing text's alignment */
107
+ alignment: TextAlignment;
108
+ }
109
+ /** Single text line definition */
110
+ export interface TextLineDef {
111
+ /** Line's text */
112
+ text: string;
113
+ /** Line's color in css format */
114
+ color: ColorInput;
115
+ /** Line's font URL. Not updatable. Supported formats: .ttf, .otf, .woff (.woff2 is not supported). */
116
+ fontUrl: string;
117
+ /**
118
+ * Line's font size: its em height in plan units, the same units as the def's `bounds` and
119
+ * `padding`.
120
+ *
121
+ * Nothing here converts from pixels. A plan sizing a line to hold an on-screen size does that
122
+ * itself, against the pixel size the renderer publishes — as it does for every other
123
+ * pixel-authored thing it draws. `0` means the line is not drawn.
124
+ */
125
+ fontSize: number;
126
+ /** Line's stroke color & width, the width in the same units as {@link fontSize}. Optional. */
127
+ stroke?: {
128
+ color: ColorInput;
129
+ width: number;
130
+ };
131
+ }
132
+ /** Text alignment */
133
+ export interface TextAlignment {
134
+ /** Horizontal alignment */
135
+ horizontal: 'left' | 'center' | 'right';
136
+ /** Vertical alignment */
137
+ vertical: 'top' | 'center' | 'bottom';
138
+ /** Text alignment within the line. Optional. */
139
+ text?: 'left' | 'center' | 'right';
140
+ }
141
+ /** Line definition */
142
+ export interface LineDef extends SharedDef {
143
+ /**
144
+ * The segment's endpoints, with an optional `elevation` placing it on its layer's plane.
145
+ *
146
+ * Updatable: both ends are re-read on every `update(def)` and written only when they moved,
147
+ * so a line can be animated in place rather than replaced. A `LineLike` exposes readonly coordinates,
148
+ * so an update means replacing this with a new one — or moving the `Line` behind it,
149
+ * whose own setters do the same job.
150
+ */
151
+ points: LineLike;
152
+ /** Line's color in css format */
153
+ color: ColorInput;
154
+ /**
155
+ * Line's width in screen-space units (pixels), so it holds its thickness as the plan zooms.
156
+ * `0` draws nothing.
157
+ */
158
+ width: number;
159
+ }
160
+ /** External 3D model definition */
161
+ export interface ModelDef extends SharedDef {
162
+ /** Model's URL. Not updatable. */
163
+ url: string;
164
+ /** Model's bounds */
165
+ bounds: Rect;
166
+ /** Model's opacity. Optional. */
167
+ opacity?: number;
168
+ }
169
+ //# sourceMappingURL=defs.d.ts.map
@@ -0,0 +1 @@
1
+ export {};