Mercurial > games > semicongine
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 1217:f819a874058f | 1218:56781cc0fc7c |
|---|---|
| 1 {.experimental: "notnil".} | |
| 2 | |
| 3 import std/sugar | |
| 4 import std/tables | |
| 5 import std/math | |
| 6 import std/sequtils | |
| 7 import std/algorithm | |
| 8 | |
| 9 import ./core/matrix | |
| 10 | |
| 11 type | |
| 12 Ease* = enum | |
| 13 None | |
| 14 Linear | |
| 15 Pow2 | |
| 16 Pow3 | |
| 17 Pow4 | |
| 18 Pow5 | |
| 19 Expo | |
| 20 Sine | |
| 21 Circ | |
| 22 AnimationTime* = 0'f32 .. 1'f32 | |
| 23 Direction* = enum | |
| 24 Forward | |
| 25 Backward | |
| 26 Alternate | |
| 27 Keyframe[T] = object | |
| 28 timestamp: AnimationTime | |
| 29 value: T | |
| 30 easeIn: Ease | |
| 31 easeOut: Ease | |
| 32 Animation*[T] = object | |
| 33 animationFunction: (state: AnimationState[T], dt: float32) -> T | |
| 34 duration: float32 | |
| 35 direction: Direction | |
| 36 iterations: int | |
| 37 AnimationState*[T] = object | |
| 38 animation*: Animation[T] | |
| 39 currentTime*: float32 | |
| 40 playing*: bool | |
| 41 currentDirection: int | |
| 42 currentIteration: int | |
| 43 currentValue*: T | |
| 44 | |
| 45 func easeConst(x: float32): float32 = 0 | |
| 46 func easeLinear(x: float32): float32 = x | |
| 47 func easePow2(x: float32): float32 = x * x | |
| 48 func easePow3(x: float32): float32 = x * x * x | |
| 49 func easePow4(x: float32): float32 = x * x * x * x | |
| 50 func easePow5(x: float32): float32 = x * x * x * x * x | |
| 51 func easeExpo(x: float32): float32 = (if x == 0: 0'f32 else: pow(2'f32, 10'f32 * x - 10'f32)) | |
| 52 func easeSine(x: float32): float32 = 1'f32 - cos((x * PI) / 2'f32) | |
| 53 func easeCirc(x: float32): float32 = 1'f32 - sqrt(1'f32 - pow(x, 2'f32)) | |
| 54 | |
| 55 const EASEFUNC_MAP = { | |
| 56 None: easeConst, | |
| 57 Linear: easeLinear, | |
| 58 Pow2: easePow2, | |
| 59 Pow3: easePow3, | |
| 60 Pow4: easePow4, | |
| 61 Pow5: easePow5, | |
| 62 Expo: easeExpo, | |
| 63 Sine: easeSine, | |
| 64 Circ: easeCirc, | |
| 65 }.toTable() | |
| 66 | |
| 67 func makeEaseOut(f: proc(x: float32): float32 {.noSideEffect.}): auto = | |
| 68 func wrapper(x: float32): float32 = | |
| 69 1 - f(1 - x) | |
| 70 return wrapper | |
| 71 | |
| 72 func combine(f1: proc(x: float32): float32 {.noSideEffect.}, f2: proc(x: float32): float32 {.noSideEffect.}): auto = | |
| 73 func wrapper(x: float32): float32 = | |
| 74 if x < 0.5: f1(x * 2) * 0.5 | |
| 75 else: f2((x - 0.5) * 2) * 0.5 + 0.5 | |
| 76 return wrapper | |
| 77 | |
| 78 func interpol(keyframe: Keyframe, t: float32): float32 = | |
| 79 if keyframe.easeOut == None: | |
| 80 return EASEFUNC_MAP[keyframe.easeIn](t) | |
| 81 elif keyframe.easeIn == None: | |
| 82 return EASEFUNC_MAP[keyframe.easeOut](t) | |
| 83 else: | |
| 84 return combine(EASEFUNC_MAP[keyframe.easeIn], makeEaseOut(EASEFUNC_MAP[keyframe.easeOut]))(t) | |
| 85 | |
| 86 func InitKeyframe*[T](timestamp: AnimationTime, value: T, easeIn = Linear, easeOut = None): Keyframe[T] = | |
| 87 Keyframe[T](timestamp: timestamp, value: value, easeIn: easeIn, easeOut: easeOut) | |
| 88 | |
| 89 func NewAnimation*[T](keyframes: openArray[Keyframe[T]], duration: float32, direction = Forward, iterations = 1): Animation[T] = | |
| 90 assert keyframes.len >= 2, "An animation needs at least 2 keyframes" | |
| 91 assert keyframes[0].timestamp == 0, "An animation's first keyframe needs to have timestamp=0" | |
| 92 assert keyframes[^1].timestamp == 1, "An animation's last keyframe needs to have timestamp=1" | |
| 93 var last = keyframes[0].timestamp | |
| 94 for kf in keyframes[1 .. ^1]: | |
| 95 assert kf.timestamp > last, "Succeding keyframes must have increasing timestamps" | |
| 96 last = kf.timestamp | |
| 97 | |
| 98 let theKeyframes = keyframes.toSeq | |
| 99 | |
| 100 proc animationFunc(state: AnimationState[T], dt: float32): T = | |
| 101 var i = 0 | |
| 102 while i < theKeyframes.len - 1: | |
| 103 if theKeyframes[i].timestamp > state.t: | |
| 104 break | |
| 105 inc i | |
| 106 | |
| 107 let | |
| 108 keyFrameDist = theKeyframes[i].timestamp - theKeyframes[i - 1].timestamp | |
| 109 timestampDist = state.t - theKeyframes[i - 1].timestamp | |
| 110 x = timestampDist / keyFrameDist | |
| 111 | |
| 112 let value = theKeyframes[i - 1].interpol(x) | |
| 113 return theKeyframes[i].value * value + theKeyframes[i - 1].value * (1 - value) | |
| 114 | |
| 115 Animation[T]( | |
| 116 animationFunction: animationFunc, | |
| 117 duration: duration, | |
| 118 direction: direction, | |
| 119 iterations: iterations | |
| 120 ) | |
| 121 | |
| 122 func NewAnimation*[T](fun: (state: AnimationState[T], dt: float32) -> T, duration: float32, direction = Forward, iterations = 1): Animation[T] = | |
| 123 assert fun != nil, "Animation function cannot be nil" | |
| 124 Animation[T]( | |
| 125 animationFunction: fun, | |
| 126 duration: duration, | |
| 127 direction: direction, | |
| 128 iterations: iterations | |
| 129 ) | |
| 130 | |
| 131 proc ResetState*[T](state: var AnimationState[T], initial: T) = | |
| 132 state.currentValue = initial | |
| 133 state.currentTime = 0 | |
| 134 state.currentDirection = if state.animation.direction == Backward: -1 else: 1 | |
| 135 state.currentIteration = state.animation.iterations | |
| 136 | |
| 137 proc t*(state: AnimationState): AnimationTime = | |
| 138 max(low(AnimationTime), min(state.currentTime / state.animation.duration, high(AnimationTime))) | |
| 139 | |
| 140 proc NewAnimationState*[T](animation: Animation[T], initial = default(T)): AnimationState[T] = | |
| 141 result = AnimationState[T](animation: animation, playing: false) | |
| 142 result.ResetState(initial) | |
| 143 | |
| 144 proc NewAnimationState*[T](value: T = default(T)): AnimationState[T] = | |
| 145 NewAnimationState[T](NewAnimation[T]((state: AnimationState[T], dt: float32) => value, 0), initial = value) | |
| 146 | |
| 147 func Start*(state: var AnimationState) = | |
| 148 state.playing = true | |
| 149 | |
| 150 func Stop*(state: var AnimationState) = | |
| 151 state.playing = false | |
| 152 | |
| 153 proc Advance*[T](state: var AnimationState[T], dt: float32): T = | |
| 154 # TODO: check this function, not 100% correct I think | |
| 155 if state.playing: | |
| 156 state.currentTime += float32(state.currentDirection) * dt | |
| 157 if not (0 <= state.currentTime and state.currentTime < state.animation.duration): | |
| 158 dec state.currentIteration | |
| 159 # last iteration reached | |
| 160 if state.currentIteration <= 0 and state.animation.iterations != 0: | |
| 161 state.Stop() | |
| 162 # more iterations | |
| 163 else: | |
| 164 case state.animation.direction: | |
| 165 of Forward: | |
| 166 state.currentTime = state.currentTime - state.animation.duration | |
| 167 of Backward: | |
| 168 state.currentTime = state.currentTime + state.animation.duration | |
| 169 of Alternate: | |
| 170 state.currentDirection = -state.currentDirection | |
| 171 state.currentTime += float32(state.currentDirection) * dt * 2'f32 | |
| 172 | |
| 173 assert state.animation.animationFunction != nil, "Animation func cannot be nil" | |
| 174 state.currentValue = state.animation.animationFunction(state, dt) | |
| 175 return state.currentValue |
