@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,28 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** Options for the zoom handler, which are also its props. */
|
|
3
|
+
export interface ZoomConfig {
|
|
4
|
+
/**
|
|
5
|
+
* What a double-click or a double-tap multiplies the zoom factor by, zooming toward the point
|
|
6
|
+
* under the pointer and keeping it there. Off when left out.
|
|
7
|
+
*/
|
|
8
|
+
dblclick?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Zooming: the wheel, a two-finger pinch, and — when asked for — a double-click or double-tap.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here beyond the actions for the first two — camera-controls recognizes both gestures
|
|
14
|
+
* itself, and `Controls` has already told it to dolly toward the cursor rather than the centre. The
|
|
15
|
+
* two-finger action pans as well as pinches, which is what makes a pinch feel like it holds the plan
|
|
16
|
+
* under the fingers; the twist half of a two-finger gesture is `Roll`'s, and comes off the gesture
|
|
17
|
+
* recognizer rather than from here, so the two never contend for `touches.two`.
|
|
18
|
+
*
|
|
19
|
+
* The double-click is not camera-controls' either, so it comes off the gesture recognizer, which
|
|
20
|
+
* reads a mouse and a finger alike: see {@link DoubleTapZoom}.
|
|
21
|
+
*
|
|
22
|
+
* Mounting is enabling. The actions are set when this mounts and returned to `NONE` when it
|
|
23
|
+
* unmounts, which is the whole of what the imperative renderer's `enable()` / `disable()` did.
|
|
24
|
+
* @param props - Whether a double-click zooms, and by how much.
|
|
25
|
+
* @returns The double-click zoom, when there is one.
|
|
26
|
+
*/
|
|
27
|
+
export declare function Zoom(props: ZoomConfig): ReactNode;
|
|
28
|
+
//# sourceMappingURL=Zoom.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { CameraControlsAction } from '../CameraControls';
|
|
3
|
+
import { useControlsEffect } from '../hooks';
|
|
4
|
+
import { DoubleTapZoom } from './DoubleTapZoom';
|
|
5
|
+
/**
|
|
6
|
+
* Zooming: the wheel, a two-finger pinch, and — when asked for — a double-click or double-tap.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here beyond the actions for the first two — camera-controls recognizes both gestures
|
|
9
|
+
* itself, and `Controls` has already told it to dolly toward the cursor rather than the centre. The
|
|
10
|
+
* two-finger action pans as well as pinches, which is what makes a pinch feel like it holds the plan
|
|
11
|
+
* under the fingers; the twist half of a two-finger gesture is `Roll`'s, and comes off the gesture
|
|
12
|
+
* recognizer rather than from here, so the two never contend for `touches.two`.
|
|
13
|
+
*
|
|
14
|
+
* The double-click is not camera-controls' either, so it comes off the gesture recognizer, which
|
|
15
|
+
* reads a mouse and a finger alike: see {@link DoubleTapZoom}.
|
|
16
|
+
*
|
|
17
|
+
* Mounting is enabling. The actions are set when this mounts and returned to `NONE` when it
|
|
18
|
+
* unmounts, which is the whole of what the imperative renderer's `enable()` / `disable()` did.
|
|
19
|
+
* @param props - Whether a double-click zooms, and by how much.
|
|
20
|
+
* @returns The double-click zoom, when there is one.
|
|
21
|
+
*/
|
|
22
|
+
export function Zoom(props) {
|
|
23
|
+
useControlsEffect((controls) => {
|
|
24
|
+
controls.mouseButtons.wheel = CameraControlsAction.DOLLY;
|
|
25
|
+
controls.touches.two = CameraControlsAction.TOUCH_DOLLY_SCREEN_PAN;
|
|
26
|
+
return () => {
|
|
27
|
+
controls.mouseButtons.wheel = CameraControlsAction.NONE;
|
|
28
|
+
controls.touches.two = CameraControlsAction.NONE;
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
if (props.dblclick === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
return _jsx(DoubleTapZoom, { factor: props.dblclick });
|
|
34
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every hook a component reaches the camera through, and the state they read.
|
|
3
|
+
*
|
|
4
|
+
* Beside `CameraControls.tsx` rather than in it, because Fast Refresh swaps a module in place only
|
|
5
|
+
* when every value it exports is a component. A hook exported next to one sends that module, and
|
|
6
|
+
* everything importing it, through a full page reload instead.
|
|
7
|
+
*
|
|
8
|
+
* The dependency runs one way, from here into `camera-api.ts`. That file holds the commands and the
|
|
9
|
+
* arithmetic behind them and imports nothing back, which is what lets `useCameraAPI` sit here with
|
|
10
|
+
* the rest.
|
|
11
|
+
*/
|
|
12
|
+
import { Point } from '@expofp/geometry';
|
|
13
|
+
import { type CameraAPI } from './camera-api';
|
|
14
|
+
import { type CameraControlsImpl } from './CameraControls';
|
|
15
|
+
/**
|
|
16
|
+
* The scene's camera controls, once {@link Controls} has mounted them.
|
|
17
|
+
*
|
|
18
|
+
* For **reading** them, and for handing them to something else that does the writing — a gesture
|
|
19
|
+
* class, a measurement, a helper taking them as an argument. What the React Compiler rejects is
|
|
20
|
+
* assigning to a *field* of a hook's return value, which is a build error rather than a bug; a
|
|
21
|
+
* method call on it, or passing it along, is fine. A component with a write it cannot route through
|
|
22
|
+
* a function uses {@link useControlsEffect}, which hands the controls over as an argument.
|
|
23
|
+
* @returns The controls, or `undefined` until they mount.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useCameraControls(): CameraControlsImpl | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Runs an effect against the scene's camera controls, for as long as the component is mounted.
|
|
28
|
+
*
|
|
29
|
+
* The controls arrive as an argument rather than from a hook, which is what lets the effect write to
|
|
30
|
+
* them; and the "wait for them to mount" subscription lives here rather than in every caller, so a
|
|
31
|
+
* component that claims a mouse action is three lines and says only what it claims. Together with
|
|
32
|
+
* {@link useCameraControls} that is the whole surface — read them, or write to them in an effect.
|
|
33
|
+
*
|
|
34
|
+
* Re-runs when the controls change and whenever `effect` does, which — the React Compiler caching
|
|
35
|
+
* the closure against what it captures — is whenever the component's own inputs move.
|
|
36
|
+
* @param effect - What to do with the controls; may return a cleanup, as any effect may.
|
|
37
|
+
*/
|
|
38
|
+
export declare function useControlsEffect(effect: (controls: CameraControlsImpl) => (() => void) | void): void;
|
|
39
|
+
/**
|
|
40
|
+
* Runs a handler once the camera has come to rest after moving — one call per movement rather than
|
|
41
|
+
* one per frame, whether a gesture or a command drove it. The event the imperative renderer
|
|
42
|
+
* published as `navigation:change`, under a name that says what happened.
|
|
43
|
+
*
|
|
44
|
+
* At rest and not at the start, so a caller that reads the camera back reads where the movement
|
|
45
|
+
* actually ended: the framing a kiosk saves, or the rect an overlay measures itself against. Every
|
|
46
|
+
* movement is reported because the one that moved the camera is not always the one that cares — the
|
|
47
|
+
* zoom buttons reach the camera through the store, and the screen recording the framing never sees
|
|
48
|
+
* that command.
|
|
49
|
+
*
|
|
50
|
+
* `sleep` rather than `rest`, which the library recommends for "the camera has stopped": `rest` is
|
|
51
|
+
* dispatched from the damping tail, and a pan here has none to dispatch from ({@link Controls}
|
|
52
|
+
* zeroes the control smoothing for truck and rotate), so a drag can finish without ever resting.
|
|
53
|
+
* `sleep` is dispatched for every movement, a jump included.
|
|
54
|
+
*
|
|
55
|
+
* A camera-level event rather than the controls themselves, so a caller recording where the plan was
|
|
56
|
+
* left never learns which library drives the camera. For as long as the component is mounted; where
|
|
57
|
+
* the camera *is* once it settles is `useCameraState`'s business.
|
|
58
|
+
* @param handler - Called once each movement has come to rest. Re-subscribed when its identity
|
|
59
|
+
* changes, so pass a stable function — under the React Compiler an inline closure over stable
|
|
60
|
+
* values is one.
|
|
61
|
+
*/
|
|
62
|
+
export declare function useCameraMoveEnd(handler: () => void): void;
|
|
63
|
+
/**
|
|
64
|
+
* Runs a handler while the user is moving the camera by hand — a drag, a pinch and a wheel tick
|
|
65
|
+
* alike, as each one reaches the camera.
|
|
66
|
+
*
|
|
67
|
+
* The user's own movement and nothing else, which is what an idle timer wants: a kiosk that treated
|
|
68
|
+
* a command as activity would have the idle reset's own camera restore re-arm the timer it had just
|
|
69
|
+
* fired, and would never return home. Deliberately not bracketed by the gesture's start and end —
|
|
70
|
+
* the controls report neither for the wheel, since scrolling arrives in intermittent ticks with no
|
|
71
|
+
* way to tell a last one from a pause.
|
|
72
|
+
* @param handler - Called as each movement of the user's reaches the camera, which may be once per
|
|
73
|
+
* frame for as long as a drag lasts. Re-subscribed when its identity changes, so pass a stable
|
|
74
|
+
* function — under the React Compiler an inline closure over stable values is one.
|
|
75
|
+
*/
|
|
76
|
+
export declare function useCameraGesture(handler: () => void): void;
|
|
77
|
+
/** Where the camera is, at the moment it was asked. */
|
|
78
|
+
export interface CameraState {
|
|
79
|
+
/** The point on the plan it is looking at, in plan units. */
|
|
80
|
+
center: Point;
|
|
81
|
+
/** How far in it is zoomed, as a factor: `1` frames the whole viewbox. */
|
|
82
|
+
zoom: number;
|
|
83
|
+
/** Which way the plan is turned, in degrees clockwise from north, in `[0, 360)`. */
|
|
84
|
+
roll: number;
|
|
85
|
+
/** How far the plan is tilted away from face-on, in degrees. */
|
|
86
|
+
pitch: number;
|
|
87
|
+
/**
|
|
88
|
+
* How much of the plan one CSS pixel spans, in plan units — the factor anything sized in
|
|
89
|
+
* pixels is laid out against, and the quantity the imperative renderer published as `ptScale`.
|
|
90
|
+
* The device ratio is not part of it.
|
|
91
|
+
*/
|
|
92
|
+
pixelSize: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Reads where the camera is.
|
|
96
|
+
*
|
|
97
|
+
* A **getter**, not a value: nothing here belongs in render output — a component that re-rendered
|
|
98
|
+
* whenever the camera moved would re-render on every frame of every pan — and every caller wants it
|
|
99
|
+
* at a moment of its own choosing, in an event handler, an effect, or a snapshot a host asked for.
|
|
100
|
+
* That is the same reading discipline `Space` documents, for the same reason.
|
|
101
|
+
*
|
|
102
|
+
* The five numbers come from four different places, which is why this exists at all rather than
|
|
103
|
+
* being arithmetic a caller could do: the target from the controls and the plan it lands on from the
|
|
104
|
+
* space, the zoom factor from the camera's handle, and the pixel size from the re-layout registry —
|
|
105
|
+
* which is what the plan's own labels are sized against, so a caller measuring against anything else
|
|
106
|
+
* would disagree with what is drawn.
|
|
107
|
+
*
|
|
108
|
+
* Outside a mounted scene, or in the frames before the controls publish themselves, it reports the
|
|
109
|
+
* camera at rest: the middle of the plan, framed whole, facing north.
|
|
110
|
+
*
|
|
111
|
+
* As with {@link useCameraAPI}, the reader is **new on every render** and this module memoizes
|
|
112
|
+
* nothing; a consumer that needs a settled identity holds it in a ref.
|
|
113
|
+
* @returns A function that reads the current state.
|
|
114
|
+
*/
|
|
115
|
+
export declare function useCameraState(): () => CameraState;
|
|
116
|
+
/**
|
|
117
|
+
* The scene's camera commands.
|
|
118
|
+
*
|
|
119
|
+
* A hook rather than a ref handed out from above, and that is the point: it only resolves inside a
|
|
120
|
+
* component mounted under `<Renderer>`, and R3F mounts those only once the canvas and its context
|
|
121
|
+
* exist — so "the camera cannot be driven before the scene is ready" is enforced by where the code
|
|
122
|
+
* can live rather than by a null check every caller has to remember. A host that needs to drive the
|
|
123
|
+
* camera from outside React publishes this object from an effect, which is exactly when it is safe.
|
|
124
|
+
*
|
|
125
|
+
* Before the controls mount every command is a no-op that resolves at once, which is the same frame
|
|
126
|
+
* or two the hook's own consumers spend waiting for their effects.
|
|
127
|
+
*
|
|
128
|
+
* **The returned object is new on every render, deliberately.** This module is not memoized by hand
|
|
129
|
+
* — the React Compiler is scoped to `.tsx` under `src/` (see `vitest.config.mts`), and reaching for
|
|
130
|
+
* `useMemo` here would put a cache in the library that every consumer then has to trust. A consumer
|
|
131
|
+
* that cannot re-subscribe cheaply should hold this in a ref rather than in a dependency list; that
|
|
132
|
+
* way the decision, and its cost, sit with the code that knows how often it renders.
|
|
133
|
+
* @returns The commands.
|
|
134
|
+
*/
|
|
135
|
+
export declare function useCameraAPI(): CameraAPI;
|
|
136
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every hook a component reaches the camera through, and the state they read.
|
|
3
|
+
*
|
|
4
|
+
* Beside `CameraControls.tsx` rather than in it, because Fast Refresh swaps a module in place only
|
|
5
|
+
* when every value it exports is a component. A hook exported next to one sends that module, and
|
|
6
|
+
* everything importing it, through a full page reload instead.
|
|
7
|
+
*
|
|
8
|
+
* The dependency runs one way, from here into `camera-api.ts`. That file holds the commands and the
|
|
9
|
+
* arithmetic behind them and imports nothing back, which is what lets `useCameraAPI` sit here with
|
|
10
|
+
* the rest.
|
|
11
|
+
*/
|
|
12
|
+
import { Point, radToDeg } from '@expofp/geometry';
|
|
13
|
+
import { useThree } from '@react-three/fiber';
|
|
14
|
+
import { use, useEffect } from 'react';
|
|
15
|
+
import { Vector3 } from 'three';
|
|
16
|
+
import { LayoutTargetsContext } from '../events/layout-targets';
|
|
17
|
+
import { useCamera } from '../space/camera-handle';
|
|
18
|
+
import { useCanvasSize } from '../space/canvas-size';
|
|
19
|
+
import { useSpace } from '../space/Space';
|
|
20
|
+
import { bearing, CameraCommands } from './camera-api';
|
|
21
|
+
/**
|
|
22
|
+
* The scene's camera controls, once {@link Controls} has mounted them.
|
|
23
|
+
*
|
|
24
|
+
* For **reading** them, and for handing them to something else that does the writing — a gesture
|
|
25
|
+
* class, a measurement, a helper taking them as an argument. What the React Compiler rejects is
|
|
26
|
+
* assigning to a *field* of a hook's return value, which is a build error rather than a bug; a
|
|
27
|
+
* method call on it, or passing it along, is fine. A component with a write it cannot route through
|
|
28
|
+
* a function uses {@link useControlsEffect}, which hands the controls over as an argument.
|
|
29
|
+
* @returns The controls, or `undefined` until they mount.
|
|
30
|
+
*/
|
|
31
|
+
export function useCameraControls() {
|
|
32
|
+
return useThree((state) => state.controls) ?? undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Runs an effect against the scene's camera controls, for as long as the component is mounted.
|
|
36
|
+
*
|
|
37
|
+
* The controls arrive as an argument rather than from a hook, which is what lets the effect write to
|
|
38
|
+
* them; and the "wait for them to mount" subscription lives here rather than in every caller, so a
|
|
39
|
+
* component that claims a mouse action is three lines and says only what it claims. Together with
|
|
40
|
+
* {@link useCameraControls} that is the whole surface — read them, or write to them in an effect.
|
|
41
|
+
*
|
|
42
|
+
* Re-runs when the controls change and whenever `effect` does, which — the React Compiler caching
|
|
43
|
+
* the closure against what it captures — is whenever the component's own inputs move.
|
|
44
|
+
* @param effect - What to do with the controls; may return a cleanup, as any effect may.
|
|
45
|
+
*/
|
|
46
|
+
export function useControlsEffect(effect) {
|
|
47
|
+
const get = useThree((state) => state.get);
|
|
48
|
+
// A subscription, so this re-runs once the controls have mounted. Deliberately not the value the
|
|
49
|
+
// effect uses: that is read fresh from the store below.
|
|
50
|
+
const mounted = useThree((state) => state.controls);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const controls = get().controls;
|
|
53
|
+
if (!controls)
|
|
54
|
+
return undefined;
|
|
55
|
+
return effect(controls) ?? undefined;
|
|
56
|
+
}, [effect, get, mounted]);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Runs a handler once the camera has come to rest after moving — one call per movement rather than
|
|
60
|
+
* one per frame, whether a gesture or a command drove it. The event the imperative renderer
|
|
61
|
+
* published as `navigation:change`, under a name that says what happened.
|
|
62
|
+
*
|
|
63
|
+
* At rest and not at the start, so a caller that reads the camera back reads where the movement
|
|
64
|
+
* actually ended: the framing a kiosk saves, or the rect an overlay measures itself against. Every
|
|
65
|
+
* movement is reported because the one that moved the camera is not always the one that cares — the
|
|
66
|
+
* zoom buttons reach the camera through the store, and the screen recording the framing never sees
|
|
67
|
+
* that command.
|
|
68
|
+
*
|
|
69
|
+
* `sleep` rather than `rest`, which the library recommends for "the camera has stopped": `rest` is
|
|
70
|
+
* dispatched from the damping tail, and a pan here has none to dispatch from ({@link Controls}
|
|
71
|
+
* zeroes the control smoothing for truck and rotate), so a drag can finish without ever resting.
|
|
72
|
+
* `sleep` is dispatched for every movement, a jump included.
|
|
73
|
+
*
|
|
74
|
+
* A camera-level event rather than the controls themselves, so a caller recording where the plan was
|
|
75
|
+
* left never learns which library drives the camera. For as long as the component is mounted; where
|
|
76
|
+
* the camera *is* once it settles is `useCameraState`'s business.
|
|
77
|
+
* @param handler - Called once each movement has come to rest. Re-subscribed when its identity
|
|
78
|
+
* changes, so pass a stable function — under the React Compiler an inline closure over stable
|
|
79
|
+
* values is one.
|
|
80
|
+
*/
|
|
81
|
+
export function useCameraMoveEnd(handler) {
|
|
82
|
+
useControlsEffect((controls) => {
|
|
83
|
+
controls.addEventListener('sleep', handler);
|
|
84
|
+
return () => controls.removeEventListener('sleep', handler);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Runs a handler while the user is moving the camera by hand — a drag, a pinch and a wheel tick
|
|
89
|
+
* alike, as each one reaches the camera.
|
|
90
|
+
*
|
|
91
|
+
* The user's own movement and nothing else, which is what an idle timer wants: a kiosk that treated
|
|
92
|
+
* a command as activity would have the idle reset's own camera restore re-arm the timer it had just
|
|
93
|
+
* fired, and would never return home. Deliberately not bracketed by the gesture's start and end —
|
|
94
|
+
* the controls report neither for the wheel, since scrolling arrives in intermittent ticks with no
|
|
95
|
+
* way to tell a last one from a pause.
|
|
96
|
+
* @param handler - Called as each movement of the user's reaches the camera, which may be once per
|
|
97
|
+
* frame for as long as a drag lasts. Re-subscribed when its identity changes, so pass a stable
|
|
98
|
+
* function — under the React Compiler an inline closure over stable values is one.
|
|
99
|
+
*/
|
|
100
|
+
export function useCameraGesture(handler) {
|
|
101
|
+
useControlsEffect((controls) => {
|
|
102
|
+
controls.addEventListener('control', handler);
|
|
103
|
+
return () => controls.removeEventListener('control', handler);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Reads where the camera is.
|
|
108
|
+
*
|
|
109
|
+
* A **getter**, not a value: nothing here belongs in render output — a component that re-rendered
|
|
110
|
+
* whenever the camera moved would re-render on every frame of every pan — and every caller wants it
|
|
111
|
+
* at a moment of its own choosing, in an event handler, an effect, or a snapshot a host asked for.
|
|
112
|
+
* That is the same reading discipline `Space` documents, for the same reason.
|
|
113
|
+
*
|
|
114
|
+
* The five numbers come from four different places, which is why this exists at all rather than
|
|
115
|
+
* being arithmetic a caller could do: the target from the controls and the plan it lands on from the
|
|
116
|
+
* space, the zoom factor from the camera's handle, and the pixel size from the re-layout registry —
|
|
117
|
+
* which is what the plan's own labels are sized against, so a caller measuring against anything else
|
|
118
|
+
* would disagree with what is drawn.
|
|
119
|
+
*
|
|
120
|
+
* Outside a mounted scene, or in the frames before the controls publish themselves, it reports the
|
|
121
|
+
* camera at rest: the middle of the plan, framed whole, facing north.
|
|
122
|
+
*
|
|
123
|
+
* As with {@link useCameraAPI}, the reader is **new on every render** and this module memoizes
|
|
124
|
+
* nothing; a consumer that needs a settled identity holds it in a ref.
|
|
125
|
+
* @returns A function that reads the current state.
|
|
126
|
+
*/
|
|
127
|
+
export function useCameraState() {
|
|
128
|
+
const controls = useCameraControls();
|
|
129
|
+
const camera = useCamera();
|
|
130
|
+
const space = useSpace();
|
|
131
|
+
const targets = use(LayoutTargetsContext);
|
|
132
|
+
return () => readCameraState({ controls, camera, space, targets });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Reads the camera, from whatever the scene holds at the moment of asking.
|
|
136
|
+
* @param context - What the scene holds.
|
|
137
|
+
* @returns Where the camera is.
|
|
138
|
+
*/
|
|
139
|
+
function readCameraState(context) {
|
|
140
|
+
const { controls, camera, space, targets } = context;
|
|
141
|
+
const pixelSize = targets?.pixelSize ?? 1;
|
|
142
|
+
// A fresh centre every time: a caller that keeps the point it was handed must never find it
|
|
143
|
+
// moved under them.
|
|
144
|
+
if (!controls)
|
|
145
|
+
return { ...atRest(), pixelSize };
|
|
146
|
+
controls.getTarget(_target);
|
|
147
|
+
return {
|
|
148
|
+
center: space.worldToPlan(_target, new Point()),
|
|
149
|
+
zoom: camera?.zoomFactor ?? 1,
|
|
150
|
+
roll: radToDeg(bearing(controls)),
|
|
151
|
+
pitch: radToDeg(controls.polarAngle),
|
|
152
|
+
pixelSize,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The camera as it stands before anything has moved it, which is what a read outside a mounted
|
|
157
|
+
* scene reports: the middle of the plan, framed whole, facing north.
|
|
158
|
+
* @returns The state.
|
|
159
|
+
*/
|
|
160
|
+
function atRest() {
|
|
161
|
+
return { center: new Point(), zoom: 1, roll: 0, pitch: 0, pixelSize: 1 };
|
|
162
|
+
}
|
|
163
|
+
const _target = new Vector3();
|
|
164
|
+
/**
|
|
165
|
+
* The scene's camera commands.
|
|
166
|
+
*
|
|
167
|
+
* A hook rather than a ref handed out from above, and that is the point: it only resolves inside a
|
|
168
|
+
* component mounted under `<Renderer>`, and R3F mounts those only once the canvas and its context
|
|
169
|
+
* exist — so "the camera cannot be driven before the scene is ready" is enforced by where the code
|
|
170
|
+
* can live rather than by a null check every caller has to remember. A host that needs to drive the
|
|
171
|
+
* camera from outside React publishes this object from an effect, which is exactly when it is safe.
|
|
172
|
+
*
|
|
173
|
+
* Before the controls mount every command is a no-op that resolves at once, which is the same frame
|
|
174
|
+
* or two the hook's own consumers spend waiting for their effects.
|
|
175
|
+
*
|
|
176
|
+
* **The returned object is new on every render, deliberately.** This module is not memoized by hand
|
|
177
|
+
* — the React Compiler is scoped to `.tsx` under `src/` (see `vitest.config.mts`), and reaching for
|
|
178
|
+
* `useMemo` here would put a cache in the library that every consumer then has to trust. A consumer
|
|
179
|
+
* that cannot re-subscribe cheaply should hold this in a ref rather than in a dependency list; that
|
|
180
|
+
* way the decision, and its cost, sit with the code that knows how often it renders.
|
|
181
|
+
* @returns The commands.
|
|
182
|
+
*/
|
|
183
|
+
export function useCameraAPI() {
|
|
184
|
+
const get = useThree((state) => state.get);
|
|
185
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
186
|
+
const controls = useCameraControls();
|
|
187
|
+
const camera = useCamera();
|
|
188
|
+
const space = useSpace();
|
|
189
|
+
const { width, height, dpr } = useCanvasSize();
|
|
190
|
+
return new CameraCommands({
|
|
191
|
+
controls,
|
|
192
|
+
camera,
|
|
193
|
+
space,
|
|
194
|
+
view: () => get().camera.view,
|
|
195
|
+
width,
|
|
196
|
+
height,
|
|
197
|
+
dpr,
|
|
198
|
+
invalidate,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { bearing, type CameraAPI, type CameraAxes, CameraCommands, type TransitionOptions, type ZoomToOptions, } from './camera-api';
|
|
2
|
+
export { CameraControls, CameraControlsAction, type CameraControlsImpl } from './CameraControls';
|
|
3
|
+
export { Controls, type ControlsConfig, type ControlsProps } from './Controls';
|
|
4
|
+
export type { InertiaConfig } from './handlers/Inertia';
|
|
5
|
+
export type { PanConfig } from './handlers/Pan';
|
|
6
|
+
export type { RollConfig } from './handlers/Roll';
|
|
7
|
+
export type { ZoomConfig } from './handlers/Zoom';
|
|
8
|
+
export { type CameraState, useCameraAPI, useCameraControls, useCameraGesture, useCameraMoveEnd, useCameraState, useControlsEffect, } from './hooks';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The package's controls surface. A barrel and nothing else: a module that both re-exports and
|
|
2
|
+
// declares a component cannot be a Fast Refresh boundary, and every hook below would send `Controls`
|
|
3
|
+
// through a full reload with it.
|
|
4
|
+
export { bearing, CameraCommands, } from './camera-api';
|
|
5
|
+
export { CameraControls, CameraControlsAction } from './CameraControls';
|
|
6
|
+
export { Controls } from './Controls';
|
|
7
|
+
export { useCameraAPI, useCameraControls, useCameraGesture, useCameraMoveEnd, useCameraState, useControlsEffect, } from './hooks';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Box, type Point2Like } from '@expofp/geometry';
|
|
2
|
+
import { type PerspectiveCamera } from 'three';
|
|
3
|
+
import { type Space } from '../space/Space';
|
|
4
|
+
/**
|
|
5
|
+
* The part of the canvas a target is framed inside, in **device** pixels, as the camera's own
|
|
6
|
+
* framing measures it.
|
|
7
|
+
*
|
|
8
|
+
* The offset is from the drawing buffer's centre rather than from its corner, because that is the
|
|
9
|
+
* only thing the fit does with it — a region centred on the canvas frames exactly as no region at
|
|
10
|
+
* all, and says so by being all zeroes.
|
|
11
|
+
*/
|
|
12
|
+
export interface FramingRegion {
|
|
13
|
+
/** How wide the region is. */
|
|
14
|
+
width: number;
|
|
15
|
+
/** How tall it is. */
|
|
16
|
+
height: number;
|
|
17
|
+
/** How far its centre sits to the right of the buffer's centre; negative for left. */
|
|
18
|
+
offsetX: number;
|
|
19
|
+
/** How far below; negative for above. */
|
|
20
|
+
offsetY: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What a camera's projection was offset by — three's own `camera.view`, which `VisibleRect` is what
|
|
24
|
+
* writes. Aliased rather than restated so the two can never drift.
|
|
25
|
+
*/
|
|
26
|
+
export type ViewOffset = PerspectiveCamera['view'];
|
|
27
|
+
/** How {@link fitBox} should frame its target. */
|
|
28
|
+
export interface FitOptions {
|
|
29
|
+
/** The target, in plan units. */
|
|
30
|
+
target: Box;
|
|
31
|
+
/** The scene's space, which takes the target into world units. */
|
|
32
|
+
space: Space;
|
|
33
|
+
/** How far the plan is turned, in radians — the camera's azimuth, negated. */
|
|
34
|
+
bearing: number;
|
|
35
|
+
/** The region to frame the target inside. */
|
|
36
|
+
region: FramingRegion;
|
|
37
|
+
/** Fraction of the region left as margin on each side; `0.25` spends half of it. */
|
|
38
|
+
padding?: number;
|
|
39
|
+
/** The closest the fit may bring the plan, as a zoom factor. */
|
|
40
|
+
maxZoom?: number;
|
|
41
|
+
}
|
|
42
|
+
/** Where the camera has to be for a target to fill its region. */
|
|
43
|
+
export interface Fit {
|
|
44
|
+
/** Where the camera should look, in world units. */
|
|
45
|
+
center: Point2Like;
|
|
46
|
+
/** The zoom factor that frames the target. */
|
|
47
|
+
zoom: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Where the camera has to be for a box of the plan to fill a region of the canvas.
|
|
51
|
+
*
|
|
52
|
+
* Pure arithmetic, kept out of the hook so the part that can be wrong is the part that can be
|
|
53
|
+
* tested. It rests on the one invariant the viewbox fit exists to establish — at zoom factor `1` one
|
|
54
|
+
* world unit is one device pixel — which is what lets a world size and a pixel size be divided by
|
|
55
|
+
* each other below.
|
|
56
|
+
*
|
|
57
|
+
* The target is measured **in the camera's own frame**: its world box is turned about its centre by
|
|
58
|
+
* the bearing before its bounds are taken, so a plan the user has rolled frames the box they can see
|
|
59
|
+
* rather than its axis-aligned shadow. Turning about the centre is what leaves the centre alone,
|
|
60
|
+
* which is why only the size of that box is read.
|
|
61
|
+
*
|
|
62
|
+
* The pan target then picks up the region's offset from the buffer's centre, which is what lands the
|
|
63
|
+
* box in the middle of *that region* rather than in the middle of the canvas. The offset is divided
|
|
64
|
+
* by the zoom because it arrives as a distance in pixels and has to leave as one in world units, and
|
|
65
|
+
* turned by the bearing for the same reason the box was.
|
|
66
|
+
* @param options - The target, and how to frame it.
|
|
67
|
+
* @returns The framing, or `undefined` for a target with no area, which nothing can be fitted to.
|
|
68
|
+
*/
|
|
69
|
+
export declare function fitBox(options: FitOptions): Fit | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* The region a caller named, in the coordinates the fit works in.
|
|
72
|
+
*
|
|
73
|
+
* A `within` rect is given in css pixels of the **canvas**, which is where a host's own panels are.
|
|
74
|
+
* The fit works in the camera's framing instead — the canvas as it would be with no visible rect —
|
|
75
|
+
* so a rect has to come back through whatever `VisibleRect` put on the projection: a uniform scale
|
|
76
|
+
* about the rect's centre, and nothing else. With no view offset that reduces to a change of origin
|
|
77
|
+
* and a multiplication by the device pixel ratio.
|
|
78
|
+
*
|
|
79
|
+
* This is the one place in the renderer where a region of the canvas and the camera's framing meet,
|
|
80
|
+
* and it is the method whose whole job is relating the two. Everything else stays clear of it.
|
|
81
|
+
* @param within - The region, in css pixels from the canvas's top-left corner; omitted means all of it.
|
|
82
|
+
* @param width - The canvas width in css pixels.
|
|
83
|
+
* @param height - The canvas height in css pixels.
|
|
84
|
+
* @param dpr - Device pixels per css pixel.
|
|
85
|
+
* @param view - The camera's view offset, when one is in force.
|
|
86
|
+
* @returns The region in device pixels, offset from the buffer's centre.
|
|
87
|
+
*/
|
|
88
|
+
export declare function framingRegion(within: Box | undefined, width: number, height: number, dpr: number, view?: ViewOffset | null): FramingRegion;
|
|
89
|
+
//# sourceMappingURL=zoom-to.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Box, Point, rectBounds } from '@expofp/geometry';
|
|
2
|
+
/**
|
|
3
|
+
* Where the camera has to be for a box of the plan to fill a region of the canvas.
|
|
4
|
+
*
|
|
5
|
+
* Pure arithmetic, kept out of the hook so the part that can be wrong is the part that can be
|
|
6
|
+
* tested. It rests on the one invariant the viewbox fit exists to establish — at zoom factor `1` one
|
|
7
|
+
* world unit is one device pixel — which is what lets a world size and a pixel size be divided by
|
|
8
|
+
* each other below.
|
|
9
|
+
*
|
|
10
|
+
* The target is measured **in the camera's own frame**: its world box is turned about its centre by
|
|
11
|
+
* the bearing before its bounds are taken, so a plan the user has rolled frames the box they can see
|
|
12
|
+
* rather than its axis-aligned shadow. Turning about the centre is what leaves the centre alone,
|
|
13
|
+
* which is why only the size of that box is read.
|
|
14
|
+
*
|
|
15
|
+
* The pan target then picks up the region's offset from the buffer's centre, which is what lands the
|
|
16
|
+
* box in the middle of *that region* rather than in the middle of the canvas. The offset is divided
|
|
17
|
+
* by the zoom because it arrives as a distance in pixels and has to leave as one in world units, and
|
|
18
|
+
* turned by the bearing for the same reason the box was.
|
|
19
|
+
* @param options - The target, and how to frame it.
|
|
20
|
+
* @returns The framing, or `undefined` for a target with no area, which nothing can be fitted to.
|
|
21
|
+
*/
|
|
22
|
+
export function fitBox(options) {
|
|
23
|
+
const { target, space, bearing, region, padding = 0, maxZoom } = options;
|
|
24
|
+
// The bounds of the target once the plan is turned — geometry's own rect maths, given the world
|
|
25
|
+
// box as a rect with a rotation. Turning is about the centre, so the centre below is still the
|
|
26
|
+
// target's and only the extent has changed.
|
|
27
|
+
const world = worldBounds(target, space);
|
|
28
|
+
const source = rectBounds({ center: world.center, size: world.size, rotation: bearing });
|
|
29
|
+
const { width: sourceWidth, height: sourceHeight } = source.size;
|
|
30
|
+
if (sourceWidth <= 0 || sourceHeight <= 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (region.width <= 0 || region.height <= 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
const margin = 1 - 2 * padding;
|
|
35
|
+
const fitted = Math.min((region.width * margin) / sourceWidth, (region.height * margin) / sourceHeight);
|
|
36
|
+
const zoom = maxZoom === undefined ? fitted : Math.min(fitted, maxZoom);
|
|
37
|
+
if (!(zoom > 0))
|
|
38
|
+
return undefined;
|
|
39
|
+
const pan = new Point(-region.offsetX / zoom, -region.offsetY / zoom).rotate(bearing, ORIGIN);
|
|
40
|
+
return { center: { x: source.center.x + pan.x, y: source.center.y + pan.y }, zoom };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The region a caller named, in the coordinates the fit works in.
|
|
44
|
+
*
|
|
45
|
+
* A `within` rect is given in css pixels of the **canvas**, which is where a host's own panels are.
|
|
46
|
+
* The fit works in the camera's framing instead — the canvas as it would be with no visible rect —
|
|
47
|
+
* so a rect has to come back through whatever `VisibleRect` put on the projection: a uniform scale
|
|
48
|
+
* about the rect's centre, and nothing else. With no view offset that reduces to a change of origin
|
|
49
|
+
* and a multiplication by the device pixel ratio.
|
|
50
|
+
*
|
|
51
|
+
* This is the one place in the renderer where a region of the canvas and the camera's framing meet,
|
|
52
|
+
* and it is the method whose whole job is relating the two. Everything else stays clear of it.
|
|
53
|
+
* @param within - The region, in css pixels from the canvas's top-left corner; omitted means all of it.
|
|
54
|
+
* @param width - The canvas width in css pixels.
|
|
55
|
+
* @param height - The canvas height in css pixels.
|
|
56
|
+
* @param dpr - Device pixels per css pixel.
|
|
57
|
+
* @param view - The camera's view offset, when one is in force.
|
|
58
|
+
* @returns The region in device pixels, offset from the buffer's centre.
|
|
59
|
+
*/
|
|
60
|
+
export function framingRegion(within, width, height, dpr, view) {
|
|
61
|
+
if (!within) {
|
|
62
|
+
return { width: width * dpr, height: height * dpr, offsetX: 0, offsetY: 0 };
|
|
63
|
+
}
|
|
64
|
+
// What the framing was scaled by, and where its centre landed — read straight back off the
|
|
65
|
+
// parameters `VisibleRect` handed `setViewOffset`.
|
|
66
|
+
const scale = view?.enabled ? view.fullWidth / view.width : 1;
|
|
67
|
+
const landingX = view?.enabled ? view.fullWidth / 2 - view.offsetX : width / 2;
|
|
68
|
+
const landingY = view?.enabled ? view.fullHeight / 2 - view.offsetY : height / 2;
|
|
69
|
+
return {
|
|
70
|
+
width: (within.size.width / scale) * dpr,
|
|
71
|
+
height: (within.size.height / scale) * dpr,
|
|
72
|
+
offsetX: ((within.center.x - landingX) / scale) * dpr,
|
|
73
|
+
offsetY: ((within.center.y - landingY) / scale) * dpr,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The target's bounds in world space.
|
|
78
|
+
*
|
|
79
|
+
* Two corners are the whole box: the plan-to-world mapping scales and translates, so opposite
|
|
80
|
+
* corners stay opposite. `fromMinMax` normalizes them, so a transform that mirrors an axis and
|
|
81
|
+
* hands them over the other way round still comes out right.
|
|
82
|
+
* @param target - The target, in plan units.
|
|
83
|
+
* @param space - The scene's space.
|
|
84
|
+
* @returns The bounds, in world units.
|
|
85
|
+
*/
|
|
86
|
+
function worldBounds(target, space) {
|
|
87
|
+
const min = space.planToWorld({ ...target.min, z: 0 });
|
|
88
|
+
const max = space.planToWorld({ ...target.max, z: 0 });
|
|
89
|
+
return Box.fromMinMax(min, max);
|
|
90
|
+
}
|
|
91
|
+
const ORIGIN = { x: 0, y: 0 };
|