DEADSOFTWARE

9c6dd4cb2aa53ab0a9086994dbf64ac00093d420
[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])
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);
82 implementation
84 uses Math, utils, xstreams;
86 constructor TAnimState.Create (aloop: Boolean; aspeed: Byte; len: Integer);
87 begin
88 ASSERT(len >= 0);
90 self := Default(TAnimState);
91 self.mLength := len;
92 self.mMinLength := 0;
93 self.mLoop := aloop;
94 self.mSpeed := aspeed;
95 self.mEnabled := true;
96 self.mCurrentFrame := 0;
97 self.mPlayed := false;
98 end;
100 procedure TAnimState.Invalidate;
101 begin
102 self := Default(TAnimState);
103 end;
105 procedure TAnimState.Update;
106 begin
107 ASSERT(self.IsValid());
108 if self.mEnabled then
109 begin
110 INC(self.mCounter);
111 if self.mCounter >= self.mSpeed then
112 begin
113 if self.mRevert then
114 begin
115 if (self.mCurrentFrame <> 0) or (mLength * mSpeed + mCounter >= mMinLength) then
116 begin
117 DEC(self.mCurrentFrame);
118 self.mPlayed := self.mCurrentFrame < 0;
119 if self.mPlayed then
120 begin
121 if self.mLoop then self.mCurrentFrame := self.mLength - 1 else INC(self.mCurrentFrame);
122 end;
123 self.mCounter := 0;
124 end;
125 end
126 else
127 begin
128 if (self.mCurrentFrame <> self.mLength - 1) or (mLength * mSpeed + mCounter >= mMinLength) then
129 begin
130 INC(self.mCurrentFrame);
131 self.mPlayed := self.mCurrentFrame > self.mLength - 1;
132 if self.mPlayed then
133 begin
134 if self.mLoop then self.mCurrentFrame := 0 else DEC(self.mCurrentFrame);
135 end;
136 self.mCounter := 0;
137 end;
138 end;
139 end;
140 end;
141 end;
143 procedure TAnimState.Reset;
144 begin
145 ASSERT(self.IsValid());
146 if self.mRevert then self.mCurrentFrame := self.mLength - 1 else self.mCurrentFrame := 0;
147 self.mCounter := 0;
148 self.mPlayed := false;
149 end;
151 procedure TAnimState.Disable;
152 begin
153 ASSERT(self.IsValid());
154 self.mEnabled := false;
155 end;
157 procedure TAnimState.Enable;
158 begin
159 ASSERT(self.IsValid());
160 self.mEnabled := true;
161 end;
163 procedure TAnimState.revert (r: Boolean);
164 begin
165 ASSERT(self.IsValid());
166 self.mRevert := r;
167 self.Reset;
168 end;
170 function TAnimState.TotalFrames (): Integer;
171 begin
172 ASSERT(self.IsValid());
173 result := self.mLength;
174 end;
176 function TAnimState.IsInvalid (): Boolean;
177 begin
178 result := self.mLength <= 0
179 end;
181 function TAnimState.IsValid (): Boolean;
182 begin
183 result := self.mLength > 0;
184 end;
186 procedure TAnimState.SaveState (st: TStream; mAlpha: Byte; mBlending: Boolean);
187 begin
188 if st <> nil then
189 begin
190 utils.writeSign(st, 'ANIM');
191 utils.writeInt(st, Byte(0)); // version
192 utils.writeInt(st, Byte(mCounter));
193 utils.writeInt(st, LongInt(mCurrentFrame));
194 utils.writeBool(st, mPlayed);
195 utils.writeInt(st, Byte(mAlpha));
196 utils.writeInt(st, Byte(mBlending));
197 utils.writeInt(st, Byte(mSpeed));
198 utils.writeBool(st, mLoop);
199 utils.writeBool(st, mEnabled);
200 utils.writeInt(st, Byte(mMinLength));
201 utils.writeBool(st, mRevert);
202 end;
203 end;
205 procedure TAnimState.LoadState (st: TStream; out mAlpha: Byte; out mBlending: Boolean);
206 begin
207 if st <> nil then
208 begin
209 if utils.checkSign(st, 'ANIM') = false then
210 raise XStreamError.Create('animation chunk expected');
211 if utils.readByte(st) <> 0 then
212 raise XStreamError.Create('invalid animation chunk version');
213 mCounter := utils.readByte(st);
214 mCurrentFrame := utils.readLongInt(st);
215 mPlayed := utils.readBool(st);
216 mAlpha := utils.readByte(st);
217 mBlending := utils.readBool(st);
218 mSpeed := utils.readByte(st);
219 mLoop := utils.readBool(st);
220 mEnabled := utils.readBool(st);
221 mMinLength := utils.readByte(st);
222 mRevert := utils.readBool(st);
223 end;
224 end;
226 function g_Anim_GetTotalFrames (const a: TAnimInfo): LongWord;
227 begin
228 ASSERT(a.frames > 0);
229 ASSERT(a.delay > 0);
230 if a.back then result := MAX(1, a.frames * 2 - 2) else result := a.frames;
231 end;
233 function g_Anim_GetTotalTime (const a: TAnimInfo): LongWord;
234 begin
235 ASSERT(a.frames > 0);
236 ASSERT(a.delay > 0);
237 result := g_Anim_GetTotalFrames(a) * a.delay;
238 end;
240 function g_Anim_GetCountByTime (const a: TAnimInfo; time: LongWord): LongInt;
241 var n, f, t: LongWord;
242 begin
243 ASSERT(a.frames > 0);
244 ASSERT(a.delay > 0);
245 n := g_Anim_GetTotalFrames(a);
246 t := g_Anim_GetTotalTime(a);
247 f := n * time div t;
248 if a.loop then result := f div n
249 else if f >= n then result := 1
250 else result := 0;
251 end;
253 procedure g_Anim_GetFrameByTime (const a: TAnimInfo; time: LongWord; out count, frame: LongInt);
254 var n, f, t: LongWord;
255 begin
256 ASSERT(a.frames > 0);
257 ASSERT(a.delay > 0);
258 (* 1. Get total number frames for one animation cycle *)
259 n := g_Anim_GetTotalFrames(a);
260 (* 2. Get time for one animation cycle *)
261 t := g_Anim_GetTotalTime(a);
262 (* 3. Get frame for specified time *)
263 f := n * time div t;
264 (* 4. Get how many times is played *)
265 if a.loop then count := f div n
266 else if f >= n then count := 1
267 else count := 0;
268 (* 5. Normalize loop animation *)
269 if a.loop then f := f mod n else f := MIN(f, n - 1);
270 (* 6. Normalize back animation *)
271 if a.back and (f >= a.frames) then f := n - f;
272 frame := f;
273 end;
275 procedure g_Anim_GetState (const anim: TAnimInfo; time: LongWord; out state: TAnimState);
276 var count, frame: LongInt; a: TAnimInfo;
277 begin
278 ASSERT(anim.frames > 0);
279 ASSERT(anim.delay > 0);
280 a := anim;
281 if a.back then
282 begin
283 a.frames := MAX(1, a.frames * 2 - 2);
284 a.back := false;
285 end;
286 g_Anim_GetFrameByTime(a, time, count, frame);
287 state := TAnimState.Create(a.loop, a.delay, a.frames);
288 state.mCounter := time MOD a.delay;
289 state.mCurrentFrame := frame;
290 state.mPlayed := count >= 1;
291 end;
293 procedure g_Anim_GetFrameFromState (const s: TAnimState; backanim: Boolean; out frame: LongInt);
294 var total: LongInt;
295 begin
296 ASSERT(s.length > 0);
297 frame := s.CurrentFrame mod s.length;
298 if backanim then
299 begin
300 total := (s.length + 1) div 2;
301 if frame >= total then
302 frame := s.length - frame - 1;
303 end;
304 end;
306 end.