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 |