Mercurial > touhou
comparison formats/src/th06/anm0.rs @ 757:21b186be2590
Split the Rust version into multiple crates.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Tue, 05 Jan 2021 02:16:32 +0100 |
parents | src/th06/anm0.rs@fc937d93a57c |
children | 2a5279168d5a |
comparison
equal
deleted
inserted
replaced
756:4d91790cf8ab | 757:21b186be2590 |
---|---|
1 //! ANM0 animation format support. | |
2 | |
3 use nom::{ | |
4 IResult, | |
5 bytes::complete::{tag, take_while_m_n}, | |
6 number::complete::{le_u8, le_u16, le_u32, le_i32, le_f32}, | |
7 sequence::tuple, | |
8 multi::{many_m_n, many0}, | |
9 }; | |
10 use std::collections::HashMap; | |
11 | |
12 /// Coordinates of a sprite into the image. | |
13 #[derive(Debug, Clone)] | |
14 pub struct Sprite { | |
15 /// Index inside the anm0. | |
16 pub index: u32, | |
17 | |
18 /// X coordinate in the sprite sheet. | |
19 pub x: f32, | |
20 | |
21 /// Y coordinate in the sprite sheet. | |
22 pub y: f32, | |
23 | |
24 /// Width of the sprite. | |
25 pub width: f32, | |
26 | |
27 /// Height of the sprite. | |
28 pub height: f32, | |
29 } | |
30 | |
31 /// A single instruction, part of a `Script`. | |
32 #[derive(Debug, Clone)] | |
33 pub struct Call { | |
34 /// Time at which this instruction will be called. | |
35 pub time: u16, | |
36 | |
37 /// The instruction to call. | |
38 pub instr: Instruction, | |
39 } | |
40 | |
41 /// Script driving an animation. | |
42 #[derive(Debug, Clone)] | |
43 pub struct Script { | |
44 /// List of instructions in this script. | |
45 pub instructions: Vec<Call>, | |
46 | |
47 /// List of interrupts in this script. | |
48 pub interrupts: HashMap<i32, u8> | |
49 } | |
50 | |
51 /// Main struct of the ANM0 animation format. | |
52 #[derive(Debug, Clone)] | |
53 pub struct Anm0 { | |
54 /// Resolution of the image used by this ANM. | |
55 pub size: (u32, u32), | |
56 | |
57 /// Format of this ANM. | |
58 pub format: u32, | |
59 | |
60 /// File name of the main image. | |
61 pub png_filename: String, | |
62 | |
63 /// File name of an alpha channel image. | |
64 pub alpha_filename: Option<String>, | |
65 | |
66 /// A list of sprites, coordinates into the attached image. | |
67 pub sprites: Vec<Sprite>, | |
68 | |
69 /// A map of scripts. | |
70 pub scripts: HashMap<u8, Script>, | |
71 } | |
72 | |
73 impl Anm0 { | |
74 /// Parse a slice of bytes into an `Anm0` struct. | |
75 pub fn from_slice(data: &[u8]) -> IResult<&[u8], Vec<Anm0>> { | |
76 many0(parse_anm0)(data) | |
77 } | |
78 | |
79 /// TODO | |
80 pub fn inv_size(&self) -> (f32, f32) { | |
81 let (x, y) = self.size; | |
82 (1. / x as f32, 1. / y as f32) | |
83 } | |
84 } | |
85 | |
86 fn parse_name(i: &[u8]) -> IResult<&[u8], String> { | |
87 let (_, slice) = take_while_m_n(0, 32, |c| c != 0)(i)?; | |
88 let string = match String::from_utf8(slice.to_vec()) { | |
89 Ok(string) => string, | |
90 // XXX: use a more specific error instead. | |
91 Err(_) => return Err(nom::Err::Failure(nom::error::Error::new(i, nom::error::ErrorKind::Eof))) | |
92 }; | |
93 Ok((i, string)) | |
94 } | |
95 | |
96 fn parse_sprite(i: &[u8]) -> IResult<&[u8], Sprite> { | |
97 let (i, (index, x, y, width, height)) = tuple((le_u32, le_f32, le_f32, le_f32, le_f32))(i)?; | |
98 Ok((i, Sprite { | |
99 index, | |
100 x, | |
101 y, | |
102 width, | |
103 height, | |
104 })) | |
105 } | |
106 | |
107 macro_rules! declare_anm_instructions { | |
108 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => { | |
109 /// Available instructions in an `Anm0`. | |
110 #[allow(missing_docs)] | |
111 #[derive(Debug, Clone, Copy)] | |
112 pub enum Instruction { | |
113 $( | |
114 $name($($arg_type),*) | |
115 ),* | |
116 } | |
117 | |
118 fn parse_instruction_args(mut i: &[u8], opcode: u8) -> IResult<&[u8], Instruction> { | |
119 let instr = match opcode { | |
120 $( | |
121 $opcode => { | |
122 $( | |
123 let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?; | |
124 i = i2; | |
125 )* | |
126 Instruction::$name($($arg),*) | |
127 } | |
128 )* | |
129 // XXX: use a more specific error instead. | |
130 _ => return Err(nom::Err::Failure(nom::error::Error::new(i, nom::error::ErrorKind::Eof))) | |
131 }; | |
132 Ok((i, instr)) | |
133 } | |
134 }; | |
135 } | |
136 | |
137 declare_anm_instructions!{ | |
138 0 => fn Delete(), | |
139 1 => fn LoadSprite(sprite_number: u32), | |
140 2 => fn SetScale(sx: f32, sy: f32), | |
141 3 => fn SetAlpha(alpha: u32), | |
142 4 => fn SetColor(red: u8, green: u8, blue: u8/*, XXX: x8*/), | |
143 5 => fn Jump(instruction: u32), | |
144 7 => fn ToggleMirrored(), | |
145 9 => fn SetRotations3d(x: f32, y: f32, z: f32), | |
146 10 => fn SetRotationsSpeed3d(x: f32, y: f32, z: f32), | |
147 11 => fn SetScaleSpeed(sx: f32, sy: f32), | |
148 12 => fn Fade(alpha: u32, duration: u32), | |
149 13 => fn SetBlendmodeAdd(), | |
150 14 => fn SetBlendmodeAlphablend(), | |
151 15 => fn KeepStill(), | |
152 16 => fn LoadRandomSprite(min_index: u32, amplitude: u32), | |
153 17 => fn Move(x: f32, y: f32, z: f32), | |
154 18 => fn MoveToLinear(x: f32, y: f32, z: f32, duration: u32), | |
155 19 => fn MoveToDecel(x: f32, y: f32, z: f32, duration: u32), | |
156 20 => fn MoveToAccel(x: f32, y: f32, z: f32, duration: u32), | |
157 21 => fn Wait(), | |
158 22 => fn InterruptLabel(label: i32), | |
159 23 => fn SetCornerRelativePlacement(), | |
160 24 => fn WaitEx(), | |
161 25 => fn SetAllowOffset(allow: u32), // TODO: better name | |
162 26 => fn SetAutomaticOrientation(automatic: u32), | |
163 27 => fn ShiftTextureX(dx: f32), | |
164 28 => fn ShiftTextureY(dy: f32), | |
165 29 => fn SetVisible(visible: u32), | |
166 30 => fn ScaleIn(sx: f32, sy: f32, duration: u32), | |
167 31 => fn Todo(todo: u32), | |
168 } | |
169 | |
170 fn parse_anm0(input: &[u8]) -> IResult<&[u8], Anm0> { | |
171 let (i, (num_sprites, num_scripts, _, width, height, format, _unknown1, | |
172 first_name_offset, _unused, second_name_offset, version, _unknown2, | |
173 _texture_offset, has_data, _next_offset, unknown3)) = | |
174 tuple((le_u32, le_u32, tag(b"\0\0\0\0"), le_u32, le_u32, le_u32, le_u32, le_u32, | |
175 le_u32, le_u32, le_u32, le_u32, le_u32, le_u32, le_u32, le_u32))(input)?; | |
176 | |
177 assert_eq!(version, 0); | |
178 assert_eq!(unknown3, 0); | |
179 assert_eq!(has_data, 0); | |
180 let num_sprites = num_sprites as usize; | |
181 let num_scripts = num_scripts as usize; | |
182 | |
183 let (i, sprite_offsets) = many_m_n(num_sprites, num_sprites, le_u32)(i)?; | |
184 let (_, script_offsets) = many_m_n(num_scripts, num_scripts, tuple((le_u32, le_u32)))(i)?; | |
185 | |
186 let png_filename = if first_name_offset > 0 { | |
187 if input.len() < first_name_offset as usize { | |
188 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof))); | |
189 } | |
190 let i = &input[first_name_offset as usize..]; | |
191 let (_, name) = parse_name(i)?; | |
192 name | |
193 } else { | |
194 String::new() | |
195 }; | |
196 | |
197 let alpha_filename = if second_name_offset > 0 { | |
198 if input.len() < second_name_offset as usize { | |
199 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof))); | |
200 } | |
201 let i = &input[second_name_offset as usize..]; | |
202 let (_, name) = parse_name(i)?; | |
203 Some(name) | |
204 } else { | |
205 None | |
206 }; | |
207 | |
208 let mut sprites = vec![]; | |
209 let mut i = &input[..]; | |
210 for offset in sprite_offsets.into_iter().map(|x| x as usize) { | |
211 if input.len() < offset { | |
212 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof))); | |
213 } | |
214 i = &input[offset..]; | |
215 let (_, sprite) = parse_sprite(i)?; | |
216 sprites.push(sprite); | |
217 } | |
218 | |
219 let mut scripts = HashMap::new(); | |
220 for (index, offset) in script_offsets.into_iter().map(|(index, offset)| (index as u8, offset as usize)) { | |
221 if input.len() < offset { | |
222 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof))); | |
223 } | |
224 i = &input[offset..]; | |
225 let mut instruction_offsets = vec![]; | |
226 | |
227 let mut instructions = vec![]; | |
228 loop { | |
229 let tell = input.len() - i.len(); | |
230 instruction_offsets.push(tell - offset); | |
231 // TODO: maybe check against the size of parsed data? | |
232 let (i2, (time, opcode, _size)) = tuple((le_u16, le_u8, le_u8))(i)?; | |
233 let (i2, instr) = parse_instruction_args(i2, opcode)?; | |
234 instructions.push(Call { time, instr }); | |
235 i = i2; | |
236 if opcode == 0 { | |
237 break; | |
238 } | |
239 } | |
240 let mut interrupts = HashMap::new(); | |
241 let mut j = 0; | |
242 for Call { time: _, instr } in &mut instructions { | |
243 match instr { | |
244 Instruction::Jump(ref mut offset) => { | |
245 let result = instruction_offsets.binary_search(&(*offset as usize)); | |
246 match result { | |
247 Ok(ptr) => *offset = ptr as u32, | |
248 Err(ptr) => { | |
249 // XXX: use a more specific error instead. | |
250 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof))); | |
251 //println!("Instruction offset not found for pointer: {}", ptr); | |
252 } | |
253 } | |
254 } | |
255 Instruction::InterruptLabel(interrupt) => { | |
256 interrupts.insert(*interrupt, j + 1); | |
257 } | |
258 _ => () | |
259 } | |
260 j += 1; | |
261 } | |
262 scripts.insert(index, Script { | |
263 instructions, | |
264 interrupts, | |
265 }); | |
266 } | |
267 | |
268 let anm0 = Anm0 { | |
269 size: (width, height), | |
270 format, | |
271 png_filename, | |
272 alpha_filename, | |
273 sprites, | |
274 scripts, | |
275 }; | |
276 Ok((i, anm0)) | |
277 } | |
278 | |
279 #[cfg(test)] | |
280 mod tests { | |
281 use super::*; | |
282 use std::io::{self, Read}; | |
283 use std::fs::File; | |
284 | |
285 #[test] | |
286 fn anm0() { | |
287 let file = File::open("EoSD/CM/player01.anm").unwrap(); | |
288 let mut file = io::BufReader::new(file); | |
289 let mut buf = vec![]; | |
290 file.read_to_end(&mut buf).unwrap(); | |
291 let (_, mut anms) = Anm0::from_slice(&buf).unwrap(); | |
292 assert_eq!(anms.len(), 1); | |
293 let anm0 = anms.pop().unwrap(); | |
294 assert_eq!(anm0.size, (256, 256)); | |
295 assert_eq!(anm0.format, 5); | |
296 } | |
297 } |