DEADSOFTWARE

anim: add get time from state 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);
82 procedure g_Anim_GetTimeFromState (const s: TAnimState; out curtime, fulltime: LongInt);
84 implementation
86 uses Math, utils, xstreams;
88 constructor TAnimState.Create (aloop: Boolean; aspeed: Byte; len: Integer);
89 begin
90 ASSERT(len >= 0);
92 self := Default(TAnimState);
93 self.mLength := len;
94 self.mMinLength := 0;
95 self.mLoop := aloop;
96 self.mSpeed := aspeed;
97 self.mEnabled := true;
98 self.mCurrentFrame := 0;
99 self.mPlayed := false;
100 end;
102 procedure TAnimState.Invalidate;
103 begin
104 self := Default(TAnimState);
105 end;
107 procedure TAnimState.Update;
108 begin
109 ASSERT(self.IsValid());
110 if self.mEnabled then
111 begin
112 INC(self.mCounter);
113 if self.mCounter >= self.mSpeed then
114 begin
115 if self.mRevert then
116 begin
117 if (self.mCurrentFrame <> 0) or (mLength * mSpeed + mCounter >= mMinLength) then
118 begin
119 DEC(self.mCurrentFrame);
120 self.mPlayed := self.mCurrentFrame < 0;
121 if self.mPlayed then
122 begin
123 if self.mLoop then self.mCurrentFrame := self.mLength - 1 else INC(self.mCurrentFrame);
124 end;
125 self.mCounter := 0;
126 end;
127 end
128 else
129 begin
130 if (self.mCurrentFrame <> self.mLength - 1) or (mLength * mSpeed + mCounter >= mMinLength) then
131 begin
132 INC(self.mCurrentFrame);
133 self.mPlayed := self.mCurrentFrame > self.mLength - 1;
134 if self.mPlayed then
135 begin
136 if self.mLoop then self.mCurrentFrame := 0 else DEC(self.mCurrentFrame);
137 end;
138 self.mCounter := 0;
139 end;
140 end;
141 end;
142 end;
143 end;
145 procedure TAnimState.Reset;
146 begin
147 ASSERT(self.IsValid());
148 if self.mRevert then self.mCurrentFrame := self.mLength - 1 else self.mCurrentFrame := 0;
149 self.mCounter := 0;
150 self.mPlayed := false;
151 end;
153 procedure TAnimState.Disable;
154 begin
155 ASSERT(self.IsValid());
156 self.mEnabled := false;
157 end;
159 procedure TAnimState.Enable;
160 begin
161 ASSERT(self.IsValid());
162 self.mEnabled := true;
163 end;
165 procedure TAnimState.revert (r: Boolean);
166 begin
167 ASSERT(self.IsValid());
168 self.mRevert := r;
169 self.Reset;
170 end;
172 function TAnimState.TotalFrames (): Integer;
173 begin
174 ASSERT(self.IsValid());
175 result := self.mLength;
176 end;
178 function TAnimState.IsInvalid (): Boolean;
179 begin
180 result := self.mLength <= 0
181 end;
183 function TAnimState.IsValid (): Boolean;
184 begin
185 result := self.mLength > 0;
186 end;
188 procedure TAnimState.SaveState (st: TStream; mAlpha: Byte; mBlending: Boolean);
189 begin
190 if st <> nil then
191 begin
192 utils.writeSign(st, 'ANIM');
193 utils.writeInt(st, Byte(0)); // version
194 utils.writeInt(st, Byte(mCounter));
195 utils.writeInt(st, LongInt(mCurrentFrame));
196 utils.writeBool(st, mPlayed);
197 utils.writeInt(st, Byte(mAlpha));
198 utils.writeInt(st, Byte(mBlending));
199 utils.writeInt(st, Byte(mSpeed));
200 utils.writeBool(st, mLoop);
201 utils.writeBool(st, mEnabled);
202 utils.writeInt(st, Byte(mMinLength));
203 utils.writeBool(st, mRevert);
204 end;
205 end;
207 procedure TAnimState.LoadState (st: TStream; out mAlpha: Byte; out mBlending: Boolean);
208 begin
209 if st <> nil then
210 begin
211 if utils.checkSign(st, 'ANIM') = false then
212 raise XStreamError.Create('animation chunk expected');
213 if utils.readByte(st) <> 0 then
214 raise XStreamError.Create('invalid animation chunk version');
215 mCounter := utils.readByte(st);
216 mCurrentFrame := utils.readLongInt(st);
217 mPlayed := utils.readBool(st);
218 mAlpha := utils.readByte(st);
219 mBlending := utils.readBool(st);
220 mSpeed := utils.readByte(st);
221 mLoop := utils.readBool(st);
222 mEnabled := utils.readBool(st);
223 mMinLength := utils.readByte(st);
224 mRevert := utils.readBool(st);
225 end;
226 end;
228 function g_Anim_GetTotalFrames (const a: TAnimInfo): LongWord;
229 begin
230 ASSERT(a.frames > 0);
231 ASSERT(a.delay > 0);
232 if a.back then result := MAX(1, a.frames * 2 - 2) else result := a.frames;
233 end;
235 function g_Anim_GetTotalTime (const a: TAnimInfo): LongWord;
236 begin
237 ASSERT(a.frames > 0);
238 ASSERT(a.delay > 0);
239 result := g_Anim_GetTotalFrames(a) * a.delay;
240 end;
242 function g_Anim_GetCountByTime (const a: TAnimInfo; time: LongWord): LongInt;
243 var n, f, t: LongWord;
244 begin
245 ASSERT(a.frames > 0);
246 ASSERT(a.delay > 0);
247 n := g_Anim_GetTotalFrames(a);
248 t := g_Anim_GetTotalTime(a);
249 f := n * time div t;
250 if a.loop then result := f div n
251 else if f >= n then result := 1
252 else result := 0;
253 end;
255 procedure g_Anim_GetFrameByTime (const a: TAnimInfo; time: LongWord; out count, frame: LongInt);
256 var n, f, t: LongWord;
257 begin
258 ASSERT(a.frames > 0);
259 ASSERT(a.delay > 0);
260 (* 1. Get total number frames for one animation cycle *)
261 n := g_Anim_GetTotalFrames(a);
262 (* 2. Get time for one animation cycle *)
263 t := g_Anim_GetTotalTime(a);
264 (* 3. Get frame for specified time *)
265 f := n * time div t;
266 (* 4. Get how many times is played *)
267 if a.loop then count := f div n
268 else if f >= n then count := 1
269 else count := 0;
270 (* 5. Normalize loop animation *)
271 if a.loop then f := f mod n else f := MIN(f, n - 1);
272 (* 6. Normalize back animation *)
273 if a.back and (f >= a.frames) then f := n - f;
274 frame := f;
275 end;
277 procedure g_Anim_GetState (const anim: TAnimInfo; time: LongWord; out state: TAnimState);
278 var count, frame: LongInt; a: TAnimInfo;
279 begin
280 ASSERT(anim.frames > 0);
281 ASSERT(anim.delay > 0);
282 a := anim;
283 if a.back then
284 begin
285 a.frames := MAX(1, a.frames * 2 - 2);
286 a.back := false;
287 end;
288 g_Anim_GetFrameByTime(a, time, count, frame);
289 state := TAnimState.Create(a.loop, a.delay, a.frames);
290 state.mCounter := time MOD a.delay;
291 state.mCurrentFrame := frame;
292 state.mPlayed := count >= 1;
293 end;
295 procedure g_Anim_GetFrameFromState (const s: TAnimState; backanim: Boolean; out frame: LongInt);
296 var total: LongInt;
297 begin
298 ASSERT(s.length > 0);
299 frame := s.CurrentFrame mod s.length;
300 if backanim then
301 begin
302 total := (s.length + 1) div 2;
303 if frame >= total then
304 frame := s.length - frame - 1;
305 end;
306 end;
308 procedure g_Anim_GetInterplatedFrameFromState (const s: TAnimState; newlength: LongInt; out frame: LongInt);
309 var delay, curframe, curcount, fulltime, curtime, newtime: LongInt;
310 begin
311 ASSERT(s.length > 0);
312 ASSERT(newlength > 0);
313 (* 1. normalize state values *)
314 delay := MAX(1, s.speed);
315 curframe := MIN(MAX(s.CurrentFrame, 0), s.length - 1);
316 curcount := MIN(MAX(s.CurrentCounter, 0), delay - 1);
317 (* 2. calc current time (normalized) *)
318 fulltime := s.length * delay;
319 curtime := MIN(curframe * delay + curcount, fulltime);
320 (* 3. calc interpolated frame *)
321 newtime := curtime * newlength div s.length;
322 frame := newtime div delay;
323 ASSERT(frame >= 0);
324 ASSERT(frame < newlength);
325 end;
327 procedure g_Anim_GetTimeFromState (const s: TAnimState; out curtime, fulltime: LongInt);
328 var delay, curframe, curcount: LongInt;
329 begin
330 ASSERT(s.length > 0);
331 (* 1. normalize state values *)
332 delay := MAX(1, s.speed);
333 curframe := MIN(MAX(s.CurrentFrame, 0), s.length - 1);
334 curcount := MIN(MAX(s.CurrentCounter, 0), delay - 1);
335 (* 2. calc current time (normalized) *)
336 fulltime := s.length * delay;
337 curtime := MIN(curframe * delay + curcount, fulltime);
338 end;
340 end.