@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.
- package/README.md +145 -0
- package/dist/Renderer.d.ts +102 -0
- package/dist/Renderer.js +96 -0
- package/dist/controls/CameraControls.d.ts +108 -0
- package/dist/controls/CameraControls.js +203 -0
- package/dist/controls/Controls.d.ts +53 -0
- package/dist/controls/Controls.js +54 -0
- package/dist/controls/camera-api.d.ts +226 -0
- package/dist/controls/camera-api.js +179 -0
- package/dist/controls/gestures.d.ts +71 -0
- package/dist/controls/gestures.js +98 -0
- package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
- package/dist/controls/handlers/DoubleTapZoom.js +82 -0
- package/dist/controls/handlers/Inertia.d.ts +60 -0
- package/dist/controls/handlers/Inertia.js +222 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +212 -0
- package/dist/controls/handlers/Pitch.d.ts +19 -0
- package/dist/controls/handlers/Pitch.js +200 -0
- package/dist/controls/handlers/Roll.d.ts +30 -0
- package/dist/controls/handlers/Roll.js +196 -0
- package/dist/controls/handlers/Zoom.d.ts +28 -0
- package/dist/controls/handlers/Zoom.js +34 -0
- package/dist/controls/hooks.d.ts +136 -0
- package/dist/controls/hooks.js +200 -0
- package/dist/controls/index.d.ts +9 -0
- package/dist/controls/index.js +7 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +108 -0
- package/dist/core/InstanceUniforms.js +298 -0
- package/dist/core/SquareDataTexture.d.ts +277 -0
- package/dist/core/SquareDataTexture.js +476 -0
- package/dist/core/batch/BatchedMesh.d.ts +74 -0
- package/dist/core/batch/BatchedMesh.js +155 -0
- package/dist/core/instanced/FrustumCulling.d.ts +141 -0
- package/dist/core/instanced/FrustumCulling.js +246 -0
- package/dist/core/instanced/InstancedMesh.d.ts +302 -0
- package/dist/core/instanced/InstancedMesh.js +497 -0
- package/dist/core/instanced/Instances.d.ts +119 -0
- package/dist/core/instanced/Instances.js +230 -0
- package/dist/core/instanced/Raycasting.d.ts +118 -0
- package/dist/core/instanced/Raycasting.js +134 -0
- package/dist/core/instanced/instanced-material.d.ts +77 -0
- package/dist/core/instanced/instanced-material.js +228 -0
- package/dist/core/lines/BatchedLines.d.ts +137 -0
- package/dist/core/lines/BatchedLines.js +215 -0
- package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
- package/dist/core/lines/FrustumCullingLines.js +85 -0
- package/dist/core/lines/RaycastingLines.d.ts +45 -0
- package/dist/core/lines/RaycastingLines.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +124 -0
- package/dist/core/shader-source.d.ts +70 -0
- package/dist/core/shader-source.js +110 -0
- package/dist/core/sprites/BatchedSprites.d.ts +115 -0
- package/dist/core/sprites/BatchedSprites.js +173 -0
- package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
- package/dist/core/sprites/RaycastingSprites.js +71 -0
- package/dist/core/sprites/sprite-material.d.ts +59 -0
- package/dist/core/sprites/sprite-material.js +192 -0
- package/dist/core/text/BatchedText.d.ts +233 -0
- package/dist/core/text/BatchedText.js +473 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +289 -0
- package/dist/core/text/Text.d.ts +88 -0
- package/dist/core/text/Text.js +230 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +123 -0
- package/dist/core/text/glyph-packing.d.ts +17 -0
- package/dist/core/text/glyph-packing.js +61 -0
- package/dist/core/typed-array.d.ts +16 -0
- package/dist/core/typed-array.js +20 -0
- package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
- package/dist/debug/components/DebugPanelProvider.js +50 -0
- package/dist/debug/components/DebugTools.d.ts +22 -0
- package/dist/debug/components/DebugTools.js +28 -0
- package/dist/debug/components/Gui.d.ts +22 -0
- package/dist/debug/components/Gui.js +42 -0
- package/dist/debug/components/MemoryInfo.d.ts +22 -0
- package/dist/debug/components/MemoryInfo.js +42 -0
- package/dist/debug/components/Panel.d.ts +31 -0
- package/dist/debug/components/Panel.js +104 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-chunk.d.ts +26 -0
- package/dist/debug/debug-chunk.js +32 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +41 -0
- package/dist/debug/index.js +43 -0
- package/dist/events/LayoutDriver.d.ts +36 -0
- package/dist/events/LayoutDriver.js +192 -0
- package/dist/events/PointerMissed.d.ts +23 -0
- package/dist/events/PointerMissed.js +58 -0
- package/dist/events/def-culling.d.ts +46 -0
- package/dist/events/def-culling.js +69 -0
- package/dist/events/layout-targets.d.ts +151 -0
- package/dist/events/layout-targets.js +397 -0
- package/dist/events/placement/box.d.ts +59 -0
- package/dist/events/placement/box.js +96 -0
- package/dist/events/placement/grid.d.ts +161 -0
- package/dist/events/placement/grid.js +235 -0
- package/dist/events/placement/index.d.ts +2 -0
- package/dist/events/placement/index.js +1 -0
- package/dist/events/placement/pass.d.ts +70 -0
- package/dist/events/placement/pass.js +617 -0
- package/dist/events/pointer.d.ts +194 -0
- package/dist/events/pointer.js +356 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +177 -0
- package/dist/external/external-camera.d.ts +18 -0
- package/dist/external/external-camera.js +103 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/containers/SlotContainer.d.ts +312 -0
- package/dist/objects/containers/SlotContainer.js +311 -0
- package/dist/objects/containers/hooks.d.ts +61 -0
- package/dist/objects/containers/hooks.js +196 -0
- package/dist/objects/containers/index.d.ts +6 -0
- package/dist/objects/containers/index.js +4 -0
- package/dist/objects/containers/keys.d.ts +10 -0
- package/dist/objects/containers/keys.js +18 -0
- package/dist/objects/containers/mesh-writes.d.ts +106 -0
- package/dist/objects/containers/mesh-writes.js +97 -0
- package/dist/objects/containers/props.d.ts +33 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +152 -0
- package/dist/objects/images/Images.d.ts +49 -0
- package/dist/objects/images/Images.js +394 -0
- package/dist/objects/images/atlas-packer.d.ts +129 -0
- package/dist/objects/images/atlas-packer.js +287 -0
- package/dist/objects/images/image-container.d.ts +74 -0
- package/dist/objects/images/image-container.js +100 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/pack-sources.d.ts +190 -0
- package/dist/objects/images/pack-sources.js +272 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
- package/dist/objects/images/worker/atlas-worker.js +123 -0
- package/dist/objects/images/worker/client.d.ts +62 -0
- package/dist/objects/images/worker/client.js +249 -0
- package/dist/objects/images/worker/index.d.ts +10 -0
- package/dist/objects/images/worker/index.js +1 -0
- package/dist/objects/layer/Layer.d.ts +28 -0
- package/dist/objects/layer/Layer.js +134 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/layer-context.d.ts +74 -0
- package/dist/objects/layer/layer-context.js +82 -0
- package/dist/objects/layer/mount-scheduler.d.ts +131 -0
- package/dist/objects/layer/mount-scheduler.js +289 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -0
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +384 -0
- package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
- package/dist/objects/lighting/OcclusionPasses.js +271 -0
- package/dist/objects/lighting/index.d.ts +11 -0
- package/dist/objects/lighting/index.js +10 -0
- package/dist/objects/lighting/lighting-gui.d.ts +24 -0
- package/dist/objects/lighting/lighting-gui.js +114 -0
- package/dist/objects/lighting/occluder.d.ts +34 -0
- package/dist/objects/lighting/occluder.js +62 -0
- package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
- package/dist/objects/lighting/occlusion-targets.js +276 -0
- package/dist/objects/lines/LineInstance.d.ts +21 -0
- package/dist/objects/lines/LineInstance.js +73 -0
- package/dist/objects/lines/Lines.d.ts +17 -0
- package/dist/objects/lines/Lines.js +31 -0
- package/dist/objects/lines/index.d.ts +2 -0
- package/dist/objects/lines/index.js +1 -0
- package/dist/objects/lines/line-container.d.ts +35 -0
- package/dist/objects/lines/line-container.js +40 -0
- package/dist/objects/materials/ColorMaterial.d.ts +92 -0
- package/dist/objects/materials/ColorMaterial.js +124 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +74 -0
- package/dist/objects/materials/ModelMaterial.d.ts +53 -0
- package/dist/objects/materials/ModelMaterial.js +111 -0
- package/dist/objects/materials/TextureMaterial.d.ts +33 -0
- package/dist/objects/materials/TextureMaterial.js +48 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
- package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
- package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
- package/dist/objects/materials/effects/ao/index.d.ts +129 -0
- package/dist/objects/materials/effects/ao/index.js +72 -0
- package/dist/objects/materials/effects/color-dim.d.ts +115 -0
- package/dist/objects/materials/effects/color-dim.js +206 -0
- package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
- package/dist/objects/materials/effects/contact-shadow.js +167 -0
- package/dist/objects/materials/effects/effects-context.d.ts +53 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/materials/shared.d.ts +24 -0
- package/dist/objects/materials/shared.js +72 -0
- package/dist/objects/models/ModelInstance.d.ts +22 -0
- package/dist/objects/models/ModelInstance.js +33 -0
- package/dist/objects/models/Models.d.ts +22 -0
- package/dist/objects/models/Models.js +186 -0
- package/dist/objects/models/gltf-cache.d.ts +17 -0
- package/dist/objects/models/gltf-cache.js +50 -0
- package/dist/objects/models/index.d.ts +2 -0
- package/dist/objects/models/index.js +1 -0
- package/dist/objects/models/model-asset.d.ts +48 -0
- package/dist/objects/models/model-asset.js +77 -0
- package/dist/objects/models/model-container.d.ts +123 -0
- package/dist/objects/models/model-container.js +207 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +39 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +131 -0
- package/dist/objects/shapes/index.d.ts +2 -0
- package/dist/objects/shapes/index.js +1 -0
- package/dist/objects/shapes/shape-container.d.ts +141 -0
- package/dist/objects/shapes/shape-container.js +242 -0
- package/dist/objects/shapes/shape-geometry.d.ts +27 -0
- package/dist/objects/shapes/shape-geometry.js +94 -0
- package/dist/objects/text/TextInstance.d.ts +22 -0
- package/dist/objects/text/TextInstance.js +95 -0
- package/dist/objects/text/Texts.d.ts +30 -0
- package/dist/objects/text/Texts.js +43 -0
- package/dist/objects/text/index.d.ts +2 -0
- package/dist/objects/text/index.js +1 -0
- package/dist/objects/text/text-container.d.ts +121 -0
- package/dist/objects/text/text-container.js +229 -0
- package/dist/objects/text/text-layout.d.ts +92 -0
- package/dist/objects/text/text-layout.js +180 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +47 -0
- package/dist/space/Scene.js +127 -0
- package/dist/space/Space.d.ts +325 -0
- package/dist/space/Space.js +359 -0
- package/dist/space/VisibleRect.d.ts +45 -0
- package/dist/space/VisibleRect.js +129 -0
- package/dist/space/camera-handle.d.ts +67 -0
- package/dist/space/camera-handle.js +63 -0
- package/dist/space/camera-kind.d.ts +26 -0
- package/dist/space/camera-kind.js +30 -0
- package/dist/space/canvas-size.d.ts +24 -0
- package/dist/space/canvas-size.js +20 -0
- package/dist/space/clipping.d.ts +69 -0
- package/dist/space/clipping.js +130 -0
- package/dist/space/layout-measure.d.ts +67 -0
- package/dist/space/layout-measure.js +175 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +234 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +198 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +21 -0
- package/dist/types/guards.js +52 -0
- package/dist/update/index.d.ts +3 -0
- package/dist/update/index.js +2 -0
- package/dist/update/slot-writers.d.ts +30 -0
- package/dist/update/slot-writers.js +76 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +104 -0
- package/dist/utils/arrays.d.ts +13 -0
- package/dist/utils/arrays.js +20 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +27 -0
- package/dist/utils/hot-state.d.ts +41 -0
- package/dist/utils/hot-state.js +37 -0
- package/dist/utils/logging.d.ts +29 -0
- package/dist/utils/logging.js +55 -0
- package/dist/utils/math.d.ts +52 -0
- package/dist/utils/math.js +76 -0
- 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
|