diff semiconginev2/old/animation.nim @ 1218:56781cc0fc7c compiletime-tests

did: renamge main package
author sam <sam@basx.dev>
date Wed, 17 Jul 2024 21:01:37 +0700
parents semicongine/old/animation.nim@a3eb305bcac2
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/old/animation.nim	Wed Jul 17 21:01:37 2024 +0700
@@ -0,0 +1,175 @@
+{.experimental: "notnil".}
+
+import std/sugar
+import std/tables
+import std/math
+import std/sequtils
+import std/algorithm
+
+import ./core/matrix
+
+type
+  Ease* = enum
+    None
+    Linear
+    Pow2
+    Pow3
+    Pow4
+    Pow5
+    Expo
+    Sine
+    Circ
+  AnimationTime* = 0'f32 .. 1'f32
+  Direction* = enum
+    Forward
+    Backward
+    Alternate
+  Keyframe[T] = object
+    timestamp: AnimationTime
+    value: T
+    easeIn: Ease
+    easeOut: Ease
+  Animation*[T] = object
+    animationFunction: (state: AnimationState[T], dt: float32) -> T
+    duration: float32
+    direction: Direction
+    iterations: int
+  AnimationState*[T] = object
+    animation*: Animation[T]
+    currentTime*: float32
+    playing*: bool
+    currentDirection: int
+    currentIteration: int
+    currentValue*: T
+
+func easeConst(x: float32): float32 = 0
+func easeLinear(x: float32): float32 = x
+func easePow2(x: float32): float32 = x * x
+func easePow3(x: float32): float32 = x * x * x
+func easePow4(x: float32): float32 = x * x * x * x
+func easePow5(x: float32): float32 = x * x * x * x * x
+func easeExpo(x: float32): float32 = (if x == 0: 0'f32 else: pow(2'f32, 10'f32 * x - 10'f32))
+func easeSine(x: float32): float32 = 1'f32 - cos((x * PI) / 2'f32)
+func easeCirc(x: float32): float32 = 1'f32 - sqrt(1'f32 - pow(x, 2'f32))
+
+const EASEFUNC_MAP = {
+    None: easeConst,
+    Linear: easeLinear,
+    Pow2: easePow2,
+    Pow3: easePow3,
+    Pow4: easePow4,
+    Pow5: easePow5,
+    Expo: easeExpo,
+    Sine: easeSine,
+    Circ: easeCirc,
+}.toTable()
+
+func makeEaseOut(f: proc(x: float32): float32 {.noSideEffect.}): auto =
+  func wrapper(x: float32): float32 =
+    1 - f(1 - x)
+  return wrapper
+
+func combine(f1: proc(x: float32): float32 {.noSideEffect.}, f2: proc(x: float32): float32 {.noSideEffect.}): auto =
+  func wrapper(x: float32): float32 =
+    if x < 0.5: f1(x * 2) * 0.5
+    else: f2((x - 0.5) * 2) * 0.5 + 0.5
+  return wrapper
+
+func interpol(keyframe: Keyframe, t: float32): float32 =
+  if keyframe.easeOut == None:
+    return EASEFUNC_MAP[keyframe.easeIn](t)
+  elif keyframe.easeIn == None:
+    return EASEFUNC_MAP[keyframe.easeOut](t)
+  else:
+    return combine(EASEFUNC_MAP[keyframe.easeIn], makeEaseOut(EASEFUNC_MAP[keyframe.easeOut]))(t)
+
+func InitKeyframe*[T](timestamp: AnimationTime, value: T, easeIn = Linear, easeOut = None): Keyframe[T] =
+  Keyframe[T](timestamp: timestamp, value: value, easeIn: easeIn, easeOut: easeOut)
+
+func NewAnimation*[T](keyframes: openArray[Keyframe[T]], duration: float32, direction = Forward, iterations = 1): Animation[T] =
+  assert keyframes.len >= 2, "An animation needs at least 2 keyframes"
+  assert keyframes[0].timestamp == 0, "An animation's first keyframe needs to have timestamp=0"
+  assert keyframes[^1].timestamp == 1, "An animation's last keyframe needs to have timestamp=1"
+  var last = keyframes[0].timestamp
+  for kf in keyframes[1 .. ^1]:
+    assert kf.timestamp > last, "Succeding keyframes must have increasing timestamps"
+    last = kf.timestamp
+
+  let theKeyframes = keyframes.toSeq
+
+  proc animationFunc(state: AnimationState[T], dt: float32): T =
+    var i = 0
+    while i < theKeyframes.len - 1:
+      if theKeyframes[i].timestamp > state.t:
+        break
+      inc i
+
+    let
+      keyFrameDist = theKeyframes[i].timestamp - theKeyframes[i - 1].timestamp
+      timestampDist = state.t - theKeyframes[i - 1].timestamp
+      x = timestampDist / keyFrameDist
+
+    let value = theKeyframes[i - 1].interpol(x)
+    return theKeyframes[i].value * value + theKeyframes[i - 1].value * (1 - value)
+
+  Animation[T](
+    animationFunction: animationFunc,
+    duration: duration,
+    direction: direction,
+    iterations: iterations
+  )
+
+func NewAnimation*[T](fun: (state: AnimationState[T], dt: float32) -> T, duration: float32, direction = Forward, iterations = 1): Animation[T] =
+  assert fun != nil, "Animation function cannot be nil"
+  Animation[T](
+    animationFunction: fun,
+    duration: duration,
+    direction: direction,
+    iterations: iterations
+  )
+
+proc ResetState*[T](state: var AnimationState[T], initial: T) =
+  state.currentValue = initial
+  state.currentTime = 0
+  state.currentDirection = if state.animation.direction == Backward: -1 else: 1
+  state.currentIteration = state.animation.iterations
+
+proc t*(state: AnimationState): AnimationTime =
+  max(low(AnimationTime), min(state.currentTime / state.animation.duration, high(AnimationTime)))
+
+proc NewAnimationState*[T](animation: Animation[T], initial = default(T)): AnimationState[T] =
+  result = AnimationState[T](animation: animation, playing: false)
+  result.ResetState(initial)
+
+proc NewAnimationState*[T](value: T = default(T)): AnimationState[T] =
+  NewAnimationState[T](NewAnimation[T]((state: AnimationState[T], dt: float32) => value, 0), initial = value)
+
+func Start*(state: var AnimationState) =
+  state.playing = true
+
+func Stop*(state: var AnimationState) =
+  state.playing = false
+
+proc Advance*[T](state: var AnimationState[T], dt: float32): T =
+  # TODO: check this function, not 100% correct I think
+  if state.playing:
+    state.currentTime += float32(state.currentDirection) * dt
+    if not (0 <= state.currentTime and state.currentTime < state.animation.duration):
+      dec state.currentIteration
+      # last iteration reached
+      if state.currentIteration <= 0 and state.animation.iterations != 0:
+        state.Stop()
+      # more iterations
+      else:
+        case state.animation.direction:
+          of Forward:
+            state.currentTime = state.currentTime - state.animation.duration
+          of Backward:
+            state.currentTime = state.currentTime + state.animation.duration
+          of Alternate:
+            state.currentDirection = -state.currentDirection
+            state.currentTime += float32(state.currentDirection) * dt * 2'f32
+
+    assert state.animation.animationFunction != nil, "Animation func cannot be nil"
+    state.currentValue = state.animation.animationFunction(state, dt)
+  return state.currentValue