cione-comp-lib
Version:
`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`
368 lines (367 loc) • 13.6 kB
JavaScript
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 };