@expofp/renderer 0.0.0-experimental.2d6c681
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,26 @@
|
|
|
1
|
+
import { type Camera, type OrthographicCamera, type PerspectiveCamera } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a camera projects perspectively.
|
|
4
|
+
*
|
|
5
|
+
* Three's own `isPerspectiveCamera` brand rather than `instanceof`, so a camera from a second copy
|
|
6
|
+
* of three — a host's, under external mode — answers the same as one of ours.
|
|
7
|
+
* @param camera - The camera to test.
|
|
8
|
+
* @returns Whether it is a perspective camera.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isPerspective(camera: Camera): camera is PerspectiveCamera;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a camera projects orthographically.
|
|
13
|
+
* @param camera - The camera to test.
|
|
14
|
+
* @returns Whether it is an orthographic camera.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isOrthographic(camera: Camera): camera is OrthographicCamera;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a camera derives its own projection from a frustum, rather than carrying one handed to it.
|
|
19
|
+
*
|
|
20
|
+
* The question three's `Raycaster.setFromCamera` asks before it refuses a camera, and the one
|
|
21
|
+
* `Camera.tsx` and `VisibleRect.tsx` ask before writing frustum fields nothing else has.
|
|
22
|
+
* @param camera - The camera to test.
|
|
23
|
+
* @returns Whether it projects either standard way.
|
|
24
|
+
*/
|
|
25
|
+
export declare function projectsFromFrustum(camera: Camera): boolean;
|
|
26
|
+
//# sourceMappingURL=camera-kind.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a camera projects perspectively.
|
|
3
|
+
*
|
|
4
|
+
* Three's own `isPerspectiveCamera` brand rather than `instanceof`, so a camera from a second copy
|
|
5
|
+
* of three — a host's, under external mode — answers the same as one of ours.
|
|
6
|
+
* @param camera - The camera to test.
|
|
7
|
+
* @returns Whether it is a perspective camera.
|
|
8
|
+
*/
|
|
9
|
+
export function isPerspective(camera) {
|
|
10
|
+
return camera.isPerspectiveCamera === true;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Whether a camera projects orthographically.
|
|
14
|
+
* @param camera - The camera to test.
|
|
15
|
+
* @returns Whether it is an orthographic camera.
|
|
16
|
+
*/
|
|
17
|
+
export function isOrthographic(camera) {
|
|
18
|
+
return camera.isOrthographicCamera === true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether a camera derives its own projection from a frustum, rather than carrying one handed to it.
|
|
22
|
+
*
|
|
23
|
+
* The question three's `Raycaster.setFromCamera` asks before it refuses a camera, and the one
|
|
24
|
+
* `Camera.tsx` and `VisibleRect.tsx` ask before writing frustum fields nothing else has.
|
|
25
|
+
* @param camera - The camera to test.
|
|
26
|
+
* @returns Whether it projects either standard way.
|
|
27
|
+
*/
|
|
28
|
+
export function projectsFromFrustum(camera) {
|
|
29
|
+
return isPerspective(camera) || isOrthographic(camera);
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** How large the canvas is, and how densely it draws. */
|
|
2
|
+
export interface CanvasSize {
|
|
3
|
+
/** Width in css pixels. */
|
|
4
|
+
readonly width: number;
|
|
5
|
+
/** Height in css pixels. */
|
|
6
|
+
readonly height: number;
|
|
7
|
+
/** Device pixels per css pixel. Multiply by it for drawing-buffer sizes. */
|
|
8
|
+
readonly dpr: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The canvas's size, subscribed to one number at a time.
|
|
12
|
+
*
|
|
13
|
+
* Never read `state.size` as an object. R3F re-runs its canvas configuration on every render of the
|
|
14
|
+
* `<Canvas>` element and replaces that object as it goes, dimensions unchanged or not — so a
|
|
15
|
+
* component selecting it re-renders on renders that changed nothing, and anything memoized against
|
|
16
|
+
* it is rebuilt. Selecting the numbers is immune, because the numbers only move when the canvas
|
|
17
|
+
* really does.
|
|
18
|
+
*
|
|
19
|
+
* The returned object is a fresh literal, which costs nothing: it is not what the subscription
|
|
20
|
+
* compares, and the React Compiler keeps its identity stable across renders where the numbers hold.
|
|
21
|
+
* @returns The canvas's css size and device pixel ratio.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useCanvasSize(): CanvasSize;
|
|
24
|
+
//# sourceMappingURL=canvas-size.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber';
|
|
2
|
+
/**
|
|
3
|
+
* The canvas's size, subscribed to one number at a time.
|
|
4
|
+
*
|
|
5
|
+
* Never read `state.size` as an object. R3F re-runs its canvas configuration on every render of the
|
|
6
|
+
* `<Canvas>` element and replaces that object as it goes, dimensions unchanged or not — so a
|
|
7
|
+
* component selecting it re-renders on renders that changed nothing, and anything memoized against
|
|
8
|
+
* it is rebuilt. Selecting the numbers is immune, because the numbers only move when the canvas
|
|
9
|
+
* really does.
|
|
10
|
+
*
|
|
11
|
+
* The returned object is a fresh literal, which costs nothing: it is not what the subscription
|
|
12
|
+
* compares, and the React Compiler keeps its identity stable across renders where the numbers hold.
|
|
13
|
+
* @returns The canvas's css size and device pixel ratio.
|
|
14
|
+
*/
|
|
15
|
+
export function useCanvasSize() {
|
|
16
|
+
const width = useThree((state) => state.size.width);
|
|
17
|
+
const height = useThree((state) => state.size.height);
|
|
18
|
+
const dpr = useThree((state) => state.viewport.dpr);
|
|
19
|
+
return { width, height, dpr };
|
|
20
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The steepest the plan may **ever** be pitched away from face-on, in degrees.
|
|
3
|
+
*
|
|
4
|
+
* A hard cap, not a setting. The near plane below is sized against this angle: pitching the camera
|
|
5
|
+
* foreshortens an elevation along the view axis, and this is the steepest view the depth guarantee
|
|
6
|
+
* must survive, so a steeper view is one it never allowed for. `Camera` clamps `maxPitch` to it,
|
|
7
|
+
* and defaults to it.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately **not** what the polygon offsets in `objects/materials` are sized against, though the
|
|
10
|
+
* imperative renderer's were (`⌈tan(maxPolarAngle)⌉`): a slope-scaled `polygonOffsetFactor` reads
|
|
11
|
+
* the actual slope per draw, so no worst-case angle enters that calculation — see `sharedParams`.
|
|
12
|
+
*
|
|
13
|
+
* In degrees, like every pitch the renderer's API talks about; the cosine the arithmetic actually
|
|
14
|
+
* wants is {@link MIN_PITCH_COSINE}, so the conversion happens once, here, and nowhere else.
|
|
15
|
+
*
|
|
16
|
+
* The imperative renderer's own limit, and the angle its depth guarantee was stated against.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_PITCH = 85;
|
|
19
|
+
/**
|
|
20
|
+
* The near clipping plane that keeps depth precision at the plan constant as the camera dollies.
|
|
21
|
+
*
|
|
22
|
+
* Two constraints set it, and the larger wins so precision is never traded away for headroom:
|
|
23
|
+
*
|
|
24
|
+
* - **Headroom.** `distance × (1 − HEADROOM_RATIO)`. Dominates at close and default zoom, where it
|
|
25
|
+
* keeps the near plane a fixed fraction of the way to the plan and precision is already better
|
|
26
|
+
* than asked for.
|
|
27
|
+
* - **Precision.** `distance² / (levels × precision)`, the distance a depth buffer of that many
|
|
28
|
+
* levels can still resolve at the plan. Dominates far out, where it buys the guarantee back at
|
|
29
|
+
* the cost of some headroom.
|
|
30
|
+
*
|
|
31
|
+
* The guarantee is stated in **plan** units — see {@link ELEVATION_PRECISION} — so it is scaled
|
|
32
|
+
* into world units before the depth buffer is asked about it. The imperative renderer did not do
|
|
33
|
+
* that: it applied the plan-unit constant to world distances, which quietly loosened the guarantee
|
|
34
|
+
* on any plan drawn smaller than world scale. On a 60 000-unit plan in a 1600-pixel buffer that is a
|
|
35
|
+
* factor of 2.7 at default zoom, enough for two surfaces separated by exactly the documented minimum
|
|
36
|
+
* to z-fight.
|
|
37
|
+
*
|
|
38
|
+
* Sized against {@link MAX_PITCH} rather than against whatever pitch a scene currently allows, which
|
|
39
|
+
* is the conservative choice on purpose: the worst case is the only one that is always true, and a
|
|
40
|
+
* near plane computed from the pitch *in force* would have to be recomputed whenever that moved and
|
|
41
|
+
* would silently weaken the guarantee wherever nothing was enforcing a limit at all. What it costs
|
|
42
|
+
* is headroom, and only in the regime where {@link MIN_HEADROOM_RATIO} has already capped it.
|
|
43
|
+
* @param distance - Camera distance to the plan, in world units.
|
|
44
|
+
* @param scale - World units per plan unit, from the scene's {@link Space}.
|
|
45
|
+
* @returns The near plane in world units, or `undefined` while the camera sits on the plan.
|
|
46
|
+
*/
|
|
47
|
+
export declare function nearPlane(distance: number, scale: number): number | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The far plane for a camera that may pull back that far, with a factor of two spare so content
|
|
50
|
+
* behind the plan — anything at a negative elevation — is not clipped at full zoom-out.
|
|
51
|
+
* @param maxDistance - The furthest the camera may get from the plan, in world units.
|
|
52
|
+
* @returns The far plane in world units.
|
|
53
|
+
*/
|
|
54
|
+
export declare function farPlane(maxDistance: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* {@link ELEVATION_PRECISION} in the world units everything downstream of the scene's space works
|
|
57
|
+
* in: the least separation at which two surfaces still draw in a defined order.
|
|
58
|
+
*
|
|
59
|
+
* Every reader of the constant needs it this way round, which is why the conversion lives here
|
|
60
|
+
* rather than at each of them. The near plane sizes the depth buffer against it, `occludeHits` keeps
|
|
61
|
+
* a hit that near behind an occluder because the occluder is drawn *on* it rather than in front of
|
|
62
|
+
* it, and `orderCoplanarHits` treats two hits that near as one depth the buffer never separated — so
|
|
63
|
+
* a tuning of the constant moves picking and drawing together, which is the only way the two can
|
|
64
|
+
* agree.
|
|
65
|
+
* @param scale - World units per plan unit, from the scene's {@link Space}.
|
|
66
|
+
* @returns The separation in world units.
|
|
67
|
+
*/
|
|
68
|
+
export declare function depthSeparation(scale: number): number;
|
|
69
|
+
//# sourceMappingURL=clipping.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the camera's frustum begins and ends.
|
|
3
|
+
*
|
|
4
|
+
* Both planes answer the same question from opposite sides — what the camera can still draw — and
|
|
5
|
+
* they are set on different clocks: the near plane follows the camera's distance, every frame it
|
|
6
|
+
* moves, because that is what depth precision at the plan depends on; the far plane is fixed by how
|
|
7
|
+
* far the zoom bounds let the camera retreat, so it changes only when those do.
|
|
8
|
+
*/
|
|
9
|
+
import { degToRad } from '@expofp/geometry';
|
|
10
|
+
/**
|
|
11
|
+
* The steepest the plan may **ever** be pitched away from face-on, in degrees.
|
|
12
|
+
*
|
|
13
|
+
* A hard cap, not a setting. The near plane below is sized against this angle: pitching the camera
|
|
14
|
+
* foreshortens an elevation along the view axis, and this is the steepest view the depth guarantee
|
|
15
|
+
* must survive, so a steeper view is one it never allowed for. `Camera` clamps `maxPitch` to it,
|
|
16
|
+
* and defaults to it.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately **not** what the polygon offsets in `objects/materials` are sized against, though the
|
|
19
|
+
* imperative renderer's were (`⌈tan(maxPolarAngle)⌉`): a slope-scaled `polygonOffsetFactor` reads
|
|
20
|
+
* the actual slope per draw, so no worst-case angle enters that calculation — see `sharedParams`.
|
|
21
|
+
*
|
|
22
|
+
* In degrees, like every pitch the renderer's API talks about; the cosine the arithmetic actually
|
|
23
|
+
* wants is {@link MIN_PITCH_COSINE}, so the conversion happens once, here, and nowhere else.
|
|
24
|
+
*
|
|
25
|
+
* The imperative renderer's own limit, and the angle its depth guarantee was stated against.
|
|
26
|
+
*/
|
|
27
|
+
export const MAX_PITCH = 85;
|
|
28
|
+
/**
|
|
29
|
+
* The near clipping plane that keeps depth precision at the plan constant as the camera dollies.
|
|
30
|
+
*
|
|
31
|
+
* Two constraints set it, and the larger wins so precision is never traded away for headroom:
|
|
32
|
+
*
|
|
33
|
+
* - **Headroom.** `distance × (1 − HEADROOM_RATIO)`. Dominates at close and default zoom, where it
|
|
34
|
+
* keeps the near plane a fixed fraction of the way to the plan and precision is already better
|
|
35
|
+
* than asked for.
|
|
36
|
+
* - **Precision.** `distance² / (levels × precision)`, the distance a depth buffer of that many
|
|
37
|
+
* levels can still resolve at the plan. Dominates far out, where it buys the guarantee back at
|
|
38
|
+
* the cost of some headroom.
|
|
39
|
+
*
|
|
40
|
+
* The guarantee is stated in **plan** units — see {@link ELEVATION_PRECISION} — so it is scaled
|
|
41
|
+
* into world units before the depth buffer is asked about it. The imperative renderer did not do
|
|
42
|
+
* that: it applied the plan-unit constant to world distances, which quietly loosened the guarantee
|
|
43
|
+
* on any plan drawn smaller than world scale. On a 60 000-unit plan in a 1600-pixel buffer that is a
|
|
44
|
+
* factor of 2.7 at default zoom, enough for two surfaces separated by exactly the documented minimum
|
|
45
|
+
* to z-fight.
|
|
46
|
+
*
|
|
47
|
+
* Sized against {@link MAX_PITCH} rather than against whatever pitch a scene currently allows, which
|
|
48
|
+
* is the conservative choice on purpose: the worst case is the only one that is always true, and a
|
|
49
|
+
* near plane computed from the pitch *in force* would have to be recomputed whenever that moved and
|
|
50
|
+
* would silently weaken the guarantee wherever nothing was enforcing a limit at all. What it costs
|
|
51
|
+
* is headroom, and only in the regime where {@link MIN_HEADROOM_RATIO} has already capped it.
|
|
52
|
+
* @param distance - Camera distance to the plan, in world units.
|
|
53
|
+
* @param scale - World units per plan unit, from the scene's {@link Space}.
|
|
54
|
+
* @returns The near plane in world units, or `undefined` while the camera sits on the plan.
|
|
55
|
+
*/
|
|
56
|
+
export function nearPlane(distance, scale) {
|
|
57
|
+
if (distance <= 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
// Pitching the camera foreshortens an elevation along the view axis, so the separation the depth
|
|
60
|
+
// buffer actually sees is the plan one times the cosine.
|
|
61
|
+
const precision = depthSeparation(scale) * MIN_PITCH_COSINE;
|
|
62
|
+
const near = (distance * distance) / (DEPTH_BUFFER_LEVELS * precision);
|
|
63
|
+
return Math.min(Math.max(near, distance * (1 - HEADROOM_RATIO)), distance * (1 - MIN_HEADROOM_RATIO));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The far plane for a camera that may pull back that far, with a factor of two spare so content
|
|
67
|
+
* behind the plan — anything at a negative elevation — is not clipped at full zoom-out.
|
|
68
|
+
* @param maxDistance - The furthest the camera may get from the plan, in world units.
|
|
69
|
+
* @returns The far plane in world units.
|
|
70
|
+
*/
|
|
71
|
+
export function farPlane(maxDistance) {
|
|
72
|
+
return Math.max(maxDistance, 0.1) * 2;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* {@link ELEVATION_PRECISION} in the world units everything downstream of the scene's space works
|
|
76
|
+
* in: the least separation at which two surfaces still draw in a defined order.
|
|
77
|
+
*
|
|
78
|
+
* Every reader of the constant needs it this way round, which is why the conversion lives here
|
|
79
|
+
* rather than at each of them. The near plane sizes the depth buffer against it, `occludeHits` keeps
|
|
80
|
+
* a hit that near behind an occluder because the occluder is drawn *on* it rather than in front of
|
|
81
|
+
* it, and `orderCoplanarHits` treats two hits that near as one depth the buffer never separated — so
|
|
82
|
+
* a tuning of the constant moves picking and drawing together, which is the only way the two can
|
|
83
|
+
* agree.
|
|
84
|
+
* @param scale - World units per plan unit, from the scene's {@link Space}.
|
|
85
|
+
* @returns The separation in world units.
|
|
86
|
+
*/
|
|
87
|
+
export function depthSeparation(scale) {
|
|
88
|
+
return ELEVATION_PRECISION * scale;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Minimum z-axis separation between overlapping surfaces, in **plan** units. Picking reads it too,
|
|
92
|
+
* through {@link depthSeparation}: geometry closer than this to a hit is drawn on it rather than in
|
|
93
|
+
* front of it, so it does not occlude it, and two hits this close are one depth the buffer never
|
|
94
|
+
* ordered, so the pointer takes whichever of them draws last.
|
|
95
|
+
*
|
|
96
|
+
* **For plan data providers:** shapes sharing an x/y footprint — an elevated platform over a floor
|
|
97
|
+
* area, layered decorations at one position — must be separated by at least this much on z to be
|
|
98
|
+
* ordered correctly. No minimum horizontal separation is required: screen-pixel density and depth
|
|
99
|
+
* precision both scale with camera distance and with the scene's scale, so any separation visible on
|
|
100
|
+
* screen already has depth margin, at any viewport size, zoom, scale or camera rotation.
|
|
101
|
+
*
|
|
102
|
+
* Deliberately the renderer's own constant rather than `@expofp/geometry`'s `ELEVATION_TOLERANCE`,
|
|
103
|
+
* which happens to hold the same number. They answer different questions — geometry's is "are these
|
|
104
|
+
* two elevations the same point?", this one is "must these two elevations still draw in the right
|
|
105
|
+
* order?" — and importing it would let a tuning of the geometry predicate silently re-tune the depth
|
|
106
|
+
* buffer.
|
|
107
|
+
*
|
|
108
|
+
* Unexported: every reader wants it scaled, so {@link depthSeparation} is the way out of this module
|
|
109
|
+
* and this stays the one place the plan-unit figure is written down.
|
|
110
|
+
*/
|
|
111
|
+
const ELEVATION_PRECISION = 0.1;
|
|
112
|
+
/**
|
|
113
|
+
* Fraction of the camera's distance kept clear of the near plane at close and default zoom, so
|
|
114
|
+
* depth-buffer levels are not spent on the empty space between the camera and the plan.
|
|
115
|
+
*/
|
|
116
|
+
const HEADROOM_RATIO = 0.98;
|
|
117
|
+
/**
|
|
118
|
+
* Least headroom the precision constraint may leave. Without it a plan drawn very small — a viewbox
|
|
119
|
+
* hundreds of times the buffer — asks for a near plane beyond the camera's own target and blanks the
|
|
120
|
+
* canvas. Past this point precision degrades instead, which is visible only where two surfaces are
|
|
121
|
+
* within the documented minimum of each other.
|
|
122
|
+
*/
|
|
123
|
+
const MIN_HEADROOM_RATIO = 0.5;
|
|
124
|
+
/** 24-bit depth buffer: 2²⁴ discrete levels, which covers 99%+ of devices. */
|
|
125
|
+
const DEPTH_BUFFER_LEVELS = 2 ** 24;
|
|
126
|
+
/**
|
|
127
|
+
* How much of an elevation survives being viewed at {@link MAX_PITCH} — the least of it the depth
|
|
128
|
+
* buffer ever has to resolve, and so the figure the near plane is sized against.
|
|
129
|
+
*/
|
|
130
|
+
const MIN_PITCH_COSINE = Math.cos(degToRad(MAX_PITCH));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Camera } from 'three';
|
|
2
|
+
import { type CameraControlsImpl } from '../controls';
|
|
3
|
+
import { type Space } from './Space';
|
|
4
|
+
/**
|
|
5
|
+
* How much of the plan one CSS pixel spans, in plan units — what everything sized in pixels is
|
|
6
|
+
* authored against, and what the imperative renderer published as `ptScale`.
|
|
7
|
+
*
|
|
8
|
+
* **One number for a view that has many.** Perspective foreshortens a pitched plan, so no single
|
|
9
|
+
* factor is true across the whole screen. The one the plan is laid out against is the value where
|
|
10
|
+
* the camera is aimed ({@link aimedAt}), which is why the reading does not move with pitch — only
|
|
11
|
+
* with dolly.
|
|
12
|
+
*
|
|
13
|
+
* **Derived where the controls can say, measured where they cannot.** They hold the distance to
|
|
14
|
+
* that point, and the projection says how much world a screen spans at it: the imperative
|
|
15
|
+
* renderer's own arithmetic, for free. Without them — external mode, a bare scene — the screen row
|
|
16
|
+
* through the aim is taken onto the plan and divided by the buffer's width, which asks nothing of
|
|
17
|
+
* the camera but its matrices. That row is an iso-depth line wherever the screen's x axis lies
|
|
18
|
+
* parallel to the plan, true for this renderer's camera and a map host's alike, so the average
|
|
19
|
+
* along it is exact rather than approximate.
|
|
20
|
+
*
|
|
21
|
+
* Neither path knows a `visibleRect` exists: its framing rides in `projectionMatrix`, which one
|
|
22
|
+
* reads `m00` out of and the other projects through.
|
|
23
|
+
*
|
|
24
|
+
* **CSS pixels, not device pixels** — a plan reports the same pixel size at every device ratio. The
|
|
25
|
+
* renderer's public surface is CSS pixels, because a caller authors in CSS pixels and the ratio of
|
|
26
|
+
* the drawing buffer is the renderer's own business; the caller hands over the canvas's width in
|
|
27
|
+
* CSS pixels and the ratio never enters here.
|
|
28
|
+
* @param camera - The camera in use.
|
|
29
|
+
* @param space - The scene's space.
|
|
30
|
+
* @param cssWidth - The canvas's width in CSS pixels.
|
|
31
|
+
* @param controls - The camera controls, when mounted; they own the distance to the target.
|
|
32
|
+
* @returns Plan units per CSS pixel, or `undefined` while the camera misses the plan or the canvas
|
|
33
|
+
* has no width.
|
|
34
|
+
*/
|
|
35
|
+
export declare function measurePixelSize(camera: Camera, space: Space, cssWidth: number, controls?: CameraControlsImpl): number | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Which way the plan is turned, in radians clockwise from north — {@link measurePixelSize}'s
|
|
38
|
+
* counterpart on the roll axis, split the same way.
|
|
39
|
+
*
|
|
40
|
+
* The controls hold an azimuth, so where they exist that is the whole answer. Without them it is
|
|
41
|
+
* the screen direction of the plan's own x axis at {@link aimedAt}, read off the matrix that takes
|
|
42
|
+
* the plan to clip space: the plan's x axis is that matrix's first column, and a screen direction is
|
|
43
|
+
* what its clip image becomes once the perspective divide is differentiated through. Under an
|
|
44
|
+
* external host this composes the host's bearing with the static transform's own rotation, which is
|
|
45
|
+
* the venue-relative angle the imperative path fed its roll handlers by hand.
|
|
46
|
+
*
|
|
47
|
+
* A direction rather than the angle between two projected points is what removes the probe the
|
|
48
|
+
* imperative renderer used, and with it the question of how far apart to place it.
|
|
49
|
+
* @param camera - The camera in use.
|
|
50
|
+
* @param space - The scene's space.
|
|
51
|
+
* @param cssWidth - The canvas's width in CSS pixels.
|
|
52
|
+
* @param cssHeight - Its height in CSS pixels, which weights the two NDC axes back into pixels.
|
|
53
|
+
* @param controls - The camera controls, when mounted; they hold the angle outright.
|
|
54
|
+
* @returns The roll in `[0, 2π)`, or `undefined` while the camera misses the plan.
|
|
55
|
+
*/
|
|
56
|
+
export declare function measureRoll(camera: Camera, space: Space, cssWidth: number, cssHeight: number, controls?: CameraControlsImpl): number | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Whether a new pixel size differs enough from the last one to re-lay out on.
|
|
59
|
+
*
|
|
60
|
+
* Absolute rather than relative, and the same `1e-4` the imperative renderer compared with, so the
|
|
61
|
+
* handlers ported onto this API keep firing at the zoom steps they were tuned against.
|
|
62
|
+
* @param pixelSize - The new value.
|
|
63
|
+
* @param previous - The value last laid out at.
|
|
64
|
+
* @returns Whether the change is worth acting on.
|
|
65
|
+
*/
|
|
66
|
+
export declare function pixelSizeMoved(pixelSize: number, previous: number): boolean;
|
|
67
|
+
//# sourceMappingURL=layout-measure.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Point } from '@expofp/geometry';
|
|
2
|
+
import { Matrix4, Ray, Vector2, Vector4 } from 'three';
|
|
3
|
+
import { bearing } from '../controls';
|
|
4
|
+
import { euclideanModulo } from '../utils/math';
|
|
5
|
+
import { isOrthographic, isPerspective } from './camera-kind';
|
|
6
|
+
/**
|
|
7
|
+
* How much of the plan one CSS pixel spans, in plan units — what everything sized in pixels is
|
|
8
|
+
* authored against, and what the imperative renderer published as `ptScale`.
|
|
9
|
+
*
|
|
10
|
+
* **One number for a view that has many.** Perspective foreshortens a pitched plan, so no single
|
|
11
|
+
* factor is true across the whole screen. The one the plan is laid out against is the value where
|
|
12
|
+
* the camera is aimed ({@link aimedAt}), which is why the reading does not move with pitch — only
|
|
13
|
+
* with dolly.
|
|
14
|
+
*
|
|
15
|
+
* **Derived where the controls can say, measured where they cannot.** They hold the distance to
|
|
16
|
+
* that point, and the projection says how much world a screen spans at it: the imperative
|
|
17
|
+
* renderer's own arithmetic, for free. Without them — external mode, a bare scene — the screen row
|
|
18
|
+
* through the aim is taken onto the plan and divided by the buffer's width, which asks nothing of
|
|
19
|
+
* the camera but its matrices. That row is an iso-depth line wherever the screen's x axis lies
|
|
20
|
+
* parallel to the plan, true for this renderer's camera and a map host's alike, so the average
|
|
21
|
+
* along it is exact rather than approximate.
|
|
22
|
+
*
|
|
23
|
+
* Neither path knows a `visibleRect` exists: its framing rides in `projectionMatrix`, which one
|
|
24
|
+
* reads `m00` out of and the other projects through.
|
|
25
|
+
*
|
|
26
|
+
* **CSS pixels, not device pixels** — a plan reports the same pixel size at every device ratio. The
|
|
27
|
+
* renderer's public surface is CSS pixels, because a caller authors in CSS pixels and the ratio of
|
|
28
|
+
* the drawing buffer is the renderer's own business; the caller hands over the canvas's width in
|
|
29
|
+
* CSS pixels and the ratio never enters here.
|
|
30
|
+
* @param camera - The camera in use.
|
|
31
|
+
* @param space - The scene's space.
|
|
32
|
+
* @param cssWidth - The canvas's width in CSS pixels.
|
|
33
|
+
* @param controls - The camera controls, when mounted; they own the distance to the target.
|
|
34
|
+
* @returns Plan units per CSS pixel, or `undefined` while the camera misses the plan or the canvas
|
|
35
|
+
* has no width.
|
|
36
|
+
*/
|
|
37
|
+
export function measurePixelSize(camera, space, cssWidth, controls) {
|
|
38
|
+
if (!(cssWidth > 0) || !(space.scale > 0))
|
|
39
|
+
return undefined;
|
|
40
|
+
if (controls) {
|
|
41
|
+
const worldWidth = visibleWorldWidth(camera, controls.distance);
|
|
42
|
+
if (!(worldWidth > 0))
|
|
43
|
+
return undefined;
|
|
44
|
+
// The camera measures the world, and only the fit says how much plan that world is.
|
|
45
|
+
return worldWidth / cssWidth / space.scale;
|
|
46
|
+
}
|
|
47
|
+
const aim = aimedAt(camera, space);
|
|
48
|
+
if (!aim)
|
|
49
|
+
return undefined;
|
|
50
|
+
// Only the row's height is wanted, so `_ndc` is free to be re-aimed along it below.
|
|
51
|
+
const rowY = space.planToNdc(aim, camera, _ndc)?.y;
|
|
52
|
+
if (rowY === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
const left = space.ndcToPlan(_ndc.set(-1, rowY), camera, _left);
|
|
55
|
+
const right = space.ndcToPlan(_ndc.set(1, rowY), camera, _right);
|
|
56
|
+
if (!left || !right)
|
|
57
|
+
return undefined;
|
|
58
|
+
const planLength = Math.hypot(right.x - left.x, right.y - left.y);
|
|
59
|
+
if (!(planLength > 0))
|
|
60
|
+
return undefined;
|
|
61
|
+
return planLength / cssWidth;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Which way the plan is turned, in radians clockwise from north — {@link measurePixelSize}'s
|
|
65
|
+
* counterpart on the roll axis, split the same way.
|
|
66
|
+
*
|
|
67
|
+
* The controls hold an azimuth, so where they exist that is the whole answer. Without them it is
|
|
68
|
+
* the screen direction of the plan's own x axis at {@link aimedAt}, read off the matrix that takes
|
|
69
|
+
* the plan to clip space: the plan's x axis is that matrix's first column, and a screen direction is
|
|
70
|
+
* what its clip image becomes once the perspective divide is differentiated through. Under an
|
|
71
|
+
* external host this composes the host's bearing with the static transform's own rotation, which is
|
|
72
|
+
* the venue-relative angle the imperative path fed its roll handlers by hand.
|
|
73
|
+
*
|
|
74
|
+
* A direction rather than the angle between two projected points is what removes the probe the
|
|
75
|
+
* imperative renderer used, and with it the question of how far apart to place it.
|
|
76
|
+
* @param camera - The camera in use.
|
|
77
|
+
* @param space - The scene's space.
|
|
78
|
+
* @param cssWidth - The canvas's width in CSS pixels.
|
|
79
|
+
* @param cssHeight - Its height in CSS pixels, which weights the two NDC axes back into pixels.
|
|
80
|
+
* @param controls - The camera controls, when mounted; they hold the angle outright.
|
|
81
|
+
* @returns The roll in `[0, 2π)`, or `undefined` while the camera misses the plan.
|
|
82
|
+
*/
|
|
83
|
+
export function measureRoll(camera, space, cssWidth, cssHeight, controls) {
|
|
84
|
+
if (controls)
|
|
85
|
+
return bearing(controls);
|
|
86
|
+
const aim = aimedAt(camera, space);
|
|
87
|
+
if (!aim)
|
|
88
|
+
return undefined;
|
|
89
|
+
_planToClip
|
|
90
|
+
.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
|
|
91
|
+
.multiply(space.matrix);
|
|
92
|
+
// Column-major, so elements 0 to 3 are the first column: the plan's x axis as a clip direction.
|
|
93
|
+
const column = _planToClip.elements;
|
|
94
|
+
const clip = _clip.set(aim.x, aim.y, 0, 1).applyMatrix4(_planToClip);
|
|
95
|
+
// d(ndc)/dx by the quotient rule, less its `1/w²` — positive, so it moves no direction. NDC is
|
|
96
|
+
// aspect-distorted, so each axis is weighted back into pixels; canvas y grows downward where NDC
|
|
97
|
+
// y grows up.
|
|
98
|
+
const dx = (column[0] * clip.w - clip.x * column[3]) * cssWidth;
|
|
99
|
+
const dy = -(column[1] * clip.w - clip.y * column[3]) * cssHeight;
|
|
100
|
+
if (dx === 0 && dy === 0)
|
|
101
|
+
return undefined;
|
|
102
|
+
// The plan turned clockwise by θ shows its east axis at screen angle -θ.
|
|
103
|
+
return euclideanModulo(-Math.atan2(dy, dx), 2 * Math.PI);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether a new pixel size differs enough from the last one to re-lay out on.
|
|
107
|
+
*
|
|
108
|
+
* Absolute rather than relative, and the same `1e-4` the imperative renderer compared with, so the
|
|
109
|
+
* handlers ported onto this API keep firing at the zoom steps they were tuned against.
|
|
110
|
+
* @param pixelSize - The new value.
|
|
111
|
+
* @param previous - The value last laid out at.
|
|
112
|
+
* @returns Whether the change is worth acting on.
|
|
113
|
+
*/
|
|
114
|
+
export function pixelSizeMoved(pixelSize, previous) {
|
|
115
|
+
return Math.abs(pixelSize - previous) >= PIXEL_SIZE_EPSILON;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Where the camera is aimed on the plan: the point its own axis crosses, which is what the controls
|
|
119
|
+
* would call their target and what the derived path's distance is measured to.
|
|
120
|
+
*
|
|
121
|
+
* Read off the camera's world matrix rather than asked of anything that might not be there —
|
|
122
|
+
* external mode's camera carries a rigid one recovered from the host's folded matrix, so its eye
|
|
123
|
+
* and direction are as true as a camera three placed itself.
|
|
124
|
+
* @param camera - The camera in use.
|
|
125
|
+
* @param space - The scene's space.
|
|
126
|
+
* @returns The plan point, or `undefined` when the camera looks along the plan and never meets it.
|
|
127
|
+
*/
|
|
128
|
+
function aimedAt(camera, space) {
|
|
129
|
+
_axis.origin.setFromMatrixPosition(camera.matrixWorld);
|
|
130
|
+
camera.getWorldDirection(_axis.direction);
|
|
131
|
+
return space.rayToPlan(_axis, _aim);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The world-space width one screen covers at a distance.
|
|
135
|
+
*
|
|
136
|
+
* Read off the projection matrix rather than rebuilt from `fov` and `zoom`: `m00` is `2·near /
|
|
137
|
+
* (right − left)` under perspective and `2 / (right − left)` under orthographic, so it holds for a
|
|
138
|
+
* projection nobody derived from a symmetric frustum — the off-centre, expanded one a `visibleRect`
|
|
139
|
+
* installs, which leaves `fov` untouched.
|
|
140
|
+
*
|
|
141
|
+
* Width where the imperative renderer took height, so both of {@link measurePixelSize}'s paths
|
|
142
|
+
* divide by the one canvas dimension they are given; for a projection whose aspect is the buffer's
|
|
143
|
+
* the two agree exactly.
|
|
144
|
+
* @param camera - The camera in use.
|
|
145
|
+
* @param distance - Distance from the camera to the point it is aimed at, in world units.
|
|
146
|
+
* @returns The width in world units, or `NaN` for a camera that projects neither way.
|
|
147
|
+
*/
|
|
148
|
+
function visibleWorldWidth(camera, distance) {
|
|
149
|
+
// Column-major: element 0 is row 0 of column 0.
|
|
150
|
+
const m00 = camera.projectionMatrix.elements[0];
|
|
151
|
+
if (!(m00 > 0))
|
|
152
|
+
return NaN;
|
|
153
|
+
// A perspective frustum widens with distance; an orthographic one covers the same width wherever
|
|
154
|
+
// it sits, which is the whole difference between the two.
|
|
155
|
+
if (isPerspective(camera))
|
|
156
|
+
return (2 * distance) / m00;
|
|
157
|
+
if (isOrthographic(camera))
|
|
158
|
+
return 2 / m00;
|
|
159
|
+
return NaN;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The change in {@link measurePixelSize} below which nothing is re-laid out. Not configurable: it
|
|
163
|
+
* is the granularity every handler's thresholds are expressed against, so a scene tuning it would
|
|
164
|
+
* be tuning every handler at once.
|
|
165
|
+
*/
|
|
166
|
+
const PIXEL_SIZE_EPSILON = 1e-4;
|
|
167
|
+
// Scratch, each filled and consumed within one call — and the two readings never overlap, so the
|
|
168
|
+
// aim they share is one point rather than two.
|
|
169
|
+
const _axis = new Ray();
|
|
170
|
+
const _aim = new Point();
|
|
171
|
+
const _left = new Point();
|
|
172
|
+
const _right = new Point();
|
|
173
|
+
const _ndc = new Vector2();
|
|
174
|
+
const _planToClip = new Matrix4();
|
|
175
|
+
const _clip = new Vector4();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { type Camera as ThreeCamera } from 'three';
|
|
2
|
+
import { type CanvasPoint, type CanvasPointLike, type PlanPoint, type PlanPointLike, type Space } from './Space';
|
|
3
|
+
/**
|
|
4
|
+
* Where things are: the two directions between the plan's own coordinates and the canvas the user
|
|
5
|
+
* points at, and the one question that rides on them.
|
|
6
|
+
*
|
|
7
|
+
* The counterpart to `CameraAPI`, and the division is the obvious one — that moves the camera, this
|
|
8
|
+
* reads where the camera has left things. Nothing here changes anything, so nothing here returns a
|
|
9
|
+
* promise: every method answers for the plan as it stands at the moment of asking.
|
|
10
|
+
*
|
|
11
|
+
* Both conversions can fail, and both failures are real rather than defensive: a tilted camera looks
|
|
12
|
+
* past the plan entirely above the horizon, and a plan point can sit behind the camera. `undefined`
|
|
13
|
+
* says so in each direction, rather than a number that would read as a position.
|
|
14
|
+
*
|
|
15
|
+
* This is the surface that replaces the imperative renderer's `viewport`, whose `canvasToSvg`
|
|
16
|
+
* covered only the first of these — and returned one point per loaded scene, leaving every caller
|
|
17
|
+
* to pick the default one out of an array.
|
|
18
|
+
*/
|
|
19
|
+
export interface ViewportAPI {
|
|
20
|
+
/**
|
|
21
|
+
* Where a point on the canvas lands on the plan.
|
|
22
|
+
*
|
|
23
|
+
* **The plan plane, not the geometry drawn on it.** A click on the roof of a raised model reports
|
|
24
|
+
* the ground point the ray carries on to, which is the floor coordinate this answers for; what the
|
|
25
|
+
* ray actually struck is a pointer event's business, and `PointerEventData.point` reports that.
|
|
26
|
+
* @param point - The canvas position, in css pixels from the canvas's top-left corner.
|
|
27
|
+
* @param out - A point to write into, for a caller converting enough of them to care. Omit it and
|
|
28
|
+
* each call allocates, which is what makes the result safe to keep.
|
|
29
|
+
* @returns The plan position, or `undefined` when the camera never meets the plan there.
|
|
30
|
+
*/
|
|
31
|
+
canvasToPlan(point: CanvasPointLike, out?: PlanPoint): PlanPoint | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Where a point on the plan lands on the canvas.
|
|
34
|
+
*
|
|
35
|
+
* **Unclamped:** a point off the side of the plan reports the position it really has, outside the
|
|
36
|
+
* canvas, which is what makes it usable for placing something that may scroll into view.
|
|
37
|
+
* @param point - The plan position; a missing `z` is read as `0`, so an elevated point projects
|
|
38
|
+
* from the height it is drawn at.
|
|
39
|
+
* @param out - A point to write into, for a caller converting enough of them to care. Omit it and
|
|
40
|
+
* each call allocates, which is what makes the result safe to keep.
|
|
41
|
+
* @returns The canvas position in css pixels, or `undefined` when the point is behind the camera.
|
|
42
|
+
*/
|
|
43
|
+
planToCanvas(point: PlanPointLike, out?: CanvasPoint): CanvasPoint | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Whether a point on the plan is on screen.
|
|
46
|
+
*
|
|
47
|
+
* **The whole canvas counts**, including any part of it a host's own UI covers: a `visibleRect`
|
|
48
|
+
* expands the frustum rather than cropping it, so the plan really is drawn edge to edge and a
|
|
49
|
+
* point behind a bottom sheet is drawn as much as one beside it. A caller asking the narrower
|
|
50
|
+
* question — can the *user* see it — tests {@link planToCanvas} against its own region.
|
|
51
|
+
* @param point - The plan position; a missing `z` is read as `0`.
|
|
52
|
+
* @returns Whether it is drawn on the canvas.
|
|
53
|
+
*/
|
|
54
|
+
isVisible(point: PlanPointLike): boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* What {@link Viewport} needs from the scene it reads.
|
|
58
|
+
*
|
|
59
|
+
* Handed over whole rather than reached for, as `CameraContext` is, and the store getter is here for
|
|
60
|
+
* the same reason: R3F may swap the camera out from under a held reference, and every conversion
|
|
61
|
+
* below has to run through the one the canvas is currently drawn with.
|
|
62
|
+
*/
|
|
63
|
+
interface ViewportContext {
|
|
64
|
+
/** Reads the canvas state, which is where the camera in use is found. */
|
|
65
|
+
get: () => {
|
|
66
|
+
camera: ThreeCamera;
|
|
67
|
+
};
|
|
68
|
+
/** The scene's space, which owns the mapping every conversion here composes. */
|
|
69
|
+
space: Space;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The scene's coordinate conversions.
|
|
73
|
+
*
|
|
74
|
+
* A hook for the same reason `useCameraAPI` is one: it resolves only inside a component mounted
|
|
75
|
+
* under `<Renderer>`, so "the plan cannot be measured before the scene exists" is enforced by where
|
|
76
|
+
* the code can live rather than by a null check every caller has to remember. A host that needs to
|
|
77
|
+
* convert coordinates from outside React publishes this object from an effect.
|
|
78
|
+
*
|
|
79
|
+
* **Nothing here subscribes to the canvas size**, and it does not need to: `Space` is refitted in
|
|
80
|
+
* place on every resize, so the object below reads the current fit through the same reference for as
|
|
81
|
+
* long as the scene lives, and the camera is read at the moment of asking. That is the reading
|
|
82
|
+
* discipline `Space` documents — and the reason it is safe here is that no render output is derived
|
|
83
|
+
* from these answers.
|
|
84
|
+
*
|
|
85
|
+
* **The returned object is new on every render, deliberately**, as `useCameraAPI`'s is: this module
|
|
86
|
+
* memoizes nothing, so a consumer that cannot re-subscribe cheaply holds it in a ref and pays that
|
|
87
|
+
* cost where the cost is understood.
|
|
88
|
+
* @returns The conversions.
|
|
89
|
+
*/
|
|
90
|
+
export declare function useViewport(): ViewportAPI;
|
|
91
|
+
/**
|
|
92
|
+
* The coordinate conversions, over whatever the scene currently holds.
|
|
93
|
+
*
|
|
94
|
+
* A class rather than an object literal because it is the renderer's published surface, and built
|
|
95
|
+
* fresh rather than mutated so that nothing here is state — every answer is composed from the space
|
|
96
|
+
* and the camera as they stand when the method is called.
|
|
97
|
+
*/
|
|
98
|
+
export declare class Viewport implements ViewportAPI {
|
|
99
|
+
private readonly context;
|
|
100
|
+
/**
|
|
101
|
+
* @param context - What the scene holds; the instance is rebuilt whenever it moves.
|
|
102
|
+
*/
|
|
103
|
+
constructor(context: ViewportContext);
|
|
104
|
+
/** @inheritdoc */
|
|
105
|
+
canvasToPlan(point: CanvasPointLike, out?: PlanPoint): PlanPoint | undefined;
|
|
106
|
+
/** @inheritdoc */
|
|
107
|
+
planToCanvas(point: PlanPointLike, out?: CanvasPoint): CanvasPoint | undefined;
|
|
108
|
+
/** @inheritdoc */
|
|
109
|
+
isVisible(point: PlanPointLike): boolean;
|
|
110
|
+
}
|
|
111
|
+
export {};
|
|
112
|
+
//# sourceMappingURL=viewport.d.ts.map
|