@expofp/renderer 3.2.5 → 3.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,467 @@
1
+ import { extend } from '@react-three/fiber';
2
+ import { Box3, Sphere, } from 'three';
3
+ import { BatchedText as TroikaBatchedText, Text as TroikaText, } from 'troika-three-text';
4
+ import { InstanceUniforms } from '../InstanceUniforms';
5
+ import { configureDerivedMaterial, patchBaseMaterial, readsMemberMatrices, } from './batched-text-material';
6
+ import { packGlyphs } from './glyph-packing';
7
+ import { MemberTexture } from './MemberTexture';
8
+ /**
9
+ * troika's `BatchedText` with stable member ids, explicit paint order, and per-instance uniforms.
10
+ *
11
+ * Upstream keys everything by pack order — a counter assigned while packing the glyph attributes —
12
+ * which shifts on every add, remove and reorder, so no id survives a repack and nothing outside the
13
+ * class can address a member. This class separates the two things that counter conflated:
14
+ *
15
+ * - **The id** is a stable, batch-allocated index (free list, high-water mark). It is the row a
16
+ * member occupies in the matrix texture and in the uniforms texture, and it is what
17
+ * {@link addText} hands back so a caller can address the member later.
18
+ * - **Paint order** is the order ids are iterated while packing, since draw order is simply the
19
+ * order glyph runs appear in the batched attributes. {@link setOrder} sets it.
20
+ *
21
+ * Those are independent — the id is the *value* written into `aTroikaTextBatchMemberIndex`, the
22
+ * order is *where* it is written — which is what lets a batch have both numbered uniforms and a 2D
23
+ * painter's draw order without an indirect-index texture. The cost is that a pure reorder still
24
+ * repacks (rewriting the glyph attributes), where a `BatchedMesh` would only re-sort its draw list.
25
+ *
26
+ * Other departures from upstream:
27
+ *
28
+ * - **Sync is flag-driven, not scanned.** Members report property writes through
29
+ * `TextExt._batchParent`, so an idle frame costs nothing instead of walking every member.
30
+ * - **Per-member frustum culling and visibility**, by zeroing a hidden member's matrix so its glyphs
31
+ * collapse to a degenerate triangle. See {@link MemberTexture}.
32
+ * - **Bounds are cached**, recomputed only when {@link invalidateBounds} says they moved, where
33
+ * upstream recomposed every member's matrix on every frame. The recompose itself stays in
34
+ * {@link updateBounds}, so a caller cannot forget it; what changed is that every mutation which
35
+ * moves a member now reports back, rather than the flag being set by accident.
36
+ * - **The derived material inherits the base's `transparent`**, rather than troika forcing it true.
37
+ * See {@link configureDerivedMaterial}.
38
+ *
39
+ * **Not built on {@link InstancedMeshExt}'s machinery**, despite the family resemblance. That mesh
40
+ * draws one shared geometry through a per-frame list of instance ids, so it culls and sorts by
41
+ * rewriting the list; here draw order *is* the order glyph runs sit in the attributes, so a reorder
42
+ * costs a repack and culling collapses a matrix row instead. Its members are real objects carrying
43
+ * bounds and a layout of their own rather than indices into one geometry, its ids are allocated
44
+ * per member rather than in bulk by count, and visibility rides on the member's own `visible`. What
45
+ * the two genuinely share is {@link InstanceUniforms}.
46
+ *
47
+ * Members must be {@link TextExt}, and are assumed to agree on `orientation` and `sdfGlyphSize`:
48
+ * both feed uniforms that are batch-wide rather than per-member, so mixing them across a batch
49
+ * leaves whichever member was written last in charge.
50
+ *
51
+ * Render it with {@link BatchedText}.
52
+ */
53
+ export class BatchedTextExt extends TroikaBatchedText {
54
+ /** Per-instance uniform store, shared with {@link BatchedMeshExt} and {@link InstancedMeshExt}. */
55
+ uniforms = new InstanceUniforms();
56
+ /** The per-member transform and style rows the batch's shader draws from. */
57
+ memberTexture = new MemberTexture();
58
+ /** Member ids in paint order; the iteration order for packing, and therefore the draw order. */
59
+ ordered = [];
60
+ /** Id to member, with holes for released ids. Indexed by id, so it needs no map lookup. */
61
+ textById = [];
62
+ /** Released ids, reused before the high-water mark grows. */
63
+ freeIds = [];
64
+ _addedMembersCount = 0;
65
+ boundsNeedsUpdate = false;
66
+ batchNeedsSync = true;
67
+ /**
68
+ * Whether {@link dispose} has run. Syncing a member is asynchronous — troika fetches and parses
69
+ * the font — so a batch can be torn down with a repack still pending against it.
70
+ */
71
+ disposed = false;
72
+ constructor() {
73
+ super();
74
+ this.addEventListener('synccomplete', () => {
75
+ this.boundsNeedsUpdate = true;
76
+ });
77
+ }
78
+ /** Number of members in the batch. */
79
+ get size() {
80
+ return this._members.size;
81
+ }
82
+ /**
83
+ * One past the highest id ever handed out — the row count both textures are sized to, and the
84
+ * bound every per-member sweep iterates to. Larger than {@link size} whenever a removal left a
85
+ * hole.
86
+ *
87
+ * **A pure high-water mark: removing a member never pulls it back in**, unlike `Instances`, the
88
+ * equivalent for {@link InstancedMeshExt}, which shrinks past trailing dead ids. Converting either
89
+ * to the other buys nothing. A dead row costs the sweeps here one array read and a `continue`; and
90
+ * this same number also sizes both textures, where `Instances` has a separate `capacity` for that
91
+ * — so shrinking it would reallocate the matrix and uniform textures every time the last member
92
+ * left, to save those reads.
93
+ */
94
+ get addedMembersCount() {
95
+ return this._addedMembersCount;
96
+ }
97
+ /** The material whose `onBeforeCompile` is already wrapped, so it is wrapped exactly once. */
98
+ patchedMaterial = null;
99
+ /** Marks the batch as needing a sync pass. Members call this through `TextExt._batchParent`. */
100
+ requestSync() {
101
+ this.batchNeedsSync = true;
102
+ }
103
+ /**
104
+ * Marks the batch's bounds as stale. Members call this through `TextExt._batchParent` whenever
105
+ * their transform changes, so no caller has to remember to.
106
+ */
107
+ invalidateBounds() {
108
+ this.boundsNeedsUpdate = true;
109
+ }
110
+ /**
111
+ * The member holding an id.
112
+ * @param memberId - The member to read.
113
+ * @returns The member, or `undefined` when the id holds none.
114
+ */
115
+ getText(memberId) {
116
+ return this.textById[memberId];
117
+ }
118
+ /**
119
+ * Shows or hides a member. A hidden member keeps its id and its glyphs; it is collapsed at draw
120
+ * time rather than repacked out, so hiding costs no repack.
121
+ *
122
+ * Invalidates the bounds, because they enclose only the visible members — without this, hiding
123
+ * one would leave the batch's sphere too large and showing one would leave it too small, either
124
+ * way mis-culling the whole batch.
125
+ * @param memberId - The member to show or hide.
126
+ * @param visible - Whether it draws.
127
+ */
128
+ setVisibleAt(memberId, visible) {
129
+ const text = this.textById[memberId];
130
+ if (!text || text.visible === visible)
131
+ return;
132
+ text.visible = visible;
133
+ this.invalidateBounds();
134
+ }
135
+ /**
136
+ * Sets the order members draw in — the order their glyph runs are packed into the batched
137
+ * attributes, which is the only thing that resolves overlap between texts: 2D content never writes
138
+ * depth, so depth testing has nothing of its own to compare against.
139
+ *
140
+ * Resolved to ids as it is copied, rather than retained as members: the caller's array is
141
+ * usually its own live membership list, and the repack wants the row each run belongs to anyway.
142
+ * @param texts - Every member, in paint order. Members missing from it are not drawn, and anything
143
+ * that is not a member is dropped.
144
+ */
145
+ setOrder(texts) {
146
+ this.ordered = [];
147
+ for (const text of texts) {
148
+ const memberId = this._members.get(text)?.index;
149
+ if (memberId !== undefined)
150
+ this.ordered.push(memberId);
151
+ }
152
+ this._needsRepack = true;
153
+ this.batchNeedsSync = true;
154
+ this.boundsNeedsUpdate = true;
155
+ }
156
+ /**
157
+ * Declares per-instance uniforms, merging with any already declared and rebuilding the backing
158
+ * texture (existing values carried across). See {@link InstanceUniforms.add}.
159
+ * @param schema - The uniforms to add.
160
+ */
161
+ addUniforms(schema) {
162
+ this.uniforms.add(schema, Math.max(1, this._addedMembersCount));
163
+ // The generated GLSL changed shape, so whatever was compiled against the old layout is stale.
164
+ this.materialsNeedUpdate();
165
+ }
166
+ /**
167
+ * Reads one member's uniform.
168
+ * @param memberId - The member to address.
169
+ * @param name - The uniform's name.
170
+ * @param target - Receives the result; unused for a `float`, which is returned directly.
171
+ * @returns The value.
172
+ */
173
+ getUniformAt(memberId, name, target) {
174
+ return this.uniforms.get(memberId, name, target);
175
+ }
176
+ /**
177
+ * Writes one member's uniform and marks its row for upload.
178
+ * @param memberId - The member to address.
179
+ * @param name - The uniform's name.
180
+ * @param value - The value to write.
181
+ */
182
+ setUniformAt(memberId, name, value) {
183
+ this.uniforms.set(memberId, name, value);
184
+ }
185
+ /**
186
+ * Adds a member, allocating it an id.
187
+ * @param text - The text to batch.
188
+ * @returns The member’s id — stable for as long as it stays in the batch, and the index every
189
+ * other method here takes.
190
+ */
191
+ addText(text) {
192
+ const existing = this._members.get(text);
193
+ if (existing)
194
+ return existing.index;
195
+ const memberId = this.freeIds.pop() ?? this._addedMembersCount++;
196
+ super.addText(text);
197
+ // Upstream leaves this at -1 until a repack assigns pack order; here it is the stable id, set
198
+ // once, and the repack only reads it.
199
+ this._members.get(text).index = memberId;
200
+ text._batchParent = this;
201
+ this.textById[memberId] = text;
202
+ this.ordered.push(memberId);
203
+ // Both textures are sized by the high-water mark; the matrix texture resizes itself inside
204
+ // `MemberTexture`, the uniforms texture has to be told.
205
+ this.uniforms.resize(this._addedMembersCount);
206
+ this.batchNeedsSync = true;
207
+ this.boundsNeedsUpdate = true;
208
+ return memberId;
209
+ }
210
+ removeText(text) {
211
+ const info = this._members.get(text);
212
+ // Detaches the `synccomplete` listener, deletes the member, and sets `_needsRepack`.
213
+ super.removeText(text);
214
+ if (!info)
215
+ return;
216
+ this.textById[info.index] = undefined;
217
+ this.freeIds.push(info.index);
218
+ if (text._batchParent === this)
219
+ text._batchParent = undefined;
220
+ const position = this.ordered.indexOf(info.index);
221
+ if (position >= 0)
222
+ this.ordered.splice(position, 1);
223
+ this.batchNeedsSync = true;
224
+ this.boundsNeedsUpdate = true;
225
+ }
226
+ /**
227
+ * Copies the text-level properties, and deliberately not the membership.
228
+ *
229
+ * Upstream hands the *source's own* members to the copy. Members are owned rather than shared —
230
+ * {@link addText} points `_batchParent` at the batch they join — so that step silently cuts the
231
+ * source off from its own members: nothing they report afterwards reaches the batch still drawing
232
+ * them. Cloning the members instead would be correct but costs a font fetch and a layout per
233
+ * member, so a copy starts empty and a caller that wants members adds its own.
234
+ *
235
+ * Not hypothetical: three.js devtools clones the object it highlights.
236
+ * @param source - The batch to copy from.
237
+ * @returns This batch.
238
+ */
239
+ copy(source) {
240
+ // `Text`'s copy, reached past `BatchedText`'s — the membership hand-off is the only thing the
241
+ // override adds, and there is no flag to skip just that.
242
+ TroikaText.prototype.copy.call(this, source);
243
+ return this;
244
+ }
245
+ dispose() {
246
+ // Set before anything else: a sync started earlier may still be waiting on a font, and its
247
+ // repack must not run against the geometry this is about to release.
248
+ this.disposed = true;
249
+ // Disposes the geometry. The matrix textures are ours, not the empty record troika looks in.
250
+ super.dispose();
251
+ // Upstream leaves every member holding a listener on the disposed batch, and every member of
252
+ // ours holding a `_batchParent` back-reference; both keep the batch alive.
253
+ for (const text of this.textById) {
254
+ if (!text)
255
+ continue;
256
+ super.removeText(text);
257
+ if (text._batchParent === this)
258
+ text._batchParent = undefined;
259
+ }
260
+ this.memberTexture.dispose();
261
+ this.uniforms.dispose();
262
+ this.ordered.length = 0;
263
+ this.textById.length = 0;
264
+ this.freeIds.length = 0;
265
+ this._addedMembersCount = 0;
266
+ this.dispatchEvent(disposeEvent);
267
+ }
268
+ /**
269
+ * Refreshes every member's local matrix and recomputes the batch's bounds from them.
270
+ *
271
+ * Inherited `updateMatrixWorld` calls this once per frame, so the flag is what keeps it cheap:
272
+ * upstream recomposed every member's matrix on every frame whether anything moved or not.
273
+ *
274
+ * Members are not in the scene graph, so this pass is the only thing that maintains their
275
+ * matrices — which is why the recompose sits ahead of the visibility check rather than inside it.
276
+ * A member hidden now may be shown later in the same frame, after this pass has run, and the
277
+ * per-draw texture fill would otherwise draw it at wherever it last stood.
278
+ */
279
+ updateBounds() {
280
+ if (!this.boundsNeedsUpdate)
281
+ return;
282
+ const bbox = this.geometry.boundingBox.makeEmpty();
283
+ for (const memberId of this.ordered) {
284
+ const text = this.textById[memberId];
285
+ if (!text)
286
+ continue;
287
+ if (text.matrixAutoUpdate)
288
+ text.updateMatrix(); // ignore world matrix
289
+ if (!text.visible)
290
+ continue;
291
+ _box.copy(text.geometry.boundingBox).applyMatrix4(text.matrix);
292
+ bbox.union(_box);
293
+ }
294
+ bbox.getBoundingSphere(this.geometry.boundingSphere);
295
+ // Cleared last, not first: `updateMatrix` above reports back through `invalidateBounds`, so
296
+ // clearing up front would let this pass re-dirty itself and recompute forever.
297
+ this.boundsNeedsUpdate = false;
298
+ }
299
+ raycast(raycaster, intersects) {
300
+ // Inherited, `Text.raycast` tests a *single* block's bounds — and for a batch the block it reads
301
+ // is whichever member synced last, at the batch's own transform, so upstream reports one phantom
302
+ // rectangle and never names a member. Here each member is tested against its own bounds instead,
303
+ // and the hits carry the member’s id so a caller can trace one back to what it drew.
304
+ //
305
+ // Picking is block-shaped, not glyph-shaped: a hit anywhere in a line's box counts, gaps between
306
+ // letters included. That matches how a label reads as one target.
307
+ this.updateBounds();
308
+ // Whole-batch early-out over the visible members' bounds, so a layer nowhere near the pointer
309
+ // costs one sphere test. Skipped while the bounds are degenerate — before the first sync, or
310
+ // with every member hidden — where an empty box yields no usable sphere.
311
+ const bounds = this.geometry.boundingSphere;
312
+ if (bounds && Number.isFinite(bounds.radius) && bounds.radius > 0) {
313
+ _sphere.copy(bounds).applyMatrix4(this.matrixWorld);
314
+ if (!raycaster.ray.intersectsSphere(_sphere))
315
+ return;
316
+ }
317
+ for (let memberId = 0; memberId < this._addedMembersCount; memberId++) {
318
+ const text = this.textById[memberId];
319
+ // An unsynced member has no bounds to test: troika lays text out asynchronously, and until it
320
+ // finishes the member draws nothing either.
321
+ if (!text?.visible || !text.textRenderInfo)
322
+ continue;
323
+ // Members are not in the scene graph, so nothing maintains their world matrices — `updateBounds`
324
+ // keeps only the local ones. Composing it here is what lets troika's own block test run
325
+ // unchanged, and it is the same matrix the shader places the member with.
326
+ if (text.matrixAutoUpdate)
327
+ text.updateMatrix();
328
+ text.matrixWorld.multiplyMatrices(this.matrixWorld, text.matrix);
329
+ const before = intersects.length;
330
+ text.raycast(raycaster, intersects);
331
+ // The member named itself as the hit object; relabel the tail it just appended so hits point at
332
+ // the batch and name the member, which is the only identifier a caller can resolve.
333
+ for (let hit = before; hit < intersects.length; hit++) {
334
+ intersects[hit].object = this;
335
+ intersects[hit].instanceId = memberId;
336
+ }
337
+ }
338
+ }
339
+ onBeforeRender(renderer, scene, camera, geometry, material) {
340
+ if (this.batchNeedsSync)
341
+ this.sync();
342
+ // Not always the batch's own material: `scene.overrideMaterial` substitutes one wholesale, and
343
+ // a devtool inspecting the scene may hand over a copy of ours. Upstream tests
344
+ // `isTroikaTextMaterial`, which is too loose — see {@link readsMemberMatrices}.
345
+ if (readsMemberMatrices(material))
346
+ this.memberTexture.prepareForRender(this, material, camera);
347
+ this.uniforms.texture?.update(renderer);
348
+ }
349
+ /**
350
+ * Syncs members and repacks the batched geometry if needed.
351
+ *
352
+ * Unlike upstream, `callback` is invoked on every path — including the ones that find nothing to
353
+ * do — so `new Promise(resolve => batch.sync(resolve))` always settles.
354
+ * @param callback - Invoked once the batch is in sync.
355
+ */
356
+ sync(callback) {
357
+ if (this.disposed || !this.batchNeedsSync) {
358
+ callback?.();
359
+ return;
360
+ }
361
+ this.batchNeedsSync = false;
362
+ let needsRepack = this._needsRepack;
363
+ this._needsRepack = false;
364
+ const pendingSyncs = [];
365
+ this._members.forEach((packingInfo, text) => {
366
+ const needsMemberSync = text._needsSync;
367
+ if (packingInfo.dirty || needsMemberSync) {
368
+ packingInfo.dirty = false;
369
+ needsRepack = true;
370
+ if (needsMemberSync)
371
+ pendingSyncs.push(new Promise((resolve) => text.sync(resolve)));
372
+ }
373
+ });
374
+ if (!needsRepack) {
375
+ callback?.();
376
+ return;
377
+ }
378
+ this.dispatchEvent(syncStartEvent);
379
+ const repack = () => {
380
+ // Disposed while a member was still loading its font: the geometry this would write into is
381
+ // already released, and nothing is left to draw it. The callback still runs, so a caller
382
+ // awaiting the sync is not stranded by a teardown.
383
+ if (!this.disposed) {
384
+ this.repackBatchedGeometry();
385
+ this.dispatchEvent(syncCompleteEvent);
386
+ }
387
+ callback?.();
388
+ };
389
+ if (pendingSyncs.length) {
390
+ void Promise.all(pendingSyncs).then(repack);
391
+ }
392
+ else {
393
+ repack();
394
+ }
395
+ }
396
+ /** The derived material, or `[outline, main]` once a member configures an outline. */
397
+ get material() {
398
+ return super.material;
399
+ }
400
+ /**
401
+ * Sets the *base* material to derive from, tolerating the pair the getter answers with.
402
+ *
403
+ * The accessor is asymmetric upstream — the getter returns `[outline, main]` once an outline is
404
+ * configured, the setter only understands a single base — so `object.material =
405
+ * f(object.material)` stores an array where a material belongs, and the next read walks off it
406
+ * and takes the render loop down. Keeping the main pass makes the round trip a no-op, which is
407
+ * what a caller writing it meant; the outline is derived from the main one anyway.
408
+ *
409
+ * three.js devtools recolors a highlighted object exactly that way, and `Mesh.copy` does it too,
410
+ * so both a highlight and a {@link copy} of stroked text reach here with a pair.
411
+ */
412
+ set material(value) {
413
+ super.material = Array.isArray(value) ? value[value.length - 1] : value;
414
+ }
415
+ /**
416
+ * Derives the batch's material: this batch's uniforms onto the base, then troika's derivation,
417
+ * then the corrections that make the result drawable as a batch.
418
+ * @param baseMaterial - The material to derive from.
419
+ * @returns The derived material.
420
+ */
421
+ createDerivedMaterial(baseMaterial) {
422
+ // Once per material — a second wrap would nest, declaring the prologue twice.
423
+ if (baseMaterial !== this.patchedMaterial) {
424
+ this.patchedMaterial = baseMaterial;
425
+ patchBaseMaterial(baseMaterial, this.uniforms);
426
+ }
427
+ const derived = super.createDerivedMaterial(baseMaterial);
428
+ return configureDerivedMaterial(derived, baseMaterial, this.uniforms);
429
+ }
430
+ /** Rewrites the batched glyph attributes from the members' render info, and re-measures. */
431
+ repackBatchedGeometry() {
432
+ const renderInfo = packGlyphs(this.geometry, this.ordered, (memberId) => this.textById[memberId]);
433
+ // The inherited `Text` code reads `this.textRenderInfo` for whole-batch state (the SDF atlas,
434
+ // its glyph size and exponent), which the pass hands back from whichever member it last saw.
435
+ if (renderInfo) {
436
+ this._textRenderInfo = renderInfo;
437
+ }
438
+ this.boundsNeedsUpdate = true;
439
+ this.updateBounds();
440
+ }
441
+ /**
442
+ * Flags the material(s) for recompilation, e.g. after the uniform schema changed.
443
+ *
444
+ * Handles the pair `material` returns once an outline is configured, not just the single derived
445
+ * material — missing the outline layer would leave it compiled against the previous uniform
446
+ * layout.
447
+ */
448
+ materialsNeedUpdate() {
449
+ const material = this.material;
450
+ for (const m of Array.isArray(material) ? material : [material]) {
451
+ m.needsUpdate = true;
452
+ }
453
+ }
454
+ }
455
+ /**
456
+ * Registers the class as an R3F element, by value so the generated key cannot collide with anything
457
+ * three puts in the catalog when the canvas mounts.
458
+ */
459
+ export const BatchedText = extend(BatchedTextExt);
460
+ // Reused rather than built per dispatch: three's `dispatchEvent` stamps `target` on the object it
461
+ // is handed, so these are one allocation for the batch's lifetime instead of one per event.
462
+ const syncStartEvent = { type: 'syncstart' };
463
+ const syncCompleteEvent = { type: 'synccomplete' };
464
+ const disposeEvent = { type: 'dispose' };
465
+ // Scratch reused across calls, each filled and consumed within a single synchronous block.
466
+ const _sphere = new Sphere();
467
+ const _box = new Box3();
@@ -0,0 +1,90 @@
1
+ import { type Camera, type ColorRepresentation, Matrix4 } from 'three';
2
+ import { type BatchTextMaterial } from 'troika-three-text';
3
+ import { type TextExt } from './Text';
4
+ /** What {@link MemberTexture} reads off the batch it fills for. */
5
+ export interface MemberTextureHost {
6
+ /**
7
+ * The batch's world transform. Member matrices are local to *it*, so the frustum has to be built
8
+ * against this — upstream composed with `scene.matrixWorld`, which agrees only while the batch
9
+ * sits at the scene root with an identity transform, and every layer is a nested `<group>`.
10
+ */
11
+ matrixWorld: Matrix4;
12
+ /** One past the highest id ever handed out — the rows the texture is sized to, and swept. */
13
+ addedMembersCount: number;
14
+ /** The batch's own color, which a member with none of its own draws in. */
15
+ color: ColorRepresentation | undefined;
16
+ /**
17
+ * The member holding an id.
18
+ * @param memberId - The member to read.
19
+ * @returns The member, or `undefined` when the id holds none.
20
+ */
21
+ getText(memberId: number): TextExt | undefined;
22
+ }
23
+ /**
24
+ * The data texture a batch's shader reads each member's transform and style from: one 32-float row
25
+ * per member, refilled once per draw with frustum culling folded in.
26
+ *
27
+ * Troika's `BatchedText.prepareForRender`, lifted out of the class and given ownership of the
28
+ * textures it fills — a pair, since the outline pass writes different fields into the same layout.
29
+ * Rows are addressed by id, so the texture is sized to the batch's high-water mark rather than to
30
+ * its live member count.
31
+ *
32
+ * Departures from upstream:
33
+ *
34
+ * - **The frustum is built against the batch**, not the scene root. See
35
+ * {@link MemberTextureHost.matrixWorld}.
36
+ * - **Hidden and culled members are collapsed, not repacked out** — a zero matrix takes every glyph
37
+ * of the member to a point, so it costs vertex shading but no fragments, and no repack.
38
+ * - **The upload is conditional.** Writes are compared against what the texture already holds, so a
39
+ * frame that changed nothing sends nothing.
40
+ * - **`setMatrixTexture` is inlined**, so a material that lost the method still draws. See
41
+ * {@link publish}.
42
+ */
43
+ export declare class MemberTexture {
44
+ /** One texture per pass, each allocated on the first draw that needs it. */
45
+ private readonly textures;
46
+ /** Whether this pass wrote a value differing from what the texture already held. */
47
+ private needsUpdate;
48
+ /**
49
+ * Refills the texture for one draw and points the material at it.
50
+ * @param host - The batch being drawn.
51
+ * @param material - The text material being drawn, main or outline.
52
+ * @param camera - The camera to cull against.
53
+ */
54
+ prepareForRender(host: MemberTextureHost, material: BatchTextMaterial, camera: Camera): void;
55
+ /** Frees both passes' textures. */
56
+ dispose(): void;
57
+ /**
58
+ * The pass's texture, sized to hold every row, reallocated when the row count outgrows it.
59
+ * @param pass - Which pass is being drawn.
60
+ * @param rowCount - Rows the texture must hold, at least one.
61
+ * @returns The texture.
62
+ */
63
+ private textureFor;
64
+ /**
65
+ * Writes one member's row: its transform, then the style fields the pass being drawn reads.
66
+ *
67
+ * The member populates the material's uniforms itself, since that is where the defaults and the
68
+ * percentage parsing live; this reads the results straight back out of them.
69
+ * @param texData - The texture's backing array.
70
+ * @param start - The row's first float.
71
+ * @param text - The member to write.
72
+ * @param material - The material being drawn, main or outline.
73
+ * @param batchColor - The color a member with none of its own draws in.
74
+ */
75
+ private writeMember;
76
+ /**
77
+ * Zeroes a member's matrix, taking every glyph of it to a point.
78
+ * @param texData - The texture's backing array.
79
+ * @param start - The row's first float.
80
+ */
81
+ private collapse;
82
+ /**
83
+ * Writes one float, noting whether it changed.
84
+ * @param texData - The texture's backing array.
85
+ * @param index - The float's index.
86
+ * @param value - The value to write.
87
+ */
88
+ private setTexData;
89
+ }
90
+ //# sourceMappingURL=MemberTexture.d.ts.map