@expofp/renderer 3.2.5 → 3.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/dist/Renderer.d.ts +88 -0
- package/dist/Renderer.js +53 -0
- package/dist/controls/CameraControls.d.ts +102 -0
- package/dist/controls/CameraControls.js +239 -0
- package/dist/controls/camera-api.d.ts +206 -0
- package/dist/controls/camera-api.js +181 -0
- package/dist/controls/camera-move.d.ts +13 -0
- package/dist/controls/camera-move.js +18 -0
- package/dist/controls/camera-state.d.ts +40 -0
- package/dist/controls/camera-state.js +65 -0
- package/dist/controls/gestures.d.ts +70 -0
- package/dist/controls/gestures.js +84 -0
- package/dist/controls/handlers/Inertia.d.ts +111 -0
- package/dist/controls/handlers/Inertia.js +215 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +32 -0
- package/dist/controls/handlers/Pitch.d.ts +94 -0
- package/dist/controls/handlers/Pitch.js +191 -0
- package/dist/controls/handlers/Roll.d.ts +103 -0
- package/dist/controls/handlers/Roll.js +182 -0
- package/dist/controls/handlers/Zoom.d.ts +15 -0
- package/dist/controls/handlers/Zoom.js +26 -0
- package/dist/controls/index.d.ts +56 -0
- package/dist/controls/index.js +58 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +93 -0
- package/dist/core/InstanceUniforms.js +266 -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 +149 -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 +224 -0
- package/dist/core/lines/BatchedLines.d.ts +135 -0
- package/dist/core/lines/BatchedLines.js +213 -0
- package/dist/core/lines/FrustumCulling.d.ts +43 -0
- package/dist/core/lines/FrustumCulling.js +85 -0
- package/dist/core/lines/Raycasting.d.ts +44 -0
- package/dist/core/lines/Raycasting.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +115 -0
- package/dist/core/shader-source.d.ts +50 -0
- package/dist/core/shader-source.js +84 -0
- package/dist/core/text/BatchedText.d.ts +227 -0
- package/dist/core/text/BatchedText.js +467 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +194 -0
- package/dist/core/text/Text.d.ts +73 -0
- package/dist/core/text/Text.js +215 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +121 -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 +38 -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 +20 -0
- package/dist/debug/components/Gui.js +32 -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 +98 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +51 -0
- package/dist/debug/index.js +64 -0
- package/dist/events/LayoutDriver.d.ts +31 -0
- package/dist/events/LayoutDriver.js +88 -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 +104 -0
- package/dist/events/layout-targets.js +245 -0
- package/dist/events/pointer.d.ts +118 -0
- package/dist/events/pointer.js +168 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +178 -0
- package/dist/external/external-camera.d.ts +17 -0
- package/dist/external/external-camera.js +82 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +11 -783
- package/dist/index.js +12 -8031
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/Lighting.d.ts +20 -0
- package/dist/objects/Lighting.js +85 -0
- package/dist/objects/containers/SlotContainer.d.ts +266 -0
- package/dist/objects/containers/SlotContainer.js +251 -0
- package/dist/objects/containers/hooks.d.ts +46 -0
- package/dist/objects/containers/hooks.js +118 -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 +23 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +83 -0
- package/dist/objects/images/Images.d.ts +37 -0
- package/dist/objects/images/Images.js +204 -0
- package/dist/objects/images/atlas-packer.d.ts +128 -0
- package/dist/objects/images/atlas-packer.js +315 -0
- package/dist/objects/images/image-container.d.ts +51 -0
- package/dist/objects/images/image-container.js +70 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/layer/Layer.d.ts +27 -0
- package/dist/objects/layer/Layer.js +114 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/mount-scheduler.d.ts +102 -0
- package/dist/objects/layer/mount-scheduler.js +145 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -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 +29 -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 +85 -0
- package/dist/objects/materials/ColorMaterial.js +89 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +72 -0
- package/dist/objects/materials/ModelMaterial.d.ts +41 -0
- package/dist/objects/materials/ModelMaterial.js +84 -0
- package/dist/objects/materials/TextureMaterial.d.ts +30 -0
- package/dist/objects/materials/TextureMaterial.js +29 -0
- package/dist/objects/materials/dim.d.ts +123 -0
- package/dist/objects/materials/dim.js +213 -0
- package/dist/objects/materials/shared.d.ts +6 -0
- package/dist/objects/materials/shared.js +51 -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 +177 -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 +139 -0
- package/dist/objects/models/model-container.js +227 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +40 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +120 -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 +165 -0
- package/dist/objects/shapes/shape-container.js +272 -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 +41 -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 +105 -0
- package/dist/objects/text/text-container.js +206 -0
- package/dist/objects/text/text-layout.d.ts +83 -0
- package/dist/objects/text/text-layout.js +154 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +44 -0
- package/dist/space/Scene.js +112 -0
- package/dist/space/Space.d.ts +288 -0
- package/dist/space/Space.js +329 -0
- package/dist/space/VisibleRect.d.ts +72 -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 +55 -0
- package/dist/space/clipping.js +108 -0
- package/dist/space/layout-measure.d.ts +65 -0
- package/dist/space/layout-measure.js +173 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +169 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +183 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +14 -0
- package/dist/types/guards.js +42 -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 +72 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +98 -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/logging.d.ts +26 -0
- package/dist/utils/logging.js +47 -0
- package/dist/utils/math.d.ts +24 -0
- package/dist/utils/math.js +27 -0
- package/package.json +67 -19
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @expofp/renderer
|
|
2
|
+
|
|
3
|
+
Interactive floor plan renderer built on [react-three-fiber](https://r3f.docs.pmnd.rs/).
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
- **`Renderer`** (`src/Renderer.tsx`) — the public entry: mounts the R3F
|
|
8
|
+
`Canvas` and the scene (`controls/`, `lighting/`, `batch/`). Pass `debug` to
|
|
9
|
+
enable the debug overlay.
|
|
10
|
+
- **Scene topics** live in their own folders (`batch/`, `controls/`,
|
|
11
|
+
`lighting/`, …); add new rendering features as sibling topic folders.
|
|
12
|
+
- **`debug/`** — the debug overlay (lil-gui panel + stats-gl overlay), loaded
|
|
13
|
+
through a single dynamic `import()` in `Renderer`. Everything under `debug/`
|
|
14
|
+
(and its heavier deps, lil-gui + stats-gl) is code-split into one lazily
|
|
15
|
+
loaded chunk that is **never fetched when `debug` is off**. Add new debug
|
|
16
|
+
pieces to `debug/index.tsx` so they ride along in the same chunk.
|
|
17
|
+
|
|
18
|
+
## Navigation
|
|
19
|
+
|
|
20
|
+
`RendererProps` carries two different kinds of thing:
|
|
21
|
+
|
|
22
|
+
- **Camera limits** — `minZoom` / `maxZoom` / `minPitch` / `maxPitch`, declared as
|
|
23
|
+
`CameraProps` in `src/space/Camera.tsx`. They constrain the camera itself, so
|
|
24
|
+
they hold for a programmatic move as much as for a dragged one. `Camera` writes
|
|
25
|
+
them and defaults them, beside the clipping planes derived from the same
|
|
26
|
+
numbers; `maxPitch` both defaults to and is capped at `MAX_PITCH` (degrees, in
|
|
27
|
+
`clipping.ts`), the angle every depth calculation in the renderer is sized
|
|
28
|
+
against.
|
|
29
|
+
- **`controls`** — what a _user_ may do. Each handler takes `false`, `true` or
|
|
30
|
+
omitted for its defaults, or an options object.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<Renderer
|
|
34
|
+
scene={scene}
|
|
35
|
+
minZoom={0.1}
|
|
36
|
+
maxZoom={100}
|
|
37
|
+
minPitch={0}
|
|
38
|
+
maxPitch={85}
|
|
39
|
+
controls={{
|
|
40
|
+
pan: { inertia: { durationMs: 600 } },
|
|
41
|
+
zoom: true,
|
|
42
|
+
roll: { threshold: 25 },
|
|
43
|
+
pitch: false,
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pan and zoom default on; **roll and pitch default on only in a development
|
|
49
|
+
build**, so this package's Storybook and dev server have them and a production
|
|
50
|
+
plan does not until a caller asks. An explicit value always wins.
|
|
51
|
+
|
|
52
|
+
Each handler is a component under `src/controls/handlers/`, mirroring the
|
|
53
|
+
imperative renderer's `ui/handlers/`, and owns its own config type and defaults.
|
|
54
|
+
Mounting is enabling: a handler claims its mouse and touch actions on mount and
|
|
55
|
+
gives them back on unmount, and the controls are built with every action switched
|
|
56
|
+
off. To add a gesture, write a sibling component and a field on `ControlsConfig`.
|
|
57
|
+
|
|
58
|
+
`Camera` writes the limits in two effects — the pitch limits, and the framing
|
|
59
|
+
(distance limits, far plane, and the dolly that preserves zoom across a resize),
|
|
60
|
+
which is one transaction because the dolly must follow the limits.
|
|
61
|
+
|
|
62
|
+
The controls are reached two ways and no other: `useCameraControls()` reads
|
|
63
|
+
them, and `useControlsEffect(effect)` runs an effect against them once they
|
|
64
|
+
exist, handing them over as an argument. The React Compiler rejects assigning to
|
|
65
|
+
a _field_ of a hook's return value — a method call on it, or passing it to a
|
|
66
|
+
function that does the assigning, is fine — so a component with a field to write
|
|
67
|
+
either routes it through a helper or uses `useControlsEffect`. Two-finger
|
|
68
|
+
gestures come off `useGesture(type, handler)`.
|
|
69
|
+
|
|
70
|
+
## Adding GUI controls from a component
|
|
71
|
+
|
|
72
|
+
`debug/gui-context.ts` exposes `useGui()`. It returns the active lil-gui
|
|
73
|
+
instance, or `null` when the overlay is disabled or still loading — so guard on a
|
|
74
|
+
non-null value. `useGui` is imported from the tiny `gui-context` module (a
|
|
75
|
+
type-only lil-gui import), so consuming it does **not** pull lil-gui into the
|
|
76
|
+
main chunk.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
function MyThing() {
|
|
80
|
+
const gui = useGui();
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!gui) return;
|
|
83
|
+
const folder = gui.addFolder('My thing');
|
|
84
|
+
folder.add(/* … */);
|
|
85
|
+
return () => folder.destroy();
|
|
86
|
+
}, [gui]);
|
|
87
|
+
// …
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Recommended extensions
|
|
92
|
+
|
|
93
|
+
Two browser extensions make what the renderer draws inspectable:
|
|
94
|
+
|
|
95
|
+
- **[Three.js DevTools](https://chromewebstore.google.com/detail/threejs-devtools/jechbjkglifdaldbdbigibihfaclnkbo)**
|
|
96
|
+
adds a DevTools tab showing the live scene tree — the quickest way to see how
|
|
97
|
+
Defs translate into three.js objects. Hovering a mesh there highlights its
|
|
98
|
+
geometry on the plan.
|
|
99
|
+
- **[SpectorJS](https://spector.babylonjs.com/)** lists every WebGL draw call of
|
|
100
|
+
a captured frame, with the intermediate canvas state between them. It takes a
|
|
101
|
+
little coaxing: tick **Show offscreen canvas(es)** to see the canvases React
|
|
102
|
+
renders (this works inconsistently — closing and reopening the extension a
|
|
103
|
+
couple of times usually gets there), pick the canvas from the **Choose
|
|
104
|
+
canvas…** dropdown by matching its dimensions, then press the red **Record**
|
|
105
|
+
button. In debug mode the capture opens immediately, because the scene
|
|
106
|
+
re-renders every frame; without it you have to trigger a frame first, most
|
|
107
|
+
easily by panning or zooming slightly.
|
|
108
|
+
|
|
109
|
+
## Running unit tests
|
|
110
|
+
|
|
111
|
+
Run `nx test @expofp/renderer` to execute the unit tests via [Vitest](https://vitest.dev/).
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type Box } from '@expofp/geometry';
|
|
2
|
+
import { type HTMLAttributes, type ReactNode, type Ref } from 'react';
|
|
3
|
+
import { type ControlsConfig } from './controls';
|
|
4
|
+
import { type ExternalCanvasProps } from './external/ExternalCanvas';
|
|
5
|
+
import { type CameraProps } from './space/Camera';
|
|
6
|
+
import { type SceneDef } from './types/defs';
|
|
7
|
+
import { type PointerMissedEventData } from './types/events';
|
|
8
|
+
export { type ExternalCanvasProps } from './external/ExternalCanvas';
|
|
9
|
+
/**
|
|
10
|
+
* Props for {@link Renderer}. One surface for both mounts: the external trio rides along optional
|
|
11
|
+
* (the partial of {@link ExternalCanvasProps}, the way the camera limits ride along from
|
|
12
|
+
* {@link CameraProps}), and **`gl` arriving is what selects external mode** — exactly as it
|
|
13
|
+
* selected the imperative renderer's. With `gl` set, everything canvas- and camera-shaped is
|
|
14
|
+
* ignored: the host owns the canvas, the view and the frames, so the DOM attributes, `ref`,
|
|
15
|
+
* the camera limits, `controls` and `visibleRect` have nothing to configure.
|
|
16
|
+
*/
|
|
17
|
+
export interface RendererProps extends HTMLAttributes<HTMLDivElement>, CameraProps, Partial<ExternalCanvasProps> {
|
|
18
|
+
/** Ref to the underlying WebGL canvas element. Internal mode — the external canvas is the host's. */
|
|
19
|
+
ref?: Ref<HTMLCanvasElement>;
|
|
20
|
+
/**
|
|
21
|
+
* When `true`, enables the debug UI overlay — in both modes; the GUI, the readout panels and the
|
|
22
|
+
* frame stats attach to the document beside whichever canvas is drawing. The one figure external
|
|
23
|
+
* mode cannot always show is GPU memory: it needs the context created *after* the debug chunk has
|
|
24
|
+
* instrumented context creation, which a host arranges by awaiting `prepareDebug()` before
|
|
25
|
+
* building its map. Without that the memory panel shows and stays empty — visibly wrong rather
|
|
26
|
+
* than silently absent.
|
|
27
|
+
*/
|
|
28
|
+
debug?: boolean;
|
|
29
|
+
/** The scene to render. */
|
|
30
|
+
scene: SceneDef;
|
|
31
|
+
/**
|
|
32
|
+
* Components rendered inside the canvas, under the scene's space and camera. This is where a
|
|
33
|
+
* caller puts anything that has to reach the renderer from within — camera commands, event
|
|
34
|
+
* bridges — since those hooks only work below a mounted canvas.
|
|
35
|
+
*/
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
/**
|
|
38
|
+
* Soft cap, in megabytes, on the GPU memory the image atlases may consume. When the atlases would
|
|
39
|
+
* exceed it, every image source is downscaled by one shared linear factor — preserving their
|
|
40
|
+
* relative sizes, with no priority — until they fit. Canvas sources are counted at full size but
|
|
41
|
+
* never scaled (their pixels are the caller's), so a limit smaller than the canvases alone need is
|
|
42
|
+
* simply not met. May be changed at runtime: the resizable atlases re-derive their textures live,
|
|
43
|
+
* with no repack. Omitted (or `0`) means no cap, and the budget stands down entirely.
|
|
44
|
+
*/
|
|
45
|
+
memoryLimit?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Called for a click that reached no layer handler — empty background, or a def in a layer that
|
|
48
|
+
* declares none, since such a layer is never picked. The event reports where the click landed on
|
|
49
|
+
* the plan, which a click on nothing otherwise could not tell you.
|
|
50
|
+
*
|
|
51
|
+
* Clicks that *do* reach a layer go to that layer's `onClick` instead, so a caller that wants every
|
|
52
|
+
* click regardless (say, to publish plan coordinates) wires the same function in both places.
|
|
53
|
+
*/
|
|
54
|
+
onPointerMissed?: (event: PointerMissedEventData) => void;
|
|
55
|
+
/**
|
|
56
|
+
* What the user may do with the plan by mouse and touch — pan, zoom, roll, pitch.
|
|
57
|
+
*
|
|
58
|
+
* The other half of navigation from the limits this extends {@link CameraProps} for: a caller
|
|
59
|
+
* switches a gesture off without changing where the camera may go, and constrains the camera
|
|
60
|
+
* without taking a gesture away.
|
|
61
|
+
*/
|
|
62
|
+
controls?: ControlsConfig;
|
|
63
|
+
/**
|
|
64
|
+
* The part of the canvas the plan should be framed inside, in css pixels from its top-left
|
|
65
|
+
* corner — for a host whose own header, sheet or panel covers the rest.
|
|
66
|
+
*
|
|
67
|
+
* **The plan is still drawn across the whole canvas**: nothing is clipped, and the strips behind
|
|
68
|
+
* that UI show more of the plan than they would otherwise rather than being cropped away. What
|
|
69
|
+
* changes is where the viewbox lands, which is guaranteed to be inside this rect.
|
|
70
|
+
*
|
|
71
|
+
* Distinct from `zoomTo`'s `within`, which is about a single command's framing and moves nothing.
|
|
72
|
+
* Ignored for a scene that carries a `staticTransform`, where the host places the plan itself.
|
|
73
|
+
*/
|
|
74
|
+
visibleRect?: Box;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* R3F floorplan renderer. Composes **one** scene tree — space, camera, layers, debug overlay, the
|
|
78
|
+
* caller's children — and mounts it on one of two canvases: its own (an R3F `<Canvas>`, wrapped in
|
|
79
|
+
* the debug loader) or a host's, when the host's `gl` arrives and {@link ExternalCanvas} adopts its
|
|
80
|
+
* canvas, camera and frame loop instead. The tree is the same in both; what external mode drops is
|
|
81
|
+
* exactly what the host owns — the managed camera, the controls, the `visibleRect` framing — and
|
|
82
|
+
* the tree simply mounts none of them there.
|
|
83
|
+
* @param props - Renderer props; in internal mode, keys other than the renderer's own pass through
|
|
84
|
+
* to the underlying canvas.
|
|
85
|
+
* @returns The rendered canvas and scene.
|
|
86
|
+
*/
|
|
87
|
+
export declare function Renderer(props: RendererProps): ReactNode;
|
|
88
|
+
//# sourceMappingURL=Renderer.d.ts.map
|
package/dist/Renderer.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StrictMode, Suspense } from 'react';
|
|
3
|
+
import { Controls } from './controls';
|
|
4
|
+
import { CanvasWithDebug, DebugOverlay } from './debug';
|
|
5
|
+
import { PointerMissed } from './events/PointerMissed';
|
|
6
|
+
import { ExternalCanvas } from './external/ExternalCanvas';
|
|
7
|
+
import { Scene } from './space/Scene';
|
|
8
|
+
import { createLogger } from './utils/logging';
|
|
9
|
+
const logger = createLogger('renderer');
|
|
10
|
+
/**
|
|
11
|
+
* R3F floorplan renderer. Composes **one** scene tree — space, camera, layers, debug overlay, the
|
|
12
|
+
* caller's children — and mounts it on one of two canvases: its own (an R3F `<Canvas>`, wrapped in
|
|
13
|
+
* the debug loader) or a host's, when the host's `gl` arrives and {@link ExternalCanvas} adopts its
|
|
14
|
+
* canvas, camera and frame loop instead. The tree is the same in both; what external mode drops is
|
|
15
|
+
* exactly what the host owns — the managed camera, the controls, the `visibleRect` framing — and
|
|
16
|
+
* the tree simply mounts none of them there.
|
|
17
|
+
* @param props - Renderer props; in internal mode, keys other than the renderer's own pass through
|
|
18
|
+
* to the underlying canvas.
|
|
19
|
+
* @returns The rendered canvas and scene.
|
|
20
|
+
*/
|
|
21
|
+
export function Renderer(props) {
|
|
22
|
+
// Every prop of the renderer's own is pulled out here rather than read off `props` below, so none
|
|
23
|
+
// of them ride along in `canvasProps` and land on the canvas's wrapper element as unknown DOM
|
|
24
|
+
// attributes — which is what React warns about, and the reason every member of `CameraProps` has
|
|
25
|
+
// to be named here as well as reassembled below. They are passed on untouched: what an omitted
|
|
26
|
+
// one means is `Camera`'s to say, and it is the only place that says it.
|
|
27
|
+
const { debug, scene, memoryLimit, minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime, controls, visibleRect, onPointerMissed, gl, requestFrame, onFrame, children, ...canvasProps } = props;
|
|
28
|
+
const cameraProps = { minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime };
|
|
29
|
+
const external = gl !== undefined;
|
|
30
|
+
// The one tree, whichever canvas it lands on. The mount decides nothing about it; the tree
|
|
31
|
+
// leaves out what external mode has no object for — the controls here, the camera inside Scene.
|
|
32
|
+
const tree = (_jsx(DebugOverlay, { enabled: debug, children: _jsxs(Scene, { sceneDef: scene, external: external, memoryLimit: memoryLimit, visibleRect: visibleRect, ...cameraProps, children: [!external && _jsx(Controls, { config: controls }), _jsx(PointerMissed, { onPointerMissed: onPointerMissed }), children] }) }));
|
|
33
|
+
// The host's context arriving is what selects external mode. The mount swap is an element-type
|
|
34
|
+
// change, so flipping modes at runtime remounts — like `debug`, both are fixed for a renderer's
|
|
35
|
+
// lifetime in practice.
|
|
36
|
+
if (gl) {
|
|
37
|
+
if (!requestFrame || !onFrame) {
|
|
38
|
+
logger.error('external mode needs requestFrame and onFrame alongside gl; nothing will render');
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// No StrictMode here: ExternalCanvas wraps the tree it renders into the foreign root itself,
|
|
42
|
+
// and its own bootstrap effect must run once per context, not be double-invoked into churn.
|
|
43
|
+
return (_jsx(ExternalCanvas, { gl: gl, requestFrame: requestFrame, onFrame: onFrame, children: tree }));
|
|
44
|
+
}
|
|
45
|
+
return (_jsx(StrictMode, { children: _jsx(Suspense, { fallback: null, children: _jsx(CanvasWithDebug, { ...canvasProps, debugEnabled: debug,
|
|
46
|
+
// Tonemapping off: plan colors are authored values, not scene-referred light.
|
|
47
|
+
flat: true,
|
|
48
|
+
// three leaves the stencil buffer off by default. Line layers need one: it is how they keep
|
|
49
|
+
// translucent lines from darkening where they overlap (see `LineMaterial`), and without the
|
|
50
|
+
// buffer the stencil settings silently do nothing. In the MapLibre embed the context is the
|
|
51
|
+
// map's own, which already has one.
|
|
52
|
+
gl: { stencil: true }, frameloop: debug ? 'always' : 'demand', resize: { debounce: 50 }, children: tree }) }) }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type NonFunctionKeys } from '@react-three/fiber/dist/declarations/src/core/utils';
|
|
2
|
+
import { MapControls as CameraControlsImpl, type SmoothTimeOption } from 'camera-controls';
|
|
3
|
+
import { type Ref } from 'react';
|
|
4
|
+
import { type OrthographicCamera, type PerspectiveCamera } from 'three';
|
|
5
|
+
/**
|
|
6
|
+
* The scene's camera controls, once {@link Controls} has mounted them.
|
|
7
|
+
*
|
|
8
|
+
* For **reading** them, and for handing them to something else that does the writing — a gesture
|
|
9
|
+
* class, a measurement, a helper taking them as an argument. What the React Compiler rejects is
|
|
10
|
+
* assigning to a *field* of a hook's return value, which is a build error rather than a bug; a
|
|
11
|
+
* method call on it, or passing it along, is fine. A component with a write it cannot route through
|
|
12
|
+
* a function uses {@link useControlsEffect}, which hands the controls over as an argument.
|
|
13
|
+
* @returns The controls, or `undefined` until they mount.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useCameraControls(): CameraControlsImpl | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* How far the plan is turned from north: clockwise, in radians, in `[0, 2π)`.
|
|
18
|
+
*
|
|
19
|
+
* The camera holds an azimuth, which runs the other way and over any range the user has spun it
|
|
20
|
+
* through. Every reader of "which way is the plan facing" wants this instead, so the negation and
|
|
21
|
+
* the wrap live here once rather than at each of them — the same getter the imperative renderer put
|
|
22
|
+
* on its controller.
|
|
23
|
+
* @param controls - The scene's controls.
|
|
24
|
+
* @returns The bearing in radians.
|
|
25
|
+
*/
|
|
26
|
+
export declare function bearing(controls: CameraControlsImpl): number;
|
|
27
|
+
/**
|
|
28
|
+
* Runs an effect against the scene's camera controls, for as long as the component is mounted.
|
|
29
|
+
*
|
|
30
|
+
* The controls arrive as an argument rather than from a hook, which is what lets the effect write to
|
|
31
|
+
* them; and the "wait for them to mount" subscription lives here rather than in every caller, so a
|
|
32
|
+
* component that claims a mouse action is three lines and says only what it claims. Together with
|
|
33
|
+
* {@link useCameraControls} that is the whole surface — read them, or write to them in an effect.
|
|
34
|
+
*
|
|
35
|
+
* Re-runs when the controls change and whenever `effect` does, which — the React Compiler caching
|
|
36
|
+
* the closure against what it captures — is whenever the component's own inputs move.
|
|
37
|
+
* @param effect - What to do with the controls; may return a cleanup, as any effect may.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useControlsEffect(effect: (controls: CameraControlsImpl) => (() => void) | void): void;
|
|
40
|
+
/** Props for {@link CameraControls}: camera-controls element props plus lifecycle callbacks. */
|
|
41
|
+
export type CameraControlsProps = Partial<Omit<Pick<CameraControlsImpl, NonFunctionKeys<CameraControlsImpl>>, 'smoothTime' | 'controlSmoothTime'>> & {
|
|
42
|
+
/** Ref to hold the camera-controls instance */
|
|
43
|
+
ref?: Ref<CameraControlsImpl>;
|
|
44
|
+
/** Camera to control; defaults to R3F's default camera. */
|
|
45
|
+
camera?: PerspectiveCamera | OrthographicCamera;
|
|
46
|
+
/** Element input listeners attach to; defaults to the R3F event target. */
|
|
47
|
+
domElement?: HTMLElement;
|
|
48
|
+
/** When set, registers these as R3F's default controls (`state.controls`). */
|
|
49
|
+
makeDefault?: boolean;
|
|
50
|
+
/** Called when a user control interaction begins. */
|
|
51
|
+
onControlStart?: (e?: {
|
|
52
|
+
type: 'controlstart';
|
|
53
|
+
}) => void;
|
|
54
|
+
/** Called on each frame of a control interaction. */
|
|
55
|
+
onControl?: (e?: {
|
|
56
|
+
type: 'control';
|
|
57
|
+
}) => void;
|
|
58
|
+
/** Called when a user control interaction ends. */
|
|
59
|
+
onControlEnd?: (e?: {
|
|
60
|
+
type: 'controlend';
|
|
61
|
+
}) => void;
|
|
62
|
+
/** Called when a programmatic camera transition starts. */
|
|
63
|
+
onTransitionStart?: (e?: {
|
|
64
|
+
type: 'transitionstart';
|
|
65
|
+
}) => void;
|
|
66
|
+
/** Called whenever the camera updates. */
|
|
67
|
+
onUpdate?: (e?: {
|
|
68
|
+
type: 'update';
|
|
69
|
+
}) => void;
|
|
70
|
+
/** Called when the controls wake (begin needing frame updates). */
|
|
71
|
+
onWake?: (e?: {
|
|
72
|
+
type: 'wake';
|
|
73
|
+
}) => void;
|
|
74
|
+
/** Called when the camera comes to rest. */
|
|
75
|
+
onRest?: (e?: {
|
|
76
|
+
type: 'rest';
|
|
77
|
+
}) => void;
|
|
78
|
+
/** Called when the controls sleep (stop needing frame updates). */
|
|
79
|
+
onSleep?: (e?: {
|
|
80
|
+
type: 'sleep';
|
|
81
|
+
}) => void;
|
|
82
|
+
/** Whether to keep R3F pointer events enabled during control interaction. */
|
|
83
|
+
events?: boolean;
|
|
84
|
+
/** When set, drops render resolution (`performance.regress`) during interaction. */
|
|
85
|
+
regress?: boolean;
|
|
86
|
+
/** Approximate time in seconds to reach the target. A smaller value will reach the target faster. */
|
|
87
|
+
smoothTime?: SmoothTimeOption;
|
|
88
|
+
/** The smoothTime used while the user is actively controlling the camera */
|
|
89
|
+
controlSmoothTime?: SmoothTimeOption;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* R3F wrapper around the `camera-controls` library. Builds a controls instance
|
|
93
|
+
* for the active camera, wires its events to R3F's invalidate/regress loop and
|
|
94
|
+
* the optional callbacks, optionally makes it the default controls, and
|
|
95
|
+
* publishes the live camera state (position, target, spherical, clipping planes)
|
|
96
|
+
* to the debug panel as it changes.
|
|
97
|
+
* @param props - Controls configuration and event callbacks.
|
|
98
|
+
* @returns The controls instance as an R3F `<primitive>`.
|
|
99
|
+
*/
|
|
100
|
+
export declare function CameraControls(props: CameraControlsProps): import("react").JSX.Element;
|
|
101
|
+
export { CameraControlsImpl };
|
|
102
|
+
//# sourceMappingURL=CameraControls.d.ts.map
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { radToDeg } from '@expofp/geometry';
|
|
3
|
+
import { extend, useFrame, useThree } from '@react-three/fiber';
|
|
4
|
+
import { MapControls as CameraControlsImpl } from 'camera-controls';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { Box3, MathUtils, Matrix4, Quaternion, Raycaster, Sphere, Spherical, Vector2, Vector3, Vector4, } from 'three';
|
|
7
|
+
import { useDebugPanel } from '../debug/debug-context';
|
|
8
|
+
import { euclideanModulo } from '../utils/math';
|
|
9
|
+
/**
|
|
10
|
+
* The scene's camera controls, once {@link Controls} has mounted them.
|
|
11
|
+
*
|
|
12
|
+
* For **reading** them, and for handing them to something else that does the writing — a gesture
|
|
13
|
+
* class, a measurement, a helper taking them as an argument. What the React Compiler rejects is
|
|
14
|
+
* assigning to a *field* of a hook's return value, which is a build error rather than a bug; a
|
|
15
|
+
* method call on it, or passing it along, is fine. A component with a write it cannot route through
|
|
16
|
+
* a function uses {@link useControlsEffect}, which hands the controls over as an argument.
|
|
17
|
+
* @returns The controls, or `undefined` until they mount.
|
|
18
|
+
*/
|
|
19
|
+
export function useCameraControls() {
|
|
20
|
+
return useThree((state) => state.controls) ?? undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* How far the plan is turned from north: clockwise, in radians, in `[0, 2π)`.
|
|
24
|
+
*
|
|
25
|
+
* The camera holds an azimuth, which runs the other way and over any range the user has spun it
|
|
26
|
+
* through. Every reader of "which way is the plan facing" wants this instead, so the negation and
|
|
27
|
+
* the wrap live here once rather than at each of them — the same getter the imperative renderer put
|
|
28
|
+
* on its controller.
|
|
29
|
+
* @param controls - The scene's controls.
|
|
30
|
+
* @returns The bearing in radians.
|
|
31
|
+
*/
|
|
32
|
+
export function bearing(controls) {
|
|
33
|
+
return euclideanModulo(-controls.azimuthAngle, 2 * Math.PI);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Runs an effect against the scene's camera controls, for as long as the component is mounted.
|
|
37
|
+
*
|
|
38
|
+
* The controls arrive as an argument rather than from a hook, which is what lets the effect write to
|
|
39
|
+
* them; and the "wait for them to mount" subscription lives here rather than in every caller, so a
|
|
40
|
+
* component that claims a mouse action is three lines and says only what it claims. Together with
|
|
41
|
+
* {@link useCameraControls} that is the whole surface — read them, or write to them in an effect.
|
|
42
|
+
*
|
|
43
|
+
* Re-runs when the controls change and whenever `effect` does, which — the React Compiler caching
|
|
44
|
+
* the closure against what it captures — is whenever the component's own inputs move.
|
|
45
|
+
* @param effect - What to do with the controls; may return a cleanup, as any effect may.
|
|
46
|
+
*/
|
|
47
|
+
export function useControlsEffect(effect) {
|
|
48
|
+
const get = useThree((state) => state.get);
|
|
49
|
+
// A subscription, so this re-runs once the controls have mounted. Deliberately not the value the
|
|
50
|
+
// effect uses: that is read fresh from the store below.
|
|
51
|
+
const mounted = useThree((state) => state.controls);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const controls = get().controls;
|
|
54
|
+
if (!controls)
|
|
55
|
+
return undefined;
|
|
56
|
+
return effect(controls) ?? undefined;
|
|
57
|
+
}, [effect, get, mounted]);
|
|
58
|
+
}
|
|
59
|
+
// Install the subset of THREE that camera-controls uses and register it with
|
|
60
|
+
// R3F, once at module load — before any controls are constructed during render,
|
|
61
|
+
// so the `new CameraControlsImpl(...)` below never runs uninstalled. Importing
|
|
62
|
+
// only the classes it needs keeps the bundle tree-shakeable.
|
|
63
|
+
// See https://github.com/yomotsu/camera-controls#important
|
|
64
|
+
const subsetOfThree = {
|
|
65
|
+
Box3,
|
|
66
|
+
MathUtils: { clamp: MathUtils.clamp },
|
|
67
|
+
Matrix4,
|
|
68
|
+
Quaternion,
|
|
69
|
+
Raycaster,
|
|
70
|
+
Sphere,
|
|
71
|
+
Spherical,
|
|
72
|
+
Vector2,
|
|
73
|
+
Vector3,
|
|
74
|
+
Vector4,
|
|
75
|
+
};
|
|
76
|
+
CameraControlsImpl.install({ THREE: subsetOfThree });
|
|
77
|
+
extend({ CameraControlsImpl });
|
|
78
|
+
/**
|
|
79
|
+
* R3F wrapper around the `camera-controls` library. Builds a controls instance
|
|
80
|
+
* for the active camera, wires its events to R3F's invalidate/regress loop and
|
|
81
|
+
* the optional callbacks, optionally makes it the default controls, and
|
|
82
|
+
* publishes the live camera state (position, target, spherical, clipping planes)
|
|
83
|
+
* to the debug panel as it changes.
|
|
84
|
+
* @param props - Controls configuration and event callbacks.
|
|
85
|
+
* @returns The controls instance as an R3F `<primitive>`.
|
|
86
|
+
*/
|
|
87
|
+
export function CameraControls(props) {
|
|
88
|
+
const { ref, camera, domElement, makeDefault, onControlStart, onControl, onControlEnd, onTransitionStart, onUpdate, onWake, onRest, onSleep, regress, ...restProps } = props;
|
|
89
|
+
const defaultCamera = useThree((state) => state.camera);
|
|
90
|
+
const gl = useThree((state) => state.gl);
|
|
91
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
92
|
+
const set = useThree((state) => state.set);
|
|
93
|
+
const get = useThree((state) => state.get);
|
|
94
|
+
const performance = useThree((state) => state.performance);
|
|
95
|
+
const setDebugPanel = useDebugPanel();
|
|
96
|
+
const explCamera = camera ?? defaultCamera;
|
|
97
|
+
// The canvas itself, not `state.events.connected`. R3F connects its pointer events to the canvas
|
|
98
|
+
// unless a caller names an `eventSource`, which this renderer never does — and `connected` is
|
|
99
|
+
// `false` until it has, so consulting it only adds a window where there is no element to attach
|
|
100
|
+
// to. A caller wanting a different one passes `domElement`.
|
|
101
|
+
const explDomElement = domElement ?? gl.domElement;
|
|
102
|
+
// Hold the instance in state, not a memo: React Fast Refresh treats every
|
|
103
|
+
// dependency list as changed when a module is edited, so `useMemo` (and the
|
|
104
|
+
// React Compiler's memo cache) would hand back a fresh instance on each save,
|
|
105
|
+
// snapping the camera back to its default target. State survives a refresh, so
|
|
106
|
+
// the instance is rebuilt only when the camera it drives actually changes.
|
|
107
|
+
const [controls, setControls] = useState(() => createControls(explCamera));
|
|
108
|
+
const [controlsCamera, setControlsCamera] = useState(explCamera);
|
|
109
|
+
if (controlsCamera !== explCamera) {
|
|
110
|
+
setControlsCamera(explCamera);
|
|
111
|
+
setControls((controls) => Object.assign(controls, { camera: explCamera }));
|
|
112
|
+
}
|
|
113
|
+
useFrame((_state, delta) => {
|
|
114
|
+
const updated = controls.update(delta);
|
|
115
|
+
if (updated || gl.info.render.frame == 0) {
|
|
116
|
+
const position = explCamera.position.toArray().map((value) => +value.toFixed(2));
|
|
117
|
+
const target = controls
|
|
118
|
+
.getTarget(_target)
|
|
119
|
+
.toArray()
|
|
120
|
+
.map((value) => +value.toFixed(2));
|
|
121
|
+
const { theta, phi, radius } = controls.getSpherical(_spherical);
|
|
122
|
+
const spherical = [radToDeg(theta), radToDeg(phi), radius].map((value) => +value.toFixed(2));
|
|
123
|
+
const clippingPlanes = [explCamera.near, explCamera.far].map((value) => +value.toFixed(2));
|
|
124
|
+
setDebugPanel('camera', { position, target, spherical, clippingPlanes });
|
|
125
|
+
}
|
|
126
|
+
}, -1);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
controls.connect(explDomElement);
|
|
129
|
+
// Android WebViews send no pointercancel for a system gesture — the back-navigation swipe — so
|
|
130
|
+
// a control interaction the user has already left would go on animating. touchcancel is what
|
|
131
|
+
// they do send, and dropping the interaction on it is what the imperative renderer did.
|
|
132
|
+
const cancel = () => controls.cancel();
|
|
133
|
+
explDomElement.addEventListener('touchcancel', cancel);
|
|
134
|
+
return () => {
|
|
135
|
+
explDomElement.removeEventListener('touchcancel', cancel);
|
|
136
|
+
controls.disconnect();
|
|
137
|
+
};
|
|
138
|
+
}, [explDomElement, controls]);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
function invalidateAndRegress() {
|
|
141
|
+
invalidate();
|
|
142
|
+
if (regress)
|
|
143
|
+
performance.regress();
|
|
144
|
+
}
|
|
145
|
+
const handleControlStart = (e) => {
|
|
146
|
+
invalidateAndRegress();
|
|
147
|
+
onControlStart?.(e);
|
|
148
|
+
};
|
|
149
|
+
const handleControl = (e) => {
|
|
150
|
+
invalidateAndRegress();
|
|
151
|
+
onControl?.(e);
|
|
152
|
+
};
|
|
153
|
+
const handleControlEnd = (e) => {
|
|
154
|
+
onControlEnd?.(e);
|
|
155
|
+
};
|
|
156
|
+
const handleTransitionStart = (e) => {
|
|
157
|
+
invalidateAndRegress();
|
|
158
|
+
onTransitionStart?.(e);
|
|
159
|
+
};
|
|
160
|
+
const handleUpdate = (e) => {
|
|
161
|
+
invalidateAndRegress();
|
|
162
|
+
onUpdate?.(e);
|
|
163
|
+
};
|
|
164
|
+
const handleWake = (e) => {
|
|
165
|
+
invalidateAndRegress();
|
|
166
|
+
onWake?.(e);
|
|
167
|
+
};
|
|
168
|
+
const handleRest = (e) => {
|
|
169
|
+
onRest?.(e);
|
|
170
|
+
};
|
|
171
|
+
const handleSleep = (e) => {
|
|
172
|
+
onSleep?.(e);
|
|
173
|
+
};
|
|
174
|
+
controls.addEventListener('controlstart', handleControlStart);
|
|
175
|
+
controls.addEventListener('control', handleControl);
|
|
176
|
+
controls.addEventListener('controlend', handleControlEnd);
|
|
177
|
+
controls.addEventListener('transitionstart', handleTransitionStart);
|
|
178
|
+
controls.addEventListener('update', handleUpdate);
|
|
179
|
+
controls.addEventListener('wake', handleWake);
|
|
180
|
+
controls.addEventListener('rest', handleRest);
|
|
181
|
+
controls.addEventListener('sleep', handleSleep);
|
|
182
|
+
return () => {
|
|
183
|
+
controls.removeEventListener('controlstart', handleControlStart);
|
|
184
|
+
controls.removeEventListener('control', handleControl);
|
|
185
|
+
controls.removeEventListener('controlend', handleControlEnd);
|
|
186
|
+
controls.removeEventListener('transitionstart', handleTransitionStart);
|
|
187
|
+
controls.removeEventListener('update', handleUpdate);
|
|
188
|
+
controls.removeEventListener('wake', handleWake);
|
|
189
|
+
controls.removeEventListener('rest', handleRest);
|
|
190
|
+
controls.removeEventListener('sleep', handleSleep);
|
|
191
|
+
};
|
|
192
|
+
}, [
|
|
193
|
+
controls,
|
|
194
|
+
invalidate,
|
|
195
|
+
regress,
|
|
196
|
+
performance,
|
|
197
|
+
onControlStart,
|
|
198
|
+
onControl,
|
|
199
|
+
onControlEnd,
|
|
200
|
+
onTransitionStart,
|
|
201
|
+
onUpdate,
|
|
202
|
+
onWake,
|
|
203
|
+
onRest,
|
|
204
|
+
onSleep,
|
|
205
|
+
]);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (makeDefault) {
|
|
208
|
+
const old = get().controls;
|
|
209
|
+
set({ controls: controls });
|
|
210
|
+
return () => set({ controls: old });
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}, [makeDefault, controls, set, get]);
|
|
214
|
+
return _jsx("primitive", { ref: ref, object: controls, ...restProps });
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Builds a controls instance with every mouse and touch action switched off.
|
|
218
|
+
*
|
|
219
|
+
* **The handler components own the actions**, each setting its own on mount and clearing it on
|
|
220
|
+
* unmount, so the library's defaults — orbit on the left button, truck on the right, dolly on the
|
|
221
|
+
* middle and the wheel — would be a second author writing the same fields. Zeroed here at
|
|
222
|
+
* construction rather than through a prop or an effect, both of which run *after* a handler's:
|
|
223
|
+
* R3F re-applies a prop whenever its identity changes, and a parent's effect runs after its
|
|
224
|
+
* children's, so either would undo whatever had just been set.
|
|
225
|
+
*
|
|
226
|
+
* This is where the imperative renderer did it too, in its `CameraController` constructor.
|
|
227
|
+
* @param camera - The camera the controls drive.
|
|
228
|
+
* @returns The controls, inert until a handler claims an action.
|
|
229
|
+
*/
|
|
230
|
+
function createControls(camera) {
|
|
231
|
+
const controls = new CameraControlsImpl(camera);
|
|
232
|
+
const { NONE } = CameraControlsImpl.ACTION;
|
|
233
|
+
controls.mouseButtons = { left: NONE, middle: NONE, right: NONE, wheel: NONE };
|
|
234
|
+
controls.touches = { one: NONE, two: NONE, three: NONE };
|
|
235
|
+
return controls;
|
|
236
|
+
}
|
|
237
|
+
export { CameraControlsImpl };
|
|
238
|
+
const _target = new Vector3();
|
|
239
|
+
const _spherical = new Spherical();
|