quilt-three 0.0.0-stage → 0.10.0
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/LICENSE +21 -0
- package/README.md +452 -2
- package/dist/camera-helpers.d.ts +33 -0
- package/dist/camera-helpers.d.ts.map +1 -0
- package/dist/camera-helpers.js +212 -0
- package/dist/camera-helpers.js.map +1 -0
- package/dist/index.d.ts +160 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +743 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation-controls.d.ts +94 -0
- package/dist/navigation-controls.d.ts.map +1 -0
- package/dist/navigation-controls.js +451 -0
- package/dist/navigation-controls.js.map +1 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +247 -0
- package/dist/react.js.map +1 -0
- package/dist/webgpu.d.ts +13 -0
- package/dist/webgpu.d.ts.map +1 -0
- package/dist/webgpu.js +54 -0
- package/dist/webgpu.js.map +1 -0
- package/package.json +63 -4
- package/src/camera-helpers.ts +261 -0
- package/src/index.ts +836 -0
- package/src/navigation-controls.ts +495 -0
- package/src/react.tsx +298 -0
- package/src/webgpu.ts +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 niko-dellic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,453 @@
|
|
|
1
|
-
#
|
|
1
|
+
# quilt-three
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared-scene Three.js viewports for Quilt, with optional React Three Fiber bindings.
|
|
4
|
+
The main workspace shares one canvas and renderer. Each same-origin companion document
|
|
5
|
+
has its own renderer. Geometry, authored materials, and camera poses remain
|
|
6
|
+
application-owned; moving a pane does not delete scene data.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install quilt-three three
|
|
12
|
+
npm install --save-dev @types/three
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The compatibility target is Three.js r186. For React:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install quilt-react react@^19 react-dom@^19 @react-three/fiber@^9.8.1
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`quilt-three` does not import or install React or R3F. The `/react` entry requires
|
|
22
|
+
React 19 and R3F 9.8.1 or newer within major 9. The optional React peer range also
|
|
23
|
+
allows React 18 applications to use the **imperative base entry** without adopting
|
|
24
|
+
R3F. Existing `quilt-react` React 18 support is unchanged.
|
|
25
|
+
|
|
26
|
+
## Vanilla
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Scene, PerspectiveCamera } from 'three';
|
|
30
|
+
import { Workspace, createLayout } from 'quilt-vanilla';
|
|
31
|
+
import { ThreeSession } from 'quilt-three';
|
|
32
|
+
import 'quilt-vanilla/styles.css';
|
|
33
|
+
|
|
34
|
+
const scene = new Scene(); // Populate and own assets in your application.
|
|
35
|
+
const camera = new PerspectiveCamera(50, 1, 0.1, 100);
|
|
36
|
+
camera.position.set(5, 5, 5);
|
|
37
|
+
camera.lookAt(0, 0, 0);
|
|
38
|
+
const session = new ThreeSession({ scene, window });
|
|
39
|
+
const workspace = new Workspace({
|
|
40
|
+
container: document.querySelector<HTMLElement>('#workspace')!,
|
|
41
|
+
initialLayout: createLayout({ pane: { id: 'perspective', type: 'three', title: 'Perspective' } }),
|
|
42
|
+
paneTypes: {
|
|
43
|
+
three: {
|
|
44
|
+
title: 'Perspective',
|
|
45
|
+
render(context) {
|
|
46
|
+
return session.register(context, { camera });
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// After changing scene data:
|
|
53
|
+
session.invalidate();
|
|
54
|
+
// At application shutdown:
|
|
55
|
+
workspace.dispose();
|
|
56
|
+
await session.dispose();
|
|
57
|
+
// Dispose application-owned geometries/materials/textures separately.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Keep cameras in an application map keyed by pane ID for multiple views. Do not
|
|
61
|
+
put Three.js objects in layout params. Perspective aspect follows each pane;
|
|
62
|
+
orthographic resizing preserves its vertical extent, horizontal center, and zoom.
|
|
63
|
+
|
|
64
|
+
`register()` returns a `ThreeView` class instance and registers cleanup with Quilt.
|
|
65
|
+
The session retains its main renderer until session disposal, even if all panes
|
|
66
|
+
are hidden, closed, or popped out. Companion renderers are released when unused.
|
|
67
|
+
The main application must dispose the session explicitly. Disposal detaches views
|
|
68
|
+
synchronously; await its returned promise to wait for GPU resources, including
|
|
69
|
+
factories or initialization that finish after unmounting. `ThreeView.ready` resolves
|
|
70
|
+
after initialization and the first draw. Quilt automatically includes it in pane
|
|
71
|
+
readiness; cancelled mounts settle quietly and failed initialization rejects readiness.
|
|
72
|
+
|
|
73
|
+
## WebGPU
|
|
74
|
+
|
|
75
|
+
WebGL remains the default. Opt in through the separate entry point:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { ThreeSession } from 'quilt-three';
|
|
79
|
+
import { createWebGPURenderer } from 'quilt-three/webgpu';
|
|
80
|
+
|
|
81
|
+
const session = new ThreeSession({
|
|
82
|
+
scene,
|
|
83
|
+
window,
|
|
84
|
+
createRenderer: (canvas) => createWebGPURenderer(canvas, { antialias: true }),
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
React uses the same factory with
|
|
89
|
+
`<ThreeSessionProvider options={{ createRenderer: createWebGPURenderer }}>`.
|
|
90
|
+
The factory initializes asynchronously and acquires native devices from the canvas's
|
|
91
|
+
own document, including companions. It releases those devices when its renderer is
|
|
92
|
+
disposed. Do not reuse a renderer or device across workspace documents.
|
|
93
|
+
|
|
94
|
+
When no native adapter is available, the factory uses Three.js's WebGL backend.
|
|
95
|
+
Set `{ fallback: false }` to require native WebGPU and report unavailability instead;
|
|
96
|
+
`forceWebGL: true` explicitly selects the fallback backend. WebGPU availability
|
|
97
|
+
requires a compatible browser/device and a secure context (localhost qualifies).
|
|
98
|
+
`quilt-three` itself does not load `three/webgpu`; only this opt-in entry does.
|
|
99
|
+
|
|
100
|
+
Pipelines receive `ThreeRenderer`, a union of `WebGLRenderer` and `WebGPURenderer`.
|
|
101
|
+
Use `isWebGPURenderer(renderer)` from the base entry to narrow it. GLSL
|
|
102
|
+
`ShaderMaterial` pipelines are not portable to WebGPU; use Three.js node materials
|
|
103
|
+
for that backend. Quilt sets each renderer's viewport coordinates appropriately.
|
|
104
|
+
Try `&renderer=webgpu` in either demo, with `&effects` for the node-material proof
|
|
105
|
+
or `&forceWebGL` to exercise its fallback.
|
|
106
|
+
|
|
107
|
+
## Classes and camera helpers
|
|
108
|
+
|
|
109
|
+
`ThreeSession` owns renderers and scheduling. `ThreeView` owns each mounted viewport's
|
|
110
|
+
controls, input, pipeline, and cleanup; obtain it through `session.register()`.
|
|
111
|
+
`createThreeSession(options)` remains a convenience wrapper for `new ThreeSession(options)`.
|
|
112
|
+
Pure geometry utilities remain functions, and controls/pipelines implement interfaces without
|
|
113
|
+
requiring inheritance. React components wrap these same instances.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { CameraHelpers } from 'quilt-three';
|
|
117
|
+
|
|
118
|
+
const helpers = new CameraHelpers(session, {
|
|
119
|
+
visible: true,
|
|
120
|
+
ray: 'pointer', // 'center' (default), 'pointer', or false for markers only
|
|
121
|
+
maxDistance: 10,
|
|
122
|
+
});
|
|
123
|
+
helpers.setVisible(false); // Hide all markers and rays.
|
|
124
|
+
helpers.setCameraVisible(camera, false); // Hide one source camera's visualization.
|
|
125
|
+
helpers.dispose();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Helpers follow registered cameras, omit a camera's own visualization in its viewport,
|
|
129
|
+
and never change camera layers. Markers show position, orientation, and up direction: perspective cameras use a
|
|
130
|
+
triangle above the frame; orthographic cameras use a circular ring.
|
|
131
|
+
Pointer rays use pane-local coordinates (including companion windows), hit the first
|
|
132
|
+
visible scene surface, and revert to the center on leave. Orthographic picking uses an
|
|
133
|
+
offset parallel ray, with the displayed line connecting the camera marker to its endpoint.
|
|
134
|
+
A small circular dot marks actual ray intersections; rays that miss have no endpoint dot.
|
|
135
|
+
Helpers do not participate in picking. `ray: false` leaves camera markers visible.
|
|
136
|
+
|
|
137
|
+
Optional `cameras` retains an explicit set of source cameras, even without mounted views;
|
|
138
|
+
`visibleIn(camera)` filters the viewing cameras that display helpers. `color` and `size`
|
|
139
|
+
customize the wireframe. There is no helper count limit or reserved camera layer.
|
|
140
|
+
Disposal is idempotent and releases only helper-owned graphics. Session disposal also
|
|
141
|
+
disposes attached helpers, without disposing application scene assets.
|
|
142
|
+
|
|
143
|
+
In React, place `<CameraHelpers visible={showCameras} ray="pointer" />` from
|
|
144
|
+
`quilt-three/react` inside `ThreeSessionProvider`, alongside `SharedScene` and the workspace.
|
|
145
|
+
`visible` updates reactively; other helper options are mount-only. Unmounting cleans up
|
|
146
|
+
the helper instance, including React Strict Mode effect replays.
|
|
147
|
+
|
|
148
|
+
## React Three Fiber
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { Workspace } from 'quilt-react';
|
|
152
|
+
import type { PaneProps } from 'quilt-react';
|
|
153
|
+
import { SharedScene, ThreeSessionProvider, ThreeView } from 'quilt-three/react';
|
|
154
|
+
|
|
155
|
+
// `camera` and `initialLayout` are application-owned, as in the vanilla example.
|
|
156
|
+
function ScenePane(props: PaneProps) {
|
|
157
|
+
return <ThreeView paneContext={props} camera={camera} />;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function App() {
|
|
161
|
+
return (
|
|
162
|
+
<ThreeSessionProvider>
|
|
163
|
+
<SharedScene>
|
|
164
|
+
<mesh onClick={() => console.log('selected')}>
|
|
165
|
+
<boxGeometry />
|
|
166
|
+
<meshStandardMaterial color="teal" />
|
|
167
|
+
</mesh>
|
|
168
|
+
<ambientLight intensity={2} />
|
|
169
|
+
</SharedScene>
|
|
170
|
+
<Workspace
|
|
171
|
+
initialLayout={initialLayout}
|
|
172
|
+
paneTypes={{ three: { title: '3D', render: ScenePane } }}
|
|
173
|
+
/>
|
|
174
|
+
</ThreeSessionProvider>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Declare one `SharedScene` per provider. Its R3F tree remains mounted while views
|
|
180
|
+
move or pop out. `ThreeView` children are **HTML overlays**, not additional R3F
|
|
181
|
+
scenes. Application contexts used by scene content should be explicitly provided
|
|
182
|
+
inside `SharedScene`: its R3F root is separate from the DOM React root.
|
|
183
|
+
|
|
184
|
+
`useFrame` runs once per session tick, not once per camera. `useThree().camera`
|
|
185
|
+
outside an input callback is the scene owner's default camera; pass view cameras
|
|
186
|
+
explicitly to view-specific logic. Use `useThreeSession().invalidate()` after
|
|
187
|
+
external changes. Provider options are mount-only; camera/controls/pipeline
|
|
188
|
+
identity changes remount that view, so keep factory references stable.
|
|
189
|
+
|
|
190
|
+
The adapter advances R3F in manual mode and performs all view drawing afterward.
|
|
191
|
+
Do not add another automatic render loop or a competing positive-priority renderer.
|
|
192
|
+
Pointer events are forwarded from each pane using its camera, including companions.
|
|
193
|
+
R3F event state belongs to the shared root; simultaneous multi-pointer interaction
|
|
194
|
+
across different documents is not a supported interaction model in this release.
|
|
195
|
+
|
|
196
|
+
## Scheduling, controls, and graphics
|
|
197
|
+
|
|
198
|
+
- The frame scheduler uses a visible companion when the main document is hidden.
|
|
199
|
+
- Demand rendering is the default. Set `frameloop: 'always'` for continuous animation.
|
|
200
|
+
- `update(delta)` advances shared simulation once per tick; hidden views skip drawing.
|
|
201
|
+
- Pixel ratio follows each document and is capped at 2 by default; configure `maxPixelRatio`.
|
|
202
|
+
- Controls factories receive `{ camera, element, invalidate, inputScope }`. Return `enabled`,
|
|
203
|
+
`update(delta)` (true while another frame is needed), and `dispose()`.
|
|
204
|
+
- Subscribe controls change events to `invalidate`, and unsubscribe in `dispose`.
|
|
205
|
+
`NavigationControls` handles invalidation and cleanup itself. All demo workspaces
|
|
206
|
+
share this adapter. In Orbit, right-drag, Shift-drag, and middle-drag pan with
|
|
207
|
+
damping; pane menus remain available from the tab bar. No additional controls
|
|
208
|
+
package is required.
|
|
209
|
+
- Only the first live view of a given camera accepts input during overlapping mounts.
|
|
210
|
+
Use independent cameras for independently interactive docked views.
|
|
211
|
+
- A renderer factory receives a canvas created in the correct document. Return a
|
|
212
|
+
fresh renderer or a promise for one, owned by the session; do not share it with another
|
|
213
|
+
session/root. A factory that rejects must release resources it allocated before
|
|
214
|
+
rejecting. The session releases returned renderers even if the view was removed
|
|
215
|
+
while the factory was pending. Use the supplied WebGPU factory for document/device ownership.
|
|
216
|
+
- The default canvas host is the closest Quilt workspace/companion. `container(doc)`
|
|
217
|
+
can supply an explicit positioned host. Avoid transformed/scaled ancestors and
|
|
218
|
+
arbitrary nonrectangular clipping in this release.
|
|
219
|
+
- Renderer initialization failure rejects mounting. Quilt preserves the original
|
|
220
|
+
view if a companion cannot initialize. Context loss pauses that surface, reports
|
|
221
|
+
the error, and redraws when the WebGL context is restored. Native GPU device loss
|
|
222
|
+
pauses the affected document and reports through `onError`; recreate the session
|
|
223
|
+
to recover. Automatic device replacement is not provided.
|
|
224
|
+
|
|
225
|
+
## Custom pipelines and the effect proof
|
|
226
|
+
|
|
227
|
+
A view may supply `pipeline(renderer)`, returning `resize(width, height, dpr)`,
|
|
228
|
+
`render(frame)`, and `dispose()`. Width and height are CSS pixels. The pipeline is
|
|
229
|
+
created per mounted view and must release its own targets, materials, and geometry.
|
|
230
|
+
Caller-supplied scene assets must remain untouched.
|
|
231
|
+
|
|
232
|
+
The session sets the output viewport/scissor before rendering. A pipeline must
|
|
233
|
+
restore any renderer state it changes, including target, viewport, scissor, clear
|
|
234
|
+
settings, and color/tone-mapping configuration. No arbitrary third-party composer
|
|
235
|
+
compatibility is promised. Render target resources cannot cross renderer instances or GPU devices.
|
|
236
|
+
|
|
237
|
+
The demo's grayscale pipeline renders into a pane-sized target, then composites
|
|
238
|
+
into the pane's scissored rectangle. Other panes retain their ordinary materials.
|
|
239
|
+
It has separate GLSL and node-material implementations for WebGL and WebGPU.
|
|
240
|
+
It is a validation example, not a built-in effect API.
|
|
241
|
+
|
|
242
|
+
## Examples and validation
|
|
243
|
+
|
|
244
|
+
From the repository, build before serving:
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
npm run build
|
|
248
|
+
npm run dev
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- `/vanilla.html?workspace=three`: Three.js workspace in the vanilla demo.
|
|
252
|
+
- `/react.html?workspace=three`: Three.js workspace with a persistent R3F scene.
|
|
253
|
+
- Add `&effects` to either for independent grayscale processing on the perspective view.
|
|
254
|
+
- `npm run benchmark:three`: writes `artifacts/three-benchmark.json` with CPU submission
|
|
255
|
+
timings, draw calls, resource counts, and GPU timings when the extension is available.
|
|
256
|
+
|
|
257
|
+
WebGL graphics are tested in Chromium, Firefox, and WebKit. Native WebGPU is
|
|
258
|
+
validated in a dedicated Chromium project and in WebKit when an adapter is available;
|
|
259
|
+
its WebGL fallback is tested in all three engines. Native Firefox WebGPU was not
|
|
260
|
+
available on the validation host. Tests include Strict Mode, pending initialization,
|
|
261
|
+
late factory results, repeated disposal, shared asset preservation, and companion
|
|
262
|
+
return. See the [validation record](https://github.com/niko-dellic/quilt/blob/main/docs/three-validation.md).
|
|
263
|
+
Third-party R3F components must support the renderer selected by the application.
|
|
264
|
+
Material overrides, XR, workers, and effect collections remain outside the supported scope.
|
|
265
|
+
|
|
266
|
+
Select **4 view** in the existing workspace switcher. Switching presets preserves
|
|
267
|
+
the scene and camera poses. Each camera has a compact oriented marker and a ray
|
|
268
|
+
ending at the nearest visible scene surface or the maximum picking distance (10 world
|
|
269
|
+
units by default). Hovering a viewport aims its ray through the cursor; leaving restores
|
|
270
|
+
the center ray. Each pane's **Camera helper** button toggles that camera's marker and
|
|
271
|
+
ray in the other panes. A camera never displays its own helper; camera layers are unchanged.
|
|
272
|
+
|
|
273
|
+
Select **Custom views** to configure rows and columns. Helpers start hidden. The
|
|
274
|
+
**Camera helpers** button beside **Apply grid** toggles them globally, including
|
|
275
|
+
new viewports added by changing the grid. Both per-camera and global preferences
|
|
276
|
+
persist across workspace switches and grid changes for the current demo session.
|
|
277
|
+
These controls are shared by the vanilla and React demos and use `CameraHelpers`.
|
|
278
|
+
|
|
279
|
+
### Synchronizing demo cameras
|
|
280
|
+
|
|
281
|
+
The link icon toggles **Sync camera positions**. In **4 view**, use the button in
|
|
282
|
+
any pane to align the other cameras to that pane's exact XYZ position. In **Custom
|
|
283
|
+
views**, the button beside the grid controls starts from the first viewport.
|
|
284
|
+
While enabled, moving a camera updates the other cameras in that workspace to the
|
|
285
|
+
same position. Each camera keeps its own orientation and zoom; its controls target
|
|
286
|
+
moves with its position so subsequent input preserves its viewing direction.
|
|
287
|
+
While sync is enabled, camera helpers are hidden and their buttons are disabled.
|
|
288
|
+
Turning sync off restores the previous helper settings and independent movement without resetting camera poses.
|
|
289
|
+
The two workspaces retain their own toggle settings for the current demo session.
|
|
290
|
+
|
|
291
|
+
## Optional Free navigation
|
|
292
|
+
|
|
293
|
+
`NavigationControls` is an optional class implementing `ViewControls`. It combines
|
|
294
|
+
OrbitControls with keyboard navigation and works through the same controls factory
|
|
295
|
+
in vanilla and React. Other controls factories remain supported.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { PerspectiveCamera, Vector3 } from 'three';
|
|
299
|
+
import { NavigationControls } from 'quilt-three';
|
|
300
|
+
import type { ControlsContext, ViewOptions } from 'quilt-three';
|
|
301
|
+
|
|
302
|
+
const camera = new PerspectiveCamera(50, 1, 0.1, 100);
|
|
303
|
+
camera.position.set(5, 5, 5);
|
|
304
|
+
const target = new Vector3(); // Application-owned orbit point for this camera.
|
|
305
|
+
camera.lookAt(target);
|
|
306
|
+
|
|
307
|
+
const controls = (context: ControlsContext) =>
|
|
308
|
+
new NavigationControls(context, {
|
|
309
|
+
target,
|
|
310
|
+
orbitTargetPolicy: 'follow',
|
|
311
|
+
speed: 2,
|
|
312
|
+
minSpeed: 0.05,
|
|
313
|
+
maxSpeed: 100,
|
|
314
|
+
boostMultiplier: 4,
|
|
315
|
+
acceleration: 0.15,
|
|
316
|
+
braking: 0.15,
|
|
317
|
+
lookSensitivity: 0.003,
|
|
318
|
+
bindings: { forward: ['KeyW', 'ArrowUp'], down: [] },
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const viewOptions: ViewOptions = { camera, controls };
|
|
322
|
+
// Vanilla: session.register(paneContext, viewOptions).
|
|
323
|
+
// React: <ThreeView paneContext={paneContext} {...viewOptions} />.
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Keep React camera and factory references stable: changing their identity remounts
|
|
327
|
+
the view. Use a separate application-owned target for each independently controlled
|
|
328
|
+
camera. No global installation or Three.js prototype changes are needed.
|
|
329
|
+
|
|
330
|
+
### Mouse and keyboard controls
|
|
331
|
+
|
|
332
|
+
These are the default bindings; the example above disables Q/down deliberately.
|
|
333
|
+
|
|
334
|
+
| Input | Orbit | Free navigation |
|
|
335
|
+
| -------------------------------- | ----------------------- | ------------------------------------------------------------------- |
|
|
336
|
+
| Left-drag | Orbit around the target | Stop translation, return to Orbit, and start orbiting |
|
|
337
|
+
| Right-drag | Damped pan | Yaw and pitch without adding roll |
|
|
338
|
+
| Middle-drag or Shift + left-drag | Damped pan | Use left-drag to return to Orbit first |
|
|
339
|
+
| Wheel | Zoom | Adjust movement speed within its limits |
|
|
340
|
+
| W / Up, S / Down | Enter Free navigation | Move forward / backward along the camera direction, including pitch |
|
|
341
|
+
| A / Left, D / Right | Enter Free navigation | Strafe along camera-right |
|
|
342
|
+
| Q / E | Enter Free navigation | Move down / up along `camera.up` |
|
|
343
|
+
| Shift | Modifies Orbit gestures | Boost movement speed |
|
|
344
|
+
| Escape | Cancel an active drag | Stop immediately and return to Orbit |
|
|
345
|
+
| Double-click | Reset original pose | Reset original pose and return to Orbit |
|
|
346
|
+
|
|
347
|
+
Hover the viewport to use navigation keys. Diagonal movement is normalized, and
|
|
348
|
+
movement accelerates smoothly. Releasing keys brakes smoothly but leaves Free
|
|
349
|
+
navigation active. The demos display a noninteractive indicator with an Escape hint
|
|
350
|
+
while Free navigation is active. The library leaves overlay UI to the application.
|
|
351
|
+
|
|
352
|
+
Both perspective and orthographic cameras retain their projection. In orthographic
|
|
353
|
+
views, forward movement changes camera position without perspective-style zoom.
|
|
354
|
+
Native context menus are suppressed on viewport input surfaces; Quilt pane menus
|
|
355
|
+
remain available from the tab bar.
|
|
356
|
+
|
|
357
|
+
### Options and defaults
|
|
358
|
+
|
|
359
|
+
| Option | Default | Meaning |
|
|
360
|
+
| -------------------------- | ------------------------------------ | -------------------------------------------------------------------------- |
|
|
361
|
+
| `target` | A point 10 units ahead of the camera | Mutable application-owned `Vector3` used as the orbit target |
|
|
362
|
+
| `orbitTargetPolicy` | `'follow'` | Target behavior when returning to Orbit; see below |
|
|
363
|
+
| `speed` | `2` | World units per second, clamped to the speed limits |
|
|
364
|
+
| `minSpeed` / `maxSpeed` | `0.05` / `100` | Bounds for the base movement speed, including wheel adjustments |
|
|
365
|
+
| `boostMultiplier` | `4` | Multiplier while a boost key is held |
|
|
366
|
+
| `acceleration` / `braking` | `0.15` / `0.15` | Time constants in seconds; larger values accelerate or stop more gradually |
|
|
367
|
+
| `lookSensitivity` | `0.003` | Radians per CSS pixel during Free mouse-look |
|
|
368
|
+
| `bindings` | The keys listed above | `KeyboardEvent.code` arrays per action |
|
|
369
|
+
|
|
370
|
+
Numeric options must be positive and finite; `maxSpeed` must be at least `minSpeed`.
|
|
371
|
+
Acceleration and braking describe Free navigation inertia, not Orbit damping.
|
|
372
|
+
Orbit and pan currently use a fixed damping factor of `0.12`.
|
|
373
|
+
|
|
374
|
+
Bindings accept `forward`, `backward`, `left`, `right`, `down`, `up`, and `boost`.
|
|
375
|
+
Omitted actions retain defaults, supplied arrays replace them, and empty arrays
|
|
376
|
+
disable them. For example, `{ forward: ['KeyI'], down: [] }` replaces W/Up with I,
|
|
377
|
+
disables Q/down, and leaves all other actions unchanged. The default boost codes are
|
|
378
|
+
`ShiftLeft` and `ShiftRight`.
|
|
379
|
+
|
|
380
|
+
### Orbit target policies
|
|
381
|
+
|
|
382
|
+
| Policy | Demo label | Behavior when returning to Orbit |
|
|
383
|
+
| ------------ | -------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
384
|
+
| `'follow'` | Follow navigation | Retain the viewing direction; put the target ahead at the distance saved when Free navigation began |
|
|
385
|
+
| `'preserve'` | Preserve orbit point | Restore the world-space target saved when Free navigation began; turn the camera toward it immediately |
|
|
386
|
+
|
|
387
|
+
During Free translation, the working target moves by the same XYZ displacement as
|
|
388
|
+
the camera. Follow then rebuilds that target on return:
|
|
389
|
+
|
|
390
|
+
```text
|
|
391
|
+
target = camera position + viewing direction × previous orbit distance
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Follow includes vertical displacement and pitch. Preserve restores the full saved
|
|
395
|
+
point instead. Its return changes orientation as needed, but retains camera position
|
|
396
|
+
and zoom. The saved point survives pane remounts and companion transitions.
|
|
397
|
+
|
|
398
|
+
Assign `controls.orbitTargetPolicy` at runtime to choose the policy for the next
|
|
399
|
+
return without moving the camera immediately. A change made while Free navigation
|
|
400
|
+
is active uses the point saved on entry to that navigation sequence.
|
|
401
|
+
|
|
402
|
+
In both demos, **Settings → Camera navigation → Orbit target** exposes these two
|
|
403
|
+
choices, with Follow selected initially. The choice applies to all viewports and
|
|
404
|
+
survives workspace changes and popouts for the current demo session. The **Hotkeys**
|
|
405
|
+
tab documents the keyboard and mouse controls; arrow keys navigate the hovered
|
|
406
|
+
viewport, while focused tabs retain their tab-switching shortcuts.
|
|
407
|
+
|
|
408
|
+
### Runtime API
|
|
409
|
+
|
|
410
|
+
| Member | Behavior |
|
|
411
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
412
|
+
| `mode` | Read `'orbit'` or `'free'` |
|
|
413
|
+
| `setMode(mode)` | Switch modes, clearing held movement and velocity |
|
|
414
|
+
| `speed` | Read or set base speed; assignments are clamped to the configured limits |
|
|
415
|
+
| `orbitTargetPolicy` | Read or set `'follow'` or `'preserve'` |
|
|
416
|
+
| `camera` / `target` | Access the controlled camera and mutable orbit target |
|
|
417
|
+
| `enabled` | Enable or disable input; disabling stops movement |
|
|
418
|
+
| `setPosition(Vector3)` | Translate camera and working target together, preserving orientation and zoom and clearing residual motion |
|
|
419
|
+
| `reset()` | Restore the original pose and Orbit mode |
|
|
420
|
+
| `update(delta)` | Advance controls in seconds; return true while another frame is needed |
|
|
421
|
+
| `dispose()` | Remove listeners and dispose the underlying OrbitControls; safe to call again |
|
|
422
|
+
|
|
423
|
+
`setPosition()` does not replace the saved Preserve return point during Free
|
|
424
|
+
navigation. This makes it suitable for position synchronization without rotating
|
|
425
|
+
peer cameras. Speed and target policy are runtime settings; bindings, speed limits,
|
|
426
|
+
boost, inertia, and sensitivity are constructor options.
|
|
427
|
+
|
|
428
|
+
Use `addEventListener('change', handler)` for camera changes and
|
|
429
|
+
`addEventListener('modechange', handler)` for mode changes; the latter event carries
|
|
430
|
+
`mode`. Remove application subscriptions when their owner is disposed.
|
|
431
|
+
|
|
432
|
+
### Scheduling and input ownership
|
|
433
|
+
|
|
434
|
+
The controller invalidates its session automatically and advances through the existing
|
|
435
|
+
session scheduler. It creates no animation loop; on-demand rendering settles after
|
|
436
|
+
movement and damping stop. The React adapter uses the same scheduling infrastructure.
|
|
437
|
+
|
|
438
|
+
Only one hovered viewport owns keyboard navigation within a session, including
|
|
439
|
+
companions. Inputs, textareas, selects, editable content, visible menus/dialogs, and
|
|
440
|
+
Ctrl/Meta/Alt shortcuts are excluded. Leaving a viewport, switching hovered panes,
|
|
441
|
+
losing window focus, hiding/disabling a view, or disposing controls stops keyboard
|
|
442
|
+
motion immediately. Held-key repeat cannot transfer movement to another pane:
|
|
443
|
+
a fresh key press is required. After editing a form control, focus the viewport to
|
|
444
|
+
resume navigation.
|
|
445
|
+
|
|
446
|
+
Camera pose, target, mode, and speed survive remounts within the same session; held
|
|
447
|
+
keys and velocity do not. `ThreeSession` supplies `ControlsContext.inputScope` for
|
|
448
|
+
shared ownership and retained state. Standalone controllers may supply the same
|
|
449
|
+
scope object across documents; without one, the scope is the input element's document.
|
|
450
|
+
|
|
451
|
+
Mounted views dispose their controls. View teardown never disposes shared scene
|
|
452
|
+
assets. Collision detection, gravity, pointer lock, and gamepad navigation are outside
|
|
453
|
+
this controller's scope.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Camera, ColorRepresentation } from 'three';
|
|
2
|
+
import type { ThreeSession } from './index.js';
|
|
3
|
+
export interface CameraHelpersOptions {
|
|
4
|
+
visible?: boolean;
|
|
5
|
+
ray?: 'center' | 'pointer' | false;
|
|
6
|
+
maxDistance?: number;
|
|
7
|
+
color?: ColorRepresentation;
|
|
8
|
+
/** Marker size multiplier, independent of camera projection and clipping planes. */
|
|
9
|
+
size?: number;
|
|
10
|
+
/** Omit to follow all registered cameras. Explicit cameras remain visible when their panes unmount. */
|
|
11
|
+
cameras?: readonly Camera[];
|
|
12
|
+
/** Select which viewing cameras display helpers. A camera never displays its own helper. */
|
|
13
|
+
visibleIn?: (camera: Camera) => boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Small camera outlines and optional picking rays. Never changes camera layers or scene assets. */
|
|
16
|
+
export declare class CameraHelpers {
|
|
17
|
+
private readonly session;
|
|
18
|
+
private readonly options;
|
|
19
|
+
private readonly entries;
|
|
20
|
+
private readonly hidden;
|
|
21
|
+
private readonly unsubscribe;
|
|
22
|
+
private disposed;
|
|
23
|
+
private visible;
|
|
24
|
+
private readonly distance;
|
|
25
|
+
private readonly size;
|
|
26
|
+
constructor(session: ThreeSession, options?: CameraHelpersOptions);
|
|
27
|
+
setVisible(visible: boolean): void;
|
|
28
|
+
setCameraVisible(camera: Camera, visible: boolean): void;
|
|
29
|
+
private sync;
|
|
30
|
+
private prepare;
|
|
31
|
+
dispose(): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=camera-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-helpers.d.ts","sourceRoot":"","sources":["../src/camera-helpers.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAoC,MAAM,OAAO,CAAC;AAC3F,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,YAAY,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uGAAuG;IACvG,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,4FAA4F;IAC5F,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;CACzC;AAED,oGAAoG;AACpG,qBAAa,aAAa;IAUtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;IAClE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAE9B,YACmB,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,oBAAyB,EAgBpD;IAED,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIjC;IACD,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAKvD;IACD,OAAO,CAAC,IAAI;IAiBZ,OAAO,CAAC,OAAO;IAsBf,OAAO,IAAI,IAAI,CAOd;CACF"}
|