@expofp/renderer 3.2.4 → 3.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 -8028
- 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,272 @@
|
|
|
1
|
+
import { isMesh } from '@expofp/geometry';
|
|
2
|
+
import { isTranslucentColor } from '../../utils/colors';
|
|
3
|
+
import { SlotContainer } from '../containers';
|
|
4
|
+
import { shapeGeometry } from './shape-geometry';
|
|
5
|
+
/**
|
|
6
|
+
* Every kind of shape batch, **in the order they draw within a layer** — which is what `Shapes`
|
|
7
|
+
* takes each batch's `renderOrder` from, so this list is the one place that order is stated.
|
|
8
|
+
*
|
|
9
|
+
* Solids draw after the flat shapes, so a volume occludes the flat content of its own layer rather
|
|
10
|
+
* than being painted over by it — the same rule that governs whole layers, applied within one.
|
|
11
|
+
*/
|
|
12
|
+
export const SHAPE_KINDS = ['planar', 'solid', 'translucent'];
|
|
13
|
+
/**
|
|
14
|
+
* The batch a def belongs in.
|
|
15
|
+
*
|
|
16
|
+
* A shape is **solid** exactly when it is a `Mesh` whose vertices span more than one z — elevation is
|
|
17
|
+
* placement, not volume, so a quad or a lifted-but-flat mesh stays planar and is placed correctly by
|
|
18
|
+
* draw order alone. A solid is **translucent** when its color carries an alpha below 1. That has to
|
|
19
|
+
* be a partition rather than a per-instance flag because `transparent` is a *material* property:
|
|
20
|
+
* three decides which render list an object belongs to, never which instance, so two solids of
|
|
21
|
+
* differing transparency cannot share a mesh.
|
|
22
|
+
*
|
|
23
|
+
* **Only volumes split on translucency.** A flat shape with an alpha below 1 stays `planar`, because
|
|
24
|
+
* flat content already blends without leaving the opaque list: `sharedParams` reaches alpha blending
|
|
25
|
+
* through `CustomBlending` rather than through `transparent: true`, which three honors either way,
|
|
26
|
+
* and nothing planar writes depth. So array order is the whole of it — a translucent flat shape
|
|
27
|
+
* blends over whatever precedes it, and an opaque one authored later paints straight over it. That
|
|
28
|
+
* last part is a capability, not an accident: ordering an opaque fill after a translucent one is how
|
|
29
|
+
* a caller occludes it, and a batch of its own would take that away by forcing it after every opaque
|
|
30
|
+
* object in the scene, where nothing in its layer could cover it again.
|
|
31
|
+
*
|
|
32
|
+
* A volume has no such freedom. It writes depth and is depth-tested, so what ends up on top is the
|
|
33
|
+
* depth buffer's answer rather than the author's, and blending correctly means being drawn after the
|
|
34
|
+
* opaque content it blends with — which is a render pass, and therefore a batch.
|
|
35
|
+
*
|
|
36
|
+
* One function rather than a predicate in `Shapes` that places a def and another in `ShapeInstance`
|
|
37
|
+
* that notices it no longer belongs. Those are the same question, and two expressions of it can
|
|
38
|
+
* drift — a def placed by one rule and checked by the other would either sit in the wrong batch or
|
|
39
|
+
* ask to be re-split forever. A planar shape returns before the color is looked at, so the parse
|
|
40
|
+
* costs nothing on the many layers holding only flat content.
|
|
41
|
+
* @param def - The def to classify.
|
|
42
|
+
* @returns The kind of batch it draws from.
|
|
43
|
+
*/
|
|
44
|
+
export function shapeKindOf(def) {
|
|
45
|
+
if (!isMesh(def.shape) || def.shape.isPlanar)
|
|
46
|
+
return 'planar';
|
|
47
|
+
return isTranslucentColor(def.color) ? 'translucent' : 'solid';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One shape layer's mesh bookkeeping: reserved buffer space, slot allocation, which geometries the
|
|
51
|
+
* mesh has registered, and the paint-order table.
|
|
52
|
+
*
|
|
53
|
+
* Slot allocation is the mesh's own — `addInstance`/`deleteInstance` maintain its free list — so
|
|
54
|
+
* what this adds is what the mesh will not track: how much vertex and index space is reserved, and
|
|
55
|
+
* which of its geometry ids each template was given. Building the templates is not its work either;
|
|
56
|
+
* they come from {@link shapeGeometry}, which caches them across every container.
|
|
57
|
+
*/
|
|
58
|
+
export class ShapeContainer extends SlotContainer {
|
|
59
|
+
/**
|
|
60
|
+
* The mesh's constructor args, sized to hold the defs the container was created with exactly.
|
|
61
|
+
* Passed as `<batchedMesh args={container.capacity}>`; R3F diffs `args` element-wise and a change
|
|
62
|
+
* disposes and rebuilds the mesh, orphaning every claimed slot — so this is a snapshot taken at
|
|
63
|
+
* creation, which growth never mutates.
|
|
64
|
+
*/
|
|
65
|
+
capacity;
|
|
66
|
+
/**
|
|
67
|
+
* Which kind of member this container holds, and so what its mesh is laid out for: a volume batch
|
|
68
|
+
* carries creased normals for the lit material and is non-indexed because that is what
|
|
69
|
+
* `toCreasedNormals` produces, and a translucent one draws in three's transparent pass. A member
|
|
70
|
+
* whose color stops matching is in the wrong pass, which is what {@link reportKindMismatch} exists
|
|
71
|
+
* to correct.
|
|
72
|
+
*/
|
|
73
|
+
kind;
|
|
74
|
+
/**
|
|
75
|
+
* Which id the mesh gave each geometry it has registered. Per container even though the templates
|
|
76
|
+
* behind them are shared, because an id is the mesh's own numbering into its own buffers.
|
|
77
|
+
*/
|
|
78
|
+
geometryIds = new Map();
|
|
79
|
+
/** What is allocated now, which growth mutates — unlike the {@link capacity} snapshot. */
|
|
80
|
+
allocated;
|
|
81
|
+
/**
|
|
82
|
+
* What the next growth jumps to, refreshed by {@link reserve}. Kept apart from {@link allocated}:
|
|
83
|
+
* a reservation must not resize anything by itself, only size the resize an overflowing claim was
|
|
84
|
+
* going to do anyway.
|
|
85
|
+
*/
|
|
86
|
+
target;
|
|
87
|
+
kindMismatchHandler;
|
|
88
|
+
/**
|
|
89
|
+
* Builds a shape layer's container, sized to hold `defs` exactly so a layer that mounts full never
|
|
90
|
+
* reallocates. The mesh itself is R3F's: the component renders `<batchedMesh>` from
|
|
91
|
+
* {@link capacity} and hands it over through {@link SlotContainer.bind}, and R3F disposes it on
|
|
92
|
+
* unmount. Nothing else needs disposal — `addGeometry` copies the template geometries' data into
|
|
93
|
+
* the mesh, so the templates are never uploaded to the GPU. They are not this container's to free
|
|
94
|
+
* in any case: {@link shapeGeometry} shares them across every container, and each is collected
|
|
95
|
+
* with the shape that keyed it.
|
|
96
|
+
* @param defs - The layer's shapes, used only to size the initial capacity.
|
|
97
|
+
* @param kind - Which of the layer's batches this is. A batched mesh mirrors its first geometry's
|
|
98
|
+
* attributes *and* its index-ness, and three rejects any later geometry that disagrees — so this
|
|
99
|
+
* decides the layout for every geometry in it, the shared unit plane included. It also decides
|
|
100
|
+
* the render pass its members belong to, and therefore which colors are at home in it.
|
|
101
|
+
*/
|
|
102
|
+
constructor(defs, kind = 'planar') {
|
|
103
|
+
super();
|
|
104
|
+
this.kind = kind;
|
|
105
|
+
this.allocated = measureCapacity(defs, (shape) => this.geometryFor(shape));
|
|
106
|
+
// A snapshot, not a live view: growth mutates `allocated.vertices`/`indices`, and leaking that
|
|
107
|
+
// into the mounted element's `args` would make R3F rebuild the mesh.
|
|
108
|
+
this.capacity = [this.allocated.instances, this.allocated.vertices, this.allocated.indices];
|
|
109
|
+
this.target = {
|
|
110
|
+
instances: this.allocated.instances,
|
|
111
|
+
vertices: this.allocated.vertices,
|
|
112
|
+
indices: this.allocated.indices,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The geometry a shape draws with, in this container's layout. Resolved through the shared cache,
|
|
117
|
+
* so a shape drawn by two containers — the same volume in the opaque and the translucent batch —
|
|
118
|
+
* is built once and handed to both.
|
|
119
|
+
* @param shape - The shape to resolve.
|
|
120
|
+
* @returns The geometry, built on first use.
|
|
121
|
+
*/
|
|
122
|
+
geometryFor(shape) {
|
|
123
|
+
// Both volume kinds share one layout, which is what lets the cache serve a def crossing between
|
|
124
|
+
// them without rebuilding anything.
|
|
125
|
+
return shapeGeometry(shape, this.kind !== 'planar');
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Declares the membership this container is about to hold, setting the size the next growth jumps
|
|
129
|
+
* to. The component calls this from render, before React mounts the instances that will claim in
|
|
130
|
+
* their effects — so a membership change that outgrows the buffers costs one resize per buffer,
|
|
131
|
+
* sized exactly, instead of one per claim. Bookkeeping only: nothing resizes until a claim or
|
|
132
|
+
* geometry registration actually overflows, so a reservation from a discarded or repeated render
|
|
133
|
+
* is harmless.
|
|
134
|
+
* @param defs - The shapes this container is about to hold.
|
|
135
|
+
*/
|
|
136
|
+
reserve(defs) {
|
|
137
|
+
this.target.instances = defs.length;
|
|
138
|
+
// Geometry space is a high-water mark like the instance count — registered geometries are never
|
|
139
|
+
// removed — so the target is what is used now plus every geometry the new membership will
|
|
140
|
+
// register on its first claim.
|
|
141
|
+
let vertices = this.allocated.usedVertices;
|
|
142
|
+
let indices = this.allocated.usedIndices;
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
for (const def of defs) {
|
|
145
|
+
const geometry = this.geometryFor(def.shape);
|
|
146
|
+
if (this.geometryIds.has(geometry) || seen.has(geometry))
|
|
147
|
+
continue;
|
|
148
|
+
seen.add(geometry);
|
|
149
|
+
vertices += vertexCountOf(geometry);
|
|
150
|
+
indices += indexCountOf(geometry);
|
|
151
|
+
}
|
|
152
|
+
this.target.vertices = vertices;
|
|
153
|
+
this.target.indices = indices;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Registers what to do when a member turns out to be the wrong kind. `Shapes` supplies a re-split;
|
|
157
|
+
* `undefined` clears it.
|
|
158
|
+
*
|
|
159
|
+
* A method rather than an assignable field because the container is held in `useState`, and the
|
|
160
|
+
* React Compiler rejects writing to a property of a state value ("This value cannot be modified").
|
|
161
|
+
* Calling a method is fine, which is how {@link reserve} already mutates.
|
|
162
|
+
* @param handler - The re-split to run, or `undefined` to clear it.
|
|
163
|
+
*/
|
|
164
|
+
setKindMismatchHandler(handler) {
|
|
165
|
+
this.kindMismatchHandler = handler;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Reports that a member no longer matches {@link kind} — its color crossed alpha 1, or its shape
|
|
169
|
+
* was replaced with one of another kind — so it is drawing from the wrong batch. Runs the registered handler, if any;
|
|
170
|
+
* a container mounted outside `Shapes` has none and the report is simply dropped.
|
|
171
|
+
*/
|
|
172
|
+
reportKindMismatch() {
|
|
173
|
+
this.kindMismatchHandler?.();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* A batched mesh reports the instance it hit as `batchId`, which is the slot this container
|
|
177
|
+
* allocated.
|
|
178
|
+
* @param hit - The intersection to trace.
|
|
179
|
+
* @returns The def, or `undefined` when the hit cannot be traced to one.
|
|
180
|
+
*/
|
|
181
|
+
defFromHit(hit) {
|
|
182
|
+
return this.defAt(hit.batchId);
|
|
183
|
+
}
|
|
184
|
+
addInstance(geometry) {
|
|
185
|
+
const geometryId = this.geometryIdFor(geometry);
|
|
186
|
+
// Full exactly when no slot is free and no room remains to append one; `addInstance` would
|
|
187
|
+
// throw. Growth jumps to the reserved membership's size — releases run before claims within a
|
|
188
|
+
// commit, so that size is enough for every claim the change brings and the resize happens once —
|
|
189
|
+
// with one appended slot as the floor when the reservation is stale or smaller. Never shrunk on
|
|
190
|
+
// release: freed ids keep later ids live, so the mesh forbids shrinking past them, and capacity
|
|
191
|
+
// kept as a high-water mark lets membership oscillate without rebuilding the instance textures.
|
|
192
|
+
if (this.mesh.instanceCount === this.mesh.maxInstanceCount) {
|
|
193
|
+
this.mesh.setInstanceCount(Math.max(this.target.instances, this.mesh.maxInstanceCount + 1));
|
|
194
|
+
}
|
|
195
|
+
return this.mesh.addInstance(geometryId);
|
|
196
|
+
}
|
|
197
|
+
removeInstance(slot) {
|
|
198
|
+
// In act environments R3F disposes an unmounting mesh synchronously, before this passive cleanup
|
|
199
|
+
// runs (in the browser disposal is deferred to idle priority, after it). Safe today: r185's
|
|
200
|
+
// `deleteInstance` touches only bookkeeping arrays, never the nulled textures. A three upgrade
|
|
201
|
+
// that changes that will fail the unmount specs.
|
|
202
|
+
this.mesh.deleteInstance(slot);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Registers a geometry with the mesh if it is not already, growing the buffers first.
|
|
206
|
+
* @param geometry - The geometry to register.
|
|
207
|
+
* @returns The mesh's geometry id.
|
|
208
|
+
*/
|
|
209
|
+
geometryIdFor(geometry) {
|
|
210
|
+
const existing = this.geometryIds.get(geometry);
|
|
211
|
+
if (existing !== undefined)
|
|
212
|
+
return existing;
|
|
213
|
+
// Sizing has to happen before `addGeometry`: the mesh throws once its reserved vertex or index
|
|
214
|
+
// space runs out. Growth jumps to the reserved membership's size, so a change that brings
|
|
215
|
+
// several new geometries resizes the buffers once, on the first registration, and exactly.
|
|
216
|
+
const vertices = this.allocated.usedVertices + vertexCountOf(geometry);
|
|
217
|
+
const indices = this.allocated.usedIndices + indexCountOf(geometry);
|
|
218
|
+
if (vertices > this.allocated.vertices || indices > this.allocated.indices) {
|
|
219
|
+
this.allocated.vertices = Math.max(vertices, this.target.vertices);
|
|
220
|
+
this.allocated.indices = Math.max(indices, this.target.indices);
|
|
221
|
+
this.mesh.setGeometrySize(this.allocated.vertices, this.allocated.indices);
|
|
222
|
+
}
|
|
223
|
+
this.allocated.usedVertices = vertices;
|
|
224
|
+
this.allocated.usedIndices = indices;
|
|
225
|
+
const id = this.mesh.addGeometry(geometry);
|
|
226
|
+
this.geometryIds.set(geometry, id);
|
|
227
|
+
return id;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Sizes a container to hold the given shapes exactly, floored at 1 so a layer that mounts empty is
|
|
232
|
+
* not built at a degenerate zero size.
|
|
233
|
+
* @param defs - The layer's shapes.
|
|
234
|
+
* @param geometryFor - Resolves a shape's geometry.
|
|
235
|
+
* @returns The initial capacity, with nothing used yet.
|
|
236
|
+
*/
|
|
237
|
+
function measureCapacity(defs, geometryFor) {
|
|
238
|
+
let vertices = 0;
|
|
239
|
+
let indices = 0;
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
for (const def of defs) {
|
|
242
|
+
const geometry = geometryFor(def.shape);
|
|
243
|
+
if (seen.has(geometry))
|
|
244
|
+
continue;
|
|
245
|
+
seen.add(geometry);
|
|
246
|
+
vertices += vertexCountOf(geometry);
|
|
247
|
+
indices += indexCountOf(geometry);
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
instances: Math.max(defs.length, 1),
|
|
251
|
+
vertices: Math.max(vertices, 1),
|
|
252
|
+
indices: Math.max(indices, 1),
|
|
253
|
+
usedVertices: 0,
|
|
254
|
+
usedIndices: 0,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Vertices a geometry reserves.
|
|
259
|
+
* @param geometry - The geometry.
|
|
260
|
+
* @returns The vertex count.
|
|
261
|
+
*/
|
|
262
|
+
function vertexCountOf(geometry) {
|
|
263
|
+
return geometry.getAttribute('position').count;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Indices a geometry reserves, or `0` when it is non-indexed.
|
|
267
|
+
* @param geometry - The geometry.
|
|
268
|
+
* @returns The index count.
|
|
269
|
+
*/
|
|
270
|
+
function indexCountOf(geometry) {
|
|
271
|
+
return geometry.getIndex()?.count ?? 0;
|
|
272
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Shape } from '@expofp/geometry';
|
|
2
|
+
import { BufferGeometry } from 'three';
|
|
3
|
+
/**
|
|
4
|
+
* The template geometry a shape draws with, in the layout its batch locks itself into. Boxes and
|
|
5
|
+
* rects share one unit plane and are placed by their instance transform; meshes and polygons carry
|
|
6
|
+
* absolute coordinates in their own geometry.
|
|
7
|
+
*
|
|
8
|
+
* The cache is process-wide rather than per container, because the two solid batches — opaque and
|
|
9
|
+
* translucent — build byte-identical geometry for the same shape. Without sharing, a volume moving
|
|
10
|
+
* between them as its alpha crosses 1 re-runs `toNonIndexed`, `computeVertexNormals` and
|
|
11
|
+
* `toCreasedNormals` over every vertex, and does so for the batch's *whole* membership, since a
|
|
12
|
+
* container resolves every member's geometry to measure its capacity before anything claims a slot.
|
|
13
|
+
*
|
|
14
|
+
* Sharing one template across several meshes is safe because `BatchedMesh.addGeometry` **copies** its
|
|
15
|
+
* data in: a template is never uploaded to the GPU, and nothing mutates one after it is built. What
|
|
16
|
+
* it costs is that a template now outlives the container that first asked for it — the mesh-like
|
|
17
|
+
* ones until their shape is collected, the two planes for the life of the process, which is a few
|
|
18
|
+
* hundred bytes of vertex data against a plane rebuilt per shape layer of every plan.
|
|
19
|
+
* @param shape - The shape to resolve.
|
|
20
|
+
* @param solid - Whether the container drawing it holds volumes. A batched mesh mirrors its first
|
|
21
|
+
* geometry's attributes *and* its index-ness and three rejects any later geometry that disagrees,
|
|
22
|
+
* so the container's own flag decides the layout rather than anything derived here — a container
|
|
23
|
+
* holding a shape it should not is then caught by `addGeometry` rather than served quietly.
|
|
24
|
+
* @returns The geometry, built on first use.
|
|
25
|
+
*/
|
|
26
|
+
export declare function shapeGeometry(shape: Shape, solid: boolean): BufferGeometry;
|
|
27
|
+
//# sourceMappingURL=shape-geometry.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { isBox, isRect } from '@expofp/geometry';
|
|
2
|
+
import { BufferGeometry, PlaneGeometry } from 'three';
|
|
3
|
+
import { toCreasedNormals } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
4
|
+
/**
|
|
5
|
+
* The template geometry a shape draws with, in the layout its batch locks itself into. Boxes and
|
|
6
|
+
* rects share one unit plane and are placed by their instance transform; meshes and polygons carry
|
|
7
|
+
* absolute coordinates in their own geometry.
|
|
8
|
+
*
|
|
9
|
+
* The cache is process-wide rather than per container, because the two solid batches — opaque and
|
|
10
|
+
* translucent — build byte-identical geometry for the same shape. Without sharing, a volume moving
|
|
11
|
+
* between them as its alpha crosses 1 re-runs `toNonIndexed`, `computeVertexNormals` and
|
|
12
|
+
* `toCreasedNormals` over every vertex, and does so for the batch's *whole* membership, since a
|
|
13
|
+
* container resolves every member's geometry to measure its capacity before anything claims a slot.
|
|
14
|
+
*
|
|
15
|
+
* Sharing one template across several meshes is safe because `BatchedMesh.addGeometry` **copies** its
|
|
16
|
+
* data in: a template is never uploaded to the GPU, and nothing mutates one after it is built. What
|
|
17
|
+
* it costs is that a template now outlives the container that first asked for it — the mesh-like
|
|
18
|
+
* ones until their shape is collected, the two planes for the life of the process, which is a few
|
|
19
|
+
* hundred bytes of vertex data against a plane rebuilt per shape layer of every plan.
|
|
20
|
+
* @param shape - The shape to resolve.
|
|
21
|
+
* @param solid - Whether the container drawing it holds volumes. A batched mesh mirrors its first
|
|
22
|
+
* geometry's attributes *and* its index-ness and three rejects any later geometry that disagrees,
|
|
23
|
+
* so the container's own flag decides the layout rather than anything derived here — a container
|
|
24
|
+
* holding a shape it should not is then caught by `addGeometry` rather than served quietly.
|
|
25
|
+
* @returns The geometry, built on first use.
|
|
26
|
+
*/
|
|
27
|
+
export function shapeGeometry(shape, solid) {
|
|
28
|
+
const layout = solid ? 'solid' : 'planar';
|
|
29
|
+
if (isBox(shape) || isRect(shape))
|
|
30
|
+
return (planes[layout] ??= makePlaneGeometry(solid));
|
|
31
|
+
let layouts = shapeGeometries.get(shape);
|
|
32
|
+
if (!layouts) {
|
|
33
|
+
layouts = {};
|
|
34
|
+
shapeGeometries.set(shape, layouts);
|
|
35
|
+
}
|
|
36
|
+
return (layouts[layout] ??= makeMeshGeometry(shape, solid));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The unit plane every box and rect draws with, scaled and placed by its instance transform.
|
|
40
|
+
*
|
|
41
|
+
* A planar container strips it to positions: `MeshBasicMaterial` is unlit, so normals and uvs go
|
|
42
|
+
* unused, and stripping them matches the position-only mesh geometry beside it — a batched mesh
|
|
43
|
+
* locks its attribute layout to the first geometry it sees, so every geometry in it must agree. A
|
|
44
|
+
* solid container keeps the normals for the lit material and drops the index, because creased mesh
|
|
45
|
+
* geometry is non-indexed and three refuses to mix the two.
|
|
46
|
+
* @param solid - Whether the container draws volumes.
|
|
47
|
+
* @returns The unit plane, in the container's layout.
|
|
48
|
+
*/
|
|
49
|
+
function makePlaneGeometry(solid) {
|
|
50
|
+
const plane = new PlaneGeometry().deleteAttribute('uv');
|
|
51
|
+
return solid ? plane.toNonIndexed() : plane.deleteAttribute('normal');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Builds geometry from a mesh-like shape: indexed and position-only for a planar container,
|
|
55
|
+
* non-indexed with creased normals for a solid one.
|
|
56
|
+
*
|
|
57
|
+
* The creasing is what makes an extruded shape read as architecture rather than as a smoothed blob —
|
|
58
|
+
* faces meeting at more than 30° keep a hard edge, so walls stay flat and corners stay sharp.
|
|
59
|
+
* @param mesh - The shape supplying vertices and triangle indices.
|
|
60
|
+
* @param solid - Whether the container draws volumes.
|
|
61
|
+
* @returns The geometry.
|
|
62
|
+
*/
|
|
63
|
+
function makeMeshGeometry(mesh, solid) {
|
|
64
|
+
const geometry = new BufferGeometry()
|
|
65
|
+
.setFromPoints(mesh.vertices)
|
|
66
|
+
.setIndex(mesh.indices.flat());
|
|
67
|
+
if (!solid)
|
|
68
|
+
return geometry;
|
|
69
|
+
// Not chained: `computeVertexNormals` returns void rather than `this`, and `toCreasedNormals`
|
|
70
|
+
// needs the seed normals it writes.
|
|
71
|
+
const expanded = geometry.toNonIndexed();
|
|
72
|
+
expanded.computeVertexNormals();
|
|
73
|
+
return toCreasedNormals(expanded, CREASE_ANGLE);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Template geometry per mesh-like shape, weak so it is collected with the shape that keyed it.
|
|
77
|
+
*
|
|
78
|
+
* Both layouts hang off one entry rather than the key carrying the layout, because a shape can be
|
|
79
|
+
* asked for either. Its own kind fixes which one it is *placed* in, but a def whose `shape` is
|
|
80
|
+
* replaced with one of another kind draws from its old batch for the commit between the mismatch
|
|
81
|
+
* report and the re-split — long enough to resolve a geometry there. Keyed by shape alone, that
|
|
82
|
+
* first ask would decide the layout for every later one, and a lit batch would end up drawing a
|
|
83
|
+
* position-only geometry with no normals to shade: dark, and no error to say why.
|
|
84
|
+
*
|
|
85
|
+
* Identity is the right key for the same reason it is the right key for a slot's resource: a shape
|
|
86
|
+
* is replaced rather than mutated when a def's geometry changes, so a new shape arrives as a new
|
|
87
|
+
* key and the entry it supersedes is collected with the old one. An in-place topology mutation is
|
|
88
|
+
* not a supported update either way.
|
|
89
|
+
*/
|
|
90
|
+
const shapeGeometries = new WeakMap();
|
|
91
|
+
/** The unit plane in each layout, built on the first box or rect a container of that kind draws. */
|
|
92
|
+
const planes = {};
|
|
93
|
+
/** Above this angle between neighboring faces, a shared edge stays hard instead of being smoothed. */
|
|
94
|
+
const CREASE_ANGLE = Math.PI / 6;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type TextDef } from '../../types/defs';
|
|
2
|
+
import { type InstanceProps } from '../containers';
|
|
3
|
+
import { type TextContainer } from './text-container';
|
|
4
|
+
/** Props for {@link TextInstance}. */
|
|
5
|
+
export type TextInstanceProps = InstanceProps<TextDef, TextContainer>;
|
|
6
|
+
/**
|
|
7
|
+
* Keeps one text def's lines current: claims a group of members on mount, writes each line's
|
|
8
|
+
* content, placement and visibility on mount and after every `update(def)`, and releases the group
|
|
9
|
+
* on unmount.
|
|
10
|
+
*
|
|
11
|
+
* Renders no scene node — the batch belongs to the container. The claim keys on `def.lines.length`,
|
|
12
|
+
* so adding or removing a line re-claims the group with the new size; changing a line's *content*
|
|
13
|
+
* is an ordinary write.
|
|
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 lines live in the batch, not the scene graph.
|
|
20
|
+
*/
|
|
21
|
+
export declare const TextInstance: import("react").MemoExoticComponent<(props: TextInstanceProps) => null>;
|
|
22
|
+
//# sourceMappingURL=TextInstance.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { Color, LinearSRGBColorSpace, SRGBColorSpace } from 'three';
|
|
3
|
+
import { normalizeColor } from '../../utils/colors';
|
|
4
|
+
import { createLogger } from '../../utils/logging';
|
|
5
|
+
import { useDefSync } from '../containers';
|
|
6
|
+
import { applyPlacement, layoutText } from './text-layout';
|
|
7
|
+
const logger = createLogger('text-instance');
|
|
8
|
+
/**
|
|
9
|
+
* Keeps one text def's lines current: claims a group of members on mount, writes each line's
|
|
10
|
+
* content, placement and visibility on mount and after every `update(def)`, and releases the group
|
|
11
|
+
* on unmount.
|
|
12
|
+
*
|
|
13
|
+
* Renders no scene node — the batch belongs to the container. The claim keys on `def.lines.length`,
|
|
14
|
+
* so adding or removing a line re-claims the group with the new size; changing a line's *content*
|
|
15
|
+
* is an ordinary write.
|
|
16
|
+
*
|
|
17
|
+
* Memoized for the same reason as `ShapeInstance`: a structural change hands the layer a fresh
|
|
18
|
+
* `defs` snapshot, so element-identity bailout never applies and only prop equality keeps unchanged
|
|
19
|
+
* siblings from re-rendering. Correctness never depends on it.
|
|
20
|
+
* @param props - The def and its container.
|
|
21
|
+
* @returns `null`; the lines live in the batch, not the scene graph.
|
|
22
|
+
*/
|
|
23
|
+
export const TextInstance = memo(function TextInstance(props) {
|
|
24
|
+
const { def, container } = props;
|
|
25
|
+
useDefSync(container, def, def.lines.length, (slot) => {
|
|
26
|
+
writeText(container, slot, def);
|
|
27
|
+
});
|
|
28
|
+
return null;
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Writes a def's whole line stack into its group.
|
|
32
|
+
* @param container - The container holding the group.
|
|
33
|
+
* @param slot - The def's group.
|
|
34
|
+
* @param def - The text def supplying content and layout.
|
|
35
|
+
*/
|
|
36
|
+
function writeText(container, slot, def) {
|
|
37
|
+
const { texts, slots } = container.linesAt(slot);
|
|
38
|
+
const placements = layoutText(def);
|
|
39
|
+
for (let line = 0; line < texts.length; line++) {
|
|
40
|
+
writeLine(container.mesh, slots[line], texts[line], def.lines[line], placements[line], def);
|
|
41
|
+
}
|
|
42
|
+
// Content writes flag the batch as needing a sync; this is what actually runs one, coalesced
|
|
43
|
+
// across every instance that wrote in the same tick.
|
|
44
|
+
container.scheduleSync();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Writes one line's content and placement into its member.
|
|
48
|
+
* @param mesh - The batch, for the visibility write that also invalidates its bounds.
|
|
49
|
+
* @param lineSlot - The member's batch slot.
|
|
50
|
+
* @param text - The member.
|
|
51
|
+
* @param line - The line def.
|
|
52
|
+
* @param placed - The line's laid-out placement.
|
|
53
|
+
* @param def - The owning def, supplying the bounds the placement is relative to.
|
|
54
|
+
*/
|
|
55
|
+
function writeLine(mesh, lineSlot, text, line, placed, def) {
|
|
56
|
+
// A zero font size is the floorplan's "too small to read at this zoom" sentinel — its scale tables
|
|
57
|
+
// fall through to zero rather than carrying a separate hidden flag. Routed through the batch, not
|
|
58
|
+
// `text.visible`, so the bounds that enclose only visible members are invalidated with it.
|
|
59
|
+
mesh.setVisibleAt(lineSlot, line.fontSize > 0);
|
|
60
|
+
if (line.fontSize <= 0)
|
|
61
|
+
return;
|
|
62
|
+
// All syncable; troika's own setters skip an unchanged write, so an update that only moved a
|
|
63
|
+
// label re-lays-out nothing.
|
|
64
|
+
text.text = line.text;
|
|
65
|
+
text.font = line.fontUrl;
|
|
66
|
+
text.anchorX = placed.anchorX;
|
|
67
|
+
text.anchorY = placed.anchorY;
|
|
68
|
+
text.textAlign = placed.textAlign;
|
|
69
|
+
writeColorAndOpacity(text, line.color, 'fill');
|
|
70
|
+
// `stroke` is a halo drawn *around* the glyphs, so it maps to troika's outline rather than its
|
|
71
|
+
// stroke, which is drawn inside them.
|
|
72
|
+
//
|
|
73
|
+
// Divided by the font size, because the outline is measured in the member's own units and the
|
|
74
|
+
// member lays out at one em while the def's width is in the same units as its `fontSize`. Without
|
|
75
|
+
// the conversion a two-unit halo becomes two *ems* and swallows the glyphs entirely.
|
|
76
|
+
if (line.stroke) {
|
|
77
|
+
writeColorAndOpacity(text, line.stroke.color, 'outline');
|
|
78
|
+
text.outlineWidth = line.fontSize == 0 ? 0 : line.stroke.width / line.fontSize;
|
|
79
|
+
}
|
|
80
|
+
applyPlacement(text, placed, def.bounds);
|
|
81
|
+
}
|
|
82
|
+
function writeColorAndOpacity(text, color, field) {
|
|
83
|
+
const colorFieldName = field == 'fill' ? 'color' : 'outlineColor';
|
|
84
|
+
const opacityFieldName = `${field}Opacity`;
|
|
85
|
+
const rgb = normalizeColor(color);
|
|
86
|
+
if (!rgb) {
|
|
87
|
+
logger.warn(`Invalid color: ${color} %O`, text);
|
|
88
|
+
text[colorFieldName] = color;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
_color.setRGB(rgb.r / 255, rgb.g / 255, rgb.b / 255, SRGBColorSpace);
|
|
92
|
+
text[colorFieldName] = _color.getHex(LinearSRGBColorSpace);
|
|
93
|
+
text[opacityFieldName] = rgb.a;
|
|
94
|
+
}
|
|
95
|
+
const _color = new Color();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type TextDef } from '../../types/defs';
|
|
2
|
+
import { type DefsProps } from '../containers';
|
|
3
|
+
/** Props for {@link Texts}. */
|
|
4
|
+
export type TextsProps = DefsProps<TextDef>;
|
|
5
|
+
/**
|
|
6
|
+
* Draws a collection of texts as members of one `BatchedText`, so a layer of labels renders in a
|
|
7
|
+
* single draw call.
|
|
8
|
+
*
|
|
9
|
+
* Each def contributes one member per line, laid out as a block inside the def's bounds. Membership
|
|
10
|
+
* is reconciled by React exactly as it is for shapes: a new `defs` array re-renders this component
|
|
11
|
+
* and React mounts or unmounts only the delta, with no differ of our own.
|
|
12
|
+
*
|
|
13
|
+
* Two things differ from a shape layer, both because a text batch draws through packed glyph
|
|
14
|
+
* attributes rather than an indirect draw list:
|
|
15
|
+
*
|
|
16
|
+
* - **Paint order is the pack order**, so there is no `customSort`; the container hands the batch an
|
|
17
|
+
* ordered member list and a change repacks. The order pass skips an unchanged order to keep an
|
|
18
|
+
* `update()` that moved nothing from repacking.
|
|
19
|
+
* - **Syncing is asynchronous** — troika fetches and parses the font — so the container drives it
|
|
20
|
+
* eagerly with R3F's `invalidate` as the completion callback, rather than leaving it to the next
|
|
21
|
+
* `onBeforeRender`, which would have nothing to request the frame that shows the result.
|
|
22
|
+
*
|
|
23
|
+
* The material is an ordinary `<ColorMaterial>` child: troika derives its text material from
|
|
24
|
+
* whatever base it is given, and the base is what supplies the layer blending — which is also what
|
|
25
|
+
* keeps the batch out of three's transparent render list.
|
|
26
|
+
* @param props - The texts to draw.
|
|
27
|
+
* @returns The batch and one instance component per text.
|
|
28
|
+
*/
|
|
29
|
+
export declare function Texts(props: TextsProps): import("react").JSX.Element;
|
|
30
|
+
//# sourceMappingURL=Texts.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useThree } from '@react-three/fiber';
|
|
3
|
+
import { BatchedText } from '../../core/text/BatchedText';
|
|
4
|
+
import { defKey, useContainer } from '../containers';
|
|
5
|
+
import { ColorMaterial } from '../materials/ColorMaterial';
|
|
6
|
+
import { TextContainer } from './text-container';
|
|
7
|
+
import { TextInstance } from './TextInstance';
|
|
8
|
+
/**
|
|
9
|
+
* Draws a collection of texts as members of one `BatchedText`, so a layer of labels renders in a
|
|
10
|
+
* single draw call.
|
|
11
|
+
*
|
|
12
|
+
* Each def contributes one member per line, laid out as a block inside the def's bounds. Membership
|
|
13
|
+
* is reconciled by React exactly as it is for shapes: a new `defs` array re-renders this component
|
|
14
|
+
* and React mounts or unmounts only the delta, with no differ of our own.
|
|
15
|
+
*
|
|
16
|
+
* Two things differ from a shape layer, both because a text batch draws through packed glyph
|
|
17
|
+
* attributes rather than an indirect draw list:
|
|
18
|
+
*
|
|
19
|
+
* - **Paint order is the pack order**, so there is no `customSort`; the container hands the batch an
|
|
20
|
+
* ordered member list and a change repacks. The order pass skips an unchanged order to keep an
|
|
21
|
+
* `update()` that moved nothing from repacking.
|
|
22
|
+
* - **Syncing is asynchronous** — troika fetches and parses the font — so the container drives it
|
|
23
|
+
* eagerly with R3F's `invalidate` as the completion callback, rather than leaving it to the next
|
|
24
|
+
* `onBeforeRender`, which would have nothing to request the frame that shows the result.
|
|
25
|
+
*
|
|
26
|
+
* The material is an ordinary `<ColorMaterial>` child: troika derives its text material from
|
|
27
|
+
* whatever base it is given, and the base is what supplies the layer blending — which is also what
|
|
28
|
+
* keeps the batch out of three's transparent render list.
|
|
29
|
+
* @param props - The texts to draw.
|
|
30
|
+
* @returns The batch and one instance component per text.
|
|
31
|
+
*/
|
|
32
|
+
export function Texts(props) {
|
|
33
|
+
const { defs } = props;
|
|
34
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
35
|
+
// The container is bookkeeping only — the members and the mesh are created against R3F's object —
|
|
36
|
+
// so the copy StrictMode's double-invoked initializer discards is plain garbage.
|
|
37
|
+
const container = useContainer(() => new TextContainer(invalidate), defs);
|
|
38
|
+
// The `ref` arrow (not `ref={container.bind}`) keeps the React Compiler from inferring `container`
|
|
39
|
+
// itself as a ref and rejecting its render-time reads.
|
|
40
|
+
return (_jsxs(_Fragment, { children: [_jsx(BatchedText, { ref: (mesh) => container.bind(mesh), children: _jsx(ColorMaterial, {}) }), defs.map((def) => (_jsx(TextInstance, { def: def, container: container }, defKey(def))))] }));
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Texts } from './Texts';
|