@expofp/renderer 3.34.2 → 3.35.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/dist/Renderer.d.ts +14 -0
- package/dist/Renderer.js +16 -4
- package/dist/core/shader-source.d.ts +20 -0
- package/dist/core/shader-source.js +26 -0
- package/dist/debug/components/Gui.d.ts +2 -0
- package/dist/debug/components/Gui.js +11 -1
- package/dist/external/ExternalCanvas.js +5 -6
- package/dist/external/external-camera.js +26 -11
- package/dist/index.d.ts +1 -0
- package/dist/objects/Background.js +1 -1
- package/dist/objects/containers/SlotContainer.js +7 -1
- package/dist/objects/containers/hooks.js +1 -1
- package/dist/objects/containers/props.d.ts +10 -0
- package/dist/objects/images/Images.js +9 -2
- package/dist/objects/images/image-container.js +1 -1
- package/dist/objects/layer/Layer.js +10 -10
- package/dist/objects/layer/mount-scheduler.d.ts +83 -54
- package/dist/objects/layer/mount-scheduler.js +204 -60
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +385 -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/Lines.js +3 -1
- package/dist/objects/materials/ColorMaterial.d.ts +58 -51
- package/dist/objects/materials/ColorMaterial.js +74 -52
- package/dist/objects/materials/LineMaterial.js +1 -1
- package/dist/objects/materials/ModelMaterial.d.ts +30 -18
- package/dist/objects/materials/ModelMaterial.js +68 -41
- package/dist/objects/materials/TextureMaterial.js +1 -1
- 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/{dim.d.ts → effects/color-dim.d.ts} +4 -4
- package/dist/objects/materials/{dim.js → effects/color-dim.js} +6 -24
- 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 +52 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/models/Models.js +18 -2
- package/dist/objects/models/model-container.js +1 -1
- package/dist/objects/shapes/Shapes.js +20 -2
- package/dist/objects/text/Texts.js +3 -1
- package/dist/objects/text/text-container.js +1 -1
- package/dist/space/Scene.d.ts +3 -0
- package/dist/space/Scene.js +17 -5
- package/package.json +2 -2
- package/dist/objects/Lighting.d.ts +0 -20
- package/dist/objects/Lighting.js +0 -85
package/dist/Renderer.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type Box } from '@expofp/geometry';
|
|
|
2
2
|
import { type HTMLAttributes, type ReactNode, type Ref } from 'react';
|
|
3
3
|
import { type ControlsConfig } from './controls';
|
|
4
4
|
import { type ExternalCanvasProps } from './external/ExternalCanvas';
|
|
5
|
+
import { type LightingProps } from './objects/lighting';
|
|
5
6
|
import { type CameraProps } from './space/Camera';
|
|
6
7
|
import { type SceneDef } from './types/defs';
|
|
7
8
|
import { type PointerMissedEventData } from './types/events';
|
|
@@ -60,6 +61,19 @@ export interface RendererProps extends HTMLAttributes<HTMLDivElement>, CameraPro
|
|
|
60
61
|
* without taking a gesture away.
|
|
61
62
|
*/
|
|
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;
|
|
63
77
|
/**
|
|
64
78
|
* The part of the canvas the plan should be framed inside, in css pixels from its top-left
|
|
65
79
|
* corner — for a host whose own header, sheet or panel covers the rest.
|
package/dist/Renderer.js
CHANGED
|
@@ -24,12 +24,12 @@ export function Renderer(props) {
|
|
|
24
24
|
// attributes — which is what React warns about, and the reason every member of `CameraProps` has
|
|
25
25
|
// to be named here as well as reassembled below. They are passed on untouched: what an omitted
|
|
26
26
|
// one means is `Camera`'s to say, and it is the only place that says it.
|
|
27
|
-
const { debug, scene, memoryLimit, minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime, controls, visibleRect, onPointerMissed, gl, requestFrame, onFrame, children, ...canvasProps } = props;
|
|
27
|
+
const { debug, scene, memoryLimit, minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime, controls, lighting, visibleRect, onPointerMissed, gl, requestFrame, onFrame, children, ...canvasProps } = props;
|
|
28
28
|
const cameraProps = { minZoom, maxZoom, minPitch, maxPitch, bounds, smoothTime };
|
|
29
29
|
const external = gl !== undefined;
|
|
30
30
|
// The one tree, whichever canvas it lands on. The mount decides nothing about it; the tree
|
|
31
31
|
// leaves out what external mode has no object for — the controls here, the camera inside Scene.
|
|
32
|
-
const tree = (_jsx(DebugOverlay, { enabled: debug, children: _jsxs(Scene, { sceneDef: scene, external: external, memoryLimit: memoryLimit, visibleRect: visibleRect, ...cameraProps, children: [!external && _jsx(Controls, { config: controls }), _jsx(PointerMissed, { onPointerMissed: onPointerMissed }), children] }) }));
|
|
32
|
+
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] }) }));
|
|
33
33
|
// The host's context arriving is what selects external mode. The mount swap is an element-type
|
|
34
34
|
// change, so flipping modes at runtime remounts — like `debug`, both are fixed for a renderer's
|
|
35
35
|
// lifetime in practice.
|
|
@@ -43,11 +43,23 @@ export function Renderer(props) {
|
|
|
43
43
|
return (_jsx(ExternalCanvas, { gl: gl, requestFrame: requestFrame, onFrame: onFrame, children: tree }));
|
|
44
44
|
}
|
|
45
45
|
return (_jsx(StrictMode, { children: _jsx(Suspense, { fallback: null, children: _jsx(CanvasWithDebug, { ...canvasProps, debugEnabled: debug,
|
|
46
|
-
//
|
|
46
|
+
// R3F force-writes ACES filmic tone mapping onto a fresh renderer unless `flat` is set,
|
|
47
|
+
// which lands it on three's own default, `NoToneMapping`, instead. This renderer does not
|
|
48
|
+
// tone map: `Lighting` keeps the rig's light sum in range by construction, rather than
|
|
49
|
+
// compressing an unbounded one back into range afterward.
|
|
47
50
|
flat: true,
|
|
48
51
|
// three leaves the stencil buffer off by default. Line layers need one: it is how they keep
|
|
49
52
|
// translucent lines from darkening where they overlap (see `LineMaterial`), and without the
|
|
50
53
|
// buffer the stencil settings silently do nothing. In the MapLibre embed the context is the
|
|
51
54
|
// map's own, which already has one.
|
|
52
|
-
gl: { stencil: true },
|
|
55
|
+
gl: { stencil: true },
|
|
56
|
+
// three reads a program's info log straight after linking it, and that read blocks until
|
|
57
|
+
// the driver has finished linking. Left to itself the driver links in the background,
|
|
58
|
+
// because `KHR_parallel_shader_compile` is there, so the check is the whole of what makes
|
|
59
|
+
// a compile synchronous: 17 programs cost 64 ms of main thread on a large scene, in
|
|
60
|
+
// blocks of up to 12 ms, and they land in the frames where the layers are still mounting.
|
|
61
|
+
// Debug keeps the check, because a broken shader has to report itself somewhere.
|
|
62
|
+
onCreated: (state) => {
|
|
63
|
+
state.gl.debug.checkShaderErrors = debug ?? false;
|
|
64
|
+
}, frameloop: debug ? 'always' : 'demand', resize: { debounce: 50 }, children: tree }) }) }));
|
|
53
65
|
}
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* Both helpers throw when they cannot find it, for the same reason: what reaches the driver
|
|
12
12
|
* otherwise is a compile error naming an undeclared identifier, a long way from the patch that
|
|
13
13
|
* failed to declare it.
|
|
14
|
+
*
|
|
15
|
+
* {@link sceneColorAnchor} is not about `main`, but it answers the same kind of question: where in a
|
|
16
|
+
* shader's own text a patch belongs.
|
|
14
17
|
*/
|
|
15
18
|
/**
|
|
16
19
|
* Declares GLSL above `main`, where file-scope declarations belong — samplers, varyings, functions.
|
|
@@ -47,4 +50,21 @@ export declare function openMain(source: string, glsl: string): string;
|
|
|
47
50
|
* @throws {Error} If the source does not contain the anchor.
|
|
48
51
|
*/
|
|
49
52
|
export declare function replaceAnchor(source: string, anchor: string, glsl: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Where the fragment's **scene** color is final: the line a patch that changes the scene must land
|
|
55
|
+
* in front of.
|
|
56
|
+
*
|
|
57
|
+
* `<opaque_fragment>` writes the scene color, and three's output chain follows it — tone mapping,
|
|
58
|
+
* the colorspace conversion, fog, dithering. This returns the first of those. A patch below any of
|
|
59
|
+
* them would edit a value the chain already transformed, so its strength would depend on how bright
|
|
60
|
+
* the pixel already was.
|
|
61
|
+
*
|
|
62
|
+
* Two spellings, because three resolves `#include` after every `onBeforeCompile` while troika
|
|
63
|
+
* resolves it earlier, in its own rebuild of `main`. The expanded spelling is the `#if` line rather
|
|
64
|
+
* than the tone-mapping line inside it: the renderer pins `NoToneMapping`, which leaves
|
|
65
|
+
* `TONE_MAPPING` undefined, and the preprocessor then strips that block and anything put in it.
|
|
66
|
+
* @param fragmentShader - The shader to search.
|
|
67
|
+
* @returns The anchor found, or `undefined` if the shader has no output chain.
|
|
68
|
+
*/
|
|
69
|
+
export declare function sceneColorAnchor(fragmentShader: string): string | undefined;
|
|
50
70
|
//# sourceMappingURL=shader-source.d.ts.map
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* Both helpers throw when they cannot find it, for the same reason: what reaches the driver
|
|
12
12
|
* otherwise is a compile error naming an undeclared identifier, a long way from the patch that
|
|
13
13
|
* failed to declare it.
|
|
14
|
+
*
|
|
15
|
+
* {@link sceneColorAnchor} is not about `main`, but it answers the same kind of question: where in a
|
|
16
|
+
* shader's own text a patch belongs.
|
|
14
17
|
*/
|
|
15
18
|
/**
|
|
16
19
|
* Declares GLSL above `main`, where file-scope declarations belong — samplers, varyings, functions.
|
|
@@ -56,6 +59,29 @@ export function replaceAnchor(source, anchor, glsl) {
|
|
|
56
59
|
throw new Error(`shader-source: the shader has no "${anchor}" to rewrite.`);
|
|
57
60
|
return source.slice(0, at) + glsl + source.slice(at + anchor.length);
|
|
58
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Where the fragment's **scene** color is final: the line a patch that changes the scene must land
|
|
64
|
+
* in front of.
|
|
65
|
+
*
|
|
66
|
+
* `<opaque_fragment>` writes the scene color, and three's output chain follows it — tone mapping,
|
|
67
|
+
* the colorspace conversion, fog, dithering. This returns the first of those. A patch below any of
|
|
68
|
+
* them would edit a value the chain already transformed, so its strength would depend on how bright
|
|
69
|
+
* the pixel already was.
|
|
70
|
+
*
|
|
71
|
+
* Two spellings, because three resolves `#include` after every `onBeforeCompile` while troika
|
|
72
|
+
* resolves it earlier, in its own rebuild of `main`. The expanded spelling is the `#if` line rather
|
|
73
|
+
* than the tone-mapping line inside it: the renderer pins `NoToneMapping`, which leaves
|
|
74
|
+
* `TONE_MAPPING` undefined, and the preprocessor then strips that block and anything put in it.
|
|
75
|
+
* @param fragmentShader - The shader to search.
|
|
76
|
+
* @returns The anchor found, or `undefined` if the shader has no output chain.
|
|
77
|
+
*/
|
|
78
|
+
export function sceneColorAnchor(fragmentShader) {
|
|
79
|
+
const include = /*glsl*/ `#include <tonemapping_fragment>`;
|
|
80
|
+
const expanded = /*glsl*/ `#if defined( TONE_MAPPING )`;
|
|
81
|
+
if (fragmentShader.includes(include))
|
|
82
|
+
return include;
|
|
83
|
+
return fragmentShader.includes(expanded) ? expanded : undefined;
|
|
84
|
+
}
|
|
59
85
|
/**
|
|
60
86
|
* Rewrites the first opening of `main`, splicing rather than replacing — so a `$` in the injected
|
|
61
87
|
* GLSL cannot be read as a replacement pattern.
|
|
@@ -12,6 +12,8 @@ import { type PropsWithChildren } from 'react';
|
|
|
12
12
|
* unmount/remount, the same element is re-attached and the scene's own effects
|
|
13
13
|
* re-add their folders. Rendered as an ancestor of the scene, so on unmount
|
|
14
14
|
* React tears the scene's folders down before this component destroys the panel.
|
|
15
|
+
* It also registers the panel's single change handler, which asks for a redraw. Every folder in the
|
|
16
|
+
* panel inherits it, so no controller carries one of its own.
|
|
15
17
|
* @param props - Component props; `children` is the scene subtree that consumes
|
|
16
18
|
* the GUI via `useGui`.
|
|
17
19
|
* @returns The children wrapped with the GUI provided through context.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import './Gui.module.css'; // Global overrides of lil-gui's stock styling (checkbox row layout).
|
|
3
|
+
import { useThree } from '@react-three/fiber';
|
|
3
4
|
import { GUI } from 'lil-gui';
|
|
4
5
|
import { useEffect, useState } from 'react';
|
|
5
6
|
import { GuiContext } from '../debug-context';
|
|
@@ -15,6 +16,8 @@ import { GuiContext } from '../debug-context';
|
|
|
15
16
|
* unmount/remount, the same element is re-attached and the scene's own effects
|
|
16
17
|
* re-add their folders. Rendered as an ancestor of the scene, so on unmount
|
|
17
18
|
* React tears the scene's folders down before this component destroys the panel.
|
|
19
|
+
* It also registers the panel's single change handler, which asks for a redraw. Every folder in the
|
|
20
|
+
* panel inherits it, so no controller carries one of its own.
|
|
18
21
|
* @param props - Component props; `children` is the scene subtree that consumes
|
|
19
22
|
* the GUI via `useGui`.
|
|
20
23
|
* @returns The children wrapped with the GUI provided through context.
|
|
@@ -22,11 +25,18 @@ import { GuiContext } from '../debug-context';
|
|
|
22
25
|
export function Gui(props) {
|
|
23
26
|
const { children } = props;
|
|
24
27
|
const [gui] = useState(() => new GUI({ autoPlace: false, closeFolders: true }));
|
|
28
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
25
29
|
useEffect(() => {
|
|
26
30
|
gui.domElement.classList.add('lil-auto-place', 'autoPlace');
|
|
27
31
|
document.body.appendChild(gui.domElement);
|
|
28
32
|
gui.close();
|
|
33
|
+
// One handler for the whole panel: lil-gui walks a changed controller up its parent chain, so
|
|
34
|
+
// this fires for every controller in every folder and no folder registers its own. It calls
|
|
35
|
+
// whichever `invalidate` the current mount installed — in the external embed `ExternalCanvas`
|
|
36
|
+
// replaces it with the host's `requestFrame`, which is how a GUI change reaches a host that owns
|
|
37
|
+
// the frame.
|
|
38
|
+
gui.onChange(() => invalidate());
|
|
29
39
|
return () => gui.destroy();
|
|
30
|
-
}, [gui]);
|
|
40
|
+
}, [gui, invalidate]);
|
|
31
41
|
return _jsx(GuiContext, { value: gui, children: children });
|
|
32
42
|
}
|
|
@@ -82,8 +82,7 @@ function mountExternal(context, requestFrame, onFrame) {
|
|
|
82
82
|
camera.matrixAutoUpdate = false;
|
|
83
83
|
camera.matrixWorldAutoUpdate = false;
|
|
84
84
|
camera.manual = true;
|
|
85
|
-
// Constructed bare on purpose: the
|
|
86
|
-
// output — and anything the renderer would opine on is left unset.
|
|
85
|
+
// Constructed bare on purpose: anything the renderer would opine on is left unset.
|
|
87
86
|
const glRenderer = new WebGLRenderer({ canvas, context });
|
|
88
87
|
// Never clear: the host's own layers are already on the canvas when this renderer draws.
|
|
89
88
|
glRenderer.autoClear = false;
|
|
@@ -106,10 +105,10 @@ function mountExternal(context, requestFrame, onFrame) {
|
|
|
106
105
|
void root
|
|
107
106
|
.configure({
|
|
108
107
|
gl: glRenderer,
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
108
|
+
// R3F force-writes ACES filmic tone mapping onto a fresh renderer unless `flat` is set, which
|
|
109
|
+
// is the one value landing on three's own default, `NoToneMapping`. This renderer does not
|
|
110
|
+
// tone map: `Lighting` keeps the rig's light sum in range by construction, rather than
|
|
111
|
+
// compressing an unbounded one back into range afterward.
|
|
113
112
|
flat: true,
|
|
114
113
|
// The cast widens past R3F's frustum-camera union; `manual` above is what keeps R3F's own
|
|
115
114
|
// camera handling off it.
|
|
@@ -50,21 +50,34 @@ export function setExternalCameraProjection(camera, mainMatrix) {
|
|
|
50
50
|
_mainInverse.copy(M).invert();
|
|
51
51
|
_near.set(0, 0, 1).applyMatrix4(_mainInverse);
|
|
52
52
|
_far.set(0, 0, -1).applyMatrix4(_mainInverse);
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const nearZ = _near.distanceToSquared(_position) <= _far.distanceToSquared(_position) ? 1 : -1;
|
|
54
|
+
if (nearZ === 1) {
|
|
55
|
+
_direction.copy(_far).sub(_near).normalize();
|
|
55
56
|
}
|
|
56
57
|
else {
|
|
57
|
-
_direction.copy(
|
|
58
|
+
_direction.copy(_near).sub(_far).normalize();
|
|
58
59
|
}
|
|
59
|
-
// Step 3:
|
|
60
|
+
// Step 3: the camera's up axis, measured rather than chosen, and a rigid view matrix from it.
|
|
61
|
+
//
|
|
62
|
+
// Any up not parallel to the view direction gives a valid (P′, V′) pair for *drawing*, because P
|
|
63
|
+
// is recomputed from M · matrixWorld at the end and absorbs whatever roll V′ was given. The axis
|
|
64
|
+
// is also *read*, though: `Lighting` takes the plan's heading from the camera's up axis, so a
|
|
65
|
+
// chosen one aims the key light at an angle the host never had — a fixed world +z aimed it from
|
|
66
|
+
// the far side of the plan at every bearing, and every face turned toward the camera lost its
|
|
67
|
+
// direct light.
|
|
68
|
+
//
|
|
69
|
+
// Two points at one clip x and one clip z, one at the top of the screen and one at the bottom,
|
|
70
|
+
// differ by a vector along the camera's up axis alone: a constant clip z is a constant view-space
|
|
71
|
+
// z, so the forward components cancel and the difference is (0, Δy, 0) in view space. Clip y
|
|
72
|
+
// grows upward under every projection, maplibre's reverse-z included, so this reads none of the
|
|
73
|
+
// host's own conventions — and unlike a chosen axis it has no degenerate view to fall back from,
|
|
74
|
+
// since the axis it recovers is perpendicular to the view direction by construction.
|
|
60
75
|
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
_up.
|
|
66
|
-
if (Math.abs(_up.dot(_direction)) > 0.999)
|
|
67
|
-
_up.set(0, 1, 0);
|
|
76
|
+
// Unprojected at `nearZ`, the near plane's own clip z: a point there is finite under either z
|
|
77
|
+
// convention, which an interior z is not.
|
|
78
|
+
_screenTop.set(0, 1, nearZ).applyMatrix4(_mainInverse);
|
|
79
|
+
_screenBottom.set(0, -1, nearZ).applyMatrix4(_mainInverse);
|
|
80
|
+
_up.copy(_screenTop).sub(_screenBottom).normalize();
|
|
68
81
|
_target.copy(_position).add(_direction);
|
|
69
82
|
camera.matrixWorld.lookAt(_position, _target, _up);
|
|
70
83
|
camera.matrixWorld.setPosition(_position);
|
|
@@ -84,5 +97,7 @@ const _position = new Vector3();
|
|
|
84
97
|
const _near = new Vector3();
|
|
85
98
|
const _far = new Vector3();
|
|
86
99
|
const _direction = new Vector3();
|
|
100
|
+
const _screenTop = new Vector3();
|
|
101
|
+
const _screenBottom = new Vector3();
|
|
87
102
|
const _target = new Vector3();
|
|
88
103
|
const _up = new Vector3();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { type CameraAPI, type CameraAxes, type CameraState, type ControlsConfig, type InertiaConfig, type PanConfig, type RollConfig, type TransitionOptions, useCameraAPI, useCameraMoveStart, useCameraState, type ZoomToOptions, } from './controls';
|
|
2
2
|
export { prepareDebug } from './debug/debug-chunk';
|
|
3
3
|
export { useDebugPanel } from './debug/debug-context';
|
|
4
|
+
export { type LightingProps, type ShadowProps } from './objects/lighting';
|
|
4
5
|
export * from './Renderer';
|
|
5
6
|
export { type CameraProps, type SmoothTime } from './space/Camera';
|
|
6
7
|
export { type CanvasPoint, type CanvasPointLike, type PlanPoint, type PlanPointLike, } from './space/Space';
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { declareBeforeMain } from '../core/shader-source';
|
|
4
4
|
import { ColorMaterial } from './materials/ColorMaterial';
|
|
5
|
-
import { DIM_INHERIT, DIM_UNIFORM } from './materials/dim';
|
|
5
|
+
import { DIM_INHERIT, DIM_UNIFORM } from './materials/effects/color-dim';
|
|
6
6
|
/**
|
|
7
7
|
* The scene's background: a screen-space quad drawn below every layer, replacing what a clear
|
|
8
8
|
* color would paint — and unlike one, dimmed with the venue. The root `Layer` renders it inside
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DIM_SCHEMA, DIM_UNIFORM, dimOrdered, instanceDim, isDimmed } from '../materials/dim';
|
|
1
|
+
import { DIM_SCHEMA, DIM_UNIFORM, dimOrdered, instanceDim, isDimmed, } from '../materials/effects/color-dim';
|
|
2
2
|
/**
|
|
3
3
|
* One layer's mesh bookkeeping: the bound mesh, which slot each def holds, and the paint order
|
|
4
4
|
* across them.
|
|
@@ -133,6 +133,12 @@ export class SlotContainer {
|
|
|
133
133
|
// `setPaintOrder` defaults to 0 and the component's order pass relabels it in the same effect
|
|
134
134
|
// flush, before anything draws.
|
|
135
135
|
this.orderBySlot[slot] = this.orderByDef.get(def) ?? 0;
|
|
136
|
+
// Seeded here as well, so the instance's first `setDimAt` — every instance writes its dim as it
|
|
137
|
+
// mounts — reads the split it already draws on rather than `undefined`, and leaves the order
|
|
138
|
+
// pass to the structural one. Without it, mounting a layer of n defs runs n full order passes.
|
|
139
|
+
// A dim that arrives with the sync still crosses the split and still re-orders, and a claim
|
|
140
|
+
// made before the layer's own dim is known is relabelled by `setPaintOrder` in the same flush.
|
|
141
|
+
this.dimmedBySlot[slot] = isDimmed(def.dim, this.layerDimmed);
|
|
136
142
|
return slot;
|
|
137
143
|
}
|
|
138
144
|
/**
|
|
@@ -2,7 +2,7 @@ import { useThree } from '@react-three/fiber';
|
|
|
2
2
|
import { use, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
3
|
import { registerHitResolver } from '../../events/pointer';
|
|
4
4
|
import { registerSlotWriter, subscribeDef, syncNow, versionOf } from '../../update';
|
|
5
|
-
import { LayerDimContext } from '../materials/dim';
|
|
5
|
+
import { LayerDimContext } from '../materials/effects/color-dim';
|
|
6
6
|
/**
|
|
7
7
|
* Creates one layer's container and keeps it wired to its layer for as long as the component is
|
|
8
8
|
* mounted: the membership's paint order, and how hits on its mesh resolve back to defs.
|
|
@@ -12,6 +12,16 @@ export interface DefsProps<T extends RenderableDef> {
|
|
|
12
12
|
* updated.
|
|
13
13
|
*/
|
|
14
14
|
defs: readonly T[];
|
|
15
|
+
/**
|
|
16
|
+
* How many of `defs` may mount their instances now, counted from the start of the list — the
|
|
17
|
+
* staged-mount slice, from `useStagedChildren`. Defaults to all of them, which is what a caller
|
|
18
|
+
* mounting a layer outside the scheduler wants.
|
|
19
|
+
*
|
|
20
|
+
* Only the instances are sliced. The container is still built, sized and paint-ordered from the
|
|
21
|
+
* whole of `defs`, so a growing prefix claims into buffers that were allocated once, and the
|
|
22
|
+
* order pass that labels them runs per membership change rather than per slice.
|
|
23
|
+
*/
|
|
24
|
+
mounted?: number;
|
|
15
25
|
}
|
|
16
26
|
/** Props for a component keeping one def's instance in sync. */
|
|
17
27
|
export interface InstanceProps<T extends RenderableDef, C> {
|
|
@@ -35,6 +35,11 @@ import { TextureBudgetContext, useResizeFactor } from './texture-budget';
|
|
|
35
35
|
*/
|
|
36
36
|
export function Images(props) {
|
|
37
37
|
const { defs, maxAtlasSize } = props;
|
|
38
|
+
// Handed to each atlas as a prefix of *its* placements rather than apportioned between them, and
|
|
39
|
+
// a split source has several placements per def — so a slice mounts somewhere between one and a
|
|
40
|
+
// few instances per granted child. The scheduler measures what a slice cost and sizes the next
|
|
41
|
+
// one from that, which is what keeps the budget whatever the ratio turns out to be here.
|
|
42
|
+
const mounted = props.mounted ?? defs.length;
|
|
38
43
|
const gl = useThree((state) => state.gl);
|
|
39
44
|
const maxTextureSize = maxAtlasSize ?? gl.capabilities.maxTextureSize;
|
|
40
45
|
// Repack when the *set* of images changes, via the "adjust state on prop change" pattern. The
|
|
@@ -48,7 +53,7 @@ export function Images(props) {
|
|
|
48
53
|
if (!sameSet(packed.defs, defs)) {
|
|
49
54
|
setPacked((current) => packImages(defs, maxTextureSize, current.generation + 1));
|
|
50
55
|
}
|
|
51
|
-
return (_jsx(_Fragment, { children: packed.atlases.map((atlas) => (_jsx(ImageAtlas, { atlas: atlas, defs: defs }, atlasKey(atlas, packed.generation)))) }));
|
|
56
|
+
return (_jsx(_Fragment, { children: packed.atlases.map((atlas) => (_jsx(ImageAtlas, { atlas: atlas, defs: defs, mounted: mounted }, atlasKey(atlas, packed.generation)))) }));
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
59
|
* Draws one atlas as an `InstancedMesh`: the R3F-owned mesh with the atlas texture as its
|
|
@@ -67,6 +72,8 @@ export function Images(props) {
|
|
|
67
72
|
*/
|
|
68
73
|
function ImageAtlas(props) {
|
|
69
74
|
const { atlas, defs } = props;
|
|
75
|
+
// Only the placements are staged; the container and the texture are built from the whole atlas.
|
|
76
|
+
const mounted = props.mounted ?? atlas.images.length;
|
|
70
77
|
const [texture] = useState(() => makeAtlasTexture(atlas.canvas));
|
|
71
78
|
const container = useContainer(() => new ImageContainer(atlas), defs);
|
|
72
79
|
const invalidate = useThree((state) => state.invalidate);
|
|
@@ -94,7 +101,7 @@ function ImageAtlas(props) {
|
|
|
94
101
|
// pair are placeholders the constructor demands (see `ImageMeshArgs`). The material has to be per
|
|
95
102
|
// mesh: `InstancedMeshExt` wraps whatever it draws with in a closure over its own textures, and two
|
|
96
103
|
// instanced meshes on one material would both render against the first one's.
|
|
97
|
-
return (_jsxs(_Fragment, { children: [_jsxs(InstancedMesh, { args: container.args, ref: (mesh) => container.bind(mesh), sortObjects: true, customSort: container.customSort, perObjectFrustumCulled: true, children: [_jsx("planeGeometry", {}), _jsx(TextureMaterial, { map: texture, uvOffset: UV_OFFSET_UNIFORM })] }), atlas.images.map((packed) => (_jsx(ImageInstance, { def: packed.def, packed: packed, container: container }, `${defKey(packed.def)}:${packed.uv.u0}:${packed.uv.v0}`)))] }));
|
|
104
|
+
return (_jsxs(_Fragment, { children: [_jsxs(InstancedMesh, { args: container.args, ref: (mesh) => container.bind(mesh), sortObjects: true, customSort: container.customSort, perObjectFrustumCulled: true, children: [_jsx("planeGeometry", {}), _jsx(TextureMaterial, { map: texture, uvOffset: UV_OFFSET_UNIFORM })] }), atlas.images.slice(0, mounted).map((packed) => (_jsx(ImageInstance, { def: packed.def, packed: packed, container: container }, `${defKey(packed.def)}:${packed.uv.u0}:${packed.uv.v0}`)))] }));
|
|
98
105
|
}
|
|
99
106
|
/**
|
|
100
107
|
* Whether two snapshots hold the same *set* of images, order aside — the question "does this need a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Vector4 } from 'three';
|
|
2
2
|
import { SlotContainer } from '../containers';
|
|
3
|
-
import { DIM_SCHEMA } from '../materials/dim';
|
|
3
|
+
import { DIM_SCHEMA } from '../materials/effects/color-dim';
|
|
4
4
|
/**
|
|
5
5
|
* The per-instance uniform each atlas mesh reads its texture region from. The container owns the
|
|
6
6
|
* name: it writes each slot's region here (`setUniformAt`) and hands it to the mesh's
|
|
@@ -5,7 +5,7 @@ import { usePointerProps } from '../../events/pointer';
|
|
|
5
5
|
import { isImageDefs, isLayerDefs, isLineDefs, isModelDefs, isShapeDefs, isTextDefs, } from '../../types/guards';
|
|
6
6
|
import { Images } from '../images';
|
|
7
7
|
import { Lines } from '../lines';
|
|
8
|
-
import { LayerDimContext } from '../materials/dim';
|
|
8
|
+
import { LayerDimContext } from '../materials/effects/color-dim';
|
|
9
9
|
import { Models } from '../models';
|
|
10
10
|
import { Shapes } from '../shapes';
|
|
11
11
|
import { Texts } from '../text';
|
|
@@ -64,7 +64,7 @@ function LayerNode(props) {
|
|
|
64
64
|
// visibility this is never a prop: nothing between here and the material has any use for it.
|
|
65
65
|
const inheritedDim = use(LayerDimContext);
|
|
66
66
|
const dim = layer.dim ?? inheritedDim;
|
|
67
|
-
const { children,
|
|
67
|
+
const { children, mounted } = useStagedChildren(layer);
|
|
68
68
|
useLayoutTargets(layer, children);
|
|
69
69
|
const pointerProps = usePointerProps(layer, visible);
|
|
70
70
|
let contents = null;
|
|
@@ -72,8 +72,8 @@ function LayerNode(props) {
|
|
|
72
72
|
const childStep = orderStep / children.length;
|
|
73
73
|
contents = children.map((child, index) => (_jsx(LayerNode, { layer: child, parentVisible: visible, orderBase: orderBase + index * childStep, orderStep: childStep }, child.name)));
|
|
74
74
|
}
|
|
75
|
-
else if (
|
|
76
|
-
contents = _jsx(LeafContent, { defs: children });
|
|
75
|
+
else if (mounted > 0) {
|
|
76
|
+
contents = _jsx(LeafContent, { defs: children, mounted: mounted });
|
|
77
77
|
}
|
|
78
78
|
const group = (_jsxs("group", { name: layer.name, visible: !layer.hidden, renderOrder: orderBase, ...pointerProps, children: [prelude, contents] }));
|
|
79
79
|
// Always provided, never conditionally on the layer declaring a `dim`: making the provider appear
|
|
@@ -98,17 +98,17 @@ function LayerNode(props) {
|
|
|
98
98
|
* @returns The container for the defs' kind.
|
|
99
99
|
*/
|
|
100
100
|
function LeafContent(props) {
|
|
101
|
-
const { defs } = props;
|
|
101
|
+
const { defs, mounted } = props;
|
|
102
102
|
let content = null;
|
|
103
103
|
if (isShapeDefs(defs))
|
|
104
|
-
content = _jsx(Shapes, { defs: defs });
|
|
104
|
+
content = _jsx(Shapes, { defs: defs, mounted: mounted });
|
|
105
105
|
else if (isImageDefs(defs))
|
|
106
|
-
content = _jsx(Images, { defs: defs });
|
|
106
|
+
content = _jsx(Images, { defs: defs, mounted: mounted });
|
|
107
107
|
else if (isTextDefs(defs))
|
|
108
|
-
content = _jsx(Texts, { defs: defs });
|
|
108
|
+
content = _jsx(Texts, { defs: defs, mounted: mounted });
|
|
109
109
|
else if (isLineDefs(defs))
|
|
110
|
-
content = _jsx(Lines, { defs: defs });
|
|
110
|
+
content = _jsx(Lines, { defs: defs, mounted: mounted });
|
|
111
111
|
else if (isModelDefs(defs))
|
|
112
|
-
content = _jsx(Models, { defs: defs });
|
|
112
|
+
content = _jsx(Models, { defs: defs, mounted: mounted });
|
|
113
113
|
return _jsx(Suspense, { fallback: null, children: content });
|
|
114
114
|
}
|