UNPKG

filtered-vector

Version:
292 lines (274 loc) 7.34 kB
'use strict' module.exports = createFilteredVector var cubicHermite = require('cubic-hermite') var bsearch = require('binary-search-bounds') function clamp(lo, hi, x) { return Math.min(hi, Math.max(lo, x)) } function FilteredVector(state0, velocity0, t0) { this.dimension = state0.length this.bounds = [ new Array(this.dimension), new Array(this.dimension) ] for(var i=0; i<this.dimension; ++i) { this.bounds[0][i] = -Infinity this.bounds[1][i] = Infinity } this._state = state0.slice().reverse() this._velocity = velocity0.slice().reverse() this._time = [ t0 ] this._scratch = [ state0.slice(), state0.slice(), state0.slice(), state0.slice(), state0.slice() ] } var proto = FilteredVector.prototype proto.flush = function(t) { var idx = bsearch.gt(this._time, t) - 1 if(idx <= 0) { return } this._time.splice(0, idx) this._state.splice(0, idx * this.dimension) this._velocity.splice(0, idx * this.dimension) } proto.curve = function(t) { var time = this._time var n = time.length var idx = bsearch.le(time, t) var result = this._scratch[0] var state = this._state var velocity = this._velocity var d = this.dimension var bounds = this.bounds if(idx < 0) { var ptr = d-1 for(var i=0; i<d; ++i, --ptr) { result[i] = state[ptr] } } else if(idx >= n-1) { var ptr = state.length-1 var tf = t - time[n-1] for(var i=0; i<d; ++i, --ptr) { result[i] = state[ptr] + tf * velocity[ptr] } } else { var ptr = d * (idx+1) - 1 var t0 = time[idx] var t1 = time[idx+1] var dt = (t1 - t0) || 1.0 var x0 = this._scratch[1] var x1 = this._scratch[2] var v0 = this._scratch[3] var v1 = this._scratch[4] var steady = true for(var i=0; i<d; ++i, --ptr) { x0[i] = state[ptr] v0[i] = velocity[ptr] * dt x1[i] = state[ptr+d] v1[i] = velocity[ptr+d] * dt steady = steady && (x0[i] === x1[i] && v0[i] === v1[i] && v0[i] === 0.0) } if(steady) { for(var i=0; i<d; ++i) { result[i] = x0[i] } } else { cubicHermite(x0, v0, x1, v1, (t-t0)/dt, result) } } var lo = bounds[0] var hi = bounds[1] for(var i=0; i<d; ++i) { result[i] = clamp(lo[i], hi[i], result[i]) } return result } proto.dcurve = function(t) { var time = this._time var n = time.length var idx = bsearch.le(time, t) var result = this._scratch[0] var state = this._state var velocity = this._velocity var d = this.dimension if(idx >= n-1) { var ptr = state.length-1 var tf = t - time[n-1] for(var i=0; i<d; ++i, --ptr) { result[i] = velocity[ptr] } } else { var ptr = d * (idx+1) - 1 var t0 = time[idx] var t1 = time[idx+1] var dt = (t1 - t0) || 1.0 var x0 = this._scratch[1] var x1 = this._scratch[2] var v0 = this._scratch[3] var v1 = this._scratch[4] var steady = true for(var i=0; i<d; ++i, --ptr) { x0[i] = state[ptr] v0[i] = velocity[ptr] * dt x1[i] = state[ptr+d] v1[i] = velocity[ptr+d] * dt steady = steady && (x0[i] === x1[i] && v0[i] === v1[i] && v0[i] === 0.0) } if(steady) { for(var i=0; i<d; ++i) { result[i] = 0.0 } } else { cubicHermite.derivative(x0, v0, x1, v1, (t-t0)/dt, result) for(var i=0; i<d; ++i) { result[i] /= dt } } } return result } proto.lastT = function() { var time = this._time return time[time.length-1] } proto.stable = function() { var velocity = this._velocity var ptr = velocity.length for(var i=this.dimension-1; i>=0; --i) { if(velocity[--ptr]) { return false } } return true } proto.jump = function(t) { var t0 = this.lastT() var d = this.dimension if(t < t0 || arguments.length !== d+1) { return } var state = this._state var velocity = this._velocity var ptr = state.length-this.dimension var bounds = this.bounds var lo = bounds[0] var hi = bounds[1] this._time.push(t0, t) for(var j=0; j<2; ++j) { for(var i=0; i<d; ++i) { state.push(state[ptr++]) velocity.push(0) } } this._time.push(t) for(var i=d; i>0; --i) { state.push(clamp(lo[i-1], hi[i-1], arguments[i])) velocity.push(0) } } proto.push = function(t) { var t0 = this.lastT() var d = this.dimension if(t < t0 || arguments.length !== d+1) { return } var state = this._state var velocity = this._velocity var ptr = state.length-this.dimension var dt = t - t0 var bounds = this.bounds var lo = bounds[0] var hi = bounds[1] var sf = (dt > 1e-6) ? 1/dt : 0 this._time.push(t) for(var i=d; i>0; --i) { var xc = clamp(lo[i-1], hi[i-1], arguments[i]) state.push(xc) velocity.push((xc - state[ptr++]) * sf) } } proto.set = function(t) { var d = this.dimension if(t < this.lastT() || arguments.length !== d+1) { return } var state = this._state var velocity = this._velocity var bounds = this.bounds var lo = bounds[0] var hi = bounds[1] this._time.push(t) for(var i=d; i>0; --i) { state.push(clamp(lo[i-1], hi[i-1], arguments[i])) velocity.push(0) } } proto.move = function(t) { var t0 = this.lastT() var d = this.dimension if(t <= t0 || arguments.length !== d+1) { return } var state = this._state var velocity = this._velocity var statePtr = state.length - this.dimension var bounds = this.bounds var lo = bounds[0] var hi = bounds[1] var dt = t - t0 var sf = (dt > 1e-6) ? 1/dt : 0.0 this._time.push(t) for(var i=d; i>0; --i) { var dx = arguments[i] state.push(clamp(lo[i-1], hi[i-1], state[statePtr++] + dx)) velocity.push(dx * sf) } } proto.idle = function(t) { var t0 = this.lastT() if(t < t0) { return } var d = this.dimension var state = this._state var velocity = this._velocity var statePtr = state.length-d var bounds = this.bounds var lo = bounds[0] var hi = bounds[1] var dt = t - t0 this._time.push(t) for(var i=d-1; i>=0; --i) { state.push(clamp(lo[i], hi[i], state[statePtr] + dt * velocity[statePtr])) velocity.push(0) statePtr += 1 } } function getZero(d) { var result = new Array(d) for(var i=0; i<d; ++i) { result[i] = 0.0 } return result } function createFilteredVector(initState, initVelocity, initTime) { switch(arguments.length) { case 0: return new FilteredVector([0], [0], 0) case 1: if(typeof initState === 'number') { var zero = getZero(initState) return new FilteredVector(zero, zero, 0) } else { return new FilteredVector(initState, getZero(initState.length), 0) } case 2: if(typeof initVelocity === 'number') { var zero = getZero(initState.length) return new FilteredVector(initState, zero, +initVelocity) } else { initTime = 0 } case 3: if(initState.length !== initVelocity.length) { throw new Error('state and velocity lengths must match') } return new FilteredVector(initState, initVelocity, initTime) } }