Browse Source

physics detached from nodes and network.

flowchartTest
Alex de Mulder 9 years ago
parent
commit
714f4affb3
11 changed files with 690 additions and 3293 deletions
  1. +476
    -3015
      dist/vis.js
  2. +12
    -73
      lib/network/Network.js
  3. +0
    -115
      lib/network/Node.js
  4. +0
    -13
      lib/network/mixins/MixinLoader.js
  5. +108
    -4
      lib/network/modules/PhysicsEngine.js
  6. +11
    -5
      lib/network/modules/components/physics/BarnesHutSolver.js
  7. +8
    -6
      lib/network/modules/components/physics/CentralGravitySolver.js
  8. +10
    -8
      lib/network/modules/components/physics/HierarchicalRepulsionSolver.js
  9. +44
    -43
      lib/network/modules/components/physics/HierarchicalSpringSolver.js
  10. +11
    -6
      lib/network/modules/components/physics/RepulsionSolver.js
  11. +10
    -5
      lib/network/modules/components/physics/SpringSolver.js

+ 476
- 3015
dist/vis.js
File diff suppressed because it is too large
View File


+ 12
- 73
lib/network/Network.js View File

@ -161,7 +161,10 @@ function Network (container, data, options) {
nodeDistance: 150, nodeDistance: 150,
damping: 0.09 damping: 0.09
}, },
model:'BarnesHut'
model:'BarnesHut',
timestep: 0.5,
maxVelocity: 50,
minVelocity: 0.1 // px/s
}, },
navigation: { navigation: {
enabled: false enabled: false
@ -189,8 +192,8 @@ function Network (container, data, options) {
type: "continuous", type: "continuous",
roundness: 0.5 roundness: 0.5
}, },
maxVelocity: 50,
minVelocity: 0.1, // px/s
maxVelocity: 50, // ---------------- MOVED TO PHYSICS ----------------------- //
minVelocity: 0.1, // px/s // ---------------- MOVED TO PHYSICS ----------------------- //
stabilize: true, // stabilize before displaying the network stabilize: true, // stabilize before displaying the network
stabilizationIterations: 1000, // maximum number of iteration to stabilize stabilizationIterations: 1000, // maximum number of iteration to stabilize
stabilizationStepsize: 100, stabilizationStepsize: 100,
@ -233,7 +236,8 @@ function Network (container, data, options) {
}, },
functions:{ functions:{
createNode: this._createNode.bind(this), createNode: this._createNode.bind(this),
createEdge: this._createEdge.bind(this)
createEdge: this._createEdge.bind(this),
getScale: function() {return this.scale;}.bind(this)
}, },
emitter: { emitter: {
on: this.on.bind(this), on: this.on.bind(this),
@ -287,7 +291,6 @@ function Network (container, data, options) {
// create a frame and canvas // create a frame and canvas
this._create(); this._create();
// load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it) // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
this._loadClusterSystem();
// load the selection system. (mandatory, required by Network) // load the selection system. (mandatory, required by Network)
this._loadSelectionSystem(); this._loadSelectionSystem();
// load the selection system. (mandatory, required by Network) // load the selection system. (mandatory, required by Network)
@ -2315,68 +2318,6 @@ Network.prototype._restoreFrozenNodes = function() {
}; };
/**
* Check if any of the nodes is still moving
* @param {number} vmin the minimum velocity considered as 'moving'
* @return {boolean} true if moving, false if non of the nodes is moving
* @private
*/
Network.prototype._isMoving = function(nodes, nodeIndices, vmin) {
for (let i = 0; i < nodeIndices.length; i++) {
let node = nodes[nodeIndices[i]];
if (node.isMoving(vmin) == true) {
return true;
}
}
return false;
};
/**
* /**
* Perform one discrete step for all nodes
*
* @private
*/
Network.prototype._discreteStepNodes = function(nodes, nodeIndices) {
var interval = this.physicsDiscreteStepsize;
var nodesPresent = false;
if (this.constants.maxVelocity > 0) {
for (let i = 0; i < nodeIndices.length; i++) {
let node = nodes[nodeIndices[i]];
node.discreteStepLimited(interval, this.constants.maxVelocity);
nodesPresent = true;
}
}
else {
for (let i = 0; i < nodeIndices.length; i++) {
let node = nodes[nodeIndices[i]];
node.discreteStep(interval);
nodesPresent = true;
}
}
if (nodesPresent == true) {
var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
if (vminCorrected > 0.5*this.constants.maxVelocity) {
return true;
}
else {
return this._isMoving(nodes, nodeIndices, vminCorrected);
}
}
return false;
};
Network.prototype._revertPhysicsTick = function(nodes, nodeIndices) {
for (let i = 0; i < nodeIndices.length; i++) {
nodes[nodeIndices[i]].revertPosition();
}
}
/** /**
* A single simulation step (or "tick") in the physics simulation * A single simulation step (or "tick") in the physics simulation
* *
@ -2385,16 +2326,14 @@ Network.prototype._revertPhysicsTick = function(nodes, nodeIndices) {
Network.prototype._physicsTick = function() { Network.prototype._physicsTick = function() {
if (!this.freezeSimulationEnabled) { if (!this.freezeSimulationEnabled) {
if (this.moving == true) { if (this.moving == true) {
this.physics.step();
var mainMovingStatus = this._discreteStepNodes(this.body.nodes, this.body.nodeIndices);
var supportMovingStatus = this._discreteStepNodes(this.body.supportNodes, this.body.supportNodeIndices);
this.physics.calculateForces();
this.moving = this.physics.moveNodes()
// determine if the network has stabilzied // determine if the network has stabilzied
this.moving = mainMovingStatus || supportMovingStatus;
if (this.moving == false) { if (this.moving == false) {
this._revertPhysicsTick(this.body.nodes, this.body.nodeIndices);
this._revertPhysicsTick(this.body.supportNodes, this.body.supportNodeIndices);
this.physics.revert();
} }
else { else {
// this is here to ensure that there is no start event when the network is already stable. // this is here to ensure that there is no start event when the network is already stable.

+ 0
- 115
lib/network/Node.js View File

@ -79,17 +79,6 @@ function Node(properties, imagelist, grouplist, networkConstants) {
} }
/**
* Revert the position and velocity of the previous step.
*/
Node.prototype.revertPosition = function() {
this.x = this.previousState.x;
this.y = this.previousState.y;
this.vx = this.previousState.vx;
this.vy = this.previousState.vy;
}
/** /**
* Attach a edge to the node * Attach a edge to the node
* @param {Edge} edge * @param {Edge} edge
@ -304,99 +293,6 @@ Node.prototype.distanceToBorder = function (ctx, angle) {
// TODO: implement calculation of distance to border for all shapes // TODO: implement calculation of distance to border for all shapes
}; };
/**
* Set forces acting on the node
* @param {number} fx Force in horizontal direction
* @param {number} fy Force in vertical direction
*/
Node.prototype._setForce = function(fx, fy) {
this.fx = fx;
this.fy = fy;
};
/**
* Add forces acting on the node
* @param {number} fx Force in horizontal direction
* @param {number} fy Force in vertical direction
* @private
*/
Node.prototype._addForce = function(fx, fy) {
this.fx += fx;
this.fy += fy;
};
/**
* Store the state before the next step
*/
Node.prototype.storeState = function() {
this.previousState.x = this.x;
this.previousState.y = this.y;
this.previousState.vx = this.vx;
this.previousState.vy = this.vy;
}
/**
* Perform one discrete step for the node
* @param {number} interval Time interval in seconds
*/
Node.prototype.discreteStep = function(interval) {
this.storeState();
if (!this.xFixed) {
var dx = this.damping * this.vx; // damping force
var ax = (this.fx - dx) / this.options.mass; // acceleration
this.vx += ax * interval; // velocity
this.x += this.vx * interval; // position
}
else {
this.fx = 0;
this.vx = 0;
}
if (!this.yFixed) {
var dy = this.damping * this.vy; // damping force
var ay = (this.fy - dy) / this.options.mass; // acceleration
this.vy += ay * interval; // velocity
this.y += this.vy * interval; // position
}
else {
this.fy = 0;
this.vy = 0;
}
};
/**
* Perform one discrete step for the node
* @param {number} interval Time interval in seconds
* @param {number} maxVelocity The speed limit imposed on the velocity
*/
Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
this.storeState();
if (!this.xFixed) {
var dx = this.damping * this.vx; // damping force
var ax = (this.fx - dx) / this.options.mass; // acceleration
this.vx += ax * interval; // velocity
this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
this.x += this.vx * interval; // position
}
else {
this.fx = 0;
this.vx = 0;
}
if (!this.yFixed) {
var dy = this.damping * this.vy; // damping force
var ay = (this.fy - dy) / this.options.mass; // acceleration
this.vy += ay * interval; // velocity
this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
this.y += this.vy * interval; // position
}
else {
this.fy = 0;
this.vy = 0;
}
};
/** /**
* Check if this node has a fixed x and y position * Check if this node has a fixed x and y position
@ -406,17 +302,6 @@ Node.prototype.isFixed = function() {
return (this.xFixed && this.yFixed); return (this.xFixed && this.yFixed);
}; };
/**
* Check if this node is moving
* @param {number} vmin the minimum velocity considered as "moving"
* @return {boolean} true if moving, false if it has no velocity
*/
Node.prototype.isMoving = function(vmin) {
var velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2));
// this.velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2))
return (velocity > vmin);
};
/** /**
* check if this node is selecte * check if this node is selecte
* @return {boolean} selected True if node is selected, else false * @return {boolean} selected True if node is selected, else false

+ 0
- 13
lib/network/mixins/MixinLoader.js View File

@ -1,6 +1,3 @@
var PhysicsMixin = require('./physics/PhysicsMixin');
var ClusterMixin = require('./ClusterMixin');
var SectorsMixin = require('./SectorsMixin');
var SelectionMixin = require('./SelectionMixin'); var SelectionMixin = require('./SelectionMixin');
var ManipulationMixin = require('./ManipulationMixin'); var ManipulationMixin = require('./ManipulationMixin');
var NavigationMixin = require('./NavigationMixin'); var NavigationMixin = require('./NavigationMixin');
@ -53,16 +50,6 @@ exports._loadPhysicsSystem = function () {
}; };
/**
* Mixin the cluster system and initialize the parameters required.
*
* @private
*/
exports._loadClusterSystem = function () {
this.clusteredNodes = {};
this._loadMixin(ClusterMixin);
};
/** /**

+ 108
- 4
lib/network/modules/PhysicsEngine.js View File

@ -14,7 +14,8 @@ import {CentralGravitySolver} from "./components/physics/CentralGravitySolver";
class PhysicsEngine { class PhysicsEngine {
constructor(body, options) { constructor(body, options) {
this.body = body; this.body = body;
this.physicsBody = {calculationNodes: {}, calculationNodeIndices:[]};
this.physicsBody = {calculationNodes: {}, calculationNodeIndices:[], forces: {}, velocities: {}};
this.previousStates = {};
this.setOptions(options); this.setOptions(options);
} }
@ -31,7 +32,7 @@ class PhysicsEngine {
if (this.options.model == "repulsion") { if (this.options.model == "repulsion") {
options = this.options.repulsion; options = this.options.repulsion;
this.nodesSolver = new Repulsion(this.body, this.physicsBody, options); this.nodesSolver = new Repulsion(this.body, this.physicsBody, options);
this.edgesSolver = new SpringSolver(this.body, options);
this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
} }
else if (this.options.model == "hierarchicalRepulsion") { else if (this.options.model == "hierarchicalRepulsion") {
options = this.options.hierarchicalRepulsion; options = this.options.hierarchicalRepulsion;
@ -41,10 +42,11 @@ class PhysicsEngine {
else { // barnesHut else { // barnesHut
options = this.options.barnesHut; options = this.options.barnesHut;
this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options); this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options);
this.edgesSolver = new SpringSolver(this.body, options);
this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
} }
this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options); this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
this.modelOptions = options;
} }
/** /**
@ -57,6 +59,7 @@ class PhysicsEngine {
*/ */
_updateCalculationNodes() { _updateCalculationNodes() {
this.physicsBody.calculationNodes = {}; this.physicsBody.calculationNodes = {};
this.physicsBody.forces = {};
this.physicsBody.calculationNodeIndices = []; this.physicsBody.calculationNodeIndices = [];
for (let i = 0; i < this.body.nodeIndices.length; i++) { for (let i = 0; i < this.body.nodeIndices.length; i++) {
@ -75,10 +78,111 @@ class PhysicsEngine {
console.error("Support node detected that does not have an edge!") console.error("Support node detected that does not have an edge!")
} }
} }
this.physicsBody.calculationNodeIndices = Object.keys(this.physicsBody.calculationNodes); this.physicsBody.calculationNodeIndices = Object.keys(this.physicsBody.calculationNodes);
for (let i = 0; i < this.physicsBody.calculationNodeIndices.length; i++) {
let nodeId = this.physicsBody.calculationNodeIndices[i];
this.physicsBody.forces[nodeId] = {x:0,y:0};
// forces can be reset because they are recalculated. Velocities have to persist.
if (this.physicsBody.velocities[nodeId] === undefined) {
this.physicsBody.velocities[nodeId] = {x:0,y:0};
}
}
// clean deleted nodes from the velocity vector
for (let nodeId in this.physicsBody.velocities) {
if (this.physicsBody.calculationNodes[nodeId] === undefined) {
delete this.physicsBody.velocities[nodeId];
}
}
}
revert() {
var nodeIds = Object.keys(this.previousStates);
var nodes = this.physicsBody.calculationNodes;
var velocities = this.physicsBody.velocities;
for (let i = 0; i < nodeIds.length; i++) {
let nodeId = nodeIds[i];
if (nodes[nodeId] !== undefined) {
velocities[nodeId].x = this.previousStates[nodeId].vx;
velocities[nodeId].y = this.previousStates[nodeId].vy;
nodes[nodeId].x = this.previousStates[nodeId].x;
nodes[nodeId].y = this.previousStates[nodeId].y;
}
else {
delete this.previousStates[nodeId];
}
}
}
moveNodes() {
var nodesPresent = false;
var nodeIndices = this.physicsBody.calculationNodeIndices;
var maxVelocity = this.options.maxVelocity === 0 ? 1e9 : this.options.maxVelocity;
var moving = false;
var vminCorrected = this.options.minVelocity / Math.max(this.body.functions.getScale(),0.05);
for (let i = 0; i < nodeIndices.length; i++) {
let nodeId = nodeIndices[i];
let nodeVelocity = this._performStep(nodeId, maxVelocity);
moving = nodeVelocity > vminCorrected;
nodesPresent = true;
}
if (nodesPresent == true) {
if (vminCorrected > 0.5*this.options.maxVelocity) {
return true;
}
else {
return moving;
}
}
return false;
}
_performStep(nodeId,maxVelocity) {
var node = this.physicsBody.calculationNodes[nodeId];
var timestep = this.options.timestep;
var forces = this.physicsBody.forces;
var velocities = this.physicsBody.velocities;
// store the state so we can revert
this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
if (!node.xFixed) {
let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
velocities[nodeId].x += ax * timestep; // velocity
velocities[nodeId].x = (Math.abs(velocities[nodeId].x) > maxVelocity) ? ((velocities[nodeId].x > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].x;
node.x += velocities[nodeId].x * timestep; // position
}
else {
forces[nodeId].x = 0;
velocities[nodeId].x = 0;
}
if (!node.yFixed) {
let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
velocities[nodeId].y += ay * timestep; // velocity
velocities[nodeId].y = (Math.abs(velocities[nodeId].y) > maxVelocity) ? ((velocities[nodeId].y > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].y;
node.y += velocities[nodeId].y * timestep; // position
}
else {
forces[nodeId].y = 0;
velocities[nodeId].y = 0;
}
var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
return totalVelocity;
} }
step() {
calculateForces() {
this.gravitySolver.solve(); this.gravitySolver.solve();
this.nodesSolver.solve(); this.nodesSolver.solve();
this.edgesSolver.solve(); this.edgesSolver.solve();

+ 11
- 5
lib/network/modules/components/physics/BarnesHutSolver.js View File

@ -6,8 +6,12 @@ class BarnesHutSolver {
constructor(body, physicsBody, options) { constructor(body, physicsBody, options) {
this.body = body; this.body = body;
this.physicsBody = physicsBody; this.physicsBody = physicsBody;
this.options = options;
this.barnesHutTree; this.barnesHutTree;
this.setOptions(options);
}
setOptions(options) {
this.options = options;
} }
@ -75,8 +79,9 @@ class BarnesHutSolver {
var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
var fx = dx * gravityForce; var fx = dx * gravityForce;
var fy = dy * gravityForce; var fy = dy * gravityForce;
node.fx += fx;
node.fy += fy;
this.physicsBody.forces[node.id].x += fx;
this.physicsBody.forces[node.id].y += fy;
} }
else { else {
// Did not pass the condition, go into children if available // Did not pass the condition, go into children if available
@ -96,8 +101,9 @@ class BarnesHutSolver {
var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance); var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
var fx = dx * gravityForce; var fx = dx * gravityForce;
var fy = dy * gravityForce; var fy = dy * gravityForce;
node.fx += fx;
node.fy += fy;
this.physicsBody.forces[node.id].x += fx;
this.physicsBody.forces[node.id].y += fy;
} }
} }
} }

+ 8
- 6
lib/network/modules/components/physics/CentralGravitySolver.js View File

@ -16,20 +16,22 @@ class CentralGravitySolver {
solve() { solve() {
var dx, dy, distance, node, i; var dx, dy, distance, node, i;
var nodes = this.physicsBody.calculationNodes; var nodes = this.physicsBody.calculationNodes;
var calculationNodeIndices = this.physicsBody.calculationNodeIndices;
var nodeIndices = this.physicsBody.calculationNodeIndices;
var forces = this.physicsBody.forces;
var gravity = this.options.centralGravity; var gravity = this.options.centralGravity;
var gravityForce = 0; var gravityForce = 0;
for (i = 0; i < calculationNodeIndices.length; i++) {
node = nodes[calculationNodeIndices[i]];
node.damping = this.options.damping;
for (i = 0; i < nodeIndices.length; i++) {
let nodeId = nodeIndices[i];
node = nodes[nodeId];
dx = -node.x; dx = -node.x;
dy = -node.y; dy = -node.y;
distance = Math.sqrt(dx * dx + dy * dy); distance = Math.sqrt(dx * dx + dy * dy);
gravityForce = (distance == 0) ? 0 : (gravity / distance); gravityForce = (distance == 0) ? 0 : (gravity / distance);
node.fx = dx * gravityForce;
node.fy = dy * gravityForce;
forces[nodeId].x = dx * gravityForce;
forces[nodeId].y = dy * gravityForce;
} }
} }
} }

+ 10
- 8
lib/network/modules/components/physics/HierarchicalRepulsionSolver.js View File

@ -6,6 +6,10 @@ class HierarchicalRepulsionSolver {
constructor(body, physicsBody, options) { constructor(body, physicsBody, options) {
this.body = body; this.body = body;
this.physicsBody = physicsBody; this.physicsBody = physicsBody;
this.setOptions(options);
}
setOptions(options) {
this.options = options; this.options = options;
} }
@ -16,11 +20,11 @@ class HierarchicalRepulsionSolver {
* @private * @private
*/ */
solve() { solve() {
var dx, dy, distance, fx, fy,
repulsingForce, node1, node2, i, j;
var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j;
var nodes = this.physicsBody.calculationNodes; var nodes = this.physicsBody.calculationNodes;
var nodeIndices = this.physicsBody.calculationNodeIndices; var nodeIndices = this.physicsBody.calculationNodeIndices;
var forces = this.physicsBody.forces;
// repulsing forces between nodes // repulsing forces between nodes
var nodeDistance = this.options.nodeDistance; var nodeDistance = this.options.nodeDistance;
@ -34,12 +38,10 @@ class HierarchicalRepulsionSolver {
// nodes only affect nodes on their level // nodes only affect nodes on their level
if (node1.level == node2.level) { if (node1.level == node2.level) {
dx = node2.x - node1.x; dx = node2.x - node1.x;
dy = node2.y - node1.y; dy = node2.y - node1.y;
distance = Math.sqrt(dx * dx + dy * dy); distance = Math.sqrt(dx * dx + dy * dy);
var steepness = 0.05; var steepness = 0.05;
if (distance < nodeDistance) { if (distance < nodeDistance) {
repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2); repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2);
@ -57,10 +59,10 @@ class HierarchicalRepulsionSolver {
fx = dx * repulsingForce; fx = dx * repulsingForce;
fy = dy * repulsingForce; fy = dy * repulsingForce;
node1.fx -= fx;
node1.fy -= fy;
node2.fx += fx;
node2.fy += fy;
forces[node1.id].x -= fx;
forces[node1.id].y -= fy;
forces[node2.id].x += fx;
forces[node2.id].y += fy;
} }
} }
} }

+ 44
- 43
lib/network/modules/components/physics/HierarchicalSpringSolver.js View File

@ -6,6 +6,10 @@ class HierarchicalSpringSolver {
constructor(body, physicsBody, options) { constructor(body, physicsBody, options) {
this.body = body; this.body = body;
this.physicsBody = physicsBody; this.physicsBody = physicsBody;
this.setOptions(options);
}
setOptions(options) {
this.options = options; this.options = options;
} }
@ -19,14 +23,14 @@ class HierarchicalSpringSolver {
var dx, dy, fx, fy, springForce, distance; var dx, dy, fx, fy, springForce, distance;
var edges = this.body.edges; var edges = this.body.edges;
var nodes = this.physicsBody.calculationNodes;
var nodeIndices = this.physicsBody.calculationNodeIndices; var nodeIndices = this.physicsBody.calculationNodeIndices;
var forces = this.physicsBody.forces;
// initialize the spring force counters // initialize the spring force counters
for (let i = 0; i < nodeIndices.length; i++) { for (let i = 0; i < nodeIndices.length; i++) {
let node1 = nodes[nodeIndices[i]];
node1.springFx = 0;
node1.springFy = 0;
let nodeId = nodeIndices[i];
forces[nodeId].springFx = 0;
forces[nodeId].springFy = 0;
} }
@ -35,34 +39,31 @@ class HierarchicalSpringSolver {
if (edges.hasOwnProperty(edgeId)) { if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId]; edge = edges[edgeId];
if (edge.connected === true) { if (edge.connected === true) {
// only calculate forces if nodes are in the same sector
if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) {
edgeLength = edge.properties.length === undefined ? this.options.springLength : edge.properties.length;
dx = (edge.from.x - edge.to.x);
dy = (edge.from.y - edge.to.y);
distance = Math.sqrt(dx * dx + dy * dy);
distance = distance == 0 ? 0.01 : distance;
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.options.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce;
fy = dy * springForce;
if (edge.to.level != edge.from.level) {
edge.to.springFx -= fx;
edge.to.springFy -= fy;
edge.from.springFx += fx;
edge.from.springFy += fy;
}
else {
let factor = 0.5;
edge.to.fx -= factor*fx;
edge.to.fy -= factor*fy;
edge.from.fx += factor*fx;
edge.from.fy += factor*fy;
}
edgeLength = edge.properties.length === undefined ? this.options.springLength : edge.properties.length;
dx = (edge.from.x - edge.to.x);
dy = (edge.from.y - edge.to.y);
distance = Math.sqrt(dx * dx + dy * dy);
distance = distance == 0 ? 0.01 : distance;
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.options.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce;
fy = dy * springForce;
if (edge.to.level != edge.from.level) {
forces[edge.toId].springFx -= fx;
forces[edge.toId].springFy -= fy;
forces[edge.fromId].springFx += fx;
forces[edge.fromId].springFy += fy;
}
else {
let factor = 0.5;
forces[edge.toId].x -= factor*fx;
forces[edge.toId].y -= factor*fy;
forces[edge.fromId].x += factor*fx;
forces[edge.fromId].y += factor*fy;
} }
} }
} }
@ -72,29 +73,29 @@ class HierarchicalSpringSolver {
var springForce = 1; var springForce = 1;
var springFx, springFy; var springFx, springFy;
for (let i = 0; i < nodeIndices.length; i++) { for (let i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
let nodeId = nodeIndices[i];
springFx = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFx));
springFy = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFy));
node.fx += springFx;
node.fy += springFy;
forces[nodeId].x += springFx;
forces[nodeId].y += springFy;
} }
// retain energy balance // retain energy balance
var totalFx = 0; var totalFx = 0;
var totalFy = 0; var totalFy = 0;
for (let i = 0; i < nodeIndices.length; i++) { for (let i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
totalFx += node.fx;
totalFy += node.fy;
let nodeId = nodeIndices[i];
totalFx += forces[nodeId].x;
totalFy += forces[nodeId].y;
} }
var correctionFx = totalFx / nodeIndices.length; var correctionFx = totalFx / nodeIndices.length;
var correctionFy = totalFy / nodeIndices.length; var correctionFy = totalFy / nodeIndices.length;
for (let i = 0; i < nodeIndices.length; i++) { for (let i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
node.fx -= correctionFx;
node.fy -= correctionFy;
let nodeId = nodeIndices[i];
forces[nodeId].x -= correctionFx;
forces[nodeId].y -= correctionFy;
} }
} }

+ 11
- 6
lib/network/modules/components/physics/RepulsionSolver.js View File

@ -6,9 +6,12 @@ class RepulsionSolver {
constructor(body, physicsBody, options) { constructor(body, physicsBody, options) {
this.body = body; this.body = body;
this.physicsBody = physicsBody; this.physicsBody = physicsBody;
this.options = options;
this.setOptions(options);
} }
setOptions(options) {
this.options = options;
}
/** /**
* Calculate the forces the nodes apply on each other based on a repulsion field. * Calculate the forces the nodes apply on each other based on a repulsion field.
* This field is linearly approximated. * This field is linearly approximated.
@ -20,12 +23,13 @@ class RepulsionSolver {
var nodes = this.physicsBody.calculationNodes; var nodes = this.physicsBody.calculationNodes;
var nodeIndices = this.physicsBody.calculationNodeIndices; var nodeIndices = this.physicsBody.calculationNodeIndices;
var forces = this.physicsBody.forces;
// repulsing forces between nodes // repulsing forces between nodes
var nodeDistance = this.options.nodeDistance; var nodeDistance = this.options.nodeDistance;
// approximation constants // approximation constants
var a = (-2 / 3) /nodeDistance;
var a = (-2 / 3) / nodeDistance;
var b = 4 / 3; var b = 4 / 3;
// we loop from i over all but the last entree in the array // we loop from i over all but the last entree in the array
@ -56,10 +60,11 @@ class RepulsionSolver {
fx = dx * repulsingForce; fx = dx * repulsingForce;
fy = dy * repulsingForce; fy = dy * repulsingForce;
node1.fx -= fx;
node1.fy -= fy;
node2.fx += fx;
node2.fy += fy;
forces[node1.id].x -= fx;
forces[node1.id].y -= fy;
forces[node2.id].x += fx;
forces[node2.id].y += fy;
} }
} }
} }

+ 10
- 5
lib/network/modules/components/physics/SpringSolver.js View File

@ -3,8 +3,13 @@
*/ */
class SpringSolver { class SpringSolver {
constructor(body, options) {
constructor(body, physicsBody, options) {
this.body = body; this.body = body;
this.physicsBody = physicsBody;
this.setOptions(options);
}
setOptions(options) {
this.options = options; this.options = options;
} }
@ -65,10 +70,10 @@ class SpringSolver {
fx = dx * springForce; fx = dx * springForce;
fy = dy * springForce; fy = dy * springForce;
node1.fx += fx;
node1.fy += fy;
node2.fx -= fx;
node2.fy -= fy;
this.physicsBody.forces[node1.id].x += fx;
this.physicsBody.forces[node1.id].y += fy;
this.physicsBody.forces[node2.id].x -= fx;
this.physicsBody.forces[node2.id].y -= fy;
} }
} }

Loading…
Cancel
Save