@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,194 @@
1
+ import { Color, DataTexture, FloatType, Frustum, Matrix4, RGBAFormat, Sphere, } from 'three';
2
+ /**
3
+ * The data texture a batch's shader reads each member's transform and style from: one 32-float row
4
+ * per member, refilled once per draw with frustum culling folded in.
5
+ *
6
+ * Troika's `BatchedText.prepareForRender`, lifted out of the class and given ownership of the
7
+ * textures it fills — a pair, since the outline pass writes different fields into the same layout.
8
+ * Rows are addressed by id, so the texture is sized to the batch's high-water mark rather than to
9
+ * its live member count.
10
+ *
11
+ * Departures from upstream:
12
+ *
13
+ * - **The frustum is built against the batch**, not the scene root. See
14
+ * {@link MemberTextureHost.matrixWorld}.
15
+ * - **Hidden and culled members are collapsed, not repacked out** — a zero matrix takes every glyph
16
+ * of the member to a point, so it costs vertex shading but no fragments, and no repack.
17
+ * - **The upload is conditional.** Writes are compared against what the texture already holds, so a
18
+ * frame that changed nothing sends nothing.
19
+ * - **`setMatrixTexture` is inlined**, so a material that lost the method still draws. See
20
+ * {@link publish}.
21
+ */
22
+ export class MemberTexture {
23
+ /** One texture per pass, each allocated on the first draw that needs it. */
24
+ textures = {};
25
+ /** Whether this pass wrote a value differing from what the texture already held. */
26
+ needsUpdate = false;
27
+ /**
28
+ * Refills the texture for one draw and points the material at it.
29
+ * @param host - The batch being drawn.
30
+ * @param material - The text material being drawn, main or outline.
31
+ * @param camera - The camera to cull against.
32
+ */
33
+ prepareForRender(host, material, camera) {
34
+ const isOutline = material.isTextOutlineMaterial;
35
+ material.uniforms.uTroikaIsOutline.value = isOutline;
36
+ const rowCount = host.addedMembersCount;
37
+ // Floored at one member: `Math.log2(0)` is `-Infinity`, which upstream turns into a zero-width,
38
+ // `NaN`-height texture.
39
+ const texture = this.textureFor(isOutline ? 'outline' : 'main', Math.max(1, rowCount));
40
+ const texData = texture.image.data;
41
+ this.needsUpdate = false;
42
+ _matrix
43
+ .multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
44
+ .multiply(host.matrixWorld);
45
+ _frustum.setFromProjectionMatrix(_matrix, camera.coordinateSystem);
46
+ // Hoisted out of the loop: `Text`'s `material` getter calls `hasOutline()`, which walks every
47
+ // member, so reading it per member made this loop quadratic — and it returns an *array* once
48
+ // outlines are on, whose `.color` is undefined. The material being drawn is already in hand.
49
+ const batchColor = host.color ?? material.color ?? 0xffffff;
50
+ for (let memberId = 0; memberId < rowCount; memberId++) {
51
+ const text = host.getText(memberId);
52
+ // An unsynced member has no layout to write and draws nothing either.
53
+ if (!text?.textRenderInfo)
54
+ continue;
55
+ const start = memberId * FLOATS_PER_MEMBER;
56
+ if (isDrawn(text)) {
57
+ this.writeMember(texData, start, text, material, batchColor);
58
+ }
59
+ else {
60
+ this.collapse(texData, start);
61
+ }
62
+ }
63
+ // Assigning `false` is a no-op in three — the setter only bumps the version for `true` — so this
64
+ // requests an upload when something changed and leaves the texture alone when nothing did.
65
+ texture.needsUpdate = this.needsUpdate;
66
+ publish(material, texture);
67
+ }
68
+ /** Frees both passes' textures. */
69
+ dispose() {
70
+ for (const pass of Object.keys(this.textures)) {
71
+ this.textures[pass]?.dispose();
72
+ delete this.textures[pass];
73
+ }
74
+ }
75
+ /**
76
+ * The pass's texture, sized to hold every row, reallocated when the row count outgrows it.
77
+ * @param pass - Which pass is being drawn.
78
+ * @param rowCount - Rows the texture must hold, at least one.
79
+ * @returns The texture.
80
+ */
81
+ textureFor(pass, rowCount) {
82
+ let texture = this.textures[pass];
83
+ const dataLength = Math.pow(2, Math.ceil(Math.log2(rowCount * FLOATS_PER_MEMBER)));
84
+ if (texture && dataLength === texture.image.data.length)
85
+ return texture;
86
+ texture?.dispose();
87
+ const width = Math.min(dataLength / 4, 1024);
88
+ texture = new DataTexture(new Float32Array(dataLength), width, dataLength / 4 / width, RGBAFormat, FloatType);
89
+ this.textures[pass] = texture;
90
+ return texture;
91
+ }
92
+ /**
93
+ * Writes one member's row: its transform, then the style fields the pass being drawn reads.
94
+ *
95
+ * The member populates the material's uniforms itself, since that is where the defaults and the
96
+ * percentage parsing live; this reads the results straight back out of them.
97
+ * @param texData - The texture's backing array.
98
+ * @param start - The row's first float.
99
+ * @param text - The member to write.
100
+ * @param material - The material being drawn, main or outline.
101
+ * @param batchColor - The color a member with none of its own draws in.
102
+ */
103
+ writeMember(texData, start, text, material, batchColor) {
104
+ const isOutline = material.isTextOutlineMaterial;
105
+ const matrix = text.matrix.elements;
106
+ for (let i = 0; i < 16; i++) {
107
+ this.setTexData(texData, start + i, matrix[i]);
108
+ }
109
+ text._prepareForRender(material);
110
+ const { uTroikaTotalBounds, uTroikaClipRect, uTroikaPositionOffset, uTroikaEdgeOffset, uTroikaBlurRadius, uTroikaStrokeWidth, uTroikaStrokeColor, uTroikaStrokeOpacity, uTroikaFillOpacity, uTroikaCurveRadius, } = material.uniforms;
111
+ // Total bounds for uv
112
+ for (let i = 0; i < 4; i++) {
113
+ this.setTexData(texData, start + 16 + i, uTroikaTotalBounds.value.getComponent(i));
114
+ }
115
+ // Clip rect
116
+ for (let i = 0; i < 4; i++) {
117
+ this.setTexData(texData, start + 20 + i, uTroikaClipRect.value.getComponent(i));
118
+ }
119
+ // Color
120
+ const color = (isOutline ? text.outlineColor || 0 : text.color) ?? batchColor;
121
+ this.setTexData(texData, start + 24, _color.set(color).getHex());
122
+ // Fill opacity / outline opacity
123
+ this.setTexData(texData, start + 25, uTroikaFillOpacity.value);
124
+ // Curve radius
125
+ this.setTexData(texData, start + 26, uTroikaCurveRadius.value);
126
+ if (isOutline) {
127
+ // Outline properties
128
+ this.setTexData(texData, start + 28, uTroikaPositionOffset.value.x);
129
+ this.setTexData(texData, start + 29, uTroikaPositionOffset.value.y);
130
+ this.setTexData(texData, start + 30, uTroikaEdgeOffset.value);
131
+ this.setTexData(texData, start + 31, uTroikaBlurRadius.value);
132
+ }
133
+ else {
134
+ // Stroke properties
135
+ this.setTexData(texData, start + 28, uTroikaStrokeWidth.value);
136
+ this.setTexData(texData, start + 29, _color.set(uTroikaStrokeColor.value).getHex());
137
+ this.setTexData(texData, start + 30, uTroikaStrokeOpacity.value);
138
+ }
139
+ }
140
+ /**
141
+ * Zeroes a member's matrix, taking every glyph of it to a point.
142
+ * @param texData - The texture's backing array.
143
+ * @param start - The row's first float.
144
+ */
145
+ collapse(texData, start) {
146
+ for (let i = 0; i < 16; i++) {
147
+ this.setTexData(texData, start + i, 0);
148
+ }
149
+ }
150
+ /**
151
+ * Writes one float, noting whether it changed.
152
+ * @param texData - The texture's backing array.
153
+ * @param index - The float's index.
154
+ * @param value - The value to write.
155
+ */
156
+ setTexData(texData, index, value) {
157
+ if (value !== texData[index]) {
158
+ texData[index] = value;
159
+ this.needsUpdate = true;
160
+ }
161
+ }
162
+ }
163
+ /**
164
+ * Whether a member draws this frame: shown, and inside the frustum this pass was set up with.
165
+ * @param text - The member to test.
166
+ * @returns Whether it draws.
167
+ */
168
+ function isDrawn(text) {
169
+ if (!text.visible)
170
+ return false;
171
+ return _frustum.intersectsSphere(_sphere.copy(text.geometry.boundingSphere).applyMatrix4(text.matrix));
172
+ }
173
+ /**
174
+ * Points a material at the texture it should read members from, sizing it in the same write.
175
+ *
176
+ * Troika's `setMatrixTexture`, inlined — this is the whole of it. Calling the method instead would
177
+ * throw on any material that lost it, which is every copy built by calling `material.constructor`;
178
+ * the uniforms it writes are inherited by all of them, so writing them directly leaves such a copy
179
+ * drawing the batch where the batch actually is.
180
+ * @param material - The material being drawn.
181
+ * @param texture - The texture it should read.
182
+ */
183
+ function publish(material, texture) {
184
+ const { uTroikaMatricesTexture, uTroikaMatricesTextureSize } = material.uniforms;
185
+ uTroikaMatricesTexture.value = texture;
186
+ uTroikaMatricesTextureSize.value.set(texture.image.width, texture.image.height);
187
+ }
188
+ /** Floats each member occupies; troika's packing, unchanged. */
189
+ const FLOATS_PER_MEMBER = 32;
190
+ // Scratch reused across calls, each filled and consumed within a single synchronous block.
191
+ const _color = new Color();
192
+ const _matrix = new Matrix4();
193
+ const _frustum = new Frustum();
194
+ const _sphere = new Sphere();
@@ -0,0 +1,73 @@
1
+ import { Matrix4 } from 'three';
2
+ import { Text as TroikaText, type TextMaterial } from 'troika-three-text';
3
+ import { type BatchedTextExt } from './BatchedText';
4
+ /**
5
+ * troika's `Text` with the deviations a batched, 2D-painter renderer needs.
6
+ *
7
+ * Four departures from upstream, each load-bearing:
8
+ *
9
+ * 1. **The clip rect is not applied to the geometry when batched.** Upstream ends
10
+ * `_prepareForRender` with `geometry.applyClipRect(…)`, but a member's geometry *is* the batch's
11
+ * shared `GlyphsGeometry` — applying one member's rect would clip the whole batch, and dirty a
12
+ * buffer every member then re-dirties. The batch reads the rect out of the uniforms instead.
13
+ * 2. **`depthOffset` is unsupported.** Upstream writes `material.polygonOffset` and friends from it
14
+ * on every render, so a base material that set them itself is overwritten every frame — with
15
+ * `false`, since `depthOffset` defaults to `0`. Set polygon offset on the base material directly;
16
+ * this class never touches it.
17
+ * 3. **The `color` shortcut is skipped when batched.** It mutates the *shared* derived material once
18
+ * per member per frame, and the batch ignores `material.color` anyway — it reads `text.color`
19
+ * straight into the data texture.
20
+ * 4. **`strokeOpacity` defaults to `1` rather than staying `undefined`.** Upstream leaves it unset
21
+ * when `strokeWidth` is `0`; the batch writes that uniform into a `Float32Array`, where
22
+ * `undefined` becomes `NaN`, and `NaN !== NaN` makes its change-detection report a write every
23
+ * frame — re-uploading the whole texture forever.
24
+ *
25
+ * Members are **not in the scene graph**, so nothing calls `updateMatrixWorld` for them: their local
26
+ * `matrix` is the transform the batch draws with, and the batch's bounds pass is what recomposes it.
27
+ * That pass runs only when something reports a move, and three gives `position` and `scale` no change
28
+ * callback — so after moving a member, call {@link updateMatrix}, or write the matrix wholesale with
29
+ * {@link setMatrix}. Both report back through {@link _batchParent}.
30
+ *
31
+ * Render it with {@link Text}.
32
+ */
33
+ export declare class TextExt extends TroikaText {
34
+ /**
35
+ * The batch this text belongs to, set by {@link BatchedTextExt.addText}. It is how a member
36
+ * reaches its batch without the batch scanning for changes: property writes invalidate the sync
37
+ * through it, and transform writes invalidate the bounds.
38
+ */
39
+ _batchParent: BatchedTextExt | undefined;
40
+ /**
41
+ * Backing store for the `_needsSync` accessor installed on the prototype below.
42
+ *
43
+ * Initialized `true`, which is both what a fresh member wants — it has never been laid out — and
44
+ * exactly what the base constructor leaves behind: its own property writes set the flag before
45
+ * this initializer runs, so seeding `false` here would quietly undo them.
46
+ */
47
+ _syncFlag: boolean;
48
+ /**
49
+ * Writes the local transform directly and tells the batch its bounds moved.
50
+ *
51
+ * Turns off `matrixAutoUpdate`, so the batch's bounds pass leaves the matrix alone — the caller
52
+ * owns it from here.
53
+ * @param matrix - The transform to draw this text with.
54
+ */
55
+ setMatrix(matrix: Matrix4): void;
56
+ /**
57
+ * Recomposes the local matrix from `position`/`quaternion`/`scale`, and tells the batch its bounds
58
+ * moved.
59
+ *
60
+ * Members are not in the scene graph, so three never calls this; a caller that moved one should,
61
+ * and the report is what schedules the batch's bounds pass. That pass recomposes the matrix again
62
+ * — harmless, and it is what leaves a caller who reaches for `invalidateBounds` instead equally
63
+ * correct.
64
+ */
65
+ updateMatrix(): void;
66
+ _prepareForRender(material: TextMaterial): void;
67
+ }
68
+ /**
69
+ * Registers the class as an R3F element, by value so the generated key cannot collide with anything
70
+ * three puts in the catalog when the canvas mounts.
71
+ */
72
+ export declare const Text: import("react").ExoticComponent<import("@react-three/fiber/dist/declarations/src/core/utils").Mutable<import("@react-three/fiber/dist/declarations/src/core/utils").Overwrite<Partial<import("@react-three/fiber/dist/declarations/src/core/utils").Overwrite<TextExt, import("@react-three/fiber").MathProps<TextExt> & import("@react-three/fiber").ReactProps<TextExt> & Partial<import("@react-three/fiber").EventHandlers>>>, Omit<import("@react-three/fiber").InstanceProps<TextExt, typeof TextExt>, "object">>>>;
73
+ //# sourceMappingURL=Text.d.ts.map
@@ -0,0 +1,215 @@
1
+ import { extend } from '@react-three/fiber';
2
+ import { Color, Matrix4, Vector3 } from 'three';
3
+ import { Text as TroikaText } from 'troika-three-text';
4
+ /**
5
+ * troika's `Text` with the deviations a batched, 2D-painter renderer needs.
6
+ *
7
+ * Four departures from upstream, each load-bearing:
8
+ *
9
+ * 1. **The clip rect is not applied to the geometry when batched.** Upstream ends
10
+ * `_prepareForRender` with `geometry.applyClipRect(…)`, but a member's geometry *is* the batch's
11
+ * shared `GlyphsGeometry` — applying one member's rect would clip the whole batch, and dirty a
12
+ * buffer every member then re-dirties. The batch reads the rect out of the uniforms instead.
13
+ * 2. **`depthOffset` is unsupported.** Upstream writes `material.polygonOffset` and friends from it
14
+ * on every render, so a base material that set them itself is overwritten every frame — with
15
+ * `false`, since `depthOffset` defaults to `0`. Set polygon offset on the base material directly;
16
+ * this class never touches it.
17
+ * 3. **The `color` shortcut is skipped when batched.** It mutates the *shared* derived material once
18
+ * per member per frame, and the batch ignores `material.color` anyway — it reads `text.color`
19
+ * straight into the data texture.
20
+ * 4. **`strokeOpacity` defaults to `1` rather than staying `undefined`.** Upstream leaves it unset
21
+ * when `strokeWidth` is `0`; the batch writes that uniform into a `Float32Array`, where
22
+ * `undefined` becomes `NaN`, and `NaN !== NaN` makes its change-detection report a write every
23
+ * frame — re-uploading the whole texture forever.
24
+ *
25
+ * Members are **not in the scene graph**, so nothing calls `updateMatrixWorld` for them: their local
26
+ * `matrix` is the transform the batch draws with, and the batch's bounds pass is what recomposes it.
27
+ * That pass runs only when something reports a move, and three gives `position` and `scale` no change
28
+ * callback — so after moving a member, call {@link updateMatrix}, or write the matrix wholesale with
29
+ * {@link setMatrix}. Both report back through {@link _batchParent}.
30
+ *
31
+ * Render it with {@link Text}.
32
+ */
33
+ export class TextExt extends TroikaText {
34
+ /**
35
+ * The batch this text belongs to, set by {@link BatchedTextExt.addText}. It is how a member
36
+ * reaches its batch without the batch scanning for changes: property writes invalidate the sync
37
+ * through it, and transform writes invalidate the bounds.
38
+ */
39
+ _batchParent = undefined;
40
+ /**
41
+ * Backing store for the `_needsSync` accessor installed on the prototype below.
42
+ *
43
+ * Initialized `true`, which is both what a fresh member wants — it has never been laid out — and
44
+ * exactly what the base constructor leaves behind: its own property writes set the flag before
45
+ * this initializer runs, so seeding `false` here would quietly undo them.
46
+ */
47
+ _syncFlag = true;
48
+ /**
49
+ * Writes the local transform directly and tells the batch its bounds moved.
50
+ *
51
+ * Turns off `matrixAutoUpdate`, so the batch's bounds pass leaves the matrix alone — the caller
52
+ * owns it from here.
53
+ * @param matrix - The transform to draw this text with.
54
+ */
55
+ setMatrix(matrix) {
56
+ this.matrixAutoUpdate = false;
57
+ this.matrix.copy(matrix);
58
+ this._batchParent?.invalidateBounds();
59
+ }
60
+ /**
61
+ * Recomposes the local matrix from `position`/`quaternion`/`scale`, and tells the batch its bounds
62
+ * moved.
63
+ *
64
+ * Members are not in the scene graph, so three never calls this; a caller that moved one should,
65
+ * and the report is what schedules the batch's bounds pass. That pass recomposes the matrix again
66
+ * — harmless, and it is what leaves a caller who reaches for `invalidateBounds` instead equally
67
+ * correct.
68
+ */
69
+ updateMatrix() {
70
+ super.updateMatrix();
71
+ this._batchParent?.invalidateBounds();
72
+ }
73
+ _prepareForRender(material) {
74
+ const defaultStrokeColor = 0x808080;
75
+ const defaultOrient = '+x+y';
76
+ const isOutline = material.isTextOutlineMaterial;
77
+ const uniforms = material.uniforms;
78
+ const textInfo = this.textRenderInfo;
79
+ const batched = this._batchParent !== undefined;
80
+ if (textInfo) {
81
+ const { sdfTexture, blockBounds } = textInfo;
82
+ const { width, height } = sdfTexture.image;
83
+ uniforms.uTroikaSDFTexture.value = sdfTexture;
84
+ uniforms.uTroikaSDFTextureSize.value.set(width, height);
85
+ uniforms.uTroikaSDFGlyphSize.value = textInfo.sdfGlyphSize;
86
+ uniforms.uTroikaSDFExponent.value = textInfo.sdfExponent;
87
+ uniforms.uTroikaTotalBounds.value.fromArray(blockBounds);
88
+ uniforms.uTroikaUseGlyphColors.value = !isOutline && !!textInfo.glyphColors;
89
+ let distanceOffset = 0;
90
+ let blurRadius = 0;
91
+ let strokeWidth = 0;
92
+ let fillOpacity;
93
+ // Deviation 4 (see class docs): initialized, not left undefined, so the batch never writes
94
+ // `NaN` into its data texture and re-uploads it on every frame thereafter.
95
+ let strokeOpacity = 1;
96
+ let strokeColor;
97
+ let offsetX = 0;
98
+ let offsetY = 0;
99
+ if (isOutline) {
100
+ const { outlineWidth, outlineOffsetX, outlineOffsetY, outlineBlur, outlineOpacity } = this;
101
+ distanceOffset = this._parsePercent(outlineWidth) || 0;
102
+ blurRadius = Math.max(0, this._parsePercent(outlineBlur) || 0);
103
+ fillOpacity = outlineOpacity;
104
+ offsetX = this._parsePercent(outlineOffsetX) || 0;
105
+ offsetY = this._parsePercent(outlineOffsetY) || 0;
106
+ }
107
+ else {
108
+ strokeWidth = Math.max(0, this._parsePercent(this.strokeWidth) || 0);
109
+ if (strokeWidth) {
110
+ strokeColor = this.strokeColor;
111
+ uniforms.uTroikaStrokeColor.value.set(strokeColor ?? defaultStrokeColor);
112
+ strokeOpacity = this.strokeOpacity ?? 1;
113
+ }
114
+ fillOpacity = this.fillOpacity;
115
+ }
116
+ uniforms.uTroikaEdgeOffset.value = distanceOffset;
117
+ uniforms.uTroikaPositionOffset.value.set(offsetX, offsetY);
118
+ uniforms.uTroikaBlurRadius.value = blurRadius;
119
+ uniforms.uTroikaStrokeWidth.value = strokeWidth;
120
+ uniforms.uTroikaStrokeOpacity.value = strokeOpacity;
121
+ uniforms.uTroikaFillOpacity.value = fillOpacity ?? 1;
122
+ uniforms.uTroikaCurveRadius.value = this.curveRadius || 0;
123
+ const clipRect = this.clipRect;
124
+ if (clipRect && Array.isArray(clipRect) && clipRect.length === 4) {
125
+ uniforms.uTroikaClipRect.value.fromArray(clipRect);
126
+ }
127
+ else {
128
+ // no clipping - choose a finite rect that shouldn't ever be reached by overflowing glyphs or outlines
129
+ const pad = (this.fontSize || 0.1) * 100;
130
+ uniforms.uTroikaClipRect.value.set(blockBounds[0] - pad, blockBounds[1] - pad, blockBounds[2] + pad, blockBounds[3] + pad);
131
+ }
132
+ // Deviation 1 (see class docs): the geometry is the batch's, shared by every member.
133
+ if (!batched)
134
+ this.geometry.applyClipRect(uniforms.uTroikaClipRect.value);
135
+ }
136
+ uniforms.uTroikaSDFDebug.value = !!this.debugSDF;
137
+ // Deviation 2 (see class docs): upstream wrote `material.polygonOffset`,
138
+ // `polygonOffsetFactor` and `polygonOffsetUnits` from `this.depthOffset` here.
139
+ // Deviation 3 (see class docs): the shortcut writes to the shared derived material, and the
140
+ // batch reads `text.color` directly, so batched members skip it entirely.
141
+ if (!batched) {
142
+ // Shortcut for setting material color via `color` prop on the mesh; this is
143
+ // applied only to the derived material to avoid mutating a shared base material.
144
+ const color = isOutline ? this.outlineColor || 0 : this.color;
145
+ if (color == null) {
146
+ // @ts-expect-error - Vendored
147
+ delete material.color; //inherit from base
148
+ }
149
+ else {
150
+ // eslint-disable-next-line no-prototype-builtins
151
+ const colorObj = material.hasOwnProperty('color')
152
+ ? material.color
153
+ : (material.color = new Color());
154
+ if (color !== colorObj._input || typeof color === 'object') {
155
+ colorObj.set((colorObj._input = color));
156
+ }
157
+ }
158
+ }
159
+ // base orientation
160
+ let orient = this.orientation || defaultOrient;
161
+ if (orient !== material._orientation) {
162
+ const rotMat = uniforms.uTroikaOrient.value;
163
+ orient = orient.replace(/[^-+xyz]/g, '');
164
+ const match = orient !== defaultOrient && /^([-+])([xyz])([-+])([xyz])$/.exec(orient);
165
+ if (match) {
166
+ const [, hSign, hAxis, vSign, vAxis] = match;
167
+ // @ts-expect-error - Vendored
168
+ _horizontal.set(0, 0, 0)[hAxis] = hSign === '-' ? 1 : -1;
169
+ // @ts-expect-error - Vendored
170
+ _vertical.set(0, 0, 0)[vAxis] = vSign === '-' ? -1 : 1;
171
+ _matrix.lookAt(ORIGIN, _horizontal.cross(_vertical), _vertical);
172
+ rotMat.setFromMatrix4(_matrix);
173
+ }
174
+ else {
175
+ rotMat.identity();
176
+ }
177
+ material._orientation = orient;
178
+ }
179
+ }
180
+ }
181
+ /**
182
+ * Routes every write of troika's own dirty flag through the batch.
183
+ *
184
+ * Upstream re-declares all eighteen syncable properties (`text`, `fontSize`, `anchorX`, …) as
185
+ * accessors that set `_needsSync`; hooking the flag itself instead of copying that list covers every
186
+ * one of them, plus the queued-resync path inside `Text.sync`, and cannot drift when troika adds a
187
+ * nineteenth.
188
+ *
189
+ * Runs during `super()` — the base constructor seeds the syncable properties — at which point the
190
+ * `_batchParent` field initializer has not run yet, so the optional call is doing real work.
191
+ */
192
+ Object.defineProperty(TextExt.prototype, '_needsSync', {
193
+ configurable: true,
194
+ get() {
195
+ return this._syncFlag;
196
+ },
197
+ set(value) {
198
+ this._syncFlag = value;
199
+ // Undefined while the base constructor runs, which is fine: a member is always constructed
200
+ // before it joins a batch, so there is no parent to tell yet.
201
+ if (value)
202
+ this._batchParent?.requestSync();
203
+ },
204
+ });
205
+ /**
206
+ * Registers the class as an R3F element, by value so the generated key cannot collide with anything
207
+ * three puts in the catalog when the canvas mounts.
208
+ */
209
+ export const Text = extend(TextExt);
210
+ // Scratch reused across calls, each filled and consumed within a single synchronous block.
211
+ const _matrix = new Matrix4();
212
+ const _horizontal = new Vector3();
213
+ const _vertical = new Vector3();
214
+ /** The eye a base-orientation `lookAt` turns about — always the local origin. */
215
+ const ORIGIN = new Vector3();
@@ -0,0 +1,56 @@
1
+ import { type Material } from 'three';
2
+ import { type BatchTextMaterial, type TextMaterial } from 'troika-three-text';
3
+ import { type InstanceUniforms } from '../InstanceUniforms';
4
+ /**
5
+ * The material side of a text batch: what troika's derivation has to become before it can draw a
6
+ * batch's members, and where the batch's per-instance uniforms are declared.
7
+ *
8
+ * Troika builds that material in two moments with its own derivation between them, so the work
9
+ * splits the same way — {@link patchBaseMaterial} runs on the base material *before*
10
+ * `createDerivedMaterial`, {@link configureDerivedMaterial} on the result *after*. Each order is
11
+ * load-bearing; see the two.
12
+ *
13
+ * Free functions holding no state, as `instanced-material.ts` is. What is per-batch — which material
14
+ * has already been wrapped — stays on the batch.
15
+ */
16
+ /** The per-member index attribute troika's batch material declares, and the uniforms texture indexes by. */
17
+ export declare const MEMBER_INDEX_ATTRIBUTE = "aTroikaTextBatchMemberIndex";
18
+ /**
19
+ * Wraps a base material's `onBeforeCompile` so a batch's per-instance uniforms are declared ahead of
20
+ * anything the material itself patches in.
21
+ *
22
+ * The prologue goes on the **base**, ahead of troika's rewrite: troika calls the base material's
23
+ * handler first and then rebuilds `main` around it, renaming the original `troikaOrigMain`.
24
+ * Injecting here puts the prologue and whatever the material patches in on the same side of that
25
+ * rename, so a material patch reading a per-instance uniform finds it in scope; injecting from the
26
+ * derived handler instead would strand the two in different functions.
27
+ *
28
+ * Call once per material — a second wrap would nest, and the caller holds the memo that prevents it.
29
+ * @param material - The base material troika derives from.
30
+ * @param uniforms - The batch's per-instance uniforms.
31
+ */
32
+ export declare function patchBaseMaterial(material: Material, uniforms: InstanceUniforms): void;
33
+ /**
34
+ * Finishes troika's derivation: reverts its two impositions on the base, and installs the one shader
35
+ * hook a batch needs.
36
+ * @param derived - What `createDerivedMaterial` returned.
37
+ * @param base - The material it was derived from.
38
+ * @param uniforms - The batch's per-instance uniforms, whose presence the program is cached by.
39
+ * @returns `derived`.
40
+ */
41
+ export declare function configureDerivedMaterial(derived: TextMaterial, base: Material, uniforms: InstanceUniforms): TextMaterial;
42
+ /**
43
+ * Whether a material draws through the batched text shader — the only thing a batch's per-draw
44
+ * texture fill asks of the material it is handed.
45
+ *
46
+ * `isTroikaTextMaterial`, which upstream tests instead, does not establish that. Troika sets it on
47
+ * the text derivation, which the batch's material only sits on top of — so a plain unbatched `Text`
48
+ * material carries the flag too, and filling one would write matrices its shader never reads. The
49
+ * uniforms are the honest test: a batch derives over a link that declares them, an unbatched `Text`
50
+ * never does, and every derivation merges its base's uniform map forward, so they survive into any
51
+ * sibling built by calling `material.constructor`.
52
+ * @param material - The material three is about to draw with.
53
+ * @returns Whether it reads its transforms from a matrix texture.
54
+ */
55
+ export declare function readsMemberMatrices(material: Material): material is BatchTextMaterial;
56
+ //# sourceMappingURL=batched-text-material.d.ts.map
@@ -0,0 +1,121 @@
1
+ import { openMain } from '../shader-source';
2
+ /**
3
+ * The material side of a text batch: what troika's derivation has to become before it can draw a
4
+ * batch's members, and where the batch's per-instance uniforms are declared.
5
+ *
6
+ * Troika builds that material in two moments with its own derivation between them, so the work
7
+ * splits the same way — {@link patchBaseMaterial} runs on the base material *before*
8
+ * `createDerivedMaterial`, {@link configureDerivedMaterial} on the result *after*. Each order is
9
+ * load-bearing; see the two.
10
+ *
11
+ * Free functions holding no state, as `instanced-material.ts` is. What is per-batch — which material
12
+ * has already been wrapped — stays on the batch.
13
+ */
14
+ /** The per-member index attribute troika's batch material declares, and the uniforms texture indexes by. */
15
+ export const MEMBER_INDEX_ATTRIBUTE = 'aTroikaTextBatchMemberIndex';
16
+ /**
17
+ * Wraps a base material's `onBeforeCompile` so a batch's per-instance uniforms are declared ahead of
18
+ * anything the material itself patches in.
19
+ *
20
+ * The prologue goes on the **base**, ahead of troika's rewrite: troika calls the base material's
21
+ * handler first and then rebuilds `main` around it, renaming the original `troikaOrigMain`.
22
+ * Injecting here puts the prologue and whatever the material patches in on the same side of that
23
+ * rename, so a material patch reading a per-instance uniform finds it in scope; injecting from the
24
+ * derived handler instead would strand the two in different functions.
25
+ *
26
+ * Call once per material — a second wrap would nest, and the caller holds the memo that prevents it.
27
+ * @param material - The base material troika derives from.
28
+ * @param uniforms - The batch's per-instance uniforms.
29
+ */
30
+ export function patchBaseMaterial(material, uniforms) {
31
+ const onBeforeCompileBase = material.onBeforeCompile.bind(material);
32
+ material.onBeforeCompile = (shader, renderer) => {
33
+ uniforms.patchShader(shader, MEMBER_INDEX_ATTRIBUTE);
34
+ onBeforeCompileBase(shader, renderer);
35
+ };
36
+ }
37
+ /**
38
+ * Finishes troika's derivation: reverts its two impositions on the base, and installs the one shader
39
+ * hook a batch needs.
40
+ * @param derived - What `createDerivedMaterial` returned.
41
+ * @param base - The material it was derived from.
42
+ * @param uniforms - The batch's per-instance uniforms, whose presence the program is cached by.
43
+ * @returns `derived`.
44
+ */
45
+ export function configureDerivedMaterial(derived, base, uniforms) {
46
+ // troika forces `transparent = true` on every derived text material, which moves the batch into
47
+ // three's *transparent* render list — drawn after every opaque object regardless of `renderOrder`,
48
+ // so text would always cover the layers above it. Our base materials get alpha blending from
49
+ // `CustomBlending` instead (objects/materials/shared.ts), so this restores what the base asked for.
50
+ // Everything else troika leaves alone already delegates to the base through the prototype chain.
51
+ derived.transparent = base.transparent;
52
+ // With `transparent` reverted the batch renders from three's *opaque* list, which
53
+ // `painterSortStable` orders by `material.id` — and troika builds its outline material with
54
+ // `id + 0.1`, so the preliminary outline pass would sort *after* the fill and paint over it. (Its
55
+ // own forced transparency hid this: the transparent list sorts by depth, with no `material.id`
56
+ // term.) Building it here, with a lower id, restores the intended order; troika's getter finds it
57
+ // already cached and uses it.
58
+ derived._outlineMtl = makeOutlineMaterial(derived);
59
+ // Recompile when the uniform schema changes, since that changes the generated GLSL.
60
+ const cacheKeyBase = derived.customProgramCacheKey.bind(derived);
61
+ derived.customProgramCacheKey = () => `efpText_${!!uniforms.texture}|${cacheKeyBase()}`;
62
+ // Assignment, *not* the read-bind-wrap pattern the sibling meshes use: troika defines
63
+ // `onBeforeCompile` as an accessor pair whose getter returns troika's own handler and whose setter
64
+ // stashes ours (troika-three-utils DerivedMaterial), so calling through the getter from inside our
65
+ // handler would re-enter troika's, which calls ours again. Troika invokes this one last, after its
66
+ // shader upgrade, which is the only thing that has to be true.
67
+ derived.onBeforeCompile = (shader) => {
68
+ // Late by necessity: troika's own `vertexTransform` *reads* this offset, and the transform runs
69
+ // from the main it builds, ahead of the base's. Initializing any earlier would leave the
70
+ // transform reading the garbage this exists to prevent.
71
+ shader.vertexShader = openMain(shader.vertexShader, POSITION_OFFSET_INIT);
72
+ };
73
+ return derived;
74
+ }
75
+ /**
76
+ * Whether a material draws through the batched text shader — the only thing a batch's per-draw
77
+ * texture fill asks of the material it is handed.
78
+ *
79
+ * `isTroikaTextMaterial`, which upstream tests instead, does not establish that. Troika sets it on
80
+ * the text derivation, which the batch's material only sits on top of — so a plain unbatched `Text`
81
+ * material carries the flag too, and filling one would write matrices its shader never reads. The
82
+ * uniforms are the honest test: a batch derives over a link that declares them, an unbatched `Text`
83
+ * never does, and every derivation merges its base's uniform map forward, so they survive into any
84
+ * sibling built by calling `material.constructor`.
85
+ * @param material - The material three is about to draw with.
86
+ * @returns Whether it reads its transforms from a matrix texture.
87
+ */
88
+ export function readsMemberMatrices(material) {
89
+ return !!material.uniforms?.uTroikaMatricesTexture;
90
+ }
91
+ /**
92
+ * Builds the outline pass's material: troika's own recipe, with the id inverted.
93
+ *
94
+ * Prototype-chained to the fill material so it shares every uniform and shader, exactly as troika
95
+ * does — the two differ only in `isTextOutlineMaterial`, which is what the per-draw fill branches on
96
+ * to write the outline row of the data texture.
97
+ * @param derived - The fill material to derive the outline pass from.
98
+ * @returns The outline material.
99
+ */
100
+ function makeOutlineMaterial(derived) {
101
+ const outline = Object.create(derived, {
102
+ // Below the fill, not above it: three's opaque sort is ascending by `material.id`, and the
103
+ // outline is a *preliminary* pass. A fraction keeps it clear of every integer material id.
104
+ id: { value: derived.id - 0.5 },
105
+ });
106
+ outline.isTextOutlineMaterial = true;
107
+ outline.depthWrite = false;
108
+ outline.map = null;
109
+ // troika disposes its own outline material with the fill; matched so replacing it leaks nothing.
110
+ derived.addEventListener('dispose', function onDispose() {
111
+ derived.removeEventListener('dispose', onDispose);
112
+ outline.dispose();
113
+ });
114
+ return outline;
115
+ }
116
+ /**
117
+ * Seeds the position-offset varying. An uninitialized varying reads as garbage on some Samsung/Mali
118
+ * drivers, and troika's `vertexTransform` only assigns this one in the outline branch — so the main
119
+ * pass would never write it at all.
120
+ */
121
+ const POSITION_OFFSET_INIT = /* glsl */ `uTroikaPositionOffset = vec2(0.0);`;