filtered-vector
Version:
Filter an input vector valued curve
292 lines (274 loc) • 7.34 kB
JavaScript
'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)
}
}