@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.
Files changed (228) hide show
  1. package/README.md +111 -0
  2. package/dist/Renderer.d.ts +88 -0
  3. package/dist/Renderer.js +53 -0
  4. package/dist/controls/CameraControls.d.ts +102 -0
  5. package/dist/controls/CameraControls.js +239 -0
  6. package/dist/controls/camera-api.d.ts +206 -0
  7. package/dist/controls/camera-api.js +181 -0
  8. package/dist/controls/camera-move.d.ts +13 -0
  9. package/dist/controls/camera-move.js +18 -0
  10. package/dist/controls/camera-state.d.ts +40 -0
  11. package/dist/controls/camera-state.js +65 -0
  12. package/dist/controls/gestures.d.ts +70 -0
  13. package/dist/controls/gestures.js +84 -0
  14. package/dist/controls/handlers/Inertia.d.ts +111 -0
  15. package/dist/controls/handlers/Inertia.js +215 -0
  16. package/dist/controls/handlers/Pan.d.ts +23 -0
  17. package/dist/controls/handlers/Pan.js +32 -0
  18. package/dist/controls/handlers/Pitch.d.ts +94 -0
  19. package/dist/controls/handlers/Pitch.js +191 -0
  20. package/dist/controls/handlers/Roll.d.ts +103 -0
  21. package/dist/controls/handlers/Roll.js +182 -0
  22. package/dist/controls/handlers/Zoom.d.ts +15 -0
  23. package/dist/controls/handlers/Zoom.js +26 -0
  24. package/dist/controls/index.d.ts +56 -0
  25. package/dist/controls/index.js +58 -0
  26. package/dist/controls/zoom-to.d.ts +89 -0
  27. package/dist/controls/zoom-to.js +91 -0
  28. package/dist/core/InstanceUniforms.d.ts +93 -0
  29. package/dist/core/InstanceUniforms.js +266 -0
  30. package/dist/core/SquareDataTexture.d.ts +277 -0
  31. package/dist/core/SquareDataTexture.js +476 -0
  32. package/dist/core/batch/BatchedMesh.d.ts +74 -0
  33. package/dist/core/batch/BatchedMesh.js +149 -0
  34. package/dist/core/instanced/FrustumCulling.d.ts +141 -0
  35. package/dist/core/instanced/FrustumCulling.js +246 -0
  36. package/dist/core/instanced/InstancedMesh.d.ts +302 -0
  37. package/dist/core/instanced/InstancedMesh.js +497 -0
  38. package/dist/core/instanced/Instances.d.ts +119 -0
  39. package/dist/core/instanced/Instances.js +230 -0
  40. package/dist/core/instanced/Raycasting.d.ts +118 -0
  41. package/dist/core/instanced/Raycasting.js +134 -0
  42. package/dist/core/instanced/instanced-material.d.ts +77 -0
  43. package/dist/core/instanced/instanced-material.js +224 -0
  44. package/dist/core/lines/BatchedLines.d.ts +135 -0
  45. package/dist/core/lines/BatchedLines.js +213 -0
  46. package/dist/core/lines/FrustumCulling.d.ts +43 -0
  47. package/dist/core/lines/FrustumCulling.js +85 -0
  48. package/dist/core/lines/Raycasting.d.ts +44 -0
  49. package/dist/core/lines/Raycasting.js +91 -0
  50. package/dist/core/lines/lines-material.d.ts +47 -0
  51. package/dist/core/lines/lines-material.js +115 -0
  52. package/dist/core/shader-source.d.ts +50 -0
  53. package/dist/core/shader-source.js +84 -0
  54. package/dist/core/text/BatchedText.d.ts +227 -0
  55. package/dist/core/text/BatchedText.js +467 -0
  56. package/dist/core/text/MemberTexture.d.ts +90 -0
  57. package/dist/core/text/MemberTexture.js +194 -0
  58. package/dist/core/text/Text.d.ts +73 -0
  59. package/dist/core/text/Text.js +215 -0
  60. package/dist/core/text/batched-text-material.d.ts +56 -0
  61. package/dist/core/text/batched-text-material.js +121 -0
  62. package/dist/core/text/glyph-packing.d.ts +17 -0
  63. package/dist/core/text/glyph-packing.js +61 -0
  64. package/dist/core/typed-array.d.ts +16 -0
  65. package/dist/core/typed-array.js +20 -0
  66. package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
  67. package/dist/debug/components/DebugPanelProvider.js +38 -0
  68. package/dist/debug/components/DebugTools.d.ts +22 -0
  69. package/dist/debug/components/DebugTools.js +28 -0
  70. package/dist/debug/components/Gui.d.ts +20 -0
  71. package/dist/debug/components/Gui.js +32 -0
  72. package/dist/debug/components/MemoryInfo.d.ts +22 -0
  73. package/dist/debug/components/MemoryInfo.js +42 -0
  74. package/dist/debug/components/Panel.d.ts +31 -0
  75. package/dist/debug/components/Panel.js +98 -0
  76. package/dist/debug/components/Stats.d.ts +9 -0
  77. package/dist/debug/components/Stats.js +24 -0
  78. package/dist/debug/debug-context.d.ts +41 -0
  79. package/dist/debug/debug-context.js +59 -0
  80. package/dist/debug/index.d.ts +51 -0
  81. package/dist/debug/index.js +64 -0
  82. package/dist/events/LayoutDriver.d.ts +31 -0
  83. package/dist/events/LayoutDriver.js +88 -0
  84. package/dist/events/PointerMissed.d.ts +23 -0
  85. package/dist/events/PointerMissed.js +58 -0
  86. package/dist/events/def-culling.d.ts +46 -0
  87. package/dist/events/def-culling.js +69 -0
  88. package/dist/events/layout-targets.d.ts +104 -0
  89. package/dist/events/layout-targets.js +245 -0
  90. package/dist/events/pointer.d.ts +118 -0
  91. package/dist/events/pointer.js +168 -0
  92. package/dist/external/ExternalCanvas.d.ts +56 -0
  93. package/dist/external/ExternalCanvas.js +178 -0
  94. package/dist/external/external-camera.d.ts +17 -0
  95. package/dist/external/external-camera.js +82 -0
  96. package/dist/external/external-events.d.ts +14 -0
  97. package/dist/external/external-events.js +33 -0
  98. package/dist/index.d.ts +11 -783
  99. package/dist/index.js +12 -8031
  100. package/dist/objects/Background.d.ts +31 -0
  101. package/dist/objects/Background.js +37 -0
  102. package/dist/objects/Lighting.d.ts +20 -0
  103. package/dist/objects/Lighting.js +85 -0
  104. package/dist/objects/containers/SlotContainer.d.ts +266 -0
  105. package/dist/objects/containers/SlotContainer.js +251 -0
  106. package/dist/objects/containers/hooks.d.ts +46 -0
  107. package/dist/objects/containers/hooks.js +118 -0
  108. package/dist/objects/containers/index.d.ts +6 -0
  109. package/dist/objects/containers/index.js +4 -0
  110. package/dist/objects/containers/keys.d.ts +10 -0
  111. package/dist/objects/containers/keys.js +18 -0
  112. package/dist/objects/containers/mesh-writes.d.ts +106 -0
  113. package/dist/objects/containers/mesh-writes.js +97 -0
  114. package/dist/objects/containers/props.d.ts +23 -0
  115. package/dist/objects/containers/props.js +1 -0
  116. package/dist/objects/images/ImageInstance.d.ts +29 -0
  117. package/dist/objects/images/ImageInstance.js +83 -0
  118. package/dist/objects/images/Images.d.ts +37 -0
  119. package/dist/objects/images/Images.js +204 -0
  120. package/dist/objects/images/atlas-packer.d.ts +128 -0
  121. package/dist/objects/images/atlas-packer.js +315 -0
  122. package/dist/objects/images/image-container.d.ts +51 -0
  123. package/dist/objects/images/image-container.js +70 -0
  124. package/dist/objects/images/index.d.ts +2 -0
  125. package/dist/objects/images/index.js +1 -0
  126. package/dist/objects/images/texture-budget.d.ts +120 -0
  127. package/dist/objects/images/texture-budget.js +215 -0
  128. package/dist/objects/layer/Layer.d.ts +27 -0
  129. package/dist/objects/layer/Layer.js +114 -0
  130. package/dist/objects/layer/index.d.ts +2 -0
  131. package/dist/objects/layer/index.js +1 -0
  132. package/dist/objects/layer/mount-scheduler.d.ts +102 -0
  133. package/dist/objects/layer/mount-scheduler.js +145 -0
  134. package/dist/objects/layer/visibility-gui.d.ts +19 -0
  135. package/dist/objects/layer/visibility-gui.js +93 -0
  136. package/dist/objects/lines/LineInstance.d.ts +21 -0
  137. package/dist/objects/lines/LineInstance.js +73 -0
  138. package/dist/objects/lines/Lines.d.ts +17 -0
  139. package/dist/objects/lines/Lines.js +29 -0
  140. package/dist/objects/lines/index.d.ts +2 -0
  141. package/dist/objects/lines/index.js +1 -0
  142. package/dist/objects/lines/line-container.d.ts +35 -0
  143. package/dist/objects/lines/line-container.js +40 -0
  144. package/dist/objects/materials/ColorMaterial.d.ts +85 -0
  145. package/dist/objects/materials/ColorMaterial.js +89 -0
  146. package/dist/objects/materials/LineMaterial.d.ts +44 -0
  147. package/dist/objects/materials/LineMaterial.js +72 -0
  148. package/dist/objects/materials/ModelMaterial.d.ts +41 -0
  149. package/dist/objects/materials/ModelMaterial.js +84 -0
  150. package/dist/objects/materials/TextureMaterial.d.ts +30 -0
  151. package/dist/objects/materials/TextureMaterial.js +29 -0
  152. package/dist/objects/materials/dim.d.ts +123 -0
  153. package/dist/objects/materials/dim.js +213 -0
  154. package/dist/objects/materials/shared.d.ts +6 -0
  155. package/dist/objects/materials/shared.js +51 -0
  156. package/dist/objects/models/ModelInstance.d.ts +22 -0
  157. package/dist/objects/models/ModelInstance.js +33 -0
  158. package/dist/objects/models/Models.d.ts +22 -0
  159. package/dist/objects/models/Models.js +177 -0
  160. package/dist/objects/models/gltf-cache.d.ts +17 -0
  161. package/dist/objects/models/gltf-cache.js +50 -0
  162. package/dist/objects/models/index.d.ts +2 -0
  163. package/dist/objects/models/index.js +1 -0
  164. package/dist/objects/models/model-asset.d.ts +48 -0
  165. package/dist/objects/models/model-asset.js +77 -0
  166. package/dist/objects/models/model-container.d.ts +139 -0
  167. package/dist/objects/models/model-container.js +227 -0
  168. package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
  169. package/dist/objects/shapes/ShapeInstance.js +40 -0
  170. package/dist/objects/shapes/Shapes.d.ts +21 -0
  171. package/dist/objects/shapes/Shapes.js +120 -0
  172. package/dist/objects/shapes/index.d.ts +2 -0
  173. package/dist/objects/shapes/index.js +1 -0
  174. package/dist/objects/shapes/shape-container.d.ts +165 -0
  175. package/dist/objects/shapes/shape-container.js +272 -0
  176. package/dist/objects/shapes/shape-geometry.d.ts +27 -0
  177. package/dist/objects/shapes/shape-geometry.js +94 -0
  178. package/dist/objects/text/TextInstance.d.ts +22 -0
  179. package/dist/objects/text/TextInstance.js +95 -0
  180. package/dist/objects/text/Texts.d.ts +30 -0
  181. package/dist/objects/text/Texts.js +41 -0
  182. package/dist/objects/text/index.d.ts +2 -0
  183. package/dist/objects/text/index.js +1 -0
  184. package/dist/objects/text/text-container.d.ts +105 -0
  185. package/dist/objects/text/text-container.js +206 -0
  186. package/dist/objects/text/text-layout.d.ts +83 -0
  187. package/dist/objects/text/text-layout.js +154 -0
  188. package/dist/space/Camera.d.ts +103 -0
  189. package/dist/space/Camera.js +321 -0
  190. package/dist/space/Scene.d.ts +44 -0
  191. package/dist/space/Scene.js +112 -0
  192. package/dist/space/Space.d.ts +288 -0
  193. package/dist/space/Space.js +329 -0
  194. package/dist/space/VisibleRect.d.ts +72 -0
  195. package/dist/space/VisibleRect.js +129 -0
  196. package/dist/space/camera-handle.d.ts +67 -0
  197. package/dist/space/camera-handle.js +63 -0
  198. package/dist/space/camera-kind.d.ts +26 -0
  199. package/dist/space/camera-kind.js +30 -0
  200. package/dist/space/canvas-size.d.ts +24 -0
  201. package/dist/space/canvas-size.js +20 -0
  202. package/dist/space/clipping.d.ts +55 -0
  203. package/dist/space/clipping.js +108 -0
  204. package/dist/space/layout-measure.d.ts +65 -0
  205. package/dist/space/layout-measure.js +173 -0
  206. package/dist/space/viewport.d.ts +112 -0
  207. package/dist/space/viewport.js +67 -0
  208. package/dist/types/defs.d.ts +169 -0
  209. package/dist/types/defs.js +1 -0
  210. package/dist/types/events.d.ts +183 -0
  211. package/dist/types/events.js +1 -0
  212. package/dist/types/guards.d.ts +14 -0
  213. package/dist/types/guards.js +42 -0
  214. package/dist/update/index.d.ts +3 -0
  215. package/dist/update/index.js +2 -0
  216. package/dist/update/slot-writers.d.ts +30 -0
  217. package/dist/update/slot-writers.js +72 -0
  218. package/dist/update/store.d.ts +41 -0
  219. package/dist/update/store.js +98 -0
  220. package/dist/utils/arrays.d.ts +13 -0
  221. package/dist/utils/arrays.js +20 -0
  222. package/dist/utils/colors.d.ts +19 -0
  223. package/dist/utils/colors.js +27 -0
  224. package/dist/utils/logging.d.ts +26 -0
  225. package/dist/utils/logging.js +47 -0
  226. package/dist/utils/math.d.ts +24 -0
  227. package/dist/utils/math.js +27 -0
  228. 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,4 @@
1
+ export { useContainer, useDefSync } from './hooks';
2
+ export { defKey } from './keys';
3
+ export { shapeToMatrix, writeColorIfChanged, writeMatrixIfChanged, } from './mesh-writes';
4
+ export { SlotContainer, } from './SlotContainer';
@@ -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);