DEADSOFTWARE

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