@expofp/renderer 3.2.5 → 3.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/dist/Renderer.d.ts +88 -0
- package/dist/Renderer.js +53 -0
- package/dist/controls/CameraControls.d.ts +102 -0
- package/dist/controls/CameraControls.js +239 -0
- package/dist/controls/camera-api.d.ts +206 -0
- package/dist/controls/camera-api.js +181 -0
- package/dist/controls/camera-move.d.ts +13 -0
- package/dist/controls/camera-move.js +18 -0
- package/dist/controls/camera-state.d.ts +40 -0
- package/dist/controls/camera-state.js +65 -0
- package/dist/controls/gestures.d.ts +70 -0
- package/dist/controls/gestures.js +84 -0
- package/dist/controls/handlers/Inertia.d.ts +111 -0
- package/dist/controls/handlers/Inertia.js +215 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +32 -0
- package/dist/controls/handlers/Pitch.d.ts +94 -0
- package/dist/controls/handlers/Pitch.js +191 -0
- package/dist/controls/handlers/Roll.d.ts +103 -0
- package/dist/controls/handlers/Roll.js +182 -0
- package/dist/controls/handlers/Zoom.d.ts +15 -0
- package/dist/controls/handlers/Zoom.js +26 -0
- package/dist/controls/index.d.ts +56 -0
- package/dist/controls/index.js +58 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +93 -0
- package/dist/core/InstanceUniforms.js +266 -0
- package/dist/core/SquareDataTexture.d.ts +277 -0
- package/dist/core/SquareDataTexture.js +476 -0
- package/dist/core/batch/BatchedMesh.d.ts +74 -0
- package/dist/core/batch/BatchedMesh.js +149 -0
- package/dist/core/instanced/FrustumCulling.d.ts +141 -0
- package/dist/core/instanced/FrustumCulling.js +246 -0
- package/dist/core/instanced/InstancedMesh.d.ts +302 -0
- package/dist/core/instanced/InstancedMesh.js +497 -0
- package/dist/core/instanced/Instances.d.ts +119 -0
- package/dist/core/instanced/Instances.js +230 -0
- package/dist/core/instanced/Raycasting.d.ts +118 -0
- package/dist/core/instanced/Raycasting.js +134 -0
- package/dist/core/instanced/instanced-material.d.ts +77 -0
- package/dist/core/instanced/instanced-material.js +224 -0
- package/dist/core/lines/BatchedLines.d.ts +135 -0
- package/dist/core/lines/BatchedLines.js +213 -0
- package/dist/core/lines/FrustumCulling.d.ts +43 -0
- package/dist/core/lines/FrustumCulling.js +85 -0
- package/dist/core/lines/Raycasting.d.ts +44 -0
- package/dist/core/lines/Raycasting.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +115 -0
- package/dist/core/shader-source.d.ts +50 -0
- package/dist/core/shader-source.js +84 -0
- package/dist/core/text/BatchedText.d.ts +227 -0
- package/dist/core/text/BatchedText.js +467 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +194 -0
- package/dist/core/text/Text.d.ts +73 -0
- package/dist/core/text/Text.js +215 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +121 -0
- package/dist/core/text/glyph-packing.d.ts +17 -0
- package/dist/core/text/glyph-packing.js +61 -0
- package/dist/core/typed-array.d.ts +16 -0
- package/dist/core/typed-array.js +20 -0
- package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
- package/dist/debug/components/DebugPanelProvider.js +38 -0
- package/dist/debug/components/DebugTools.d.ts +22 -0
- package/dist/debug/components/DebugTools.js +28 -0
- package/dist/debug/components/Gui.d.ts +20 -0
- package/dist/debug/components/Gui.js +32 -0
- package/dist/debug/components/MemoryInfo.d.ts +22 -0
- package/dist/debug/components/MemoryInfo.js +42 -0
- package/dist/debug/components/Panel.d.ts +31 -0
- package/dist/debug/components/Panel.js +98 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +51 -0
- package/dist/debug/index.js +64 -0
- package/dist/events/LayoutDriver.d.ts +31 -0
- package/dist/events/LayoutDriver.js +88 -0
- package/dist/events/PointerMissed.d.ts +23 -0
- package/dist/events/PointerMissed.js +58 -0
- package/dist/events/def-culling.d.ts +46 -0
- package/dist/events/def-culling.js +69 -0
- package/dist/events/layout-targets.d.ts +104 -0
- package/dist/events/layout-targets.js +245 -0
- package/dist/events/pointer.d.ts +118 -0
- package/dist/events/pointer.js +168 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +178 -0
- package/dist/external/external-camera.d.ts +17 -0
- package/dist/external/external-camera.js +82 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +11 -783
- package/dist/index.js +12 -8031
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/Lighting.d.ts +20 -0
- package/dist/objects/Lighting.js +85 -0
- package/dist/objects/containers/SlotContainer.d.ts +266 -0
- package/dist/objects/containers/SlotContainer.js +251 -0
- package/dist/objects/containers/hooks.d.ts +46 -0
- package/dist/objects/containers/hooks.js +118 -0
- package/dist/objects/containers/index.d.ts +6 -0
- package/dist/objects/containers/index.js +4 -0
- package/dist/objects/containers/keys.d.ts +10 -0
- package/dist/objects/containers/keys.js +18 -0
- package/dist/objects/containers/mesh-writes.d.ts +106 -0
- package/dist/objects/containers/mesh-writes.js +97 -0
- package/dist/objects/containers/props.d.ts +23 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +83 -0
- package/dist/objects/images/Images.d.ts +37 -0
- package/dist/objects/images/Images.js +204 -0
- package/dist/objects/images/atlas-packer.d.ts +128 -0
- package/dist/objects/images/atlas-packer.js +315 -0
- package/dist/objects/images/image-container.d.ts +51 -0
- package/dist/objects/images/image-container.js +70 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/layer/Layer.d.ts +27 -0
- package/dist/objects/layer/Layer.js +114 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/mount-scheduler.d.ts +102 -0
- package/dist/objects/layer/mount-scheduler.js +145 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -0
- package/dist/objects/lines/LineInstance.d.ts +21 -0
- package/dist/objects/lines/LineInstance.js +73 -0
- package/dist/objects/lines/Lines.d.ts +17 -0
- package/dist/objects/lines/Lines.js +29 -0
- package/dist/objects/lines/index.d.ts +2 -0
- package/dist/objects/lines/index.js +1 -0
- package/dist/objects/lines/line-container.d.ts +35 -0
- package/dist/objects/lines/line-container.js +40 -0
- package/dist/objects/materials/ColorMaterial.d.ts +85 -0
- package/dist/objects/materials/ColorMaterial.js +89 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +72 -0
- package/dist/objects/materials/ModelMaterial.d.ts +41 -0
- package/dist/objects/materials/ModelMaterial.js +84 -0
- package/dist/objects/materials/TextureMaterial.d.ts +30 -0
- package/dist/objects/materials/TextureMaterial.js +29 -0
- package/dist/objects/materials/dim.d.ts +123 -0
- package/dist/objects/materials/dim.js +213 -0
- package/dist/objects/materials/shared.d.ts +6 -0
- package/dist/objects/materials/shared.js +51 -0
- package/dist/objects/models/ModelInstance.d.ts +22 -0
- package/dist/objects/models/ModelInstance.js +33 -0
- package/dist/objects/models/Models.d.ts +22 -0
- package/dist/objects/models/Models.js +177 -0
- package/dist/objects/models/gltf-cache.d.ts +17 -0
- package/dist/objects/models/gltf-cache.js +50 -0
- package/dist/objects/models/index.d.ts +2 -0
- package/dist/objects/models/index.js +1 -0
- package/dist/objects/models/model-asset.d.ts +48 -0
- package/dist/objects/models/model-asset.js +77 -0
- package/dist/objects/models/model-container.d.ts +139 -0
- package/dist/objects/models/model-container.js +227 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +40 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +120 -0
- package/dist/objects/shapes/index.d.ts +2 -0
- package/dist/objects/shapes/index.js +1 -0
- package/dist/objects/shapes/shape-container.d.ts +165 -0
- package/dist/objects/shapes/shape-container.js +272 -0
- package/dist/objects/shapes/shape-geometry.d.ts +27 -0
- package/dist/objects/shapes/shape-geometry.js +94 -0
- package/dist/objects/text/TextInstance.d.ts +22 -0
- package/dist/objects/text/TextInstance.js +95 -0
- package/dist/objects/text/Texts.d.ts +30 -0
- package/dist/objects/text/Texts.js +41 -0
- package/dist/objects/text/index.d.ts +2 -0
- package/dist/objects/text/index.js +1 -0
- package/dist/objects/text/text-container.d.ts +105 -0
- package/dist/objects/text/text-container.js +206 -0
- package/dist/objects/text/text-layout.d.ts +83 -0
- package/dist/objects/text/text-layout.js +154 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +44 -0
- package/dist/space/Scene.js +112 -0
- package/dist/space/Space.d.ts +288 -0
- package/dist/space/Space.js +329 -0
- package/dist/space/VisibleRect.d.ts +72 -0
- package/dist/space/VisibleRect.js +129 -0
- package/dist/space/camera-handle.d.ts +67 -0
- package/dist/space/camera-handle.js +63 -0
- package/dist/space/camera-kind.d.ts +26 -0
- package/dist/space/camera-kind.js +30 -0
- package/dist/space/canvas-size.d.ts +24 -0
- package/dist/space/canvas-size.js +20 -0
- package/dist/space/clipping.d.ts +55 -0
- package/dist/space/clipping.js +108 -0
- package/dist/space/layout-measure.d.ts +65 -0
- package/dist/space/layout-measure.js +173 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +169 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +183 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +14 -0
- package/dist/types/guards.js +42 -0
- package/dist/update/index.d.ts +3 -0
- package/dist/update/index.js +2 -0
- package/dist/update/slot-writers.d.ts +30 -0
- package/dist/update/slot-writers.js +72 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +98 -0
- package/dist/utils/arrays.d.ts +13 -0
- package/dist/utils/arrays.js +20 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +27 -0
- package/dist/utils/logging.d.ts +26 -0
- package/dist/utils/logging.js +47 -0
- package/dist/utils/math.d.ts +24 -0
- package/dist/utils/math.js +27 -0
- package/package.json +67 -19
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { use, useEffect } from 'react';
|
|
2
|
+
import { useGui } from '../../debug/debug-context';
|
|
3
|
+
import { isLayerDefs } from '../../types/guards';
|
|
4
|
+
import { childrenOf, subscribeDef, update } from '../../update';
|
|
5
|
+
import { MountSchedulerContext } from './mount-scheduler';
|
|
6
|
+
/**
|
|
7
|
+
* Adds a visibility checkbox for one layer: labeled with the layer's name, checked while the
|
|
8
|
+
* layer is visible. Toggling flips `layer.hidden` and calls `update(layer)` — exactly the write an
|
|
9
|
+
* SDK caller would make, so the checkbox exercises the real update path. The controller binds a
|
|
10
|
+
* getter/setter proxy over the def and `listen()`s, so a `hidden` change made elsewhere shows up
|
|
11
|
+
* in the checkbox too.
|
|
12
|
+
* @param folder - The folder to add the checkbox to.
|
|
13
|
+
* @param layer - The layer the checkbox controls.
|
|
14
|
+
*/
|
|
15
|
+
function addVisibilityToggle(folder, layer) {
|
|
16
|
+
const proxy = {
|
|
17
|
+
get visible() {
|
|
18
|
+
return !layer.hidden;
|
|
19
|
+
},
|
|
20
|
+
set visible(value) {
|
|
21
|
+
layer.hidden = !value;
|
|
22
|
+
update(layer);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
folder.add(proxy, 'visible').name(layer.name).listen();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns a sync function that rebuilds `folder`'s checkboxes when the listed layers changed since
|
|
29
|
+
* the last call, and is a cheap no-op otherwise — safe to subscribe to chatty sources (every
|
|
30
|
+
* `update()` on a layer, every scheduler admission) without churning the GUI.
|
|
31
|
+
* @param folder - The folder holding the checkboxes.
|
|
32
|
+
* @param list - Reads the layers to list.
|
|
33
|
+
* @returns The sync function.
|
|
34
|
+
*/
|
|
35
|
+
function makeSyncToggles(folder, list) {
|
|
36
|
+
let shown = [];
|
|
37
|
+
return () => {
|
|
38
|
+
const layers = list();
|
|
39
|
+
if (layers.length === shown.length && layers.every((layer, i) => layer === shown[i]))
|
|
40
|
+
return;
|
|
41
|
+
shown = layers;
|
|
42
|
+
for (const controller of [...folder.controllers])
|
|
43
|
+
controller.destroy();
|
|
44
|
+
for (const layer of layers)
|
|
45
|
+
addVisibilityToggle(folder, layer);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds the layer-visibility debug GUI: called by the tree's root `Layer` alone, a no-op without
|
|
50
|
+
* a debug GUI in context. Two folders, one checkbox per layer:
|
|
51
|
+
*
|
|
52
|
+
* - **Main layers** — the root's direct child layers, in child order, kept current through the
|
|
53
|
+
* same store subscription `update()` drives (a root membership change rebuilds the rows).
|
|
54
|
+
* - **Content layers** — every leaf (shape) layer in the tree, in the mount scheduler's
|
|
55
|
+
* registration order, which is the flattened leaf order. No new bookkeeping tracks the leaves:
|
|
56
|
+
* `useStagedMount` already registers each one with the scheduler, so this folder mirrors the
|
|
57
|
+
* scheduler's queue and follows leaves as they mount and unmount. Without a scheduler in
|
|
58
|
+
* context (a plain `<Layer>` mount outside `Scene`) the folder stays empty.
|
|
59
|
+
*
|
|
60
|
+
* The effect runs entirely against the defs and the GUI — toggles never re-render the layer tree
|
|
61
|
+
* beyond what `update()` itself triggers.
|
|
62
|
+
* @param layer - The tree's root layer def.
|
|
63
|
+
*/
|
|
64
|
+
export function useVisibilityGui(layer) {
|
|
65
|
+
const gui = useGui();
|
|
66
|
+
const scheduler = use(MountSchedulerContext);
|
|
67
|
+
// This effect runs after every descendant's, so by first sync the scheduler's queue already
|
|
68
|
+
// holds the whole initial tree's leaves.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!gui)
|
|
71
|
+
return undefined;
|
|
72
|
+
const main = gui.addFolder('Main layers');
|
|
73
|
+
const syncMain = makeSyncToggles(main, () => {
|
|
74
|
+
const children = childrenOf(layer);
|
|
75
|
+
return isLayerDefs(children) ? children : [];
|
|
76
|
+
});
|
|
77
|
+
syncMain();
|
|
78
|
+
const unsubscribeMain = subscribeDef(layer, syncMain);
|
|
79
|
+
const content = gui.addFolder('Content layers');
|
|
80
|
+
let unsubscribeContent;
|
|
81
|
+
if (scheduler) {
|
|
82
|
+
const syncContent = makeSyncToggles(content, () => scheduler.leaves());
|
|
83
|
+
syncContent();
|
|
84
|
+
unsubscribeContent = scheduler.subscribe(syncContent);
|
|
85
|
+
}
|
|
86
|
+
return () => {
|
|
87
|
+
unsubscribeMain();
|
|
88
|
+
unsubscribeContent?.();
|
|
89
|
+
main.destroy();
|
|
90
|
+
content.destroy();
|
|
91
|
+
};
|
|
92
|
+
}, [gui, scheduler, layer]);
|
|
93
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type LineDef } from '../../types/defs';
|
|
2
|
+
import { type InstanceProps } from '../containers';
|
|
3
|
+
import { type LineContainer } from './line-container';
|
|
4
|
+
/** Props for {@link LineInstance}. */
|
|
5
|
+
export type LineInstanceProps = InstanceProps<LineDef, LineContainer>;
|
|
6
|
+
/**
|
|
7
|
+
* Keeps one line's instance current: claims a slot on mount, writes the def's endpoints, color,
|
|
8
|
+
* width and visibility on mount and after every `update(def)`, and releases the slot on unmount.
|
|
9
|
+
*
|
|
10
|
+
* Renders no scene node — the batch belongs to the container. Nothing here can force a re-claim:
|
|
11
|
+
* unlike a shape, whose geometry is its slot's resource, a line is the same segment template
|
|
12
|
+
* wherever it runs, so moving one is an ordinary write and its slot survives.
|
|
13
|
+
*
|
|
14
|
+
* Memoized for the same reason as `ShapeInstance`: a structural change hands the layer a fresh
|
|
15
|
+
* `defs` snapshot, so element-identity bailout never applies and only prop equality keeps unchanged
|
|
16
|
+
* siblings from re-rendering. Correctness never depends on it.
|
|
17
|
+
* @param props - The def and its container.
|
|
18
|
+
* @returns `null`; the instance lives in the batch, not the scene graph.
|
|
19
|
+
*/
|
|
20
|
+
export declare const LineInstance: import("react").MemoExoticComponent<(props: LineInstanceProps) => null>;
|
|
21
|
+
//# sourceMappingURL=LineInstance.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { Vector3 } from 'three';
|
|
3
|
+
import { createLogger } from '../../utils/logging';
|
|
4
|
+
import { useDefSync, writeColorIfChanged } from '../containers';
|
|
5
|
+
const logger = createLogger('line-instance');
|
|
6
|
+
/**
|
|
7
|
+
* Keeps one line's instance current: claims a slot on mount, writes the def's endpoints, color,
|
|
8
|
+
* width and visibility on mount and after every `update(def)`, and releases the slot on unmount.
|
|
9
|
+
*
|
|
10
|
+
* Renders no scene node — the batch belongs to the container. Nothing here can force a re-claim:
|
|
11
|
+
* unlike a shape, whose geometry is its slot's resource, a line is the same segment template
|
|
12
|
+
* wherever it runs, so moving one is an ordinary write and its slot survives.
|
|
13
|
+
*
|
|
14
|
+
* Memoized for the same reason as `ShapeInstance`: a structural change hands the layer a fresh
|
|
15
|
+
* `defs` snapshot, so element-identity bailout never applies and only prop equality keeps unchanged
|
|
16
|
+
* siblings from re-rendering. Correctness never depends on it.
|
|
17
|
+
* @param props - The def and its container.
|
|
18
|
+
* @returns `null`; the instance lives in the batch, not the scene graph.
|
|
19
|
+
*/
|
|
20
|
+
export const LineInstance = memo(function LineInstance(props) {
|
|
21
|
+
const { def, container } = props;
|
|
22
|
+
useDefSync(container, def, null, (slot) => {
|
|
23
|
+
writeLinePoints(container, slot, def);
|
|
24
|
+
writeColorIfChanged(container.mesh, slot, def, logger);
|
|
25
|
+
writeLineWidth(container, slot, def);
|
|
26
|
+
});
|
|
27
|
+
return null;
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Writes a line's endpoints into its instance.
|
|
31
|
+
*
|
|
32
|
+
* A `LineLike` is 2D plus an optional elevation, which is what places the segment on its layer's
|
|
33
|
+
* plane; the batch compares and skips, so a def that only changed color pays two vector fills.
|
|
34
|
+
* @param container - The container holding the instance.
|
|
35
|
+
* @param slot - The instance's slot.
|
|
36
|
+
* @param def - The line def supplying the endpoints.
|
|
37
|
+
*/
|
|
38
|
+
function writeLinePoints(container, slot, def) {
|
|
39
|
+
const { p0, p1, elevation = 0 } = def.points;
|
|
40
|
+
_start.set(p0.x, p0.y, elevation);
|
|
41
|
+
_end.set(p1.x, p1.y, elevation);
|
|
42
|
+
// Compared before writing, like every other instance write: a color-only `update(def)` re-runs the
|
|
43
|
+
// whole sync, and an unchanged segment costs six comparisons instead of two uniform uploads and a
|
|
44
|
+
// bounds invalidation.
|
|
45
|
+
container.mesh.getPointsAt(slot, _heldStart, _heldEnd);
|
|
46
|
+
if (_heldStart.equals(_start) && _heldEnd.equals(_end))
|
|
47
|
+
return;
|
|
48
|
+
container.mesh.setPointsAt(slot, _start, _end);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Writes a line's width into its instance, skipping the upload when it did not change.
|
|
52
|
+
*
|
|
53
|
+
* Clamped at zero, which is where the def's contract and the batch's part ways: the batch reads a
|
|
54
|
+
* negative width as "this member has none of its own" and falls back to the material's, an
|
|
55
|
+
* affordance for callers that never set one. A `LineDef` always carries a width, so a negative one
|
|
56
|
+
* is bad input rather than a request to inherit — and it must not come out thicker than a zero.
|
|
57
|
+
* @param container - The container holding the instance.
|
|
58
|
+
* @param slot - The instance's slot.
|
|
59
|
+
* @param def - The line def supplying the width.
|
|
60
|
+
*/
|
|
61
|
+
function writeLineWidth(container, slot, def) {
|
|
62
|
+
const width = Math.max(def.width, 0);
|
|
63
|
+
if (container.mesh.getWidthAt(slot) === width)
|
|
64
|
+
return;
|
|
65
|
+
container.mesh.setWidthAt(slot, width);
|
|
66
|
+
}
|
|
67
|
+
// Scratch shared by every endpoint write: filled and consumed within a single synchronous
|
|
68
|
+
// statement, so a write costs one buffer upload and no allocation. The `_held*` pair is only ever
|
|
69
|
+
// read back and compared, so it never collides with the endpoints being written.
|
|
70
|
+
const _start = new Vector3();
|
|
71
|
+
const _end = new Vector3();
|
|
72
|
+
const _heldStart = new Vector3();
|
|
73
|
+
const _heldEnd = new Vector3();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type LineDef } from '../../types/defs';
|
|
2
|
+
import { type DefsProps } from '../containers';
|
|
3
|
+
/** Props for {@link Lines}. */
|
|
4
|
+
export type LinesProps = DefsProps<LineDef>;
|
|
5
|
+
/**
|
|
6
|
+
* Draws a collection of lines as members of one `BatchedLines`, so a layer of route segments
|
|
7
|
+
* renders in a single instanced draw.
|
|
8
|
+
*
|
|
9
|
+
* Membership is reconciled by React exactly as it is for shapes: a new `defs` array re-renders this
|
|
10
|
+
* component and React mounts or unmounts only the delta, with no differ of our own. Order is applied
|
|
11
|
+
* as it is for images — the container labels each slot with its def's place in the layer, and the
|
|
12
|
+
* batch's `customSort` orders the draw list by it, so a reorder writes nothing to the GPU.
|
|
13
|
+
* @param props - The lines to draw.
|
|
14
|
+
* @returns The batch and one instance component per line.
|
|
15
|
+
*/
|
|
16
|
+
export declare function Lines(props: LinesProps): import("react").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=Lines.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BatchedLines } from '../../core/lines/BatchedLines';
|
|
3
|
+
import { defKey, useContainer } from '../containers';
|
|
4
|
+
import { LineMaterial } from '../materials/LineMaterial';
|
|
5
|
+
import { LineContainer } from './line-container';
|
|
6
|
+
import { LineInstance } from './LineInstance';
|
|
7
|
+
/**
|
|
8
|
+
* Draws a collection of lines as members of one `BatchedLines`, so a layer of route segments
|
|
9
|
+
* renders in a single instanced draw.
|
|
10
|
+
*
|
|
11
|
+
* Membership is reconciled by React exactly as it is for shapes: a new `defs` array re-renders this
|
|
12
|
+
* component and React mounts or unmounts only the delta, with no differ of our own. Order is applied
|
|
13
|
+
* as it is for images — the container labels each slot with its def's place in the layer, and the
|
|
14
|
+
* batch's `customSort` orders the draw list by it, so a reorder writes nothing to the GPU.
|
|
15
|
+
* @param props - The lines to draw.
|
|
16
|
+
* @returns The batch and one instance component per line.
|
|
17
|
+
*/
|
|
18
|
+
export function Lines(props) {
|
|
19
|
+
const { defs } = props;
|
|
20
|
+
// Sized from the first render's defs; membership changes grow the batch in place, never rebuild
|
|
21
|
+
// it. The container is bookkeeping only, so the copy StrictMode's double-invoked initializer
|
|
22
|
+
// discards is plain garbage.
|
|
23
|
+
const container = useContainer(() => new LineContainer(defs), defs);
|
|
24
|
+
// `args` is diffed element-wise and a change would dispose and rebuild the batch, orphaning every
|
|
25
|
+
// claimed slot — it is a frozen snapshot, so that never happens. The `ref` arrow is
|
|
26
|
+
// deliberate: `ref={container.bind}` would make the React Compiler infer `container` itself as a
|
|
27
|
+
// ref and reject its render-time reads.
|
|
28
|
+
return (_jsxs(_Fragment, { children: [_jsx(BatchedLines, { args: container.args, ref: (mesh) => container.bind(mesh), customSort: container.customSort, children: _jsx(LineMaterial, {}) }), defs.map((def) => (_jsx(LineInstance, { def: def, container: container }, defKey(def))))] }));
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Lines } from './Lines';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type InstancedMeshExtParams } from '../../core/instanced/InstancedMesh';
|
|
2
|
+
import { type BatchedLinesExt } from '../../core/lines/BatchedLines';
|
|
3
|
+
import { type LineDef } from '../../types/defs';
|
|
4
|
+
import { SlotContainer } from '../containers';
|
|
5
|
+
/**
|
|
6
|
+
* The constructor tuple R3F builds a line batch from, passed as `<BatchedLines args={…}>`. R3F diffs
|
|
7
|
+
* `args` element-wise and rebuilds the object when any element changes, so this is snapshotted once
|
|
8
|
+
* and never mutated.
|
|
9
|
+
*
|
|
10
|
+
* Only the options: the batch builds its own segment template and takes its material from the
|
|
11
|
+
* `<LineMaterial>` child R3F mounts under it.
|
|
12
|
+
*/
|
|
13
|
+
export type LineMeshArgs = [params: InstancedMeshExtParams];
|
|
14
|
+
/**
|
|
15
|
+
* One line layer's bookkeeping.
|
|
16
|
+
*
|
|
17
|
+
* Thinner than the shape container and the image one alike, because a line has no per-instance
|
|
18
|
+
* resource to allocate against — every member draws the same segment template, placed by its own
|
|
19
|
+
* endpoints. That is why the resource type is `null` here: nothing about a `LineDef` can force a
|
|
20
|
+
* re-claim, so a def keeps its slot for as long as it is mounted, `points` included.
|
|
21
|
+
*/
|
|
22
|
+
export declare class LineContainer extends SlotContainer<null, BatchedLinesExt, LineDef> {
|
|
23
|
+
/** The constructor args R3F builds the batch from; a frozen snapshot, never mutated. */
|
|
24
|
+
readonly args: LineMeshArgs;
|
|
25
|
+
/**
|
|
26
|
+
* Builds a line layer's bookkeeping, sized to hold `defs` exactly so a layer that mounts full
|
|
27
|
+
* never reallocates. The batch itself is R3F's — the component renders `<BatchedLines>` and hands
|
|
28
|
+
* it over through {@link SlotContainer.bind} — so R3F disposes it on unmount.
|
|
29
|
+
* @param defs - The layer's lines, used only to size the initial capacity.
|
|
30
|
+
*/
|
|
31
|
+
constructor(defs: readonly LineDef[]);
|
|
32
|
+
protected addInstance(): number;
|
|
33
|
+
protected removeInstance(slot: number): void;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=line-container.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SlotContainer } from '../containers';
|
|
2
|
+
/**
|
|
3
|
+
* One line layer's bookkeeping.
|
|
4
|
+
*
|
|
5
|
+
* Thinner than the shape container and the image one alike, because a line has no per-instance
|
|
6
|
+
* resource to allocate against — every member draws the same segment template, placed by its own
|
|
7
|
+
* endpoints. That is why the resource type is `null` here: nothing about a `LineDef` can force a
|
|
8
|
+
* re-claim, so a def keeps its slot for as long as it is mounted, `points` included.
|
|
9
|
+
*/
|
|
10
|
+
export class LineContainer extends SlotContainer {
|
|
11
|
+
/** The constructor args R3F builds the batch from; a frozen snapshot, never mutated. */
|
|
12
|
+
args;
|
|
13
|
+
/**
|
|
14
|
+
* Builds a line layer's bookkeeping, sized to hold `defs` exactly so a layer that mounts full
|
|
15
|
+
* never reallocates. The batch itself is R3F's — the component renders `<BatchedLines>` and hands
|
|
16
|
+
* it over through {@link SlotContainer.bind} — so R3F disposes it on unmount.
|
|
17
|
+
* @param defs - The layer's lines, used only to size the initial capacity.
|
|
18
|
+
*/
|
|
19
|
+
constructor(defs) {
|
|
20
|
+
// Reversed, and that is the material's doing rather than this layer's: a line batch stencils
|
|
21
|
+
// first-fragment-wins so translucent lines do not darken where they overlap, which hands the
|
|
22
|
+
// pixel to whichever member rasterizes *first*. Draw order is therefore the reverse of paint
|
|
23
|
+
// order. Slots are still labeled in paint order, so "last sibling on top" holds here as
|
|
24
|
+
// everywhere else.
|
|
25
|
+
super({ reverse: true });
|
|
26
|
+
this.args = [{ capacity: Math.max(defs.length, 1) }];
|
|
27
|
+
}
|
|
28
|
+
addInstance() {
|
|
29
|
+
// The batch grows its own buffers if the layer ever outgrows the capacity it was sized to, so
|
|
30
|
+
// there is nothing to pre-scan; the id is captured from the creation callback.
|
|
31
|
+
let slot = -1;
|
|
32
|
+
this.mesh.addInstances(1, (id) => {
|
|
33
|
+
slot = id;
|
|
34
|
+
});
|
|
35
|
+
return slot;
|
|
36
|
+
}
|
|
37
|
+
removeInstance(slot) {
|
|
38
|
+
this.mesh.removeInstances(slot);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type ColorRepresentation, type WebGLProgramParametersWithUniforms } from 'three';
|
|
2
|
+
/** Props for {@link ColorMaterial}. */
|
|
3
|
+
export interface ColorMaterialProps {
|
|
4
|
+
/** Base color. Defaults to white, which is what a per-instance tint multiplies against. */
|
|
5
|
+
color?: ColorRepresentation;
|
|
6
|
+
/**
|
|
7
|
+
* Whether this batch draws **volumes** — meshes carrying real z, which need shading to read as
|
|
8
|
+
* solid and depth testing to occlude themselves and each other. Defaults to `false`, the flat
|
|
9
|
+
* content that is every 2D primitive in a plan.
|
|
10
|
+
*/
|
|
11
|
+
solid?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Whether those volumes are translucent, which moves them into three's transparent pass. Only
|
|
14
|
+
* meaningful together with {@link solid}; flat content never uses it (see the note below on why 2D
|
|
15
|
+
* stays in the opaque list).
|
|
16
|
+
*/
|
|
17
|
+
translucent?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Runs on the shader before the dim patch does — the slot for a caller's own shader surgery,
|
|
20
|
+
* like the background quad's clip-space projection and its dim-uniform stand-in. Captured at
|
|
21
|
+
* first render and fixed for the material's lifetime, like every other axis of this component:
|
|
22
|
+
* a changing handler identity would risk a recompile per render.
|
|
23
|
+
*/
|
|
24
|
+
onBeforeCompile?: (parameters: WebGLProgramParametersWithUniforms) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The material a shape batch draws with, rendered as a child of the mesh so R3F attaches, updates,
|
|
28
|
+
* and disposes it. Also troika's base material for text (see `Texts`), which never asks for `solid`.
|
|
29
|
+
*
|
|
30
|
+
* One component with a branch rather than two files, because the two are the same thing along one
|
|
31
|
+
* axis — what a batch of shapes draws with — and the three differences are the whole point, best
|
|
32
|
+
* read side by side:
|
|
33
|
+
*
|
|
34
|
+
* | | flat (default) | `solid` |
|
|
35
|
+
* | --- | --- | --- |
|
|
36
|
+
* | base | `MeshBasicMaterial`, unlit | `MeshPhysicalMaterial`, lit |
|
|
37
|
+
* | `depthWrite` | off, from {@link sharedParams} | **on** |
|
|
38
|
+
* | polygon offset | on, from {@link sharedParams} | **off** |
|
|
39
|
+
*
|
|
40
|
+
* Each difference is load-bearing. A volume is the one kind of content that writes depth: that is how
|
|
41
|
+
* its own faces resolve against each other, and what lets 2D content drawn afterwards be occluded by
|
|
42
|
+
* it. It carries no polygon offset because solids are what everything else is biased *against* —
|
|
43
|
+
* offsetting them too would only move the problem.
|
|
44
|
+
*
|
|
45
|
+
* `ior: 1` and `specularIntensity: 0` drive the lit material's `F0` to zero, so no specular lobe sits
|
|
46
|
+
* on top of the diffuse term. That is what makes the lighting identity exact: with the two lights
|
|
47
|
+
* summing to intensity `π`, a face square-on to the camera renders at precisely the authored color
|
|
48
|
+
* (see `Lighting`). A default `MeshPhysicalMaterial` would put a sheen on every wall and push it
|
|
49
|
+
* above its albedo.
|
|
50
|
+
*
|
|
51
|
+
* Per-instance color arrives through `BatchedMesh.setColorAt` either way: three injects the
|
|
52
|
+
* batching-color chunk for whatever material the batch draws with.
|
|
53
|
+
*
|
|
54
|
+
* **`translucent` is the one place the array-order contract bends, and it is deliberate.** It sets
|
|
55
|
+
* `transparent: true`, which moves the batch into three's transparent list — drawn after *every*
|
|
56
|
+
* opaque object in the scene, whatever `renderOrder` says (`WebGLRenderer.renderScene`). That is
|
|
57
|
+
* exactly what makes a translucent volume blend with the opaque content behind it even when that
|
|
58
|
+
* content lives in a later layer, which no position in the ordered stream can achieve: a translucent
|
|
59
|
+
* surface can only blend with what is already in the framebuffer.
|
|
60
|
+
*
|
|
61
|
+
* What it costs is the reverse case. A planar def sitting *on* a translucent volume — a label or logo
|
|
62
|
+
* on a glass roof — draws in the opaque pass and so lands **before** it: the label composites against
|
|
63
|
+
* whatever was under the glass rather than onto it, and the volume then glazes over the label. So a
|
|
64
|
+
* translucent solid is right about what is behind it and wrong about what is on it. Flat content is
|
|
65
|
+
* never given this flag for the same reason, plus one of its own: 2D is coplanar, so it has no depth
|
|
66
|
+
* order to sort by and order is all it has.
|
|
67
|
+
*
|
|
68
|
+
* `forceSinglePass` is switched off here alone. three renders a `DoubleSide` transparent material
|
|
69
|
+
* twice — back faces, then front (`WebGLRenderer.js:1348`) — which is a correctly ordered two-pass
|
|
70
|
+
* for free, and the reason a closed translucent volume does not blend its own far and near surfaces
|
|
71
|
+
* in whatever order the batch happened to pack them.
|
|
72
|
+
*
|
|
73
|
+
* Depth writes stay **on** even when translucent, which is not three's usual convention. Two rules
|
|
74
|
+
* depend on it: a translucent volume must still be occluded by anything nearer, and a farther one
|
|
75
|
+
* drawn later must not paint over it. MapLibre makes the same choice for `fill-extrusion`
|
|
76
|
+
* (`DepthMode.ReadWrite` in both of its passes). The trade and the two rejected alternatives are
|
|
77
|
+
* recorded in the design spec's Decision 2.
|
|
78
|
+
*
|
|
79
|
+
* The branch is stable for a mounted batch — `Shapes` renders a separate element per kind, each with
|
|
80
|
+
* a constant `solid` — so R3F never swaps one base class for the other under a live mesh.
|
|
81
|
+
* @param props - The material's base color, and whether it draws volumes.
|
|
82
|
+
* @returns The material element.
|
|
83
|
+
*/
|
|
84
|
+
export declare function ColorMaterial(props: ColorMaterialProps): import("react").JSX.Element;
|
|
85
|
+
//# sourceMappingURL=ColorMaterial.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useDimPatch } from './dim';
|
|
4
|
+
import { sharedParams } from './shared';
|
|
5
|
+
/**
|
|
6
|
+
* The material a shape batch draws with, rendered as a child of the mesh so R3F attaches, updates,
|
|
7
|
+
* and disposes it. Also troika's base material for text (see `Texts`), which never asks for `solid`.
|
|
8
|
+
*
|
|
9
|
+
* One component with a branch rather than two files, because the two are the same thing along one
|
|
10
|
+
* axis — what a batch of shapes draws with — and the three differences are the whole point, best
|
|
11
|
+
* read side by side:
|
|
12
|
+
*
|
|
13
|
+
* | | flat (default) | `solid` |
|
|
14
|
+
* | --- | --- | --- |
|
|
15
|
+
* | base | `MeshBasicMaterial`, unlit | `MeshPhysicalMaterial`, lit |
|
|
16
|
+
* | `depthWrite` | off, from {@link sharedParams} | **on** |
|
|
17
|
+
* | polygon offset | on, from {@link sharedParams} | **off** |
|
|
18
|
+
*
|
|
19
|
+
* Each difference is load-bearing. A volume is the one kind of content that writes depth: that is how
|
|
20
|
+
* its own faces resolve against each other, and what lets 2D content drawn afterwards be occluded by
|
|
21
|
+
* it. It carries no polygon offset because solids are what everything else is biased *against* —
|
|
22
|
+
* offsetting them too would only move the problem.
|
|
23
|
+
*
|
|
24
|
+
* `ior: 1` and `specularIntensity: 0` drive the lit material's `F0` to zero, so no specular lobe sits
|
|
25
|
+
* on top of the diffuse term. That is what makes the lighting identity exact: with the two lights
|
|
26
|
+
* summing to intensity `π`, a face square-on to the camera renders at precisely the authored color
|
|
27
|
+
* (see `Lighting`). A default `MeshPhysicalMaterial` would put a sheen on every wall and push it
|
|
28
|
+
* above its albedo.
|
|
29
|
+
*
|
|
30
|
+
* Per-instance color arrives through `BatchedMesh.setColorAt` either way: three injects the
|
|
31
|
+
* batching-color chunk for whatever material the batch draws with.
|
|
32
|
+
*
|
|
33
|
+
* **`translucent` is the one place the array-order contract bends, and it is deliberate.** It sets
|
|
34
|
+
* `transparent: true`, which moves the batch into three's transparent list — drawn after *every*
|
|
35
|
+
* opaque object in the scene, whatever `renderOrder` says (`WebGLRenderer.renderScene`). That is
|
|
36
|
+
* exactly what makes a translucent volume blend with the opaque content behind it even when that
|
|
37
|
+
* content lives in a later layer, which no position in the ordered stream can achieve: a translucent
|
|
38
|
+
* surface can only blend with what is already in the framebuffer.
|
|
39
|
+
*
|
|
40
|
+
* What it costs is the reverse case. A planar def sitting *on* a translucent volume — a label or logo
|
|
41
|
+
* on a glass roof — draws in the opaque pass and so lands **before** it: the label composites against
|
|
42
|
+
* whatever was under the glass rather than onto it, and the volume then glazes over the label. So a
|
|
43
|
+
* translucent solid is right about what is behind it and wrong about what is on it. Flat content is
|
|
44
|
+
* never given this flag for the same reason, plus one of its own: 2D is coplanar, so it has no depth
|
|
45
|
+
* order to sort by and order is all it has.
|
|
46
|
+
*
|
|
47
|
+
* `forceSinglePass` is switched off here alone. three renders a `DoubleSide` transparent material
|
|
48
|
+
* twice — back faces, then front (`WebGLRenderer.js:1348`) — which is a correctly ordered two-pass
|
|
49
|
+
* for free, and the reason a closed translucent volume does not blend its own far and near surfaces
|
|
50
|
+
* in whatever order the batch happened to pack them.
|
|
51
|
+
*
|
|
52
|
+
* Depth writes stay **on** even when translucent, which is not three's usual convention. Two rules
|
|
53
|
+
* depend on it: a translucent volume must still be occluded by anything nearer, and a farther one
|
|
54
|
+
* drawn later must not paint over it. MapLibre makes the same choice for `fill-extrusion`
|
|
55
|
+
* (`DepthMode.ReadWrite` in both of its passes). The trade and the two rejected alternatives are
|
|
56
|
+
* recorded in the design spec's Decision 2.
|
|
57
|
+
*
|
|
58
|
+
* The branch is stable for a mounted batch — `Shapes` renders a separate element per kind, each with
|
|
59
|
+
* a constant `solid` — so R3F never swaps one base class for the other under a live mesh.
|
|
60
|
+
* @param props - The material's base color, and whether it draws volumes.
|
|
61
|
+
* @returns The material element.
|
|
62
|
+
*/
|
|
63
|
+
export function ColorMaterial(props) {
|
|
64
|
+
// Read-then-default rather than a destructuring default: the React Compiler cannot lower
|
|
65
|
+
// `const { color = 'white' } = props` (BuildHIR::lowerAssignment, AssignmentPattern), and
|
|
66
|
+
// floorplan's dev build runs the compiler with `panicThreshold: 'all_errors'`, which turns that
|
|
67
|
+
// into a hard 500 for the whole app. Storybook's pass defaults to `panicThreshold: 'none'` and
|
|
68
|
+
// silently skips the component instead, so only the running app surfaces it.
|
|
69
|
+
const color = props.color ?? 'white';
|
|
70
|
+
// Before the branch, because it is a hook — and passed to both, because a volume dims exactly as
|
|
71
|
+
// flat content does: the patch runs on whatever `gl_FragColor` the material arrived at, which for
|
|
72
|
+
// a lit material is the shaded color.
|
|
73
|
+
const dimPatch = useDimPatch();
|
|
74
|
+
// The caller's surgery first, then dimming — so a pre-patch that declares the dim uniform's
|
|
75
|
+
// stand-in is already in the shader when the dim patch looks for it. One stable handler, for the
|
|
76
|
+
// same reason `useDimPatch` keeps its own.
|
|
77
|
+
const [onBeforeCompile] = useState(() => {
|
|
78
|
+
const pre = props.onBeforeCompile;
|
|
79
|
+
return (parameters) => {
|
|
80
|
+
pre?.(parameters);
|
|
81
|
+
dimPatch(parameters);
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
if (props.solid) {
|
|
85
|
+
const translucent = props.translucent ?? false;
|
|
86
|
+
return (_jsx("meshPhysicalMaterial", { ...sharedParams, color: color, depthWrite: true, polygonOffset: false, ior: 1, specularIntensity: 0, transparent: translucent, forceSinglePass: !translucent, onBeforeCompile: onBeforeCompile }));
|
|
87
|
+
}
|
|
88
|
+
return _jsx("meshBasicMaterial", { ...sharedParams, color: color, onBeforeCompile: onBeforeCompile });
|
|
89
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ColorRepresentation } from 'three';
|
|
2
|
+
/** Props for {@link LineMaterial}. */
|
|
3
|
+
export interface LineMaterialProps {
|
|
4
|
+
/** Base color. Defaults to white, which is what a per-instance tint multiplies against. */
|
|
5
|
+
color?: ColorRepresentation;
|
|
6
|
+
/**
|
|
7
|
+
* Width every member without one of its own draws at, in screen-space pixels — `BatchedLinesExt`
|
|
8
|
+
* resolves an unset per-member width to this. Defaults to 1.
|
|
9
|
+
*/
|
|
10
|
+
width?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The material line batches draw with, rendered as a child of the batch so R3F attaches, updates,
|
|
14
|
+
* and disposes it.
|
|
15
|
+
*
|
|
16
|
+
* Three's own, configured — it knows nothing about batching. What makes one of these draw members
|
|
17
|
+
* that differ in width and tint is a patch the batch installs on whatever material it is handed
|
|
18
|
+
* (`core/lines/lines-material`), so this stays a material a plain `LineSegments2` could draw with.
|
|
19
|
+
*
|
|
20
|
+
* `worldUnits` is left at three's default, so `width` is screen-space and a line keeps its
|
|
21
|
+
* thickness as the floorplan zooms. `depthFunc` and the blending come from the shared params, the
|
|
22
|
+
* same as every other 2D layer: order decides overlap, not depth.
|
|
23
|
+
*
|
|
24
|
+
* **The stencil is what keeps translucent lines from darkening where they overlap.** Alpha blending
|
|
25
|
+
* composites per fragment, so anywhere two line quads cover the same pixel — a route's shared
|
|
26
|
+
* endpoints, where two round caps land on top of each other, or two lines simply crossing — the
|
|
27
|
+
* color is blended twice and reads darker than either line. The three settings below make the first
|
|
28
|
+
* fragment to reach a pixel the only one that draws:
|
|
29
|
+
*
|
|
30
|
+
* - `stencilFunc: NotEqual` against {@link LINE_STENCIL_REF} passes only where the layer has not
|
|
31
|
+
* drawn yet.
|
|
32
|
+
* - `stencilZPass: Replace` stamps the ref as it passes, so the next fragment there fails.
|
|
33
|
+
* - `stencilWrite` turns both on.
|
|
34
|
+
*
|
|
35
|
+
* Two consequences worth knowing. Lines within a layer never blend with *each other* — only the
|
|
36
|
+
* winner shows — so a line that must blend over another belongs in its own layer. And "first
|
|
37
|
+
* fragment wins" is why a line batch is drawn back to front: the one that paints on top has to
|
|
38
|
+
* rasterize first. Between layers nothing changes, because the clear below hands each
|
|
39
|
+
* batch a fresh stencil, so ordinary blending and occlusion still apply across them.
|
|
40
|
+
* @param props - The material's base color and fallback width.
|
|
41
|
+
* @returns The material element.
|
|
42
|
+
*/
|
|
43
|
+
export declare function LineMaterial(props: LineMaterialProps): import("react").JSX.Element;
|
|
44
|
+
//# sourceMappingURL=LineMaterial.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { extend } from '@react-three/fiber';
|
|
3
|
+
import { NotEqualStencilFunc, ReplaceStencilOp, } from 'three';
|
|
4
|
+
import { LineMaterial as LineMaterialBase } from 'three/addons/lines/LineMaterial.js';
|
|
5
|
+
import { useDimPatch } from './dim';
|
|
6
|
+
import { sharedParams } from './shared';
|
|
7
|
+
/**
|
|
8
|
+
* The stencil value a line layer stamps on every pixel it covers.
|
|
9
|
+
*
|
|
10
|
+
* `255` is deliberate rather than arbitrary. In the MapLibre embed the SDK renders into the map's
|
|
11
|
+
* own canvas and GL context, so this is *MapLibre's* stencil buffer: it assigns tile clipping ids
|
|
12
|
+
* counting up from zero, and the floorplan draws last, so taking the top of the range keeps the two
|
|
13
|
+
* out of each other's way.
|
|
14
|
+
*/
|
|
15
|
+
const LINE_STENCIL_REF = 255;
|
|
16
|
+
/**
|
|
17
|
+
* Registers three's line material as an R3F element, by value so the generated key cannot collide
|
|
18
|
+
* with anything three puts in the catalog when the canvas mounts.
|
|
19
|
+
*/
|
|
20
|
+
const LineMaterialElement = extend(LineMaterialBase);
|
|
21
|
+
/**
|
|
22
|
+
* The material line batches draw with, rendered as a child of the batch so R3F attaches, updates,
|
|
23
|
+
* and disposes it.
|
|
24
|
+
*
|
|
25
|
+
* Three's own, configured — it knows nothing about batching. What makes one of these draw members
|
|
26
|
+
* that differ in width and tint is a patch the batch installs on whatever material it is handed
|
|
27
|
+
* (`core/lines/lines-material`), so this stays a material a plain `LineSegments2` could draw with.
|
|
28
|
+
*
|
|
29
|
+
* `worldUnits` is left at three's default, so `width` is screen-space and a line keeps its
|
|
30
|
+
* thickness as the floorplan zooms. `depthFunc` and the blending come from the shared params, the
|
|
31
|
+
* same as every other 2D layer: order decides overlap, not depth.
|
|
32
|
+
*
|
|
33
|
+
* **The stencil is what keeps translucent lines from darkening where they overlap.** Alpha blending
|
|
34
|
+
* composites per fragment, so anywhere two line quads cover the same pixel — a route's shared
|
|
35
|
+
* endpoints, where two round caps land on top of each other, or two lines simply crossing — the
|
|
36
|
+
* color is blended twice and reads darker than either line. The three settings below make the first
|
|
37
|
+
* fragment to reach a pixel the only one that draws:
|
|
38
|
+
*
|
|
39
|
+
* - `stencilFunc: NotEqual` against {@link LINE_STENCIL_REF} passes only where the layer has not
|
|
40
|
+
* drawn yet.
|
|
41
|
+
* - `stencilZPass: Replace` stamps the ref as it passes, so the next fragment there fails.
|
|
42
|
+
* - `stencilWrite` turns both on.
|
|
43
|
+
*
|
|
44
|
+
* Two consequences worth knowing. Lines within a layer never blend with *each other* — only the
|
|
45
|
+
* winner shows — so a line that must blend over another belongs in its own layer. And "first
|
|
46
|
+
* fragment wins" is why a line batch is drawn back to front: the one that paints on top has to
|
|
47
|
+
* rasterize first. Between layers nothing changes, because the clear below hands each
|
|
48
|
+
* batch a fresh stencil, so ordinary blending and occlusion still apply across them.
|
|
49
|
+
* @param props - The material's base color and fallback width.
|
|
50
|
+
* @returns The material element.
|
|
51
|
+
*/
|
|
52
|
+
export function LineMaterial(props) {
|
|
53
|
+
// Read-then-default rather than destructuring defaults, which the React Compiler cannot lower —
|
|
54
|
+
// see `ColorMaterial` for what that costs.
|
|
55
|
+
const color = props.color ?? 'white';
|
|
56
|
+
const width = props.width ?? 1;
|
|
57
|
+
const onBeforeCompile = useDimPatch();
|
|
58
|
+
/**
|
|
59
|
+
* Hands the batch about to draw a clean stencil to stamp.
|
|
60
|
+
*
|
|
61
|
+
* The stamps are per *layer*, not per frame: without this, a batch drawn later would be rejected
|
|
62
|
+
* everywhere an earlier one already covered, and a route's animated overlay would not appear over
|
|
63
|
+
* the route beneath it. Clearing here rather than in the batch keeps it beside the settings that
|
|
64
|
+
* make it necessary — this material is what each line layer mounts its own copy of, so "once per
|
|
65
|
+
* batch, before it draws" falls out of where the callback lives.
|
|
66
|
+
* @param renderer - The renderer about to draw the batch.
|
|
67
|
+
*/
|
|
68
|
+
function onBeforeRender(renderer) {
|
|
69
|
+
renderer.clearStencil();
|
|
70
|
+
}
|
|
71
|
+
return (_jsx(LineMaterialElement, { ...sharedParams, color: color, linewidth: width, stencilWrite: true, stencilFunc: NotEqualStencilFunc, stencilZPass: ReplaceStencilOp, stencilRef: LINE_STENCIL_REF, onBeforeRender: onBeforeRender, onBeforeCompile: onBeforeCompile }));
|
|
72
|
+
}
|