import Stats from '../vendor/stats.js'
/**
* Class Animator controls running of a function.
*/
class Animator {
/**
* Sets parameters, then calls start().
* To have the model not start immediately do:
* - const anim = new Animator(\<args>).stop()
*
* @param {Function} fcn The function the animator runs and controls
* @param {number} steps How many steps to run. Default is -1, forever
* @param {number} fps Max frames/second to run. Default is 30
*/
constructor(fcn, steps = -1, fps = 30) {
Object.assign(this, { fcn, steps, fps, ticks: 0 })
this.stats = null
this.timeoutID = null
this.idle = null
this.idleFps = null
this.idleID = null
this.start()
}
/**
* Starts the model running
*
* @returns The animator, allows chaining
*/
start() {
// if (this.timeoutID) return // avoid multiple starts
this.clearIDs()
this.timeoutID = setInterval(() => this.step(), 1000 / this.fps)
// if (this.idleID) this.idleID = clearInterval(this.idleID)
return this // chaining off ctor
}
/**
* Stops the model running
*
* @returns The animator, allows chaining
*/
stop() {
// if (this.timeoutID) this.timeoutID = clearInterval(this.timeoutID)
// if (this.idleID) this.idleID = clearInterval(this.idleID)
this.clearIDs()
if (this.idle)
this.idleID = setInterval(() => this.idle(), 1000 / this.idleFps)
return this // chaining off ctor
}
step() {
// if (this.steps === 0) return this.stop()
if (this.ticks === this.steps) return this.stop()
this.ticks++
// this.steps--
this.fcn()
if (this.stats) this.stats.update()
return this // chaining off ctor
}
clearIDs() {
if (this.timeoutID) this.timeoutID = clearInterval(this.timeoutID)
if (this.idleID) this.idleID = clearInterval(this.idleID)
}
isRunning() {
return this.timeoutID != null
}
startStats(statsPosition = 'top:0px;left:0px', parentID = document.body) {
if (this.stats) {
this.stopStats()
}
const stats = new Stats()
const parent =
typeof parentID === 'string'
? document.getElementById(parentID)
: parentID
if (parent != document.body) {
parent.style.position = 'relative'
}
parent.appendChild(stats.dom)
stats.dom.style.cssText = statsPosition // 'top:10px;right:20px;'
stats.dom.style.position = 'absolute'
this.stats = stats
return this // chaining off ctor
}
stopStats() {
const stats = this.stats
if (stats && stats.dom && stats.dom.parentNode) {
stats.dom.parentNode.removeChild(stats.dom)
} else {
console.warn('Stats panel not found or already removed.')
}
this.stats = null
return this // chaining off ctor
}
// startStats(left = '0px') {
// if (this.stats) return console.log('startStats: already running')
// this.stats = new Stats()
// document.body.appendChild(this.stats.dom)
// this.stats.dom.style.left = left
// return this // chaining off ctor
// }
setFps(fps) {
if (fps <= 0) {
console.log('fps must be > 0, using 0.00000001')
fps = 0.00000001
}
this.reset(this.steps, fps)
}
setSteps(steps) {
this.reset(steps, this.fps)
}
// set the new steps & fps, restStart if currently running
reset(steps = this.steps, fps = this.fps) {
const wasRunning = this.isRunning()
if (wasRunning) this.stop()
this.steps = steps
this.ticks = 0
this.fps = fps
if (wasRunning) this.start()
}
restart(model, view, ctrl = undefined) {
model.reset()
// window.ui.view.reset()
this.reset()
view.draw()
if (ctrl) ctrl.reset()
}
// stop if running, start otherwise
// if starting and steps === 0, reset with steps = -1, forever.
toggle() {
// if (this.timeoutID) this.stop()
if (this.isRunning()) this.stop()
// else if (this.steps === 0) this.reset()
else this.start()
}
// call the fcn once. stops if currently running
once() {
this.stop()
this.step()
}
setIdle(fcn, fps = 4) {
this.idle = fcn
this.idleFps = fps
}
}
export default Animator