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