Skip to content

Instantly share code, notes, and snippets.

@Falconerd
Created April 13, 2026 06:52
Show Gist options
  • Select an option

  • Save Falconerd/df2c43facb931f5d60f8ced7b4060180 to your computer and use it in GitHub Desktop.

Select an option

Save Falconerd/df2c43facb931f5d60f8ced7b4060180 to your computer and use it in GitHub Desktop.
ease-y animations
package ease
import "core:math"
Ease :: struct {
target: [^]f32,
start: [4]f32,
end: [4]f32,
count: int,
t: f32,
duration: f32,
easing: Easing,
generation: u32,
on_done: proc(ease: ^Ease, user_data: rawptr),
user_data: rawptr,
pool: ^Ease_Pool,
}
Easing :: enum {
LINEAR,
CUBIC_IN,
CUBIC_OUT,
BACK_OUT,
ELASTIC_OUT,
}
Handle :: struct {
index: u32,
generation: u32,
}
Ease_Pool :: struct {
eases: [dynamic]Ease,
// ---- VTABLE ----
update_all: proc(this: ^Ease_Pool, dt: f32),
create: proc(
this: ^Ease_Pool,
target: [^]f32,
to: [^]f32,
duration: f32,
count: int,
easing: Easing,
on_done: proc(ease: ^Ease, user_data: rawptr) = nil,
user_data: rawptr = nil,
) -> Handle,
is_complete: proc(this: ^Ease_Pool, handle: Handle) -> bool,
}
ease_no_op :: proc(ease: ^Ease, user_data: rawptr) {}
ease_pool_init :: proc(pool: ^Ease_Pool) {
pool.update_all = ease_pool_update_all
pool.create = ease_pool_create
pool.is_complete = ease_pool_is_complete
}
@(private = "file")
get_unused_index :: proc(eases: ^[dynamic]Ease) -> (index: u32, ok: bool) {
for e, i in eases {
if e.t >= 1 {
return u32(i), true
}
}
return
}
@(private = "file")
get_ease :: proc(eases: ^[dynamic]Ease) -> (^Ease, u32) {
if index, ok := get_unused_index(eases); ok {
return &eases[index], index
}
index := u32(len(eases))
append(eases, Ease{})
return &eases[index], index
}
ease_pool_is_complete :: proc(this: ^Ease_Pool, handle: Handle) -> bool {
if int(handle.index) >= len(this.eases) do return true
e := this.eases[handle.index]
if e.generation != handle.generation do return true
return e.t >= 1
}
ease_pool_create :: proc(
this: ^Ease_Pool,
target: [^]f32,
to: [^]f32,
duration: f32,
count: int,
easing: Easing = .LINEAR,
on_done := ease_no_op,
user_data: rawptr = nil,
) -> Handle {
assert(count <= 4)
to := to
e, index := get_ease(&this.eases)
e.target = target
for i in 0 ..< count {
e.start[i] = target[i]
e.end[i] = to[i]
}
e.t = 0
e.duration = duration
e.count = count
e.easing = easing
e.on_done = on_done
e.user_data = user_data
e.pool = this
e.generation += 1
return {index = index, generation = e.generation}
}
ease_pool_update_all :: proc(this: ^Ease_Pool, dt: f32) {
for &e in this.eases {
if e.t >= 1 do continue
e.t += dt / e.duration
eased := easing_apply(e.easing, e.t)
for c in 0 ..< e.count {
e.target[c] = math.lerp(e.start[c], e.end[c], eased)
}
if e.t >= 1 && e.on_done != nil {
e.on_done(&e, e.user_data)
}
}
}
easing_apply :: proc(easing: Easing, t: f32) -> f32 {
t := math.clamp(t, 0, 1)
switch easing {
case .LINEAR:
return t
case .CUBIC_IN:
return t * t * t
case .CUBIC_OUT:
t1 := 1 - t
return 1 - t1 * t1 * t1
case .BACK_OUT:
c1 :: f32(1.70158)
c3 :: c1 + 1
t1 := t - 1
return 1 + c3 * t1 * t1 * t1 + c1 * t1 * t1
case .ELASTIC_OUT:
if t == 0 do return 0
if t == 1 do return 1
c4 :: (2 * math.PI) / 3
return math.pow(f32(2), -10 * t) * math.sin((t * 10 - 0.75) * c4) + 1
}
return t
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment