Files
cell/prosperon/tween.cm

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 = undefined
stop = undefined
cb = undefined
update = undefined
}
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 }