|
|
@ -69,14 +69,12 @@ class PhysicsEngine { |
|
|
|
this.body.emitter.on('restorePhysics', () => { |
|
|
|
this.setOptions(this.options); |
|
|
|
if (this.ready === true) { |
|
|
|
this.stabilized = false; |
|
|
|
this.runSimulation(); |
|
|
|
this.startSimulation(); |
|
|
|
} |
|
|
|
}); |
|
|
|
this.body.emitter.on('startSimulation', () => { |
|
|
|
if (this.ready === true) { |
|
|
|
this.stabilized = false; |
|
|
|
this.runSimulation(); |
|
|
|
this.startSimulation(); |
|
|
|
} |
|
|
|
}) |
|
|
|
this.body.emitter.on('stopSimulation', () => {this.stopSimulation();}); |
|
|
@ -122,15 +120,14 @@ class PhysicsEngine { |
|
|
|
|
|
|
|
initPhysics() { |
|
|
|
if (this.physicsEnabled === true) { |
|
|
|
this.stabilized = false; |
|
|
|
if (this.options.stabilization.enabled === true) { |
|
|
|
this.body.emitter.emit('_blockRedrawRequests'); |
|
|
|
this.stabilize(); |
|
|
|
} |
|
|
|
else { |
|
|
|
this.stabilized = false; |
|
|
|
this.ready = true; |
|
|
|
this.body.emitter.emit('zoomExtent', {duration: 0}, true) |
|
|
|
this.runSimulation(); |
|
|
|
this.startSimulation(); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
@ -139,17 +136,12 @@ class PhysicsEngine { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
stopSimulation() { |
|
|
|
this.stabilized = true; |
|
|
|
if (this.viewFunction !== undefined) { |
|
|
|
this.body.emitter.off('initRedraw', this.viewFunction); |
|
|
|
this.viewFunction = undefined; |
|
|
|
this.body.emitter.emit('_stopRendering'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
runSimulation() { |
|
|
|
/** |
|
|
|
* Start the simulation |
|
|
|
*/ |
|
|
|
startSimulation() { |
|
|
|
if (this.physicsEnabled === true) { |
|
|
|
this.stabilized = false; |
|
|
|
if (this.viewFunction === undefined) { |
|
|
|
this.viewFunction = this.simulationStep.bind(this); |
|
|
|
this.body.emitter.on('initRedraw', this.viewFunction); |
|
|
@ -161,6 +153,25 @@ class PhysicsEngine { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Stop the simulation, force stabilization. |
|
|
|
*/ |
|
|
|
stopSimulation() { |
|
|
|
this.stabilized = true; |
|
|
|
this._emitStabilized(); |
|
|
|
if (this.viewFunction !== undefined) { |
|
|
|
this.body.emitter.off('initRedraw', this.viewFunction); |
|
|
|
this.viewFunction = undefined; |
|
|
|
this.body.emitter.emit('_stopRendering'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* The viewFunction inserts this step into each renderloop. It calls the physics tick and handles the cleanup at stabilized. |
|
|
|
* |
|
|
|
*/ |
|
|
|
simulationStep() { |
|
|
|
// check if the physics have settled
|
|
|
|
var startTime = Date.now(); |
|
|
@ -181,15 +192,9 @@ class PhysicsEngine { |
|
|
|
// The event is triggered on the next tick, to prevent the case that
|
|
|
|
// it is fired while initializing the Network, in which case you would not
|
|
|
|
// be able to catch it
|
|
|
|
var me = this; |
|
|
|
var params = { |
|
|
|
iterations: this.stabilizationIterations |
|
|
|
}; |
|
|
|
this.stabilizationIterations = 0; |
|
|
|
this.startedStabilization = false; |
|
|
|
setTimeout(function () { |
|
|
|
me.body.emitter.emit('stabilized', params); |
|
|
|
}, 0); |
|
|
|
this._emitStabilized(); |
|
|
|
} |
|
|
|
else { |
|
|
|
this.stabilizationIterations = 0; |
|
|
@ -198,6 +203,14 @@ class PhysicsEngine { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
_emitStabilized() { |
|
|
|
if (this.stabilizationIterations > 1) { |
|
|
|
setTimeout(() => { |
|
|
|
this.body.emitter.emit('stabilized', {iterations: this.stabilizationIterations}); |
|
|
|
}, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* A single simulation step (or 'tick') in the physics simulation |
|
|
|
* |
|
|
@ -225,10 +238,7 @@ class PhysicsEngine { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also |
|
|
|
* handled in the calculateForces function. We then use a quadratic curve with the center node as control. |
|
|
|
* This function joins the datanodes and invisible (called support) nodes into one object. |
|
|
|
* We do this so we do not contaminate this.body.nodes with the support nodes. |
|
|
|
* Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time. |
|
|
|
* |
|
|
|
* @private |
|
|
|
*/ |
|
|
@ -277,6 +287,9 @@ class PhysicsEngine { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized. |
|
|
|
*/ |
|
|
|
revert() { |
|
|
|
var nodeIds = Object.keys(this.previousStates); |
|
|
|
var nodes = this.body.nodes; |
|
|
@ -298,10 +311,14 @@ class PhysicsEngine { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* move the nodes one timestap and check if they are stabilized |
|
|
|
* @returns {boolean} |
|
|
|
*/ |
|
|
|
moveNodes() { |
|
|
|
var nodesPresent = false; |
|
|
|
var nodeIndices = this.physicsBody.physicsNodeIndices; |
|
|
|
var maxVelocity = this.options.maxVelocity === 0 ? 1e9 : this.options.maxVelocity; |
|
|
|
var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1e9; |
|
|
|
var stabilized = true; |
|
|
|
var vminCorrected = this.options.minVelocity / Math.max(this.body.view.scale,0.05); |
|
|
|
|
|
|
@ -325,6 +342,15 @@ class PhysicsEngine { |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Perform the actual step |
|
|
|
* |
|
|
|
* @param nodeId |
|
|
|
* @param maxVelocity |
|
|
|
* @returns {number} |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
_performStep(nodeId,maxVelocity) { |
|
|
|
var node = this.body.nodes[nodeId]; |
|
|
|
var timestep = this.options.timestep; |
|
|
@ -362,6 +388,10 @@ class PhysicsEngine { |
|
|
|
return totalVelocity; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* calculate the forces for one physics iteration. |
|
|
|
*/ |
|
|
|
calculateForces() { |
|
|
|
this.gravitySolver.solve(); |
|
|
|
this.nodesSolver.solve(); |
|
|
@ -412,24 +442,36 @@ class PhysicsEngine { |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
stabilize() { |
|
|
|
// stop the render loop
|
|
|
|
this.stopSimulation(); |
|
|
|
|
|
|
|
// set stabilze to false
|
|
|
|
this.stabilized = false; |
|
|
|
|
|
|
|
// block redraw requests
|
|
|
|
this.body.emitter.emit('_blockRedrawRequests'); |
|
|
|
this.body.emitter.emit('startStabilizing'); |
|
|
|
this.startedStabilization = true; |
|
|
|
|
|
|
|
// start the stabilization
|
|
|
|
if (this.options.stabilization.onlyDynamicEdges === true) { |
|
|
|
this._freezeNodes(); |
|
|
|
} |
|
|
|
this.stabilizationSteps = 0; |
|
|
|
this.stabilizationIterations = 0; |
|
|
|
|
|
|
|
setTimeout(this._stabilizationBatch.bind(this),0); |
|
|
|
} |
|
|
|
|
|
|
|
_stabilizationBatch() { |
|
|
|
var count = 0; |
|
|
|
while (this.stabilized === false && count < this.options.stabilization.updateInterval && this.stabilizationSteps < this.options.stabilization.iterations) { |
|
|
|
while (this.stabilized === false && count < this.options.stabilization.updateInterval && this.stabilizationIterations < this.options.stabilization.iterations) { |
|
|
|
this.physicsTick(); |
|
|
|
this.stabilizationSteps++; |
|
|
|
this.stabilizationIterations++; |
|
|
|
count++; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.stabilized === false && this.stabilizationSteps < this.options.stabilization.iterations) { |
|
|
|
this.body.emitter.emit('stabilizationProgress', {steps: this.stabilizationSteps, total: this.options.stabilization.iterations}); |
|
|
|
if (this.stabilized === false && this.stabilizationIterations < this.options.stabilization.iterations) { |
|
|
|
this.body.emitter.emit('stabilizationProgress', {iterations: this.stabilizationIterations, total: this.options.stabilization.iterations}); |
|
|
|
setTimeout(this._stabilizationBatch.bind(this),0); |
|
|
|
} |
|
|
|
else { |
|
|
|