@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,525 @@
1
+ import { Buffer, Framebuffer, RenderPass, Texture, UniformStore } from '@luma.gl/core'
2
+ import { Model } from '@luma.gl/engine'
3
+ import { CoreModule } from '@/graph/modules/core-module'
4
+ import calculateLevelFrag from '@/graph/modules/ForceManyBody/calculate-level.frag?raw'
5
+ import calculateLevelVert from '@/graph/modules/ForceManyBody/calculate-level.vert?raw'
6
+ import forceFrag from '@/graph/modules/ForceManyBody/force-level.frag?raw'
7
+ import forceCenterFrag from '@/graph/modules/ForceManyBody/force-centermass.frag?raw'
8
+ import { createIndexesForBuffer } from '@/graph/modules/Shared/buffer'
9
+ import clearFrag from '@/graph/modules/Shared/clear.frag?raw'
10
+ import updateVert from '@/graph/modules/Shared/quad.vert?raw'
11
+
12
+ type LevelTarget = {
13
+ texture: Texture;
14
+ fbo: Framebuffer;
15
+ }
16
+
17
+ export class ForceManyBody extends CoreModule {
18
+ private randomValuesTexture: Texture | undefined
19
+ private pointIndices: Buffer | undefined
20
+ private levels = 0
21
+ private levelTargets = new Map<number, LevelTarget>()
22
+
23
+ private clearLevelsCommand: Model | undefined
24
+ private calculateLevelsCommand: Model | undefined
25
+ private forceCommand: Model | undefined
26
+ private forceFromItsOwnCentermassCommand: Model | undefined
27
+
28
+ private clearLevelsVertexCoordBuffer: Buffer | undefined
29
+ private forceVertexCoordBuffer: Buffer | undefined
30
+
31
+ private calculateLevelsUniformStore: UniformStore<{
32
+ calculateLevelsUniforms: {
33
+ pointsTextureSize: number;
34
+ levelTextureSize: number;
35
+ cellSize: number;
36
+ };
37
+ }> | undefined
38
+
39
+ private forceUniformStore: UniformStore<{
40
+ forceUniforms: {
41
+ level: number;
42
+ levels: number;
43
+ levelTextureSize: number;
44
+ alpha: number;
45
+ repulsion: number;
46
+ spaceSize: number;
47
+ theta: number;
48
+ };
49
+ }> | undefined
50
+
51
+ private forceCenterUniformStore: UniformStore<{
52
+ forceCenterUniforms: {
53
+ levelTextureSize: number;
54
+ alpha: number;
55
+ repulsion: number;
56
+ };
57
+ }> | undefined
58
+
59
+ private previousPointsTextureSize: number | undefined
60
+ private previousSpaceSize: number | undefined
61
+
62
+ public create (): void {
63
+ const { device, store } = this
64
+ if (!store.pointsTextureSize) return
65
+
66
+ this.levels = Math.log2(store.adjustedSpaceSize)
67
+
68
+ // Allocate quadtree levels
69
+ for (let level = 0; level < this.levels; level += 1) {
70
+ const levelTextureSize = Math.pow(2, level + 1)
71
+ const existingTarget = this.levelTargets.get(level)
72
+
73
+ if (
74
+ existingTarget &&
75
+ existingTarget.texture.width === levelTextureSize &&
76
+ existingTarget.texture.height === levelTextureSize
77
+ ) {
78
+ // Clear existing texture data to zero
79
+ existingTarget.texture.copyImageData({
80
+ data: new Float32Array(levelTextureSize * levelTextureSize * 4).fill(0),
81
+ bytesPerRow: levelTextureSize,
82
+ mipLevel: 0,
83
+ x: 0,
84
+ y: 0,
85
+ })
86
+ continue
87
+ }
88
+
89
+ // Destroy old resources if size changed
90
+ if (existingTarget) {
91
+ if (!existingTarget.texture.destroyed) existingTarget.texture.destroy()
92
+ if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy()
93
+ }
94
+
95
+ const texture = device.createTexture({
96
+ width: levelTextureSize,
97
+ height: levelTextureSize,
98
+ format: 'rgba32float',
99
+ usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_DST,
100
+ })
101
+ texture.copyImageData({
102
+ data: new Float32Array(levelTextureSize * levelTextureSize * 4).fill(0),
103
+ bytesPerRow: levelTextureSize,
104
+ mipLevel: 0,
105
+ x: 0,
106
+ y: 0,
107
+ })
108
+ const fbo = device.createFramebuffer({
109
+ width: levelTextureSize,
110
+ height: levelTextureSize,
111
+ colorAttachments: [texture],
112
+ })
113
+ this.levelTargets.set(level, { texture, fbo })
114
+ }
115
+
116
+ // Drop any stale higher-level buffers if space size shrank
117
+ for (const [level, target] of Array.from(this.levelTargets.entries())) {
118
+ if (level >= this.levels) {
119
+ if (!target.texture.destroyed) target.texture.destroy()
120
+ if (!target.fbo.destroyed) target.fbo.destroy()
121
+ this.levelTargets.delete(level)
122
+ }
123
+ }
124
+
125
+ // Random jitter texture to prevent sticking
126
+ const totalPixels = store.pointsTextureSize * store.pointsTextureSize
127
+ const randomValuesState = new Float32Array(totalPixels * 4)
128
+ for (let i = 0; i < totalPixels; ++i) {
129
+ randomValuesState[i * 4] = store.getRandomFloat(-1, 1) * 0.00001
130
+ randomValuesState[i * 4 + 1] = store.getRandomFloat(-1, 1) * 0.00001
131
+ }
132
+
133
+ if (!this.randomValuesTexture || this.randomValuesTexture.destroyed) {
134
+ this.randomValuesTexture = device.createTexture({
135
+ width: store.pointsTextureSize,
136
+ height: store.pointsTextureSize,
137
+ format: 'rgba32float',
138
+ usage: Texture.SAMPLE | Texture.COPY_DST,
139
+ })
140
+ }
141
+ this.randomValuesTexture.copyImageData({
142
+ data: randomValuesState,
143
+ bytesPerRow: store.pointsTextureSize,
144
+ mipLevel: 0,
145
+ x: 0,
146
+ y: 0,
147
+ })
148
+
149
+ // Point index buffer
150
+ const indexData = createIndexesForBuffer(store.pointsTextureSize)
151
+ const requiredByteLength = indexData.byteLength
152
+ if (!this.pointIndices || this.pointIndices.byteLength !== requiredByteLength) {
153
+ this.pointIndices?.destroy()
154
+ this.pointIndices = device.createBuffer({
155
+ data: indexData,
156
+ usage: Buffer.VERTEX | Buffer.COPY_DST,
157
+ })
158
+ } else {
159
+ this.pointIndices.write(indexData)
160
+ }
161
+
162
+ this.previousPointsTextureSize = store.pointsTextureSize
163
+ this.previousSpaceSize = store.adjustedSpaceSize
164
+ }
165
+
166
+ public initPrograms (): void {
167
+ const { device, store, data, points } = this
168
+ if (!data.pointsNumber || !points || !store.pointsTextureSize) return
169
+
170
+ // Clear levels command (fullscreen quad)
171
+ if (!this.clearLevelsCommand) {
172
+ if (!this.clearLevelsVertexCoordBuffer) {
173
+ this.clearLevelsVertexCoordBuffer = device.createBuffer({
174
+ data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
175
+ })
176
+ }
177
+ this.clearLevelsCommand = new Model(device, {
178
+ fs: clearFrag,
179
+ vs: updateVert,
180
+ topology: 'triangle-strip',
181
+ vertexCount: 4,
182
+ attributes: {
183
+ vertexCoord: this.clearLevelsVertexCoordBuffer,
184
+ },
185
+ bufferLayout: [
186
+ { name: 'vertexCoord', format: 'float32x2' },
187
+ ],
188
+ })
189
+ }
190
+
191
+ // Calculate levels command (point list)
192
+ if (!this.calculateLevelsCommand) {
193
+ if (!this.calculateLevelsUniformStore) {
194
+ this.calculateLevelsUniformStore = new UniformStore({
195
+ calculateLevelsUniforms: {
196
+ uniformTypes: {
197
+ pointsTextureSize: 'f32',
198
+ levelTextureSize: 'f32',
199
+ cellSize: 'f32',
200
+ },
201
+ defaultUniforms: {
202
+ pointsTextureSize: store.pointsTextureSize,
203
+ levelTextureSize: 0,
204
+ cellSize: 0,
205
+ },
206
+ },
207
+ })
208
+ }
209
+
210
+ this.calculateLevelsCommand = new Model(device, {
211
+ fs: calculateLevelFrag,
212
+ vs: calculateLevelVert,
213
+ topology: 'point-list',
214
+ vertexCount: data.pointsNumber,
215
+ attributes: {
216
+ pointIndices: this.pointIndices!,
217
+ },
218
+ bufferLayout: [
219
+ { name: 'pointIndices', format: 'float32x2' },
220
+ ],
221
+ defines: {
222
+ USE_UNIFORM_BUFFERS: true,
223
+ },
224
+ bindings: {
225
+ calculateLevelsUniforms: this.calculateLevelsUniformStore.getManagedUniformBuffer(device, 'calculateLevelsUniforms'),
226
+ positionsTexture: points.previousPositionTexture!,
227
+ },
228
+ parameters: {
229
+ blend: true,
230
+ blendColorOperation: 'add',
231
+ blendColorSrcFactor: 'one',
232
+ blendColorDstFactor: 'one',
233
+ blendAlphaOperation: 'add',
234
+ blendAlphaSrcFactor: 'one',
235
+ blendAlphaDstFactor: 'one',
236
+ depthWriteEnabled: false,
237
+ depthCompare: 'always',
238
+ },
239
+ })
240
+ }
241
+
242
+ // Force command (fullscreen quad)
243
+ if (!this.forceCommand) {
244
+ if (!this.forceUniformStore) {
245
+ this.forceUniformStore = new UniformStore({
246
+ forceUniforms: {
247
+ uniformTypes: {
248
+ level: 'f32',
249
+ levels: 'f32',
250
+ levelTextureSize: 'f32',
251
+ alpha: 'f32',
252
+ repulsion: 'f32',
253
+ spaceSize: 'f32',
254
+ theta: 'f32',
255
+ },
256
+ defaultUniforms: {
257
+ level: 0,
258
+ levels: this.levels,
259
+ levelTextureSize: 0,
260
+ alpha: store.alpha,
261
+ repulsion: this.config.simulationRepulsion ?? 0,
262
+ spaceSize: store.adjustedSpaceSize ?? 0,
263
+ theta: this.config.simulationRepulsionTheta ?? 0,
264
+ },
265
+ },
266
+ })
267
+ }
268
+
269
+ if (!this.forceVertexCoordBuffer) {
270
+ this.forceVertexCoordBuffer = device.createBuffer({
271
+ data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
272
+ })
273
+ }
274
+
275
+ this.forceCommand = new Model(device, {
276
+ fs: forceFrag,
277
+ vs: updateVert,
278
+ topology: 'triangle-strip',
279
+ vertexCount: 4,
280
+ attributes: {
281
+ vertexCoord: this.forceVertexCoordBuffer,
282
+ },
283
+ bufferLayout: [
284
+ { name: 'vertexCoord', format: 'float32x2' },
285
+ ],
286
+ defines: {
287
+ USE_UNIFORM_BUFFERS: true,
288
+ },
289
+ bindings: {
290
+ forceUniforms: this.forceUniformStore.getManagedUniformBuffer(device, 'forceUniforms'),
291
+ positionsTexture: points.previousPositionTexture!,
292
+ },
293
+ parameters: {
294
+ blend: true,
295
+ blendColorOperation: 'add',
296
+ blendColorSrcFactor: 'one',
297
+ blendColorDstFactor: 'one',
298
+ blendAlphaOperation: 'add',
299
+ blendAlphaSrcFactor: 'one',
300
+ blendAlphaDstFactor: 'one',
301
+ depthWriteEnabled: false,
302
+ depthCompare: 'always',
303
+ },
304
+ })
305
+ }
306
+
307
+ // Force-from-centermass command (fullscreen quad)
308
+ if (!this.forceFromItsOwnCentermassCommand) {
309
+ if (!this.forceCenterUniformStore) {
310
+ this.forceCenterUniformStore = new UniformStore({
311
+ forceCenterUniforms: {
312
+ uniformTypes: {
313
+ levelTextureSize: 'f32',
314
+ alpha: 'f32',
315
+ repulsion: 'f32',
316
+ },
317
+ defaultUniforms: {
318
+ levelTextureSize: 0,
319
+ alpha: store.alpha,
320
+ repulsion: this.config.simulationRepulsion ?? 0,
321
+ },
322
+ },
323
+ })
324
+ }
325
+
326
+ if (!this.forceVertexCoordBuffer) {
327
+ this.forceVertexCoordBuffer = device.createBuffer({
328
+ data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
329
+ })
330
+ }
331
+
332
+ this.forceFromItsOwnCentermassCommand = new Model(device, {
333
+ fs: forceCenterFrag,
334
+ vs: updateVert,
335
+ topology: 'triangle-strip',
336
+ vertexCount: 4,
337
+ attributes: {
338
+ vertexCoord: this.forceVertexCoordBuffer,
339
+ },
340
+ bufferLayout: [
341
+ { name: 'vertexCoord', format: 'float32x2' },
342
+ ],
343
+ defines: {
344
+ USE_UNIFORM_BUFFERS: true,
345
+ },
346
+ bindings: {
347
+ forceCenterUniforms: this.forceCenterUniformStore.getManagedUniformBuffer(device, 'forceCenterUniforms'),
348
+ positionsTexture: points.previousPositionTexture!,
349
+ randomValues: this.randomValuesTexture!,
350
+ },
351
+ parameters: {
352
+ blend: true,
353
+ blendColorOperation: 'add',
354
+ blendColorSrcFactor: 'one',
355
+ blendColorDstFactor: 'one',
356
+ blendAlphaOperation: 'add',
357
+ blendAlphaSrcFactor: 'one',
358
+ blendAlphaDstFactor: 'one',
359
+ depthWriteEnabled: false,
360
+ depthCompare: 'always',
361
+ },
362
+ })
363
+ }
364
+ }
365
+
366
+ public run (renderPass?: RenderPass): void {
367
+ // Skip if sizes changed and create() wasn't called yet
368
+ if (this.store.pointsTextureSize !== this.previousPointsTextureSize || this.store.adjustedSpaceSize !== this.previousSpaceSize) {
369
+ return
370
+ }
371
+ this.drawLevels()
372
+ this.drawForces(renderPass)
373
+ }
374
+
375
+ public destroy (): void {
376
+ this.calculateLevelsUniformStore?.destroy()
377
+ this.calculateLevelsUniformStore = undefined
378
+ this.forceUniformStore?.destroy()
379
+ this.forceUniformStore = undefined
380
+ this.forceCenterUniformStore?.destroy()
381
+ this.forceCenterUniformStore = undefined
382
+
383
+ this.clearLevelsCommand?.destroy()
384
+ this.clearLevelsCommand = undefined
385
+ this.calculateLevelsCommand?.destroy()
386
+ this.calculateLevelsCommand = undefined
387
+ this.forceCommand?.destroy()
388
+ this.forceCommand = undefined
389
+ this.forceFromItsOwnCentermassCommand?.destroy()
390
+ this.forceFromItsOwnCentermassCommand = undefined
391
+
392
+ this.pointIndices?.destroy()
393
+ this.pointIndices = undefined
394
+
395
+ if (this.randomValuesTexture && !this.randomValuesTexture.destroyed) {
396
+ this.randomValuesTexture.destroy()
397
+ }
398
+ this.randomValuesTexture = undefined
399
+
400
+ for (const target of this.levelTargets.values()) {
401
+ if (!target.texture.destroyed) target.texture.destroy()
402
+ if (!target.fbo.destroyed) target.fbo.destroy()
403
+ }
404
+ this.levelTargets.clear()
405
+
406
+ if (this.clearLevelsVertexCoordBuffer && !this.clearLevelsVertexCoordBuffer.destroyed) {
407
+ this.clearLevelsVertexCoordBuffer.destroy()
408
+ }
409
+ this.clearLevelsVertexCoordBuffer = undefined
410
+
411
+ if (this.forceVertexCoordBuffer && !this.forceVertexCoordBuffer.destroyed) {
412
+ this.forceVertexCoordBuffer.destroy()
413
+ }
414
+ this.forceVertexCoordBuffer = undefined
415
+ }
416
+
417
+ private drawLevels (): void {
418
+ const { device, store, data, points } = this
419
+ if (!points || !data.pointsNumber || !this.calculateLevelsCommand || !this.calculateLevelsUniformStore || !this.clearLevelsCommand) return
420
+ if (!points.previousPositionTexture || points.previousPositionTexture.destroyed) return
421
+
422
+ for (let level = 0; level < this.levels; level += 1) {
423
+ const target = this.levelTargets.get(level)
424
+ if (!target || target.fbo.destroyed || target.texture.destroyed) continue
425
+
426
+ const levelTextureSize = Math.pow(2, level + 1)
427
+ const cellSize = (store.adjustedSpaceSize ?? 0) / levelTextureSize
428
+
429
+ this.calculateLevelsUniformStore.setUniforms({
430
+ calculateLevelsUniforms: {
431
+ pointsTextureSize: store.pointsTextureSize ?? 0,
432
+ levelTextureSize,
433
+ cellSize,
434
+ },
435
+ })
436
+
437
+ this.calculateLevelsCommand.setVertexCount(data.pointsNumber)
438
+ this.calculateLevelsCommand.setBindings({
439
+ calculateLevelsUniforms: this.calculateLevelsUniformStore.getManagedUniformBuffer(device, 'calculateLevelsUniforms'),
440
+ positionsTexture: points.previousPositionTexture!,
441
+ })
442
+
443
+ const levelPass = device.beginRenderPass({
444
+ framebuffer: target.fbo,
445
+ clearColor: [0, 0, 0, 0],
446
+ })
447
+
448
+ this.clearLevelsCommand.draw(levelPass)
449
+ this.calculateLevelsCommand.draw(levelPass)
450
+
451
+ levelPass.end()
452
+ }
453
+ }
454
+
455
+ private drawForces (renderPass?: RenderPass): void {
456
+ const { device, store, points } = this
457
+ if (!points || !this.forceCommand || !this.forceUniformStore || !this.forceFromItsOwnCentermassCommand || !this.forceCenterUniformStore) return
458
+ if (!points.previousPositionTexture || points.previousPositionTexture.destroyed) return
459
+ if (!this.randomValuesTexture || this.randomValuesTexture.destroyed) return
460
+ if (!renderPass && (!points.velocityFbo || points.velocityFbo.destroyed)) return
461
+
462
+ // Update bindings shared for both force commands
463
+ this.forceCommand.setBindings({
464
+ forceUniforms: this.forceUniformStore.getManagedUniformBuffer(device, 'forceUniforms'),
465
+ positionsTexture: points.previousPositionTexture!,
466
+ })
467
+ this.forceFromItsOwnCentermassCommand.setBindings({
468
+ forceCenterUniforms: this.forceCenterUniformStore.getManagedUniformBuffer(device, 'forceCenterUniforms'),
469
+ positionsTexture: points.previousPositionTexture!,
470
+ randomValues: this.randomValuesTexture!,
471
+ })
472
+
473
+ const drawPass = renderPass ?? device.beginRenderPass({
474
+ framebuffer: points.velocityFbo,
475
+ })
476
+
477
+ for (let level = 0; level < this.levels; level += 1) {
478
+ const target = this.levelTargets.get(level)
479
+ if (!target || target.texture.destroyed) continue
480
+ const levelTextureSize = Math.pow(2, level + 1)
481
+
482
+ this.forceUniformStore.setUniforms({
483
+ forceUniforms: {
484
+ level,
485
+ levels: this.levels,
486
+ levelTextureSize,
487
+ alpha: store.alpha,
488
+ repulsion: this.config.simulationRepulsion ?? 0,
489
+ spaceSize: store.adjustedSpaceSize ?? 0,
490
+ theta: this.config.simulationRepulsionTheta ?? 0,
491
+ },
492
+ })
493
+
494
+ this.forceCommand.setBindings({
495
+ forceUniforms: this.forceUniformStore.getManagedUniformBuffer(device, 'forceUniforms'),
496
+ positionsTexture: points.previousPositionTexture!,
497
+ levelFbo: target.texture,
498
+ })
499
+
500
+ this.forceCommand.draw(drawPass)
501
+
502
+ // Only the deepest level uses the centermass fallback
503
+ if (level === this.levels - 1) {
504
+ this.forceCenterUniformStore.setUniforms({
505
+ forceCenterUniforms: {
506
+ levelTextureSize,
507
+ alpha: store.alpha,
508
+ repulsion: this.config.simulationRepulsion ?? 0,
509
+ },
510
+ })
511
+ this.forceFromItsOwnCentermassCommand.setBindings({
512
+ forceCenterUniforms: this.forceCenterUniformStore.getManagedUniformBuffer(device, 'forceCenterUniforms'),
513
+ positionsTexture: points.previousPositionTexture!,
514
+ randomValues: this.randomValuesTexture!,
515
+ levelFbo: target.texture,
516
+ })
517
+ this.forceFromItsOwnCentermassCommand.draw(drawPass)
518
+ }
519
+ }
520
+
521
+ if (!renderPass) {
522
+ drawPass.end()
523
+ }
524
+ }
525
+ }
@@ -0,0 +1,89 @@
1
+ export function forceFrag (startLevel: number, maxLevels: number): string {
2
+ startLevel = Math.min(startLevel, maxLevels)
3
+ const delta = maxLevels - startLevel
4
+ const calcAdd = `
5
+ float dist = sqrt(l);
6
+ if (dist > 0.0) {
7
+ float c = alpha * repulsion * centermass.b;
8
+ addVelocity += calcAdd(vec2(x, y), l, c);
9
+ addVelocity += addVelocity * random.rg;
10
+ }
11
+ `
12
+ function quad (level: number): string {
13
+ if (level >= maxLevels) {
14
+ return calcAdd
15
+ } else {
16
+ const groupSize = Math.pow(2, level + 1)
17
+
18
+ const iEnding = new Array(level + 1 - delta).fill(0).map((_, l) => `pow(2.0, ${level - (l + delta)}.0) * i${l + delta}`).join('+')
19
+ const jEnding = new Array(level + 1 - delta).fill(0).map((_, l) => `pow(2.0, ${level - (l + delta)}.0) * j${l + delta}`).join('+')
20
+
21
+ return `
22
+ for (float ij${level} = 0.0; ij${level} < 4.0; ij${level} += 1.0) {
23
+ float i${level} = 0.0;
24
+ float j${level} = 0.0;
25
+ if (ij${level} == 1.0 || ij${level} == 3.0) i${level} = 1.0;
26
+ if (ij${level} == 2.0 || ij${level} == 3.0) j${level} = 1.0;
27
+ float i = pow(2.0, ${startLevel}.0) * n / width${level + 1} + ${iEnding};
28
+ float j = pow(2.0, ${startLevel}.0) * m / width${level + 1} + ${jEnding};
29
+ float groupPosX = (i + 0.5) / ${groupSize}.0;
30
+ float groupPosY = (j + 0.5) / ${groupSize}.0;
31
+
32
+ vec4 centermass = texture(level[${level}], vec2(groupPosX, groupPosY));
33
+ if (centermass.r > 0.0 && centermass.g > 0.0 && centermass.b > 0.0) {
34
+ float x = centermass.r / centermass.b - pointPosition.r;
35
+ float y = centermass.g / centermass.b - pointPosition.g;
36
+ float l = x * x + y * y;
37
+ if ((width${level + 1} * width${level + 1}) / theta < l) {
38
+ ${calcAdd}
39
+ } else {
40
+ ${quad(level + 1)}
41
+ }
42
+ }
43
+ }
44
+ `
45
+ }
46
+ }
47
+ return `#version 300 es
48
+ precision highp float;
49
+
50
+ uniform sampler2D positionsTexture;
51
+ uniform sampler2D randomValues;
52
+ uniform float spaceSize;
53
+ uniform float repulsion;
54
+ uniform float theta;
55
+ uniform float alpha;
56
+ uniform sampler2D level[${maxLevels}];
57
+ in vec2 textureCoords;
58
+ out vec4 fragColor;
59
+
60
+ vec2 calcAdd(vec2 xy, float l, float c) {
61
+ float distanceMin2 = 1.0;
62
+ if (l < distanceMin2) l = sqrt(distanceMin2 * l);
63
+ float add = c / l;
64
+ return add * xy;
65
+ }
66
+
67
+ void main() {
68
+ vec4 pointPosition = texture(positionsTexture, textureCoords);
69
+ vec4 random = texture(randomValues, textureCoords);
70
+
71
+ float width0 = spaceSize;
72
+
73
+ vec2 velocity = vec2(0.0);
74
+ vec2 addVelocity = vec2(0.0);
75
+
76
+ ${new Array(maxLevels).fill(0).map((_, i) => `float width${i + 1} = width${i} / 2.0;`).join('\n')}
77
+
78
+ for (float n = 0.0; n < pow(2.0, ${delta}.0); n += 1.0) {
79
+ for (float m = 0.0; m < pow(2.0, ${delta}.0); m += 1.0) {
80
+ ${quad(delta)}
81
+ }
82
+ }
83
+
84
+ velocity -= addVelocity;
85
+
86
+ fragColor = vec4(velocity, 0.0, 0.0);
87
+ }
88
+ `
89
+ }
@@ -0,0 +1,9 @@
1
+ #ifdef GL_ES
2
+ precision highp float;
3
+ #endif
4
+
5
+ varying vec4 rgba;
6
+
7
+ void main() {
8
+ gl_FragColor = rgba;
9
+ }
@@ -0,0 +1,25 @@
1
+ #ifdef GL_ES
2
+ precision highp float;
3
+ #endif
4
+
5
+ uniform sampler2D positionsTexture;
6
+ uniform float pointsTextureSize;
7
+ uniform float levelTextureSize;
8
+ uniform float cellSize;
9
+
10
+ attribute vec2 pointIndices;
11
+
12
+ varying vec4 rgba;
13
+
14
+ void main() {
15
+ vec4 pointPosition = texture2D(positionsTexture, pointIndices / pointsTextureSize);
16
+ rgba = vec4(pointPosition.rg, 1.0, 0.0);
17
+
18
+ float n = floor(pointPosition.x / cellSize);
19
+ float m = floor(pointPosition.y / cellSize);
20
+
21
+ vec2 levelPosition = 2.0 * (vec2(n, m) + 0.5) / levelTextureSize - 1.0;
22
+
23
+ gl_Position = vec4(levelPosition, 0.0, 1.0);
24
+ gl_PointSize = 1.0;
25
+ }