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