@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,251 @@
|
|
|
1
|
+
import { DIM_SCHEMA, DIM_UNIFORM, dimOrdered, instanceDim, isDimmed } from '../materials/dim';
|
|
2
|
+
/**
|
|
3
|
+
* One layer's mesh bookkeeping: the bound mesh, which slot each def holds, and the paint order
|
|
4
|
+
* across them.
|
|
5
|
+
*
|
|
6
|
+
* A container is what stands between a layer's defs and one mesh. It is created during render,
|
|
7
|
+
* sized to the membership it first sees, and held in `useState` for as long as its component is
|
|
8
|
+
* mounted; the mesh itself is R3F's, constructed from the subclass's args and handed back through
|
|
9
|
+
* {@link bind}, so R3F owns its lifetime and disposal. Instances claim and release slots from their
|
|
10
|
+
* own effects, and write their type-specific state through the subclass's own methods.
|
|
11
|
+
*
|
|
12
|
+
* What lives here is everything that does not depend on which def type is drawn:
|
|
13
|
+
*
|
|
14
|
+
* - **Slot allocation**, in both directions. `slotOf` is what a paint-order pass over the layer's
|
|
15
|
+
* defs looks each def up with, and {@link defAt} is what turns a pointer hit — which names a slot
|
|
16
|
+
* — back into the def that drew it. Slots come from a mesh's free list, so a released slot may be
|
|
17
|
+
* handed to a different def later; freeing by slot keeps both directions from outliving the claim
|
|
18
|
+
* they describe.
|
|
19
|
+
* - **Paint order**, explicitly. Instance ids come from a mesh's free list, so they do not track
|
|
20
|
+
* sibling order — a container that let slot order stand in for paint order would reorder itself
|
|
21
|
+
* after any claim/release churn, and again under StrictMode's remount, which re-claims in reverse.
|
|
22
|
+
* Explicit order is also what the 2D painter's algorithm needs: 2D content never writes depth, so
|
|
23
|
+
* nothing it draws is in the buffer for the next 2D fragment to test against, and the draw list's
|
|
24
|
+
* order is the only thing that decides overlap.
|
|
25
|
+
* - **The two fields every def type carries**, `hidden` and `dim`, through {@link setVisibleAt} and
|
|
26
|
+
* {@link setDimAt} — which is what lets one hook drive them for every def type.
|
|
27
|
+
*
|
|
28
|
+
* A subclass supplies {@link addInstance} and {@link removeInstance}, names its mesh and def types,
|
|
29
|
+
* and adds whatever its own defs need.
|
|
30
|
+
* @template R - The per-instance resource a claim allocates against (a geometry, a texture region,
|
|
31
|
+
* a line count); compared by identity, so a stable reference means a stable slot. `null` for a
|
|
32
|
+
* container whose members share everything.
|
|
33
|
+
* @template M - The mesh this container writes into.
|
|
34
|
+
* @template T - The def kind the layer holds.
|
|
35
|
+
*/
|
|
36
|
+
export class SlotContainer {
|
|
37
|
+
/**
|
|
38
|
+
* Sorts a mesh's render list into paint order. Assigned straight to the mesh's `customSort` JSX
|
|
39
|
+
* prop, whose R3F-generated type leaves the value effectively unchecked — so the entry type here
|
|
40
|
+
* needs no cast against three's published one, which omits `index`.
|
|
41
|
+
*
|
|
42
|
+
* An arrow property rather than a method, because it is handed over as a bare value and a
|
|
43
|
+
* prototype method would arrive unbound.
|
|
44
|
+
* @param list - The render list, sorted in place.
|
|
45
|
+
*/
|
|
46
|
+
customSort = (list) => {
|
|
47
|
+
const order = this.orderBySlot;
|
|
48
|
+
list.sort((a, b) => this.sortDirection * ((order[a.index] ?? 0) - (order[b.index] ?? 0)));
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The layer's membership, in authored paint order. Kept so a def whose own dim changes can re-run
|
|
52
|
+
* the order pass without this container having re-rendered — `update(def)` reaches the instance,
|
|
53
|
+
* not here.
|
|
54
|
+
*/
|
|
55
|
+
layerDefs = [];
|
|
56
|
+
/**
|
|
57
|
+
* Whether the layer is dimmed, which is what a def without a `dim` of its own inherits.
|
|
58
|
+
*
|
|
59
|
+
* A flag rather than the layer's amount, because the amount is the shader's business and this is
|
|
60
|
+
* only ever asked a yes-or-no question. It is also what makes an eased layer dim cheap: the order
|
|
61
|
+
* pass re-runs when the value crosses zero, not on every frame of the ease.
|
|
62
|
+
*/
|
|
63
|
+
layerDimmed = false;
|
|
64
|
+
/**
|
|
65
|
+
* Whether each slot currently draws dimmed, so a dim write that does not cross the split costs
|
|
66
|
+
* nothing.
|
|
67
|
+
*/
|
|
68
|
+
dimmedBySlot = [];
|
|
69
|
+
/**
|
|
70
|
+
* Each def's order, durable and weak — collected with the def. It outlives a slot, so a re-claim
|
|
71
|
+
* can seed the new slot's order without waiting for the next order pass.
|
|
72
|
+
*/
|
|
73
|
+
orderByDef = new WeakMap();
|
|
74
|
+
/** Each slot's place in the paint order, read by {@link customSort}. */
|
|
75
|
+
orderBySlot = [];
|
|
76
|
+
slotByDef = new Map();
|
|
77
|
+
defBySlot = new Map();
|
|
78
|
+
schema;
|
|
79
|
+
sortDirection;
|
|
80
|
+
/** Set by {@link bind}, which runs before any effect can claim a slot. */
|
|
81
|
+
boundMesh;
|
|
82
|
+
/**
|
|
83
|
+
* @param options - How this container differs from the default.
|
|
84
|
+
*/
|
|
85
|
+
constructor(options = {}) {
|
|
86
|
+
this.schema = options.schema ?? DIM_SCHEMA;
|
|
87
|
+
this.sortDirection = options.reverse ? -1 : 1;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The mesh every instance writes into. Undefined until {@link bind}; refs attach before passive
|
|
91
|
+
* effects run, so every claim and slot write sees it bound.
|
|
92
|
+
*/
|
|
93
|
+
get mesh() {
|
|
94
|
+
// Asserted rather than guarded: reading before the ref attached is a programming error, and the
|
|
95
|
+
// ordering that rules it out is React's, not something a check here could repair.
|
|
96
|
+
return this.boundMesh;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Binds the mesh R3F constructed from this container's args, declaring its per-instance uniforms.
|
|
100
|
+
*
|
|
101
|
+
* Bind-once: the ref-cleanup `null` and any re-bind are ignored, because React StrictMode cycles
|
|
102
|
+
* refs (attach → cleanup → attach) around a passive effect cycle — a container that forgot its
|
|
103
|
+
* mesh there would turn {@link release} into a no-op and leak the slot when the remounted instance
|
|
104
|
+
* re-claims, and one that re-declared its uniforms would strand the values already written into
|
|
105
|
+
* them.
|
|
106
|
+
* @param mesh - The mounted mesh, or the ref-cleanup `null`.
|
|
107
|
+
*/
|
|
108
|
+
bind(mesh) {
|
|
109
|
+
if (!mesh || this.boundMesh)
|
|
110
|
+
return;
|
|
111
|
+
this.boundMesh = mesh;
|
|
112
|
+
// The material owns each uniform's name and shape; the mesh only has to declare the rows before
|
|
113
|
+
// any slot writes into them — which the ref this runs from guarantees, since refs attach before
|
|
114
|
+
// the effects that claim slots and long before the first draw.
|
|
115
|
+
mesh.addUniforms(this.schema);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Reserves a slot for one instance, backed by `resource`.
|
|
119
|
+
*
|
|
120
|
+
* Paint order is deliberately not a parameter: it is a property of the whole sibling list, so the
|
|
121
|
+
* component writes it for every def in one pass after each membership change (see
|
|
122
|
+
* {@link setPaintOrder}) — an instance never learns its index, which is what lets a memoized
|
|
123
|
+
* instance ignore structural re-renders entirely.
|
|
124
|
+
* @param def - The def the slot will draw; def-to-slot bookkeeping is keyed by its identity.
|
|
125
|
+
* @param resource - The per-instance resource to allocate against.
|
|
126
|
+
* @returns The slot index.
|
|
127
|
+
*/
|
|
128
|
+
claim(def, resource) {
|
|
129
|
+
const slot = this.addInstance(resource);
|
|
130
|
+
this.slotByDef.set(def, slot);
|
|
131
|
+
this.defBySlot.set(slot, def);
|
|
132
|
+
// Seeded from the durable assignment so a re-claim keeps its order; a first claim before any
|
|
133
|
+
// `setPaintOrder` defaults to 0 and the component's order pass relabels it in the same effect
|
|
134
|
+
// flush, before anything draws.
|
|
135
|
+
this.orderBySlot[slot] = this.orderByDef.get(def) ?? 0;
|
|
136
|
+
return slot;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Frees a slot, and everything recorded about the claim it held.
|
|
140
|
+
* @param slot - The slot to free.
|
|
141
|
+
*/
|
|
142
|
+
release(slot) {
|
|
143
|
+
this.removeInstance(slot);
|
|
144
|
+
this.orderBySlot[slot] = 0;
|
|
145
|
+
this.dimmedBySlot[slot] = undefined;
|
|
146
|
+
const def = this.defBySlot.get(slot);
|
|
147
|
+
if (!def)
|
|
148
|
+
return;
|
|
149
|
+
this.slotByDef.delete(def);
|
|
150
|
+
this.defBySlot.delete(slot);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The def holding a slot — the reverse of {@link claim}.
|
|
154
|
+
* @param slot - The slot to look up, or `undefined` for a hit that named none.
|
|
155
|
+
* @returns The def, or `undefined` when the slot is free.
|
|
156
|
+
*/
|
|
157
|
+
defAt(slot) {
|
|
158
|
+
return slot === undefined ? undefined : this.defBySlot.get(slot);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The def an intersection with this container's mesh hit.
|
|
162
|
+
*
|
|
163
|
+
* Which field of an intersection names a slot is the container's own knowledge — `instanceId` for
|
|
164
|
+
* an instanced mesh, `batchId` for a batched one, the member id for a text batch, whose defs own
|
|
165
|
+
* several — so it is answered here rather than at each registration site.
|
|
166
|
+
* @param hit - The intersection to trace.
|
|
167
|
+
* @returns The def, or `undefined` when the hit cannot be traced to one.
|
|
168
|
+
*/
|
|
169
|
+
defFromHit(hit) {
|
|
170
|
+
return this.defAt(hit.instanceId);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Shows or hides one instance. Every def type carries `hidden`, so `useDefSync` drives this for
|
|
174
|
+
* all of them.
|
|
175
|
+
* @param slot - The slot to show or hide.
|
|
176
|
+
* @param visible - Whether the instance should draw.
|
|
177
|
+
*/
|
|
178
|
+
setVisibleAt(slot, visible) {
|
|
179
|
+
this.mesh.setVisibleAt(slot, visible);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Sets one instance's own dim, or clears it back to its layer's.
|
|
183
|
+
*
|
|
184
|
+
* The counterpart to {@link setVisibleAt}, and here for the same reason: `dim` is a `SharedDef`
|
|
185
|
+
* field, so `useDefSync` drives it for every def type. The raw field is passed through rather than
|
|
186
|
+
* an encoded amount, which keeps the sentinel — and the rest of the shader's contract — inside the
|
|
187
|
+
* material module that reads it.
|
|
188
|
+
* @param slot - The slot to dim.
|
|
189
|
+
* @param dim - The def's `dim`; `undefined` defers to the layer's.
|
|
190
|
+
*/
|
|
191
|
+
setDimAt(slot, dim) {
|
|
192
|
+
this.writeDim(slot, instanceDim(dim));
|
|
193
|
+
// Crossing the dimmed/lit split changes where this def draws, and nothing else will notice: a
|
|
194
|
+
// def's own `update()` reaches its instance, never the component that renders this container.
|
|
195
|
+
const dimmed = isDimmed(dim, this.layerDimmed);
|
|
196
|
+
if (this.layerDefs.length === 0 || this.dimmedBySlot[slot] === dimmed)
|
|
197
|
+
return;
|
|
198
|
+
this.applyOrder();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Writes an encoded dim into a slot's uniform — the part of {@link setDimAt} that knows how a slot
|
|
202
|
+
* maps onto the mesh, split out so a container whose slot spans several mesh instances overrides
|
|
203
|
+
* only that and keeps the split-crossing check above.
|
|
204
|
+
* @param slot - The slot to write.
|
|
205
|
+
* @param amount - The encoded amount, from `instanceDim`.
|
|
206
|
+
*/
|
|
207
|
+
writeDim(slot, amount) {
|
|
208
|
+
this.mesh.setUniformAt(slot, DIM_UNIFORM, amount);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Declares the layer's membership and dim, and re-runs the order pass over them.
|
|
212
|
+
*
|
|
213
|
+
* The component calls this from an effect after each structural change — child effects run first,
|
|
214
|
+
* so every mounted instance has claimed by then. Defs that have not claimed yet are recorded
|
|
215
|
+
* anyway: a later claim seeds its slot's order from this assignment, which is what keeps a
|
|
216
|
+
* re-claim correctly ordered without React involvement.
|
|
217
|
+
* @param defs - The current membership, in paint order.
|
|
218
|
+
* @param layerDimmed - Whether the layer is dimmed, inherited by defs without a `dim` of their own.
|
|
219
|
+
*/
|
|
220
|
+
setPaintOrder(defs, layerDimmed) {
|
|
221
|
+
this.layerDefs = defs;
|
|
222
|
+
this.layerDimmed = layerDimmed;
|
|
223
|
+
this.applyOrder();
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Labels every claimed slot with its place in the layer's draw order, undimmed defs last.
|
|
227
|
+
*
|
|
228
|
+
* Overridden by a container whose mesh has no sortable draw list, where ordering means handing the
|
|
229
|
+
* mesh a rebuilt member list instead.
|
|
230
|
+
*/
|
|
231
|
+
applyOrder() {
|
|
232
|
+
const ordered = dimOrdered(this.layerDefs, this.layerDimmed);
|
|
233
|
+
for (let order = 0; order < ordered.length; order++) {
|
|
234
|
+
const def = ordered[order];
|
|
235
|
+
this.orderByDef.set(def, order);
|
|
236
|
+
const slot = this.slotOf(def);
|
|
237
|
+
if (slot === undefined)
|
|
238
|
+
continue;
|
|
239
|
+
this.orderBySlot[slot] = order;
|
|
240
|
+
this.dimmedBySlot[slot] = isDimmed(def.dim, this.layerDimmed);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The slot a def holds, for an order pass walking the layer's defs.
|
|
245
|
+
* @param def - The def to look up.
|
|
246
|
+
* @returns The slot, or `undefined` when the def has not claimed one.
|
|
247
|
+
*/
|
|
248
|
+
slotOf(def) {
|
|
249
|
+
return this.slotByDef.get(def);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import { type RenderableDef } from '../../types/defs';
|
|
3
|
+
import { type SlotContainer, type SlotMesh } from './SlotContainer';
|
|
4
|
+
/**
|
|
5
|
+
* Creates one layer's container and keeps it wired to its layer for as long as the component is
|
|
6
|
+
* mounted: the membership's paint order, and how hits on its mesh resolve back to defs.
|
|
7
|
+
*
|
|
8
|
+
* The container is built once, from the first render's defs, and outlives every render after —
|
|
9
|
+
* membership changes grow it in place rather than rebuilding it, which is what lets the mesh R3F
|
|
10
|
+
* built from its args stand too. `create` is therefore called once and its later identities ignored.
|
|
11
|
+
* @param create - Builds the container. Called on the first render only.
|
|
12
|
+
* @param defs - The layer's defs, in authored paint order.
|
|
13
|
+
* @returns The container.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useContainer<T extends RenderableDef, C extends SlotContainer<unknown, SlotMesh, T>>(create: () => C, defs: readonly T[]): C;
|
|
16
|
+
/**
|
|
17
|
+
* Claims a slot for one instance, releasing on unmount. Paint order is not this hook's concern —
|
|
18
|
+
* the container writes it for the whole sibling list in one pass (see {@link useContainer}).
|
|
19
|
+
* @param container - The container holding the slot.
|
|
20
|
+
* @param def - The def this instance renders.
|
|
21
|
+
* @param resource - The per-instance resource backing the slot; a new identity re-claims.
|
|
22
|
+
* @returns A ref holding the current slot, or `-1` while unclaimed. Read it at call time — a
|
|
23
|
+
* re-claim changes the slot without re-rendering.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useSlot<R>(container: SlotContainer<R>, def: RenderableDef, resource: R): RefObject<number>;
|
|
26
|
+
/**
|
|
27
|
+
* Claims a slot for one instance and keeps it in sync with its def: the slot is released on
|
|
28
|
+
* unmount, and on mount and after every `update(def)` the def's visibility and dim are written and —
|
|
29
|
+
* only while it is visible — `write` fills in the rest of the instance's state.
|
|
30
|
+
*
|
|
31
|
+
* The sync is registered as the def's slot writer, so it is also what the camera re-layout frame
|
|
32
|
+
* loop pulls out of `slotWriters` and calls directly — the React path and the per-frame path are
|
|
33
|
+
* one implementation, and both therefore agree about visibility.
|
|
34
|
+
*
|
|
35
|
+
* `hidden` and `dim` live on every def type, so their writes — and the short-circuit `hidden` gates
|
|
36
|
+
* — belong here rather than repeated in each instance component's callback. A hidden instance is
|
|
37
|
+
* never drawn, so computing its transform, color, or glyphs would be wasted; the update that shows
|
|
38
|
+
* it re-runs this sync, which writes the then-current state. Everything type-specific stays in
|
|
39
|
+
* `write`, which is what keeps this hook generic across every def type.
|
|
40
|
+
* @param container - The container holding the slot.
|
|
41
|
+
* @param def - The def this instance renders.
|
|
42
|
+
* @param resource - The per-instance resource backing the slot; a new identity re-claims.
|
|
43
|
+
* @param write - Writes the def's type-specific state into its slot. Called only while visible.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useDefSync<R>(container: SlotContainer<R>, def: RenderableDef, resource: R, write: (slot: number) => void): void;
|
|
46
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber';
|
|
2
|
+
import { use, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
|
+
import { registerHitResolver } from '../../events/pointer';
|
|
4
|
+
import { registerSlotWriter, subscribeDef, syncNow, versionOf } from '../../update';
|
|
5
|
+
import { LayerDimContext } from '../materials/dim';
|
|
6
|
+
/**
|
|
7
|
+
* Creates one layer's container and keeps it wired to its layer for as long as the component is
|
|
8
|
+
* mounted: the membership's paint order, and how hits on its mesh resolve back to defs.
|
|
9
|
+
*
|
|
10
|
+
* The container is built once, from the first render's defs, and outlives every render after —
|
|
11
|
+
* membership changes grow it in place rather than rebuilding it, which is what lets the mesh R3F
|
|
12
|
+
* built from its args stand too. `create` is therefore called once and its later identities ignored.
|
|
13
|
+
* @param create - Builds the container. Called on the first render only.
|
|
14
|
+
* @param defs - The layer's defs, in authored paint order.
|
|
15
|
+
* @returns The container.
|
|
16
|
+
*/
|
|
17
|
+
export function useContainer(create, defs) {
|
|
18
|
+
const [container] = useState(create);
|
|
19
|
+
// Only whether the layer is dimmed, not by how much: the order pass splits two ways, so an eased
|
|
20
|
+
// dim re-runs it as its value crosses zero rather than on every frame of the ease.
|
|
21
|
+
const layerDimmed = use(LayerDimContext) > 0;
|
|
22
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
23
|
+
// Paint order for the whole membership, one pass per structural change. Child effects run before
|
|
24
|
+
// this one, so every instance the change mounted has already claimed its slot; defs whose
|
|
25
|
+
// instances bailed out (memo) never re-ran, which is fine — order lives in the container, not in
|
|
26
|
+
// their props. Running inside the same effect flush as the claims is what keeps a frame from
|
|
27
|
+
// drawing between a claim's default order and this pass.
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
container.setPaintOrder(defs, layerDimmed);
|
|
30
|
+
invalidate();
|
|
31
|
+
}, [container, defs, layerDimmed, invalidate]);
|
|
32
|
+
// How a hit on this container's mesh becomes a def. From an effect, so the mesh is bound by now;
|
|
33
|
+
// the container answers which field of an intersection names its slot, so nothing here has to know
|
|
34
|
+
// one mesh kind from another.
|
|
35
|
+
useEffect(() => registerHitResolver(container.mesh, (hit) => container.defFromHit(hit)), [container]);
|
|
36
|
+
return container;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Claims a slot for one instance, releasing on unmount. Paint order is not this hook's concern —
|
|
40
|
+
* the container writes it for the whole sibling list in one pass (see {@link useContainer}).
|
|
41
|
+
* @param container - The container holding the slot.
|
|
42
|
+
* @param def - The def this instance renders.
|
|
43
|
+
* @param resource - The per-instance resource backing the slot; a new identity re-claims.
|
|
44
|
+
* @returns A ref holding the current slot, or `-1` while unclaimed. Read it at call time — a
|
|
45
|
+
* re-claim changes the slot without re-rendering.
|
|
46
|
+
*/
|
|
47
|
+
export function useSlot(container, def, resource) {
|
|
48
|
+
const slotRef = useRef(-1);
|
|
49
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const slot = container.claim(def, resource);
|
|
52
|
+
slotRef.current = slot;
|
|
53
|
+
// On a re-claim the instance is already registered, so its state must be rewritten into the new
|
|
54
|
+
// slot. On first mount nothing is registered yet and `useDefSync` performs the initial write.
|
|
55
|
+
syncNow(def);
|
|
56
|
+
invalidate();
|
|
57
|
+
return () => {
|
|
58
|
+
container.release(slot);
|
|
59
|
+
slotRef.current = -1;
|
|
60
|
+
invalidate();
|
|
61
|
+
};
|
|
62
|
+
}, [container, def, resource, invalidate]);
|
|
63
|
+
return slotRef;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Claims a slot for one instance and keeps it in sync with its def: the slot is released on
|
|
67
|
+
* unmount, and on mount and after every `update(def)` the def's visibility and dim are written and —
|
|
68
|
+
* only while it is visible — `write` fills in the rest of the instance's state.
|
|
69
|
+
*
|
|
70
|
+
* The sync is registered as the def's slot writer, so it is also what the camera re-layout frame
|
|
71
|
+
* loop pulls out of `slotWriters` and calls directly — the React path and the per-frame path are
|
|
72
|
+
* one implementation, and both therefore agree about visibility.
|
|
73
|
+
*
|
|
74
|
+
* `hidden` and `dim` live on every def type, so their writes — and the short-circuit `hidden` gates
|
|
75
|
+
* — belong here rather than repeated in each instance component's callback. A hidden instance is
|
|
76
|
+
* never drawn, so computing its transform, color, or glyphs would be wasted; the update that shows
|
|
77
|
+
* it re-runs this sync, which writes the then-current state. Everything type-specific stays in
|
|
78
|
+
* `write`, which is what keeps this hook generic across every def type.
|
|
79
|
+
* @param container - The container holding the slot.
|
|
80
|
+
* @param def - The def this instance renders.
|
|
81
|
+
* @param resource - The per-instance resource backing the slot; a new identity re-claims.
|
|
82
|
+
* @param write - Writes the def's type-specific state into its slot. Called only while visible.
|
|
83
|
+
*/
|
|
84
|
+
export function useDefSync(container, def, resource, write) {
|
|
85
|
+
const slotRef = useSlot(container, def, resource);
|
|
86
|
+
const version = useSyncExternalStore((listener) => subscribeDef(def, listener), () => versionOf(def));
|
|
87
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
88
|
+
/**
|
|
89
|
+
* Writes the def's current state into its slot: visibility always, the rest only when shown.
|
|
90
|
+
*/
|
|
91
|
+
function sync() {
|
|
92
|
+
const slot = slotRef.current;
|
|
93
|
+
if (slot < 0)
|
|
94
|
+
return;
|
|
95
|
+
const visible = !def.hidden;
|
|
96
|
+
container.setVisibleAt(slot, visible);
|
|
97
|
+
// Written even while hidden, unlike everything in `write`: it is one number, and writing it
|
|
98
|
+
// unconditionally is what makes the slot's dim true of the def at all times rather than only
|
|
99
|
+
// while it draws — including across the re-claim that `useSlot` syncs through.
|
|
100
|
+
container.setDimAt(slot, def.dim);
|
|
101
|
+
if (!visible)
|
|
102
|
+
return;
|
|
103
|
+
write(slot);
|
|
104
|
+
}
|
|
105
|
+
// Held in a ref and refreshed after every render: `sync` closes over a fresh `write` each time,
|
|
106
|
+
// and the effect below must not re-run just because that identity changed — only a version bump
|
|
107
|
+
// means the def actually moved.
|
|
108
|
+
const syncRef = useRef(sync);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
syncRef.current = sync;
|
|
111
|
+
});
|
|
112
|
+
useEffect(() => registerSlotWriter(def, () => syncRef.current(), invalidate), [def, invalidate]);
|
|
113
|
+
// Through the registry rather than the ref, so the React path runs the very same write every other
|
|
114
|
+
// path does — the frame request that shows it included.
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
syncNow(def);
|
|
117
|
+
}, [def, version]);
|
|
118
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { useContainer, useDefSync } from './hooks';
|
|
2
|
+
export { defKey } from './keys';
|
|
3
|
+
export { type ColorWritable, type MatrixWritable, shapeToMatrix, writeColorIfChanged, writeMatrixIfChanged, } from './mesh-writes';
|
|
4
|
+
export { type DefsProps, type InstanceProps } from './props';
|
|
5
|
+
export { type BatchedMeshRenderItem, type RenderListItem, SlotContainer, type SlotContainerOptions, type SlotMesh, } from './SlotContainer';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RenderableDef } from '../../types/defs';
|
|
2
|
+
/**
|
|
3
|
+
* A stable React key for a def. Defs carry no id, and their array position shifts as siblings are
|
|
4
|
+
* added or removed, so identity is the only durable key — which is also what lets React mount and
|
|
5
|
+
* unmount exactly the defs that entered or left a layer.
|
|
6
|
+
* @param def - The def to key.
|
|
7
|
+
* @returns The def's key, allocated on first use.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defKey(def: RenderableDef): string;
|
|
10
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Keys already handed out, so a def keeps the same key for its whole life. */
|
|
2
|
+
const keys = new WeakMap();
|
|
3
|
+
let nextKey = 0;
|
|
4
|
+
/**
|
|
5
|
+
* A stable React key for a def. Defs carry no id, and their array position shifts as siblings are
|
|
6
|
+
* added or removed, so identity is the only durable key — which is also what lets React mount and
|
|
7
|
+
* unmount exactly the defs that entered or left a layer.
|
|
8
|
+
* @param def - The def to key.
|
|
9
|
+
* @returns The def's key, allocated on first use.
|
|
10
|
+
*/
|
|
11
|
+
export function defKey(def) {
|
|
12
|
+
let key = keys.get(def);
|
|
13
|
+
if (key === undefined) {
|
|
14
|
+
key = `def-${nextKey++}`;
|
|
15
|
+
keys.set(def, key);
|
|
16
|
+
}
|
|
17
|
+
return key;
|
|
18
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type Shape } from '@expofp/geometry';
|
|
2
|
+
import { Matrix4, Vector4 } from 'three';
|
|
3
|
+
import { type ColorInput } from '../../types/defs';
|
|
4
|
+
import { type Logger } from '../../utils/logging';
|
|
5
|
+
/**
|
|
6
|
+
* A mesh whose instances carry a transform.
|
|
7
|
+
*
|
|
8
|
+
* Named as a capability rather than listed as a union of our mesh classes, because not every batch
|
|
9
|
+
* has one: `BatchedLinesExt` places its members by their endpoints and has no per-instance matrix
|
|
10
|
+
* at all, yet still writes colors through the helper below. A class that satisfies only one of
|
|
11
|
+
* these two interfaces is the point — the alternative was no-op matrix methods on a class that has
|
|
12
|
+
* no transforms, which would read as a capability it does not have.
|
|
13
|
+
*
|
|
14
|
+
* Implementations flag their own buffer uploads and invalidate their own bounds inside
|
|
15
|
+
* `setMatrixAt`, so no caller has to.
|
|
16
|
+
*/
|
|
17
|
+
export interface MatrixWritable {
|
|
18
|
+
/**
|
|
19
|
+
* Reads an instance's transform.
|
|
20
|
+
* @param slot - The instance to read.
|
|
21
|
+
* @param target - Receives the result.
|
|
22
|
+
*/
|
|
23
|
+
getMatrixAt(slot: number, target: Matrix4): unknown;
|
|
24
|
+
/**
|
|
25
|
+
* Writes an instance's transform.
|
|
26
|
+
* @param slot - The instance to write.
|
|
27
|
+
* @param matrix - The transform.
|
|
28
|
+
*/
|
|
29
|
+
setMatrixAt(slot: number, matrix: Matrix4): unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A mesh whose instances carry an RGBA color, alpha in the fourth channel.
|
|
33
|
+
*
|
|
34
|
+
* `BatchedMesh` types this pair for `Color | Vector4` and our vendored classes widen it the same
|
|
35
|
+
* way, so a `Vector4` is what every implementation routes to its own opacity storage.
|
|
36
|
+
*/
|
|
37
|
+
export interface ColorWritable {
|
|
38
|
+
/**
|
|
39
|
+
* Reads an instance's color.
|
|
40
|
+
* @param slot - The instance to read.
|
|
41
|
+
* @param target - Receives the result, alpha in `.w`.
|
|
42
|
+
*/
|
|
43
|
+
getColorAt(slot: number, target: Vector4): unknown;
|
|
44
|
+
/**
|
|
45
|
+
* Writes an instance's color.
|
|
46
|
+
* @param slot - The instance to write.
|
|
47
|
+
* @param color - The color, alpha in `.w`.
|
|
48
|
+
*/
|
|
49
|
+
setColorAt(slot: number, color: Vector4): unknown;
|
|
50
|
+
}
|
|
51
|
+
/** A def carrying a color of its own. */
|
|
52
|
+
interface ColoredDef {
|
|
53
|
+
/** The def's color, as a css string or packed number. */
|
|
54
|
+
color: ColorInput;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Composes the instance transform a shape draws with, into the shared scratch matrix.
|
|
58
|
+
*
|
|
59
|
+
* A box or rect is drawn as the shared unit plane, so its transform carries the whole shape: the
|
|
60
|
+
* plane is scaled to the shape's size, spun about z by its rotation, and moved to its center. A
|
|
61
|
+
* mesh already carries absolute coordinates in its own geometry, including a z per vertex, so it
|
|
62
|
+
* draws at identity. A polygon is the one in between: its vertices are 2D and its `elevation` is a
|
|
63
|
+
* single scalar beside them, so that scalar is the only part of its placement the geometry cannot
|
|
64
|
+
* hold and the transform lifts it. An image's `bounds` is a rect, so it composes here too and then
|
|
65
|
+
* post-multiplies its own origin and tile offsets onto the result.
|
|
66
|
+
*
|
|
67
|
+
* The result **is the shared scratch** — valid until the next call, which is all any caller needs:
|
|
68
|
+
* a write composes, optionally adjusts, and uploads within one synchronous statement. Handing it
|
|
69
|
+
* back rather than taking a target keeps every instance type off its own private matrix.
|
|
70
|
+
* @param shape - The shape supplying the transform.
|
|
71
|
+
* @returns The scratch matrix, composed.
|
|
72
|
+
*/
|
|
73
|
+
export declare function shapeToMatrix(shape: Shape): Matrix4;
|
|
74
|
+
/**
|
|
75
|
+
* Writes a transform into an instance, skipping the upload when nothing moved.
|
|
76
|
+
*
|
|
77
|
+
* The compare-and-skip is what keeps a def that only changed color from paying for a matrix
|
|
78
|
+
* upload and a bounds invalidation: a color-only `update(def)` re-runs the whole sync, and an
|
|
79
|
+
* unchanged transform costs sixteen comparisons instead.
|
|
80
|
+
* @param mesh - The mesh holding the instance.
|
|
81
|
+
* @param slot - The instance's slot.
|
|
82
|
+
* @param matrix - The transform to write.
|
|
83
|
+
* @returns Whether the matrix was written.
|
|
84
|
+
*/
|
|
85
|
+
export declare function writeMatrixIfChanged(mesh: MatrixWritable, slot: number, matrix: Matrix4): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Parses a def's color and writes it into an instance, skipping the upload when it did not change
|
|
88
|
+
* — the color counterpart to {@link writeMatrixIfChanged}, so a def that only moved does not pay
|
|
89
|
+
* for a color upload either.
|
|
90
|
+
*
|
|
91
|
+
* Alpha rides in the fourth channel, which every mesh routes to its own opacity storage.
|
|
92
|
+
*
|
|
93
|
+
* A color that does not parse warns and leaves the instance untouched rather than throwing: a bad
|
|
94
|
+
* color is a bad def, not a broken renderer, and the sync that found it has other writes to finish.
|
|
95
|
+
* Naming the def in the warning is why this takes the whole def rather than the color alone — it is
|
|
96
|
+
* the only thing that makes the report actionable.
|
|
97
|
+
* @param mesh - The mesh holding the instance.
|
|
98
|
+
* @param slot - The instance's slot.
|
|
99
|
+
* @param def - The def supplying the color, and named in the warning.
|
|
100
|
+
* @param logger - Reports an unparsable color, so the warning names the instance component.
|
|
101
|
+
* @returns Whether the color was written; `false` both for one that did not change and for one that
|
|
102
|
+
* did not parse.
|
|
103
|
+
*/
|
|
104
|
+
export declare function writeColorIfChanged(mesh: ColorWritable, slot: number, def: ColoredDef, logger: Logger): boolean;
|
|
105
|
+
export {};
|
|
106
|
+
//# sourceMappingURL=mesh-writes.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isBox, isRect } from '@expofp/geometry';
|
|
2
|
+
import { Color, Matrix4, Quaternion, SRGBColorSpace, Vector3, Vector4 } from 'three';
|
|
3
|
+
import { normalizeColor } from '../../utils/colors';
|
|
4
|
+
/**
|
|
5
|
+
* Composes the instance transform a shape draws with, into the shared scratch matrix.
|
|
6
|
+
*
|
|
7
|
+
* A box or rect is drawn as the shared unit plane, so its transform carries the whole shape: the
|
|
8
|
+
* plane is scaled to the shape's size, spun about z by its rotation, and moved to its center. A
|
|
9
|
+
* mesh already carries absolute coordinates in its own geometry, including a z per vertex, so it
|
|
10
|
+
* draws at identity. A polygon is the one in between: its vertices are 2D and its `elevation` is a
|
|
11
|
+
* single scalar beside them, so that scalar is the only part of its placement the geometry cannot
|
|
12
|
+
* hold and the transform lifts it. An image's `bounds` is a rect, so it composes here too and then
|
|
13
|
+
* post-multiplies its own origin and tile offsets onto the result.
|
|
14
|
+
*
|
|
15
|
+
* The result **is the shared scratch** — valid until the next call, which is all any caller needs:
|
|
16
|
+
* a write composes, optionally adjusts, and uploads within one synchronous statement. Handing it
|
|
17
|
+
* back rather than taking a target keeps every instance type off its own private matrix.
|
|
18
|
+
* @param shape - The shape supplying the transform.
|
|
19
|
+
* @returns The scratch matrix, composed.
|
|
20
|
+
*/
|
|
21
|
+
export function shapeToMatrix(shape) {
|
|
22
|
+
if (!isBox(shape) && !isRect(shape)) {
|
|
23
|
+
// A `Mesh` has no `elevation` at all, so this is identity for one; a `Polygon` is lifted onto
|
|
24
|
+
// its plane. Zero composes to identity either way, so the common case costs nothing.
|
|
25
|
+
return _matrix.makeTranslation(0, 0, 'elevation' in shape ? shape.elevation : 0);
|
|
26
|
+
}
|
|
27
|
+
// A Box is a pure 2D AABB — it carries neither field — so both default to zero.
|
|
28
|
+
const elevation = 'elevation' in shape ? shape.elevation : 0;
|
|
29
|
+
const rotation = 'rotation' in shape ? shape.rotation : 0;
|
|
30
|
+
_position.set(shape.center.x, shape.center.y, elevation);
|
|
31
|
+
_quaternion.setFromAxisAngle(Z_AXIS, rotation);
|
|
32
|
+
_scale.set(shape.size.x, shape.size.y, 1);
|
|
33
|
+
return _matrix.compose(_position, _quaternion, _scale);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Writes a transform into an instance, skipping the upload when nothing moved.
|
|
37
|
+
*
|
|
38
|
+
* The compare-and-skip is what keeps a def that only changed color from paying for a matrix
|
|
39
|
+
* upload and a bounds invalidation: a color-only `update(def)` re-runs the whole sync, and an
|
|
40
|
+
* unchanged transform costs sixteen comparisons instead.
|
|
41
|
+
* @param mesh - The mesh holding the instance.
|
|
42
|
+
* @param slot - The instance's slot.
|
|
43
|
+
* @param matrix - The transform to write.
|
|
44
|
+
* @returns Whether the matrix was written.
|
|
45
|
+
*/
|
|
46
|
+
export function writeMatrixIfChanged(mesh, slot, matrix) {
|
|
47
|
+
mesh.getMatrixAt(slot, _previousMatrix);
|
|
48
|
+
if (_previousMatrix.equals(matrix))
|
|
49
|
+
return false;
|
|
50
|
+
mesh.setMatrixAt(slot, matrix);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parses a def's color and writes it into an instance, skipping the upload when it did not change
|
|
55
|
+
* — the color counterpart to {@link writeMatrixIfChanged}, so a def that only moved does not pay
|
|
56
|
+
* for a color upload either.
|
|
57
|
+
*
|
|
58
|
+
* Alpha rides in the fourth channel, which every mesh routes to its own opacity storage.
|
|
59
|
+
*
|
|
60
|
+
* A color that does not parse warns and leaves the instance untouched rather than throwing: a bad
|
|
61
|
+
* color is a bad def, not a broken renderer, and the sync that found it has other writes to finish.
|
|
62
|
+
* Naming the def in the warning is why this takes the whole def rather than the color alone — it is
|
|
63
|
+
* the only thing that makes the report actionable.
|
|
64
|
+
* @param mesh - The mesh holding the instance.
|
|
65
|
+
* @param slot - The instance's slot.
|
|
66
|
+
* @param def - The def supplying the color, and named in the warning.
|
|
67
|
+
* @param logger - Reports an unparsable color, so the warning names the instance component.
|
|
68
|
+
* @returns Whether the color was written; `false` both for one that did not change and for one that
|
|
69
|
+
* did not parse.
|
|
70
|
+
*/
|
|
71
|
+
export function writeColorIfChanged(mesh, slot, def, logger) {
|
|
72
|
+
const rgb = normalizeColor(def.color);
|
|
73
|
+
if (!rgb) {
|
|
74
|
+
logger.warn(`Invalid color: ${String(def.color)} %O`, def);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
_color.setRGB(rgb.r / 255, rgb.g / 255, rgb.b / 255, SRGBColorSpace);
|
|
78
|
+
_colorWithAlpha.set(_color.r, _color.g, _color.b, rgb.a);
|
|
79
|
+
mesh.getColorAt(slot, _previousColor);
|
|
80
|
+
if (_previousColor.equals(_colorWithAlpha))
|
|
81
|
+
return false;
|
|
82
|
+
mesh.setColorAt(slot, _colorWithAlpha);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
// Scratch shared by every write: each is filled and consumed within a single synchronous
|
|
86
|
+
// statement, so a write costs buffer uploads and no allocation. `_matrix` is the one callers see
|
|
87
|
+
// (through `shapeToMatrix`); the `_previous*` pair is only ever read back and compared inside the
|
|
88
|
+
// write helpers, so a caller still holding `_matrix` never collides with them.
|
|
89
|
+
const _position = new Vector3();
|
|
90
|
+
const _quaternion = new Quaternion();
|
|
91
|
+
const _scale = new Vector3();
|
|
92
|
+
const _matrix = new Matrix4();
|
|
93
|
+
const _previousMatrix = new Matrix4();
|
|
94
|
+
const _color = new Color();
|
|
95
|
+
const _colorWithAlpha = new Vector4();
|
|
96
|
+
const _previousColor = new Vector4();
|
|
97
|
+
const Z_AXIS = new Vector3(0, 0, 1);
|