@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,288 @@
1
+ import { type Box, Point, type Point2Like, type PointLike } from '@expofp/geometry';
2
+ import { type Camera, Matrix4, Ray, Raycaster, Vector2, type Vector2Like, Vector3, type Vector3Like } from 'three';
3
+ /**
4
+ * A point in **plan space**: the plan's own coordinates, the ones every def is authored in. Reads x
5
+ * right, y down and z **up**, so an elevation is a positive number.
6
+ *
7
+ * Plan points are `@expofp/geometry` points because plan space crosses the public API — a pointer
8
+ * event reports where it landed as one, and floorplan authors its defs in the same type. So does
9
+ * {@link CanvasPoint}, for the same reason: those two are what a caller converts between.
10
+ */
11
+ export type PlanPoint = Point & {
12
+ __space: 'plan';
13
+ };
14
+ /**
15
+ * A plain object in plan space — what a caller may pass in.
16
+ *
17
+ * The brand is required rather than optional, so a bare `{ x, y }` is assignable to no space at all
18
+ * and crossing into one is an explicit `as`. An optional brand would make every plain object
19
+ * assignable to *every* space, which is the mistake these types exist to catch.
20
+ */
21
+ export type PlanPointLike = PointLike & {
22
+ __space: 'plan';
23
+ };
24
+ /**
25
+ * A point in **world space**: three's own space, the one the camera is placed in. World z grows *away*
26
+ * from the viewer under the viewbox fit, which is why {@link Space} mirrors it.
27
+ *
28
+ * World and NDC points are three's vectors instead of `@expofp/geometry`: neither is part of the
29
+ * public API, and both exist only to be multiplied by matrices and unprojected through cameras.
30
+ */
31
+ export type WorldPoint = Vector3 & {
32
+ __space: 'world';
33
+ };
34
+ /** A plain object point in world space. */
35
+ export type WorldPointLike = Vector3Like & {
36
+ __space: 'world';
37
+ };
38
+ /**
39
+ * A point in **canvas space**: css pixels from the canvas's top-left corner, x right and y **down**,
40
+ * the coordinates a DOM pointer event reports.
41
+ *
42
+ * Two dimensions and no more, unlike a plan point: what is drawn at a canvas position has a height,
43
+ * but the position itself does not — which is why {@link Space.ndcToCanvas} writes a zero `z` rather
44
+ * than carrying one through.
45
+ *
46
+ * Canvas and plan points are the pair most easily confused — both are 2D `{ x, y }` over comparable
47
+ * ranges, and both read as "where on screen" — so this is the brand that earns its keep most.
48
+ */
49
+ export type CanvasPoint = Point & {
50
+ __space: 'canvas';
51
+ };
52
+ /** A plain object point in canvas space — what a caller reading a DOM event may pass in. */
53
+ export type CanvasPointLike = Point2Like & {
54
+ __space: 'canvas';
55
+ };
56
+ /**
57
+ * A point in **normalized device coordinates**: where something sits on the canvas irrespective of
58
+ * its size, `[-1, 1]` on both axes with the origin at the centre and y growing upward.
59
+ */
60
+ export type NDCPoint = Vector2 & {
61
+ __space: 'ndc';
62
+ };
63
+ /** A plain object point in normalized device coordinates. */
64
+ export type NDCPointLike = Vector2Like & {
65
+ __space: 'ndc';
66
+ };
67
+ /** How a {@link Space} maps the plan onto the canvas. */
68
+ export interface SpaceOptions {
69
+ /** The scene's viewbox, in plan units — the region the fit frames. */
70
+ viewbox: Box;
71
+ /** The canvas width in css pixels. */
72
+ width: number;
73
+ /** The canvas height in css pixels. */
74
+ height: number;
75
+ /**
76
+ * The device pixel ratio the canvas draws at.
77
+ *
78
+ * Separate rather than pre-multiplied into css pixels because both are needed: the fit measures the buffer pixels,
79
+ * while canvas coordinates arrive from DOM events in css pixels. Passing the buffer size instead would just move the
80
+ * division to the other side.
81
+ */
82
+ dpr: number;
83
+ /**
84
+ * A caller-supplied plan → world matrix, 16 elements column-major. When present it *replaces* the
85
+ * viewbox fit rather than composing with it, and carries its own z convention — the maplibre
86
+ * transform scales z upward where the fit mirrors it.
87
+ */
88
+ staticTransform?: number[];
89
+ }
90
+ /**
91
+ * The scene's space.
92
+ * @returns The space {@link Scene} published, or the identity one when rendered outside a scene.
93
+ */
94
+ export declare function useSpace(): Space;
95
+ /**
96
+ * The mapping between the plan's own coordinates and the world the camera is placed in, and the
97
+ * conversions that ride on it.
98
+ *
99
+ * The mapping is one matrix. Without a `staticTransform` it is the **viewbox fit**: centre the
100
+ * viewbox on the world origin and scale it uniformly to best fill the drawing buffer, mirroring z so a positive
101
+ * elevation lands nearer the camera. That fit is what makes one world unit one device pixel when
102
+ * the plan exactly fills the canvas, which in turn is what lets the camera's zoom math be written
103
+ * without reference to the scene at all — see {@link Camera}.
104
+ *
105
+ * With a `staticTransform` the matrix is whatever the caller supplied, and the fit is not computed:
106
+ * a host like maplibre places the plan itself, in its own space and with its own z convention.
107
+ *
108
+ * **Stable for the scene's lifetime.** A resize does not build a new space — {@link Scene} refits
109
+ * this one in place — so every holder reads the current fit through the same object and none of
110
+ * them re-render for it. What makes that safe is a reading discipline: render output must never be
111
+ * derived from these fields, because no render follows a refit. Read them at the moment of use —
112
+ * in event handlers, effects and the frame loop, which always run against the current fit. `Scene`
113
+ * itself is the one exception, and it re-applies the world matrix from the same effect that refits.
114
+ */
115
+ export declare class Space {
116
+ /** Plan → world. */
117
+ readonly matrix: Matrix4;
118
+ /** World → plan. */
119
+ readonly matrixInverse: Matrix4;
120
+ private _scale;
121
+ private _isExternal;
122
+ /** The canvas width in css pixels, which the canvas conversions measure against. */
123
+ private width;
124
+ /** The canvas height in css pixels. */
125
+ private height;
126
+ /**
127
+ * @param options - How to map the plan onto the canvas. Omit them entirely for the identity
128
+ * space, in which world coordinates already read as plan coordinates; the individual fields are
129
+ * not separately optional, because a fit needs all of them.
130
+ */
131
+ constructor(options?: SpaceOptions);
132
+ /**
133
+ * World units per plan unit: the geometric mean of the two transformed axes, rather than the
134
+ * length of one of them.
135
+ *
136
+ * Both paths that build a matrix produce a similarity — the fit scales x and y alike, and the
137
+ * maplibre solver forces uniform scale and no shear — so the two axes agree and the mean is
138
+ * simply their common length. It differs only for a transform this renderer does not produce, and
139
+ * there no single number is right anyway: an anisotropic plan covers different amounts of plan per
140
+ * pixel along each axis, and the mean is the one that preserves area.
141
+ */
142
+ get scale(): number;
143
+ /**
144
+ * Whether an external host placed the plan itself, by supplying a valid `staticTransform`,
145
+ * rather than this renderer fitting the viewbox to the canvas — external mode, where the host
146
+ * owns the canvas, the camera and the frames.
147
+ */
148
+ get isExternal(): boolean;
149
+ /**
150
+ * Refits the plan onto the canvas in place, leaving every held reference reading the new fit.
151
+ * @param options - How to map the plan onto the canvas.
152
+ */
153
+ refit(options: SpaceOptions): void;
154
+ /**
155
+ * Converts a point from plan to world coordinates.
156
+ * @param point - The plan-space point; a missing `z` is read as `0`.
157
+ * @param out - Where to write the result.
158
+ * @returns The world-space point.
159
+ */
160
+ planToWorld(point: PlanPointLike, out?: WorldPoint): WorldPoint;
161
+ /**
162
+ * Converts a point from world to plan coordinates.
163
+ * @param point - The world-space point.
164
+ * @param out - Where to write the result.
165
+ * @returns The plan-space point.
166
+ */
167
+ worldToPlan(point: WorldPointLike, out?: PlanPoint): PlanPoint;
168
+ /**
169
+ * Converts a point on the canvas — css pixels from its top-left corner — to normalized device
170
+ * coordinates, clamped to the canvas.
171
+ * @param point - The canvas-space point.
172
+ * @param out - Where to write the result.
173
+ * @returns The point in normalized device coordinates.
174
+ */
175
+ canvasToNdc(point: CanvasPointLike, out?: NDCPoint): NDCPoint;
176
+ /**
177
+ * Where a point in normalized device coordinates lands on the canvas, in css pixels from its
178
+ * top-left corner.
179
+ *
180
+ * **Unclamped, where {@link canvasToNdc} clamps**, and the asymmetry is the point: clamping on the
181
+ * way in keeps a pointer event on the canvas it was dispatched from, while clamping on the way out
182
+ * would erase the one thing a caller asking about an off-screen point wants to know.
183
+ *
184
+ * No guard on a canvas with no size either, where `canvasToNdc` needs one — this multiplies by the
185
+ * size where that divides by it, so an unsized canvas gives an honest zero rather than a `NaN`.
186
+ * @param ndc - The point in normalized device coordinates.
187
+ * @param out - Where to write the result.
188
+ * @returns The canvas-space point.
189
+ */
190
+ ndcToCanvas(ndc: NDCPointLike, out?: CanvasPoint): CanvasPoint;
191
+ /**
192
+ * Where a ray crosses the plan, in plan units.
193
+ *
194
+ * The ray is taken into plan space and crossed with plan `z = 0` there, rather than crossed with
195
+ * the plan's image in world space and converted back. Both agree for the viewbox fit; only this
196
+ * one stays correct for a `staticTransform` that rotates or lifts the plan off world `z = 0`.
197
+ * @param ray - The ray, in world space.
198
+ * @param out - Where to write the result.
199
+ * @returns The crossing, or `undefined` when the ray runs parallel to the plan.
200
+ */
201
+ rayToPlan(ray: Ray, out?: PlanPoint): PlanPoint | undefined;
202
+ /**
203
+ * Where a point in normalized device coordinates lands on the plan.
204
+ * @param ndc - The point in normalized device coordinates.
205
+ * @param camera - The camera the canvas is drawn through.
206
+ * @param out - Where to write the result.
207
+ * @returns The plan position, or `undefined` when the camera never meets the plan.
208
+ */
209
+ ndcToPlan(ndc: NDCPointLike, camera: Camera, out?: PlanPoint): PlanPoint | undefined;
210
+ /**
211
+ * Where a point on the plan lands in normalized device coordinates — the inverse of
212
+ * {@link ndcToPlan}, and the projection every question about "where is this on screen" is built
213
+ * from.
214
+ *
215
+ * Carried through **clip space** rather than through three's `Vector3.project`, which divides by
216
+ * `w` without looking at its sign: a point behind the camera comes back mirrored through the
217
+ * origin, and so lands inside the canvas as convincingly as a point in front of it. Reading `w`
218
+ * first is what separates the two.
219
+ *
220
+ * The camera's view offset rides in `projectionMatrix`, so a scene framed inside a `visibleRect`
221
+ * projects through this with no seam — the same way picking and culling follow it.
222
+ * @param point - The plan-space point; a missing `z` is read as `0`.
223
+ * @param camera - The camera the canvas is drawn through.
224
+ * @param out - Where to write the result.
225
+ * @returns The point in normalized device coordinates, unclamped, or `undefined` when it sits
226
+ * behind the camera and has no image on the canvas at all.
227
+ */
228
+ planToNdc(point: PlanPointLike, camera: Camera, out?: NDCPoint): NDCPoint | undefined;
229
+ /**
230
+ * Where a point on the canvas — css pixels from its top-left corner — lands on the plan.
231
+ * @param point - The canvas-space point.
232
+ * @param camera - The camera the canvas is drawn through.
233
+ * @param out - Where to write the result.
234
+ * @returns The plan position, or `undefined` when the camera never meets the plan.
235
+ */
236
+ canvasToPlan(point: CanvasPointLike, camera: Camera, out?: PlanPoint): PlanPoint | undefined;
237
+ /**
238
+ * Where a point on the plan lands on the canvas, in css pixels from its top-left corner — the
239
+ * inverse of {@link canvasToPlan}, composed from the same two halves the other way round.
240
+ * @param point - The plan-space point; a missing `z` is read as `0`.
241
+ * @param camera - The camera the canvas is drawn through.
242
+ * @param out - Where to write the result.
243
+ * @returns The canvas position, unclamped, or `undefined` when the point sits behind the camera.
244
+ */
245
+ planToCanvas(point: PlanPointLike, camera: Camera, out?: CanvasPoint): CanvasPoint | undefined;
246
+ /**
247
+ * Whether a caller supplied a usable plan → world matrix, warning when they supplied an unusable
248
+ * one rather than silently falling back.
249
+ * @param staticTransform - What the caller supplied, if anything.
250
+ * @returns Whether it should be used as the matrix.
251
+ */
252
+ private validateStaticTransform;
253
+ }
254
+ /**
255
+ * The scale that fits a viewbox inside a drawing buffer, contain-style: the constraining axis fills
256
+ * and the other one keeps slack.
257
+ *
258
+ * Exported because it is also what tells a caller how large the plan is drawn: `VisibleRect` needs
259
+ * the plan's footprint in device pixels, and deriving it from this rather than from `Space.scale`
260
+ * keeps it free of the commit-ordering hazard the class documents — the fit is a function of the
261
+ * props it already holds.
262
+ * @param viewbox - The region to frame, in plan units.
263
+ * @param bufferWidth - The drawing buffer's width in device pixels.
264
+ * @param bufferHeight - The drawing buffer's height in device pixels.
265
+ * @returns World units per plan unit, or `1` while either is degenerate.
266
+ */
267
+ export declare function fitScale(viewbox: Box, bufferWidth: number, bufferHeight: number): number;
268
+ /**
269
+ * The scene's space, published by {@link Scene}. The default is the identity space, so a component
270
+ * rendered without one still converts — it just converts nothing.
271
+ */
272
+ export declare const SpaceContext: import("react").Context<Space>;
273
+ /**
274
+ * Points a raycaster from a camera through a point in normalized device coordinates.
275
+ *
276
+ * The fallback is for a camera whose projection was handed to it rather than derived from a frustum
277
+ * — an external host like maplibre supplies one — which three's own `setFromCamera` refuses. It unprojects the
278
+ * same screen point on the near and far planes; both lie on the true ray whatever the matrix folds,
279
+ * where anchoring the origin at the eye would need a camera transform such a camera does not carry.
280
+ *
281
+ * Exported for the external event system, which must aim R3F's raycaster the same way; the space's own
282
+ * conversions go through the module's private raycaster.
283
+ * @param raycaster - The raycaster to aim.
284
+ * @param ndc - The point in normalized device coordinates.
285
+ * @param camera - The camera to cast from.
286
+ */
287
+ export declare function castRay(raycaster: Raycaster, ndc: NDCPointLike, camera: Camera): void;
288
+ //# sourceMappingURL=Space.d.ts.map
@@ -0,0 +1,329 @@
1
+ import { Point } from '@expofp/geometry';
2
+ import { createContext, use } from 'react';
3
+ import { Matrix4, Plane, Ray, Raycaster, Vector2, Vector3, Vector4, } from 'three';
4
+ import { createLogger } from '../utils/logging';
5
+ import { clamp } from '../utils/math';
6
+ import { projectsFromFrustum } from './camera-kind';
7
+ const logger = createLogger('space');
8
+ /**
9
+ * The scene's space.
10
+ * @returns The space {@link Scene} published, or the identity one when rendered outside a scene.
11
+ */
12
+ export function useSpace() {
13
+ return use(SpaceContext);
14
+ }
15
+ /**
16
+ * The mapping between the plan's own coordinates and the world the camera is placed in, and the
17
+ * conversions that ride on it.
18
+ *
19
+ * The mapping is one matrix. Without a `staticTransform` it is the **viewbox fit**: centre the
20
+ * viewbox on the world origin and scale it uniformly to best fill the drawing buffer, mirroring z so a positive
21
+ * elevation lands nearer the camera. That fit is what makes one world unit one device pixel when
22
+ * the plan exactly fills the canvas, which in turn is what lets the camera's zoom math be written
23
+ * without reference to the scene at all — see {@link Camera}.
24
+ *
25
+ * With a `staticTransform` the matrix is whatever the caller supplied, and the fit is not computed:
26
+ * a host like maplibre places the plan itself, in its own space and with its own z convention.
27
+ *
28
+ * **Stable for the scene's lifetime.** A resize does not build a new space — {@link Scene} refits
29
+ * this one in place — so every holder reads the current fit through the same object and none of
30
+ * them re-render for it. What makes that safe is a reading discipline: render output must never be
31
+ * derived from these fields, because no render follows a refit. Read them at the moment of use —
32
+ * in event handlers, effects and the frame loop, which always run against the current fit. `Scene`
33
+ * itself is the one exception, and it re-applies the world matrix from the same effect that refits.
34
+ */
35
+ export class Space {
36
+ /** Plan → world. */
37
+ matrix = new Matrix4();
38
+ /** World → plan. */
39
+ matrixInverse = new Matrix4();
40
+ _scale = 1;
41
+ _isExternal = false;
42
+ /** The canvas width in css pixels, which the canvas conversions measure against. */
43
+ width = 0;
44
+ /** The canvas height in css pixels. */
45
+ height = 0;
46
+ /**
47
+ * @param options - How to map the plan onto the canvas. Omit them entirely for the identity
48
+ * space, in which world coordinates already read as plan coordinates; the individual fields are
49
+ * not separately optional, because a fit needs all of them.
50
+ */
51
+ constructor(options) {
52
+ if (options)
53
+ this.refit(options);
54
+ }
55
+ /**
56
+ * World units per plan unit: the geometric mean of the two transformed axes, rather than the
57
+ * length of one of them.
58
+ *
59
+ * Both paths that build a matrix produce a similarity — the fit scales x and y alike, and the
60
+ * maplibre solver forces uniform scale and no shear — so the two axes agree and the mean is
61
+ * simply their common length. It differs only for a transform this renderer does not produce, and
62
+ * there no single number is right anyway: an anisotropic plan covers different amounts of plan per
63
+ * pixel along each axis, and the mean is the one that preserves area.
64
+ */
65
+ get scale() {
66
+ return this._scale;
67
+ }
68
+ /**
69
+ * Whether an external host placed the plan itself, by supplying a valid `staticTransform`,
70
+ * rather than this renderer fitting the viewbox to the canvas — external mode, where the host
71
+ * owns the canvas, the camera and the frames.
72
+ */
73
+ get isExternal() {
74
+ return this._isExternal;
75
+ }
76
+ /**
77
+ * Refits the plan onto the canvas in place, leaving every held reference reading the new fit.
78
+ * @param options - How to map the plan onto the canvas.
79
+ */
80
+ refit(options) {
81
+ this.width = options.width;
82
+ this.height = options.height;
83
+ this._isExternal = this.validateStaticTransform(options.staticTransform);
84
+ if (this._isExternal) {
85
+ this.matrix.fromArray(options.staticTransform);
86
+ }
87
+ else {
88
+ const fit = fitScale(options.viewbox, this.width * options.dpr, this.height * options.dpr);
89
+ const { x: centerX, y: centerY } = options.viewbox.center;
90
+ // three composes a matrix as T·R·S, so the plan-space translation has to be pre-scaled for the
91
+ // product to come out as scale∘translate — the order the imperative renderer built it in.
92
+ this.matrix
93
+ .makeTranslation(-centerX * fit, -centerY * fit, 0)
94
+ .scale(new Vector3(fit, fit, -fit));
95
+ }
96
+ this.matrixInverse.copy(this.matrix).invert();
97
+ this._scale = Math.sqrt(axisLength(this.matrix, 0) * axisLength(this.matrix, 1));
98
+ }
99
+ /**
100
+ * Converts a point from plan to world coordinates.
101
+ * @param point - The plan-space point; a missing `z` is read as `0`.
102
+ * @param out - Where to write the result.
103
+ * @returns The world-space point.
104
+ */
105
+ planToWorld(point, out = new Vector3()) {
106
+ return out.set(point.x, point.y, point.z ?? 0).applyMatrix4(this.matrix);
107
+ }
108
+ /**
109
+ * Converts a point from world to plan coordinates.
110
+ * @param point - The world-space point.
111
+ * @param out - Where to write the result.
112
+ * @returns The plan-space point.
113
+ */
114
+ worldToPlan(point, out = new Point()) {
115
+ _world.set(point.x, point.y, point.z).applyMatrix4(this.matrixInverse);
116
+ out.set(_world.x, _world.y, _world.z);
117
+ return out;
118
+ }
119
+ /**
120
+ * Converts a point on the canvas — css pixels from its top-left corner — to normalized device
121
+ * coordinates, clamped to the canvas.
122
+ * @param point - The canvas-space point.
123
+ * @param out - Where to write the result.
124
+ * @returns The point in normalized device coordinates.
125
+ */
126
+ canvasToNdc(point, out = new Vector2()) {
127
+ if (this.width <= 0 || this.height <= 0) {
128
+ logger.warn('canvasToNdc: the canvas has no size');
129
+ return out.set(0, 0);
130
+ }
131
+ const x = (point.x / this.width) * 2 - 1;
132
+ // NDC y grows upward where canvas y grows downward.
133
+ const y = -(point.y / this.height) * 2 + 1;
134
+ return out.set(clamp(x, -1, 1), clamp(y, -1, 1));
135
+ }
136
+ /**
137
+ * Where a point in normalized device coordinates lands on the canvas, in css pixels from its
138
+ * top-left corner.
139
+ *
140
+ * **Unclamped, where {@link canvasToNdc} clamps**, and the asymmetry is the point: clamping on the
141
+ * way in keeps a pointer event on the canvas it was dispatched from, while clamping on the way out
142
+ * would erase the one thing a caller asking about an off-screen point wants to know.
143
+ *
144
+ * No guard on a canvas with no size either, where `canvasToNdc` needs one — this multiplies by the
145
+ * size where that divides by it, so an unsized canvas gives an honest zero rather than a `NaN`.
146
+ * @param ndc - The point in normalized device coordinates.
147
+ * @param out - Where to write the result.
148
+ * @returns The canvas-space point.
149
+ */
150
+ ndcToCanvas(ndc, out = new Point()) {
151
+ // Canvas y grows downward where NDC y grows upward. Zero elevation, always: a point on the
152
+ // canvas has none, and an `out` that carried one from an earlier use must not keep it.
153
+ return out.set(((ndc.x + 1) / 2) * this.width, ((1 - ndc.y) / 2) * this.height, 0);
154
+ }
155
+ /**
156
+ * Where a ray crosses the plan, in plan units.
157
+ *
158
+ * The ray is taken into plan space and crossed with plan `z = 0` there, rather than crossed with
159
+ * the plan's image in world space and converted back. Both agree for the viewbox fit; only this
160
+ * one stays correct for a `staticTransform` that rotates or lifts the plan off world `z = 0`.
161
+ * @param ray - The ray, in world space.
162
+ * @param out - Where to write the result.
163
+ * @returns The crossing, or `undefined` when the ray runs parallel to the plan.
164
+ */
165
+ rayToPlan(ray, out = new Point()) {
166
+ _ray.copy(ray).applyMatrix4(this.matrixInverse);
167
+ if (!_ray.intersectPlane(PLAN_PLANE, _world))
168
+ return undefined;
169
+ return out.set(_world.x, _world.y, 0);
170
+ }
171
+ /**
172
+ * Where a point in normalized device coordinates lands on the plan.
173
+ * @param ndc - The point in normalized device coordinates.
174
+ * @param camera - The camera the canvas is drawn through.
175
+ * @param out - Where to write the result.
176
+ * @returns The plan position, or `undefined` when the camera never meets the plan.
177
+ */
178
+ ndcToPlan(ndc, camera, out) {
179
+ castRay(_raycaster, ndc, camera);
180
+ return this.rayToPlan(_raycaster.ray, out);
181
+ }
182
+ /**
183
+ * Where a point on the plan lands in normalized device coordinates — the inverse of
184
+ * {@link ndcToPlan}, and the projection every question about "where is this on screen" is built
185
+ * from.
186
+ *
187
+ * Carried through **clip space** rather than through three's `Vector3.project`, which divides by
188
+ * `w` without looking at its sign: a point behind the camera comes back mirrored through the
189
+ * origin, and so lands inside the canvas as convincingly as a point in front of it. Reading `w`
190
+ * first is what separates the two.
191
+ *
192
+ * The camera's view offset rides in `projectionMatrix`, so a scene framed inside a `visibleRect`
193
+ * projects through this with no seam — the same way picking and culling follow it.
194
+ * @param point - The plan-space point; a missing `z` is read as `0`.
195
+ * @param camera - The camera the canvas is drawn through.
196
+ * @param out - Where to write the result.
197
+ * @returns The point in normalized device coordinates, unclamped, or `undefined` when it sits
198
+ * behind the camera and has no image on the canvas at all.
199
+ */
200
+ planToNdc(point, camera, out = new Vector2()) {
201
+ this.planToWorld(point, _projected);
202
+ _clip
203
+ .set(_projected.x, _projected.y, _projected.z, 1)
204
+ .applyMatrix4(camera.matrixWorldInverse)
205
+ .applyMatrix4(camera.projectionMatrix);
206
+ if (_clip.w <= 0)
207
+ return undefined;
208
+ return out.set(_clip.x / _clip.w, _clip.y / _clip.w);
209
+ }
210
+ /**
211
+ * Where a point on the canvas — css pixels from its top-left corner — lands on the plan.
212
+ * @param point - The canvas-space point.
213
+ * @param camera - The camera the canvas is drawn through.
214
+ * @param out - Where to write the result.
215
+ * @returns The plan position, or `undefined` when the camera never meets the plan.
216
+ */
217
+ canvasToPlan(point, camera, out) {
218
+ return this.ndcToPlan(this.canvasToNdc(point, _ndc), camera, out);
219
+ }
220
+ /**
221
+ * Where a point on the plan lands on the canvas, in css pixels from its top-left corner — the
222
+ * inverse of {@link canvasToPlan}, composed from the same two halves the other way round.
223
+ * @param point - The plan-space point; a missing `z` is read as `0`.
224
+ * @param camera - The camera the canvas is drawn through.
225
+ * @param out - Where to write the result.
226
+ * @returns The canvas position, unclamped, or `undefined` when the point sits behind the camera.
227
+ */
228
+ planToCanvas(point, camera, out) {
229
+ const ndc = this.planToNdc(point, camera, _screen);
230
+ if (!ndc)
231
+ return undefined;
232
+ return this.ndcToCanvas(ndc, out);
233
+ }
234
+ /**
235
+ * Whether a caller supplied a usable plan → world matrix, warning when they supplied an unusable
236
+ * one rather than silently falling back.
237
+ * @param staticTransform - What the caller supplied, if anything.
238
+ * @returns Whether it should be used as the matrix.
239
+ */
240
+ validateStaticTransform(staticTransform) {
241
+ if (staticTransform === undefined)
242
+ return false;
243
+ if (staticTransform.length !== 16) {
244
+ logger.warn(`staticTransform must be 16 elements long, got ${staticTransform.length}`);
245
+ return false;
246
+ }
247
+ return true;
248
+ }
249
+ }
250
+ /**
251
+ * The scale that fits a viewbox inside a drawing buffer, contain-style: the constraining axis fills
252
+ * and the other one keeps slack.
253
+ *
254
+ * Exported because it is also what tells a caller how large the plan is drawn: `VisibleRect` needs
255
+ * the plan's footprint in device pixels, and deriving it from this rather than from `Space.scale`
256
+ * keeps it free of the commit-ordering hazard the class documents — the fit is a function of the
257
+ * props it already holds.
258
+ * @param viewbox - The region to frame, in plan units.
259
+ * @param bufferWidth - The drawing buffer's width in device pixels.
260
+ * @param bufferHeight - The drawing buffer's height in device pixels.
261
+ * @returns World units per plan unit, or `1` while either is degenerate.
262
+ */
263
+ export function fitScale(viewbox, bufferWidth, bufferHeight) {
264
+ const { width, height } = viewbox.size;
265
+ if (width <= 0 || height <= 0 || bufferWidth <= 0 || bufferHeight <= 0)
266
+ return 1;
267
+ return Math.min(bufferWidth / width, bufferHeight / height);
268
+ }
269
+ /**
270
+ * The scene's space, published by {@link Scene}. The default is the identity space, so a component
271
+ * rendered without one still converts — it just converts nothing.
272
+ */
273
+ export const SpaceContext = createContext(new Space());
274
+ /**
275
+ * Points a raycaster from a camera through a point in normalized device coordinates.
276
+ *
277
+ * The fallback is for a camera whose projection was handed to it rather than derived from a frustum
278
+ * — an external host like maplibre supplies one — which three's own `setFromCamera` refuses. It unprojects the
279
+ * same screen point on the near and far planes; both lie on the true ray whatever the matrix folds,
280
+ * where anchoring the origin at the eye would need a camera transform such a camera does not carry.
281
+ *
282
+ * Exported for the external event system, which must aim R3F's raycaster the same way; the space's own
283
+ * conversions go through the module's private raycaster.
284
+ * @param raycaster - The raycaster to aim.
285
+ * @param ndc - The point in normalized device coordinates.
286
+ * @param camera - The camera to cast from.
287
+ */
288
+ export function castRay(raycaster, ndc, camera) {
289
+ if (projectsFromFrustum(camera)) {
290
+ raycaster.setFromCamera(_cast.set(ndc.x, ndc.y), camera);
291
+ return;
292
+ }
293
+ raycaster.ray.origin.set(ndc.x, ndc.y, -1).unproject(camera);
294
+ raycaster.ray.direction
295
+ .set(ndc.x, ndc.y, 1)
296
+ .unproject(camera)
297
+ .sub(raycaster.ray.origin)
298
+ .normalize();
299
+ }
300
+ /**
301
+ * The length one unit along an axis is drawn at.
302
+ * @param matrix - The matrix to measure.
303
+ * @param axis - Which axis: `0` for x, `1` for y.
304
+ * @returns The transformed axis's length.
305
+ */
306
+ function axisLength(matrix, axis) {
307
+ return new Vector3().setFromMatrixColumn(matrix, axis).length();
308
+ }
309
+ /** The plan's own plane in plan space, which every pointer position is reported on. */
310
+ const PLAN_PLANE = new Plane(new Vector3(0, 0, 1), 0);
311
+ /**
312
+ * One raycaster for the module, not one per space and deliberately not R3F's.
313
+ *
314
+ * `Space` is React-free, so it cannot reach `state.raycaster`; and it should not, because R3F sets
315
+ * that one up for the pointer event it is dispatching — a coordinate query that overwrote its ray
316
+ * mid-dispatch would corrupt the hit test in flight.
317
+ */
318
+ const _raycaster = new Raycaster();
319
+ const _ray = new Ray();
320
+ const _ndc = new Vector2();
321
+ /** The screen point {@link castRay} hands three's `setFromCamera`. */
322
+ const _cast = new Vector2();
323
+ const _world = new Vector3();
324
+ /** The world point {@link Space.planToNdc} projects, kept apart from `_world` so the two never alias. */
325
+ const _projected = new Vector3();
326
+ /** Where that projection lands before the perspective divide, which is where `w` can still be read. */
327
+ const _clip = new Vector4();
328
+ /** The projected point {@link Space.planToCanvas} hands on to {@link Space.ndcToCanvas}. */
329
+ const _screen = new Vector2();