DEADSOFTWARE

anim: add state interpolation function (unused now)
[d2df-sdl.git] / src / game / g_animations.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_animations;
18 interface
20 uses Classes;
22 type
23 TAnimState = record
24 private
25 mCounter: Byte; // delay counter (normally [0..mSpeed - 1])
26 mSpeed: Byte; // delay between frames
27 mCurrentFrame: Integer; // current frame (normally [0..mLength - 1])
28 mLoop: Boolean; // looped animation
29 mEnabled: Boolean; // allow update state
30 mPlayed: Boolean; // anmation played at least once
31 mMinLength: Byte; // delay at animation end
32 mRevert: Boolean; // reverse play
33 mLength: Integer; // total frames (normally mLength > 0)
35 public
36 constructor Create (aloop: Boolean; aspeed: Byte; len: Integer);
37 procedure Invalidate;
39 procedure Reset;
40 procedure Update;
41 procedure Enable;
42 procedure Disable;
43 procedure Revert (r: Boolean);
45 procedure SaveState (st: TStream; mAlpha: Byte; mBlending: Boolean);
46 procedure LoadState (st: TStream; out mAlpha: Byte; out mBlending: Boolean);
48 function TotalFrames (): Integer; inline;
49 function IsInvalid (): Boolean; inline;
50 function IsValid (): Boolean; inline;
52 public
53 property played: Boolean read mPlayed;
54 property enabled: Boolean read mEnabled;
55 property isReverse: Boolean read mRevert;
56 property loop: Boolean read mLoop write mLoop;
57 property speed: Byte read mSpeed write mSpeed;
58 property minLength: Byte read mMinLength write mMinLength;
59 property currentFrame: Integer read mCurrentFrame write mCurrentFrame;
60 property currentCounter: Byte read mCounter write mCounter;
61 property counter: Byte read mCounter;
62 property length: Integer read mLength;
63 end;
65 type
66 TAnimInfo = record
67 loop: Boolean; (* loop animation normalization *)
68 delay: Byte; (* delay between frames [1..255] *)
69 frames: Word; (* number of frames in animation stream [1..65535] *)
70 back: Boolean; (* back animation normalization *)
71 end;
74 function g_Anim_GetTotalFrames (const a: TAnimInfo): LongWord;
75 function g_Anim_GetTotalTime (const a: TAnimInfo): LongWord;
76 function g_Anim_GetCountByTime (const a: TAnimInfo; time: LongWord): LongInt;
77 procedure g_Anim_GetFrameByTime (const a: TAnimInfo; time: LongWord; out count, frame: LongInt);
78 procedure g_Anim_GetState (const anim: TAnimInfo; time: LongWord; out state: TAnimState);
80 procedure g_Anim_GetFrameFromState (const s: TAnimState; backanim: Boolean; out frame: LongInt);
81 procedure g_Anim_GetInterplatedFrameFromState (const s: TAnimState; newlength: LongInt; out frame: LongInt);
83 implementation
85 uses Math, utils, xstreams;
87 constructor TAnimState.Create (aloop: Boolean; aspeed: Byte; len: Integer);
88 begin
89 ASSERT(len >= 0);
91 self := Default(TAnimState);
92 self.mLength := len;
93 self.mMinLength := 0;
94 self.mLoop := aloop;
95 self.mSpeed := aspeed;
96 self.mEnabled := true;
97 self.mCurrentFrame := 0;
98 self.mPlayed := false;
99 end;
101 procedure TAnimState.Invalidate;
102 begin
103 self := Default(TAnimState);
104 end;
106 procedure TAnimState.Update;
107 begin
108 ASSERT(self.IsValid());
109 if self.mEnabled then
110 begin
111 INC(self.mCounter);
112 if self.mCounter >= self.mSpeed then
113 begin
114 if self.mRevert then
115 begin
116 if (self.mCurrentFrame <> 0) or (mLength * mSpeed + mCounter >= mMinLength) then
117 begin
118 DEC(self.mCurrentFrame);
119 self.mPlayed := self.mCurrentFrame < 0;
120 if self.mPlayed then
121 begin
122 if self.mLoop then self.mCurrentFrame := self.mLength - 1 else INC(self.mCurrentFrame);
123 end;
124 self.mCounter := 0;
125 end;
126 end
127 else
128 begin
129 if (self.mCurrentFrame <> self.mLength - 1) or (mLength * mSpeed + mCounter >= mMinLength) then
130 begin
131 INC(self.mCurrentFrame);
132 self.mPlayed := self.mCurrentFrame > self.mLength - 1;
133 if self.mPlayed then
134 begin
135 if self.mLoop then self.mCurrentFrame := 0 else DEC(self.mCurrentFrame);
136 end;
137 self.mCounter := 0;
138 end;
139 end;
140 end;
141 end;
142 end;
144 procedure TAnimState.Reset;
145 begin
146 ASSERT(self.IsValid());
147 if self.mRevert then self.mCurrentFrame := self.mLength - 1 else self.mCurrentFrame := 0;
148 self.mCounter := 0;
149 self.mPlayed := false;
150 end;
152 procedure TAnimState.Disable;
153 begin
154 ASSERT(self.IsValid());
155 self.mEnabled := false;
156 end;
158 procedure TAnimState.Enable;
159 begin
160 ASSERT(self.IsValid());
161 self.mEnabled := true;
162 end;
164 procedure TAnimState.revert (r: Boolean);
165 begin
166 ASSERT(self.IsValid());
167 self.mRevert := r;
168 self.Reset;
169 end;
171 function TAnimState.TotalFrames (): Integer;
172 begin
173 ASSERT(self.IsValid());
174 result := self.mLength;
175 end;
177 function TAnimState.IsInvalid (): Boolean;
178 begin
179 result := self.mLength <= 0
180 end;
182 function TAnimState.IsValid (): Boolean;
183 begin
184 result := self.mLength > 0;
185 end;
187 procedure TAnimState.SaveState (st: TStream; mAlpha: Byte; mBlending: Boolean);
188 begin
189 if st <> nil then
190 begin
191 utils.writeSign(st, 'ANIM');
192 utils.writeInt(st, Byte(0)); // version
193 utils.writeInt(st, Byte(mCounter));
194 utils.writeInt(st, LongInt(mCurrentFrame));
195 utils.writeBool(st, mPlayed);
196 utils.writeInt(st, Byte(mAlpha));
197 utils.writeInt(st, Byte(mBlending));
198 utils.writeInt(st, Byte(mSpeed));
199 utils.writeBool(st, mLoop);
200 utils.writeBool(st, mEnabled);
201 utils.writeInt(st, Byte(mMinLength));
202 utils.writeBool(st, mRevert);
203 end;
204 end;
206 procedure TAnimState.LoadState (st: TStream; out mAlpha: Byte; out mBlending: Boolean);
207 begin
208 if st <> nil then
209 begin
210 if utils.checkSign(st, 'ANIM') = false then
211 raise XStreamError.Create('animation chunk expected');
212 if utils.readByte(st) <> 0 then
213 raise XStreamError.Create('invalid animation chunk version');
214 mCounter := utils.readByte(st);
215 mCurrentFrame := utils.readLongInt(st);
216 mPlayed := utils.readBool(st);
217 mAlpha := utils.readByte(st);
218 mBlending := utils.readBool(st);
219 mSpeed := utils.readByte(st);
220 mLoop := utils.readBool(st);
221 mEnabled := utils.readBool(st);
222 mMinLength := utils.readByte(st);
223 mRevert := utils.readBool(st);
224 end;
225 end;
227 function g_Anim_GetTotalFrames (const a: TAnimInfo): LongWord;
228 begin
229 ASSERT(a.frames > 0);
230 ASSERT(a.delay > 0);
231 if a.back then result := MAX(1, a.frames * 2 - 2) else result := a.frames;
232 end;
234 function g_Anim_GetTotalTime (const a: TAnimInfo): LongWord;
235 begin
236 ASSERT(a.frames > 0);
237 ASSERT(a.delay > 0);
238 result := g_Anim_GetTotalFrames(a) * a.delay;
239 end;
241 function g_Anim_GetCountByTime (const a: TAnimInfo; time: LongWord): LongInt;
242 var n, f, t: LongWord;
243 begin
244 ASSERT(a.frames > 0);
245 ASSERT(a.delay > 0);
246 n := g_Anim_GetTotalFrames(a);
247 t := g_Anim_GetTotalTime(a);
248 f := n * time div t;
249 if a.loop then result := f div n
250 else if f >= n then result := 1
251 else result := 0;
252 end;
254 procedure g_Anim_GetFrameByTime (const a: TAnimInfo; time: LongWord; out count, frame: LongInt);
255 var n, f, t: LongWord;
256 begin
257 ASSERT(a.frames > 0);
258 ASSERT(a.delay > 0);
259 (* 1. Get total number frames for one animation cycle *)
260 n := g_Anim_GetTotalFrames(a);
261 (* 2. Get time for one animation cycle *)
262 t := g_Anim_GetTotalTime(a);
263 (* 3. Get frame for specified time *)
264 f := n * time div t;
265 (* 4. Get how many times is played *)
266 if a.loop then count := f div n
267 else if f >= n then count := 1
268 else count := 0;
269 (* 5. Normalize loop animation *)
270 if a.loop then f := f mod n else f := MIN(f, n - 1);
271 (* 6. Normalize back animation *)
272 if a.back and (f >= a.frames) then f := n - f;
273 frame := f;
274 end;
276 procedure g_Anim_GetState (const anim: TAnimInfo; time: LongWord; out state: TAnimState);
277 var count, frame: LongInt; a: TAnimInfo;
278 begin
279 ASSERT(anim.frames > 0);
280 ASSERT(anim.delay > 0);
281 a := anim;
282 if a.back then
283 begin
284 a.frames := MAX(1, a.frames * 2 - 2);
285 a.back := false;
286 end;
287 g_Anim_GetFrameByTime(a, time, count, frame);
288 state := TAnimState.Create(a.loop, a.delay, a.frames);
289 state.mCounter := time MOD a.delay;
290 state.mCurrentFrame := frame;
291 state.mPlayed := count >= 1;
292 end;
294 procedure g_Anim_GetFrameFromState (const s: TAnimState; backanim: Boolean; out frame: LongInt);
295 var total: LongInt;
296 begin
297 ASSERT(s.length > 0);
298 frame := s.CurrentFrame mod s.length;
299 if backanim then
300 begin
301 total := (s.length + 1) div 2;
302 if frame >= total then
303 frame := s.length - frame - 1;
304 end;
305 end;
307 procedure g_Anim_GetInterplatedFrameFromState (const s: TAnimState; newlength: LongInt; out frame: LongInt);
308 var delay, curframe, curcount, fulltime, curtime, newtime: LongInt;
309 begin
310 ASSERT(s.length > 0);
311 ASSERT(newlength > 0);
312 (* 1. normalize state values *)
313 delay := MAX(1, s.speed);
314 curframe := MIN(MAX(s.CurrentFrame, 0), s.length - 1);
315 curcount := MIN(MAX(s.CurrentCounter, 0), delay - 1);
316 (* 2. calc current time (normalized) *)
317 fulltime := s.length * delay;
318 curtime := MIN(curframe * delay + curcount, fulltime);
319 (* 3. calc interpolated frame *)
320 newtime := curtime * newlength div s.length;
321 frame := newtime div delay;
322 ASSERT(frame >= 0);
323 ASSERT(frame < newlength);
324 end;
326 end.