UNPKG

cione-comp-lib

Version:

`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`

368 lines (367 loc) 13.6 kB
import graphicGL from "../../util/graphicGL.mjs"; import Pass from "../../../../claygl/src/compositor/Pass.mjs"; import FrameBuffer from "../../../../claygl/src/FrameBuffer.mjs"; import forceAtlas2Code from "./forceAtlas2.glsl.mjs"; graphicGL.Shader.import(forceAtlas2Code); var defaultConfigs = { repulsionByDegree: true, linLogMode: false, strongGravityMode: false, gravity: 1, scaling: 1, edgeWeightInfluence: 1, jitterTolerence: 0.1, preventOverlap: false, dissuadeHubs: false, gravityCenter: null }; function ForceAtlas2GPU(options) { var textureOpt = { type: graphicGL.Texture.FLOAT, minFilter: graphicGL.Texture.NEAREST, magFilter: graphicGL.Texture.NEAREST }; this._positionSourceTex = new graphicGL.Texture2D(textureOpt); this._positionSourceTex.flipY = false; this._positionTex = new graphicGL.Texture2D(textureOpt); this._positionPrevTex = new graphicGL.Texture2D(textureOpt); this._forceTex = new graphicGL.Texture2D(textureOpt); this._forcePrevTex = new graphicGL.Texture2D(textureOpt); this._weightedSumTex = new graphicGL.Texture2D(textureOpt); this._weightedSumTex.width = this._weightedSumTex.height = 1; this._globalSpeedTex = new graphicGL.Texture2D(textureOpt); this._globalSpeedPrevTex = new graphicGL.Texture2D(textureOpt); this._globalSpeedTex.width = this._globalSpeedTex.height = 1; this._globalSpeedPrevTex.width = this._globalSpeedPrevTex.height = 1; this._nodeRepulsionPass = new Pass({ fragment: graphicGL.Shader.source("ecgl.forceAtlas2.updateNodeRepulsion") }); this._positionPass = new Pass({ fragment: graphicGL.Shader.source("ecgl.forceAtlas2.updatePosition") }); this._globalSpeedPass = new Pass({ fragment: graphicGL.Shader.source("ecgl.forceAtlas2.calcGlobalSpeed") }); this._copyPass = new Pass({ fragment: graphicGL.Shader.source("clay.compositor.output") }); var additiveBlend = function(gl) { gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.ONE, gl.ONE); }; this._edgeForceMesh = new graphicGL.Mesh({ geometry: new graphicGL.Geometry({ attributes: { node1: new graphicGL.Geometry.Attribute("node1", "float", 2), node2: new graphicGL.Geometry.Attribute("node2", "float", 2), weight: new graphicGL.Geometry.Attribute("weight", "float", 1) }, dynamic: true, mainAttribute: "node1" }), material: new graphicGL.Material({ transparent: true, shader: graphicGL.createShader("ecgl.forceAtlas2.updateEdgeAttraction"), blend: additiveBlend, depthMask: false, depthText: false }), mode: graphicGL.Mesh.POINTS }); this._weightedSumMesh = new graphicGL.Mesh({ geometry: new graphicGL.Geometry({ attributes: { node: new graphicGL.Geometry.Attribute("node", "float", 2) }, dynamic: true, mainAttribute: "node" }), material: new graphicGL.Material({ transparent: true, shader: graphicGL.createShader("ecgl.forceAtlas2.calcWeightedSum"), blend: additiveBlend, depthMask: false, depthText: false }), mode: graphicGL.Mesh.POINTS }); this._framebuffer = new FrameBuffer({ depthBuffer: false }); this._dummyCamera = new graphicGL.OrthographicCamera({ left: -1, right: 1, top: 1, bottom: -1, near: 0, far: 100 }); this._globalSpeed = 0; } ForceAtlas2GPU.prototype.updateOption = function(options) { for (var name in defaultConfigs) { this[name] = defaultConfigs[name]; } var nNodes = this._nodes.length; if (nNodes > 5e4) { this.jitterTolerence = 10; } else if (nNodes > 5e3) { this.jitterTolerence = 1; } else { this.jitterTolerence = 0.1; } if (nNodes > 100) { this.scaling = 2; } else { this.scaling = 10; } if (options) { for (var name in defaultConfigs) { if (options[name] != null) { this[name] = options[name]; } } } if (this.repulsionByDegree) { var positionBuffer = this._positionSourceTex.pixels; for (var i = 0; i < this._nodes.length; i++) { positionBuffer[i * 4 + 2] = (this._nodes[i].degree || 0) + 1; } } }; ForceAtlas2GPU.prototype._updateGravityCenter = function(options) { var nodes = this._nodes; var edges = this._edges; if (!this.gravityCenter) { var min = [Infinity, Infinity]; var max = [-Infinity, -Infinity]; for (var i = 0; i < nodes.length; i++) { min[0] = Math.min(nodes[i].x, min[0]); min[1] = Math.min(nodes[i].y, min[1]); max[0] = Math.max(nodes[i].x, max[0]); max[1] = Math.max(nodes[i].y, max[1]); } this._gravityCenter = [(min[0] + max[0]) * 0.5, (min[1] + max[1]) * 0.5]; } else { this._gravityCenter = this.gravityCenter; } for (var i = 0; i < edges.length; i++) { var node1 = edges[i].node1; var node2 = edges[i].node2; nodes[node1].degree = (nodes[node1].degree || 0) + 1; nodes[node2].degree = (nodes[node2].degree || 0) + 1; } }; ForceAtlas2GPU.prototype.initData = function(nodes, edges) { this._nodes = nodes; this._edges = edges; this._updateGravityCenter(); var textureWidth = Math.ceil(Math.sqrt(nodes.length)); var textureHeight = textureWidth; var positionBuffer = new Float32Array(textureWidth * textureHeight * 4); this._resize(textureWidth, textureHeight); var offset = 0; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; positionBuffer[offset++] = node.x || 0; positionBuffer[offset++] = node.y || 0; positionBuffer[offset++] = node.mass || 1; positionBuffer[offset++] = node.size || 1; } this._positionSourceTex.pixels = positionBuffer; var edgeGeometry = this._edgeForceMesh.geometry; var edgeLen = edges.length; edgeGeometry.attributes.node1.init(edgeLen * 2); edgeGeometry.attributes.node2.init(edgeLen * 2); edgeGeometry.attributes.weight.init(edgeLen * 2); var uv = []; for (var i = 0; i < edges.length; i++) { var attributes = edgeGeometry.attributes; var weight = edges[i].weight; if (weight == null) { weight = 1; } attributes.node1.set(i, this.getNodeUV(edges[i].node1, uv)); attributes.node2.set(i, this.getNodeUV(edges[i].node2, uv)); attributes.weight.set(i, weight); attributes.node1.set(i + edgeLen, this.getNodeUV(edges[i].node2, uv)); attributes.node2.set(i + edgeLen, this.getNodeUV(edges[i].node1, uv)); attributes.weight.set(i + edgeLen, weight); } var weigtedSumGeo = this._weightedSumMesh.geometry; weigtedSumGeo.attributes.node.init(nodes.length); for (var i = 0; i < nodes.length; i++) { weigtedSumGeo.attributes.node.set(i, this.getNodeUV(i, uv)); } edgeGeometry.dirty(); weigtedSumGeo.dirty(); this._nodeRepulsionPass.material.define("fragment", "NODE_COUNT", nodes.length); this._nodeRepulsionPass.material.setUniform("textureSize", [textureWidth, textureHeight]); this._inited = false; this._frame = 0; }; ForceAtlas2GPU.prototype.getNodes = function() { return this._nodes; }; ForceAtlas2GPU.prototype.getEdges = function() { return this._edges; }; ForceAtlas2GPU.prototype.step = function(renderer) { if (!this._inited) { this._initFromSource(renderer); this._inited = true; } this._frame++; this._framebuffer.attach(this._forceTex); this._framebuffer.bind(renderer); var nodeRepulsionPass = this._nodeRepulsionPass; nodeRepulsionPass.setUniform("strongGravityMode", this.strongGravityMode); nodeRepulsionPass.setUniform("gravity", this.gravity); nodeRepulsionPass.setUniform("gravityCenter", this._gravityCenter); nodeRepulsionPass.setUniform("scaling", this.scaling); nodeRepulsionPass.setUniform("preventOverlap", this.preventOverlap); nodeRepulsionPass.setUniform("positionTex", this._positionPrevTex); nodeRepulsionPass.render(renderer); var edgeForceMesh = this._edgeForceMesh; edgeForceMesh.material.set("linLogMode", this.linLogMode); edgeForceMesh.material.set("edgeWeightInfluence", this.edgeWeightInfluence); edgeForceMesh.material.set("preventOverlap", this.preventOverlap); edgeForceMesh.material.set("positionTex", this._positionPrevTex); renderer.gl.enable(renderer.gl.BLEND); renderer.renderPass([edgeForceMesh], this._dummyCamera); this._framebuffer.attach(this._weightedSumTex); renderer.gl.clearColor(0, 0, 0, 0); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT); renderer.gl.enable(renderer.gl.BLEND); var weightedSumMesh = this._weightedSumMesh; weightedSumMesh.material.set("positionTex", this._positionPrevTex); weightedSumMesh.material.set("forceTex", this._forceTex); weightedSumMesh.material.set("forcePrevTex", this._forcePrevTex); renderer.renderPass([weightedSumMesh], this._dummyCamera); this._framebuffer.attach(this._globalSpeedTex); var globalSpeedPass = this._globalSpeedPass; globalSpeedPass.setUniform("globalSpeedPrevTex", this._globalSpeedPrevTex); globalSpeedPass.setUniform("weightedSumTex", this._weightedSumTex); globalSpeedPass.setUniform("jitterTolerence", this.jitterTolerence); renderer.gl.disable(renderer.gl.BLEND); globalSpeedPass.render(renderer); var positionPass = this._positionPass; this._framebuffer.attach(this._positionTex); positionPass.setUniform("globalSpeedTex", this._globalSpeedTex); positionPass.setUniform("positionTex", this._positionPrevTex); positionPass.setUniform("forceTex", this._forceTex); positionPass.setUniform("forcePrevTex", this._forcePrevTex); positionPass.render(renderer); this._framebuffer.unbind(renderer); this._swapTexture(); }; ForceAtlas2GPU.prototype.update = function(renderer, steps, cb) { if (steps == null) { steps = 1; } steps = Math.max(steps, 1); for (var i = 0; i < steps; i++) { this.step(renderer); } cb && cb(); }; ForceAtlas2GPU.prototype.getNodePositionTexture = function() { return this._inited ? this._positionPrevTex : this._positionSourceTex; }; ForceAtlas2GPU.prototype.getNodeUV = function(nodeIndex, uv) { uv = uv || []; var textureWidth = this._positionTex.width; var textureHeight = this._positionTex.height; uv[0] = nodeIndex % textureWidth / (textureWidth - 1); uv[1] = Math.floor(nodeIndex / textureWidth) / (textureHeight - 1) || 0; return uv; }; ForceAtlas2GPU.prototype.getNodePosition = function(renderer, out) { var positionArr = this._positionArr; var width = this._positionTex.width; var height = this._positionTex.height; var size = width * height; if (!positionArr || positionArr.length !== size * 4) { positionArr = this._positionArr = new Float32Array(size * 4); } this._framebuffer.bind(renderer); this._framebuffer.attach(this._positionPrevTex); renderer.gl.readPixels(0, 0, width, height, renderer.gl.RGBA, renderer.gl.FLOAT, positionArr); this._framebuffer.unbind(renderer); if (!out) { out = new Float32Array(this._nodes.length * 2); } for (var i = 0; i < this._nodes.length; i++) { out[i * 2] = positionArr[i * 4]; out[i * 2 + 1] = positionArr[i * 4 + 1]; } return out; }; ForceAtlas2GPU.prototype.getTextureData = function(renderer, textureName) { var tex = this["_" + textureName + "Tex"]; var width = tex.width; var height = tex.height; this._framebuffer.bind(renderer); this._framebuffer.attach(tex); var arr = new Float32Array(width * height * 4); renderer.gl.readPixels(0, 0, width, height, renderer.gl.RGBA, renderer.gl.FLOAT, arr); this._framebuffer.unbind(renderer); return arr; }; ForceAtlas2GPU.prototype.getTextureSize = function() { return { width: this._positionTex.width, height: this._positionTex.height }; }; ForceAtlas2GPU.prototype.isFinished = function(maxSteps) { return this._frame > maxSteps; }; ForceAtlas2GPU.prototype._swapTexture = function() { var tmp = this._positionPrevTex; this._positionPrevTex = this._positionTex; this._positionTex = tmp; var tmp = this._forcePrevTex; this._forcePrevTex = this._forceTex; this._forceTex = tmp; var tmp = this._globalSpeedPrevTex; this._globalSpeedPrevTex = this._globalSpeedTex; this._globalSpeedTex = tmp; }; ForceAtlas2GPU.prototype._initFromSource = function(renderer) { this._framebuffer.attach(this._positionPrevTex); this._framebuffer.bind(renderer); this._copyPass.setUniform("texture", this._positionSourceTex); this._copyPass.render(renderer); renderer.gl.clearColor(0, 0, 0, 0); this._framebuffer.attach(this._forcePrevTex); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT); this._framebuffer.attach(this._globalSpeedPrevTex); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT); this._framebuffer.unbind(renderer); }; ForceAtlas2GPU.prototype._resize = function(width, height) { ["_positionSourceTex", "_positionTex", "_positionPrevTex", "_forceTex", "_forcePrevTex"].forEach(function(texName) { this[texName].width = width; this[texName].height = height; this[texName].dirty(); }, this); }; ForceAtlas2GPU.prototype.dispose = function(renderer) { this._framebuffer.dispose(renderer); this._copyPass.dispose(renderer); this._nodeRepulsionPass.dispose(renderer); this._positionPass.dispose(renderer); this._globalSpeedPass.dispose(renderer); this._edgeForceMesh.geometry.dispose(renderer); this._weightedSumMesh.geometry.dispose(renderer); this._positionSourceTex.dispose(renderer); this._positionTex.dispose(renderer); this._positionPrevTex.dispose(renderer); this._forceTex.dispose(renderer); this._forcePrevTex.dispose(renderer); this._weightedSumTex.dispose(renderer); this._globalSpeedTex.dispose(renderer); this._globalSpeedPrevTex.dispose(renderer); }; export { ForceAtlas2GPU as default };