Mercurial > touhou
comparison src/th06/anm0_vm.rs @ 639:a8e0219162b6
Implement AnmRunner.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Thu, 04 Jul 2019 15:21:46 +0200 |
parents | |
children | 37d151fe000b |
comparison
equal
deleted
inserted
replaced
638:a806f28e94fc | 639:a8e0219162b6 |
---|---|
1 //! Animation runner. | |
2 | |
3 use crate::th06::anm0::{ | |
4 Script, | |
5 Anm0, | |
6 Call, | |
7 Instruction, | |
8 }; | |
9 use std::cell::RefCell; | |
10 use std::rc::Rc; | |
11 | |
12 #[derive(Debug, Clone)] | |
13 struct Interpolator; | |
14 | |
15 /// Base visual element. | |
16 #[derive(Debug, Clone, Default)] | |
17 pub struct Sprite { | |
18 blendfunc: u32, | |
19 frame: u16, | |
20 | |
21 width_override: f32, | |
22 height_override: f32, | |
23 angle: f32, | |
24 | |
25 removed: bool, | |
26 changed: bool, | |
27 visible: bool, | |
28 force_rotation: bool, | |
29 automatic_orientation: bool, | |
30 allow_dest_offset: bool, | |
31 mirrored: bool, | |
32 corner_relative_placement: bool, | |
33 | |
34 scale_interpolator: Option<Interpolator>, | |
35 fade_interpolator: Option<Interpolator>, | |
36 offset_interpolator: Option<Interpolator>, | |
37 rotation_interpolator: Option<Interpolator>, | |
38 color_interpolator: Option<Interpolator>, | |
39 | |
40 anm: Option<Anm0>, | |
41 | |
42 dest_offset: [f32; 3], | |
43 texcoords: [f32; 4], | |
44 texoffsets: [f32; 2], | |
45 rescale: [f32; 2], | |
46 scale_speed: [f32; 2], | |
47 rotations_3d: [f32; 3], | |
48 rotations_speed_3d: [f32; 3], | |
49 color: [u8; 4], | |
50 } | |
51 | |
52 impl Sprite { | |
53 /// Create a new sprite. | |
54 pub fn new() -> Sprite { | |
55 Default::default() | |
56 } | |
57 | |
58 /// Update sprite values from the interpolators. | |
59 pub fn update(&mut self) { | |
60 self.changed = false; | |
61 } | |
62 } | |
63 | |
64 /// Interpreter for `Anm0` instructions to update a `Sprite`. | |
65 pub struct AnmRunner { | |
66 anm: Anm0, | |
67 sprite: Rc<RefCell<Sprite>>, | |
68 running: bool, | |
69 sprite_index_offset: u32, | |
70 script: Script, | |
71 instruction_pointer: usize, | |
72 frame: u16, | |
73 waiting: bool, | |
74 variables: ([i32; 4], [f32; 4], [i32; 4]), | |
75 timeout: Option<u16>, | |
76 } | |
77 | |
78 impl AnmRunner { | |
79 /// Create a new `AnmRunner`. | |
80 pub fn new(anm: &Anm0, script_id: u8, sprite: Rc<RefCell<Sprite>>, sprite_index_offset: u32) -> AnmRunner { | |
81 let mut runner = AnmRunner { | |
82 anm: anm.clone(), | |
83 sprite: sprite, | |
84 running: true, | |
85 waiting: false, | |
86 | |
87 script: anm.scripts[&script_id].clone(), | |
88 frame: 0, | |
89 timeout: None, | |
90 instruction_pointer: 0, | |
91 variables: ([0, 0, 0, 0 ], | |
92 [0., 0., 0., 0.], | |
93 [0, 0, 0, 0 ]), | |
94 | |
95 sprite_index_offset: sprite_index_offset, | |
96 }; | |
97 runner.run_frame(); | |
98 runner.sprite_index_offset = 0; | |
99 runner | |
100 } | |
101 | |
102 /// Trigger an interrupt. | |
103 pub fn interrupt(&mut self, interrupt: i32) -> bool { | |
104 let mut new_ip = self.script.interrupts.get(&interrupt); | |
105 if new_ip.is_none() { | |
106 new_ip = self.script.interrupts.get(&-1); | |
107 } | |
108 let new_ip = if let Some(new_ip) = new_ip { | |
109 *new_ip as usize | |
110 } else { | |
111 return false; | |
112 }; | |
113 self.instruction_pointer = new_ip; | |
114 let Call { time: frame, instr: _ } = &self.script.instructions[self.instruction_pointer]; | |
115 self.frame = *frame; | |
116 self.waiting = false; | |
117 self.sprite.borrow_mut().visible = true; | |
118 true | |
119 } | |
120 | |
121 /// Advance the Anm of a single frame. | |
122 pub fn run_frame(&mut self) -> bool { | |
123 if !self.running { | |
124 return false; | |
125 } | |
126 | |
127 while self.running && !self.waiting { | |
128 let Call { time: frame, instr } = self.script.instructions[self.instruction_pointer]; | |
129 let frame = frame.clone(); | |
130 | |
131 if frame > self.frame { | |
132 break; | |
133 } else { | |
134 self.instruction_pointer += 1; | |
135 } | |
136 | |
137 if frame == self.frame { | |
138 self.run_instruction(instr); | |
139 self.sprite.borrow_mut().changed = true; | |
140 } | |
141 } | |
142 | |
143 if !self.waiting { | |
144 self.frame += 1; | |
145 } else if let Some(timeout) = self.timeout { | |
146 if timeout == self.sprite.borrow().frame { // TODO: check if it’s happening at the correct frame. | |
147 self.waiting = false; | |
148 } | |
149 } | |
150 | |
151 self.sprite.borrow_mut().update(); | |
152 | |
153 self.running | |
154 } | |
155 | |
156 fn run_instruction(&mut self, instruction: Instruction) { | |
157 let mut sprite = self.sprite.borrow_mut(); | |
158 match instruction { | |
159 Instruction::Delete() => { | |
160 sprite.removed = true; | |
161 self.running = false; | |
162 } | |
163 Instruction::LoadSprite(sprite_index) => { | |
164 sprite.anm = Some(self.anm.clone()); | |
165 let texcoords = &self.anm.sprites[(sprite_index + self.sprite_index_offset) as usize]; | |
166 sprite.texcoords = [texcoords.x, texcoords.y, texcoords.width, texcoords.height]; | |
167 } | |
168 Instruction::SetScale(sx, sy) => { | |
169 sprite.rescale = [sx, sy]; | |
170 } | |
171 Instruction::SetAlpha(alpha) => { | |
172 // TODO: check this modulo. | |
173 sprite.color[3] = (alpha % 256) as u8; | |
174 } | |
175 Instruction::SetColor(b, g, r) => { | |
176 if sprite.fade_interpolator.is_none() { | |
177 sprite.color[0] = r; | |
178 sprite.color[1] = g; | |
179 sprite.color[2] = b; | |
180 } | |
181 } | |
182 Instruction::Jump(pointer) => { | |
183 // TODO: is that really how it works? | |
184 self.instruction_pointer = pointer as usize; | |
185 self.frame = self.script.instructions[pointer as usize].time; | |
186 } | |
187 Instruction::ToggleMirrored() => { | |
188 sprite.mirrored = !sprite.mirrored; | |
189 } | |
190 Instruction::SetRotations3d(rx, ry, rz) => { | |
191 sprite.rotations_3d = [rx, ry, rz]; | |
192 } | |
193 Instruction::SetRotationsSpeed3d(srx, sry, srz) => { | |
194 sprite.rotations_speed_3d = [srx, sry, srz]; | |
195 } | |
196 Instruction::SetScaleSpeed(ssx, ssy) => { | |
197 sprite.scale_speed = [ssx, ssy]; | |
198 } | |
199 Instruction::Fade(new_alpha, duration) => { | |
200 // XXX: implement fade(). | |
201 //sprite.fade(duration, new_alpha) | |
202 } | |
203 Instruction::SetBlendmodeAlphablend() => { | |
204 sprite.blendfunc = 1; | |
205 } | |
206 Instruction::SetBlendmodeAdd() => { | |
207 sprite.blendfunc = 0; | |
208 } | |
209 Instruction::KeepStill() => { | |
210 self.running = false; | |
211 } | |
212 Instruction::LoadRandomSprite(min_index, amplitude) => { | |
213 //self.load_sprite(min_index + randrange(amp)) | |
214 } | |
215 Instruction::Move(x, y, z) => { | |
216 sprite.dest_offset = [x, y, z]; | |
217 } | |
218 Instruction::MoveToLinear(x, y, z, duration) => { | |
219 // XXX: implement move_in(). | |
220 //sprite.move_in(duration, x, y, z); | |
221 } | |
222 Instruction::MoveToDecel(x, y, z, duration) => { | |
223 // XXX: implement move_in(). | |
224 //sprite.move_in(duration, x, y, z, 2*x - x**2); | |
225 } | |
226 Instruction::MoveToAccel(x, y, z, duration) => { | |
227 // XXX: implement move_in(). | |
228 //sprite.move_in(duration, x, y, z, x**2); | |
229 } | |
230 Instruction::Wait() => { | |
231 self.waiting = true; | |
232 } | |
233 // There is nothing to do here. | |
234 Instruction::InterruptLabel(label) => (), | |
235 Instruction::SetCornerRelativePlacement() => { | |
236 sprite.corner_relative_placement = true; | |
237 } | |
238 Instruction::WaitEx() => { | |
239 sprite.visible = false; | |
240 self.waiting = true; | |
241 } | |
242 Instruction::SetAllowOffset(value) => { | |
243 sprite.allow_dest_offset = value == 1 | |
244 } | |
245 Instruction::SetAutomaticOrientation(value) => { | |
246 sprite.automatic_orientation = value == 1 | |
247 } | |
248 Instruction::ShiftTextureX(dx) => { | |
249 let [tox, toy] = sprite.texoffsets; | |
250 sprite.texoffsets = [tox + dx, toy]; | |
251 } | |
252 Instruction::ShiftTextureY(dy) => { | |
253 let [tox, toy] = sprite.texoffsets; | |
254 sprite.texoffsets = [tox, toy + dy]; | |
255 } | |
256 Instruction::SetVisible(visible) => { | |
257 sprite.visible = (visible & 1) != 0; | |
258 } | |
259 Instruction::ScaleIn(sx, sy, duration) => { | |
260 // TODO: implement scale_in(). | |
261 //sprite.scale_in(duration, sx, sy) | |
262 } | |
263 Instruction::Todo(todo) => { | |
264 // TODO. | |
265 } | |
266 } | |
267 } | |
268 } | |
269 | |
270 #[cfg(test)] | |
271 mod tests { | |
272 use super::*; | |
273 use std::io::{self, Read}; | |
274 use std::fs::File; | |
275 | |
276 #[test] | |
277 fn anm_runner() { | |
278 let file = File::open("/home/linkmauve/games/pc/東方/TH06 ~ The Embodiment of Scarlet Devil/CM/player01.anm").unwrap(); | |
279 let mut file = io::BufReader::new(file); | |
280 let mut buf = vec![]; | |
281 file.read_to_end(&mut buf).unwrap(); | |
282 let anm0 = Anm0::from_slice(&buf).unwrap(); | |
283 assert_eq!(anm0.size, (256, 256)); | |
284 assert_eq!(anm0.format, 5); | |
285 let sprite = Rc::new(RefCell::new(Sprite::new())); | |
286 let mut anm_runner = AnmRunner::new(&anm0, 1, sprite.clone(), 0); | |
287 for _ in 0..50 { | |
288 anm_runner.run_frame(); | |
289 } | |
290 } | |
291 } |