@ -1,43 +1,26 @@
import BarnesHutSolver from './components/physics/BarnesHutSolver' ;
import Repulsion from './components/physics/RepulsionSolver' ;
import HierarchicalRepulsion from './components/physics/HierarchicalRepulsionSolver' ;
import SpringSolver from './components/physics/SpringSolver' ;
import HierarchicalSpringSolver from './components/physics/HierarchicalSpringSolver' ;
import CentralGravitySolver from './components/physics/CentralGravitySolver' ;
import ForceAtlas2BasedRepulsionSolver from './components/physics/FA2BasedRepulsionSolver' ;
import ForceAtlas2BasedCentralGravitySolver from './components/physics/FA2BasedCentralGravitySolver' ;
import PhysicsBase from './PhysicsBase' ;
import PhysicsWorker from 'worker!./PhysicsWorkerWrapper' ;
var util = require ( '../../util' ) ;
class PhysicsEngine {
class PhysicsEngine extends PhysicsBase {
constructor ( body ) {
super ( ) ;
this . body = body ;
this . physicsBody = { physicsNodeIndices : [ ] , physicsEdgeIndices : [ ] , forces : { } , velocities : { } } ;
this . physicsEnabled = true ;
this . simulationInterval = 1000 / 60 ;
this . requiresTimeout = true ;
this . previousStates = { } ;
this . referenceState = { } ;
this . freezeCache = { } ;
this . renderTimer = undefined ;
// parameters for the adaptive timestep
this . adaptiveTimestep = false ;
this . adaptiveTimestepEnabled = false ;
this . adaptiveCounter = 0 ;
this . adaptiveInterval = 3 ;
this . stabilized = false ;
this . startedStabilization = false ;
this . stabilizationIterations = 0 ;
this . ready = false ; // will be set to true if the stabilize
// default options
this . options = { } ;
this . defaultOptions = {
enabled : true ,
useWorker : false ,
barnesHut : {
theta : 0.5 ,
gravitationalConstant : - 2000 ,
@ -85,8 +68,11 @@ class PhysicsEngine {
adaptiveTimestep : true
} ;
util . extend ( this . options , this . defaultOptions ) ;
this . timestep = 0.5 ;
this . layoutFailed = false ;
this . draggingNodes = [ ] ;
this . positionUpdateHandler = ( ) => { } ;
this . physicsUpdateHandler = ( ) => { } ;
this . emit = this . body . emitter . emit ;
this . bindEventListeners ( ) ;
}
@ -112,6 +98,20 @@ class PhysicsEngine {
this . stopSimulation ( false ) ;
this . body . emitter . off ( ) ;
} ) ;
this . body . emitter . on ( '_positionUpdate' , ( properties ) => this . positionUpdateHandler ( properties ) ) ;
this . body . emitter . on ( '_physicsUpdate' , ( properties ) => this . physicsUpdateHandler ( properties ) ) ;
this . body . emitter . on ( 'dragStart' , ( properties ) => {
this . draggingNodes = properties . nodes ;
} ) ;
this . body . emitter . on ( 'dragEnd' , ( ) => {
this . draggingNodes = [ ] ;
} ) ;
this . body . emitter . on ( 'destroy' , ( ) => {
if ( this . physicsWorker ) {
this . physicsWorker . terminate ( ) ;
this . physicsWorker = undefined ;
}
} ) ;
}
@ -144,43 +144,153 @@ class PhysicsEngine {
this . timestep = this . options . timestep ;
}
}
this . init ( ) ;
if ( this . options . useWorker ) {
this . initPhysicsWorker ( ) ;
this . physicsWorker . postMessage ( { type : 'options' , data : this . options } ) ;
} else {
this . initEmbeddedPhysics ( ) ;
}
}
/ * *
* configure the engine .
* /
init ( ) {
var options ;
if ( this . options . solver === 'forceAtlas2Based' ) {
options = this . options . forceAtlas2Based ;
this . nodesSolver = new ForceAtlas2BasedRepulsionSolver ( this . body , this . physicsBody , options ) ;
this . edgesSolver = new SpringSolver ( this . body , this . physicsBody , options ) ;
this . gravitySolver = new ForceAtlas2BasedCentralGravitySolver ( this . body , this . physicsBody , options ) ;
}
else if ( this . options . solver === 'repulsion' ) {
options = this . options . repulsion ;
this . nodesSolver = new Repulsion ( this . body , this . physicsBody , options ) ;
this . edgesSolver = new SpringSolver ( this . body , this . physicsBody , options ) ;
this . gravitySolver = new CentralGravitySolver ( this . body , this . physicsBody , options ) ;
}
else if ( this . options . solver === 'hierarchicalRepulsion' ) {
options = this . options . hierarchicalRepulsion ;
this . nodesSolver = new HierarchicalRepulsion ( this . body , this . physicsBody , options ) ;
this . edgesSolver = new HierarchicalSpringSolver ( this . body , this . physicsBody , options ) ;
this . gravitySolver = new CentralGravitySolver ( this . body , this . physicsBody , options ) ;
}
else { // barnesHut
options = this . options . barnesHut ;
this . nodesSolver = new BarnesHutSolver ( this . body , this . physicsBody , options ) ;
this . edgesSolver = new SpringSolver ( this . body , this . physicsBody , options ) ;
this . gravitySolver = new CentralGravitySolver ( this . body , this . physicsBody , options ) ;
}
this . modelOptions = options ;
initEmbeddedPhysics ( ) {
this . positionUpdateHandler = ( ) => { } ;
this . physicsUpdateHandler = ( properties ) => {
if ( properties . options . physics !== undefined ) {
// we've received a node that has changed physics state
// so rebuild physicsBody
this . initPhysicsData ( ) ;
}
// else we're accessing the information directly out of the node
// so no need to do anything.
} ;
if ( this . physicsWorker ) {
this . options . useWorker = false ;
this . physicsWorker . terminate ( ) ;
this . physicsWorker = undefined ;
this . initPhysicsData ( ) ;
}
this . initPhysicsSolvers ( ) ;
}
initPhysicsWorker ( ) {
if ( ! this . physicsWorker ) {
// setup path to webworker javascript file
if ( ! __webpack_public_path__ ) {
// search for element with id of 'visjs'
let parentScript = document . getElementById ( 'visjs' ) ;
if ( parentScript ) {
let src = parentScript . getAttribute ( 'src' )
__webpack_public_path__ = src . substr ( 0 , src . lastIndexOf ( '/' ) + 1 ) ;
} else {
// search all scripts for 'vis.js'
let scripts = document . getElementsByTagName ( 'script' ) ;
for ( let i = 0 ; i < scripts . length ; i ++ ) {
let src = scripts [ i ] . getAttribute ( 'src' ) ;
if ( src && src . length >= 6 ) {
let position = src . length - 6 ;
let index = src . indexOf ( 'vis.js' , position ) ;
if ( index === position ) {
__webpack_public_path__ = src . substr ( 0 , src . lastIndexOf ( '/' ) + 1 ) ;
break ;
}
}
}
}
}
// launch webworker
this . physicsWorker = new PhysicsWorker ( ) ;
this . physicsWorker . addEventListener ( 'message' , ( event ) => {
this . physicsWorkerMessageHandler ( event ) ;
} ) ;
this . physicsWorker . onerror = ( event ) => {
console . error ( 'Falling back to embedded physics engine' , event ) ;
this . initEmbeddedPhysics ( ) ;
// throw new Error(event.message + " (" + event.filename + ":" + event.lineno + ")");
} ;
this . positionUpdateHandler = ( positions ) => {
this . physicsWorker . postMessage ( { type : 'updatePositions' , data : positions } ) ;
} ;
this . physicsUpdateHandler = ( properties ) => {
this . _physicsUpdateHandler ( properties ) ;
} ;
}
}
_physicsUpdateHandler ( properties ) {
if ( properties . options . physics !== undefined ) {
if ( properties . options . physics ) {
let data = {
nodes : { } ,
edges : { }
} ;
if ( properties . type === 'node' ) {
data . nodes [ properties . id ] = this . createPhysicsNode ( properties . id ) ;
} else if ( properties . type === 'edge' ) {
data . edges [ properties . id ] = this . createPhysicsEdge ( properties . id ) ;
} else {
console . warn ( 'invalid element type' ) ;
}
this . physicsWorker . postMessage ( {
type : 'addElements' ,
data : data
} ) ;
} else {
let data = {
nodeIds : [ ] ,
edgeIds : [ ]
} ;
if ( properties . type === 'node' ) {
data . nodeIds = [ properties . id . toString ( ) ] ;
} else if ( properties . type === 'edge' ) {
data . edgeIds = [ properties . id . toString ( ) ] ;
} else {
console . warn ( 'invalid element type' ) ;
}
this . physicsWorker . postMessage ( { type : 'removeElements' , data : data } ) ;
}
} else {
this . physicsWorker . postMessage ( { type : 'updateProperties' , data : properties } ) ;
}
}
physicsWorkerMessageHandler ( event ) {
var msg = event . data ;
switch ( msg . type ) {
case 'tickResults' :
this . stabilized = msg . data . stabilized ;
this . stabilizationIterations = msg . data . stabilizationIterations ;
this . _receivedPositions ( msg . data . positions ) ;
break ;
case 'finalizeStabilization' :
this . _finalizeStabilization ( ) ;
break ;
case 'emit' :
this . emit ( msg . data . event , msg . data . data ) ;
break ;
default :
console . warn ( 'unhandled physics worker message:' , msg ) ;
}
}
_receivedPositions ( positions ) {
for ( let i = 0 ; i < this . draggingNodes . length ; i ++ ) {
delete positions [ this . draggingNodes [ i ] ] ;
}
let nodeIds = Object . keys ( positions ) ;
for ( let i = 0 ; i < nodeIds . length ; i ++ ) {
let nodeId = nodeIds [ i ] ;
let node = this . body . nodes [ nodeId ] ;
// handle case where we get a positions from an old physicsObject
if ( node ) {
node . setX ( positions [ nodeId ] . x ) ;
node . setY ( positions [ nodeId ] . y ) ;
}
}
}
/ * *
* initialize the engine
@ -209,6 +319,7 @@ class PhysicsEngine {
startSimulation ( ) {
if ( this . physicsEnabled === true && this . options . enabled === true ) {
this . stabilized = false ;
this . _updateWorkerStabilized ( ) ;
// when visible, adaptivity is disabled.
this . adaptiveTimestep = false ;
@ -232,6 +343,8 @@ class PhysicsEngine {
* /
stopSimulation ( emit = true ) {
this . stabilized = true ;
this . _updateWorkerStabilized ( ) ;
if ( emit === true ) {
this . _emitStabilized ( ) ;
}
@ -244,23 +357,35 @@ class PhysicsEngine {
}
}
_updateWorkerStabilized ( ) {
if ( this . physicsWorker ) {
this . physicsWorker . postMessage ( {
type : 'setStabilized' ,
data : this . stabilized
} ) ;
}
}
/ * *
* 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 ( ) ;
this . physicsTick ( ) ;
var physicsTime = Date . now ( ) - startTime ;
// run double speed if it is a little graph
if ( ( physicsTime < 0.4 * this . simulationInterval || this . runDoubleSpeed === true ) && this . stabilized === false ) {
if ( this . physicsWorker ) {
this . physicsWorker . postMessage ( { type : 'physicsTick' } ) ;
} else {
// check if the physics have settled
var startTime = Date . now ( ) ;
this . physicsTick ( ) ;
var physicsTime = Date . now ( ) - startTime ;
// run double speed if it is a little graph
if ( ( physicsTime < 0.4 * this . simulationInterval || this . runDoubleSpeed === true ) && this . stabilized === false ) {
this . physicsTick ( ) ;
// this makes sure there is no jitter. The decision is taken once to run it at double speed.
this . runDoubleSpeed = true ;
// this makes sure there is no jitter. The decision is taken once to run it at double speed.
this . runDoubleSpeed = true ;
}
}
if ( this . stabilized === true ) {
@ -268,6 +393,13 @@ class PhysicsEngine {
}
}
_sendWorkerStabilized ( ) {
if ( this . physicsWorker ) {
this . physicsWorker . postMessage ( {
type : 'stabilized'
} ) ;
}
}
/ * *
* trigger the stabilized event .
@ -279,86 +411,55 @@ class PhysicsEngine {
this . body . emitter . emit ( 'stabilized' , { iterations : amountOfIterations } ) ;
this . startedStabilization = false ;
this . stabilizationIterations = 0 ;
this . _sendWorkerStabilized ( ) ;
} , 0 ) ;
}
}
/ * *
* A single simulation step ( or 'tick' ) in the physics simulation
*
* @ private
* /
physicsTick ( ) {
// this is here to ensure that there is no start event when the network is already stable.
if ( this . startedStabilization === false ) {
this . body . emitter . emit ( 'startStabilizing' ) ;
this . startedStabilization = true ;
}
if ( this . stabilized === false ) {
// adaptivity means the timestep adapts to the situation, only applicable for stabilization
if ( this . adaptiveTimestep === true && this . adaptiveTimestepEnabled === true ) {
// this is the factor for increasing the timestep on success.
let factor = 1.2 ;
// we assume the adaptive interval is
if ( this . adaptiveCounter % this . adaptiveInterval === 0 ) { // we leave the timestep stable for "interval" iterations.
// first the big step and revert. Revert saves the reference state.
this . timestep = 2 * this . timestep ;
this . calculateForces ( ) ;
this . moveNodes ( ) ;
this . revert ( ) ;
// now the normal step. Since this is the last step, it is the more stable one and we will take this.
this . timestep = 0.5 * this . timestep ;
// since it's half the step, we do it twice.
this . calculateForces ( ) ;
this . moveNodes ( ) ;
this . calculateForces ( ) ;
this . moveNodes ( ) ;
// we compare the two steps. if it is acceptable we double the step.
if ( this . _evaluateStepQuality ( ) === true ) {
this . timestep = factor * this . timestep ;
}
else {
// if not, we decrease the step to a minimum of the options timestep.
// if the decreased timestep is smaller than the options step, we do not reset the counter
// we assume that the options timestep is stable enough.
if ( this . timestep / factor < this . options . timestep ) {
this . timestep = this . options . timestep ;
}
else {
// if the timestep was larger than 2 times the option one we check the adaptivity again to ensure
// that large instabilities do not form.
this . adaptiveCounter = - 1 ; // check again next iteration
this . timestep = Math . max ( this . options . timestep , this . timestep / factor ) ;
}
}
}
else {
// normal step, keeping timestep constant
this . calculateForces ( ) ;
this . moveNodes ( ) ;
createPhysicsNode ( nodeId ) {
let node = this . body . nodes [ nodeId ] ;
if ( node ) {
return {
id : node . id . toString ( ) ,
x : node . x ,
y : node . y ,
// TODO update on change
edges : {
length : node . edges . length
} ,
options : {
fixed : {
x : node . options . fixed . x ,
y : node . options . fixed . y
} ,
mass : node . options . mass
}
// increment the counter
this . adaptiveCounter += 1 ;
}
else {
// case for the static timestep, we reset it to the one in options and take a normal step.
this . timestep = this . options . timestep ;
this . calculateForces ( ) ;
this . moveNodes ( ) ;
}
}
}
// determine if the network has stabilzied
if ( this . stabilized === true ) {
this . revert ( ) ;
createPhysicsEdge ( edgeId ) {
let edge = this . body . edges [ edgeId ] ;
if ( edge && edge . options . physics === true ) {
let physicsEdge = {
id : edge . id ,
connected : edge . connected ,
edgeType : { } ,
toId : edge . toId ,
fromId : edge . fromId ,
options : {
length : edge . length
}
} ;
// TODO test/implment dynamic
if ( edge . edgeType . via ) {
physicsEdge . edgeType = {
via : {
id : edge . edgeType . via . id
}
}
}
this . stabilizationIterations ++ ;
return physicsEdge ;
}
}
@ -367,18 +468,25 @@ class PhysicsEngine {
*
* @ private
* /
updatePhysicsData ( ) {
initPhysicsData ( ) {
let nodes = this . body . nodes ;
let edges = this . body . edges ;
this . physicsBody . forces = { } ;
this . physicsBody . physicsNodeIndices = [ ] ;
this . physicsBody . physicsEdgeIndices = [ ] ;
let nodes = this . body . nodes ;
let edges = this . body . edges ;
let physicsWorkerNodes = { } ;
let physicsWorkerEdges = { } ;
// get node indices for physics
for ( let nodeId in nodes ) {
if ( nodes . hasOwnProperty ( nodeId ) ) {
if ( nodes [ nodeId ] . options . physics === true ) {
this . physicsBody . physicsNodeIndices . push ( nodeId ) ;
if ( this . physicsWorker ) {
physicsWorkerNodes [ nodeId ] = this . createPhysicsNode ( nodeId ) ;
}
}
}
}
@ -388,6 +496,9 @@ class PhysicsEngine {
if ( edges . hasOwnProperty ( edgeId ) ) {
if ( edges [ edgeId ] . options . physics === true ) {
this . physicsBody . physicsEdgeIndices . push ( edgeId ) ;
if ( this . physicsWorker ) {
physicsWorkerEdges [ edgeId ] = this . createPhysicsEdge ( edgeId ) ;
}
}
}
}
@ -395,11 +506,11 @@ class PhysicsEngine {
// get the velocity and the forces vector
for ( let i = 0 ; i < this . physicsBody . physicsNodeIndices . length ; i ++ ) {
let nodeId = this . physicsBody . physicsNodeIndices [ i ] ;
this . physicsBody . forces [ nodeId ] = { x : 0 , y : 0 } ;
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 } ;
this . physicsBody . velocities [ nodeId ] = { x : 0 , y : 0 } ;
}
}
@ -409,88 +520,18 @@ class PhysicsEngine {
delete this . physicsBody . velocities [ nodeId ] ;
}
}
}
/ * *
* 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 ;
var velocities = this . physicsBody . velocities ;
this . referenceState = { } ;
for ( let i = 0 ; i < nodeIds . length ; i ++ ) {
let nodeId = nodeIds [ i ] ;
if ( nodes [ nodeId ] !== undefined ) {
if ( nodes [ nodeId ] . options . physics === true ) {
this . referenceState [ nodeId ] = {
positions : { x : nodes [ nodeId ] . x , y : nodes [ nodeId ] . y }
} ;
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 ;
if ( this . physicsWorker ) {
this . physicsWorker . postMessage ( {
type : 'initPhysicsData' ,
data : {
nodes : physicsWorkerNodes ,
edges : physicsWorkerEdges
}
}
else {
delete this . previousStates [ nodeId ] ;
}
} ) ;
}
}
/ * *
* This compares the reference state to the current state
* /
_evaluateStepQuality ( ) {
let dx , dy , dpos ;
let nodes = this . body . nodes ;
let reference = this . referenceState ;
let posThreshold = 0.3 ;
for ( let nodeId in this . referenceState ) {
if ( this . referenceState . hasOwnProperty ( nodeId ) && nodes [ nodeId ] !== undefined ) {
dx = nodes [ nodeId ] . x - reference [ nodeId ] . positions . x ;
dy = nodes [ nodeId ] . y - reference [ nodeId ] . positions . y ;
dpos = Math . sqrt ( Math . pow ( dx , 2 ) + Math . pow ( dy , 2 ) )
if ( dpos > posThreshold ) {
return false ;
}
}
}
return true ;
}
/ * *
* move the nodes one timestap and check if they are stabilized
* @ returns { boolean }
* /
moveNodes ( ) {
var nodeIndices = this . physicsBody . physicsNodeIndices ;
var maxVelocity = this . options . maxVelocity ? this . options . maxVelocity : 1 e9 ;
var maxNodeVelocity = 0 ;
var averageNodeVelocity = 0 ;
// the velocity threshold (energy in the system) for the adaptivity toggle
var velocityAdaptiveThreshold = 5 ;
for ( let i = 0 ; i < nodeIndices . length ; i ++ ) {
let nodeId = nodeIndices [ i ] ;
let nodeVelocity = this . _performStep ( nodeId , maxVelocity ) ;
// stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized
maxNodeVelocity = Math . max ( maxNodeVelocity , nodeVelocity ) ;
averageNodeVelocity += nodeVelocity ;
}
// evaluating the stabilized and adaptiveTimestepEnabled conditions
this . adaptiveTimestepEnabled = ( averageNodeVelocity / nodeIndices . length ) < velocityAdaptiveThreshold ;
this . stabilized = maxNodeVelocity < this . options . minVelocity ;
}
/ * *
* Perform the actual step
*
@ -513,7 +554,7 @@ class PhysicsEngine {
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
node . setX ( node . x + velocities [ nodeId ] . x * timestep ) ; // position
}
else {
forces [ nodeId ] . x = 0 ;
@ -525,7 +566,7 @@ class PhysicsEngine {
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
node . setY ( node . y + velocities [ nodeId ] . y * timestep ) ; // position
}
else {
forces [ nodeId ] . y = 0 ;
@ -536,18 +577,7 @@ class PhysicsEngine {
return totalVelocity ;
}
/ * *
* calculate the forces for one physics iteration .
* /
calculateForces ( ) {
this . gravitySolver . solve ( ) ;
this . nodesSolver . solve ( ) ;
this . edgesSolver . solve ( ) ;
}
// TODO probably want to move freeze/restore to PhysicsBase and do in worker if running
/ * *
* When initializing and stabilizing , we can freeze nodes with a predefined position . This greatly speeds up stabilization
* because only the supportnodes for the smoothCurves have to settle .
@ -560,8 +590,7 @@ class PhysicsEngine {
if ( nodes . hasOwnProperty ( id ) ) {
if ( nodes [ id ] . x && nodes [ id ] . y ) {
this . freezeCache [ id ] = { x : nodes [ id ] . options . fixed . x , y : nodes [ id ] . options . fixed . y } ;
nodes [ id ] . options . fixed . x = true ;
nodes [ id ] . options . fixed . y = true ;
nodes [ id ] . setFixed ( true ) ;
}
}
}
@ -577,8 +606,7 @@ class PhysicsEngine {
for ( var id in nodes ) {
if ( nodes . hasOwnProperty ( id ) ) {
if ( this . freezeCache [ id ] !== undefined ) {
nodes [ id ] . options . fixed . x = this . freezeCache [ id ] . x ;
nodes [ id ] . options . fixed . y = this . freezeCache [ id ] . y ;
nodes [ id ] . setFixed ( { x : this . freezeCache [ id ] . x , y : this . freezeCache [ id ] . y } ) ;
}
}
}
@ -609,7 +637,7 @@ class PhysicsEngine {
// stop the render loop
this . stopSimulation ( ) ;
// set stabilze to false
// set stabili ze to false
this . stabilized = false ;
// block redraw requests
@ -621,38 +649,19 @@ class PhysicsEngine {
this . _freezeNodes ( ) ;
}
this . stabilizationIterations = 0 ;
setTimeout ( ( ) => this . _stabilizationBatch ( ) , 0 ) ;
}
/ * *
* One batch of stabilization
* @ private
* /
_stabilizationBatch ( ) {
// this is here to ensure that there is at least one start event.
if ( this . startedStabilization === false ) {
this . body . emitter . emit ( 'startStabilizing' ) ;
this . startedStabilization = true ;
}
var count = 0 ;
while ( this . stabilized === false && count < this . options . stabilization . updateInterval && this . stabilizationIterations < this . targetIterations ) {
this . physicsTick ( ) ;
count ++ ;
}
if ( this . stabilized === false && this . stabilizationIterations < this . targetIterations ) {
this . body . emitter . emit ( 'stabilizationProgress' , { iterations : this . stabilizationIterations , total : this . targetIterations } ) ;
setTimeout ( this . _stabilizationBatch . bind ( this ) , 0 ) ;
}
else {
this . _finalizeStabilization ( ) ;
if ( this . physicsWorker ) {
this . physicsWorker . postMessage ( {
type : 'stabilize' ,
data : {
targetIterations : iterations
}
} ) ;
} else {
setTimeout ( ( ) => this . _stabilizationBatch ( ) , 0 ) ;
}
}
/ * *
* Wrap up the stabilization , fit and emit the events .
* @ private
@ -679,7 +688,7 @@ class PhysicsEngine {
this . ready = true ;
}
}
export default PhysicsEngine ;