@expofp/renderer 0.0.0-experimental.cf063fc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/dist/Renderer.d.ts +102 -0
- package/dist/Renderer.js +96 -0
- package/dist/controls/CameraControls.d.ts +108 -0
- package/dist/controls/CameraControls.js +203 -0
- package/dist/controls/Controls.d.ts +53 -0
- package/dist/controls/Controls.js +54 -0
- package/dist/controls/camera-api.d.ts +226 -0
- package/dist/controls/camera-api.js +179 -0
- package/dist/controls/gestures.d.ts +71 -0
- package/dist/controls/gestures.js +98 -0
- package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
- package/dist/controls/handlers/DoubleTapZoom.js +82 -0
- package/dist/controls/handlers/Inertia.d.ts +60 -0
- package/dist/controls/handlers/Inertia.js +222 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +212 -0
- package/dist/controls/handlers/Pitch.d.ts +19 -0
- package/dist/controls/handlers/Pitch.js +200 -0
- package/dist/controls/handlers/Roll.d.ts +30 -0
- package/dist/controls/handlers/Roll.js +196 -0
- package/dist/controls/handlers/Zoom.d.ts +28 -0
- package/dist/controls/handlers/Zoom.js +34 -0
- package/dist/controls/hooks.d.ts +136 -0
- package/dist/controls/hooks.js +200 -0
- package/dist/controls/index.d.ts +9 -0
- package/dist/controls/index.js +7 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +108 -0
- package/dist/core/InstanceUniforms.js +298 -0
- package/dist/core/SquareDataTexture.d.ts +277 -0
- package/dist/core/SquareDataTexture.js +476 -0
- package/dist/core/batch/BatchedMesh.d.ts +74 -0
- package/dist/core/batch/BatchedMesh.js +155 -0
- package/dist/core/instanced/FrustumCulling.d.ts +141 -0
- package/dist/core/instanced/FrustumCulling.js +246 -0
- package/dist/core/instanced/InstancedMesh.d.ts +302 -0
- package/dist/core/instanced/InstancedMesh.js +497 -0
- package/dist/core/instanced/Instances.d.ts +119 -0
- package/dist/core/instanced/Instances.js +230 -0
- package/dist/core/instanced/Raycasting.d.ts +118 -0
- package/dist/core/instanced/Raycasting.js +134 -0
- package/dist/core/instanced/instanced-material.d.ts +77 -0
- package/dist/core/instanced/instanced-material.js +228 -0
- package/dist/core/lines/BatchedLines.d.ts +137 -0
- package/dist/core/lines/BatchedLines.js +215 -0
- package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
- package/dist/core/lines/FrustumCullingLines.js +85 -0
- package/dist/core/lines/RaycastingLines.d.ts +45 -0
- package/dist/core/lines/RaycastingLines.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +124 -0
- package/dist/core/shader-source.d.ts +70 -0
- package/dist/core/shader-source.js +110 -0
- package/dist/core/sprites/BatchedSprites.d.ts +115 -0
- package/dist/core/sprites/BatchedSprites.js +173 -0
- package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
- package/dist/core/sprites/RaycastingSprites.js +71 -0
- package/dist/core/sprites/sprite-material.d.ts +59 -0
- package/dist/core/sprites/sprite-material.js +192 -0
- package/dist/core/text/BatchedText.d.ts +233 -0
- package/dist/core/text/BatchedText.js +473 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +289 -0
- package/dist/core/text/Text.d.ts +88 -0
- package/dist/core/text/Text.js +230 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +123 -0
- package/dist/core/text/glyph-packing.d.ts +17 -0
- package/dist/core/text/glyph-packing.js +61 -0
- package/dist/core/typed-array.d.ts +16 -0
- package/dist/core/typed-array.js +20 -0
- package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
- package/dist/debug/components/DebugPanelProvider.js +50 -0
- package/dist/debug/components/DebugTools.d.ts +22 -0
- package/dist/debug/components/DebugTools.js +28 -0
- package/dist/debug/components/Gui.d.ts +22 -0
- package/dist/debug/components/Gui.js +42 -0
- package/dist/debug/components/MemoryInfo.d.ts +22 -0
- package/dist/debug/components/MemoryInfo.js +42 -0
- package/dist/debug/components/Panel.d.ts +31 -0
- package/dist/debug/components/Panel.js +104 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-chunk.d.ts +26 -0
- package/dist/debug/debug-chunk.js +32 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +41 -0
- package/dist/debug/index.js +43 -0
- package/dist/events/LayoutDriver.d.ts +36 -0
- package/dist/events/LayoutDriver.js +192 -0
- package/dist/events/PointerMissed.d.ts +23 -0
- package/dist/events/PointerMissed.js +58 -0
- package/dist/events/def-culling.d.ts +46 -0
- package/dist/events/def-culling.js +69 -0
- package/dist/events/layout-targets.d.ts +151 -0
- package/dist/events/layout-targets.js +397 -0
- package/dist/events/placement/box.d.ts +59 -0
- package/dist/events/placement/box.js +96 -0
- package/dist/events/placement/grid.d.ts +161 -0
- package/dist/events/placement/grid.js +235 -0
- package/dist/events/placement/index.d.ts +2 -0
- package/dist/events/placement/index.js +1 -0
- package/dist/events/placement/pass.d.ts +70 -0
- package/dist/events/placement/pass.js +617 -0
- package/dist/events/pointer.d.ts +194 -0
- package/dist/events/pointer.js +356 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +177 -0
- package/dist/external/external-camera.d.ts +18 -0
- package/dist/external/external-camera.js +103 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/containers/SlotContainer.d.ts +312 -0
- package/dist/objects/containers/SlotContainer.js +311 -0
- package/dist/objects/containers/hooks.d.ts +61 -0
- package/dist/objects/containers/hooks.js +196 -0
- package/dist/objects/containers/index.d.ts +6 -0
- package/dist/objects/containers/index.js +4 -0
- package/dist/objects/containers/keys.d.ts +10 -0
- package/dist/objects/containers/keys.js +18 -0
- package/dist/objects/containers/mesh-writes.d.ts +106 -0
- package/dist/objects/containers/mesh-writes.js +97 -0
- package/dist/objects/containers/props.d.ts +33 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +152 -0
- package/dist/objects/images/Images.d.ts +49 -0
- package/dist/objects/images/Images.js +394 -0
- package/dist/objects/images/atlas-packer.d.ts +129 -0
- package/dist/objects/images/atlas-packer.js +287 -0
- package/dist/objects/images/image-container.d.ts +74 -0
- package/dist/objects/images/image-container.js +100 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/pack-sources.d.ts +190 -0
- package/dist/objects/images/pack-sources.js +272 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
- package/dist/objects/images/worker/atlas-worker.js +123 -0
- package/dist/objects/images/worker/client.d.ts +62 -0
- package/dist/objects/images/worker/client.js +249 -0
- package/dist/objects/images/worker/index.d.ts +10 -0
- package/dist/objects/images/worker/index.js +1 -0
- package/dist/objects/layer/Layer.d.ts +28 -0
- package/dist/objects/layer/Layer.js +134 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/layer-context.d.ts +74 -0
- package/dist/objects/layer/layer-context.js +82 -0
- package/dist/objects/layer/mount-scheduler.d.ts +131 -0
- package/dist/objects/layer/mount-scheduler.js +289 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -0
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +384 -0
- package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
- package/dist/objects/lighting/OcclusionPasses.js +271 -0
- package/dist/objects/lighting/index.d.ts +11 -0
- package/dist/objects/lighting/index.js +10 -0
- package/dist/objects/lighting/lighting-gui.d.ts +24 -0
- package/dist/objects/lighting/lighting-gui.js +114 -0
- package/dist/objects/lighting/occluder.d.ts +34 -0
- package/dist/objects/lighting/occluder.js +62 -0
- package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
- package/dist/objects/lighting/occlusion-targets.js +276 -0
- package/dist/objects/lines/LineInstance.d.ts +21 -0
- package/dist/objects/lines/LineInstance.js +73 -0
- package/dist/objects/lines/Lines.d.ts +17 -0
- package/dist/objects/lines/Lines.js +31 -0
- package/dist/objects/lines/index.d.ts +2 -0
- package/dist/objects/lines/index.js +1 -0
- package/dist/objects/lines/line-container.d.ts +35 -0
- package/dist/objects/lines/line-container.js +40 -0
- package/dist/objects/materials/ColorMaterial.d.ts +92 -0
- package/dist/objects/materials/ColorMaterial.js +124 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +74 -0
- package/dist/objects/materials/ModelMaterial.d.ts +53 -0
- package/dist/objects/materials/ModelMaterial.js +111 -0
- package/dist/objects/materials/TextureMaterial.d.ts +33 -0
- package/dist/objects/materials/TextureMaterial.js +48 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
- package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
- package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
- package/dist/objects/materials/effects/ao/index.d.ts +129 -0
- package/dist/objects/materials/effects/ao/index.js +72 -0
- package/dist/objects/materials/effects/color-dim.d.ts +115 -0
- package/dist/objects/materials/effects/color-dim.js +206 -0
- package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
- package/dist/objects/materials/effects/contact-shadow.js +167 -0
- package/dist/objects/materials/effects/effects-context.d.ts +53 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/materials/shared.d.ts +24 -0
- package/dist/objects/materials/shared.js +72 -0
- package/dist/objects/models/ModelInstance.d.ts +22 -0
- package/dist/objects/models/ModelInstance.js +33 -0
- package/dist/objects/models/Models.d.ts +22 -0
- package/dist/objects/models/Models.js +186 -0
- package/dist/objects/models/gltf-cache.d.ts +17 -0
- package/dist/objects/models/gltf-cache.js +50 -0
- package/dist/objects/models/index.d.ts +2 -0
- package/dist/objects/models/index.js +1 -0
- package/dist/objects/models/model-asset.d.ts +48 -0
- package/dist/objects/models/model-asset.js +77 -0
- package/dist/objects/models/model-container.d.ts +123 -0
- package/dist/objects/models/model-container.js +207 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +39 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +131 -0
- package/dist/objects/shapes/index.d.ts +2 -0
- package/dist/objects/shapes/index.js +1 -0
- package/dist/objects/shapes/shape-container.d.ts +141 -0
- package/dist/objects/shapes/shape-container.js +242 -0
- package/dist/objects/shapes/shape-geometry.d.ts +27 -0
- package/dist/objects/shapes/shape-geometry.js +94 -0
- package/dist/objects/text/TextInstance.d.ts +22 -0
- package/dist/objects/text/TextInstance.js +95 -0
- package/dist/objects/text/Texts.d.ts +30 -0
- package/dist/objects/text/Texts.js +43 -0
- package/dist/objects/text/index.d.ts +2 -0
- package/dist/objects/text/index.js +1 -0
- package/dist/objects/text/text-container.d.ts +121 -0
- package/dist/objects/text/text-container.js +229 -0
- package/dist/objects/text/text-layout.d.ts +92 -0
- package/dist/objects/text/text-layout.js +180 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +47 -0
- package/dist/space/Scene.js +127 -0
- package/dist/space/Space.d.ts +325 -0
- package/dist/space/Space.js +359 -0
- package/dist/space/VisibleRect.d.ts +45 -0
- package/dist/space/VisibleRect.js +129 -0
- package/dist/space/camera-handle.d.ts +67 -0
- package/dist/space/camera-handle.js +63 -0
- package/dist/space/camera-kind.d.ts +26 -0
- package/dist/space/camera-kind.js +30 -0
- package/dist/space/canvas-size.d.ts +24 -0
- package/dist/space/canvas-size.js +20 -0
- package/dist/space/clipping.d.ts +69 -0
- package/dist/space/clipping.js +130 -0
- package/dist/space/layout-measure.d.ts +67 -0
- package/dist/space/layout-measure.js +175 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +234 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +198 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +21 -0
- package/dist/types/guards.js +52 -0
- package/dist/update/index.d.ts +3 -0
- package/dist/update/index.js +2 -0
- package/dist/update/slot-writers.d.ts +30 -0
- package/dist/update/slot-writers.js +76 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +104 -0
- package/dist/utils/arrays.d.ts +13 -0
- package/dist/utils/arrays.js +20 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +27 -0
- package/dist/utils/hot-state.d.ts +41 -0
- package/dist/utils/hot-state.js +37 -0
- package/dist/utils/logging.d.ts +29 -0
- package/dist/utils/logging.js +55 -0
- package/dist/utils/math.d.ts +52 -0
- package/dist/utils/math.js +76 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
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/).
|
|
112
|
+
|
|
113
|
+
## Limits of the published package
|
|
114
|
+
|
|
115
|
+
**`dist` is not supported outside this repository. It does not even resolve.**
|
|
116
|
+
`dist/objects/images/worker/client.js` holds
|
|
117
|
+
`import AtlasWorker from './atlas-worker?worker&inline'`, and `dist/index.js`
|
|
118
|
+
reaches that file. The `?worker&inline` specifier is a Vite instruction, so
|
|
119
|
+
only Vite can answer it. Any other consumer — a different bundler, plain Node,
|
|
120
|
+
a browser loading the files as they are — fails to resolve the import and stops
|
|
121
|
+
there. Nothing in the package loads, so there is no smaller part of it to fall
|
|
122
|
+
back on.
|
|
123
|
+
|
|
124
|
+
This package has no bundler. Its `build` target is plain `tsc`, so `dist` is
|
|
125
|
+
file-per-module TypeScript output and nothing else runs over it. `tsc` copies
|
|
126
|
+
the specifier through unchanged, which is where the broken import comes from.
|
|
127
|
+
The same absence of a bundler means the React Compiler never runs on `dist`
|
|
128
|
+
either, so the components there ship unoptimized.
|
|
129
|
+
|
|
130
|
+
Nothing in this repository is affected, because nothing in this repository
|
|
131
|
+
consumes `dist`. The renderer is compiled from its own source instead.
|
|
132
|
+
Floorplan's browser build reaches `src` through the `@expofp/source` condition
|
|
133
|
+
in `vite.config.mts`, so Vite answers the specifier and inlines the worker
|
|
134
|
+
script into the bundle; floorplan also applies its own React Compiler pass.
|
|
135
|
+
This package's own unit tests and Storybook import `src` by relative path, so
|
|
136
|
+
Vite answers the specifier for them too: the Storybook run starts the real
|
|
137
|
+
worker, and the unit tests drive the protocol against a fake one. Wayfinding
|
|
138
|
+
imports types only, which are erased at emit. The offline CLI copies
|
|
139
|
+
floorplan's browser bundle rather than importing anything.
|
|
140
|
+
|
|
141
|
+
To lift the limit, give this package a Vite library build in the shape
|
|
142
|
+
floorplan already uses. Keep `build` (tsc) for the types. Add `build-browser`
|
|
143
|
+
(Vite) for the bundle. Put a `browser` condition in the exports map, above
|
|
144
|
+
`import`. That answers the worker specifier and runs the React Compiler, so
|
|
145
|
+
both problems above go with it.
|
|
@@ -0,0 +1,102 @@
|
|
|
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 LightingProps } from './objects/lighting';
|
|
6
|
+
import { type CameraProps } from './space/Camera';
|
|
7
|
+
import { type SceneDef } from './types/defs';
|
|
8
|
+
import { type PointerMissedEventData } from './types/events';
|
|
9
|
+
export { type ExternalCanvasProps } from './external/ExternalCanvas';
|
|
10
|
+
/**
|
|
11
|
+
* Props for {@link Renderer}. One surface for both mounts: the external trio rides along optional
|
|
12
|
+
* (the partial of {@link ExternalCanvasProps}, the way the camera limits ride along from
|
|
13
|
+
* {@link CameraProps}), and **`gl` arriving is what selects external mode** — exactly as it
|
|
14
|
+
* selected the imperative renderer's. With `gl` set, everything canvas- and camera-shaped is
|
|
15
|
+
* ignored: the host owns the canvas, the view and the frames, so the DOM attributes, `ref`,
|
|
16
|
+
* the camera limits, `controls` and `visibleRect` have nothing to configure.
|
|
17
|
+
*/
|
|
18
|
+
export interface RendererProps extends HTMLAttributes<HTMLDivElement>, CameraProps, Partial<ExternalCanvasProps> {
|
|
19
|
+
/** Ref to the underlying WebGL canvas element. Internal mode — the external canvas is the host's. */
|
|
20
|
+
ref?: Ref<HTMLCanvasElement>;
|
|
21
|
+
/**
|
|
22
|
+
* When `true`, enables the debug UI overlay — in both modes; the GUI, the readout panels and the
|
|
23
|
+
* frame stats attach to the document beside whichever canvas is drawing. The one figure external
|
|
24
|
+
* mode cannot always show is GPU memory: it needs the context created *after* the debug chunk has
|
|
25
|
+
* instrumented context creation, which a host arranges by awaiting `prepareDebug()` before
|
|
26
|
+
* building its map. Without that the memory panel shows and stays empty — visibly wrong rather
|
|
27
|
+
* than silently absent.
|
|
28
|
+
*/
|
|
29
|
+
debug?: boolean;
|
|
30
|
+
/** The scene to render. */
|
|
31
|
+
scene: SceneDef;
|
|
32
|
+
/**
|
|
33
|
+
* Components rendered inside the canvas, under the scene's space and camera. This is where a
|
|
34
|
+
* caller puts anything that has to reach the renderer from within — camera commands, event
|
|
35
|
+
* bridges — since those hooks only work below a mounted canvas.
|
|
36
|
+
*/
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* Soft cap, in megabytes, on the GPU memory the image atlases may consume. When the atlases would
|
|
40
|
+
* exceed it, every image source is downscaled by one shared linear factor — preserving their
|
|
41
|
+
* relative sizes, with no priority — until they fit. Canvas sources are counted at full size but
|
|
42
|
+
* never scaled (their pixels are the caller's), so a limit smaller than the canvases alone need is
|
|
43
|
+
* simply not met. May be changed at runtime: the resizable atlases re-derive their textures live,
|
|
44
|
+
* with no repack. Omitted (or `0`) means no cap, and the budget stands down entirely.
|
|
45
|
+
*/
|
|
46
|
+
memoryLimit?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Called for a click that reached no layer handler — empty background, or a def in a layer that
|
|
49
|
+
* declares none, since such a layer is never picked. The event reports where the click landed on
|
|
50
|
+
* the plan, which a click on nothing otherwise could not tell you.
|
|
51
|
+
*
|
|
52
|
+
* Clicks that *do* reach a layer go to that layer's `onClick` instead, so a caller that wants every
|
|
53
|
+
* click regardless (say, to publish plan coordinates) wires the same function in both places.
|
|
54
|
+
*/
|
|
55
|
+
onPointerMissed?: (event: PointerMissedEventData) => void;
|
|
56
|
+
/**
|
|
57
|
+
* What the user may do with the plan by mouse and touch — pan, zoom, roll, pitch.
|
|
58
|
+
*
|
|
59
|
+
* The other half of navigation from the limits this extends {@link CameraProps} for: a caller
|
|
60
|
+
* switches a gesture off without changing where the camera may go, and constrains the camera
|
|
61
|
+
* without taking a gesture away.
|
|
62
|
+
*/
|
|
63
|
+
controls?: ControlsConfig;
|
|
64
|
+
/**
|
|
65
|
+
* The scene's lighting, and the darkening that rides with it: the rig's contrast and three
|
|
66
|
+
* colors, and a `shadow` object holding the shade both darkening terms blend toward and each
|
|
67
|
+
* term's own numbers. See {@link LightingProps}.
|
|
68
|
+
*
|
|
69
|
+
* Every value is optional and every omitted one keeps the design's own default, so a caller sets
|
|
70
|
+
* the one number a plan needs and nothing else. Where the key light points is not here: that is
|
|
71
|
+
* the design's decision, and the debug GUI keeps it.
|
|
72
|
+
*
|
|
73
|
+
* Changing a value moves a mounted scene with no remount and no shader recompile. It lands on a
|
|
74
|
+
* uniform or on a field the frame reads, and asks for a redraw.
|
|
75
|
+
*/
|
|
76
|
+
lighting?: LightingProps;
|
|
77
|
+
/**
|
|
78
|
+
* The part of the canvas the plan should be framed inside, in css pixels from its top-left
|
|
79
|
+
* corner — for a host whose own header, sheet or panel covers the rest.
|
|
80
|
+
*
|
|
81
|
+
* **The plan is still drawn across the whole canvas**: nothing is clipped, and the strips behind
|
|
82
|
+
* that UI show more of the plan than they would otherwise rather than being cropped away. What
|
|
83
|
+
* changes is where the viewbox lands, which is guaranteed to be inside this rect.
|
|
84
|
+
*
|
|
85
|
+
* Distinct from `zoomTo`'s `within`, which is about a single command's framing and moves nothing.
|
|
86
|
+
* Ignored for a scene that carries a `staticTransform`, where the host places the plan itself.
|
|
87
|
+
*/
|
|
88
|
+
visibleRect?: Box;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* R3F floorplan renderer. Composes **one** scene tree — space, camera, layers, debug overlay, the
|
|
92
|
+
* caller's children — and mounts it on one of two canvases: its own (an R3F `<Canvas>`, wrapped in
|
|
93
|
+
* the debug loader) or a host's, when the host's `gl` arrives and {@link ExternalCanvas} adopts its
|
|
94
|
+
* canvas, camera and frame loop instead. The tree is the same in both; what external mode drops is
|
|
95
|
+
* exactly what the host owns — the managed camera, the controls, the `visibleRect` framing — and
|
|
96
|
+
* the tree simply mounts none of them there.
|
|
97
|
+
* @param props - Renderer props; in internal mode, keys other than the renderer's own pass through
|
|
98
|
+
* to the underlying canvas.
|
|
99
|
+
* @returns The rendered canvas and scene.
|
|
100
|
+
*/
|
|
101
|
+
export declare function Renderer(props: RendererProps): ReactNode;
|
|
102
|
+
//# sourceMappingURL=Renderer.d.ts.map
|
package/dist/Renderer.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StrictMode, Suspense, useEffect, useRef, } 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, lighting, visibleRect, onPointerMissed, gl, requestFrame, onFrame, children, ...canvasProps } = props;
|
|
28
|
+
const cameraProps = { minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime };
|
|
29
|
+
const external = gl !== undefined;
|
|
30
|
+
// The context this mount created, released here rather than left to R3F. `<Canvas>`'s teardown
|
|
31
|
+
// (`unmountComponentAtNode`) defers `forceContextLoss()` behind a 500 ms `setTimeout`, inside a
|
|
32
|
+
// `try` that disconnects events first — so a host that unmounts the renderer holds a full
|
|
33
|
+
// viewport of GPU memory for half a second after it thought the surface was gone, and holds it
|
|
34
|
+
// forever if anything ahead of the loss in that block throws. An SDK `destroy()` that returns
|
|
35
|
+
// while the plan still owns a WebGL2 context is the same bug on a timer, and a plan swap pays it
|
|
36
|
+
// as a real transient: the incoming plan's context is created while the outgoing one still
|
|
37
|
+
// holds its own. R3F's deferred pass still runs afterwards and is unharmed — its remaining work
|
|
38
|
+
// (`renderLists.dispose()`, `dispose(scene)`) is no-ops on a lost context.
|
|
39
|
+
//
|
|
40
|
+
// Safe as a plain unmount effect only because `<StrictMode>` starts *below* here, wrapping the
|
|
41
|
+
// canvas rather than this component: nothing simulates a remount at this level, so the cleanup
|
|
42
|
+
// runs when the renderer is actually going away. External mode never fills the ref — the host
|
|
43
|
+
// owns that context, and `ExternalCanvas` neuters `forceContextLoss` for the same reason.
|
|
44
|
+
const rendererRef = useRef(undefined);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return () => {
|
|
47
|
+
rendererRef.current?.forceContextLoss();
|
|
48
|
+
rendererRef.current = undefined;
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
// The one tree, whichever canvas it lands on. The mount decides nothing about it; the tree
|
|
52
|
+
// leaves out what external mode has no object for — the controls here, the camera inside Scene.
|
|
53
|
+
const tree = (_jsx(DebugOverlay, { enabled: debug, children: _jsxs(Scene, { sceneDef: scene, external: external, memoryLimit: memoryLimit, lighting: lighting, visibleRect: visibleRect, ...cameraProps, children: [!external && _jsx(Controls, { config: controls }), _jsx(PointerMissed, { onPointerMissed: onPointerMissed }), children] }) }));
|
|
54
|
+
// The host's context arriving is what selects external mode. The mount swap is an element-type
|
|
55
|
+
// change, so flipping modes at runtime remounts — like `debug`, both are fixed for a renderer's
|
|
56
|
+
// lifetime in practice.
|
|
57
|
+
if (gl) {
|
|
58
|
+
if (!requestFrame || !onFrame) {
|
|
59
|
+
logger.error('external mode needs requestFrame and onFrame alongside gl; nothing will render');
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
// No StrictMode here: ExternalCanvas wraps the tree it renders into the foreign root itself,
|
|
63
|
+
// and its own bootstrap effect must run once per context, not be double-invoked into churn.
|
|
64
|
+
return (_jsx(ExternalCanvas, { gl: gl, requestFrame: requestFrame, onFrame: onFrame, children: tree }));
|
|
65
|
+
}
|
|
66
|
+
return (_jsx(StrictMode, { children: _jsx(Suspense, { fallback: null, children: _jsx(CanvasWithDebug, { ...canvasProps,
|
|
67
|
+
// No `dpr`: R3F's default caps the buffer at 2x, and that is a performance choice rather
|
|
68
|
+
// than a size one. Every pixel-authored size is derived from CSS units and the pixel size
|
|
69
|
+
// the renderer publishes (also CSS), so a 3x canvas rasterized by a caller still draws at
|
|
70
|
+
// the size it asked for while the buffer stays at 2x. A 3x buffer is 2.25 times the area
|
|
71
|
+
// of a 2x one, on exactly the devices that can least afford it.
|
|
72
|
+
debugEnabled: debug,
|
|
73
|
+
// R3F force-writes ACES filmic tone mapping onto a fresh renderer unless `flat` is set,
|
|
74
|
+
// which lands it on three's own default, `NoToneMapping`, instead. This renderer does not
|
|
75
|
+
// tone map: `Lighting` keeps the rig's light sum in range by construction, rather than
|
|
76
|
+
// compressing an unbounded one back into range afterward.
|
|
77
|
+
flat: true,
|
|
78
|
+
// three leaves the stencil buffer off by default. Line layers need one: it is how they keep
|
|
79
|
+
// translucent lines from darkening where they overlap (see `LineMaterial`), and without the
|
|
80
|
+
// buffer the stencil settings silently do nothing. In the MapLibre embed the context is the
|
|
81
|
+
// map's own, which already has one.
|
|
82
|
+
gl: { stencil: true },
|
|
83
|
+
// three reads a program's info log straight after linking it, and that read blocks until
|
|
84
|
+
// the driver has finished linking. Left to itself the driver links in the background,
|
|
85
|
+
// because `KHR_parallel_shader_compile` is there, so the check is the whole of what makes
|
|
86
|
+
// a compile synchronous: 17 programs cost 64 ms of main thread on a large scene, in
|
|
87
|
+
// blocks of up to 12 ms, and they land in the frames where the layers are still mounting.
|
|
88
|
+
// Debug keeps the check, because a broken shader has to report itself somewhere.
|
|
89
|
+
//
|
|
90
|
+
// The root state is also this mount's one handle on the renderer, which the unmount
|
|
91
|
+
// effect above needs to release its context.
|
|
92
|
+
onCreated: (state) => {
|
|
93
|
+
state.gl.debug.checkShaderErrors = debug ?? false;
|
|
94
|
+
rendererRef.current = state.gl;
|
|
95
|
+
}, frameloop: debug ? 'always' : 'demand', resize: { debounce: 50 }, children: tree }) }) }));
|
|
96
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
/** Props for {@link CameraControls}: camera-controls element props plus lifecycle callbacks. */
|
|
6
|
+
export type CameraControlsProps = Partial<Omit<Pick<CameraControlsImpl, NonFunctionKeys<CameraControlsImpl>>, 'smoothTime' | 'controlSmoothTime'>> & {
|
|
7
|
+
/** Ref to hold the camera-controls instance */
|
|
8
|
+
ref?: Ref<CameraControlsImpl>;
|
|
9
|
+
/** Camera to control; defaults to R3F's default camera. */
|
|
10
|
+
camera?: PerspectiveCamera | OrthographicCamera;
|
|
11
|
+
/** Element input listeners attach to; defaults to the R3F event target. */
|
|
12
|
+
domElement?: HTMLElement;
|
|
13
|
+
/** When set, registers these as R3F's default controls (`state.controls`). */
|
|
14
|
+
makeDefault?: boolean;
|
|
15
|
+
/** Called when a user control interaction begins. */
|
|
16
|
+
onControlStart?: (e?: {
|
|
17
|
+
type: 'controlstart';
|
|
18
|
+
}) => void;
|
|
19
|
+
/** Called on each frame of a control interaction. */
|
|
20
|
+
onControl?: (e?: {
|
|
21
|
+
type: 'control';
|
|
22
|
+
}) => void;
|
|
23
|
+
/** Called when a user control interaction ends. */
|
|
24
|
+
onControlEnd?: (e?: {
|
|
25
|
+
type: 'controlend';
|
|
26
|
+
}) => void;
|
|
27
|
+
/** Called when a programmatic camera transition starts. */
|
|
28
|
+
onTransitionStart?: (e?: {
|
|
29
|
+
type: 'transitionstart';
|
|
30
|
+
}) => void;
|
|
31
|
+
/** Called whenever the camera updates. */
|
|
32
|
+
onUpdate?: (e?: {
|
|
33
|
+
type: 'update';
|
|
34
|
+
}) => void;
|
|
35
|
+
/** Called when the controls wake (begin needing frame updates). */
|
|
36
|
+
onWake?: (e?: {
|
|
37
|
+
type: 'wake';
|
|
38
|
+
}) => void;
|
|
39
|
+
/** Called when the camera comes to rest. */
|
|
40
|
+
onRest?: (e?: {
|
|
41
|
+
type: 'rest';
|
|
42
|
+
}) => void;
|
|
43
|
+
/** Called when the controls sleep (stop needing frame updates). */
|
|
44
|
+
onSleep?: (e?: {
|
|
45
|
+
type: 'sleep';
|
|
46
|
+
}) => void;
|
|
47
|
+
/** Whether to keep R3F pointer events enabled during control interaction. */
|
|
48
|
+
events?: boolean;
|
|
49
|
+
/** When set, drops render resolution (`performance.regress`) during interaction. */
|
|
50
|
+
regress?: boolean;
|
|
51
|
+
/** Approximate time in seconds to reach the target. A smaller value will reach the target faster. */
|
|
52
|
+
smoothTime?: SmoothTimeOption;
|
|
53
|
+
/** The smoothTime used while the user is actively controlling the camera */
|
|
54
|
+
controlSmoothTime?: SmoothTimeOption;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* R3F wrapper around the `camera-controls` library. Builds a controls instance
|
|
58
|
+
* for the active camera, wires its events to R3F's invalidate/regress loop and
|
|
59
|
+
* the optional callbacks, optionally makes it the default controls, and
|
|
60
|
+
* publishes the live camera state (position, target, spherical, clipping planes)
|
|
61
|
+
* to the debug panel as it changes.
|
|
62
|
+
* @param props - Controls configuration and event callbacks.
|
|
63
|
+
* @returns The controls instance as an R3F `<primitive>`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function CameraControls(props: CameraControlsProps): import("react").JSX.Element;
|
|
66
|
+
/**
|
|
67
|
+
* The controls class, under the name the rest of this package calls it by.
|
|
68
|
+
*
|
|
69
|
+
* A **type** and not a value: the only code that needs the class itself is right here, and a type
|
|
70
|
+
* survives an import cycle where a class would not — so a handler this module's own parent loads
|
|
71
|
+
* can name it freely. Everything else the library offers arrives through
|
|
72
|
+
* {@link CameraControlsAction}.
|
|
73
|
+
*/
|
|
74
|
+
export { type CameraControlsImpl };
|
|
75
|
+
/**
|
|
76
|
+
* The mouse and touch actions a handler claims, from the library's own enum.
|
|
77
|
+
*
|
|
78
|
+
* Beside the component rather than in a module of its own: a constant is the one thing Fast Refresh
|
|
79
|
+
* tolerates next to a component, and this one never changes.
|
|
80
|
+
*/
|
|
81
|
+
export declare const CameraControlsAction: Readonly<{
|
|
82
|
+
readonly NONE: 0;
|
|
83
|
+
readonly ROTATE_AZIMUTH: 1;
|
|
84
|
+
readonly ROTATE_POLAR: 2;
|
|
85
|
+
readonly ROTATE: number;
|
|
86
|
+
readonly TRUCK: 4;
|
|
87
|
+
readonly SCREEN_PAN: 8;
|
|
88
|
+
readonly OFFSET: 16;
|
|
89
|
+
readonly DOLLY: 32;
|
|
90
|
+
readonly ZOOM: 64;
|
|
91
|
+
readonly TOUCH_ROTATE_AZIMUTH: 128;
|
|
92
|
+
readonly TOUCH_ROTATE_POLAR: 256;
|
|
93
|
+
readonly TOUCH_ROTATE: number;
|
|
94
|
+
readonly TOUCH_TRUCK: 512;
|
|
95
|
+
readonly TOUCH_SCREEN_PAN: 1024;
|
|
96
|
+
readonly TOUCH_OFFSET: 2048;
|
|
97
|
+
readonly TOUCH_DOLLY: 4096;
|
|
98
|
+
readonly TOUCH_ZOOM: 8192;
|
|
99
|
+
readonly TOUCH_DOLLY_TRUCK: number;
|
|
100
|
+
readonly TOUCH_DOLLY_SCREEN_PAN: number;
|
|
101
|
+
readonly TOUCH_DOLLY_OFFSET: number;
|
|
102
|
+
readonly TOUCH_DOLLY_ROTATE: number;
|
|
103
|
+
readonly TOUCH_ZOOM_TRUCK: number;
|
|
104
|
+
readonly TOUCH_ZOOM_OFFSET: number;
|
|
105
|
+
readonly TOUCH_ZOOM_SCREEN_PAN: number;
|
|
106
|
+
readonly TOUCH_ZOOM_ROTATE: number;
|
|
107
|
+
}>;
|
|
108
|
+
//# sourceMappingURL=CameraControls.d.ts.map
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
// Install the subset of THREE that camera-controls uses and register it with
|
|
9
|
+
// R3F, once at module load — before any controls are constructed during render,
|
|
10
|
+
// so the `new CameraControlsImpl(...)` below never runs uninstalled. Importing
|
|
11
|
+
// only the classes it needs keeps the bundle tree-shakeable.
|
|
12
|
+
// See https://github.com/yomotsu/camera-controls#important
|
|
13
|
+
const subsetOfThree = {
|
|
14
|
+
Box3,
|
|
15
|
+
MathUtils: { clamp: MathUtils.clamp },
|
|
16
|
+
Matrix4,
|
|
17
|
+
Quaternion,
|
|
18
|
+
Raycaster,
|
|
19
|
+
Sphere,
|
|
20
|
+
Spherical,
|
|
21
|
+
Vector2,
|
|
22
|
+
Vector3,
|
|
23
|
+
Vector4,
|
|
24
|
+
};
|
|
25
|
+
CameraControlsImpl.install({ THREE: subsetOfThree });
|
|
26
|
+
extend({ CameraControlsImpl });
|
|
27
|
+
/**
|
|
28
|
+
* R3F wrapper around the `camera-controls` library. Builds a controls instance
|
|
29
|
+
* for the active camera, wires its events to R3F's invalidate/regress loop and
|
|
30
|
+
* the optional callbacks, optionally makes it the default controls, and
|
|
31
|
+
* publishes the live camera state (position, target, spherical, clipping planes)
|
|
32
|
+
* to the debug panel as it changes.
|
|
33
|
+
* @param props - Controls configuration and event callbacks.
|
|
34
|
+
* @returns The controls instance as an R3F `<primitive>`.
|
|
35
|
+
*/
|
|
36
|
+
export function CameraControls(props) {
|
|
37
|
+
const { ref, camera, domElement, makeDefault, onControlStart, onControl, onControlEnd, onTransitionStart, onUpdate, onWake, onRest, onSleep, regress, ...restProps } = props;
|
|
38
|
+
const defaultCamera = useThree((state) => state.camera);
|
|
39
|
+
const gl = useThree((state) => state.gl);
|
|
40
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
41
|
+
const set = useThree((state) => state.set);
|
|
42
|
+
const get = useThree((state) => state.get);
|
|
43
|
+
const performance = useThree((state) => state.performance);
|
|
44
|
+
const setDebugPanel = useDebugPanel();
|
|
45
|
+
const explCamera = camera ?? defaultCamera;
|
|
46
|
+
// The canvas itself, not `state.events.connected`. R3F connects its pointer events to the canvas
|
|
47
|
+
// unless a caller names an `eventSource`, which this renderer never does — and `connected` is
|
|
48
|
+
// `false` until it has, so consulting it only adds a window where there is no element to attach
|
|
49
|
+
// to. A caller wanting a different one passes `domElement`.
|
|
50
|
+
const explDomElement = domElement ?? gl.domElement;
|
|
51
|
+
// Hold the instance in state, not a memo: React Fast Refresh treats every
|
|
52
|
+
// dependency list as changed when a module is edited, so `useMemo` (and the
|
|
53
|
+
// React Compiler's memo cache) would hand back a fresh instance on each save,
|
|
54
|
+
// snapping the camera back to its default target. State survives a refresh, so
|
|
55
|
+
// the instance is rebuilt only when the camera it drives actually changes.
|
|
56
|
+
const [controls, setControls] = useState(() => createControls(explCamera));
|
|
57
|
+
const [controlsCamera, setControlsCamera] = useState(explCamera);
|
|
58
|
+
if (controlsCamera !== explCamera) {
|
|
59
|
+
setControlsCamera(explCamera);
|
|
60
|
+
setControls((controls) => Object.assign(controls, { camera: explCamera }));
|
|
61
|
+
}
|
|
62
|
+
useFrame((_state, delta) => {
|
|
63
|
+
const updated = controls.update(Math.min(delta, MAX_FRAME_DELTA));
|
|
64
|
+
if (updated || gl.info.render.frame == 0) {
|
|
65
|
+
const position = explCamera.position.toArray().map((value) => +value.toFixed(2));
|
|
66
|
+
const target = controls
|
|
67
|
+
.getTarget(_target)
|
|
68
|
+
.toArray()
|
|
69
|
+
.map((value) => +value.toFixed(2));
|
|
70
|
+
const { theta, phi, radius } = controls.getSpherical(_spherical);
|
|
71
|
+
const spherical = [radToDeg(theta), radToDeg(phi), radius].map((value) => +value.toFixed(2));
|
|
72
|
+
const clippingPlanes = [explCamera.near, explCamera.far].map((value) => +value.toFixed(2));
|
|
73
|
+
setDebugPanel('camera', { position, target, spherical, clippingPlanes });
|
|
74
|
+
}
|
|
75
|
+
}, -1);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
controls.connect(explDomElement);
|
|
78
|
+
// Android WebViews send no pointercancel for a system gesture — the back-navigation swipe — so
|
|
79
|
+
// a control interaction the user has already left would go on animating. touchcancel is what
|
|
80
|
+
// they do send, and dropping the interaction on it is what the imperative renderer did.
|
|
81
|
+
const cancel = () => controls.cancel();
|
|
82
|
+
explDomElement.addEventListener('touchcancel', cancel);
|
|
83
|
+
return () => {
|
|
84
|
+
explDomElement.removeEventListener('touchcancel', cancel);
|
|
85
|
+
controls.disconnect();
|
|
86
|
+
};
|
|
87
|
+
}, [explDomElement, controls]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
function invalidateAndRegress() {
|
|
90
|
+
invalidate();
|
|
91
|
+
if (regress)
|
|
92
|
+
performance.regress();
|
|
93
|
+
}
|
|
94
|
+
const handleControlStart = (e) => {
|
|
95
|
+
invalidateAndRegress();
|
|
96
|
+
onControlStart?.(e);
|
|
97
|
+
};
|
|
98
|
+
const handleControl = (e) => {
|
|
99
|
+
invalidateAndRegress();
|
|
100
|
+
onControl?.(e);
|
|
101
|
+
};
|
|
102
|
+
const handleControlEnd = (e) => {
|
|
103
|
+
onControlEnd?.(e);
|
|
104
|
+
};
|
|
105
|
+
const handleTransitionStart = (e) => {
|
|
106
|
+
invalidateAndRegress();
|
|
107
|
+
onTransitionStart?.(e);
|
|
108
|
+
};
|
|
109
|
+
const handleUpdate = (e) => {
|
|
110
|
+
invalidateAndRegress();
|
|
111
|
+
onUpdate?.(e);
|
|
112
|
+
};
|
|
113
|
+
const handleWake = (e) => {
|
|
114
|
+
invalidateAndRegress();
|
|
115
|
+
onWake?.(e);
|
|
116
|
+
};
|
|
117
|
+
const handleRest = (e) => {
|
|
118
|
+
onRest?.(e);
|
|
119
|
+
};
|
|
120
|
+
const handleSleep = (e) => {
|
|
121
|
+
onSleep?.(e);
|
|
122
|
+
};
|
|
123
|
+
controls.addEventListener('controlstart', handleControlStart);
|
|
124
|
+
controls.addEventListener('control', handleControl);
|
|
125
|
+
controls.addEventListener('controlend', handleControlEnd);
|
|
126
|
+
controls.addEventListener('transitionstart', handleTransitionStart);
|
|
127
|
+
controls.addEventListener('update', handleUpdate);
|
|
128
|
+
controls.addEventListener('wake', handleWake);
|
|
129
|
+
controls.addEventListener('rest', handleRest);
|
|
130
|
+
controls.addEventListener('sleep', handleSleep);
|
|
131
|
+
return () => {
|
|
132
|
+
controls.removeEventListener('controlstart', handleControlStart);
|
|
133
|
+
controls.removeEventListener('control', handleControl);
|
|
134
|
+
controls.removeEventListener('controlend', handleControlEnd);
|
|
135
|
+
controls.removeEventListener('transitionstart', handleTransitionStart);
|
|
136
|
+
controls.removeEventListener('update', handleUpdate);
|
|
137
|
+
controls.removeEventListener('wake', handleWake);
|
|
138
|
+
controls.removeEventListener('rest', handleRest);
|
|
139
|
+
controls.removeEventListener('sleep', handleSleep);
|
|
140
|
+
};
|
|
141
|
+
}, [
|
|
142
|
+
controls,
|
|
143
|
+
invalidate,
|
|
144
|
+
regress,
|
|
145
|
+
performance,
|
|
146
|
+
onControlStart,
|
|
147
|
+
onControl,
|
|
148
|
+
onControlEnd,
|
|
149
|
+
onTransitionStart,
|
|
150
|
+
onUpdate,
|
|
151
|
+
onWake,
|
|
152
|
+
onRest,
|
|
153
|
+
onSleep,
|
|
154
|
+
]);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (makeDefault) {
|
|
157
|
+
const old = get().controls;
|
|
158
|
+
set({ controls: controls });
|
|
159
|
+
return () => set({ controls: old });
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}, [makeDefault, controls, set, get]);
|
|
163
|
+
return _jsx("primitive", { ref: ref, object: controls, ...restProps });
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Builds a controls instance with every mouse and touch action switched off.
|
|
167
|
+
*
|
|
168
|
+
* **The handler components own the actions**, each setting its own on mount and clearing it on
|
|
169
|
+
* unmount, so the library's defaults — orbit on the left button, truck on the right, dolly on the
|
|
170
|
+
* middle and the wheel — would be a second author writing the same fields. Zeroed here at
|
|
171
|
+
* construction rather than through a prop or an effect, both of which run *after* a handler's:
|
|
172
|
+
* R3F re-applies a prop whenever its identity changes, and a parent's effect runs after its
|
|
173
|
+
* children's, so either would undo whatever had just been set.
|
|
174
|
+
*
|
|
175
|
+
* This is where the imperative renderer did it too, in its `CameraController` constructor.
|
|
176
|
+
* @param camera - The camera the controls drive.
|
|
177
|
+
* @returns The controls, inert until a handler claims an action.
|
|
178
|
+
*/
|
|
179
|
+
function createControls(camera) {
|
|
180
|
+
const controls = new CameraControlsImpl(camera);
|
|
181
|
+
const { NONE } = CameraControlsAction;
|
|
182
|
+
controls.mouseButtons = { left: NONE, middle: NONE, right: NONE, wheel: NONE };
|
|
183
|
+
controls.touches = { one: NONE, two: NONE, three: NONE };
|
|
184
|
+
return controls;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The mouse and touch actions a handler claims, from the library's own enum.
|
|
188
|
+
*
|
|
189
|
+
* Beside the component rather than in a module of its own: a constant is the one thing Fast Refresh
|
|
190
|
+
* tolerates next to a component, and this one never changes.
|
|
191
|
+
*/
|
|
192
|
+
export const CameraControlsAction = CameraControlsImpl.ACTION;
|
|
193
|
+
/**
|
|
194
|
+
* The longest step, in seconds, one frame may advance the controls' easing by.
|
|
195
|
+
*
|
|
196
|
+
* Under a demand frameloop the first frame after an idle spell carries the whole spell as its
|
|
197
|
+
* delta, and camera-controls does not clamp it: a move started by that frame would reach its
|
|
198
|
+
* target in one step. A ceiling rather than a fixed step, so a device drawing at 30 fps still
|
|
199
|
+
* eases in real time and only the idle gap is cut short.
|
|
200
|
+
*/
|
|
201
|
+
const MAX_FRAME_DELTA = 1 / 30;
|
|
202
|
+
const _target = new Vector3();
|
|
203
|
+
const _spherical = new Spherical();
|