282 lines
6.9 KiB
Plaintext
282 lines
6.9 KiB
Plaintext
var util = use('util')
|
|
|
|
var Ease = {
|
|
linear(t) {
|
|
return t
|
|
},
|
|
in(t) {
|
|
return t * t
|
|
},
|
|
out(t) {
|
|
var d = 1 - t
|
|
return 1 - d * d
|
|
},
|
|
inout(t) {
|
|
var d = -2 * t + 2
|
|
return t < 0.5 ? 2 * t * t : 1 - (d * d) / 2
|
|
},
|
|
}
|
|
|
|
function make_easing_fns(num) {
|
|
var obj = {}
|
|
|
|
obj.in = function (t) {
|
|
return Math.pow(t, num)
|
|
}
|
|
|
|
obj.out = function (t) {
|
|
return 1 - Math.pow(1 - t, num)
|
|
}
|
|
|
|
var mult = Math.pow(2, num - 1)
|
|
obj.inout = function (t) {
|
|
return t < 0.5 ? mult * Math.pow(t, num) : 1 - Math.pow(-2 * t + 2, num) / 2
|
|
}
|
|
|
|
return obj
|
|
}
|
|
|
|
Ease.quad = make_easing_fns(2)
|
|
Ease.cubic = make_easing_fns(3)
|
|
Ease.quart = make_easing_fns(4)
|
|
Ease.quint = make_easing_fns(5)
|
|
|
|
Ease.expo = {
|
|
in(t) {
|
|
return t == 0 ? 0 : Math.pow(2, 10 * t - 10)
|
|
},
|
|
out(t) {
|
|
return t == 1 ? 1 : 1 - Math.pow(2, -10 * t)
|
|
},
|
|
inout(t) {
|
|
return t == 0
|
|
? 0
|
|
: t == 1
|
|
? 1
|
|
: t < 0.5
|
|
? Math.pow(2, 20 * t - 10) / 2
|
|
: (2 - Math.pow(2, -20 * t + 10)) / 2
|
|
},
|
|
}
|
|
|
|
Ease.bounce = {
|
|
in(t) {
|
|
return 1 - this.out(t - 1)
|
|
},
|
|
out(t) {
|
|
var n1 = 7.5625
|
|
var d1 = 2.75
|
|
if (t < 1 / d1) {
|
|
return n1 * t * t
|
|
} else if (t < 2 / d1) {
|
|
return n1 * (t -= 1.5 / d1) * t + 0.75
|
|
} else if (t < 2.5 / d1) {
|
|
return n1 * (t -= 2.25 / d1) * t + 0.9375
|
|
} else return n1 * (t -= 2.625 / d1) * t + 0.984375
|
|
},
|
|
inout(t) {
|
|
return t < 0.5 ? (1 - this.out(1 - 2 * t)) / 2 : (1 + this.out(2 * t - 1)) / 2
|
|
},
|
|
}
|
|
|
|
Ease.sine = {
|
|
in(t) {
|
|
return 1 - Math.cos((t * Math.PI) / 2)
|
|
},
|
|
out(t) {
|
|
return Math.sin((t * Math.PI) / 2)
|
|
},
|
|
inout(t) {
|
|
return -(Math.cos(Math.PI * t) - 1) / 2
|
|
},
|
|
}
|
|
|
|
Ease.elastic = {
|
|
in(t) {
|
|
return t == 0
|
|
? 0
|
|
: t == 1
|
|
? 1
|
|
: -Math.pow(2, 10 * t - 10) *
|
|
Math.sin((t * 10 - 10.75) * this.c4)
|
|
},
|
|
out(t) {
|
|
return t == 0
|
|
? 0
|
|
: t == 1
|
|
? 1
|
|
: Math.pow(2, -10 * t) *
|
|
Math.sin((t * 10 - 0.75) * this.c4) +
|
|
1
|
|
},
|
|
inout(t) {
|
|
t == 0
|
|
? 0
|
|
: t == 1
|
|
? 1
|
|
: t < 0.5
|
|
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
|
|
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2 + 1
|
|
},
|
|
}
|
|
|
|
Ease.elastic.c4 = (2 * Math.PI) / 3
|
|
Ease.elastic.c5 = (2 * Math.PI) / 4.5
|
|
|
|
var tween = function (from, to, time, fn, cb) {
|
|
var start = os.now()
|
|
|
|
function cleanup() {
|
|
stop()
|
|
fn = null
|
|
stop = null
|
|
cb = null
|
|
update = null
|
|
}
|
|
|
|
var update = function tween_update(dt) {
|
|
var elapsed = os.now() - start
|
|
fn(util.obj_lerp(from, to, elapsed / time))
|
|
if (elapsed >= time) {
|
|
fn(to)
|
|
cb?.()
|
|
cleanup()
|
|
}
|
|
}
|
|
var stop = Register.update.register(update)
|
|
return cleanup
|
|
}
|
|
|
|
var Tween = {
|
|
default: {
|
|
loop: "hold",
|
|
time: 1,
|
|
ease: Ease.linear,
|
|
whole: true,
|
|
cb: function () {},
|
|
},
|
|
start(obj, target, tvals, options) {
|
|
var defn = Object.create(this.default)
|
|
Object.assign(defn, options)
|
|
|
|
if (defn.loop == "circle") tvals.push(tvals[0])
|
|
else if (defn.loop == "yoyo") {
|
|
for (var i = tvals.length - 2; i >= 0; i--) tvals.push(tvals[i])
|
|
}
|
|
|
|
defn.accum = 0
|
|
var slices = tvals.length - 1
|
|
var slicelen = 1 / slices
|
|
|
|
defn.fn = function (dt) {
|
|
defn.accum += dt
|
|
if (defn.accum >= defn.time && defn.loop == "hold") {
|
|
if (typeof target == "string") obj[target] = tvals[tvals.length - 1]
|
|
else target(tvals[tvals.length - 1])
|
|
defn.pause()
|
|
defn.cb.call(obj)
|
|
return
|
|
}
|
|
defn.pct = (defn.accum % defn.time) / defn.time
|
|
if (defn.loop == "none" && defn.accum >= defn.time) defn.stop()
|
|
|
|
var t = defn.whole ? defn.ease(defn.pct) : defn.pct
|
|
var nval = t / slicelen
|
|
var i = Math.trunc(nval)
|
|
nval -= i
|
|
if (!defn.whole) nval = defn.ease(nval)
|
|
|
|
if (typeof target == "string") obj[target] = tvals[i].lerp(tvals[i + 1], nval)
|
|
else target(tvals[i].lerp(tvals[i + 1], nval))
|
|
}
|
|
|
|
var playing = false
|
|
|
|
defn.play = function () {
|
|
if (playing) return
|
|
defn._end = Register.update.register(defn.fn.bind(defn))
|
|
playing = true
|
|
}
|
|
defn.restart = function () {
|
|
defn.accum = 0
|
|
if (typeof target == "string") obj[target] = tvals[0]
|
|
else target(tvals[0])
|
|
}
|
|
defn.stop = function () {
|
|
if (!playing) return
|
|
defn.pause()
|
|
defn.restart()
|
|
}
|
|
defn.pause = function () {
|
|
defn._end()
|
|
if (!playing) return
|
|
playing = false
|
|
}
|
|
|
|
return defn
|
|
},
|
|
}
|
|
|
|
Tween.make = Tween.start
|
|
|
|
Ease[cell.DOC] = `
|
|
This object provides multiple easing functions that remap a 0..1 input to produce
|
|
a smoothed or non-linear output. They can be used standalone or inside tweens.
|
|
|
|
Available functions:
|
|
- linear(t)
|
|
- in(t), out(t), inout(t)
|
|
- quad.in, quad.out, quad.inout
|
|
- cubic.in, cubic.out, cubic.inout
|
|
- quart.in, quart.out, quart.inout
|
|
- quint.in, quint.out, quint.inout
|
|
- expo.in, expo.out, expo.inout
|
|
- bounce.in, bounce.out, bounce.inout
|
|
- sine.in, sine.out, sine.inout
|
|
- elastic.in, elastic.out, elastic.inout
|
|
|
|
All easing functions expect t in [0..1] and return a remapped value in [0..1].
|
|
`
|
|
|
|
tween[cell.DOC] = `
|
|
:param from: The starting object or value to interpolate from.
|
|
:param to: The ending object or value to interpolate to.
|
|
:param time: The total duration of the tween in milliseconds or some time unit.
|
|
:param fn: A callback function that receives the interpolated value at each update.
|
|
:param cb: (Optional) A callback invoked once the tween completes.
|
|
:return: A function that, when called, cleans up and stops the tween.
|
|
|
|
Creates a simple tween that linearly interpolates from "from" to "to" over "time"
|
|
and calls "fn" with each interpolated value. Once finished, "fn" is called with "to",
|
|
then "cb" is invoked if provided, and the tween is cleaned up.
|
|
`
|
|
|
|
Tween[cell.DOC] = `
|
|
An object providing methods to create and control tweens with additional features
|
|
like looping, custom easing, multiple stages, etc.
|
|
|
|
Properties:
|
|
- default: A template object with loop/time/ease/whole/cb properties.
|
|
Methods:
|
|
- start(obj, target, tvals, options): Create a tween over multiple target values.
|
|
- make: Alias of start.
|
|
`
|
|
|
|
Tween.start[cell.DOC] = `
|
|
:param obj: The object whose property is being tweened, or context for the callback.
|
|
:param target: A string property name in obj or a callback function receiving interpolated values.
|
|
:param tvals: An array of values to tween through (each must support .lerp()).
|
|
:param options: An optional object overriding defaults (loop type, time, ease, etc.).
|
|
:return: A tween definition object with .play(), .pause(), .stop(), .restart(), etc.
|
|
|
|
Set up a multi-stage tween. You can specify looping modes (none, hold, restart, yoyo, circle),
|
|
time is the total duration, and "ease" can be any function from Ease. Once started, it updates
|
|
every frame until completion or stop/pause is called.
|
|
`
|
|
|
|
Tween.make[cell.DOC] = `
|
|
Alias of Tween.start. See Tween.start for usage details.
|
|
`
|
|
|
|
return { Tween, Ease, tween }
|