diff --git a/__tests__/data/index.ts b/__tests__/data/index.ts index 3fa25baaa26..9d1e7a503fa 100644 --- a/__tests__/data/index.ts +++ b/__tests__/data/index.ts @@ -1,5 +1,6 @@ import testData from './test-data-1'; import comboTestData from './combo-test-data'; +import smallWorld from './small-world'; export interface TestNode { id: string; @@ -32,4 +33,5 @@ const comboData: TestData = comboTestData; export default { data: typeData, comboData, + smallWorld }; diff --git a/__tests__/data/small-world.ts b/__tests__/data/small-world.ts new file mode 100644 index 00000000000..4dae25ae5cb --- /dev/null +++ b/__tests__/data/small-world.ts @@ -0,0 +1 @@ +export default {"nodes":[{"id":"Myriel"},{"id":"Napoleon"},{"id":"Mlle.Baptistine"},{"id":"Mme.Magloire"},{"id":"CountessdeLo"},{"id":"Geborand"},{"id":"Champtercier"},{"id":"Cravatte"},{"id":"Count"},{"id":"OldMan"},{"id":"Labarre"},{"id":"Valjean"},{"id":"Marguerite"},{"id":"Mme.deR"},{"id":"Isabeau"},{"id":"Gervais"},{"id":"Tholomyes"},{"id":"Listolier"},{"id":"Fameuil"},{"id":"Blacheville"},{"id":"Favourite"},{"id":"Dahlia"},{"id":"Zephine"},{"id":"Fantine"},{"id":"Mme.Thenardier"},{"id":"Thenardier"},{"id":"Cosette"},{"id":"Javert"},{"id":"Fauchelevent"},{"id":"Bamatabois"},{"id":"Perpetue"},{"id":"Simplice"},{"id":"Scaufflaire"},{"id":"Woman1"},{"id":"Judge"},{"id":"Champmathieu"},{"id":"Brevet"},{"id":"Chenildieu"},{"id":"Cochepaille"},{"id":"Pontmercy"},{"id":"Boulatruelle"},{"id":"Eponine"},{"id":"Anzelma"},{"id":"Woman2"},{"id":"MotherInnocent"},{"id":"Gribier"},{"id":"Jondrette"},{"id":"Mme.Burgon"},{"id":"Gavroche"},{"id":"Gillenormand"},{"id":"Magnon"},{"id":"Mlle.Gillenormand"},{"id":"Mme.Pontmercy"},{"id":"Mlle.Vaubois"},{"id":"Lt.Gillenormand"},{"id":"Marius"},{"id":"BaronessT"},{"id":"Mabeuf"},{"id":"Enjolras"},{"id":"Combeferre"},{"id":"Prouvaire"},{"id":"Feuilly"},{"id":"Courfeyrac"},{"id":"Bahorel"},{"id":"Bossuet"},{"id":"Joly"},{"id":"Grantaire"},{"id":"MotherPlutarch"},{"id":"Gueulemer"},{"id":"Babet"},{"id":"Claquesous"},{"id":"Montparnasse"},{"id":"Toussaint"},{"id":"Child1"},{"id":"Child2"},{"id":"Brujon"},{"id":"Mme.Hucheloup"}],"edges":[{"source":"Napoleon","target":"Myriel","value":1},{"source":"Mlle.Baptistine","target":"Myriel","value":8},{"source":"Mme.Magloire","target":"Myriel","value":10},{"source":"Mme.Magloire","target":"Mlle.Baptistine","value":6},{"source":"CountessdeLo","target":"Myriel","value":1},{"source":"Geborand","target":"Myriel","value":1},{"source":"Champtercier","target":"Myriel","value":1},{"source":"Cravatte","target":"Myriel","value":1},{"source":"Count","target":"Myriel","value":2},{"source":"OldMan","target":"Myriel","value":1},{"source":"Valjean","target":"Labarre","value":1},{"source":"Valjean","target":"Mme.Magloire","value":3},{"source":"Valjean","target":"Mlle.Baptistine","value":3},{"source":"Valjean","target":"Myriel","value":5},{"source":"Marguerite","target":"Valjean","value":1},{"source":"Mme.deR","target":"Valjean","value":1},{"source":"Isabeau","target":"Valjean","value":1},{"source":"Gervais","target":"Valjean","value":1},{"source":"Listolier","target":"Tholomyes","value":4},{"source":"Fameuil","target":"Tholomyes","value":4},{"source":"Fameuil","target":"Listolier","value":4},{"source":"Blacheville","target":"Tholomyes","value":4},{"source":"Blacheville","target":"Listolier","value":4},{"source":"Blacheville","target":"Fameuil","value":4},{"source":"Favourite","target":"Tholomyes","value":3},{"source":"Favourite","target":"Listolier","value":3},{"source":"Favourite","target":"Fameuil","value":3},{"source":"Favourite","target":"Blacheville","value":4},{"source":"Dahlia","target":"Tholomyes","value":3},{"source":"Dahlia","target":"Listolier","value":3},{"source":"Dahlia","target":"Fameuil","value":3},{"source":"Dahlia","target":"Blacheville","value":3},{"source":"Dahlia","target":"Favourite","value":5},{"source":"Zephine","target":"Tholomyes","value":3},{"source":"Zephine","target":"Listolier","value":3},{"source":"Zephine","target":"Fameuil","value":3},{"source":"Zephine","target":"Blacheville","value":3},{"source":"Zephine","target":"Favourite","value":4},{"source":"Zephine","target":"Dahlia","value":4},{"source":"Fantine","target":"Tholomyes","value":3},{"source":"Fantine","target":"Listolier","value":3},{"source":"Fantine","target":"Fameuil","value":3},{"source":"Fantine","target":"Blacheville","value":3},{"source":"Fantine","target":"Favourite","value":4},{"source":"Fantine","target":"Dahlia","value":4},{"source":"Fantine","target":"Zephine","value":4},{"source":"Fantine","target":"Marguerite","value":2},{"source":"Fantine","target":"Valjean","value":9},{"source":"Mme.Thenardier","target":"Fantine","value":2},{"source":"Mme.Thenardier","target":"Valjean","value":7},{"source":"Thenardier","target":"Mme.Thenardier","value":13},{"source":"Thenardier","target":"Fantine","value":1},{"source":"Thenardier","target":"Valjean","value":12},{"source":"Cosette","target":"Mme.Thenardier","value":4},{"source":"Cosette","target":"Valjean","value":31},{"source":"Cosette","target":"Tholomyes","value":1},{"source":"Cosette","target":"Thenardier","value":1},{"source":"Javert","target":"Valjean","value":17},{"source":"Javert","target":"Fantine","value":5},{"source":"Javert","target":"Thenardier","value":5},{"source":"Javert","target":"Mme.Thenardier","value":1},{"source":"Javert","target":"Cosette","value":1},{"source":"Fauchelevent","target":"Valjean","value":8},{"source":"Fauchelevent","target":"Javert","value":1},{"source":"Bamatabois","target":"Fantine","value":1},{"source":"Bamatabois","target":"Javert","value":1},{"source":"Bamatabois","target":"Valjean","value":2},{"source":"Perpetue","target":"Fantine","value":1},{"source":"Simplice","target":"Perpetue","value":2},{"source":"Simplice","target":"Valjean","value":3},{"source":"Simplice","target":"Fantine","value":2},{"source":"Simplice","target":"Javert","value":1},{"source":"Scaufflaire","target":"Valjean","value":1},{"source":"Woman1","target":"Valjean","value":2},{"source":"Woman1","target":"Javert","value":1},{"source":"Judge","target":"Valjean","value":3},{"source":"Judge","target":"Bamatabois","value":2},{"source":"Champmathieu","target":"Valjean","value":3},{"source":"Champmathieu","target":"Judge","value":3},{"source":"Champmathieu","target":"Bamatabois","value":2},{"source":"Brevet","target":"Judge","value":2},{"source":"Brevet","target":"Champmathieu","value":2},{"source":"Brevet","target":"Valjean","value":2},{"source":"Brevet","target":"Bamatabois","value":1},{"source":"Chenildieu","target":"Judge","value":2},{"source":"Chenildieu","target":"Champmathieu","value":2},{"source":"Chenildieu","target":"Brevet","value":2},{"source":"Chenildieu","target":"Valjean","value":2},{"source":"Chenildieu","target":"Bamatabois","value":1},{"source":"Cochepaille","target":"Judge","value":2},{"source":"Cochepaille","target":"Champmathieu","value":2},{"source":"Cochepaille","target":"Brevet","value":2},{"source":"Cochepaille","target":"Chenildieu","value":2},{"source":"Cochepaille","target":"Valjean","value":2},{"source":"Cochepaille","target":"Bamatabois","value":1},{"source":"Pontmercy","target":"Thenardier","value":1},{"source":"Boulatruelle","target":"Thenardier","value":1},{"source":"Eponine","target":"Mme.Thenardier","value":2},{"source":"Eponine","target":"Thenardier","value":3},{"source":"Anzelma","target":"Eponine","value":2},{"source":"Anzelma","target":"Thenardier","value":2},{"source":"Anzelma","target":"Mme.Thenardier","value":1},{"source":"Woman2","target":"Valjean","value":3},{"source":"Woman2","target":"Cosette","value":1},{"source":"Woman2","target":"Javert","value":1},{"source":"MotherInnocent","target":"Fauchelevent","value":3},{"source":"MotherInnocent","target":"Valjean","value":1},{"source":"Gribier","target":"Fauchelevent","value":2},{"source":"Mme.Burgon","target":"Jondrette","value":1},{"source":"Gavroche","target":"Mme.Burgon","value":2},{"source":"Gavroche","target":"Thenardier","value":1},{"source":"Gavroche","target":"Javert","value":1},{"source":"Gavroche","target":"Valjean","value":1},{"source":"Gillenormand","target":"Cosette","value":3},{"source":"Gillenormand","target":"Valjean","value":2},{"source":"Magnon","target":"Gillenormand","value":1},{"source":"Magnon","target":"Mme.Thenardier","value":1},{"source":"Mlle.Gillenormand","target":"Gillenormand","value":9},{"source":"Mlle.Gillenormand","target":"Cosette","value":2},{"source":"Mlle.Gillenormand","target":"Valjean","value":2},{"source":"Mme.Pontmercy","target":"Mlle.Gillenormand","value":1},{"source":"Mme.Pontmercy","target":"Pontmercy","value":1},{"source":"Mlle.Vaubois","target":"Mlle.Gillenormand","value":1},{"source":"Lt.Gillenormand","target":"Mlle.Gillenormand","value":2},{"source":"Lt.Gillenormand","target":"Gillenormand","value":1},{"source":"Lt.Gillenormand","target":"Cosette","value":1},{"source":"Marius","target":"Mlle.Gillenormand","value":6},{"source":"Marius","target":"Gillenormand","value":12},{"source":"Marius","target":"Pontmercy","value":1},{"source":"Marius","target":"Lt.Gillenormand","value":1},{"source":"Marius","target":"Cosette","value":21},{"source":"Marius","target":"Valjean","value":19},{"source":"Marius","target":"Tholomyes","value":1},{"source":"Marius","target":"Thenardier","value":2},{"source":"Marius","target":"Eponine","value":5},{"source":"Marius","target":"Gavroche","value":4},{"source":"BaronessT","target":"Gillenormand","value":1},{"source":"BaronessT","target":"Marius","value":1},{"source":"Mabeuf","target":"Marius","value":1},{"source":"Mabeuf","target":"Eponine","value":1},{"source":"Mabeuf","target":"Gavroche","value":1},{"source":"Enjolras","target":"Marius","value":7},{"source":"Enjolras","target":"Gavroche","value":7},{"source":"Enjolras","target":"Javert","value":6},{"source":"Enjolras","target":"Mabeuf","value":1},{"source":"Enjolras","target":"Valjean","value":4},{"source":"Combeferre","target":"Enjolras","value":15},{"source":"Combeferre","target":"Marius","value":5},{"source":"Combeferre","target":"Gavroche","value":6},{"source":"Combeferre","target":"Mabeuf","value":2},{"source":"Prouvaire","target":"Gavroche","value":1},{"source":"Prouvaire","target":"Enjolras","value":4},{"source":"Prouvaire","target":"Combeferre","value":2},{"source":"Feuilly","target":"Gavroche","value":2},{"source":"Feuilly","target":"Enjolras","value":6},{"source":"Feuilly","target":"Prouvaire","value":2},{"source":"Feuilly","target":"Combeferre","value":5},{"source":"Feuilly","target":"Mabeuf","value":1},{"source":"Feuilly","target":"Marius","value":1},{"source":"Courfeyrac","target":"Marius","value":9},{"source":"Courfeyrac","target":"Enjolras","value":17},{"source":"Courfeyrac","target":"Combeferre","value":13},{"source":"Courfeyrac","target":"Gavroche","value":7},{"source":"Courfeyrac","target":"Mabeuf","value":2},{"source":"Courfeyrac","target":"Eponine","value":1},{"source":"Courfeyrac","target":"Feuilly","value":6},{"source":"Courfeyrac","target":"Prouvaire","value":3},{"source":"Bahorel","target":"Combeferre","value":5},{"source":"Bahorel","target":"Gavroche","value":5},{"source":"Bahorel","target":"Courfeyrac","value":6},{"source":"Bahorel","target":"Mabeuf","value":2},{"source":"Bahorel","target":"Enjolras","value":4},{"source":"Bahorel","target":"Feuilly","value":3},{"source":"Bahorel","target":"Prouvaire","value":2},{"source":"Bahorel","target":"Marius","value":1},{"source":"Bossuet","target":"Marius","value":5},{"source":"Bossuet","target":"Courfeyrac","value":12},{"source":"Bossuet","target":"Gavroche","value":5},{"source":"Bossuet","target":"Bahorel","value":4},{"source":"Bossuet","target":"Enjolras","value":10},{"source":"Bossuet","target":"Feuilly","value":6},{"source":"Bossuet","target":"Prouvaire","value":2},{"source":"Bossuet","target":"Combeferre","value":9},{"source":"Bossuet","target":"Mabeuf","value":1},{"source":"Bossuet","target":"Valjean","value":1},{"source":"Joly","target":"Bahorel","value":5},{"source":"Joly","target":"Bossuet","value":7},{"source":"Joly","target":"Gavroche","value":3},{"source":"Joly","target":"Courfeyrac","value":5},{"source":"Joly","target":"Enjolras","value":5},{"source":"Joly","target":"Feuilly","value":5},{"source":"Joly","target":"Prouvaire","value":2},{"source":"Joly","target":"Combeferre","value":5},{"source":"Joly","target":"Mabeuf","value":1},{"source":"Joly","target":"Marius","value":2},{"source":"Grantaire","target":"Bossuet","value":3},{"source":"Grantaire","target":"Enjolras","value":3},{"source":"Grantaire","target":"Combeferre","value":1},{"source":"Grantaire","target":"Courfeyrac","value":2},{"source":"Grantaire","target":"Joly","value":2},{"source":"Grantaire","target":"Gavroche","value":1},{"source":"Grantaire","target":"Bahorel","value":1},{"source":"Grantaire","target":"Feuilly","value":1},{"source":"Grantaire","target":"Prouvaire","value":1},{"source":"MotherPlutarch","target":"Mabeuf","value":3},{"source":"Gueulemer","target":"Thenardier","value":5},{"source":"Gueulemer","target":"Valjean","value":1},{"source":"Gueulemer","target":"Mme.Thenardier","value":1},{"source":"Gueulemer","target":"Javert","value":1},{"source":"Gueulemer","target":"Gavroche","value":1},{"source":"Gueulemer","target":"Eponine","value":1},{"source":"Babet","target":"Thenardier","value":6},{"source":"Babet","target":"Gueulemer","value":6},{"source":"Babet","target":"Valjean","value":1},{"source":"Babet","target":"Mme.Thenardier","value":1},{"source":"Babet","target":"Javert","value":2},{"source":"Babet","target":"Gavroche","value":1},{"source":"Babet","target":"Eponine","value":1},{"source":"Claquesous","target":"Thenardier","value":4},{"source":"Claquesous","target":"Babet","value":4},{"source":"Claquesous","target":"Gueulemer","value":4},{"source":"Claquesous","target":"Valjean","value":1},{"source":"Claquesous","target":"Mme.Thenardier","value":1},{"source":"Claquesous","target":"Javert","value":1},{"source":"Claquesous","target":"Eponine","value":1},{"source":"Claquesous","target":"Enjolras","value":1},{"source":"Montparnasse","target":"Javert","value":1},{"source":"Montparnasse","target":"Babet","value":2},{"source":"Montparnasse","target":"Gueulemer","value":2},{"source":"Montparnasse","target":"Claquesous","value":2},{"source":"Montparnasse","target":"Valjean","value":1},{"source":"Montparnasse","target":"Gavroche","value":1},{"source":"Montparnasse","target":"Eponine","value":1},{"source":"Montparnasse","target":"Thenardier","value":1},{"source":"Toussaint","target":"Cosette","value":2},{"source":"Toussaint","target":"Javert","value":1},{"source":"Toussaint","target":"Valjean","value":1},{"source":"Child1","target":"Gavroche","value":2},{"source":"Child2","target":"Gavroche","value":2},{"source":"Child2","target":"Child1","value":3},{"source":"Brujon","target":"Babet","value":3},{"source":"Brujon","target":"Gueulemer","value":3},{"source":"Brujon","target":"Thenardier","value":3},{"source":"Brujon","target":"Gavroche","value":1},{"source":"Brujon","target":"Eponine","value":1},{"source":"Brujon","target":"Claquesous","value":1},{"source":"Brujon","target":"Montparnasse","value":1},{"source":"Mme.Hucheloup","target":"Bossuet","value":1},{"source":"Mme.Hucheloup","target":"Joly","value":1},{"source":"Mme.Hucheloup","target":"Grantaire","value":1},{"source":"Mme.Hucheloup","target":"Bahorel","value":1},{"source":"Mme.Hucheloup","target":"Courfeyrac","value":1},{"source":"Mme.Hucheloup","target":"Gavroche","value":1},{"source":"Mme.Hucheloup","target":"Enjolras","value":1}]} \ No newline at end of file diff --git a/__tests__/g6/fruchterman-g6.test.ts b/__tests__/g6/fruchterman-g6.test.ts new file mode 100644 index 00000000000..004646f0055 --- /dev/null +++ b/__tests__/g6/fruchterman-g6.test.ts @@ -0,0 +1,123 @@ +import { Layout } from '../../src'; +import { GridLayout, FruchtermanGPULayout, FruchtermanLayout } from '../../src' +import G6 from '@antv/g6'; +import dataset from '../data'; + +const data = dataset.smallWorld; + +const div = document.createElement('div'); +div.id = 'global-spec'; +document.body.appendChild(div); + +const simpleData = { + nodes: [ + {id: 'node1'}, + {id: 'node2'}, + {id: 'node3'}, + {id: 'node4'}, + ], + edges: [ + {source: 'node1', target: 'node2'}, + ] +} + +const complexDataUrl = 'https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json'; + +describe('Grid Layout', () => { + it.only('grid layout with small data', async () => { + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + defaultEdge: { + style: { + endArrow: true, + }, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas'] + } + }); + + // fetch(complexDataUrl) + // .then((res) => res.json()) + // .then((data) => { + const preGrid = new GridLayout({ + width: 500, + height: 500, + nodeStrength: 10 + }); + preGrid.layout(data); + + const colors = [ + '#5F95FF', // blue + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262FD', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', + ]; + + data.nodes.forEach(node => { + node.cluster = Math.floor(Math.random() * 5); + node.color = colors[node.cluster]; + }) + + data.nodes[0].fx = 100; + data.nodes[0].fy = 100; + + data.nodes[17].fx = 400; + data.nodes[17].fy = 400; + + data.nodes.forEach((node, i) => { + node.label = `${i}`; + }) + + + const layout = new FruchtermanGPULayout({ // FruchtermanGPULayout FruchtermanLayout + maxIteration: 1000, + speed: 5, + // clustering: true, + // clusterField: 'cluster', + onLayoutEnd: () => { + graph.refreshPositions(); + } + // gravity: 100, + // tick: () => { + // graph.refreshPositions(); + // } + }); + await layout.layout(data); + // layout.layout(data); + + graph.data(data) + graph.render(); + + function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; + } + graph.on('node:dragstart', (e) => { + layout.stop(); + }); + graph.on('node:drag', (e) => { + const model = e.item.get('model'); + model.x = e.x; + model.y = e.y; + graph.refreshPositions(); + }); + graph.on('node:dragend', async (e) => { + refreshDragedNodePosition(e); + layout.execute(); + graph.refreshPositions(); + }); + // graph.destroy(); + // }); + }); +}); \ No newline at end of file diff --git a/__tests__/g6/gforcegpu-g6.test.ts b/__tests__/g6/gforcegpu-g6.test.ts new file mode 100644 index 00000000000..78c9740f3a6 --- /dev/null +++ b/__tests__/g6/gforcegpu-g6.test.ts @@ -0,0 +1,101 @@ +import { Layout } from '../../src'; +import { GridLayout, GForceGPULayout, GForceLayout } from '../../src' +import G6 from '@antv/g6'; +import dataset from '../data'; + +const data = dataset.smallWorld; + +const div = document.createElement('div'); +div.id = 'global-spec'; +document.body.appendChild(div); + +const simpleData = { + nodes: [ + {id: 'node1'}, + {id: 'node2'}, + {id: 'node3'}, + {id: 'node4'}, + ], + edges: [ + {source: 'node1', target: 'node2'}, + ] +} + +const complexDataUrl = 'https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json'; + +describe('Grid Layout', () => { + it.only('grid layout with small data', async () => { + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + defaultEdge: { + style: { + endArrow: true, + }, + }, + modes: { + default: ['drag-canvas', 'zoom-canvas'] + } + }); + + // fetch(complexDataUrl) + // .then((res) => res.json()) + // .then((data) => { + const preGrid = new GridLayout({ + width: 500, + height: 500, + nodeStrength: 10 + }); + preGrid.layout(data); + + data.nodes[0].fx = 100; + data.nodes[0].fy = 100; + + data.nodes[17].fx = 400; + data.nodes[17].fy = 400; + + data.nodes.forEach((node, i) => { + node.label = `${i}`; + }) + + + const layout = new GForceGPULayout({ // GForceGPULayout GForceLayout + maxIteration: 1000, + // gravity: 100, + nodeStrength: 1000, + tick: () => { + graph.refreshPositions(); + } + }); + await layout.layout(data); + // layout.layout(data); + + console.log("layout", layout, layout.getMass(data.nodes[10])) + graph.data(data) + graph.render(); + + function refreshDragedNodePosition(e) { + const model = e.item.get('model'); + model.fx = e.x; + model.fy = e.y; + } + // graph.on('node:dragstart', (e) => { + // refreshDragedNodePosition(e); + // }); + graph.on('node:drag', (e) => { + const model = e.item.get('model'); + model.x = e.x; + model.y = e.y; + graph.refreshPositions(); + }); + graph.on('node:dragend', async (e) => { + refreshDragedNodePosition(e); + await layout.execute(); + graph.refreshPositions(); + }); + // graph.destroy(); + // }); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index d0560616ff0..ca7f9acd73d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@antv/layout", - "version": "0.1.13", + "version": "0.1.14", "description": "graph layout algorithm", "main": "lib/index.js", "module": "es/index.js", diff --git a/src/layout/fruchterman.ts b/src/layout/fruchterman.ts index d04c72eb745..698e333ff98 100644 --- a/src/layout/fruchterman.ts +++ b/src/layout/fruchterman.ts @@ -38,7 +38,7 @@ export class FruchtermanLayout extends Base { public gravity: number = 10; /** 速度 */ - public speed: number = 1; + public speed: number = 5; /** 是否产生聚类力 */ public clustering: boolean = false; @@ -61,6 +61,9 @@ export class FruchtermanLayout extends Base { /** 迭代结束的回调函数 */ public onLayoutEnd: () => void = () => {}; + /** 迭代中的标识 */ + private timeInterval: number; + constructor(options?: FruchtermanLayoutOptions) { super(); this.updateCfg(options); @@ -83,6 +86,10 @@ export class FruchtermanLayout extends Base { const self = this; const nodes = self.nodes; + if (self.timeInterval !== undefined && typeof window !== "undefined") { + window.clearInterval(self.timeInterval); + } + if (!nodes || nodes.length === 0) { if (self.onLayoutEnd) self.onLayoutEnd(); return; @@ -165,7 +172,14 @@ export class FruchtermanLayout extends Base { clusterMap[key].cy /= clusterMap[key].count; } } - for (let i = 0; i < maxIteration; i++) { + + if (typeof window === "undefined") return; + + let iter = 0; + // interval for render the result after each iteration + this.timeInterval = window.setInterval(() => { + + // for (let i = 0; i < maxIteration; i++) { const displacements: Point[] = []; nodes.forEach((_, j) => { displacements[j] = { x: 0, y: 0 }; @@ -218,7 +232,12 @@ export class FruchtermanLayout extends Base { // move nodes.forEach((n, j) => { - if (!isNumber(n.x) || !isNumber(n.y)) return; + if (isNumber(n.fx) && isNumber(n.fy)) { + n.x = n.fx; + n.y = n.fy; + return; + } + if (!isNumber(n.x) || !isNumber(n.y)) return; const distLength = Math.sqrt( displacements[j].x * displacements[j].x + displacements[j].y * displacements[j].y @@ -233,9 +252,15 @@ export class FruchtermanLayout extends Base { n.y += (displacements[j].y / distLength) * limitedDist; } }); - } - if (self.onLayoutEnd) self.onLayoutEnd(); + if (self.tick) self.tick(); + + iter++; + if (iter >= maxIteration) { + if (self.onLayoutEnd) self.onLayoutEnd(); + window.clearInterval(self.timeInterval); + } + }, 0); return { nodes, @@ -308,6 +333,21 @@ export class FruchtermanLayout extends Base { }); } + public stop() { + if (this.timeInterval && typeof window !== "undefined") { + window.clearInterval(this.timeInterval); + } + } + + public destroy() { + const self = this; + self.stop(); + self.tick = null; + self.nodes = null; + self.edges = null; + self.destroyed = true; + } + public getType() { return "fruchterman"; } diff --git a/src/layout/gForce.ts b/src/layout/gForce.ts index 349cf333226..62c1b5707a0 100644 --- a/src/layout/gForce.ts +++ b/src/layout/gForce.ts @@ -224,7 +224,8 @@ export class GForceLayout extends Base { self.degrees = getDegree(nodes.length, self.nodeIdxMap, edges); if (!self.getMass) { self.getMass = d => { - return self.degrees[self.nodeIdxMap[d.id]] || 1; + const mass = self.degrees[self.nodeIdxMap[d.id]] || 1; + return mass; }; } @@ -280,9 +281,9 @@ export class GForceLayout extends Base { if (self.onLayoutEnd) self.onLayoutEnd(); } iter++; - if (iter > maxIteration) { - window.clearInterval(self.timeInterval); + if (iter >= maxIteration) { if (self.onLayoutEnd) self.onLayoutEnd(); + window.clearInterval(self.timeInterval); } }, 0); } @@ -311,10 +312,10 @@ export class GForceLayout extends Base { (((nodeStrength(ni) + nodeStrength(nj)) / 2) * factor) / (nVecLength * nVecLength); const massj = getMass ? getMass(nj) : 1; - accArray[2 * i] += (direX * param) / massi; - accArray[2 * i + 1] += (direY * param) / massi; - accArray[2 * j] -= (direX * param) / massj; - accArray[2 * j + 1] -= (direY * param) / massj; + accArray[2 * i] += (direX * param); + accArray[2 * i + 1] += (direY * param); + accArray[2 * j] -= (direX * param); + accArray[2 * j + 1] -= (direY * param); if (preventOverlap && vecLength < (nodeSize(ni) + nodeSize(nj)) / 2) { const paramOverlap = (nodeStrength(ni) + nodeStrength(nj)) / 2 / (vecLength * vecLength); @@ -419,6 +420,11 @@ export class GForceLayout extends Base { nodes: INode[] ) { nodes.forEach((node, i) => { + if (isNumber(node.fx) && isNumber(node.fy)) { + node.x = node.fx; + node.y = node.fy; + return; + } const distX = velArray[2 * i] * stepInterval; const distY = velArray[2 * i + 1] * stepInterval; node.x += distX; diff --git a/src/layout/gpu/fruchterman.ts b/src/layout/gpu/fruchterman.ts index 3fe7253f85d..ccb272130d4 100644 --- a/src/layout/gpu/fruchterman.ts +++ b/src/layout/gpu/fruchterman.ts @@ -16,13 +16,13 @@ import { isNumber } from "../../util"; // @ts-ignore import { World } from "@antv/g-webgpu"; // compile at runtime in dev mode -// @ts-ignore -// import { Compiler } from '@antv/g-webgpu-compiler' -// import { fruchtermanCode, clusterCode } from './fruchtermanShader' import { buildTextureData, attributesToTextureData } from "../../util/gpu"; // use compiled bundle in prod mode import { fruchtermanBundle, clusterBundle } from "./fruchtermanShader"; import { LAYOUT_MESSAGE } from "../constants"; +// @ts-ignore +// import { Compiler } from '@antv/g-webgpu-compiler' +// import { fruchtermanCode, clusterCode } from './fruchtermanShader' type INode = OutNode & { cluster: string | number; @@ -177,6 +177,18 @@ export class FruchtermanGPULayout extends Base { count: clusterCount } = attributesToTextureData([self.clusterField], nodes); + // pushing the fx and fy + nodes.forEach((node, i) => { + let fx = 0, fy = 0; + if (isNumber(node.fx) && isNumber(node.fy)) { + fx = node.fx || 0.001; + fy = node.fy || 0.001; + } + attributeArray[4 * i + 1] = fx; + attributeArray[4 * i + 2] = fy; + }) + + const numParticles = nodes.length; const { maxEdgePerVetex, array: nodesEdgesArray } = buildTextureData( nodes, diff --git a/src/layout/gpu/fruchtermanShader.ts b/src/layout/gpu/fruchtermanShader.ts index f2826f08a92..55724446a2d 100644 --- a/src/layout/gpu/fruchtermanShader.ts +++ b/src/layout/gpu/fruchtermanShader.ts @@ -114,6 +114,21 @@ class Fruchterman { this.u_Data[i] = currentNode; return; } + + // [gravity, fx, fy, 0] + const nodeAttributes = this.u_AttributeArray[i]; + + if (nodeAttributes[1] != 0 && nodeAttributes[2] != 0) { + // the node is fixed + this.u_Data[i] = [ + nodeAttributes[1], + nodeAttributes[2], + currentNode[2], + currentNode[3] + ]; + return; + } + // repulsive const repulsive = this.calcRepulsive(i, currentNode); dx += repulsive[0]; @@ -123,13 +138,13 @@ class Fruchterman { dx += attractive[0]; dy += attractive[1]; // gravity - const nodeAttributes = this.u_AttributeArray[i]; const gravity = this.calcGravity(currentNode, nodeAttributes); dx -= gravity[0]; dy -= gravity[1]; // speed dx *= this.u_Speed; dy *= this.u_Speed; + // move const distLength = sqrt(dx * dx + dy * dy); if (distLength > 0.0) { @@ -145,7 +160,7 @@ class Fruchterman { } ` -export const fruchtermanBundle = `{"shaders":{"WGSL":"import \\"GLSL.std.450\\" as std;\\n\\n\\n# var gWebGPUDebug : bool = false;\\n# var gWebGPUDebugOutput : vec4 = vec4(0.0);\\n\\n[[builtin global_invocation_id]] var globalInvocationID : vec3;\\n# [[builtin work_group_size]] var workGroupSize : vec3;\\n# [[builtin work_group_id]] var workGroupID : vec3;\\n[[builtin local_invocation_id]] var localInvocationID : vec3;\\n# [[builtin num_work_groups]] var numWorkGroups : vec3;\\n[[builtin local_invocation_idx]] var localInvocationIndex : u32;\\n\\ntype GWebGPUParams = [[block]] struct {\\n [[offset 0]] u_K : f32;\\n [[offset 4]] u_K2 : f32;\\n [[offset 8]] u_Center : vec2;\\n [[offset 16]] u_Gravity : f32;\\n [[offset 20]] u_ClusterGravity : f32;\\n [[offset 24]] u_Speed : f32;\\n [[offset 28]] u_MaxDisplace : f32;\\n [[offset 32]] u_Clustering : f32;\\n};\\n[[binding 0, set 0]] var gWebGPUUniformParams : GWebGPUParams;\\ntype GWebGPUBuffer0 = [[block]] struct {\\n [[offset 0]] u_Data : [[stride 16]] array>;\\n};\\n[[binding 1, set 0]] var gWebGPUBuffer0 : GWebGPUBuffer0;\\ntype GWebGPUBuffer1 = [[block]] struct {\\n [[offset 0]] u_AttributeArray : [[stride 16]] array>;\\n};\\n[[binding 2, set 0]] var gWebGPUBuffer1 : GWebGPUBuffer1;\\ntype GWebGPUBuffer2 = [[block]] struct {\\n [[offset 0]] u_ClusterCenters : [[stride 16]] array>;\\n};\\n[[binding 3, set 0]] var gWebGPUBuffer2 : GWebGPUBuffer2;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nfn calcRepulsive(i : i32, currentNode : vec4) -> vec2 {var dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nfor (var j : i32 = 0; j < __DefineValuePlaceholder__VERTEX_COUNT; j = j + 1) {if (i != j) {var nextNode : vec4 = gWebGPUBuffer0.u_Data[j];\\nvar xDist : f32 = currentNode.x - nextNode.x;\\nvar yDist : f32 = currentNode.y - nextNode.y;\\nvar dist : f32 = ((xDist * xDist) + (yDist * yDist)) + 0.01;\\nvar param : f32 = gWebGPUUniformParams.u_K2 / dist;\\nif (dist > 0.0) {dx = dx + param * xDist;\\ndy = dy + param * yDist;\\nif ((xDist == 0.0) && (yDist == 0.0)) {var sign : f32 = select(1.0, -1.0, i < j);\\ndx = dx + param * std::sign;\\ndy = dy + param * std::sign;}}}}\\nreturn vec2(dx, dy);}\\nfn calcGravity(currentNode : vec4, nodeAttributes : vec4) -> vec2 {var dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nvar vx : f32 = currentNode.x - gWebGPUUniformParams.u_Center.x;\\nvar vy : f32 = currentNode.y - gWebGPUUniformParams.u_Center.y;\\nvar gf : f32 = (0.01 * gWebGPUUniformParams.u_K) * gWebGPUUniformParams.u_Gravity;\\ndx = gf * vx;\\ndy = gf * vy;\\nif (gWebGPUUniformParams.u_Clustering == 1.0) {var clusterIdx : i32 = i32(nodeAttributes.x);\\nvar center : vec4 = gWebGPUBuffer2.u_ClusterCenters[clusterIdx];\\nvar cvx : f32 = currentNode.x - center.x;\\nvar cvy : f32 = currentNode.y - center.y;\\nvar dist : f32 = std::sqrt((cvx * cvx) + (cvy * cvy)) + 0.01;\\nvar parma : f32 = (gWebGPUUniformParams.u_K * gWebGPUUniformParams.u_ClusterGravity) / dist;\\ndx = dx + parma * cvx;\\ndy = dy + parma * cvy;}\\nreturn vec2(dx, dy);}\\nfn calcAttractive(i : i32, currentNode : vec4) -> vec2 {var dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nvar arr_offset : i32 = i32(std::floor(currentNode.z + 0.5));\\nvar length : i32 = i32(std::floor(currentNode.w + 0.5));\\nvar node_buffer : vec4;\\nfor (var p : i32 = 0; p < __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX; p = p + 1) {if (p >= length) {break;}\\nvar arr_idx : i32 = arr_offset + i32(p);\\nvar buf_offset : i32 = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[i32(arr_idx / 4)];}\\nvar float_j : f32 = select(node_buffer.x, select(node_buffer.y, select(node_buffer.z, node_buffer.w, buf_offset == 2), buf_offset == 1), buf_offset == 0);\\nvar nextNode : vec4 = gWebGPUBuffer0.u_Data[i32(float_j)];\\nvar xDist : f32 = currentNode.x - nextNode.x;\\nvar yDist : f32 = currentNode.y - nextNode.y;\\nvar dist : f32 = std::sqrt((xDist * xDist) + (yDist * yDist)) + 0.01;\\nvar attractiveF : f32 = dist / gWebGPUUniformParams.u_K;\\nif (dist > 0.0) {dx = dx - xDist * attractiveF;\\ndy = dy - yDist * attractiveF;\\nif ((xDist == 0.0) && (yDist == 0.0)) {var sign : f32 = select(1.0, -1.0, i < i32(float_j));\\ndx = dx - std::sign * attractiveF;\\ndy = dy - std::sign * attractiveF;}}}\\nreturn vec2(dx, dy);}\\nfn main() -> void {var i : i32 = globalInvocationID.x;\\nvar currentNode : vec4 = gWebGPUBuffer0.u_Data[i];\\nvar dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nif (i >= __DefineValuePlaceholder__VERTEX_COUNT) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvar repulsive : vec2 = calcRepulsive(i, currentNode);\\ndx = dx + repulsive.x;\\ndy = dy + repulsive.y;\\nvar attractive : vec2 = calcAttractive(i, currentNode);\\ndx = dx + attractive.x;\\ndy = dy + attractive.y;\\nvar nodeAttributes : vec4 = gWebGPUBuffer1.u_AttributeArray[i];\\nvar gravity : vec2 = calcGravity(currentNode, nodeAttributes);\\ndx = dx - gravity.x;\\ndy = dy - gravity.y;\\ndx = dx * gWebGPUUniformParams.u_Speed;\\ndy = dy * gWebGPUUniformParams.u_Speed;\\nvar distLength : f32 = std::sqrt((dx * dx) + (dy * dy));\\nif (distLength > 0.0) {var limitedDist : f32 = std::min(gWebGPUUniformParams.u_MaxDisplace * gWebGPUUniformParams.u_Speed, distLength);\\ngWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + ((dx / distLength) * limitedDist), currentNode.y + ((dy / distLength) * limitedDist), currentNode.z, currentNode.w);}\\nreturn;}\\n\\nentry_point compute as \\"main\\" = main;\\n","GLSL450":"\\n\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\nivec3 globalInvocationID = ivec3(gl_GlobalInvocationID);\\nivec3 workGroupSize = ivec3(1,1,1);\\nivec3 workGroupID = ivec3(gl_WorkGroupID);\\nivec3 localInvocationID = ivec3(gl_LocalInvocationID);\\nivec3 numWorkGroups = ivec3(gl_NumWorkGroups);\\nint localInvocationIndex = int(gl_LocalInvocationIndex);\\n\\nlayout(std140, set = 0, binding = 0) uniform GWebGPUParams {\\n float u_K;\\n float u_K2;\\n vec2 u_Center;\\n float u_Gravity;\\n float u_ClusterGravity;\\n float u_Speed;\\n float u_MaxDisplace;\\n float u_Clustering;\\n} gWebGPUUniformParams;\\nlayout(std430, set = 0, binding = 1) buffer GWebGPUBuffer0 {\\n vec4 u_Data[];\\n} gWebGPUBuffer0;\\n\\nlayout(std430, set = 0, binding = 2) buffer readonly GWebGPUBuffer1 {\\n vec4 u_AttributeArray[];\\n} gWebGPUBuffer1;\\n\\nlayout(std430, set = 0, binding = 3) buffer readonly GWebGPUBuffer2 {\\n vec4 u_ClusterCenters[];\\n} gWebGPUBuffer2;\\n\\n\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\nlayout (\\n local_size_x = 1,\\n local_size_y = 1,\\n local_size_z = 1\\n) in;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nvec2 calcRepulsive(int i, vec4 currentNode) {float dx = 0.0;\\nfloat dy = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = gWebGPUBuffer0.u_Data[j];\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = ((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat param = gWebGPUUniformParams.u_K2 / dist;\\nif (dist > 0.0) {dx += param * xDist;\\ndy += param * yDist;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < j) ? (1.0) : (-1.0);\\ndx += param * sign;\\ndy += param * sign;}}}}\\nreturn vec2(dx, dy);}\\nvec2 calcGravity(vec4 currentNode, vec4 nodeAttributes) {float dx = 0.0;\\nfloat dy = 0.0;\\nfloat vx = currentNode.x - gWebGPUUniformParams.u_Center.x;\\nfloat vy = currentNode.y - gWebGPUUniformParams.u_Center.y;\\nfloat gf = (0.01 * gWebGPUUniformParams.u_K) * gWebGPUUniformParams.u_Gravity;\\ndx = gf * vx;\\ndy = gf * vy;\\nif (gWebGPUUniformParams.u_Clustering == 1.0) {int clusterIdx = int(nodeAttributes.x);\\nvec4 center = gWebGPUBuffer2.u_ClusterCenters[clusterIdx];\\nfloat cvx = currentNode.x - center.x;\\nfloat cvy = currentNode.y - center.y;\\nfloat dist = sqrt((cvx * cvx) + (cvy * cvy)) + 0.01;\\nfloat parma = (gWebGPUUniformParams.u_K * gWebGPUUniformParams.u_ClusterGravity) / dist;\\ndx += parma * cvx;\\ndy += parma * cvy;}\\nreturn vec2(dx, dy);}\\nvec2 calcAttractive(int i, vec4 currentNode) {float dx = 0.0;\\nfloat dy = 0.0;\\nint arr_offset = int(floor(currentNode.z + 0.5));\\nint length = int(floor(currentNode.w + 0.5));\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + int(p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[int(arr_idx / 4)];}\\nfloat float_j = (buf_offset == 0) ? (node_buffer.x) : ((buf_offset == 1) ? (node_buffer.y) : ((buf_offset == 2) ? (node_buffer.z) : (node_buffer.w)));\\nvec4 nextNode = gWebGPUBuffer0.u_Data[int(float_j)];\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = sqrt((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat attractiveF = dist / gWebGPUUniformParams.u_K;\\nif (dist > 0.0) {dx -= xDist * attractiveF;\\ndy -= yDist * attractiveF;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < int(float_j)) ? (1.0) : (-1.0);\\ndx -= sign * attractiveF;\\ndy -= sign * attractiveF;}}}\\nreturn vec2(dx, dy);}\\nvoid main() {int i = globalInvocationID.x;\\nvec4 currentNode = gWebGPUBuffer0.u_Data[i];\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nif (i >= VERTEX_COUNT) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvec2 repulsive = calcRepulsive(i, currentNode);\\ndx += repulsive.x;\\ndy += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode);\\ndx += attractive.x;\\ndy += attractive.y;\\nvec4 nodeAttributes = gWebGPUBuffer1.u_AttributeArray[i];\\nvec2 gravity = calcGravity(currentNode, nodeAttributes);\\ndx -= gravity.x;\\ndy -= gravity.y;\\ndx *= gWebGPUUniformParams.u_Speed;\\ndy *= gWebGPUUniformParams.u_Speed;\\nfloat distLength = sqrt((dx * dx) + (dy * dy));\\nif (distLength > 0.0) {float limitedDist = min(gWebGPUUniformParams.u_MaxDisplace * gWebGPUUniformParams.u_Speed, distLength);\\ngWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + ((dx / distLength) * limitedDist), currentNode.y + ((dy / distLength) * limitedDist), currentNode.z, currentNode.w);}}\\n","GLSL100":"\\n\\nfloat epsilon = 0.00001;\\nvec2 addrTranslation_1Dto2D(float address1D, vec2 texSize) {\\n vec2 conv_const = vec2(1.0 / texSize.x, 1.0 / (texSize.x * texSize.y));\\n vec2 normAddr2D = float(address1D) * conv_const;\\n return vec2(fract(normAddr2D.x + epsilon), normAddr2D.y);\\n}\\n\\nvoid barrier() {}\\n \\n\\nuniform vec2 u_OutputTextureSize;\\nuniform int u_OutputTexelCount;\\nvarying vec2 v_TexCoord;\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\n\\nuniform sampler2D u_Data;\\nuniform vec2 u_DataSize;\\nvec4 getDatau_Data(vec2 address2D) {\\n return vec4(texture2D(u_Data, address2D).rgba);\\n}\\nvec4 getDatau_Data(float address1D) {\\n return getDatau_Data(addrTranslation_1Dto2D(address1D, u_DataSize));\\n}\\nvec4 getDatau_Data(int address1D) {\\n return getDatau_Data(float(address1D));\\n}\\nuniform float u_K;\\nuniform float u_K2;\\nuniform vec2 u_Center;\\nuniform float u_Gravity;\\nuniform float u_ClusterGravity;\\nuniform float u_Speed;\\nuniform float u_MaxDisplace;\\nuniform float u_Clustering;\\nuniform sampler2D u_AttributeArray;\\nuniform vec2 u_AttributeArraySize;\\nvec4 getDatau_AttributeArray(vec2 address2D) {\\n return vec4(texture2D(u_AttributeArray, address2D).rgba);\\n}\\nvec4 getDatau_AttributeArray(float address1D) {\\n return getDatau_AttributeArray(addrTranslation_1Dto2D(address1D, u_AttributeArraySize));\\n}\\nvec4 getDatau_AttributeArray(int address1D) {\\n return getDatau_AttributeArray(float(address1D));\\n}\\nuniform sampler2D u_ClusterCenters;\\nuniform vec2 u_ClusterCentersSize;\\nvec4 getDatau_ClusterCenters(vec2 address2D) {\\n return vec4(texture2D(u_ClusterCenters, address2D).rgba);\\n}\\nvec4 getDatau_ClusterCenters(float address1D) {\\n return getDatau_ClusterCenters(addrTranslation_1Dto2D(address1D, u_ClusterCentersSize));\\n}\\nvec4 getDatau_ClusterCenters(int address1D) {\\n return getDatau_ClusterCenters(float(address1D));\\n}\\nvec2 calcRepulsive(int i, vec4 currentNode) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = getDatau_Data(j);\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = ((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat param = u_K2 / dist;\\nif (dist > 0.0) {dx += param * xDist;\\ndy += param * yDist;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < j) ? (1.0) : (-1.0);\\ndx += param * sign;\\ndy += param * sign;}}}}\\nreturn vec2(dx, dy);}\\nvec2 calcGravity(vec4 currentNode, vec4 nodeAttributes) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nfloat vx = currentNode.x - u_Center.x;\\nfloat vy = currentNode.y - u_Center.y;\\nfloat gf = (0.01 * u_K) * u_Gravity;\\ndx = gf * vx;\\ndy = gf * vy;\\nif (u_Clustering == 1.0) {int clusterIdx = int(nodeAttributes.x);\\nvec4 center = getDatau_ClusterCenters(clusterIdx);\\nfloat cvx = currentNode.x - center.x;\\nfloat cvy = currentNode.y - center.y;\\nfloat dist = sqrt((cvx * cvx) + (cvy * cvy)) + 0.01;\\nfloat parma = (u_K * u_ClusterGravity) / dist;\\ndx += parma * cvx;\\ndy += parma * cvy;}\\nreturn vec2(dx, dy);}\\nvec2 calcAttractive(int i, vec4 currentNode) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nint arr_offset = int(floor(currentNode.z + 0.5));\\nint length = int(floor(currentNode.w + 0.5));\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + int(p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = getDatau_Data(int(arr_idx / 4));}\\nfloat float_j = (buf_offset == 0) ? (node_buffer.x) : ((buf_offset == 1) ? (node_buffer.y) : ((buf_offset == 2) ? (node_buffer.z) : (node_buffer.w)));\\nvec4 nextNode = getDatau_Data(int(float_j));\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = sqrt((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat attractiveF = dist / u_K;\\nif (dist > 0.0) {dx -= xDist * attractiveF;\\ndy -= yDist * attractiveF;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < int(float_j)) ? (1.0) : (-1.0);\\ndx -= sign * attractiveF;\\ndy -= sign * attractiveF;}}}\\nreturn vec2(dx, dy);}\\nvoid main() {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nint i = globalInvocationID.x;\\nvec4 currentNode = getDatau_Data(i);\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nif (i >= VERTEX_COUNT) {gl_FragColor = vec4(currentNode);\\nreturn ;}\\nvec2 repulsive = calcRepulsive(i, currentNode);\\ndx += repulsive.x;\\ndy += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode);\\ndx += attractive.x;\\ndy += attractive.y;\\nvec4 nodeAttributes = getDatau_AttributeArray(i);\\nvec2 gravity = calcGravity(currentNode, nodeAttributes);\\ndx -= gravity.x;\\ndy -= gravity.y;\\ndx *= u_Speed;\\ndy *= u_Speed;\\nfloat distLength = sqrt((dx * dx) + (dy * dy));\\nif (distLength > 0.0) {float limitedDist = min(u_MaxDisplace * u_Speed, distLength);\\ngl_FragColor = vec4(vec4(currentNode.x + ((dx / distLength) * limitedDist), currentNode.y + ((dy / distLength) * limitedDist), currentNode.z, currentNode.w));}if (gWebGPUDebug) {\\n gl_FragColor = gWebGPUDebugOutput;\\n}}\\n"},"context":{"name":"","dispatch":[1,1,1],"threadGroupSize":[1,1,1],"maxIteration":1,"defines":[{"name":"MAX_EDGE_PER_VERTEX","type":"Float","runtime":true},{"name":"VERTEX_COUNT","type":"Float","runtime":true}],"uniforms":[{"name":"u_Data","type":"vec4[]","storageClass":"StorageBuffer","readonly":false,"writeonly":false,"size":[1,1]},{"name":"u_K","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_K2","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Center","type":"vec2","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Gravity","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_ClusterGravity","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Speed","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_MaxDisplace","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Clustering","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_AttributeArray","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_ClusterCenters","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]}],"globalDeclarations":[],"output":{"name":"u_Data","size":[1,1],"length":1},"needPingpong":true}}` +export const fruchtermanBundle = `{"shaders":{"WGSL":"import \\"GLSL.std.450\\" as std;\\n\\n\\n# var gWebGPUDebug : bool = false;\\n# var gWebGPUDebugOutput : vec4 = vec4(0.0);\\n\\n[[builtin global_invocation_id]] var globalInvocationID : vec3;\\n# [[builtin work_group_size]] var workGroupSize : vec3;\\n# [[builtin work_group_id]] var workGroupID : vec3;\\n[[builtin local_invocation_id]] var localInvocationID : vec3;\\n# [[builtin num_work_groups]] var numWorkGroups : vec3;\\n[[builtin local_invocation_idx]] var localInvocationIndex : u32;\\n\\ntype GWebGPUParams = [[block]] struct {\\n [[offset 0]] u_K : f32;\\n [[offset 4]] u_K2 : f32;\\n [[offset 8]] u_Center : vec2;\\n [[offset 16]] u_Gravity : f32;\\n [[offset 20]] u_ClusterGravity : f32;\\n [[offset 24]] u_Speed : f32;\\n [[offset 28]] u_MaxDisplace : f32;\\n [[offset 32]] u_Clustering : f32;\\n};\\n[[binding 0, set 0]] var gWebGPUUniformParams : GWebGPUParams;\\ntype GWebGPUBuffer0 = [[block]] struct {\\n [[offset 0]] u_Data : [[stride 16]] array>;\\n};\\n[[binding 1, set 0]] var gWebGPUBuffer0 : GWebGPUBuffer0;\\ntype GWebGPUBuffer1 = [[block]] struct {\\n [[offset 0]] u_AttributeArray : [[stride 16]] array>;\\n};\\n[[binding 2, set 0]] var gWebGPUBuffer1 : GWebGPUBuffer1;\\ntype GWebGPUBuffer2 = [[block]] struct {\\n [[offset 0]] u_ClusterCenters : [[stride 16]] array>;\\n};\\n[[binding 3, set 0]] var gWebGPUBuffer2 : GWebGPUBuffer2;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nfn calcRepulsive(i : i32, currentNode : vec4) -> vec2 {var dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nfor (var j : i32 = 0; j < __DefineValuePlaceholder__VERTEX_COUNT; j = j + 1) {if (i != j) {var nextNode : vec4 = gWebGPUBuffer0.u_Data[j];\\nvar xDist : f32 = currentNode.x - nextNode.x;\\nvar yDist : f32 = currentNode.y - nextNode.y;\\nvar dist : f32 = ((xDist * xDist) + (yDist * yDist)) + 0.01;\\nvar param : f32 = gWebGPUUniformParams.u_K2 / dist;\\nif (dist > 0.0) {dx = dx + param * xDist;\\ndy = dy + param * yDist;\\nif ((xDist == 0.0) && (yDist == 0.0)) {var sign : f32 = select(1.0, -1.0, i < j);\\ndx = dx + param * std::sign;\\ndy = dy + param * std::sign;}}}}\\nreturn vec2(dx, dy);}\\nfn calcGravity(currentNode : vec4, nodeAttributes : vec4) -> vec2 {var dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nvar vx : f32 = currentNode.x - gWebGPUUniformParams.u_Center.x;\\nvar vy : f32 = currentNode.y - gWebGPUUniformParams.u_Center.y;\\nvar gf : f32 = (0.01 * gWebGPUUniformParams.u_K) * gWebGPUUniformParams.u_Gravity;\\ndx = gf * vx;\\ndy = gf * vy;\\nif (gWebGPUUniformParams.u_Clustering == 1.0) {var clusterIdx : i32 = i32(nodeAttributes.x);\\nvar center : vec4 = gWebGPUBuffer2.u_ClusterCenters[clusterIdx];\\nvar cvx : f32 = currentNode.x - center.x;\\nvar cvy : f32 = currentNode.y - center.y;\\nvar dist : f32 = std::sqrt((cvx * cvx) + (cvy * cvy)) + 0.01;\\nvar parma : f32 = (gWebGPUUniformParams.u_K * gWebGPUUniformParams.u_ClusterGravity) / dist;\\ndx = dx + parma * cvx;\\ndy = dy + parma * cvy;}\\nreturn vec2(dx, dy);}\\nfn calcAttractive(i : i32, currentNode : vec4) -> vec2 {var dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nvar arr_offset : i32 = i32(std::floor(currentNode.z + 0.5));\\nvar length : i32 = i32(std::floor(currentNode.w + 0.5));\\nvar node_buffer : vec4;\\nfor (var p : i32 = 0; p < __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX; p = p + 1) {if (p >= length) {break;}\\nvar arr_idx : i32 = arr_offset + i32(p);\\nvar buf_offset : i32 = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[i32(arr_idx / 4)];}\\nvar float_j : f32 = select(node_buffer.x, select(node_buffer.y, select(node_buffer.z, node_buffer.w, buf_offset == 2), buf_offset == 1), buf_offset == 0);\\nvar nextNode : vec4 = gWebGPUBuffer0.u_Data[i32(float_j)];\\nvar xDist : f32 = currentNode.x - nextNode.x;\\nvar yDist : f32 = currentNode.y - nextNode.y;\\nvar dist : f32 = std::sqrt((xDist * xDist) + (yDist * yDist)) + 0.01;\\nvar attractiveF : f32 = dist / gWebGPUUniformParams.u_K;\\nif (dist > 0.0) {dx = dx - xDist * attractiveF;\\ndy = dy - yDist * attractiveF;\\nif ((xDist == 0.0) && (yDist == 0.0)) {var sign : f32 = select(1.0, -1.0, i < i32(float_j));\\ndx = dx - std::sign * attractiveF;\\ndy = dy - std::sign * attractiveF;}}}\\nreturn vec2(dx, dy);}\\nfn main() -> void {var i : i32 = globalInvocationID.x;\\nvar currentNode : vec4 = gWebGPUBuffer0.u_Data[i];\\nvar dx : f32 = 0.0;\\nvar dy : f32 = 0.0;\\nif (i >= __DefineValuePlaceholder__VERTEX_COUNT) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvar nodeAttributes : vec4 = gWebGPUBuffer1.u_AttributeArray[i];\\nif ((nodeAttributes.y != 0.0) && (nodeAttributes.z != 0.0)) {gWebGPUBuffer0.u_Data[i] = vec4(nodeAttributes.y, nodeAttributes.z, currentNode.z, currentNode.w);\\nreturn ;}\\nvar repulsive : vec2 = calcRepulsive(i, currentNode);\\ndx = dx + repulsive.x;\\ndy = dy + repulsive.y;\\nvar attractive : vec2 = calcAttractive(i, currentNode);\\ndx = dx + attractive.x;\\ndy = dy + attractive.y;\\nvar gravity : vec2 = calcGravity(currentNode, nodeAttributes);\\ndx = dx - gravity.x;\\ndy = dy - gravity.y;\\ndx = dx * gWebGPUUniformParams.u_Speed;\\ndy = dy * gWebGPUUniformParams.u_Speed;\\nvar distLength : f32 = std::sqrt((dx * dx) + (dy * dy));\\nif (distLength > 0.0) {var limitedDist : f32 = std::min(gWebGPUUniformParams.u_MaxDisplace * gWebGPUUniformParams.u_Speed, distLength);\\ngWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + ((dx / distLength) * limitedDist), currentNode.y + ((dy / distLength) * limitedDist), currentNode.z, currentNode.w);}\\nreturn;}\\n\\nentry_point compute as \\"main\\" = main;\\n","GLSL450":"\\n\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\nivec3 globalInvocationID = ivec3(gl_GlobalInvocationID);\\nivec3 workGroupSize = ivec3(1,1,1);\\nivec3 workGroupID = ivec3(gl_WorkGroupID);\\nivec3 localInvocationID = ivec3(gl_LocalInvocationID);\\nivec3 numWorkGroups = ivec3(gl_NumWorkGroups);\\nint localInvocationIndex = int(gl_LocalInvocationIndex);\\n\\nlayout(std140, set = 0, binding = 0) uniform GWebGPUParams {\\n float u_K;\\n float u_K2;\\n vec2 u_Center;\\n float u_Gravity;\\n float u_ClusterGravity;\\n float u_Speed;\\n float u_MaxDisplace;\\n float u_Clustering;\\n} gWebGPUUniformParams;\\nlayout(std430, set = 0, binding = 1) buffer GWebGPUBuffer0 {\\n vec4 u_Data[];\\n} gWebGPUBuffer0;\\n\\nlayout(std430, set = 0, binding = 2) buffer readonly GWebGPUBuffer1 {\\n vec4 u_AttributeArray[];\\n} gWebGPUBuffer1;\\n\\nlayout(std430, set = 0, binding = 3) buffer readonly GWebGPUBuffer2 {\\n vec4 u_ClusterCenters[];\\n} gWebGPUBuffer2;\\n\\n\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\nlayout (\\n local_size_x = 1,\\n local_size_y = 1,\\n local_size_z = 1\\n) in;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nvec2 calcRepulsive(int i, vec4 currentNode) {float dx = 0.0;\\nfloat dy = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = gWebGPUBuffer0.u_Data[j];\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = ((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat param = gWebGPUUniformParams.u_K2 / dist;\\nif (dist > 0.0) {dx += param * xDist;\\ndy += param * yDist;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < j) ? (1.0) : (-1.0);\\ndx += param * sign;\\ndy += param * sign;}}}}\\nreturn vec2(dx, dy);}\\nvec2 calcGravity(vec4 currentNode, vec4 nodeAttributes) {float dx = 0.0;\\nfloat dy = 0.0;\\nfloat vx = currentNode.x - gWebGPUUniformParams.u_Center.x;\\nfloat vy = currentNode.y - gWebGPUUniformParams.u_Center.y;\\nfloat gf = (0.01 * gWebGPUUniformParams.u_K) * gWebGPUUniformParams.u_Gravity;\\ndx = gf * vx;\\ndy = gf * vy;\\nif (gWebGPUUniformParams.u_Clustering == 1.0) {int clusterIdx = int(nodeAttributes.x);\\nvec4 center = gWebGPUBuffer2.u_ClusterCenters[clusterIdx];\\nfloat cvx = currentNode.x - center.x;\\nfloat cvy = currentNode.y - center.y;\\nfloat dist = sqrt((cvx * cvx) + (cvy * cvy)) + 0.01;\\nfloat parma = (gWebGPUUniformParams.u_K * gWebGPUUniformParams.u_ClusterGravity) / dist;\\ndx += parma * cvx;\\ndy += parma * cvy;}\\nreturn vec2(dx, dy);}\\nvec2 calcAttractive(int i, vec4 currentNode) {float dx = 0.0;\\nfloat dy = 0.0;\\nint arr_offset = int(floor(currentNode.z + 0.5));\\nint length = int(floor(currentNode.w + 0.5));\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + int(p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[int(arr_idx / 4)];}\\nfloat float_j = (buf_offset == 0) ? (node_buffer.x) : ((buf_offset == 1) ? (node_buffer.y) : ((buf_offset == 2) ? (node_buffer.z) : (node_buffer.w)));\\nvec4 nextNode = gWebGPUBuffer0.u_Data[int(float_j)];\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = sqrt((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat attractiveF = dist / gWebGPUUniformParams.u_K;\\nif (dist > 0.0) {dx -= xDist * attractiveF;\\ndy -= yDist * attractiveF;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < int(float_j)) ? (1.0) : (-1.0);\\ndx -= sign * attractiveF;\\ndy -= sign * attractiveF;}}}\\nreturn vec2(dx, dy);}\\nvoid main() {int i = globalInvocationID.x;\\nvec4 currentNode = gWebGPUBuffer0.u_Data[i];\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nif (i >= VERTEX_COUNT) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvec4 nodeAttributes = gWebGPUBuffer1.u_AttributeArray[i];\\nif ((nodeAttributes.y != 0.0) && (nodeAttributes.z != 0.0)) {gWebGPUBuffer0.u_Data[i] = vec4(nodeAttributes.y, nodeAttributes.z, currentNode.z, currentNode.w);\\nreturn ;}\\nvec2 repulsive = calcRepulsive(i, currentNode);\\ndx += repulsive.x;\\ndy += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode);\\ndx += attractive.x;\\ndy += attractive.y;\\nvec2 gravity = calcGravity(currentNode, nodeAttributes);\\ndx -= gravity.x;\\ndy -= gravity.y;\\ndx *= gWebGPUUniformParams.u_Speed;\\ndy *= gWebGPUUniformParams.u_Speed;\\nfloat distLength = sqrt((dx * dx) + (dy * dy));\\nif (distLength > 0.0) {float limitedDist = min(gWebGPUUniformParams.u_MaxDisplace * gWebGPUUniformParams.u_Speed, distLength);\\ngWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + ((dx / distLength) * limitedDist), currentNode.y + ((dy / distLength) * limitedDist), currentNode.z, currentNode.w);}}\\n","GLSL100":"\\n\\nfloat epsilon = 0.00001;\\nvec2 addrTranslation_1Dto2D(float address1D, vec2 texSize) {\\n vec2 conv_const = vec2(1.0 / texSize.x, 1.0 / (texSize.x * texSize.y));\\n vec2 normAddr2D = float(address1D) * conv_const;\\n return vec2(fract(normAddr2D.x + epsilon), normAddr2D.y);\\n}\\n\\nvoid barrier() {}\\n \\n\\nuniform vec2 u_OutputTextureSize;\\nuniform int u_OutputTexelCount;\\nvarying vec2 v_TexCoord;\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\n\\nuniform sampler2D u_Data;\\nuniform vec2 u_DataSize;\\nvec4 getDatau_Data(vec2 address2D) {\\n return vec4(texture2D(u_Data, address2D).rgba);\\n}\\nvec4 getDatau_Data(float address1D) {\\n return getDatau_Data(addrTranslation_1Dto2D(address1D, u_DataSize));\\n}\\nvec4 getDatau_Data(int address1D) {\\n return getDatau_Data(float(address1D));\\n}\\nuniform float u_K;\\nuniform float u_K2;\\nuniform vec2 u_Center;\\nuniform float u_Gravity;\\nuniform float u_ClusterGravity;\\nuniform float u_Speed;\\nuniform float u_MaxDisplace;\\nuniform float u_Clustering;\\nuniform sampler2D u_AttributeArray;\\nuniform vec2 u_AttributeArraySize;\\nvec4 getDatau_AttributeArray(vec2 address2D) {\\n return vec4(texture2D(u_AttributeArray, address2D).rgba);\\n}\\nvec4 getDatau_AttributeArray(float address1D) {\\n return getDatau_AttributeArray(addrTranslation_1Dto2D(address1D, u_AttributeArraySize));\\n}\\nvec4 getDatau_AttributeArray(int address1D) {\\n return getDatau_AttributeArray(float(address1D));\\n}\\nuniform sampler2D u_ClusterCenters;\\nuniform vec2 u_ClusterCentersSize;\\nvec4 getDatau_ClusterCenters(vec2 address2D) {\\n return vec4(texture2D(u_ClusterCenters, address2D).rgba);\\n}\\nvec4 getDatau_ClusterCenters(float address1D) {\\n return getDatau_ClusterCenters(addrTranslation_1Dto2D(address1D, u_ClusterCentersSize));\\n}\\nvec4 getDatau_ClusterCenters(int address1D) {\\n return getDatau_ClusterCenters(float(address1D));\\n}\\nvec2 calcRepulsive(int i, vec4 currentNode) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = getDatau_Data(j);\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = ((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat param = u_K2 / dist;\\nif (dist > 0.0) {dx += param * xDist;\\ndy += param * yDist;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < j) ? (1.0) : (-1.0);\\ndx += param * sign;\\ndy += param * sign;}}}}\\nreturn vec2(dx, dy);}\\nvec2 calcGravity(vec4 currentNode, vec4 nodeAttributes) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nfloat vx = currentNode.x - u_Center.x;\\nfloat vy = currentNode.y - u_Center.y;\\nfloat gf = (0.01 * u_K) * u_Gravity;\\ndx = gf * vx;\\ndy = gf * vy;\\nif (u_Clustering == 1.0) {int clusterIdx = int(nodeAttributes.x);\\nvec4 center = getDatau_ClusterCenters(clusterIdx);\\nfloat cvx = currentNode.x - center.x;\\nfloat cvy = currentNode.y - center.y;\\nfloat dist = sqrt((cvx * cvx) + (cvy * cvy)) + 0.01;\\nfloat parma = (u_K * u_ClusterGravity) / dist;\\ndx += parma * cvx;\\ndy += parma * cvy;}\\nreturn vec2(dx, dy);}\\nvec2 calcAttractive(int i, vec4 currentNode) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nint arr_offset = int(floor(currentNode.z + 0.5));\\nint length = int(floor(currentNode.w + 0.5));\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + int(p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = getDatau_Data(int(arr_idx / 4));}\\nfloat float_j = (buf_offset == 0) ? (node_buffer.x) : ((buf_offset == 1) ? (node_buffer.y) : ((buf_offset == 2) ? (node_buffer.z) : (node_buffer.w)));\\nvec4 nextNode = getDatau_Data(int(float_j));\\nfloat xDist = currentNode.x - nextNode.x;\\nfloat yDist = currentNode.y - nextNode.y;\\nfloat dist = sqrt((xDist * xDist) + (yDist * yDist)) + 0.01;\\nfloat attractiveF = dist / u_K;\\nif (dist > 0.0) {dx -= xDist * attractiveF;\\ndy -= yDist * attractiveF;\\nif ((xDist == 0.0) && (yDist == 0.0)) {float sign = (i < int(float_j)) ? (1.0) : (-1.0);\\ndx -= sign * attractiveF;\\ndy -= sign * attractiveF;}}}\\nreturn vec2(dx, dy);}\\nvoid main() {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nint i = globalInvocationID.x;\\nvec4 currentNode = getDatau_Data(i);\\nfloat dx = 0.0;\\nfloat dy = 0.0;\\nif (i >= VERTEX_COUNT) {gl_FragColor = vec4(currentNode);\\nreturn ;}\\nvec4 nodeAttributes = getDatau_AttributeArray(i);\\nif ((nodeAttributes.y != 0.0) && (nodeAttributes.z != 0.0)) {gl_FragColor = vec4(vec4(nodeAttributes.y, nodeAttributes.z, currentNode.z, currentNode.w));\\nreturn ;}\\nvec2 repulsive = calcRepulsive(i, currentNode);\\ndx += repulsive.x;\\ndy += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode);\\ndx += attractive.x;\\ndy += attractive.y;\\nvec2 gravity = calcGravity(currentNode, nodeAttributes);\\ndx -= gravity.x;\\ndy -= gravity.y;\\ndx *= u_Speed;\\ndy *= u_Speed;\\nfloat distLength = sqrt((dx * dx) + (dy * dy));\\nif (distLength > 0.0) {float limitedDist = min(u_MaxDisplace * u_Speed, distLength);\\ngl_FragColor = vec4(vec4(currentNode.x + ((dx / distLength) * limitedDist), currentNode.y + ((dy / distLength) * limitedDist), currentNode.z, currentNode.w));}if (gWebGPUDebug) {\\n gl_FragColor = gWebGPUDebugOutput;\\n}}\\n"},"context":{"name":"","dispatch":[1,1,1],"threadGroupSize":[1,1,1],"maxIteration":1,"defines":[{"name":"MAX_EDGE_PER_VERTEX","type":"Float","runtime":true},{"name":"VERTEX_COUNT","type":"Float","runtime":true}],"uniforms":[{"name":"u_Data","type":"vec4[]","storageClass":"StorageBuffer","readonly":false,"writeonly":false,"size":[1,1]},{"name":"u_K","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_K2","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Center","type":"vec2","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Gravity","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_ClusterGravity","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Speed","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_MaxDisplace","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_Clustering","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_AttributeArray","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_ClusterCenters","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]}],"globalDeclarations":[],"output":{"name":"u_Data","size":[1,1],"length":1},"needPingpong":true}}` export const clusterCode = ` import { globalInvocationID } from 'g-webgpu'; diff --git a/src/layout/gpu/gForce.ts b/src/layout/gpu/gForce.ts index dda6b18f9e3..9809b38b159 100644 --- a/src/layout/gpu/gForce.ts +++ b/src/layout/gpu/gForce.ts @@ -57,7 +57,7 @@ export class GForceGPULayout extends Base { public maxSpeed: number = 1000; /** 一次迭代的平均移动距离小于该值时停止迭代 */ - public minMovement: number = 1; + public minMovement: number = 0.5; /** 迭代中衰减 */ public interval: number = 0.02; @@ -226,6 +226,9 @@ export class GForceGPULayout extends Base { const centerXs: number[] = []; const centerYs: number[] = []; const centerGravities: number[] = []; + const fxs: number[] = []; + const fys: number[] = []; + if (!self.getMass) { self.getMass = d => { return self.degrees[self.nodeIdxMap[d.id]] || 1; @@ -252,19 +255,28 @@ export class GForceGPULayout extends Base { centerXs.push(nodeGravity[0]); centerYs.push(nodeGravity[1]); centerGravities.push(nodeGravity[2]); + if (isNumber(node.fx) && isNumber(node.fy)) { + fxs.push(node.fx || 0.001); + fys.push(node.fy || 0.001); + } else { + fxs.push(0); + fys.push(0); + } }); // 每个节点的额外属性占两个数组各一格,nodeAttributeArray1 中是:mass, degree, nodeSterngth, 0 const nodeAttributeArray1 = arrayToTextureData([ masses, self.degrees, - nodeStrengths + nodeStrengths, + fxs ]); // nodeAttributeArray2 中是:centerX, centerY, gravity, 0, const nodeAttributeArray2 = arrayToTextureData([ centerXs, centerYs, - centerGravities + centerGravities, + fys ]); const workerEnabled = self.workerEnabled; diff --git a/src/layout/gpu/gForceShader.ts b/src/layout/gpu/gForceShader.ts index c3c58375752..d7a704244ca 100644 --- a/src/layout/gpu/gForceShader.ts +++ b/src/layout/gpu/gForceShader.ts @@ -51,7 +51,7 @@ class GGForce { const vx = currentNode[0] - nextNode[0]; const vy = currentNode[1] - nextNode[1]; const dist = sqrt(vx * vx + vy * vy) + 0.01; - const n_dist = dist * this.u_coulombDisScale; + const n_dist = (dist + 0.1) * this.u_coulombDisScale; const direx = vx / dist; const direy = vy / dist; const attributesi = this.u_NodeAttributeArray1[i]; @@ -61,7 +61,7 @@ class GGForce { const nodeStrengthj = attributesj[2]; const nodeStrength = (nodeStrengthi + nodeStrengthj) / 2; // const param = nodeStrength * this.u_factor / (n_dist * n_dist * massi); - const param = 1000 * this.u_factor / (n_dist * n_dist); + const param = nodeStrength * this.u_factor / (n_dist * n_dist); ax += direx * param; ay += direy * param; } @@ -134,7 +134,9 @@ class GGForce { } // 每个节点属性占两个数组中各一格 + // [mass, degree, nodeStrength, fx] const nodeAttributes1 = this.u_NodeAttributeArray1[i]; + // [centerX, centerY, centerGravity, fy] const nodeAttributes2 = this.u_NodeAttributeArray2[i]; // repulsive @@ -164,16 +166,24 @@ class GGForce { } // move - const distx = vx * this.u_interval; - const disty = vy * this.u_interval; - const distLength = sqrt(distx * distx + disty * disty); - - this.u_Data[i] = [ - currentNode[0] + distx, - currentNode[1] + disty, - currentNode[2], - distLength - ]; + if (nodeAttributes1[3] != 0 && nodeAttributes2[3] != 0) { + this.u_Data[i] = [ + nodeAttributes1[3], + nodeAttributes2[3], + currentNode[2], + 0 + ]; + } else { + const distx = vx * this.u_interval; + const disty = vy * this.u_interval; + const distLength = sqrt(distx * distx + disty * disty); + this.u_Data[i] = [ + currentNode[0] + distx, + currentNode[1] + disty, + currentNode[2], + distLength + ]; + } // the avarage move distance // need to share memory @@ -182,7 +192,7 @@ class GGForce { } ` -export const gForceBundle = `{"shaders":{"WGSL":"import \\"GLSL.std.450\\" as std;\\n\\n\\n# var gWebGPUDebug : bool = false;\\n# var gWebGPUDebugOutput : vec4 = vec4(0.0);\\n\\n[[builtin global_invocation_id]] var globalInvocationID : vec3;\\n# [[builtin work_group_size]] var workGroupSize : vec3;\\n# [[builtin work_group_id]] var workGroupID : vec3;\\n[[builtin local_invocation_id]] var localInvocationID : vec3;\\n# [[builtin num_work_groups]] var numWorkGroups : vec3;\\n[[builtin local_invocation_idx]] var localInvocationIndex : u32;\\n\\ntype GWebGPUParams = [[block]] struct {\\n [[offset 0]] u_damping : f32;\\n [[offset 4]] u_maxSpeed : f32;\\n [[offset 8]] u_minMovement : f32;\\n \\n [[offset 12]] u_coulombDisScale : f32;\\n [[offset 16]] u_factor : f32;\\n \\n \\n [[offset 20]] u_interval : f32;\\n};\\n[[binding 0, set 0]] var gWebGPUUniformParams : GWebGPUParams;\\ntype GWebGPUBuffer0 = [[block]] struct {\\n [[offset 0]] u_Data : [[stride 16]] array>;\\n};\\n[[binding 1, set 0]] var gWebGPUBuffer0 : GWebGPUBuffer0;\\ntype GWebGPUBuffer1 = [[block]] struct {\\n [[offset 0]] u_AveMovement : [[stride 16]] array>;\\n};\\n[[binding 2, set 0]] var gWebGPUBuffer1 : GWebGPUBuffer1;\\ntype GWebGPUBuffer2 = [[block]] struct {\\n [[offset 0]] u_NodeAttributeArray1 : [[stride 16]] array>;\\n};\\n[[binding 3, set 0]] var gWebGPUBuffer2 : GWebGPUBuffer2;\\ntype GWebGPUBuffer3 = [[block]] struct {\\n [[offset 0]] u_NodeAttributeArray2 : [[stride 16]] array>;\\n};\\n[[binding 4, set 0]] var gWebGPUBuffer3 : GWebGPUBuffer3;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nfn unpack_float(packedValue : f32) -> vec2 {var packedIntValue : i32 = i32(packedValue);\\nvar v0 : i32 = packedIntValue / 1048576;\\nreturn vec2(v0, packedIntValue - (v0 * 1048576));}\\nfn calcRepulsive(i : i32, currentNode : vec4) -> vec2 {var ax : f32 = 0.0;\\nvar ay : f32 = 0.0;\\nfor (var j : i32 = 0; j < __DefineValuePlaceholder__VERTEX_COUNT; j = j + 1) {if (i != j) {var nextNode : vec4 = gWebGPUBuffer0.u_Data[j];\\nvar vx : f32 = currentNode.x - nextNode.x;\\nvar vy : f32 = currentNode.y - nextNode.y;\\nvar dist : f32 = std::sqrt((vx * vx) + (vy * vy)) + 0.01;\\nvar n_dist : f32 = dist * gWebGPUUniformParams.u_coulombDisScale;\\nvar direx : f32 = vx / dist;\\nvar direy : f32 = vy / dist;\\nvar attributesi : vec4 = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvar attributesj : vec4 = gWebGPUBuffer2.u_NodeAttributeArray1[j];\\nvar massi : f32 = attributesi.x;\\nvar nodeStrengthi : f32 = attributesi.z;\\nvar nodeStrengthj : f32 = attributesj.z;\\nvar nodeStrength : f32 = (nodeStrengthi + nodeStrengthj) / 2.0;\\nvar param : f32 = (1000.0 * gWebGPUUniformParams.u_factor) / (n_dist * n_dist);\\nax = ax + direx * param;\\nay = ay + direy * param;}}\\nreturn vec2(ax, ay);}\\nfn calcGravity(i : i32, currentNode : vec4, attributes2 : vec4) -> vec2 {var vx : f32 = currentNode.x - attributes2.x;\\nvar vy : f32 = currentNode.y - attributes2.y;\\nvar ax : f32 = vx * attributes2.z;\\nvar ay : f32 = vy * attributes2.z;\\nreturn vec2(ax, ay);}\\nfn calcAttractive(i : i32, currentNode : vec4, attributes1 : vec4) -> vec2 {var mass : f32 = attributes1.x;\\nvar ax : f32 = 0.0;\\nvar ay : f32 = 0.0;\\nvar compressed : vec2 = unpack_float(currentNode.z);\\nvar length : i32 = compressed.x;\\nvar arr_offset : i32 = compressed.y;\\nvar node_buffer : vec4;\\nfor (var p : i32 = 0; p < __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX; p = p + 1) {if (p >= length) {break;}\\nvar arr_idx : i32 = arr_offset + (4 * p);\\nvar buf_offset : i32 = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[i32(arr_idx / 4)];}\\nvar float_j : f32 = node_buffer.x;\\nvar nextNode : vec4 = gWebGPUBuffer0.u_Data[i32(float_j)];\\nvar vx : f32 = nextNode.x - currentNode.x;\\nvar vy : f32 = nextNode.y - currentNode.y;\\nvar dist : f32 = std::sqrt((vx * vx) + (vy * vy)) + 0.01;\\nvar direx : f32 = vx / dist;\\nvar direy : f32 = vy / dist;\\nvar edgeLength : f32 = node_buffer.y;\\nvar edgeStrength : f32 = node_buffer.z;\\nvar diff : f32 = edgeLength - dist;\\nvar param : f32 = (diff * edgeStrength) / mass;\\nax = ax - direx * param;\\nay = ay - direy * param;}\\nreturn vec2(ax, ay);}\\nfn main() -> void {var i : i32 = globalInvocationID.x;\\nvar currentNode : vec4 = gWebGPUBuffer0.u_Data[i];\\nvar movement : vec4 = gWebGPUBuffer1.u_AveMovement[0];\\nvar ax : f32 = 0.0;\\nvar ay : f32 = 0.0;\\nif ((i >= __DefineValuePlaceholder__VERTEX_COUNT) || (movement.x < gWebGPUUniformParams.u_minMovement)) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvar nodeAttributes1 : vec4 = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvar nodeAttributes2 : vec4 = gWebGPUBuffer3.u_NodeAttributeArray2[i];\\nvar repulsive : vec2 = calcRepulsive(i, currentNode);\\nax = ax + repulsive.x;\\nay = ay + repulsive.y;\\nvar attractive : vec2 = calcAttractive(i, currentNode, nodeAttributes1);\\nax = ax + attractive.x;\\nay = ay + attractive.y;\\nvar gravity : vec2 = calcGravity(i, currentNode, nodeAttributes2);\\nax = ax - gravity.x;\\nay = ay - gravity.y;\\nvar param : f32 = gWebGPUUniformParams.u_interval * gWebGPUUniformParams.u_damping;\\nvar vx : f32 = ax * param;\\nvar vy : f32 = ay * param;\\nvar vlength : f32 = std::sqrt((vx * vx) + (vy * vy)) + 0.0001;\\nif (vlength > gWebGPUUniformParams.u_maxSpeed) {var param2 : f32 = gWebGPUUniformParams.u_maxSpeed / vlength;\\nvx = param2 * vx;\\nvy = param2 * vy;}\\nvar distx : f32 = vx * gWebGPUUniformParams.u_interval;\\nvar disty : f32 = vy * gWebGPUUniformParams.u_interval;\\nvar distLength : f32 = std::sqrt((distx * distx) + (disty * disty));\\ngWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + distx, currentNode.y + disty, currentNode.z, distLength);\\nreturn;}\\n\\nentry_point compute as \\"main\\" = main;\\n","GLSL450":"\\n\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\nivec3 globalInvocationID = ivec3(gl_GlobalInvocationID);\\nivec3 workGroupSize = ivec3(1,1,1);\\nivec3 workGroupID = ivec3(gl_WorkGroupID);\\nivec3 localInvocationID = ivec3(gl_LocalInvocationID);\\nivec3 numWorkGroups = ivec3(gl_NumWorkGroups);\\nint localInvocationIndex = int(gl_LocalInvocationIndex);\\n\\nlayout(std140, set = 0, binding = 0) uniform GWebGPUParams {\\n float u_damping;\\n float u_maxSpeed;\\n float u_minMovement;\\n \\n float u_coulombDisScale;\\n float u_factor;\\n \\n \\n float u_interval;\\n} gWebGPUUniformParams;\\nlayout(std430, set = 0, binding = 1) buffer GWebGPUBuffer0 {\\n vec4 u_Data[];\\n} gWebGPUBuffer0;\\n\\nlayout(std430, set = 0, binding = 2) buffer readonly GWebGPUBuffer1 {\\n vec4 u_AveMovement[];\\n} gWebGPUBuffer1;\\n\\nlayout(std430, set = 0, binding = 3) buffer readonly GWebGPUBuffer2 {\\n vec4 u_NodeAttributeArray1[];\\n} gWebGPUBuffer2;\\n\\nlayout(std430, set = 0, binding = 4) buffer readonly GWebGPUBuffer3 {\\n vec4 u_NodeAttributeArray2[];\\n} gWebGPUBuffer3;\\n\\n\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\n#define SHIFT_20 1048576.0\\nlayout (\\n local_size_x = 1,\\n local_size_y = 1,\\n local_size_z = 1\\n) in;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nivec2 unpack_float(float packedValue) {int packedIntValue = int(packedValue);\\nint v0 = packedIntValue / int(SHIFT_20);\\nreturn ivec2(v0, packedIntValue - (v0 * int(SHIFT_20)));}\\nvec2 calcRepulsive(int i, vec4 currentNode) {float ax = 0.0;\\nfloat ay = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = gWebGPUBuffer0.u_Data[j];\\nfloat vx = currentNode.x - nextNode.x;\\nfloat vy = currentNode.y - nextNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat n_dist = dist * gWebGPUUniformParams.u_coulombDisScale;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nvec4 attributesi = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvec4 attributesj = gWebGPUBuffer2.u_NodeAttributeArray1[j];\\nfloat massi = attributesi.x;\\nfloat nodeStrengthi = attributesi.z;\\nfloat nodeStrengthj = attributesj.z;\\nfloat nodeStrength = (nodeStrengthi + nodeStrengthj) / 2.0;\\nfloat param = (1000.0 * gWebGPUUniformParams.u_factor) / (n_dist * n_dist);\\nax += direx * param;\\nay += direy * param;}}\\nreturn vec2(ax, ay);}\\nvec2 calcGravity(int i, vec4 currentNode, vec4 attributes2) {float vx = currentNode.x - attributes2.x;\\nfloat vy = currentNode.y - attributes2.y;\\nfloat ax = vx * attributes2.z;\\nfloat ay = vy * attributes2.z;\\nreturn vec2(ax, ay);}\\nvec2 calcAttractive(int i, vec4 currentNode, vec4 attributes1) {float mass = attributes1.x;\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nivec2 compressed = unpack_float(currentNode.z);\\nint length = compressed.x;\\nint arr_offset = compressed.y;\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + (4 * p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[int(arr_idx / 4)];}\\nfloat float_j = node_buffer.x;\\nvec4 nextNode = gWebGPUBuffer0.u_Data[int(float_j)];\\nfloat vx = nextNode.x - currentNode.x;\\nfloat vy = nextNode.y - currentNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nfloat edgeLength = node_buffer.y;\\nfloat edgeStrength = node_buffer.z;\\nfloat diff = edgeLength - dist;\\nfloat param = (diff * edgeStrength) / mass;\\nax -= direx * param;\\nay -= direy * param;}\\nreturn vec2(ax, ay);}\\nvoid main() {int i = globalInvocationID.x;\\nvec4 currentNode = gWebGPUBuffer0.u_Data[i];\\nvec4 movement = gWebGPUBuffer1.u_AveMovement[0];\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nif ((i >= VERTEX_COUNT) || (movement.x < gWebGPUUniformParams.u_minMovement)) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvec4 nodeAttributes1 = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvec4 nodeAttributes2 = gWebGPUBuffer3.u_NodeAttributeArray2[i];\\nvec2 repulsive = calcRepulsive(i, currentNode);\\nax += repulsive.x;\\nay += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode, nodeAttributes1);\\nax += attractive.x;\\nay += attractive.y;\\nvec2 gravity = calcGravity(i, currentNode, nodeAttributes2);\\nax -= gravity.x;\\nay -= gravity.y;\\nfloat param = gWebGPUUniformParams.u_interval * gWebGPUUniformParams.u_damping;\\nfloat vx = ax * param;\\nfloat vy = ay * param;\\nfloat vlength = sqrt((vx * vx) + (vy * vy)) + 0.0001;\\nif (vlength > gWebGPUUniformParams.u_maxSpeed) {float param2 = gWebGPUUniformParams.u_maxSpeed / vlength;\\nvx = param2 * vx;\\nvy = param2 * vy;}\\nfloat distx = vx * gWebGPUUniformParams.u_interval;\\nfloat disty = vy * gWebGPUUniformParams.u_interval;\\nfloat distLength = sqrt((distx * distx) + (disty * disty));\\ngWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + distx, currentNode.y + disty, currentNode.z, distLength);}\\n","GLSL100":"\\n\\nfloat epsilon = 0.00001;\\nvec2 addrTranslation_1Dto2D(float address1D, vec2 texSize) {\\n vec2 conv_const = vec2(1.0 / texSize.x, 1.0 / (texSize.x * texSize.y));\\n vec2 normAddr2D = float(address1D) * conv_const;\\n return vec2(fract(normAddr2D.x + epsilon), normAddr2D.y);\\n}\\n\\nvoid barrier() {}\\n \\n\\nuniform vec2 u_OutputTextureSize;\\nuniform int u_OutputTexelCount;\\nvarying vec2 v_TexCoord;\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\n#define SHIFT_20 1048576.0\\n\\nuniform sampler2D u_Data;\\nuniform vec2 u_DataSize;\\nvec4 getDatau_Data(vec2 address2D) {\\n return vec4(texture2D(u_Data, address2D).rgba);\\n}\\nvec4 getDatau_Data(float address1D) {\\n return getDatau_Data(addrTranslation_1Dto2D(address1D, u_DataSize));\\n}\\nvec4 getDatau_Data(int address1D) {\\n return getDatau_Data(float(address1D));\\n}\\nuniform float u_damping;\\nuniform float u_maxSpeed;\\nuniform float u_minMovement;\\nuniform sampler2D u_AveMovement;\\nuniform vec2 u_AveMovementSize;\\nvec4 getDatau_AveMovement(vec2 address2D) {\\n return vec4(texture2D(u_AveMovement, address2D).rgba);\\n}\\nvec4 getDatau_AveMovement(float address1D) {\\n return getDatau_AveMovement(addrTranslation_1Dto2D(address1D, u_AveMovementSize));\\n}\\nvec4 getDatau_AveMovement(int address1D) {\\n return getDatau_AveMovement(float(address1D));\\n}\\nuniform float u_coulombDisScale;\\nuniform float u_factor;\\nuniform sampler2D u_NodeAttributeArray1;\\nuniform vec2 u_NodeAttributeArray1Size;\\nvec4 getDatau_NodeAttributeArray1(vec2 address2D) {\\n return vec4(texture2D(u_NodeAttributeArray1, address2D).rgba);\\n}\\nvec4 getDatau_NodeAttributeArray1(float address1D) {\\n return getDatau_NodeAttributeArray1(addrTranslation_1Dto2D(address1D, u_NodeAttributeArray1Size));\\n}\\nvec4 getDatau_NodeAttributeArray1(int address1D) {\\n return getDatau_NodeAttributeArray1(float(address1D));\\n}\\nuniform sampler2D u_NodeAttributeArray2;\\nuniform vec2 u_NodeAttributeArray2Size;\\nvec4 getDatau_NodeAttributeArray2(vec2 address2D) {\\n return vec4(texture2D(u_NodeAttributeArray2, address2D).rgba);\\n}\\nvec4 getDatau_NodeAttributeArray2(float address1D) {\\n return getDatau_NodeAttributeArray2(addrTranslation_1Dto2D(address1D, u_NodeAttributeArray2Size));\\n}\\nvec4 getDatau_NodeAttributeArray2(int address1D) {\\n return getDatau_NodeAttributeArray2(float(address1D));\\n}\\nuniform float u_interval;\\nivec2 unpack_float(float packedValue) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nint packedIntValue = int(packedValue);\\nint v0 = packedIntValue / int(SHIFT_20);\\nreturn ivec2(v0, packedIntValue - (v0 * int(SHIFT_20)));}\\nvec2 calcRepulsive(int i, vec4 currentNode) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = getDatau_Data(j);\\nfloat vx = currentNode.x - nextNode.x;\\nfloat vy = currentNode.y - nextNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat n_dist = dist * u_coulombDisScale;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nvec4 attributesi = getDatau_NodeAttributeArray1(i);\\nvec4 attributesj = getDatau_NodeAttributeArray1(j);\\nfloat massi = attributesi.x;\\nfloat nodeStrengthi = attributesi.z;\\nfloat nodeStrengthj = attributesj.z;\\nfloat nodeStrength = (nodeStrengthi + nodeStrengthj) / 2.0;\\nfloat param = (1000.0 * u_factor) / (n_dist * n_dist);\\nax += direx * param;\\nay += direy * param;}}\\nreturn vec2(ax, ay);}\\nvec2 calcGravity(int i, vec4 currentNode, vec4 attributes2) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat vx = currentNode.x - attributes2.x;\\nfloat vy = currentNode.y - attributes2.y;\\nfloat ax = vx * attributes2.z;\\nfloat ay = vy * attributes2.z;\\nreturn vec2(ax, ay);}\\nvec2 calcAttractive(int i, vec4 currentNode, vec4 attributes1) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat mass = attributes1.x;\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nivec2 compressed = unpack_float(currentNode.z);\\nint length = compressed.x;\\nint arr_offset = compressed.y;\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + (4 * p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = getDatau_Data(int(arr_idx / 4));}\\nfloat float_j = node_buffer.x;\\nvec4 nextNode = getDatau_Data(int(float_j));\\nfloat vx = nextNode.x - currentNode.x;\\nfloat vy = nextNode.y - currentNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nfloat edgeLength = node_buffer.y;\\nfloat edgeStrength = node_buffer.z;\\nfloat diff = edgeLength - dist;\\nfloat param = (diff * edgeStrength) / mass;\\nax -= direx * param;\\nay -= direy * param;}\\nreturn vec2(ax, ay);}\\nvoid main() {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nint i = globalInvocationID.x;\\nvec4 currentNode = getDatau_Data(i);\\nvec4 movement = getDatau_AveMovement(0.0);\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nif ((i >= VERTEX_COUNT) || (movement.x < u_minMovement)) {gl_FragColor = vec4(currentNode);\\nreturn ;}\\nvec4 nodeAttributes1 = getDatau_NodeAttributeArray1(i);\\nvec4 nodeAttributes2 = getDatau_NodeAttributeArray2(i);\\nvec2 repulsive = calcRepulsive(i, currentNode);\\nax += repulsive.x;\\nay += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode, nodeAttributes1);\\nax += attractive.x;\\nay += attractive.y;\\nvec2 gravity = calcGravity(i, currentNode, nodeAttributes2);\\nax -= gravity.x;\\nay -= gravity.y;\\nfloat param = u_interval * u_damping;\\nfloat vx = ax * param;\\nfloat vy = ay * param;\\nfloat vlength = sqrt((vx * vx) + (vy * vy)) + 0.0001;\\nif (vlength > u_maxSpeed) {float param2 = u_maxSpeed / vlength;\\nvx = param2 * vx;\\nvy = param2 * vy;}\\nfloat distx = vx * u_interval;\\nfloat disty = vy * u_interval;\\nfloat distLength = sqrt((distx * distx) + (disty * disty));\\ngl_FragColor = vec4(vec4(currentNode.x + distx, currentNode.y + disty, currentNode.z, distLength));if (gWebGPUDebug) {\\n gl_FragColor = gWebGPUDebugOutput;\\n}}\\n"},"context":{"name":"","dispatch":[1,1,1],"threadGroupSize":[1,1,1],"maxIteration":1,"defines":[{"name":"MAX_EDGE_PER_VERTEX","type":"Float","runtime":true},{"name":"VERTEX_COUNT","type":"Float","runtime":true},{"name":"SHIFT_20","type":"Float","value":1048576,"runtime":false}],"uniforms":[{"name":"u_Data","type":"vec4[]","storageClass":"StorageBuffer","readonly":false,"writeonly":false,"size":[1,1]},{"name":"u_damping","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_maxSpeed","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_minMovement","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_AveMovement","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_coulombDisScale","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_factor","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_NodeAttributeArray1","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_NodeAttributeArray2","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_interval","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]}],"globalDeclarations":[],"output":{"name":"u_Data","size":[1,1],"length":1},"needPingpong":true}}` +export const gForceBundle = `{"shaders":{"WGSL":"import \\"GLSL.std.450\\" as std;\\n\\n\\n# var gWebGPUDebug : bool = false;\\n# var gWebGPUDebugOutput : vec4 = vec4(0.0);\\n\\n[[builtin global_invocation_id]] var globalInvocationID : vec3;\\n# [[builtin work_group_size]] var workGroupSize : vec3;\\n# [[builtin work_group_id]] var workGroupID : vec3;\\n[[builtin local_invocation_id]] var localInvocationID : vec3;\\n# [[builtin num_work_groups]] var numWorkGroups : vec3;\\n[[builtin local_invocation_idx]] var localInvocationIndex : u32;\\n\\ntype GWebGPUParams = [[block]] struct {\\n [[offset 0]] u_damping : f32;\\n [[offset 4]] u_maxSpeed : f32;\\n [[offset 8]] u_minMovement : f32;\\n \\n [[offset 12]] u_coulombDisScale : f32;\\n [[offset 16]] u_factor : f32;\\n \\n \\n [[offset 20]] u_interval : f32;\\n};\\n[[binding 0, set 0]] var gWebGPUUniformParams : GWebGPUParams;\\ntype GWebGPUBuffer0 = [[block]] struct {\\n [[offset 0]] u_Data : [[stride 16]] array>;\\n};\\n[[binding 1, set 0]] var gWebGPUBuffer0 : GWebGPUBuffer0;\\ntype GWebGPUBuffer1 = [[block]] struct {\\n [[offset 0]] u_AveMovement : [[stride 16]] array>;\\n};\\n[[binding 2, set 0]] var gWebGPUBuffer1 : GWebGPUBuffer1;\\ntype GWebGPUBuffer2 = [[block]] struct {\\n [[offset 0]] u_NodeAttributeArray1 : [[stride 16]] array>;\\n};\\n[[binding 3, set 0]] var gWebGPUBuffer2 : GWebGPUBuffer2;\\ntype GWebGPUBuffer3 = [[block]] struct {\\n [[offset 0]] u_NodeAttributeArray2 : [[stride 16]] array>;\\n};\\n[[binding 4, set 0]] var gWebGPUBuffer3 : GWebGPUBuffer3;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nfn unpack_float(packedValue : f32) -> vec2 {var packedIntValue : i32 = i32(packedValue);\\nvar v0 : i32 = packedIntValue / 1048576;\\nreturn vec2(v0, packedIntValue - (v0 * 1048576));}\\nfn calcRepulsive(i : i32, currentNode : vec4) -> vec2 {var ax : f32 = 0.0;\\nvar ay : f32 = 0.0;\\nfor (var j : i32 = 0; j < __DefineValuePlaceholder__VERTEX_COUNT; j = j + 1) {if (i != j) {var nextNode : vec4 = gWebGPUBuffer0.u_Data[j];\\nvar vx : f32 = currentNode.x - nextNode.x;\\nvar vy : f32 = currentNode.y - nextNode.y;\\nvar dist : f32 = std::sqrt((vx * vx) + (vy * vy)) + 0.01;\\nvar n_dist : f32 = (dist + 0.1) * gWebGPUUniformParams.u_coulombDisScale;\\nvar direx : f32 = vx / dist;\\nvar direy : f32 = vy / dist;\\nvar attributesi : vec4 = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvar attributesj : vec4 = gWebGPUBuffer2.u_NodeAttributeArray1[j];\\nvar massi : f32 = attributesi.x;\\nvar nodeStrengthi : f32 = attributesi.z;\\nvar nodeStrengthj : f32 = attributesj.z;\\nvar nodeStrength : f32 = (nodeStrengthi + nodeStrengthj) / 2.0;\\nvar param : f32 = (nodeStrength * gWebGPUUniformParams.u_factor) / (n_dist * n_dist);\\nax = ax + direx * param;\\nay = ay + direy * param;}}\\nreturn vec2(ax, ay);}\\nfn calcGravity(i : i32, currentNode : vec4, attributes2 : vec4) -> vec2 {var vx : f32 = currentNode.x - attributes2.x;\\nvar vy : f32 = currentNode.y - attributes2.y;\\nvar ax : f32 = vx * attributes2.z;\\nvar ay : f32 = vy * attributes2.z;\\nreturn vec2(ax, ay);}\\nfn calcAttractive(i : i32, currentNode : vec4, attributes1 : vec4) -> vec2 {var mass : f32 = attributes1.x;\\nvar ax : f32 = 0.0;\\nvar ay : f32 = 0.0;\\nvar compressed : vec2 = unpack_float(currentNode.z);\\nvar length : i32 = compressed.x;\\nvar arr_offset : i32 = compressed.y;\\nvar node_buffer : vec4;\\nfor (var p : i32 = 0; p < __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX; p = p + 1) {if (p >= length) {break;}\\nvar arr_idx : i32 = arr_offset + (4 * p);\\nvar buf_offset : i32 = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[i32(arr_idx / 4)];}\\nvar float_j : f32 = node_buffer.x;\\nvar nextNode : vec4 = gWebGPUBuffer0.u_Data[i32(float_j)];\\nvar vx : f32 = nextNode.x - currentNode.x;\\nvar vy : f32 = nextNode.y - currentNode.y;\\nvar dist : f32 = std::sqrt((vx * vx) + (vy * vy)) + 0.01;\\nvar direx : f32 = vx / dist;\\nvar direy : f32 = vy / dist;\\nvar edgeLength : f32 = node_buffer.y;\\nvar edgeStrength : f32 = node_buffer.z;\\nvar diff : f32 = edgeLength - dist;\\nvar param : f32 = (diff * edgeStrength) / mass;\\nax = ax - direx * param;\\nay = ay - direy * param;}\\nreturn vec2(ax, ay);}\\nfn main() -> void {var i : i32 = globalInvocationID.x;\\nvar currentNode : vec4 = gWebGPUBuffer0.u_Data[i];\\nvar movement : vec4 = gWebGPUBuffer1.u_AveMovement[0];\\nvar ax : f32 = 0.0;\\nvar ay : f32 = 0.0;\\nif ((i >= __DefineValuePlaceholder__VERTEX_COUNT) || (movement.x < gWebGPUUniformParams.u_minMovement)) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvar nodeAttributes1 : vec4 = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvar nodeAttributes2 : vec4 = gWebGPUBuffer3.u_NodeAttributeArray2[i];\\nvar repulsive : vec2 = calcRepulsive(i, currentNode);\\nax = ax + repulsive.x;\\nay = ay + repulsive.y;\\nvar attractive : vec2 = calcAttractive(i, currentNode, nodeAttributes1);\\nax = ax + attractive.x;\\nay = ay + attractive.y;\\nvar gravity : vec2 = calcGravity(i, currentNode, nodeAttributes2);\\nax = ax - gravity.x;\\nay = ay - gravity.y;\\nvar param : f32 = gWebGPUUniformParams.u_interval * gWebGPUUniformParams.u_damping;\\nvar vx : f32 = ax * param;\\nvar vy : f32 = ay * param;\\nvar vlength : f32 = std::sqrt((vx * vx) + (vy * vy)) + 0.0001;\\nif (vlength > gWebGPUUniformParams.u_maxSpeed) {var param2 : f32 = gWebGPUUniformParams.u_maxSpeed / vlength;\\nvx = param2 * vx;\\nvy = param2 * vy;}\\nvar distx : f32 = vx * gWebGPUUniformParams.u_interval;\\nvar disty : f32 = vy * gWebGPUUniformParams.u_interval;\\nvar distLength : f32 = std::sqrt((distx * distx) + (disty * disty));\\nif ((nodeAttributes1.w != 0.0) && (nodeAttributes2.w != 0.0)) {gWebGPUBuffer0.u_Data[i] = vec4(nodeAttributes1.w, nodeAttributes2.w, currentNode.z, 0.0);}else {gWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + distx, currentNode.y + disty, currentNode.z, distLength);}\\nreturn;}\\n\\nentry_point compute as \\"main\\" = main;\\n","GLSL450":"\\n\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\nivec3 globalInvocationID = ivec3(gl_GlobalInvocationID);\\nivec3 workGroupSize = ivec3(1,1,1);\\nivec3 workGroupID = ivec3(gl_WorkGroupID);\\nivec3 localInvocationID = ivec3(gl_LocalInvocationID);\\nivec3 numWorkGroups = ivec3(gl_NumWorkGroups);\\nint localInvocationIndex = int(gl_LocalInvocationIndex);\\n\\nlayout(std140, set = 0, binding = 0) uniform GWebGPUParams {\\n float u_damping;\\n float u_maxSpeed;\\n float u_minMovement;\\n \\n float u_coulombDisScale;\\n float u_factor;\\n \\n \\n float u_interval;\\n} gWebGPUUniformParams;\\nlayout(std430, set = 0, binding = 1) buffer GWebGPUBuffer0 {\\n vec4 u_Data[];\\n} gWebGPUBuffer0;\\n\\nlayout(std430, set = 0, binding = 2) buffer readonly GWebGPUBuffer1 {\\n vec4 u_AveMovement[];\\n} gWebGPUBuffer1;\\n\\nlayout(std430, set = 0, binding = 3) buffer readonly GWebGPUBuffer2 {\\n vec4 u_NodeAttributeArray1[];\\n} gWebGPUBuffer2;\\n\\nlayout(std430, set = 0, binding = 4) buffer readonly GWebGPUBuffer3 {\\n vec4 u_NodeAttributeArray2[];\\n} gWebGPUBuffer3;\\n\\n\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\n#define SHIFT_20 1048576.0\\nlayout (\\n local_size_x = 1,\\n local_size_y = 1,\\n local_size_z = 1\\n) in;\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\nivec2 unpack_float(float packedValue) {int packedIntValue = int(packedValue);\\nint v0 = packedIntValue / int(SHIFT_20);\\nreturn ivec2(v0, packedIntValue - (v0 * int(SHIFT_20)));}\\nvec2 calcRepulsive(int i, vec4 currentNode) {float ax = 0.0;\\nfloat ay = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = gWebGPUBuffer0.u_Data[j];\\nfloat vx = currentNode.x - nextNode.x;\\nfloat vy = currentNode.y - nextNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat n_dist = (dist + 0.1) * gWebGPUUniformParams.u_coulombDisScale;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nvec4 attributesi = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvec4 attributesj = gWebGPUBuffer2.u_NodeAttributeArray1[j];\\nfloat massi = attributesi.x;\\nfloat nodeStrengthi = attributesi.z;\\nfloat nodeStrengthj = attributesj.z;\\nfloat nodeStrength = (nodeStrengthi + nodeStrengthj) / 2.0;\\nfloat param = (nodeStrength * gWebGPUUniformParams.u_factor) / (n_dist * n_dist);\\nax += direx * param;\\nay += direy * param;}}\\nreturn vec2(ax, ay);}\\nvec2 calcGravity(int i, vec4 currentNode, vec4 attributes2) {float vx = currentNode.x - attributes2.x;\\nfloat vy = currentNode.y - attributes2.y;\\nfloat ax = vx * attributes2.z;\\nfloat ay = vy * attributes2.z;\\nreturn vec2(ax, ay);}\\nvec2 calcAttractive(int i, vec4 currentNode, vec4 attributes1) {float mass = attributes1.x;\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nivec2 compressed = unpack_float(currentNode.z);\\nint length = compressed.x;\\nint arr_offset = compressed.y;\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + (4 * p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = gWebGPUBuffer0.u_Data[int(arr_idx / 4)];}\\nfloat float_j = node_buffer.x;\\nvec4 nextNode = gWebGPUBuffer0.u_Data[int(float_j)];\\nfloat vx = nextNode.x - currentNode.x;\\nfloat vy = nextNode.y - currentNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nfloat edgeLength = node_buffer.y;\\nfloat edgeStrength = node_buffer.z;\\nfloat diff = edgeLength - dist;\\nfloat param = (diff * edgeStrength) / mass;\\nax -= direx * param;\\nay -= direy * param;}\\nreturn vec2(ax, ay);}\\nvoid main() {int i = globalInvocationID.x;\\nvec4 currentNode = gWebGPUBuffer0.u_Data[i];\\nvec4 movement = gWebGPUBuffer1.u_AveMovement[0];\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nif ((i >= VERTEX_COUNT) || (movement.x < gWebGPUUniformParams.u_minMovement)) {gWebGPUBuffer0.u_Data[i] = currentNode;\\nreturn ;}\\nvec4 nodeAttributes1 = gWebGPUBuffer2.u_NodeAttributeArray1[i];\\nvec4 nodeAttributes2 = gWebGPUBuffer3.u_NodeAttributeArray2[i];\\nvec2 repulsive = calcRepulsive(i, currentNode);\\nax += repulsive.x;\\nay += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode, nodeAttributes1);\\nax += attractive.x;\\nay += attractive.y;\\nvec2 gravity = calcGravity(i, currentNode, nodeAttributes2);\\nax -= gravity.x;\\nay -= gravity.y;\\nfloat param = gWebGPUUniformParams.u_interval * gWebGPUUniformParams.u_damping;\\nfloat vx = ax * param;\\nfloat vy = ay * param;\\nfloat vlength = sqrt((vx * vx) + (vy * vy)) + 0.0001;\\nif (vlength > gWebGPUUniformParams.u_maxSpeed) {float param2 = gWebGPUUniformParams.u_maxSpeed / vlength;\\nvx = param2 * vx;\\nvy = param2 * vy;}\\nfloat distx = vx * gWebGPUUniformParams.u_interval;\\nfloat disty = vy * gWebGPUUniformParams.u_interval;\\nfloat distLength = sqrt((distx * distx) + (disty * disty));\\nif ((nodeAttributes1.w != 0.0) && (nodeAttributes2.w != 0.0)) {gWebGPUBuffer0.u_Data[i] = vec4(nodeAttributes1.w, nodeAttributes2.w, currentNode.z, 0.0);}else {gWebGPUBuffer0.u_Data[i] = vec4(currentNode.x + distx, currentNode.y + disty, currentNode.z, distLength);}}\\n","GLSL100":"\\n\\nfloat epsilon = 0.00001;\\nvec2 addrTranslation_1Dto2D(float address1D, vec2 texSize) {\\n vec2 conv_const = vec2(1.0 / texSize.x, 1.0 / (texSize.x * texSize.y));\\n vec2 normAddr2D = float(address1D) * conv_const;\\n return vec2(fract(normAddr2D.x + epsilon), normAddr2D.y);\\n}\\n\\nvoid barrier() {}\\n \\n\\nuniform vec2 u_OutputTextureSize;\\nuniform int u_OutputTexelCount;\\nvarying vec2 v_TexCoord;\\n\\nbool gWebGPUDebug = false;\\nvec4 gWebGPUDebugOutput = vec4(0.0);\\n\\n#define MAX_EDGE_PER_VERTEX __DefineValuePlaceholder__MAX_EDGE_PER_VERTEX\\n#define VERTEX_COUNT __DefineValuePlaceholder__VERTEX_COUNT\\n#define SHIFT_20 1048576.0\\n\\nuniform sampler2D u_Data;\\nuniform vec2 u_DataSize;\\nvec4 getDatau_Data(vec2 address2D) {\\n return vec4(texture2D(u_Data, address2D).rgba);\\n}\\nvec4 getDatau_Data(float address1D) {\\n return getDatau_Data(addrTranslation_1Dto2D(address1D, u_DataSize));\\n}\\nvec4 getDatau_Data(int address1D) {\\n return getDatau_Data(float(address1D));\\n}\\nuniform float u_damping;\\nuniform float u_maxSpeed;\\nuniform float u_minMovement;\\nuniform sampler2D u_AveMovement;\\nuniform vec2 u_AveMovementSize;\\nvec4 getDatau_AveMovement(vec2 address2D) {\\n return vec4(texture2D(u_AveMovement, address2D).rgba);\\n}\\nvec4 getDatau_AveMovement(float address1D) {\\n return getDatau_AveMovement(addrTranslation_1Dto2D(address1D, u_AveMovementSize));\\n}\\nvec4 getDatau_AveMovement(int address1D) {\\n return getDatau_AveMovement(float(address1D));\\n}\\nuniform float u_coulombDisScale;\\nuniform float u_factor;\\nuniform sampler2D u_NodeAttributeArray1;\\nuniform vec2 u_NodeAttributeArray1Size;\\nvec4 getDatau_NodeAttributeArray1(vec2 address2D) {\\n return vec4(texture2D(u_NodeAttributeArray1, address2D).rgba);\\n}\\nvec4 getDatau_NodeAttributeArray1(float address1D) {\\n return getDatau_NodeAttributeArray1(addrTranslation_1Dto2D(address1D, u_NodeAttributeArray1Size));\\n}\\nvec4 getDatau_NodeAttributeArray1(int address1D) {\\n return getDatau_NodeAttributeArray1(float(address1D));\\n}\\nuniform sampler2D u_NodeAttributeArray2;\\nuniform vec2 u_NodeAttributeArray2Size;\\nvec4 getDatau_NodeAttributeArray2(vec2 address2D) {\\n return vec4(texture2D(u_NodeAttributeArray2, address2D).rgba);\\n}\\nvec4 getDatau_NodeAttributeArray2(float address1D) {\\n return getDatau_NodeAttributeArray2(addrTranslation_1Dto2D(address1D, u_NodeAttributeArray2Size));\\n}\\nvec4 getDatau_NodeAttributeArray2(int address1D) {\\n return getDatau_NodeAttributeArray2(float(address1D));\\n}\\nuniform float u_interval;\\nivec2 unpack_float(float packedValue) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nint packedIntValue = int(packedValue);\\nint v0 = packedIntValue / int(SHIFT_20);\\nreturn ivec2(v0, packedIntValue - (v0 * int(SHIFT_20)));}\\nvec2 calcRepulsive(int i, vec4 currentNode) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nfor (int j = 0; j < VERTEX_COUNT; j++) {if (i != j) {vec4 nextNode = getDatau_Data(j);\\nfloat vx = currentNode.x - nextNode.x;\\nfloat vy = currentNode.y - nextNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat n_dist = (dist + 0.1) * u_coulombDisScale;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nvec4 attributesi = getDatau_NodeAttributeArray1(i);\\nvec4 attributesj = getDatau_NodeAttributeArray1(j);\\nfloat massi = attributesi.x;\\nfloat nodeStrengthi = attributesi.z;\\nfloat nodeStrengthj = attributesj.z;\\nfloat nodeStrength = (nodeStrengthi + nodeStrengthj) / 2.0;\\nfloat param = (nodeStrength * u_factor) / (n_dist * n_dist);\\nax += direx * param;\\nay += direy * param;}}\\nreturn vec2(ax, ay);}\\nvec2 calcGravity(int i, vec4 currentNode, vec4 attributes2) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat vx = currentNode.x - attributes2.x;\\nfloat vy = currentNode.y - attributes2.y;\\nfloat ax = vx * attributes2.z;\\nfloat ay = vy * attributes2.z;\\nreturn vec2(ax, ay);}\\nvec2 calcAttractive(int i, vec4 currentNode, vec4 attributes1) {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nfloat mass = attributes1.x;\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nivec2 compressed = unpack_float(currentNode.z);\\nint length = compressed.x;\\nint arr_offset = compressed.y;\\nvec4 node_buffer;\\nfor (int p = 0; p < MAX_EDGE_PER_VERTEX; p++) {if (p >= length) {break;}\\nint arr_idx = arr_offset + (4 * p);\\nint buf_offset = arr_idx - ((arr_idx / 4) * 4);\\nif ((p == 0) || (buf_offset == 0)) {node_buffer = getDatau_Data(int(arr_idx / 4));}\\nfloat float_j = node_buffer.x;\\nvec4 nextNode = getDatau_Data(int(float_j));\\nfloat vx = nextNode.x - currentNode.x;\\nfloat vy = nextNode.y - currentNode.y;\\nfloat dist = sqrt((vx * vx) + (vy * vy)) + 0.01;\\nfloat direx = vx / dist;\\nfloat direy = vy / dist;\\nfloat edgeLength = node_buffer.y;\\nfloat edgeStrength = node_buffer.z;\\nfloat diff = edgeLength - dist;\\nfloat param = (diff * edgeStrength) / mass;\\nax -= direx * param;\\nay -= direy * param;}\\nreturn vec2(ax, ay);}\\nvoid main() {\\nivec3 workGroupSize = ivec3(1, 1, 1);\\nivec3 numWorkGroups = ivec3(1, 1, 1); \\nint globalInvocationIndex = int(floor(v_TexCoord.x * u_OutputTextureSize.x))\\n + int(floor(v_TexCoord.y * u_OutputTextureSize.y)) * int(u_OutputTextureSize.x);\\nint workGroupIDLength = globalInvocationIndex / (workGroupSize.x * workGroupSize.y * workGroupSize.z);\\nivec3 workGroupID = ivec3(workGroupIDLength / numWorkGroups.y / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.z, workGroupIDLength / numWorkGroups.x / numWorkGroups.y);\\nint localInvocationIDZ = globalInvocationIndex / (workGroupSize.x * workGroupSize.y);\\nint localInvocationIDY = (globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y) / workGroupSize.x;\\nint localInvocationIDX = globalInvocationIndex - localInvocationIDZ * workGroupSize.x * workGroupSize.y - localInvocationIDY * workGroupSize.x;\\nivec3 localInvocationID = ivec3(localInvocationIDX, localInvocationIDY, localInvocationIDZ);\\nivec3 globalInvocationID = workGroupID * workGroupSize + localInvocationID;\\nint localInvocationIndex = localInvocationID.z * workGroupSize.x * workGroupSize.y\\n + localInvocationID.y * workGroupSize.x + localInvocationID.x;\\nint i = globalInvocationID.x;\\nvec4 currentNode = getDatau_Data(i);\\nvec4 movement = getDatau_AveMovement(0.0);\\nfloat ax = 0.0;\\nfloat ay = 0.0;\\nif ((i >= VERTEX_COUNT) || (movement.x < u_minMovement)) {gl_FragColor = vec4(currentNode);\\nreturn ;}\\nvec4 nodeAttributes1 = getDatau_NodeAttributeArray1(i);\\nvec4 nodeAttributes2 = getDatau_NodeAttributeArray2(i);\\nvec2 repulsive = calcRepulsive(i, currentNode);\\nax += repulsive.x;\\nay += repulsive.y;\\nvec2 attractive = calcAttractive(i, currentNode, nodeAttributes1);\\nax += attractive.x;\\nay += attractive.y;\\nvec2 gravity = calcGravity(i, currentNode, nodeAttributes2);\\nax -= gravity.x;\\nay -= gravity.y;\\nfloat param = u_interval * u_damping;\\nfloat vx = ax * param;\\nfloat vy = ay * param;\\nfloat vlength = sqrt((vx * vx) + (vy * vy)) + 0.0001;\\nif (vlength > u_maxSpeed) {float param2 = u_maxSpeed / vlength;\\nvx = param2 * vx;\\nvy = param2 * vy;}\\nfloat distx = vx * u_interval;\\nfloat disty = vy * u_interval;\\nfloat distLength = sqrt((distx * distx) + (disty * disty));\\nif ((nodeAttributes1.w != 0.0) && (nodeAttributes2.w != 0.0)) {gl_FragColor = vec4(vec4(nodeAttributes1.w, nodeAttributes2.w, currentNode.z, 0.0));}else {gl_FragColor = vec4(vec4(currentNode.x + distx, currentNode.y + disty, currentNode.z, distLength));}if (gWebGPUDebug) {\\n gl_FragColor = gWebGPUDebugOutput;\\n}}\\n"},"context":{"name":"","dispatch":[1,1,1],"threadGroupSize":[1,1,1],"maxIteration":1,"defines":[{"name":"MAX_EDGE_PER_VERTEX","type":"Float","runtime":true},{"name":"VERTEX_COUNT","type":"Float","runtime":true},{"name":"SHIFT_20","type":"Float","value":1048576,"runtime":false}],"uniforms":[{"name":"u_Data","type":"vec4[]","storageClass":"StorageBuffer","readonly":false,"writeonly":false,"size":[1,1]},{"name":"u_damping","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_maxSpeed","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_minMovement","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_AveMovement","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_coulombDisScale","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_factor","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_NodeAttributeArray1","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_NodeAttributeArray2","type":"vec4[]","storageClass":"StorageBuffer","readonly":true,"writeonly":false,"size":[1,1]},{"name":"u_interval","type":"Float","storageClass":"Uniform","readonly":true,"writeonly":false,"size":[1,1]}],"globalDeclarations":[],"output":{"name":"u_Data","size":[1,1],"length":1},"needPingpong":true}}` export const aveMovementCode = `