@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,41 @@
|
|
|
1
|
+
import { type Material } from 'three';
|
|
2
|
+
/** Props for {@link ModelMaterial}. */
|
|
3
|
+
export interface ModelMaterialProps {
|
|
4
|
+
/**
|
|
5
|
+
* The material the GLB authored. Shared across every layer drawing the same model, so it is never
|
|
6
|
+
* mutated — this clones it and adjusts the clone.
|
|
7
|
+
*/
|
|
8
|
+
source: Material;
|
|
9
|
+
/**
|
|
10
|
+
* The name of a per-instance `float` uniform to multiply alpha by, so one mesh can hold instances
|
|
11
|
+
* of differing opacity. The mesh owns the name and declares it; the material only patches its
|
|
12
|
+
* shader to read it.
|
|
13
|
+
*/
|
|
14
|
+
opacityUniform: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether any instance drawing this material is translucent.
|
|
17
|
+
*
|
|
18
|
+
* Per-instance opacity is a *fragment* multiplier, but whether alpha blends at all is a **material**
|
|
19
|
+
* decision: three drops a `NormalBlending` material with `transparent: false` to `NoBlending`
|
|
20
|
+
* (`WebGLState.setMaterial`), which discards the alpha entirely — the instance then composites
|
|
21
|
+
* against the page rather than against the scene. So the mesh has to tell the material that at
|
|
22
|
+
* least one of its instances needs blending.
|
|
23
|
+
*/
|
|
24
|
+
translucent?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The material a model draws with: the GLB's own, cloned and adjusted for the plan's depth rules and
|
|
28
|
+
* for per-instance opacity.
|
|
29
|
+
*
|
|
30
|
+
* A model is one of the two kinds of content that **write depth**, translucent ones included — that
|
|
31
|
+
* is what makes it occlude itself, other models, and any 2D content drawn after it. The rest of the
|
|
32
|
+
* scene draws with `depthWrite: false` and a small polygon offset precisely so it can be biased
|
|
33
|
+
* against surfaces like these, so a model carries no offset of its own.
|
|
34
|
+
*
|
|
35
|
+
* Everything else about the material is left as the GLB authored it — maps, roughness, metalness,
|
|
36
|
+
* emissive, side. A model's appearance is the asset's business, not the renderer's.
|
|
37
|
+
* @param props - The source material and the per-instance opacity uniform's name.
|
|
38
|
+
* @returns The material element.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ModelMaterial(props: ModelMaterialProps): import("react").JSX.Element;
|
|
41
|
+
//# sourceMappingURL=ModelMaterial.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { LessEqualDepth, } from 'three';
|
|
4
|
+
import { useDimPatch } from './dim';
|
|
5
|
+
/**
|
|
6
|
+
* The material a model draws with: the GLB's own, cloned and adjusted for the plan's depth rules and
|
|
7
|
+
* for per-instance opacity.
|
|
8
|
+
*
|
|
9
|
+
* A model is one of the two kinds of content that **write depth**, translucent ones included — that
|
|
10
|
+
* is what makes it occlude itself, other models, and any 2D content drawn after it. The rest of the
|
|
11
|
+
* scene draws with `depthWrite: false` and a small polygon offset precisely so it can be biased
|
|
12
|
+
* against surfaces like these, so a model carries no offset of its own.
|
|
13
|
+
*
|
|
14
|
+
* Everything else about the material is left as the GLB authored it — maps, roughness, metalness,
|
|
15
|
+
* emissive, side. A model's appearance is the asset's business, not the renderer's.
|
|
16
|
+
* @param props - The source material and the per-instance opacity uniform's name.
|
|
17
|
+
* @returns The material element.
|
|
18
|
+
*/
|
|
19
|
+
export function ModelMaterial(props) {
|
|
20
|
+
const { source, opacityUniform } = props;
|
|
21
|
+
const dimPatch = useDimPatch();
|
|
22
|
+
const translucent = props.translucent ?? false;
|
|
23
|
+
// Cloned once and held in state: R3F would dispose a material it created on unmount, and the
|
|
24
|
+
// source belongs to the module-level model cache, which may still be serving other layers. The
|
|
25
|
+
// dim patch is installed with it, once — the patch is stable and reads the current amount through
|
|
26
|
+
// the uniform it owns.
|
|
27
|
+
const [material] = useState(() => adjust(source.clone(), opacityUniform, dimPatch, translucent));
|
|
28
|
+
useEffect(() => () => material.dispose(), [material]);
|
|
29
|
+
return _jsx("primitive", { object: material, attach: "material" });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Applies the plan's depth rules and the per-instance opacity patch to a cloned GLB material.
|
|
33
|
+
* @param material - The clone to adjust.
|
|
34
|
+
* @param opacityUniform - The per-instance uniform's name.
|
|
35
|
+
* @param dimPatch - Installs dimming on the compiled shader.
|
|
36
|
+
* @param translucent - Whether any instance drawing it needs its alpha blended.
|
|
37
|
+
* @returns The same material.
|
|
38
|
+
*/
|
|
39
|
+
function adjust(material, opacityUniform, dimPatch, translucent) {
|
|
40
|
+
material.depthFunc = LessEqualDepth;
|
|
41
|
+
material.polygonOffset = false;
|
|
42
|
+
// `transparent` moves a material into three's transparent list, drawn after *every* opaque object
|
|
43
|
+
// in the scene whatever the layer order says — which is what lets a translucent model blend with
|
|
44
|
+
// the content behind it instead of with an empty frame. It is a **material** property and never a
|
|
45
|
+
// per-instance one, so which list a def belongs in is a partition rather than a flag: `Models`
|
|
46
|
+
// keeps a layer's translucent copies in a batch of their own and re-splits when a def's opacity
|
|
47
|
+
// crosses 1, exactly as `Shapes` splits its volumes. `ColorMaterial` makes the same trade and
|
|
48
|
+
// records what it costs — a planar def sitting *on* a translucent one is drawn before it.
|
|
49
|
+
material.transparent =
|
|
50
|
+
material.transparent || translucent || material.opacity < 1;
|
|
51
|
+
// Depth writes stay on even when translucent, which is not three's usual convention and is
|
|
52
|
+
// deliberate. Two of the plan's rules depend on it: a translucent model must still be occluded by
|
|
53
|
+
// anything nearer the camera, and a planar def drawn after it — a label or logo on a booth roof —
|
|
54
|
+
// must be occluded by it. Both are depth-buffer questions, and switching writes off answers them
|
|
55
|
+
// wrongly: a *farther* solid drawn later would have nothing to reject it and would paint straight
|
|
56
|
+
// over the translucent one in front of it.
|
|
57
|
+
//
|
|
58
|
+
// What it costs is seeing *through* a translucent surface to content drawn after it, which no
|
|
59
|
+
// painter's-order renderer can offer — blending needs depth order, and array order is authoring
|
|
60
|
+
// order. MapLibre makes the same trade for `fill-extrusion` (`DepthMode.ReadWrite` in both of its
|
|
61
|
+
// passes), degrading to a color error rather than a structural one. The obligation it puts on
|
|
62
|
+
// callers — order a translucent model's layer after whatever should show through it — is spelled
|
|
63
|
+
// out in the design spec's Decision 2, along with the two alternatives and why each loses more
|
|
64
|
+
// than it gains.
|
|
65
|
+
material.depthWrite = true;
|
|
66
|
+
const chained = material.onBeforeCompile.bind(material);
|
|
67
|
+
material.onBeforeCompile = (parameters, ...rest) => {
|
|
68
|
+
chained(parameters, ...rest);
|
|
69
|
+
// Applied at the very end of the fragment chain, after `<dithering_fragment>`, so it multiplies
|
|
70
|
+
// the final composed alpha rather than an intermediate one. The instanced mesh injects the
|
|
71
|
+
// uniform fetch at the top of `main()`, so the name is in scope by the time this runs.
|
|
72
|
+
parameters.fragmentShader = parameters.fragmentShader.replace('#include <dithering_fragment>',
|
|
73
|
+
/*glsl*/ `
|
|
74
|
+
#include <dithering_fragment>
|
|
75
|
+
gl_FragColor.a *= ${opacityUniform};
|
|
76
|
+
`.trim());
|
|
77
|
+
// Dim touches rgb and opacity touches alpha, so the two patches are independent however they
|
|
78
|
+
// are ordered — and dim lands ahead of the colorspace conversion either way, which is where a
|
|
79
|
+
// gamma-corrected darken has to be.
|
|
80
|
+
dimPatch(parameters);
|
|
81
|
+
};
|
|
82
|
+
material.needsUpdate = true;
|
|
83
|
+
return material;
|
|
84
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Texture } from 'three';
|
|
2
|
+
/** Props for {@link TextureMaterial}. */
|
|
3
|
+
export interface TextureMaterialProps {
|
|
4
|
+
/**
|
|
5
|
+
* Texture map. Atlas textures are uploaded with `flipY = false`, so v runs down the canvas and
|
|
6
|
+
* the region coordinates need no vertical inversion anywhere.
|
|
7
|
+
*/
|
|
8
|
+
map: Texture;
|
|
9
|
+
/**
|
|
10
|
+
* The name of a per-instance `vec4` uniform to source the texture region from — for a texture
|
|
11
|
+
* atlas on an instanced/batched mesh that declares and writes it. The mesh owns the name; the
|
|
12
|
+
* material only patches its shader to sample `map` through it. Omit it for a plain textured
|
|
13
|
+
* material: the shader is left unpatched, so the material draws its whole `map` and works in any
|
|
14
|
+
* context, not only an instanced one.
|
|
15
|
+
*/
|
|
16
|
+
uvOffset?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The material used to render a texture, optionally through a per-instance uv-offset uniform so an
|
|
20
|
+
* instanced/batched mesh can pack many regions into one atlas.
|
|
21
|
+
*
|
|
22
|
+
* When {@link TextureMaterialProps.uvOffset} is set, the patch overwrites `vMapUv` after
|
|
23
|
+
* `<uv_vertex>` has run, so the instance's atlas region replaces whatever the stock chunk computed.
|
|
24
|
+
* Instanced meshes wrap this `onBeforeCompile` rather than replacing it, and inject their uniform
|
|
25
|
+
* fetch at the top of `main()`, so the uniform is in scope by the time this line runs.
|
|
26
|
+
* @param props - The material's texture and optional uv-offset uniform name.
|
|
27
|
+
* @returns The material element.
|
|
28
|
+
*/
|
|
29
|
+
export declare function TextureMaterial(props: TextureMaterialProps): import("react").JSX.Element;
|
|
30
|
+
//# sourceMappingURL=TextureMaterial.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useDimPatch } from './dim';
|
|
3
|
+
import { sharedParams } from './shared';
|
|
4
|
+
/**
|
|
5
|
+
* The material used to render a texture, optionally through a per-instance uv-offset uniform so an
|
|
6
|
+
* instanced/batched mesh can pack many regions into one atlas.
|
|
7
|
+
*
|
|
8
|
+
* When {@link TextureMaterialProps.uvOffset} is set, the patch overwrites `vMapUv` after
|
|
9
|
+
* `<uv_vertex>` has run, so the instance's atlas region replaces whatever the stock chunk computed.
|
|
10
|
+
* Instanced meshes wrap this `onBeforeCompile` rather than replacing it, and inject their uniform
|
|
11
|
+
* fetch at the top of `main()`, so the uniform is in scope by the time this line runs.
|
|
12
|
+
* @param props - The material's texture and optional uv-offset uniform name.
|
|
13
|
+
* @returns The material element.
|
|
14
|
+
*/
|
|
15
|
+
export function TextureMaterial(props) {
|
|
16
|
+
const { map, uvOffset } = props;
|
|
17
|
+
const applyDim = useDimPatch();
|
|
18
|
+
function onBeforeCompile(parameters) {
|
|
19
|
+
applyDim(parameters);
|
|
20
|
+
if (!uvOffset)
|
|
21
|
+
return;
|
|
22
|
+
parameters.vertexShader = parameters.vertexShader.replace('#include <uv_vertex>',
|
|
23
|
+
/*glsl*/ `
|
|
24
|
+
#include <uv_vertex>
|
|
25
|
+
vMapUv = uv * ${uvOffset}.zw + ${uvOffset}.xy;
|
|
26
|
+
`.trim());
|
|
27
|
+
}
|
|
28
|
+
return _jsx("meshBasicMaterial", { ...sharedParams, map: map, onBeforeCompile: onBeforeCompile });
|
|
29
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type IUniform, type WebGLProgramParametersWithUniforms } from 'three';
|
|
2
|
+
import { type UniformSchemaShader } from '../../core/InstanceUniforms';
|
|
3
|
+
import { type RenderableDef } from '../../types/defs';
|
|
4
|
+
/**
|
|
5
|
+
* Patches a compiled shader to dim what it draws. Returned by {@link useDimPatch} and called from a
|
|
6
|
+
* material's own `onBeforeCompile`, so a material that already patches its shader composes the two
|
|
7
|
+
* rather than choosing between them.
|
|
8
|
+
*/
|
|
9
|
+
export type DimPatch = (parameters: WebGLProgramParametersWithUniforms) => void;
|
|
10
|
+
/**
|
|
11
|
+
* The dim patch every material installs, reading the layer amount from {@link LayerDimContext}.
|
|
12
|
+
*
|
|
13
|
+
* Dimming is deliberately **not** a material prop. It cuts across every material and every def type,
|
|
14
|
+
* so threading it as a prop would put a `dim` on each material, each container, and each of their
|
|
15
|
+
* inner components, and any new one would have to remember to forward it. What a material needs to
|
|
16
|
+
* know is only *how* to be dimmed; what it is dimmed *by* belongs to the layer it draws under.
|
|
17
|
+
*
|
|
18
|
+
* The amount is held in one `IUniform` object per material and mutated in place, never replaced, so
|
|
19
|
+
* an animated dim — floorplan eases the venue's dim over 300ms — costs a number per material per
|
|
20
|
+
* frame with no recompile.
|
|
21
|
+
* @returns The patch to call from the material's `onBeforeCompile`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useDimPatch(): DimPatch;
|
|
24
|
+
/**
|
|
25
|
+
* Installs dimming into a shader about to be compiled: the layer's uniform, the declarations, and
|
|
26
|
+
* the application.
|
|
27
|
+
*
|
|
28
|
+
* **Applies only once the mesh's uniform prologue is in the shader**, and that guard is what makes
|
|
29
|
+
* installation order stop mattering. The applied line reads the per-instance amount the prologue
|
|
30
|
+
* declares as a *local of `main`*, so it has to land in the same `main`. Most meshes inject their
|
|
31
|
+
* prologue and then call the material's handler, so a material-installed patch is already last. Text
|
|
32
|
+
* is the exception: troika calls the base material's handler first and then rebuilds `main` around
|
|
33
|
+
* it — renaming the original `troikaOrigMain` and calling it from a new one — so a patch installed
|
|
34
|
+
* there would land in a scope the prologue's local never reaches. With this guard that early call
|
|
35
|
+
* simply does nothing, and `BatchedTextExt` runs the same patch again once its prologue is in.
|
|
36
|
+
* @param parameters - The shader being compiled.
|
|
37
|
+
* @param uniform - The layer's dim amount.
|
|
38
|
+
* @returns Whether the shader was patched.
|
|
39
|
+
* @throws {Error} If the prologue is present but the shader converts no color, which would leave
|
|
40
|
+
* dimming silently absent from a material that asked for it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyDim(parameters: WebGLProgramParametersWithUniforms, uniform: IUniform<number>): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Orders a layer's defs so the dimmed ones draw first and the undimmed ones last, on top.
|
|
45
|
+
*
|
|
46
|
+
* What this is for: dimming the venue behind a search leaves the few undimmed defs as the subject of
|
|
47
|
+
* the frame, and a subject that a dimmed neighbor happens to overlap would be covered by it. Paint
|
|
48
|
+
* order is the only thing that resolves overlap in a 2D layer — 2D content never writes depth,
|
|
49
|
+
* leaving depth testing nothing of its own to compare against — so being undimmed has to lift a def
|
|
50
|
+
* above the defs it is being distinguished from.
|
|
51
|
+
*
|
|
52
|
+
* **Stable**, and that is load-bearing rather than a nicety: within each group the defs keep their
|
|
53
|
+
* array order, which is the order the caller authored and the only thing that decides overlap
|
|
54
|
+
* between two defs that are both dimmed or both lit. A dim is allowed to lift a def over the dimmed
|
|
55
|
+
* ones; it is not allowed to reshuffle anything else.
|
|
56
|
+
*
|
|
57
|
+
* Returns the input array itself when the rule changes nothing — every def in one group, which is
|
|
58
|
+
* every layer that is uniformly dimmed or uniformly not. Callers key their order pass on the
|
|
59
|
+
* identity of what comes back, so an unchanged layer costs neither an allocation nor a re-label.
|
|
60
|
+
* @param defs - The layer's defs, in authored order.
|
|
61
|
+
* @param layerDimmed - Whether the layer is dimmed, which is what a def without a `dim` of its own
|
|
62
|
+
* inherits. Only the fact is needed, never the amount: this is a two-way split, and every amount
|
|
63
|
+
* above zero lands on the same side of it. That is also what keeps an eased layer dim cheap — the
|
|
64
|
+
* order pass re-runs when the value crosses zero, not on each of the frames between.
|
|
65
|
+
* @returns The defs in draw order.
|
|
66
|
+
*/
|
|
67
|
+
export declare function dimOrdered<T extends RenderableDef>(defs: readonly T[], layerDimmed: boolean): readonly T[];
|
|
68
|
+
/**
|
|
69
|
+
* Whether a def draws dimmed at all — the one question paint order asks about dimming, as opposed
|
|
70
|
+
* to the amount, which only the shader reads.
|
|
71
|
+
* @param dim - The def's own `dim`; `undefined` inherits.
|
|
72
|
+
* @param layerDimmed - Whether the layer it draws under is dimmed.
|
|
73
|
+
* @returns Whether it draws dimmed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isDimmed(dim: number | undefined, layerDimmed: boolean): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Encodes a def's own `dim` for its per-instance uniform.
|
|
78
|
+
* @param dim - The def's `dim`; `undefined` defers to the layer.
|
|
79
|
+
* @returns The value to write.
|
|
80
|
+
*/
|
|
81
|
+
export declare function instanceDim(dim: number | undefined): number;
|
|
82
|
+
/**
|
|
83
|
+
* Clamps a dim amount into the range the shader interpolates over, so `mix` never extrapolates into
|
|
84
|
+
* a color that is not a dim of the original.
|
|
85
|
+
* @param dim - The amount to clamp.
|
|
86
|
+
* @returns The amount, within `[0, 1]`.
|
|
87
|
+
*/
|
|
88
|
+
export declare function clampDim(dim: number): number;
|
|
89
|
+
/**
|
|
90
|
+
* The dim amount a layer draws at, which every material beneath it reads.
|
|
91
|
+
*
|
|
92
|
+
* Context rather than a prop because the value crosses the whole subtree and concerns none of the
|
|
93
|
+
* components between: a container hands its material nothing, and a material that never asked about
|
|
94
|
+
* layers still dims correctly. `Layer` provides it, and only when it declares a `dim` of its own —
|
|
95
|
+
* a layer without one is transparent to this, which is exactly what inheritance means.
|
|
96
|
+
*/
|
|
97
|
+
export declare const LayerDimContext: import("react").Context<number>;
|
|
98
|
+
/**
|
|
99
|
+
* The per-instance uniform a def's own dim is written to. Declared by every container that draws
|
|
100
|
+
* dimmable defs and read by {@link DIM_APPLY}; the material owns the name, the mesh only has to
|
|
101
|
+
* declare it before any slot writes one.
|
|
102
|
+
*/
|
|
103
|
+
export declare const DIM_UNIFORM = "instanceDim";
|
|
104
|
+
/**
|
|
105
|
+
* What an instance writes to defer to its layer.
|
|
106
|
+
*
|
|
107
|
+
* Any negative value would do; the sentinel only has to fall outside `[0, 1]`, which is why the
|
|
108
|
+
* amount itself can be stored as-is rather than packed around a zero that means "inherit". The
|
|
109
|
+
* imperative renderer needed that packing because its texture zero-filled and nothing wrote an
|
|
110
|
+
* instance on mount — here every instance writes its own value through `useDefSync`, so the fill is
|
|
111
|
+
* never what a draw reads.
|
|
112
|
+
*/
|
|
113
|
+
export declare const DIM_INHERIT = -1;
|
|
114
|
+
/**
|
|
115
|
+
* The schema a container declares to give its instances a dim of their own.
|
|
116
|
+
*
|
|
117
|
+
* Declared in the **vertex** stage although only the fragment shader reads it: that is what makes
|
|
118
|
+
* `InstanceUniforms` fetch it once per vertex and deliver it as a varying, instead of a texel fetch
|
|
119
|
+
* per fragment. It also moves any sibling uniform the same way, which costs nothing — images, lines
|
|
120
|
+
* and models already declare vertex uniforms, and shapes and text declare none besides this.
|
|
121
|
+
*/
|
|
122
|
+
export declare const DIM_SCHEMA: UniformSchemaShader;
|
|
123
|
+
//# sourceMappingURL=dim.d.ts.map
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber';
|
|
2
|
+
import { createContext, use, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { declareBeforeMain } from '../../core/shader-source';
|
|
4
|
+
/**
|
|
5
|
+
* The dim patch every material installs, reading the layer amount from {@link LayerDimContext}.
|
|
6
|
+
*
|
|
7
|
+
* Dimming is deliberately **not** a material prop. It cuts across every material and every def type,
|
|
8
|
+
* so threading it as a prop would put a `dim` on each material, each container, and each of their
|
|
9
|
+
* inner components, and any new one would have to remember to forward it. What a material needs to
|
|
10
|
+
* know is only *how* to be dimmed; what it is dimmed *by* belongs to the layer it draws under.
|
|
11
|
+
*
|
|
12
|
+
* The amount is held in one `IUniform` object per material and mutated in place, never replaced, so
|
|
13
|
+
* an animated dim — floorplan eases the venue's dim over 300ms — costs a number per material per
|
|
14
|
+
* frame with no recompile.
|
|
15
|
+
* @returns The patch to call from the material's `onBeforeCompile`.
|
|
16
|
+
*/
|
|
17
|
+
export function useDimPatch() {
|
|
18
|
+
const dim = use(LayerDimContext);
|
|
19
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
20
|
+
// A ref, not state: this object is written after render, every frame of an eased dim, and a ref is
|
|
21
|
+
// the container React sanctions that for. Its identity is what three holds onto, so the value can
|
|
22
|
+
// move underneath a program that is already built.
|
|
23
|
+
const uniformRef = useRef({ value: 0 });
|
|
24
|
+
// Created once and kept: R3F writes every changed prop onto the material, so handing it a fresh
|
|
25
|
+
// handler each render risks a recompile per frame — the one thing an animated dim must not cost.
|
|
26
|
+
const [patch] = useState(() => (parameters) => applyDim(parameters, uniformRef.current));
|
|
27
|
+
const amount = clampDim(dim);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
uniformRef.current.value = amount;
|
|
30
|
+
invalidate();
|
|
31
|
+
}, [amount, invalidate]);
|
|
32
|
+
return patch;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Installs dimming into a shader about to be compiled: the layer's uniform, the declarations, and
|
|
36
|
+
* the application.
|
|
37
|
+
*
|
|
38
|
+
* **Applies only once the mesh's uniform prologue is in the shader**, and that guard is what makes
|
|
39
|
+
* installation order stop mattering. The applied line reads the per-instance amount the prologue
|
|
40
|
+
* declares as a *local of `main`*, so it has to land in the same `main`. Most meshes inject their
|
|
41
|
+
* prologue and then call the material's handler, so a material-installed patch is already last. Text
|
|
42
|
+
* is the exception: troika calls the base material's handler first and then rebuilds `main` around
|
|
43
|
+
* it — renaming the original `troikaOrigMain` and calling it from a new one — so a patch installed
|
|
44
|
+
* there would land in a scope the prologue's local never reaches. With this guard that early call
|
|
45
|
+
* simply does nothing, and `BatchedTextExt` runs the same patch again once its prologue is in.
|
|
46
|
+
* @param parameters - The shader being compiled.
|
|
47
|
+
* @param uniform - The layer's dim amount.
|
|
48
|
+
* @returns Whether the shader was patched.
|
|
49
|
+
* @throws {Error} If the prologue is present but the shader converts no color, which would leave
|
|
50
|
+
* dimming silently absent from a material that asked for it.
|
|
51
|
+
*/
|
|
52
|
+
export function applyDim(parameters, uniform) {
|
|
53
|
+
const fragment = parameters.fragmentShader;
|
|
54
|
+
// Nothing to read, so nothing to do — either the mesh declared no per-instance dim, or its
|
|
55
|
+
// prologue has not been injected yet.
|
|
56
|
+
if (!fragment.includes(DIM_UNIFORM))
|
|
57
|
+
return false;
|
|
58
|
+
// Already installed, which is how a second call after the prologue arrives stays harmless.
|
|
59
|
+
if (fragment.includes(DIM_FUNCTION))
|
|
60
|
+
return false;
|
|
61
|
+
const anchor = colorspaceAnchor(fragment);
|
|
62
|
+
if (!anchor)
|
|
63
|
+
throw new Error('applyDim: the shader has no colorspace conversion to dim before.');
|
|
64
|
+
parameters.uniforms[LAYER_DIM] = uniform;
|
|
65
|
+
// Ahead of `main`, because these are file-scope declarations — and `dimColor` takes the amount as a
|
|
66
|
+
// parameter precisely so it needs nothing `main` declares.
|
|
67
|
+
parameters.fragmentShader = declareBeforeMain(fragment, DIM_DEFS).replace(anchor, `${DIM_APPLY}\n${anchor}`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Orders a layer's defs so the dimmed ones draw first and the undimmed ones last, on top.
|
|
72
|
+
*
|
|
73
|
+
* What this is for: dimming the venue behind a search leaves the few undimmed defs as the subject of
|
|
74
|
+
* the frame, and a subject that a dimmed neighbor happens to overlap would be covered by it. Paint
|
|
75
|
+
* order is the only thing that resolves overlap in a 2D layer — 2D content never writes depth,
|
|
76
|
+
* leaving depth testing nothing of its own to compare against — so being undimmed has to lift a def
|
|
77
|
+
* above the defs it is being distinguished from.
|
|
78
|
+
*
|
|
79
|
+
* **Stable**, and that is load-bearing rather than a nicety: within each group the defs keep their
|
|
80
|
+
* array order, which is the order the caller authored and the only thing that decides overlap
|
|
81
|
+
* between two defs that are both dimmed or both lit. A dim is allowed to lift a def over the dimmed
|
|
82
|
+
* ones; it is not allowed to reshuffle anything else.
|
|
83
|
+
*
|
|
84
|
+
* Returns the input array itself when the rule changes nothing — every def in one group, which is
|
|
85
|
+
* every layer that is uniformly dimmed or uniformly not. Callers key their order pass on the
|
|
86
|
+
* identity of what comes back, so an unchanged layer costs neither an allocation nor a re-label.
|
|
87
|
+
* @param defs - The layer's defs, in authored order.
|
|
88
|
+
* @param layerDimmed - Whether the layer is dimmed, which is what a def without a `dim` of its own
|
|
89
|
+
* inherits. Only the fact is needed, never the amount: this is a two-way split, and every amount
|
|
90
|
+
* above zero lands on the same side of it. That is also what keeps an eased layer dim cheap — the
|
|
91
|
+
* order pass re-runs when the value crosses zero, not on each of the frames between.
|
|
92
|
+
* @returns The defs in draw order.
|
|
93
|
+
*/
|
|
94
|
+
export function dimOrdered(defs, layerDimmed) {
|
|
95
|
+
const dimmed = [];
|
|
96
|
+
const lit = [];
|
|
97
|
+
for (const def of defs)
|
|
98
|
+
(isDimmed(def.dim, layerDimmed) ? dimmed : lit).push(def);
|
|
99
|
+
return dimmed.length === 0 || lit.length === 0 ? defs : [...dimmed, ...lit];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether a def draws dimmed at all — the one question paint order asks about dimming, as opposed
|
|
103
|
+
* to the amount, which only the shader reads.
|
|
104
|
+
* @param dim - The def's own `dim`; `undefined` inherits.
|
|
105
|
+
* @param layerDimmed - Whether the layer it draws under is dimmed.
|
|
106
|
+
* @returns Whether it draws dimmed.
|
|
107
|
+
*/
|
|
108
|
+
export function isDimmed(dim, layerDimmed) {
|
|
109
|
+
return dim === undefined ? layerDimmed : dim > 0;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Encodes a def's own `dim` for its per-instance uniform.
|
|
113
|
+
* @param dim - The def's `dim`; `undefined` defers to the layer.
|
|
114
|
+
* @returns The value to write.
|
|
115
|
+
*/
|
|
116
|
+
export function instanceDim(dim) {
|
|
117
|
+
return dim === undefined ? DIM_INHERIT : clampDim(dim);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Clamps a dim amount into the range the shader interpolates over, so `mix` never extrapolates into
|
|
121
|
+
* a color that is not a dim of the original.
|
|
122
|
+
* @param dim - The amount to clamp.
|
|
123
|
+
* @returns The amount, within `[0, 1]`.
|
|
124
|
+
*/
|
|
125
|
+
export function clampDim(dim) {
|
|
126
|
+
return Math.min(Math.max(dim, 0), 1);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Where the fragment's color is final and still linear — the point dimming has to land in front of,
|
|
130
|
+
* spelled whichever way the shader currently spells it.
|
|
131
|
+
*
|
|
132
|
+
* Two spellings because three resolves `#include` late, after every `onBeforeCompile`, while troika
|
|
133
|
+
* resolves them itself while rebuilding `main`. A patch running after troika's rebuild therefore
|
|
134
|
+
* finds the conversion already expanded to its chunk body. Both name the same line; neither is a
|
|
135
|
+
* private marker of troika's.
|
|
136
|
+
* @param fragmentShader - The shader to search.
|
|
137
|
+
* @returns The anchor found, or `undefined` if the shader converts no color.
|
|
138
|
+
*/
|
|
139
|
+
function colorspaceAnchor(fragmentShader) {
|
|
140
|
+
const include = /*glsl*/ `#include <colorspace_fragment>`;
|
|
141
|
+
const expanded = /*glsl*/ `gl_FragColor = linearToOutputTexel( gl_FragColor );`;
|
|
142
|
+
if (fragmentShader.includes(include))
|
|
143
|
+
return include;
|
|
144
|
+
return fragmentShader.includes(expanded) ? expanded : undefined;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The dim amount a layer draws at, which every material beneath it reads.
|
|
148
|
+
*
|
|
149
|
+
* Context rather than a prop because the value crosses the whole subtree and concerns none of the
|
|
150
|
+
* components between: a container hands its material nothing, and a material that never asked about
|
|
151
|
+
* layers still dims correctly. `Layer` provides it, and only when it declares a `dim` of its own —
|
|
152
|
+
* a layer without one is transparent to this, which is exactly what inheritance means.
|
|
153
|
+
*/
|
|
154
|
+
export const LayerDimContext = createContext(0);
|
|
155
|
+
/**
|
|
156
|
+
* The per-instance uniform a def's own dim is written to. Declared by every container that draws
|
|
157
|
+
* dimmable defs and read by {@link DIM_APPLY}; the material owns the name, the mesh only has to
|
|
158
|
+
* declare it before any slot writes one.
|
|
159
|
+
*/
|
|
160
|
+
export const DIM_UNIFORM = 'instanceDim';
|
|
161
|
+
/**
|
|
162
|
+
* What an instance writes to defer to its layer.
|
|
163
|
+
*
|
|
164
|
+
* Any negative value would do; the sentinel only has to fall outside `[0, 1]`, which is why the
|
|
165
|
+
* amount itself can be stored as-is rather than packed around a zero that means "inherit". The
|
|
166
|
+
* imperative renderer needed that packing because its texture zero-filled and nothing wrote an
|
|
167
|
+
* instance on mount — here every instance writes its own value through `useDefSync`, so the fill is
|
|
168
|
+
* never what a draw reads.
|
|
169
|
+
*/
|
|
170
|
+
export const DIM_INHERIT = -1;
|
|
171
|
+
/**
|
|
172
|
+
* The schema a container declares to give its instances a dim of their own.
|
|
173
|
+
*
|
|
174
|
+
* Declared in the **vertex** stage although only the fragment shader reads it: that is what makes
|
|
175
|
+
* `InstanceUniforms` fetch it once per vertex and deliver it as a varying, instead of a texel fetch
|
|
176
|
+
* per fragment. It also moves any sibling uniform the same way, which costs nothing — images, lines
|
|
177
|
+
* and models already declare vertex uniforms, and shapes and text declare none besides this.
|
|
178
|
+
*/
|
|
179
|
+
export const DIM_SCHEMA = { vertex: { [DIM_UNIFORM]: 'float' } };
|
|
180
|
+
/** The uniform carrying the layer-level amount an instance without one of its own falls back to. */
|
|
181
|
+
const LAYER_DIM = 'uDim';
|
|
182
|
+
/** The signature {@link applyDim} looks for to tell an already-patched shader from a fresh one. */
|
|
183
|
+
const DIM_FUNCTION = 'vec4 dimColor(';
|
|
184
|
+
/**
|
|
185
|
+
* Dimming's file-scope declarations.
|
|
186
|
+
*
|
|
187
|
+
* Dim is a desaturation toward gray, not a darkening — which is why it cannot be baked into a
|
|
188
|
+
* per-instance tint: a texel sampled per fragment has no tint to bake into.
|
|
189
|
+
*
|
|
190
|
+
* `dimColor` takes straight (non-premultiplied) `gl_FragColor` and returns it the same way. The
|
|
191
|
+
* imperative renderer divided rgb by alpha here and multiplied it back at the end; the round-trip
|
|
192
|
+
* cancels — `mix(c, dot(w, c) / k, t)` is linear and homogeneous in `c`, so `f(c / a) * a == f(c)`
|
|
193
|
+
* for every alpha — and it was the one thing that could produce a NaN, at `a == 0`, which SDF text
|
|
194
|
+
* edges generate in quantity. Dropping it changes no pixel.
|
|
195
|
+
*/
|
|
196
|
+
const DIM_DEFS = /* glsl */ `
|
|
197
|
+
uniform float ${LAYER_DIM};
|
|
198
|
+
|
|
199
|
+
const vec3 grayWeights = vec3(0.299, 0.587, 0.114);
|
|
200
|
+
const float darkenFactor = pow(2., 2.2); // Gamma corrected
|
|
201
|
+
|
|
202
|
+
${DIM_FUNCTION}vec4 col, float amount) {
|
|
203
|
+
vec3 gray = vec3(dot(grayWeights, col.rgb));
|
|
204
|
+
return vec4(mix(col.rgb, gray / darkenFactor, amount), col.a);
|
|
205
|
+
}
|
|
206
|
+
`;
|
|
207
|
+
/**
|
|
208
|
+
* Dimming applied to the fragment, while the color is still linear — which is what
|
|
209
|
+
* `darkenFactor`'s gamma correction assumes.
|
|
210
|
+
*/
|
|
211
|
+
const DIM_APPLY = /* glsl */ `
|
|
212
|
+
gl_FragColor = dimColor(gl_FragColor, ${DIM_UNIFORM} < 0. ? ${LAYER_DIM} : ${DIM_UNIFORM});
|
|
213
|
+
`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CustomBlending, DoubleSide, LessEqualDepth, OneFactor, OneMinusSrcAlphaFactor, SrcAlphaFactor, } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Setting transparent: true on material changes render order, these parameters imitate transparent blending
|
|
4
|
+
* without reordering layers
|
|
5
|
+
*/
|
|
6
|
+
const blendingParams = {
|
|
7
|
+
blending: CustomBlending,
|
|
8
|
+
blendSrc: SrcAlphaFactor,
|
|
9
|
+
blendSrcAlpha: OneFactor,
|
|
10
|
+
blendDst: OneMinusSrcAlphaFactor,
|
|
11
|
+
blendDstAlpha: OneMinusSrcAlphaFactor,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Resolvable depth steps of camera-ward bias on every 2D fragment — `polygonOffsetUnits`, negated.
|
|
15
|
+
*
|
|
16
|
+
* The flat, face-on term of the decal offset: with no tilt there is no depth slope for `factor` to
|
|
17
|
+
* scale, and all that separates a label from the roof it sits on is how two different transform
|
|
18
|
+
* chains rounded the same plane. Each chain lands within a float ULP or two of the exact depth, and
|
|
19
|
+
* one ULP of view depth is worth up to two resolvable steps behind a near plane keeping 98%
|
|
20
|
+
* headroom — so a couple of ULPs of disagreement is about four steps, which is what this covers.
|
|
21
|
+
*/
|
|
22
|
+
const FLAT_BIAS_UNITS = 4;
|
|
23
|
+
/**
|
|
24
|
+
* Material parameter defaults, shared between all types
|
|
25
|
+
*/
|
|
26
|
+
export const sharedParams = {
|
|
27
|
+
...blendingParams,
|
|
28
|
+
side: DoubleSide,
|
|
29
|
+
depthFunc: LessEqualDepth,
|
|
30
|
+
depthWrite: false,
|
|
31
|
+
// 2D content never writes depth, so the buffer only ever holds what 3D content put there — and a
|
|
32
|
+
// 2D fragment coplanar with a solid surface (a label on a booth roof) is compared against a depth
|
|
33
|
+
// some other triangle interpolated. A small bias toward the camera wins that comparison; it is
|
|
34
|
+
// inert while nothing writes depth.
|
|
35
|
+
//
|
|
36
|
+
// `factor` covers camera tilt: the hardware scales it by the polygon's own window-space depth
|
|
37
|
+
// slope, making the bias one pixel's worth of that slope at any pitch — clear of every
|
|
38
|
+
// slope-proportional interpolation difference, yet unable to climb through anything that occludes
|
|
39
|
+
// the surface by more than a pixel. The imperative renderer had to carry this term as a constant,
|
|
40
|
+
// `⌈tan(maxPolarAngle)⌉ = 12`: with every layer writing depth, coplanar layers were kept apart by
|
|
41
|
+
// per-layer `units` steps, and surfaces sharing a plane share a slope, so `factor` cancelled
|
|
42
|
+
// between them and only a worst-case constant could size the step. With a single bias level there
|
|
43
|
+
// is no step, the real slope replaces the ceiling, and nothing here derives from `MAX_PITCH`.
|
|
44
|
+
//
|
|
45
|
+
// `units` covers the flat, top-down case, where the slope is zero and `factor` contributes
|
|
46
|
+
// nothing — sized by `FLAT_BIAS_UNITS`.
|
|
47
|
+
polygonOffset: true,
|
|
48
|
+
polygonOffsetFactor: -1,
|
|
49
|
+
polygonOffsetUnits: -FLAT_BIAS_UNITS,
|
|
50
|
+
forceSinglePass: true,
|
|
51
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ModelDef } from '../../types/defs';
|
|
2
|
+
import { type InstanceProps } from '../containers';
|
|
3
|
+
import { type ModelContainer } from './model-container';
|
|
4
|
+
/** Props for {@link ModelInstance}. */
|
|
5
|
+
export type ModelInstanceProps = InstanceProps<ModelDef, ModelContainer>;
|
|
6
|
+
/**
|
|
7
|
+
* Keeps one model's instance current: claims a slot on mount, writes the def's placement, opacity
|
|
8
|
+
* and visibility on mount and after every `update(def)`, and releases the slot on unmount.
|
|
9
|
+
*
|
|
10
|
+
* Renders no scene node — the mesh belongs to the container. Nothing here can force a re-claim: the
|
|
11
|
+
* geometry is the model part's, shared by every def drawing it, so moving a model is an ordinary
|
|
12
|
+
* write and its slot survives. `url` is not updatable by contract, which is what makes that true: a
|
|
13
|
+
* different model is a different def, and a different def a different instance.
|
|
14
|
+
*
|
|
15
|
+
* Memoized for the same reason as `ShapeInstance`: a structural change hands the layer a fresh
|
|
16
|
+
* `defs` snapshot, so element-identity bailout never applies and only prop equality keeps unchanged
|
|
17
|
+
* siblings from re-rendering. Correctness never depends on it.
|
|
18
|
+
* @param props - The def and its container.
|
|
19
|
+
* @returns `null`; the instance lives in the mesh, not the scene graph.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ModelInstance: import("react").MemoExoticComponent<(props: ModelInstanceProps) => null>;
|
|
22
|
+
//# sourceMappingURL=ModelInstance.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { useDefSync } from '../containers';
|
|
3
|
+
import { modelKindOf } from './model-container';
|
|
4
|
+
/**
|
|
5
|
+
* Keeps one model's instance current: claims a slot on mount, writes the def's placement, opacity
|
|
6
|
+
* and visibility on mount and after every `update(def)`, and releases the slot on unmount.
|
|
7
|
+
*
|
|
8
|
+
* Renders no scene node — the mesh belongs to the container. Nothing here can force a re-claim: the
|
|
9
|
+
* geometry is the model part's, shared by every def drawing it, so moving a model is an ordinary
|
|
10
|
+
* write and its slot survives. `url` is not updatable by contract, which is what makes that true: a
|
|
11
|
+
* different model is a different def, and a different def a different instance.
|
|
12
|
+
*
|
|
13
|
+
* Memoized for the same reason as `ShapeInstance`: a structural change hands the layer a fresh
|
|
14
|
+
* `defs` snapshot, so element-identity bailout never applies and only prop equality keeps unchanged
|
|
15
|
+
* siblings from re-rendering. Correctness never depends on it.
|
|
16
|
+
* @param props - The def and its container.
|
|
17
|
+
* @returns `null`; the instance lives in the mesh, not the scene graph.
|
|
18
|
+
*/
|
|
19
|
+
export const ModelInstance = memo(function ModelInstance(props) {
|
|
20
|
+
const { def, container } = props;
|
|
21
|
+
// No per-instance resource: every def on this mesh draws the same merged geometry, so the slot is
|
|
22
|
+
// backed by nothing and nothing can invalidate it.
|
|
23
|
+
useDefSync(container, def, null, (slot) => {
|
|
24
|
+
container.place(slot, def);
|
|
25
|
+
container.setOpacity(slot, def);
|
|
26
|
+
// Checked on every sync, because a def leaves its batch's kind without re-rendering this layer:
|
|
27
|
+
// `opacity` crossing 1 reaches the instance through `update()` and stops there, so this is where
|
|
28
|
+
// a batch learns it is holding something whose material describes the wrong render list.
|
|
29
|
+
if (modelKindOf(def) !== container.kind)
|
|
30
|
+
container.reportKindMismatch();
|
|
31
|
+
});
|
|
32
|
+
return null;
|
|
33
|
+
});
|