@cosmos.gl/graph 2.6.2 → 2.7.0-beta.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 (186) hide show
  1. package/.eslintrc +147 -0
  2. package/.github/SECURITY.md +13 -0
  3. package/.github/dco.yml +4 -0
  4. package/.github/workflows/github_pages.yml +54 -0
  5. package/.storybook/main.ts +26 -0
  6. package/.storybook/manager-head.html +1 -0
  7. package/.storybook/manager.ts +14 -0
  8. package/.storybook/preview.ts +29 -0
  9. package/.storybook/style.css +3 -0
  10. package/CHARTER.md +69 -0
  11. package/CODE_OF_CONDUCT.md +178 -0
  12. package/CONTRIBUTING.md +22 -0
  13. package/GOVERNANCE.md +21 -0
  14. package/cosmos-2-0-migration-notes.md +98 -0
  15. package/cosmos_awesome.md +96 -0
  16. package/dist/config.d.ts +5 -18
  17. package/dist/graph/utils/error-message.d.ts +1 -1
  18. package/dist/helper.d.ts +39 -2
  19. package/dist/index-FUIgayhu.js +19827 -0
  20. package/dist/index-FUIgayhu.js.map +1 -0
  21. package/dist/index.d.ts +17 -64
  22. package/dist/index.js +14 -14658
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.min.js +1062 -475
  25. package/dist/index.min.js.map +1 -1
  26. package/dist/modules/Clusters/index.d.ts +11 -3
  27. package/dist/modules/ForceCenter/index.d.ts +10 -3
  28. package/dist/modules/ForceGravity/index.d.ts +5 -1
  29. package/dist/modules/ForceLink/index.d.ts +8 -5
  30. package/dist/modules/ForceManyBody/index.d.ts +16 -7
  31. package/dist/modules/ForceMouse/index.d.ts +5 -1
  32. package/dist/modules/GraphData/index.d.ts +0 -1
  33. package/dist/modules/Lines/index.d.ts +11 -5
  34. package/dist/modules/Points/index.d.ts +31 -13
  35. package/dist/modules/Store/index.d.ts +93 -0
  36. package/dist/modules/core-module.d.ts +3 -3
  37. package/dist/stories/beginners/basic-set-up/data-gen.d.ts +4 -0
  38. package/dist/stories/beginners/basic-set-up/index.d.ts +6 -0
  39. package/dist/stories/beginners/link-hovering/data-generator.d.ts +19 -0
  40. package/dist/stories/beginners/link-hovering/index.d.ts +6 -0
  41. package/dist/stories/beginners/point-labels/data.d.ts +13 -0
  42. package/dist/stories/beginners/point-labels/index.d.ts +10 -0
  43. package/dist/stories/beginners/point-labels/labels.d.ts +8 -0
  44. package/dist/stories/beginners/quick-start.d.ts +6 -0
  45. package/dist/stories/beginners/remove-points/config.d.ts +2 -0
  46. package/dist/stories/beginners/remove-points/data-gen.d.ts +4 -0
  47. package/dist/stories/beginners/remove-points/index.d.ts +6 -0
  48. package/dist/stories/beginners.stories.d.ts +10 -0
  49. package/dist/stories/clusters/polygon-selection/index.d.ts +6 -0
  50. package/dist/stories/clusters/polygon-selection/polygon.d.ts +20 -0
  51. package/dist/stories/clusters/radial.d.ts +6 -0
  52. package/dist/stories/clusters/with-labels.d.ts +6 -0
  53. package/dist/stories/clusters/worm.d.ts +6 -0
  54. package/dist/stories/clusters.stories.d.ts +9 -0
  55. package/dist/stories/create-cluster-labels.d.ts +4 -0
  56. package/dist/stories/create-cosmos.d.ts +17 -0
  57. package/dist/stories/create-story.d.ts +16 -0
  58. package/dist/stories/experiments/full-mesh.d.ts +6 -0
  59. package/dist/stories/experiments/mesh-with-holes.d.ts +6 -0
  60. package/dist/stories/experiments.stories.d.ts +7 -0
  61. package/dist/stories/generate-mesh-data.d.ts +12 -0
  62. package/dist/stories/geospatial/moscow-metro-stations/index.d.ts +16 -0
  63. package/dist/stories/geospatial/moscow-metro-stations/moscow-metro-coords.d.ts +1 -0
  64. package/dist/stories/geospatial/moscow-metro-stations/point-colors.d.ts +1 -0
  65. package/dist/stories/geospatial.stories.d.ts +6 -0
  66. package/dist/stories/shapes/all-shapes/index.d.ts +6 -0
  67. package/dist/stories/shapes/image-example/index.d.ts +6 -0
  68. package/dist/stories/shapes.stories.d.ts +7 -0
  69. package/dist/stories/test-luma-migration.d.ts +6 -0
  70. package/dist/stories/test.stories.d.ts +6 -0
  71. package/dist/webgl-device-B9ewDj5L.js +3923 -0
  72. package/dist/webgl-device-B9ewDj5L.js.map +1 -0
  73. package/logo.svg +3 -0
  74. package/package.json +5 -7
  75. package/rollup.config.js +70 -0
  76. package/src/config.ts +728 -0
  77. package/src/declaration.d.ts +12 -0
  78. package/src/graph/utils/error-message.ts +23 -0
  79. package/src/helper.ts +113 -0
  80. package/src/index.ts +1769 -0
  81. package/src/modules/Clusters/calculate-centermass.frag +12 -0
  82. package/src/modules/Clusters/calculate-centermass.vert +38 -0
  83. package/src/modules/Clusters/force-cluster.frag +55 -0
  84. package/src/modules/Clusters/index.ts +578 -0
  85. package/src/modules/Drag/index.ts +33 -0
  86. package/src/modules/FPSMonitor/css.ts +53 -0
  87. package/src/modules/FPSMonitor/index.ts +28 -0
  88. package/src/modules/ForceCenter/calculate-centermass.frag +9 -0
  89. package/src/modules/ForceCenter/calculate-centermass.vert +26 -0
  90. package/src/modules/ForceCenter/force-center.frag +37 -0
  91. package/src/modules/ForceCenter/index.ts +284 -0
  92. package/src/modules/ForceGravity/force-gravity.frag +40 -0
  93. package/src/modules/ForceGravity/index.ts +107 -0
  94. package/src/modules/ForceLink/force-spring.ts +89 -0
  95. package/src/modules/ForceLink/index.ts +293 -0
  96. package/src/modules/ForceManyBody/calculate-level.frag +9 -0
  97. package/src/modules/ForceManyBody/calculate-level.vert +37 -0
  98. package/src/modules/ForceManyBody/force-centermass.frag +61 -0
  99. package/src/modules/ForceManyBody/force-level.frag +138 -0
  100. package/src/modules/ForceManyBody/index.ts +525 -0
  101. package/src/modules/ForceManyBody/quadtree-frag-shader.ts +89 -0
  102. package/src/modules/ForceManyBodyQuadtree/calculate-level.frag +9 -0
  103. package/src/modules/ForceManyBodyQuadtree/calculate-level.vert +25 -0
  104. package/src/modules/ForceManyBodyQuadtree/index.ts +157 -0
  105. package/src/modules/ForceManyBodyQuadtree/quadtree-frag-shader.ts +93 -0
  106. package/src/modules/ForceMouse/force-mouse.frag +35 -0
  107. package/src/modules/ForceMouse/index.ts +102 -0
  108. package/src/modules/GraphData/index.ts +383 -0
  109. package/src/modules/Lines/draw-curve-line.frag +59 -0
  110. package/src/modules/Lines/draw-curve-line.vert +248 -0
  111. package/src/modules/Lines/geometry.ts +18 -0
  112. package/src/modules/Lines/hovered-line-index.frag +43 -0
  113. package/src/modules/Lines/hovered-line-index.vert +13 -0
  114. package/src/modules/Lines/index.ts +661 -0
  115. package/src/modules/Points/atlas-utils.ts +137 -0
  116. package/src/modules/Points/drag-point.frag +34 -0
  117. package/src/modules/Points/draw-highlighted.frag +44 -0
  118. package/src/modules/Points/draw-highlighted.vert +145 -0
  119. package/src/modules/Points/draw-points.frag +259 -0
  120. package/src/modules/Points/draw-points.vert +203 -0
  121. package/src/modules/Points/fill-sampled-points.frag +12 -0
  122. package/src/modules/Points/fill-sampled-points.vert +51 -0
  123. package/src/modules/Points/find-hovered-point.frag +15 -0
  124. package/src/modules/Points/find-hovered-point.vert +90 -0
  125. package/src/modules/Points/find-points-on-area-selection.frag +88 -0
  126. package/src/modules/Points/find-points-on-polygon-selection.frag +89 -0
  127. package/src/modules/Points/index.ts +2292 -0
  128. package/src/modules/Points/track-positions.frag +30 -0
  129. package/src/modules/Points/update-position.frag +39 -0
  130. package/src/modules/Shared/buffer.ts +39 -0
  131. package/src/modules/Shared/clear.frag +10 -0
  132. package/src/modules/Shared/quad.vert +13 -0
  133. package/src/modules/Store/index.ts +283 -0
  134. package/src/modules/Zoom/index.ts +148 -0
  135. package/src/modules/core-module.ts +28 -0
  136. package/src/stories/1. welcome.mdx +75 -0
  137. package/src/stories/2. configuration.mdx +111 -0
  138. package/src/stories/3. api-reference.mdx +591 -0
  139. package/src/stories/beginners/basic-set-up/data-gen.ts +33 -0
  140. package/src/stories/beginners/basic-set-up/index.ts +167 -0
  141. package/src/stories/beginners/basic-set-up/style.css +35 -0
  142. package/src/stories/beginners/link-hovering/data-generator.ts +198 -0
  143. package/src/stories/beginners/link-hovering/index.ts +65 -0
  144. package/src/stories/beginners/link-hovering/style.css +73 -0
  145. package/src/stories/beginners/point-labels/data.ts +73 -0
  146. package/src/stories/beginners/point-labels/index.ts +69 -0
  147. package/src/stories/beginners/point-labels/labels.ts +46 -0
  148. package/src/stories/beginners/point-labels/style.css +16 -0
  149. package/src/stories/beginners/quick-start.ts +54 -0
  150. package/src/stories/beginners/remove-points/config.ts +25 -0
  151. package/src/stories/beginners/remove-points/data-gen.ts +30 -0
  152. package/src/stories/beginners/remove-points/index.ts +96 -0
  153. package/src/stories/beginners/remove-points/style.css +31 -0
  154. package/src/stories/beginners.stories.ts +130 -0
  155. package/src/stories/clusters/polygon-selection/index.ts +52 -0
  156. package/src/stories/clusters/polygon-selection/polygon.ts +143 -0
  157. package/src/stories/clusters/polygon-selection/style.css +8 -0
  158. package/src/stories/clusters/radial.ts +24 -0
  159. package/src/stories/clusters/with-labels.ts +54 -0
  160. package/src/stories/clusters/worm.ts +40 -0
  161. package/src/stories/clusters.stories.ts +77 -0
  162. package/src/stories/create-cluster-labels.ts +50 -0
  163. package/src/stories/create-cosmos.ts +72 -0
  164. package/src/stories/create-story.ts +51 -0
  165. package/src/stories/experiments/full-mesh.ts +13 -0
  166. package/src/stories/experiments/mesh-with-holes.ts +13 -0
  167. package/src/stories/experiments.stories.ts +43 -0
  168. package/src/stories/generate-mesh-data.ts +125 -0
  169. package/src/stories/geospatial/moscow-metro-stations/index.ts +66 -0
  170. package/src/stories/geospatial/moscow-metro-stations/moscow-metro-coords.ts +1 -0
  171. package/src/stories/geospatial/moscow-metro-stations/point-colors.ts +46 -0
  172. package/src/stories/geospatial/moscow-metro-stations/style.css +30 -0
  173. package/src/stories/geospatial.stories.ts +30 -0
  174. package/src/stories/shapes/all-shapes/index.ts +73 -0
  175. package/src/stories/shapes/image-example/icons/box.png +0 -0
  176. package/src/stories/shapes/image-example/icons/lego.png +0 -0
  177. package/src/stories/shapes/image-example/icons/s.png +0 -0
  178. package/src/stories/shapes/image-example/icons/swift.png +0 -0
  179. package/src/stories/shapes/image-example/icons/toolbox.png +0 -0
  180. package/src/stories/shapes/image-example/index.ts +246 -0
  181. package/src/stories/shapes.stories.ts +37 -0
  182. package/src/stories/test-luma-migration.ts +195 -0
  183. package/src/stories/test.stories.ts +25 -0
  184. package/src/variables.ts +68 -0
  185. package/tsconfig.json +41 -0
  186. package/vite.config.ts +52 -0
@@ -0,0 +1,661 @@
1
+ import { Framebuffer, Buffer, Texture, UniformStore, RenderPass } from '@luma.gl/core'
2
+ import { Model } from '@luma.gl/engine'
3
+ import { CoreModule } from '@/graph/modules/core-module'
4
+ import drawLineFrag from '@/graph/modules/Lines/draw-curve-line.frag?raw'
5
+ import drawLineVert from '@/graph/modules/Lines/draw-curve-line.vert?raw'
6
+ import hoveredLineIndexFrag from '@/graph/modules/Lines/hovered-line-index.frag?raw'
7
+ import hoveredLineIndexVert from '@/graph/modules/Lines/hovered-line-index.vert?raw'
8
+ import { defaultConfigValues } from '@/graph/variables'
9
+ import { getCurveLineGeometry } from '@/graph/modules/Lines/geometry'
10
+
11
+ export class Lines extends CoreModule {
12
+ public linkIndexFbo: Framebuffer | undefined
13
+ public hoveredLineIndexFbo: Framebuffer | undefined
14
+ private drawCurveCommand: Model | undefined
15
+ private hoveredLineIndexCommand: Model | undefined
16
+ private pointABuffer: Buffer | undefined
17
+ private pointBBuffer: Buffer | undefined
18
+ private colorBuffer: Buffer | undefined
19
+ private widthBuffer: Buffer | undefined
20
+ private arrowBuffer: Buffer | undefined
21
+ private curveLineGeometry: number[][] | undefined
22
+ private curveLineBuffer: Buffer | undefined
23
+ private linkIndexBuffer: Buffer | undefined
24
+ private quadBuffer: Buffer | undefined
25
+ private linkIndexTexture: Texture | undefined
26
+ private hoveredLineIndexTexture: Texture | undefined
27
+
28
+ // Uniform stores for scalar uniforms
29
+ private drawLineUniformStore: UniformStore<{
30
+ drawLineUniforms: {
31
+ transformationMatrix: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number];
32
+ pointsTextureSize: number;
33
+ widthScale: number;
34
+ linkArrowsSizeScale: number;
35
+ spaceSize: number;
36
+ screenSize: [number, number];
37
+ linkVisibilityDistanceRange: number[];
38
+ linkVisibilityMinTransparency: number;
39
+ linkOpacity: number;
40
+ greyoutOpacity: number;
41
+ curvedWeight: number;
42
+ curvedLinkControlPointDistance: number;
43
+ curvedLinkSegments: number;
44
+ scaleLinksOnZoom: number;
45
+ maxPointSize: number;
46
+ renderMode: number;
47
+ hoveredLinkIndex: number;
48
+ hoveredLinkColor: number[];
49
+ hoveredLinkWidthIncrease: number;
50
+ };
51
+ drawLineFragmentUniforms: {
52
+ renderMode: number;
53
+ };
54
+ }> | undefined
55
+
56
+ private hoveredLineIndexUniformStore: UniformStore<{
57
+ hoveredLineIndexUniforms: {
58
+ mousePosition: number[];
59
+ screenSize: [number, number];
60
+ };
61
+ }> | undefined
62
+
63
+ // Track previous screen size to detect changes
64
+ private previousScreenSize: [number, number] | undefined
65
+
66
+ public initPrograms (): void {
67
+ const { device, config, store } = this
68
+
69
+ this.updateLinkIndexFbo()
70
+
71
+ // Initialize the hovered line index FBO
72
+ if (!this.hoveredLineIndexTexture) {
73
+ this.hoveredLineIndexTexture = device.createTexture({
74
+ width: 1,
75
+ height: 1,
76
+ format: 'rgba32float',
77
+ usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_DST,
78
+ })
79
+ this.hoveredLineIndexTexture.copyImageData({
80
+ data: new Float32Array(4).fill(0),
81
+ bytesPerRow: 1,
82
+ mipLevel: 0,
83
+ x: 0,
84
+ y: 0,
85
+ })
86
+ this.hoveredLineIndexFbo = device.createFramebuffer({
87
+ width: 1,
88
+ height: 1,
89
+ colorAttachments: [this.hoveredLineIndexTexture],
90
+ })
91
+ }
92
+
93
+ // Ensure geometry buffer exists (create empty if needed)
94
+ if (!this.curveLineGeometry) {
95
+ this.updateCurveLineGeometry()
96
+ }
97
+
98
+ // Ensure all attribute buffers exist (create empty if needed) so Model has all attributes
99
+ const linksNumber = this.data.linksNumber ?? 0
100
+ if (!this.pointABuffer) {
101
+ this.pointABuffer = device.createBuffer({
102
+ data: new Float32Array(linksNumber * 2),
103
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
104
+ })
105
+ }
106
+ if (!this.pointBBuffer) {
107
+ this.pointBBuffer = device.createBuffer({
108
+ data: new Float32Array(linksNumber * 2),
109
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
110
+ })
111
+ }
112
+ if (!this.colorBuffer) {
113
+ this.colorBuffer = device.createBuffer({
114
+ data: new Float32Array(linksNumber * 4),
115
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
116
+ })
117
+ }
118
+ if (!this.widthBuffer) {
119
+ this.widthBuffer = device.createBuffer({
120
+ data: new Float32Array(linksNumber),
121
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
122
+ })
123
+ }
124
+ if (!this.arrowBuffer) {
125
+ this.arrowBuffer = device.createBuffer({
126
+ data: new Float32Array(linksNumber),
127
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
128
+ })
129
+ }
130
+ if (!this.linkIndexBuffer) {
131
+ this.linkIndexBuffer = device.createBuffer({
132
+ data: new Float32Array(linksNumber),
133
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
134
+ })
135
+ }
136
+
137
+ if (!this.drawCurveCommand) {
138
+ // Create UniformStore for drawLine uniforms
139
+ if (!this.drawLineUniformStore) {
140
+ this.drawLineUniformStore = new UniformStore({
141
+ drawLineUniforms: {
142
+ uniformTypes: {
143
+ transformationMatrix: 'mat4x4<f32>',
144
+ pointsTextureSize: 'f32',
145
+ widthScale: 'f32',
146
+ linkArrowsSizeScale: 'f32',
147
+ spaceSize: 'f32',
148
+ screenSize: 'vec2<f32>',
149
+ linkVisibilityDistanceRange: 'vec2<f32>',
150
+ linkVisibilityMinTransparency: 'f32',
151
+ linkOpacity: 'f32',
152
+ greyoutOpacity: 'f32',
153
+ curvedWeight: 'f32',
154
+ curvedLinkControlPointDistance: 'f32',
155
+ curvedLinkSegments: 'f32',
156
+ scaleLinksOnZoom: 'f32',
157
+ maxPointSize: 'f32',
158
+ renderMode: 'f32',
159
+ hoveredLinkIndex: 'f32',
160
+ hoveredLinkColor: 'vec4<f32>',
161
+ hoveredLinkWidthIncrease: 'f32',
162
+ },
163
+ defaultUniforms: {
164
+ transformationMatrix: store.transformationMatrix4x4,
165
+ pointsTextureSize: store.pointsTextureSize,
166
+ widthScale: config.linkWidthScale ?? 1,
167
+ linkArrowsSizeScale: config.linkArrowsSizeScale ?? 1,
168
+ spaceSize: store.adjustedSpaceSize ?? 0,
169
+ screenSize: store.screenSize ?? [0, 0],
170
+ linkVisibilityDistanceRange: config.linkVisibilityDistanceRange ?? [0, 0],
171
+ linkVisibilityMinTransparency: config.linkVisibilityMinTransparency ?? 0,
172
+ linkOpacity: config.linkOpacity ?? 1,
173
+ greyoutOpacity: config.linkGreyoutOpacity ?? 1,
174
+ curvedWeight: config.curvedLinkWeight ?? 0,
175
+ curvedLinkControlPointDistance: config.curvedLinkControlPointDistance ?? 0,
176
+ curvedLinkSegments: config.curvedLinks ? config.curvedLinkSegments ?? defaultConfigValues.curvedLinkSegments : 1,
177
+ scaleLinksOnZoom: (config.scaleLinksOnZoom ?? true) ? 1 : 0,
178
+ maxPointSize: store.maxPointSize ?? 100,
179
+ renderMode: 0.0,
180
+ hoveredLinkIndex: store.hoveredLinkIndex ?? -1,
181
+ hoveredLinkColor: store.hoveredLinkColor ?? [-1, -1, -1, -1],
182
+ hoveredLinkWidthIncrease: config.hoveredLinkWidthIncrease ?? 0,
183
+ },
184
+ },
185
+ drawLineFragmentUniforms: {
186
+ uniformTypes: {
187
+ renderMode: 'f32',
188
+ },
189
+ defaultUniforms: {
190
+ renderMode: 0.0,
191
+ },
192
+ },
193
+ })
194
+ }
195
+
196
+ this.drawCurveCommand = new Model(device, {
197
+ vs: drawLineVert,
198
+ fs: drawLineFrag,
199
+ topology: 'triangle-strip',
200
+ vertexCount: this.curveLineGeometry?.length ?? 0,
201
+ attributes: {
202
+ position: this.curveLineBuffer!,
203
+ pointA: this.pointABuffer!,
204
+ pointB: this.pointBBuffer!,
205
+ color: this.colorBuffer!,
206
+ width: this.widthBuffer!,
207
+ arrow: this.arrowBuffer!,
208
+ linkIndices: this.linkIndexBuffer!,
209
+ },
210
+ bufferLayout: [
211
+ { name: 'position', format: 'float32x2' },
212
+ { name: 'pointA', format: 'float32x2', stepMode: 'instance' },
213
+ { name: 'pointB', format: 'float32x2', stepMode: 'instance' },
214
+ { name: 'color', format: 'float32x4', stepMode: 'instance' },
215
+ { name: 'width', format: 'float32', stepMode: 'instance' },
216
+ { name: 'arrow', format: 'float32', stepMode: 'instance' },
217
+ { name: 'linkIndices', format: 'float32', stepMode: 'instance' },
218
+ ],
219
+ defines: {
220
+ USE_UNIFORM_BUFFERS: true,
221
+ },
222
+ bindings: {
223
+ drawLineUniforms: this.drawLineUniformStore.getManagedUniformBuffer(device, 'drawLineUniforms'),
224
+ drawLineFragmentUniforms: this.drawLineUniformStore.getManagedUniformBuffer(device, 'drawLineFragmentUniforms'),
225
+ ...(this.points?.currentPositionTexture && { positionsTexture: this.points.currentPositionTexture }),
226
+ ...(this.points?.greyoutStatusTexture && { pointGreyoutStatus: this.points.greyoutStatusTexture }),
227
+ },
228
+ /**
229
+ * Blending behavior for link index rendering (renderMode: 1.0 - hover detection):
230
+ *
231
+ * When rendering link indices to the framebuffer, we use full opacity (1.0).
232
+ * This means:
233
+ * - The source color completely overwrites the destination
234
+ * - No blending occurs - it's like drawing with a permanent marker
235
+ * - This preserves the exact index values we need for picking/selection
236
+ */
237
+ parameters: {
238
+ cullMode: 'back',
239
+ blend: true,
240
+ blendColorOperation: 'add',
241
+ blendColorSrcFactor: 'src-alpha',
242
+ blendColorDstFactor: 'one-minus-src-alpha',
243
+ blendAlphaOperation: 'add',
244
+ blendAlphaSrcFactor: 'one',
245
+ blendAlphaDstFactor: 'one-minus-src-alpha',
246
+ depthWriteEnabled: false,
247
+ depthCompare: 'always',
248
+ },
249
+ })
250
+ }
251
+
252
+ if (!this.hoveredLineIndexCommand) {
253
+ // Initialize quad buffer for full-screen rendering
254
+ if (!this.quadBuffer) {
255
+ this.quadBuffer = device.createBuffer({
256
+ data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
257
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
258
+ })
259
+ }
260
+
261
+ if (!this.hoveredLineIndexUniformStore) {
262
+ this.hoveredLineIndexUniformStore = new UniformStore({
263
+ hoveredLineIndexUniforms: {
264
+ uniformTypes: {
265
+ mousePosition: 'vec2<f32>',
266
+ screenSize: 'vec2<f32>',
267
+ },
268
+ defaultUniforms: {
269
+ mousePosition: store.screenMousePosition ?? [0, 0],
270
+ screenSize: store.screenSize ?? [0, 0],
271
+ },
272
+ },
273
+ })
274
+ }
275
+
276
+ this.hoveredLineIndexCommand = new Model(device, {
277
+ vs: hoveredLineIndexVert,
278
+ fs: hoveredLineIndexFrag,
279
+ topology: 'triangle-strip',
280
+ vertexCount: 4,
281
+ attributes: {
282
+ vertexCoord: this.quadBuffer,
283
+ },
284
+ bufferLayout: [
285
+ { name: 'vertexCoord', format: 'float32x2' },
286
+ ],
287
+ defines: {
288
+ USE_UNIFORM_BUFFERS: true,
289
+ },
290
+ bindings: {
291
+ hoveredLineIndexUniforms: this.hoveredLineIndexUniformStore.getManagedUniformBuffer(device, 'hoveredLineIndexUniforms'),
292
+ ...(this.linkIndexTexture && { linkIndexTexture: this.linkIndexTexture }),
293
+ },
294
+ })
295
+ }
296
+ }
297
+
298
+ public draw (renderPass: RenderPass): void {
299
+ if (!this.pointABuffer || !this.pointBBuffer) return
300
+ if (!this.colorBuffer) this.updateColor()
301
+ if (!this.widthBuffer) this.updateWidth()
302
+ if (!this.arrowBuffer) this.updateArrow()
303
+ if (!this.curveLineGeometry) this.updateCurveLineGeometry()
304
+ if (!this.drawCurveCommand || !this.drawLineUniformStore) return
305
+
306
+ // Update uniforms
307
+ const { config, store } = this
308
+ this.drawLineUniformStore.setUniforms({
309
+ drawLineUniforms: {
310
+ transformationMatrix: store.transformationMatrix4x4,
311
+ pointsTextureSize: store.pointsTextureSize,
312
+ widthScale: config.linkWidthScale ?? 1,
313
+ linkArrowsSizeScale: config.linkArrowsSizeScale ?? 1,
314
+ spaceSize: store.adjustedSpaceSize ?? 0,
315
+ screenSize: store.screenSize ?? [0, 0],
316
+ linkVisibilityDistanceRange: config.linkVisibilityDistanceRange ?? [0, 0],
317
+ linkVisibilityMinTransparency: config.linkVisibilityMinTransparency ?? 0,
318
+ linkOpacity: config.linkOpacity ?? 1,
319
+ greyoutOpacity: config.linkGreyoutOpacity ?? 1,
320
+ curvedWeight: config.curvedLinkWeight ?? 0,
321
+ curvedLinkControlPointDistance: config.curvedLinkControlPointDistance ?? 0,
322
+ curvedLinkSegments: config.curvedLinks ? config.curvedLinkSegments ?? defaultConfigValues.curvedLinkSegments : 1,
323
+ scaleLinksOnZoom: (config.scaleLinksOnZoom ?? true) ? 1 : 0,
324
+ maxPointSize: store.maxPointSize ?? 100,
325
+ renderMode: 0.0, // Normal rendering
326
+ hoveredLinkIndex: store.hoveredLinkIndex ?? -1,
327
+ hoveredLinkColor: store.hoveredLinkColor ?? [-1, -1, -1, -1],
328
+ hoveredLinkWidthIncrease: config.hoveredLinkWidthIncrease ?? 0,
329
+ },
330
+ drawLineFragmentUniforms: {
331
+ renderMode: 0.0, // Normal rendering
332
+ },
333
+ })
334
+
335
+ // Update bindings dynamically (use textures directly from points module)
336
+ this.drawCurveCommand.setBindings({
337
+ drawLineUniforms: this.drawLineUniformStore.getManagedUniformBuffer(this.device, 'drawLineUniforms'),
338
+ drawLineFragmentUniforms: this.drawLineUniformStore.getManagedUniformBuffer(this.device, 'drawLineFragmentUniforms'),
339
+ ...(this.points?.currentPositionTexture && { positionsTexture: this.points.currentPositionTexture }),
340
+ ...(this.points?.greyoutStatusFbo?.colorAttachments[0] && { pointGreyoutStatus: this.points.greyoutStatusFbo.colorAttachments[0] }),
341
+ })
342
+
343
+ // Update instance count
344
+ this.drawCurveCommand.setInstanceCount(this.data.linksNumber ?? 0)
345
+
346
+ // Render normal links
347
+ this.drawCurveCommand.draw(renderPass)
348
+ }
349
+
350
+ public updateLinkIndexFbo (): void {
351
+ const { device, store } = this
352
+
353
+ // Only create and update the link index FBO if link hovering is enabled
354
+ if (!this.store.isLinkHoveringEnabled) return
355
+
356
+ const screenSize = store.screenSize ?? [0, 0]
357
+ const screenWidth = screenSize[0]
358
+ const screenHeight = screenSize[1]
359
+
360
+ // Avoid invalid uploads when size is zero
361
+ if (!screenWidth || !screenHeight) return
362
+
363
+ // Check if screen size changed
364
+ const screenSizeChanged =
365
+ this.previousScreenSize?.[0] !== screenWidth ||
366
+ this.previousScreenSize?.[1] !== screenHeight
367
+
368
+ if (!this.linkIndexTexture || screenSizeChanged) {
369
+ // Destroy old framebuffer and texture if they exist
370
+ if (this.linkIndexFbo && !this.linkIndexFbo.destroyed) {
371
+ this.linkIndexFbo.destroy()
372
+ }
373
+ if (this.linkIndexTexture && !this.linkIndexTexture.destroyed) {
374
+ this.linkIndexTexture.destroy()
375
+ }
376
+
377
+ // Create new texture
378
+ this.linkIndexTexture = device.createTexture({
379
+ width: screenWidth,
380
+ height: screenHeight,
381
+ format: 'rgba32float',
382
+ usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_DST,
383
+ })
384
+ this.linkIndexTexture.copyImageData({
385
+ data: new Float32Array(screenWidth * screenHeight * 4).fill(0),
386
+ bytesPerRow: screenWidth,
387
+ mipLevel: 0,
388
+ x: 0,
389
+ y: 0,
390
+ })
391
+
392
+ // Create new framebuffer
393
+ this.linkIndexFbo = device.createFramebuffer({
394
+ width: screenWidth,
395
+ height: screenHeight,
396
+ colorAttachments: [this.linkIndexTexture],
397
+ })
398
+
399
+ this.previousScreenSize = [screenWidth, screenHeight]
400
+ }
401
+ }
402
+
403
+ public updatePointsBuffer (): void {
404
+ const { device, data, store } = this
405
+ if (data.linksNumber === undefined || data.links === undefined) return
406
+
407
+ // Create separate buffers for pointA and pointB
408
+ const pointAData = new Float32Array(data.linksNumber * 2)
409
+ const pointBData = new Float32Array(data.linksNumber * 2)
410
+
411
+ for (let i = 0; i < data.linksNumber; i++) {
412
+ const fromIndex = data.links[i * 2] as number
413
+ const toIndex = data.links[i * 2 + 1] as number
414
+ const fromX = fromIndex % store.pointsTextureSize
415
+ const fromY = Math.floor(fromIndex / store.pointsTextureSize)
416
+ const toX = toIndex % store.pointsTextureSize
417
+ const toY = Math.floor(toIndex / store.pointsTextureSize)
418
+
419
+ pointAData[i * 2] = fromX
420
+ pointAData[i * 2 + 1] = fromY
421
+ pointBData[i * 2] = toX
422
+ pointBData[i * 2 + 1] = toY
423
+ }
424
+
425
+ // Check if buffer needs to be resized (buffers can't be resized, need to recreate)
426
+ const currentSize = (this.pointABuffer?.byteLength ?? 0) / (Float32Array.BYTES_PER_ELEMENT * 2)
427
+ if (!this.pointABuffer || currentSize !== data.linksNumber) {
428
+ if (this.pointABuffer && !this.pointABuffer.destroyed) {
429
+ this.pointABuffer.destroy()
430
+ }
431
+ this.pointABuffer = device.createBuffer({
432
+ data: pointAData,
433
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
434
+ })
435
+ // Note: Model attributes are set at creation time, so if Model exists and buffer is recreated,
436
+ // the Model will need to be recreated too. For now, we ensure buffers exist before initPrograms.
437
+ } else {
438
+ this.pointABuffer.write(pointAData)
439
+ }
440
+
441
+ if (!this.pointBBuffer || currentSize !== data.linksNumber) {
442
+ if (this.pointBBuffer && !this.pointBBuffer.destroyed) {
443
+ this.pointBBuffer.destroy()
444
+ }
445
+ this.pointBBuffer = device.createBuffer({
446
+ data: pointBData,
447
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
448
+ })
449
+ } else {
450
+ this.pointBBuffer.write(pointBData)
451
+ }
452
+
453
+ const linkIndices = new Float32Array(data.linksNumber)
454
+ for (let i = 0; i < data.linksNumber; i++) {
455
+ linkIndices[i] = i
456
+ }
457
+ if (!this.linkIndexBuffer || currentSize !== data.linksNumber) {
458
+ if (this.linkIndexBuffer && !this.linkIndexBuffer.destroyed) {
459
+ this.linkIndexBuffer.destroy()
460
+ }
461
+ this.linkIndexBuffer = device.createBuffer({
462
+ data: linkIndices,
463
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
464
+ })
465
+ } else {
466
+ this.linkIndexBuffer.write(linkIndices)
467
+ }
468
+ }
469
+
470
+ public updateColor (): void {
471
+ const { device, data } = this
472
+ const linksNumber = data.linksNumber ?? 0
473
+ const colorData = data.linkColors ?? new Float32Array(linksNumber * 4).fill(0)
474
+
475
+ if (!this.colorBuffer) {
476
+ this.colorBuffer = device.createBuffer({
477
+ data: colorData,
478
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
479
+ })
480
+ } else {
481
+ // Check if buffer needs to be resized
482
+ const currentSize = (this.colorBuffer.byteLength ?? 0) / (Float32Array.BYTES_PER_ELEMENT * 4)
483
+ if (currentSize !== linksNumber) {
484
+ if (this.colorBuffer && !this.colorBuffer.destroyed) {
485
+ this.colorBuffer.destroy()
486
+ }
487
+ this.colorBuffer = device.createBuffer({
488
+ data: colorData,
489
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
490
+ })
491
+ } else {
492
+ this.colorBuffer.write(colorData)
493
+ }
494
+ }
495
+ }
496
+
497
+ public updateWidth (): void {
498
+ const { device, data } = this
499
+ const linksNumber = data.linksNumber ?? 0
500
+ const widthData = data.linkWidths ?? new Float32Array(linksNumber).fill(0)
501
+
502
+ if (!this.widthBuffer) {
503
+ this.widthBuffer = device.createBuffer({
504
+ data: widthData,
505
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
506
+ })
507
+ } else {
508
+ // Check if buffer needs to be resized
509
+ const currentSize = (this.widthBuffer.byteLength ?? 0) / Float32Array.BYTES_PER_ELEMENT
510
+ if (currentSize !== linksNumber) {
511
+ if (this.widthBuffer && !this.widthBuffer.destroyed) {
512
+ this.widthBuffer.destroy()
513
+ }
514
+ this.widthBuffer = device.createBuffer({
515
+ data: widthData,
516
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
517
+ })
518
+ } else {
519
+ this.widthBuffer.write(widthData)
520
+ }
521
+ }
522
+ }
523
+
524
+ public updateArrow (): void {
525
+ const { device, data } = this
526
+ // linkArrows is number[] not Float32Array, so we need to convert it
527
+ // Ensure we have the right size even if linkArrows is undefined
528
+ const linksNumber = data.linksNumber ?? 0
529
+ const arrowData = data.linkArrows
530
+ ? new Float32Array(data.linkArrows)
531
+ : new Float32Array(linksNumber).fill(0)
532
+
533
+ if (!this.arrowBuffer) {
534
+ this.arrowBuffer = device.createBuffer({
535
+ data: arrowData,
536
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
537
+ })
538
+ } else {
539
+ // Check if buffer needs to be resized
540
+ const currentSize = (this.arrowBuffer.byteLength ?? 0) / Float32Array.BYTES_PER_ELEMENT
541
+ if (currentSize !== linksNumber) {
542
+ if (this.arrowBuffer && !this.arrowBuffer.destroyed) {
543
+ this.arrowBuffer.destroy()
544
+ }
545
+ this.arrowBuffer = device.createBuffer({
546
+ data: arrowData,
547
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
548
+ })
549
+ } else {
550
+ this.arrowBuffer.write(arrowData)
551
+ }
552
+ }
553
+ }
554
+
555
+ public updateCurveLineGeometry (): void {
556
+ const { device, config: { curvedLinks, curvedLinkSegments } } = this
557
+ this.curveLineGeometry = getCurveLineGeometry(curvedLinks ? curvedLinkSegments ?? defaultConfigValues.curvedLinkSegments : 1)
558
+
559
+ // Flatten the 2D array to 1D
560
+ const flatGeometry = new Float32Array(this.curveLineGeometry.length * 2)
561
+ for (let i = 0; i < this.curveLineGeometry.length; i++) {
562
+ flatGeometry[i * 2] = this.curveLineGeometry[i]![0]!
563
+ flatGeometry[i * 2 + 1] = this.curveLineGeometry[i]![1]!
564
+ }
565
+
566
+ if (!this.curveLineBuffer) {
567
+ this.curveLineBuffer = device.createBuffer({
568
+ data: flatGeometry,
569
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
570
+ })
571
+ } else {
572
+ this.curveLineBuffer.write(flatGeometry)
573
+ }
574
+
575
+ // Update vertex count in model if it exists
576
+ if (this.drawCurveCommand) {
577
+ this.drawCurveCommand.setVertexCount(this.curveLineGeometry.length)
578
+ }
579
+ }
580
+
581
+ public findHoveredLine (): void {
582
+ if (!this.data.linksNumber || !this.store.isLinkHoveringEnabled) return
583
+ if (!this.linkIndexFbo || !this.drawCurveCommand || !this.drawLineUniformStore) return
584
+
585
+ // Update uniforms for index rendering
586
+ const { config, store } = this
587
+ this.drawLineUniformStore.setUniforms({
588
+ drawLineUniforms: {
589
+ transformationMatrix: store.transformationMatrix4x4,
590
+ pointsTextureSize: store.pointsTextureSize,
591
+ widthScale: config.linkWidthScale ?? 1,
592
+ linkArrowsSizeScale: config.linkArrowsSizeScale ?? 1,
593
+ spaceSize: store.adjustedSpaceSize ?? 0,
594
+ screenSize: store.screenSize ?? [0, 0],
595
+ linkVisibilityDistanceRange: config.linkVisibilityDistanceRange ?? [0, 0],
596
+ linkVisibilityMinTransparency: config.linkVisibilityMinTransparency ?? 0,
597
+ linkOpacity: config.linkOpacity ?? 1,
598
+ greyoutOpacity: config.linkGreyoutOpacity ?? 1,
599
+ curvedWeight: config.curvedLinkWeight ?? 0,
600
+ curvedLinkControlPointDistance: config.curvedLinkControlPointDistance ?? 0,
601
+ curvedLinkSegments: config.curvedLinks ? config.curvedLinkSegments ?? defaultConfigValues.curvedLinkSegments : 1,
602
+ scaleLinksOnZoom: (config.scaleLinksOnZoom ?? true) ? 1 : 0,
603
+ maxPointSize: store.maxPointSize ?? 100,
604
+ renderMode: 1.0, // Index rendering for picking
605
+ hoveredLinkIndex: store.hoveredLinkIndex ?? -1,
606
+ hoveredLinkColor: store.hoveredLinkColor ?? [-1, -1, -1, -1],
607
+ hoveredLinkWidthIncrease: config.hoveredLinkWidthIncrease ?? 0,
608
+ },
609
+ drawLineFragmentUniforms: {
610
+ renderMode: 1.0, // Index rendering for picking
611
+ },
612
+ })
613
+
614
+ this.drawCurveCommand.setBindings({
615
+ drawLineUniforms: this.drawLineUniformStore.getManagedUniformBuffer(this.device, 'drawLineUniforms'),
616
+ drawLineFragmentUniforms: this.drawLineUniformStore.getManagedUniformBuffer(this.device, 'drawLineFragmentUniforms'),
617
+ ...(this.points?.currentPositionTexture && { positionsTexture: this.points.currentPositionTexture }),
618
+ ...(this.points?.greyoutStatusFbo?.colorAttachments[0] && { pointGreyoutStatus: this.points.greyoutStatusFbo.colorAttachments[0] }),
619
+ })
620
+
621
+ // Update instance count
622
+ this.drawCurveCommand.setInstanceCount(this.data.linksNumber ?? 0)
623
+
624
+ // Clear and render to index buffer
625
+ const clearPass = this.device.beginRenderPass({
626
+ framebuffer: this.linkIndexFbo,
627
+ clearColor: [0, 0, 0, 0],
628
+ })
629
+ clearPass.end()
630
+
631
+ // Render to index buffer for picking/hover detection
632
+ const indexPass = this.device.beginRenderPass({
633
+ framebuffer: this.linkIndexFbo,
634
+ // Note: We explicitly set clearColor to [0, 0, 0, 0] to prevent re-clearing the framebuffer.
635
+ // Without this, luma.gl would use its default clearColor of [0, 0, 0, 1] (opaque black)
636
+ clearColor: [0, 0, 0, 0], // or `clearColor: false`
637
+ })
638
+ this.drawCurveCommand.draw(indexPass)
639
+ indexPass.end()
640
+
641
+ if (this.hoveredLineIndexCommand && this.hoveredLineIndexFbo && this.hoveredLineIndexUniformStore) {
642
+ this.hoveredLineIndexUniformStore.setUniforms({
643
+ hoveredLineIndexUniforms: {
644
+ mousePosition: store.screenMousePosition ?? [0, 0],
645
+ screenSize: store.screenSize ?? [0, 0],
646
+ },
647
+ })
648
+
649
+ this.hoveredLineIndexCommand.setBindings({
650
+ hoveredLineIndexUniforms: this.hoveredLineIndexUniformStore.getManagedUniformBuffer(this.device, 'hoveredLineIndexUniforms'),
651
+ ...(this.linkIndexTexture && { linkIndexTexture: this.linkIndexTexture }),
652
+ })
653
+
654
+ const hoverPass = this.device.beginRenderPass({
655
+ framebuffer: this.hoveredLineIndexFbo,
656
+ })
657
+ this.hoveredLineIndexCommand.draw(hoverPass)
658
+ hoverPass.end()
659
+ }
660
+ }
661
+ }