cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
89 lines (74 loc) • 3.19 kB
JavaScript
/*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */
/* Given a tension, friction, and duration, a simulation at 60FPS will first run without a defined duration in order to calculate the full path. A second pass
then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */
let generateSpringRK4 = (function(){
function springAccelerationForState( state ){
return (-state.tension * state.x) - (state.friction * state.v);
}
function springEvaluateStateWithDerivative( initialState, dt, derivative ){
let state = {
x: initialState.x + derivative.dx * dt,
v: initialState.v + derivative.dv * dt,
tension: initialState.tension,
friction: initialState.friction
};
return { dx: state.v, dv: springAccelerationForState( state ) };
}
function springIntegrateState( state, dt ){
let a = {
dx: state.v,
dv: springAccelerationForState( state )
},
b = springEvaluateStateWithDerivative( state, dt * 0.5, a ),
c = springEvaluateStateWithDerivative( state, dt * 0.5, b ),
d = springEvaluateStateWithDerivative( state, dt, c ),
dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx),
dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv);
state.x = state.x + dxdt * dt;
state.v = state.v + dvdt * dt;
return state;
}
return function springRK4Factory( tension, friction, duration ){
let initState = {
x: -1,
v: 0,
tension: null,
friction: null
},
path = [0],
time_lapsed = 0,
tolerance = 1 / 10000,
DT = 16 / 1000,
have_duration, dt, last_state;
tension = parseFloat( tension ) || 500;
friction = parseFloat( friction ) || 20;
duration = duration || null;
initState.tension = tension;
initState.friction = friction;
have_duration = duration !== null;
/* Calculate the actual time it takes for this animation to complete with the provided conditions. */
if( have_duration ){
/* Run the simulation without a duration. */
time_lapsed = springRK4Factory( tension, friction );
/* Compute the adjusted time delta. */
dt = time_lapsed / duration * DT;
} else {
dt = DT;
}
for(;;){
/* Next/step function .*/
last_state = springIntegrateState( last_state || initState, dt );
/* Store the position. */
path.push( 1 + last_state.x );
time_lapsed += 16;
/* If the change threshold is reached, break. */
if( !(Math.abs( last_state.x ) > tolerance && Math.abs( last_state.v ) > tolerance) ){
break;
}
}
/* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the
computed path and returns a snapshot of the position according to a given percentComplete. */
return !have_duration ? time_lapsed : function( percentComplete ){ return path[ (percentComplete * (path.length - 1)) | 0 ]; };
};
}());
export default generateSpringRK4;