@expofp/renderer 0.0.0-experimental.cf063fc

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
@@ -0,0 +1,69 @@
1
+ import { Frustum, Matrix4, Sphere, Vector3 } from 'three';
2
+ import { isImageDef, isTextDef } from '../types/guards';
3
+ /**
4
+ * Encloses a def's bounds in a sphere the frustum can test, rotation-invariant by construction.
5
+ *
6
+ * Ported from the imperative renderer's `computeBoundingSphere` (fp-engine
7
+ * `renderer/src/util/geometry.ts`), which the legacy `UpdatesSystem` culled its update queue with.
8
+ * The sphere is centered at `bounds.center` — the rotation pivot — with a radius reaching the
9
+ * furthest corner of the rendered content, so no rotation can swing the content outside it. An
10
+ * `origin` (an image's anchor) shifts which part of the content hangs off the pivot, which widens
11
+ * the radius but never moves the center.
12
+ * @param bounds - The def's bounds.
13
+ * @param origin - The image anchor in `[0..1, 0..1]`, when the def has one; centered otherwise.
14
+ * @param out - Receives the result.
15
+ * @returns The sphere, in plan space.
16
+ */
17
+ export function boundingSphereOf(bounds, origin, out) {
18
+ const originX = origin?.[0] ?? 0.5;
19
+ const originY = origin?.[1] ?? 0.5;
20
+ const reachX = Math.max(originX, 1 - originX) * bounds.size.x;
21
+ const reachY = Math.max(originY, 1 - originY) * bounds.size.y;
22
+ _center.set(bounds.center.x, bounds.center.y, bounds.elevation);
23
+ out.set(_center, Math.hypot(reachX, reachY));
24
+ return out;
25
+ }
26
+ /**
27
+ * Whether a def is entirely off screen, for the drain that lays visible defs out first.
28
+ *
29
+ * Only text and image defs are tested — the def kinds with one cheap `bounds` enclosure, and the
30
+ * bulk of any plan's re-layout targets (labels, dots, icons, markers). Everything else answers
31
+ * "not culled" and takes the priority pass, exactly as the legacy `UpdatesSystem` treated it.
32
+ * A `true` is a **priority** signal, never a correctness one: a culled def must still be laid out,
33
+ * just after the ones the camera can see.
34
+ * @param def - The def to test.
35
+ * @param frustum - The camera's frustum, in plan space — see {@link planFrustum}.
36
+ * @returns Whether the def can wait.
37
+ */
38
+ export function culledBy(def, frustum) {
39
+ if (isTextDef(def)) {
40
+ return !frustum.intersectsSphere(boundingSphereOf(def.bounds, undefined, _sphere));
41
+ }
42
+ if (isImageDef(def)) {
43
+ return !frustum.intersectsSphere(boundingSphereOf(def.bounds, def.origin, _sphere));
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * The camera's frustum brought into plan space, where def bounds live.
49
+ *
50
+ * The same composition the imperative renderer's `computeFrustumInModelSpace` used, with the
51
+ * scene's plan → world matrix standing in for the scene's `matrixWorld`: projecting through
52
+ * `projection · viewInverse · plan` makes the frustum's planes answer for plan coordinates
53
+ * directly, so a def is tested where it is authored and nothing per-def is transformed.
54
+ * @param camera - The camera in use.
55
+ * @param planMatrix - The plan → world matrix, from the scene's `Space`.
56
+ * @param out - A frustum to write into. Omit it and each call allocates, which is what makes the
57
+ * result safe to keep.
58
+ * @returns The frustum.
59
+ */
60
+ export function planFrustum(camera, planMatrix, out = new Frustum()) {
61
+ _frustumMatrix
62
+ .multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
63
+ .multiply(planMatrix);
64
+ return out.setFromProjectionMatrix(_frustumMatrix, camera.coordinateSystem);
65
+ }
66
+ // Scratch, each filled and consumed within one synchronous call.
67
+ const _center = new Vector3();
68
+ const _sphere = new Sphere();
69
+ const _frustumMatrix = new Matrix4();
@@ -0,0 +1,151 @@
1
+ import { type Frustum } from 'three';
2
+ import { type LayerDef, type RenderableDef } from '../types/defs';
3
+ import { type PlacementContext } from './placement';
4
+ /**
5
+ * The defs a renderer re-lays out when the camera moves: the pixel size and the roll are the two
6
+ * layout axes.
7
+ *
8
+ * Every def here declares its own `onScale` or `onRoll` — the registry never holds a handler, only
9
+ * the value each def's rule last ran at, per axis. One registry per renderer, reached through
10
+ * {@link LayoutTargetsContext}, so two renderers showing the same defs at different zooms do not lay
11
+ * each other's out.
12
+ *
13
+ * A move splits in two. **The rules all run at once**, in the frame that noticed it: they are the
14
+ * cheap half — arithmetic over a def's own fields — and running them together is what lets every
15
+ * def's *decision*, a dot's "show me now" included, exist the moment the camera moves. **The writes
16
+ * are a sweep**: what the rules reported changed is queued and {@link drain} carries it to the GPU a
17
+ * frame's budget at a time — defs whose visibility flipped first, then the rest the camera can see,
18
+ * then the culled. The queue's shape is the legacy renderer's `UpdatesSystem` (fp-engine
19
+ * `core/updates.ts`); its unit is a def whose fields moved.
20
+ */
21
+ export interface LayoutTargets {
22
+ /**
23
+ * The pixel size the registered defs are laid out at, which is what the driver's epsilon compares
24
+ * against.
25
+ */
26
+ readonly pixelSize: number;
27
+ /**
28
+ * The roll the registered defs are laid out at, in radians clockwise from north — the second
29
+ * layout axis, compared the way {@link pixelSize} is.
30
+ */
31
+ readonly roll: number;
32
+ /** Whether writes are still queued — what makes a caller keep frames coming. */
33
+ readonly hasWork: boolean;
34
+ /**
35
+ * Whether a pass is owed because a participant registered, unregistered or was passed to
36
+ * `update()` since the last one ran.
37
+ *
38
+ * The driver also runs a pass when the camera moves, which this does not report: a camera move is
39
+ * the driver's own observation, not the registry's.
40
+ */
41
+ readonly needsPlacement: boolean;
42
+ /**
43
+ * Whether a participant's box moved since the last pass, through `update()`.
44
+ *
45
+ * The driver runs the pass at once for this, as for {@link needsPlacement}: the verdicts the def
46
+ * earned were earned at its old position.
47
+ */
48
+ readonly placementDrifted: boolean;
49
+ /**
50
+ * Registers defs for re-layout, and lays out and writes the ones that are not current — so a def
51
+ * is current from the moment it is registered, and a membership change re-lays out only what it
52
+ * added.
53
+ * @param layer - The layer registering them, which is what places its participants in a collision
54
+ * scope: its own, or the one its {@link LayerDef.collisionGroup} names.
55
+ * @param defs - The defs to lay out; every one of them declares an `onScale`, an `onRoll` or a
56
+ * `placement`.
57
+ * @returns An unregister function; it also drops whatever of these defs a sweep still queued.
58
+ */
59
+ register(layer: LayerDef, defs: readonly RenderableDef[]): () => void;
60
+ /**
61
+ * Records the new pixel size and runs every registered scale rule against it, queueing what they
62
+ * report changed. Nothing is written here — that is {@link drain}'s job. Applying again before
63
+ * the queue drains re-runs the rules at the newer size, and a queued def is written once, with
64
+ * whatever its fields hold by then: the writes coalesce by construction.
65
+ * @param pixelSize - The new pixel size.
66
+ */
67
+ applyScale(pixelSize: number): void;
68
+ /**
69
+ * {@link applyScale}'s counterpart on the other axis, running only the rules that declare an
70
+ * `onRoll`. Both axes feed the one write queue.
71
+ * @param roll - The new roll, in radians clockwise from north.
72
+ */
73
+ applyRoll(roll: number): void;
74
+ /**
75
+ * Runs the placement pass over every registered participant and queues what the reports change:
76
+ * what each `onPlace` returns, then what each clustering layer's `onCluster` returns, through the
77
+ * same queue — a layer among them is written at once, as from any rule.
78
+ *
79
+ * The third `apply*`, and the one that is not an axis: placement depends on the whole camera
80
+ * rather than on one quantity, so the driver decides when to call this rather than comparing a
81
+ * number. The pass itself is synchronous and complete; only the writes it produces are budgeted,
82
+ * through the same queue {@link drain} works off.
83
+ *
84
+ * A registered def that no longer participates, because its `placement` was removed or it stopped
85
+ * being screen-space, is forgotten by the pass: it has no handler left to tell, and forgetting
86
+ * means that participating again reports its verdict afresh. A caller that removes `placement`
87
+ * owns restoring the def's appearance: one left shrunk or faded by its last `onPlace(false)`
88
+ * stays that way, because the handler left with the options and nothing can report to it.
89
+ *
90
+ * A known gap follows from the same removal: the defs it was holding down stay stood down, until
91
+ * something else owes a pass, because removing an option is a plain mutation that notifies
92
+ * nobody. Their room is free, and nothing re-offers it.
93
+ * @param context - What the camera shows this pass.
94
+ */
95
+ applyPlacement(context: PlacementContext): void;
96
+ /**
97
+ * Writes queued defs within a frame's budget, and says whether any remain — the caller's cue to
98
+ * ask for another frame.
99
+ *
100
+ * Three passes, in priority order: defs whose rule flipped them between hidden and shown, then
101
+ * every other queued def, then the ones an earlier pass deferred because the frustum rejects
102
+ * them. A flip matters most because it is what the user sees change — a label vanishing or a dot
103
+ * appearing — while a resize of something already shown can wait a frame.
104
+ *
105
+ * **A drain always takes at least one def**, however little budget is left — including none,
106
+ * which is what a worker descheduled before the first check leaves. That is what makes a sweep
107
+ * unable to stall: each drain shortens the queue, so the frames the driver keeps asking for
108
+ * eventually end it.
109
+ * @param frustum - The camera's frustum in plan space, when the caller has one; without it
110
+ * nothing is deferred.
111
+ * @param budgetMs - How much of the frame a call may spend. Non-positive means no limit.
112
+ * @returns Whether defs remain for a later drain.
113
+ */
114
+ drain(frustum?: Frustum, budgetMs?: number): boolean;
115
+ }
116
+ /**
117
+ * The renderer's re-layout registry. Absent outside a renderer, where a layer simply registers
118
+ * nothing — which is what lets `Layer` be rendered bare in a spec.
119
+ */
120
+ export declare const LayoutTargetsContext: import("react").Context<LayoutTargets | undefined>;
121
+ /**
122
+ * Registers a layer's re-layout targets for as long as the layer is mounted: the defs it holds that
123
+ * declare an `onScale`, an `onRoll` or a `placement`, and the layer itself when it declares a
124
+ * layout rule.
125
+ *
126
+ * A child *layer* is never registered here even when it declares a handler — it registers itself, and
127
+ * it has to: a handler assigned to a layer after mount re-renders that layer alone, so a registration
128
+ * held by its parent would never learn of it. Self-registration also covers the root, which has no
129
+ * parent layer to be registered by.
130
+ *
131
+ * Called from the layer rather than from its containers or their instances, because this is where
132
+ * both halves are already at hand. It also means a def is laid out whether or not its instance has
133
+ * mounted — defs are the source of truth, and a staged mount picks up whatever they hold when it
134
+ * admits them.
135
+ *
136
+ * Outside a renderer there is no registry and this does nothing, which is what lets a `Layer` be
137
+ * rendered bare in a spec.
138
+ * @param layer - The layer registering; itself a target when it declares a handler, which is how a
139
+ * layer whose *membership* depends on the camera re-derives it.
140
+ * @param children - The layer's children snapshot.
141
+ */
142
+ export declare function useLayoutTargets(layer: LayerDef, children: readonly RenderableDef[]): void;
143
+ /**
144
+ * Builds a renderer's re-layout registry.
145
+ * @param invalidate - Requests a frame, for layouts that happen outside the frame loop.
146
+ * @param pixelSize - The pixel size defs are laid out at until the first frame moves it.
147
+ * @param roll - The roll defs are laid out at until the first frame moves it, in radians.
148
+ * @returns The registry.
149
+ */
150
+ export declare function createLayoutTargets(invalidate: () => void, pixelSize: number, roll?: number): LayoutTargets;
151
+ //# sourceMappingURL=layout-targets.d.ts.map
@@ -0,0 +1,397 @@
1
+ import { createContext, use, useEffect } from 'react';
2
+ import { isImageDef, isLayerDef, isPlacementTarget } from '../types/guards';
3
+ import { subscribeDef, writeChanged } from '../update';
4
+ import { createLogger } from '../utils/logging';
5
+ import { culledBy } from './def-culling';
6
+ import { createPlacement } from './placement';
7
+ const logger = createLogger('layout');
8
+ /**
9
+ * The renderer's re-layout registry. Absent outside a renderer, where a layer simply registers
10
+ * nothing — which is what lets `Layer` be rendered bare in a spec.
11
+ */
12
+ export const LayoutTargetsContext = createContext(undefined);
13
+ /**
14
+ * Registers a layer's re-layout targets for as long as the layer is mounted: the defs it holds that
15
+ * declare an `onScale`, an `onRoll` or a `placement`, and the layer itself when it declares a
16
+ * layout rule.
17
+ *
18
+ * A child *layer* is never registered here even when it declares a handler — it registers itself, and
19
+ * it has to: a handler assigned to a layer after mount re-renders that layer alone, so a registration
20
+ * held by its parent would never learn of it. Self-registration also covers the root, which has no
21
+ * parent layer to be registered by.
22
+ *
23
+ * Called from the layer rather than from its containers or their instances, because this is where
24
+ * both halves are already at hand. It also means a def is laid out whether or not its instance has
25
+ * mounted — defs are the source of truth, and a staged mount picks up whatever they hold when it
26
+ * admits them.
27
+ *
28
+ * Outside a renderer there is no registry and this does nothing, which is what lets a `Layer` be
29
+ * rendered bare in a spec.
30
+ * @param layer - The layer registering; itself a target when it declares a handler, which is how a
31
+ * layer whose *membership* depends on the camera re-derives it.
32
+ * @param children - The layer's children snapshot.
33
+ */
34
+ export function useLayoutTargets(layer, children) {
35
+ const targets = use(LayoutTargetsContext);
36
+ useEffect(() => {
37
+ if (!targets)
38
+ return undefined;
39
+ const defs = children.filter((def) => (def.onScale !== undefined || def.onRoll !== undefined || declaresPlacement(def)) &&
40
+ !isLayerDef(def));
41
+ if (layer.onScale || layer.onRoll)
42
+ defs.push(layer);
43
+ if (!defs.length)
44
+ return undefined;
45
+ return targets.register(layer, defs);
46
+ }, [targets, layer, children]);
47
+ }
48
+ /**
49
+ * Builds a renderer's re-layout registry.
50
+ * @param invalidate - Requests a frame, for layouts that happen outside the frame loop.
51
+ * @param pixelSize - The pixel size defs are laid out at until the first frame moves it.
52
+ * @param roll - The roll defs are laid out at until the first frame moves it, in radians.
53
+ * @returns The registry.
54
+ */
55
+ export function createLayoutTargets(invalidate, pixelSize, roll = 0) {
56
+ /**
57
+ * Every registered def array, with the layer that registered it. Map entries are exactly what
58
+ * the placement pass takes, so a pass is handed the registry itself.
59
+ */
60
+ const groups = new Map();
61
+ const placement = createPlacement();
62
+ /** Whether a participant registered since the last pass, so one is owed. */
63
+ let placementDirty = false;
64
+ /** Whether a participant's box moved since the last pass, so one is owed. */
65
+ let placementDrifted = false;
66
+ /**
67
+ * The pixel size each def was last laid out at — the registry's layout state, because the rule
68
+ * that lays a def out is the def's own. Keyed by def rather than by group, so a membership change
69
+ * re-lays out only the defs that joined.
70
+ *
71
+ * A def whose `onScale` is *replaced* after it was laid out is not noticed: the record says only
72
+ * "laid out at this size". Reassigning a handler is a thing nothing does — a def is built with its
73
+ * rule — and a caller that does it lays the def out itself.
74
+ */
75
+ const layouts = new WeakMap();
76
+ /**
77
+ * The roll each def's `onRoll` last ran at — the other axis's record, kept apart from
78
+ * {@link layouts} because the two move independently.
79
+ */
80
+ const rolls = new WeakMap();
81
+ /**
82
+ * Whether each def was hidden when it was last written — what tells a visibility flip from a
83
+ * resize. Seeded at registration, which is when the def's instance wrote what it held.
84
+ */
85
+ const drawnHidden = new WeakMap();
86
+ /**
87
+ * The write queue, in priority order: defs a rule flipped between hidden and shown, then every
88
+ * other def a rule changed, then the ones a drain found outside the frustum. Sets, not arrays,
89
+ * because a def re-reported before its write must still be written once — with whatever its
90
+ * fields hold by then.
91
+ */
92
+ const flipped = new Set();
93
+ const queued = new Set();
94
+ const deferred = new Set();
95
+ /** What the sweep in flight has cost so far, for the log line that closes it. */
96
+ let sweep;
97
+ let current = pixelSize;
98
+ let currentRoll = roll;
99
+ /**
100
+ * The view every rule is handed — one instance for the registry's lifetime, mutated as the axes
101
+ * move, which is what keeps a ten-thousand-label sweep allocation-free. `LayoutView` documents
102
+ * the flip side: valid during the call, so a rule destructures rather than retains it.
103
+ */
104
+ const view = { pixelSize: current, roll: currentRoll };
105
+ /**
106
+ * Runs whatever of a def's rules are stale by their records, scale before roll so a rule that
107
+ * turns a label re-fits it at the size just applied. Each rule is recorded as run before anything
108
+ * it reported is handled, because handling can reach back in here.
109
+ *
110
+ * A rule reporting nothing is still recorded as run, so a membership change does not run it over
111
+ * the def again — reporting nothing is how a rule skips the write, which is the half that costs.
112
+ * @param def - The def to lay out.
113
+ * @param report - Receives what each rule reported changed.
114
+ */
115
+ function layOutStale(def, report) {
116
+ // Hidden defs are laid out too. Whether a def draws at all can be exactly what its rule decides
117
+ // — a booth's dot appears only once the plan is far enough out for its label to stop fitting —
118
+ // so a registry that skipped them would never run the rule that would bring them back.
119
+ if (def.onScale && layouts.get(def) !== current) {
120
+ const changed = def.onScale(view);
121
+ layouts.set(def, current);
122
+ if (changed?.length)
123
+ report(changed);
124
+ }
125
+ if (def.onRoll && rolls.get(def) !== currentRoll) {
126
+ const changed = def.onRoll(view);
127
+ rolls.set(def, currentRoll);
128
+ if (changed?.length)
129
+ report(changed);
130
+ }
131
+ }
132
+ /**
133
+ * Writes a def now — the layer path or the slot path, per `writeChanged` — and records whether
134
+ * what it drew was hidden.
135
+ * @param def - The def to write.
136
+ */
137
+ function write(def) {
138
+ drawnHidden.set(def, def.hidden === true);
139
+ writeChanged([def]);
140
+ }
141
+ /**
142
+ * Queues what a rule reported changed. A layer is written at once: its change is a membership
143
+ * change, which only React reconciles, and React already defers the sweep behind it.
144
+ * @param changed - The defs the rule reported.
145
+ */
146
+ function enqueue(changed) {
147
+ for (const def of changed) {
148
+ if (isLayerDef(def)) {
149
+ write(def);
150
+ continue;
151
+ }
152
+ const drawn = drawnHidden.get(def);
153
+ if (drawn !== undefined && drawn !== (def.hidden === true)) {
154
+ queued.delete(def);
155
+ deferred.delete(def);
156
+ flipped.add(def);
157
+ }
158
+ else {
159
+ // Flipped and flipped back before its write: against what the slot holds it is a resize.
160
+ flipped.delete(def);
161
+ deferred.delete(def);
162
+ queued.add(def);
163
+ }
164
+ }
165
+ }
166
+ /**
167
+ * Runs the stale rules of every registered def the moved axis concerns, queueing their writes.
168
+ * @param axis - The axis that moved, for the log line.
169
+ * @param handler - The moved axis's handler, `onScale` or `onRoll`.
170
+ */
171
+ function runHandlers(axis, handler) {
172
+ const start = performance.now();
173
+ const before = flipped.size + queued.size + deferred.size;
174
+ let ran = 0;
175
+ for (const defs of groups.keys()) {
176
+ for (const def of defs) {
177
+ if (!def[handler])
178
+ continue;
179
+ ran++;
180
+ layOutStale(def, enqueue);
181
+ }
182
+ }
183
+ const pending = flipped.size + queued.size + deferred.size;
184
+ if (pending > before) {
185
+ sweep ??= { defs: 0, drains: 0, writeMs: 0 };
186
+ sweep.defs += pending - before;
187
+ }
188
+ logger.debug(`${axis}: ran ${ran} ${handler} handlers in ${(performance.now() - start).toFixed(2)}ms, ` +
189
+ `${pending} defs to write (${flipped.size} visibility flips)`);
190
+ }
191
+ return {
192
+ get pixelSize() {
193
+ return current;
194
+ },
195
+ get roll() {
196
+ return currentRoll;
197
+ },
198
+ get hasWork() {
199
+ return flipped.size > 0 || queued.size > 0 || deferred.size > 0;
200
+ },
201
+ get needsPlacement() {
202
+ return placementDirty;
203
+ },
204
+ get placementDrifted() {
205
+ return placementDrifted;
206
+ },
207
+ register(layer, defs) {
208
+ groups.set(defs, layer);
209
+ let wrote = false;
210
+ for (const def of defs) {
211
+ if (!drawnHidden.has(def))
212
+ drawnHidden.set(def, def.hidden === true);
213
+ // Written at once, not queued: a def is current from the moment it registers, so one
214
+ // mounting mid-zoom draws at the size the plan is at.
215
+ layOutStale(def, (changed) => {
216
+ for (const each of changed)
217
+ write(each);
218
+ wrote = true;
219
+ });
220
+ }
221
+ // A verdict cannot be computed for one def alone, because it depends on every other box, and
222
+ // the registry holds no camera. So registration only marks a pass owed; the frame the
223
+ // `invalidate` below asks for is what runs it, and it runs before that frame draws.
224
+ //
225
+ // Owed on any `placement`, not only on a participant: a flat def that carries one is exactly
226
+ // the mistake the pass's development warning exists to catch, and the pass has to run to say so.
227
+ if (defs.some(declaresPlacement)) {
228
+ placementDirty = true;
229
+ wrote = true;
230
+ }
231
+ // Registration happens in an effect rather than inside the frame loop, so anything written here
232
+ // needs a frame asking for it.
233
+ if (wrote)
234
+ invalidate();
235
+ // A participant that moves owes a pass as one that joins does: its verdict, and those of the
236
+ // defs it used to block or be blocked by, were earned at the old position. Only participants
237
+ // are watched, so a write to anything else costs nothing, and only a change to what the pass
238
+ // measures owes one: an `update()` that moved no box, such as a fade writing `opacity` on every
239
+ // animation frame, would otherwise run the pass at frame rate on a still camera, past the rate
240
+ // gate that is there for the camera. The registry's own writes (`onPlace` returns, which go
241
+ // through `syncNow`) never notify, so a verdict flip cannot owe the pass that would flip it
242
+ // again.
243
+ const unsubscribes = defs.filter(isPlacementTarget).map((def) => {
244
+ const seen = measuredBy(def);
245
+ return subscribeDef(def, () => {
246
+ if (!remeasure(def, seen))
247
+ return;
248
+ placementDrifted = true;
249
+ invalidate();
250
+ });
251
+ });
252
+ return () => {
253
+ for (const unsubscribe of unsubscribes)
254
+ unsubscribe();
255
+ groups.delete(defs);
256
+ // A participant that leaves frees its room, so the defs it beat may now fit: owe a pass, and
257
+ // ask for the frame that runs it, as registration does.
258
+ if (defs.some(isPlacementTarget)) {
259
+ placementDirty = true;
260
+ invalidate();
261
+ }
262
+ // A queued def whose layer is gone must not be written from the backlog: its instances are
263
+ // unmounting.
264
+ for (const def of defs) {
265
+ flipped.delete(def);
266
+ queued.delete(def);
267
+ deferred.delete(def);
268
+ placement.forget(def);
269
+ }
270
+ };
271
+ },
272
+ applyScale(nextPixelSize) {
273
+ current = nextPixelSize;
274
+ view.pixelSize = nextPixelSize;
275
+ runHandlers(`pxToSvgScale ${+nextPixelSize.toFixed(3)}`, 'onScale');
276
+ },
277
+ applyRoll(nextRoll) {
278
+ currentRoll = nextRoll;
279
+ view.roll = nextRoll;
280
+ runHandlers(`roll ${+nextRoll.toFixed(3)}`, 'onRoll');
281
+ },
282
+ applyPlacement(context) {
283
+ placementDirty = false;
284
+ placementDrifted = false;
285
+ // Flips and clusters are reported in one synchronous call, flips first, so one drain writes
286
+ // a seed's hiding and the cluster standing for it together.
287
+ placement.run(groups, context, (def, placed) => {
288
+ const changed = def.placement?.onPlace(placed, view);
289
+ if (changed?.length)
290
+ enqueue(changed);
291
+ }, (_layer, clusters, clustering) => {
292
+ // The options the pass hands over, not the layer's current ones: a layer that dropped
293
+ // `clustering` is still owed the empty set through the handler that drew its clusters.
294
+ const changed = clustering.onCluster(clusters, view);
295
+ if (changed?.length)
296
+ enqueue(changed);
297
+ });
298
+ },
299
+ drain(frustum, budgetMs = -1) {
300
+ if (flipped.size === 0 && queued.size === 0 && deferred.size === 0)
301
+ return false;
302
+ const start = performance.now();
303
+ /**
304
+ * Defs this drain has taken off a queue, written or deferred.
305
+ *
306
+ * It gates the budget so that the **first** def of a drain is always taken. Without that, a
307
+ * budget already spent before the loop began — the worker descheduled between `start` and the
308
+ * check, which a loaded machine does routinely — dequeues nothing at all, and a drain that
309
+ * dequeues nothing is the stall this budget exists to prevent.
310
+ */
311
+ let processed = 0;
312
+ const overBudget = () => processed > 0 && budgetMs > 0 && performance.now() - start >= budgetMs;
313
+ // Sets iterate in insertion order and tolerate deletion mid-walk, which is what makes each pass
314
+ // a plain loop: a written def leaves its set before its write runs.
315
+ for (const queue of [flipped, queued]) {
316
+ for (const def of queue) {
317
+ if (overBudget())
318
+ break;
319
+ processed++;
320
+ queue.delete(def);
321
+ // Deferral is priority, never a skip: a def the camera cannot see joins the last pass.
322
+ if (frustum !== undefined && culledBy(def, frustum)) {
323
+ deferred.add(def);
324
+ continue;
325
+ }
326
+ write(def);
327
+ }
328
+ }
329
+ for (const def of deferred) {
330
+ if (overBudget())
331
+ break;
332
+ processed++;
333
+ deferred.delete(def);
334
+ write(def);
335
+ }
336
+ const remaining = flipped.size + queued.size + deferred.size;
337
+ if (sweep) {
338
+ sweep.drains++;
339
+ sweep.writeMs += performance.now() - start;
340
+ if (remaining === 0) {
341
+ logger.debug(`wrote ${sweep.defs} defs over ${sweep.drains} frames in ${sweep.writeMs.toFixed(2)}ms`);
342
+ sweep = undefined;
343
+ }
344
+ }
345
+ return remaining > 0;
346
+ },
347
+ };
348
+ }
349
+ /**
350
+ * Whether a def declares a placement, participating in the pass or not.
351
+ * @param def - The def to test.
352
+ * @returns Whether it carries `placement`.
353
+ */
354
+ function declaresPlacement(def) {
355
+ return def.placement !== undefined;
356
+ }
357
+ /**
358
+ * Reads what the pass measures of a participant: the box its bounds, origin and padding describe.
359
+ * @param def - The participant.
360
+ * @returns The reading.
361
+ */
362
+ function measuredBy(def) {
363
+ const measured = { numbers: new Float64Array(9), padding: undefined };
364
+ remeasure(def, measured);
365
+ // The first reading is the baseline, not a change.
366
+ return measured;
367
+ }
368
+ /**
369
+ * Re-reads a participant into an earlier reading and says whether anything the pass measures moved.
370
+ * @param def - The participant.
371
+ * @param into - The reading to compare against and overwrite.
372
+ * @returns Whether the reading changed.
373
+ */
374
+ function remeasure(def, into) {
375
+ const { bounds } = def;
376
+ const origin = isImageDef(def) ? def.origin : undefined;
377
+ const padding = def.placement?.padding;
378
+ const next = _measure;
379
+ next[0] = bounds.center.x;
380
+ next[1] = bounds.center.y;
381
+ next[2] = bounds.size.x;
382
+ next[3] = bounds.size.y;
383
+ next[4] = bounds.rotation;
384
+ next[5] = bounds.elevation;
385
+ next[6] = origin?.[0] ?? 0.5;
386
+ next[7] = origin?.[1] ?? 0.5;
387
+ let changed = padding !== into.padding;
388
+ for (let i = 0; i < 8; i++) {
389
+ if (next[i] !== into.numbers[i])
390
+ changed = true;
391
+ into.numbers[i] = next[i];
392
+ }
393
+ into.padding = padding;
394
+ return changed;
395
+ }
396
+ // Scratch for the reading, filled and compared within one call.
397
+ const _measure = new Float64Array(9);
@@ -0,0 +1,59 @@
1
+ import { type Camera } from 'three';
2
+ import { type Space } from '../../space/Space';
3
+ import { type ImageDef, type TextDef } from '../../types/defs';
4
+ import { type PlacementBox } from './grid';
5
+ /**
6
+ * The screen rectangle one def occupies, in CSS pixels from the canvas's top-left corner.
7
+ *
8
+ * There is one path, because only a screen-space def is ever tested. Its anchor projects through
9
+ * the camera, and its extent comes from the pixel size rather than from the projection: constant
10
+ * pixel size means a sprite is the same number of pixels across at every depth, so the only
11
+ * camera-dependent term is where the anchor lands.
12
+ *
13
+ * `origin` shifts the rectangle relative to the anchor exactly as it shifts the quad, in the
14
+ * plan's y-down sense, which is also the canvas's. A bottom-center origin therefore puts the
15
+ * anchor on the rectangle's bottom edge and the rectangle above it.
16
+ *
17
+ * This is the def's own extent, without its `placement.padding`: that is what the grid stores, and
18
+ * {@link paddedBox} grows it into the box that is tested. Storing the grown box as well would make
19
+ * two padded defs keep twice the padding apart.
20
+ * @param def - The def to measure.
21
+ * @param space - The scene's space, for the projection.
22
+ * @param camera - The camera in use.
23
+ * @param pixelSize - Plan units per CSS pixel.
24
+ * @param out - The box to write into, reused across a pass.
25
+ * @returns The box, or nothing when the def has no extent or its anchor does not project.
26
+ */
27
+ export declare function screenBoxOf(def: ImageDef | TextDef, space: Space, camera: Camera, pixelSize: number, out: PlacementBox): PlacementBox | undefined;
28
+ /**
29
+ * The screen rectangle a constant-pixel-size sprite occupies when its anchor stands at a plan
30
+ * point, in CSS pixels from the canvas's top-left corner.
31
+ *
32
+ * What {@link screenBoxOf} measures a def with, taken apart so that a box can stand at an anchor
33
+ * that is not a def's own: a cluster's marker stands at the mean of its units' anchors.
34
+ * @param x - The anchor's plan x.
35
+ * @param y - The anchor's plan y.
36
+ * @param elevation - The anchor's elevation, in plan units.
37
+ * @param width - The sprite's width in CSS pixels.
38
+ * @param height - The sprite's height in CSS pixels.
39
+ * @param origin - Which point of the sprite stands on the anchor, centered when omitted.
40
+ * @param rotation - Its spin about the screen normal, in radians.
41
+ * @param space - The scene's space, for the projection.
42
+ * @param camera - The camera in use.
43
+ * @param out - The box to write into.
44
+ * @returns The box, or nothing when the sprite has no extent or its anchor does not project.
45
+ */
46
+ export declare function spriteBoxAt(x: number, y: number, elevation: number, width: number, height: number, origin: readonly [number, number] | undefined, rotation: number, space: Space, camera: Camera, out: PlacementBox): PlacementBox | undefined;
47
+ /**
48
+ * The box a def is tested with: its extent grown by `placement.padding`, by one number on all four
49
+ * sides or by a `[horizontal, vertical]` pair.
50
+ *
51
+ * The grid stores the extent and tests this, so each def's padding applies once, on the side that is
52
+ * testing. A def with no padding gets a copy of its extent.
53
+ * @param def - The def whose padding grows the box.
54
+ * @param box - The def's extent, from {@link screenBoxOf}.
55
+ * @param out - The box to write into, which may be `box` itself.
56
+ * @returns `out`.
57
+ */
58
+ export declare function paddedBox(def: ImageDef | TextDef, box: PlacementBox, out: PlacementBox): PlacementBox;
59
+ //# sourceMappingURL=box.d.ts.map