]>
Commit | Line | Data |
---|---|---|
c17769db KS |
1 | use nihav_core::codecs::*; |
2 | use nihav_core::io::byteio::*; | |
3 | //use std::str::FromStr; | |
4 | ||
5 | struct FrameData { | |
6 | info: NACodecInfoRef, | |
7 | width: usize, | |
8 | height: usize, | |
9 | frm0: Vec<u16>, | |
10 | frm1: Vec<u16>, | |
11 | frm2: Vec<u16>, | |
12 | } | |
13 | ||
14 | impl FrameData { | |
15 | fn new() -> Self { | |
16 | Self { | |
17 | info: NACodecInfoRef::default(), | |
18 | width: 0, | |
19 | height: 0, | |
20 | frm0: Vec::new(), | |
21 | frm1: Vec::new(), | |
22 | frm2: Vec::new(), | |
23 | } | |
24 | } | |
25 | fn init(&mut self, info: NACodecInfoRef) -> DecoderResult<()> { | |
26 | if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { | |
27 | self.width = vinfo.get_width(); | |
28 | self.height = vinfo.get_height(); | |
29 | let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, RGB565_FORMAT)); | |
30 | self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); | |
31 | ||
32 | self.frm0.resize(self.width * self.height, 0); | |
33 | self.frm1.resize(self.width * self.height, 0); | |
34 | self.frm2.resize(self.width * self.height, 0); | |
35 | Ok(()) | |
36 | } else { | |
37 | Err(DecoderError::InvalidData) | |
38 | } | |
39 | } | |
40 | fn get_frame(&mut self, pkt: &NAPacket) -> DecoderResult<NAFrameRef> { | |
41 | let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; | |
42 | if let Some(ref mut vbuf) = bufinfo.get_vbuf16() { | |
43 | let stride = vbuf.get_stride(0); | |
44 | let data = vbuf.get_data_mut().unwrap(); | |
45 | for (dst, src) in data.chunks_mut(stride).zip(self.frm0.chunks(self.width).take(self.height)) { | |
46 | dst[..self.width].copy_from_slice(src); | |
47 | } | |
48 | } else { | |
49 | return Err(DecoderError::Bug); | |
50 | } | |
51 | ||
52 | let is_intra = pkt.keyframe; | |
53 | let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); | |
54 | frm.set_keyframe(is_intra); | |
55 | frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P }); | |
56 | Ok(frm.into_ref()) | |
57 | } | |
58 | } | |
59 | ||
60 | fn decode_rle(br: &mut ByteReader, dst: &mut [u8]) -> DecoderResult<()> { | |
61 | let mut len = 0; | |
62 | let mut clr = 0; | |
63 | let mut run = false; | |
64 | ||
65 | for el in dst.iter_mut() { | |
66 | if len == 0 { | |
67 | let op = br.read_byte()?; | |
68 | run = (op & 1) != 0; | |
69 | if run { | |
70 | clr = br.read_byte()?; | |
71 | } | |
72 | len = ((op >> 1) + 1) as usize; | |
73 | } | |
74 | *el = if run { clr } else { br.read_byte()? }; | |
75 | len -= 1; | |
76 | } | |
77 | validate!(len == 0); | |
78 | ||
79 | Ok(()) | |
80 | } | |
81 | ||
82 | struct Smush2Decoder { | |
83 | glyphs4: [[u8; 16]; 256], | |
84 | glyphs8: [[u8; 64]; 256], | |
85 | pic: FrameData, | |
86 | rle_buf: Vec<u8>, | |
87 | } | |
88 | ||
89 | impl Smush2Decoder { | |
90 | fn new() -> Self { | |
91 | let mut glyphs4 = [[0; 16]; 256]; | |
92 | let mut glyphs8 = [[0; 64]; 256]; | |
93 | super::make_glyphs_47(&mut glyphs4, &mut glyphs8); | |
94 | Self { | |
95 | pic: FrameData::new(), | |
96 | rle_buf: Vec::new(), | |
97 | glyphs4, glyphs8, | |
98 | } | |
99 | } | |
100 | } | |
101 | ||
102 | struct BlockData<'a> { | |
103 | glyphs4: &'a [[u8; 16]; 256], | |
104 | glyphs8: &'a [[u8; 64]; 256], | |
105 | frm1: &'a [u16], | |
106 | frm2: &'a [u16], | |
107 | cb: &'a [u16; 256], | |
108 | clr4: [u16; 4], | |
109 | stride: usize, | |
110 | } | |
111 | ||
112 | fn draw_glyph(dst: &mut [u16], stride: usize, bsize: usize, glyph: &[u8], clr2: [u16; 2]) { | |
113 | for (dst, src) in dst.chunks_mut(stride).zip(glyph.chunks_exact(bsize)) { | |
114 | for (el, &bit) in dst[..bsize].iter_mut().zip(src.iter()) { | |
115 | *el = clr2[bit as usize]; | |
116 | } | |
117 | } | |
118 | } | |
119 | ||
120 | fn do_block2(br: &mut ByteReader, dst: &mut [u16], x: usize, y: usize, bsize: usize, bdata: &BlockData) -> DecoderResult<()> { | |
121 | let stride = bdata.stride; | |
122 | let op = br.read_byte()?; | |
123 | match op { | |
124 | 0xFF if bsize > 2 => { | |
125 | let hsize = bsize / 2; | |
126 | do_block2(br, dst, x, y, hsize, bdata)?; | |
127 | do_block2(br, &mut dst[hsize..], x + hsize, y, bsize / 2, bdata)?; | |
128 | do_block2(br, &mut dst[hsize * stride..], x, y + hsize, hsize, bdata)?; | |
129 | do_block2(br, &mut dst[hsize * (stride + 1)..], x + hsize, y + hsize, bsize / 2, bdata)?; | |
130 | }, | |
131 | 0xFF => { | |
132 | dst[0] = br.read_u16le()?; | |
133 | dst[1] = br.read_u16le()?; | |
134 | dst[stride] = br.read_u16le()?; | |
135 | dst[stride + 1] = br.read_u16le()?; | |
136 | }, | |
137 | 0xFE => { | |
138 | let pix = br.read_u16le()?; | |
139 | for dst in dst.chunks_mut(stride).take(bsize) { | |
140 | for el in dst[..bsize].iter_mut() { | |
141 | *el = pix; | |
142 | } | |
143 | } | |
144 | }, | |
145 | 0xFD => { | |
146 | let idx = br.read_byte()? as usize; | |
147 | let pix = bdata.cb[idx]; | |
148 | for dst in dst.chunks_mut(stride).take(bsize) { | |
149 | for el in dst[..bsize].iter_mut() { | |
150 | *el = pix; | |
151 | } | |
152 | } | |
153 | }, | |
154 | 0xF9..=0xFC => { | |
155 | let pix = bdata.clr4[(op - 0xF9) as usize]; | |
156 | for dst in dst.chunks_mut(stride).take(bsize) { | |
157 | for el in dst[..bsize].iter_mut() { | |
158 | *el = pix; | |
159 | } | |
160 | } | |
161 | }, | |
162 | 0xF8 if bsize > 2 => { | |
163 | let idx = br.read_byte()? as usize; | |
164 | let mut clr2 = [0; 2]; | |
165 | clr2[1] = br.read_u16le()?; | |
166 | clr2[0] = br.read_u16le()?; | |
167 | let glyph: &[u8] = if bsize == 8 { &bdata.glyphs8[idx] } else { &bdata.glyphs4[idx] }; | |
168 | draw_glyph(dst, stride, bsize, glyph, clr2); | |
169 | }, | |
170 | 0xF8 => { | |
171 | dst[0] = br.read_u16le()?; | |
172 | dst[1] = br.read_u16le()?; | |
173 | dst[stride] = br.read_u16le()?; | |
174 | dst[stride + 1] = br.read_u16le()?; | |
175 | }, | |
176 | 0xF7 if bsize > 2 => { | |
177 | let idx = br.read_byte()? as usize; | |
178 | let mut clr2 = [0; 2]; | |
179 | clr2[1] = bdata.cb[br.read_byte()? as usize]; | |
180 | clr2[0] = bdata.cb[br.read_byte()? as usize]; | |
181 | let glyph: &[u8] = if bsize == 8 { &bdata.glyphs8[idx] } else { &bdata.glyphs4[idx] }; | |
182 | draw_glyph(dst, stride, bsize, glyph, clr2); | |
183 | }, | |
184 | 0xF7 => { | |
185 | dst[0] = bdata.cb[br.read_byte()? as usize]; | |
186 | dst[1] = bdata.cb[br.read_byte()? as usize]; | |
187 | dst[stride] = bdata.cb[br.read_byte()? as usize]; | |
188 | dst[stride + 1] = bdata.cb[br.read_byte()? as usize]; | |
189 | }, | |
190 | 0xF6 => { | |
191 | let off = x + y * stride; | |
192 | let src = &bdata.frm1[off..]; | |
193 | for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { | |
194 | dst[..bsize].copy_from_slice(&src[..bsize]); | |
195 | } | |
196 | }, | |
197 | 0xF5 => { | |
198 | let off = br.read_u16le()? as i16 as isize; | |
199 | let mx = off % (stride as isize); | |
200 | let my = off / (stride as isize); | |
201 | let off = (x as isize) + mx + ((y as isize) + my) * (stride as isize); | |
202 | validate!(off >= 0); | |
203 | let src = &bdata.frm2[off as usize..]; | |
204 | for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { | |
205 | let size = dst.len().min(src.len()).min(bsize); | |
206 | dst[..size].copy_from_slice(&src[..size]); | |
207 | } | |
208 | }, | |
209 | _ => { | |
210 | let mx = C47_MV[op as usize][0] as isize; | |
211 | let my = C47_MV[op as usize][1] as isize; | |
212 | let off = (x as isize) + mx + ((y as isize) + my) * (stride as isize); | |
213 | let src = &bdata.frm2[off as usize..]; | |
214 | for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { | |
215 | let size = dst.len().min(src.len()).min(bsize); | |
216 | dst[..size].copy_from_slice(&src[..size]); | |
217 | } | |
218 | }, | |
219 | }; | |
220 | Ok(()) | |
221 | } | |
222 | ||
223 | impl NADecoder for Smush2Decoder { | |
224 | fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { | |
225 | self.pic.init(info) | |
226 | } | |
227 | fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> { | |
228 | let src = pkt.get_buffer(); | |
229 | validate!(src.len() > 8); | |
230 | ||
231 | let mut mr = MemoryReader::new_read(&src); | |
232 | let mut br = ByteReader::new(&mut mr); | |
233 | ||
234 | let mut reorder = 0; | |
235 | while br.left() > 0 { | |
236 | let tag = br.read_tag()?; | |
237 | let size = br.read_u32be()? as usize; | |
238 | let tend = br.tell() + (size as u64); | |
239 | validate!((size as i64) <= br.left()); | |
240 | match &tag { | |
241 | b"Bl16" => { | |
242 | validate!(size >= 8); | |
243 | br.read_skip(2)?; | |
244 | let _x = br.read_u16le()? as usize; | |
245 | let _y = br.read_u16le()? as usize; | |
246 | br.read_skip(2)?; | |
247 | validate!(_x <= self.pic.width && _y <= self.pic.height); | |
248 | if size > 8 { | |
249 | let w = br.read_u32le()? as usize; | |
250 | let h = br.read_u32le()? as usize; | |
251 | validate!(w == self.pic.width && h == self.pic.height); | |
252 | let seq = br.read_u16le()?; | |
253 | let compr = br.read_byte()?; | |
254 | reorder = br.read_byte()?; | |
255 | br.read_skip(4)?; | |
256 | let mut clr4 = [0; 4]; | |
257 | for el in clr4.iter_mut() { | |
258 | *el = br.read_u16le()?; | |
259 | } | |
260 | let bg_clr = br.read_u16le()?; | |
261 | let _fg_clr = br.read_u16le()?; | |
262 | let _unp_size = br.read_u32le()?; | |
263 | let mut cb = [0; 256]; | |
264 | for el in cb.iter_mut() { | |
265 | *el = br.read_u16le()?; | |
266 | } | |
267 | if size > 0x230 { | |
268 | br.read_skip(8)?; | |
269 | } | |
270 | validate!(br.tell() < tend); | |
271 | let start = br.tell() as usize; | |
272 | br.seek(SeekFrom::Start(tend))?; | |
273 | let mut mr = MemoryReader::new_read(&src[start..(tend as usize)]); | |
274 | let mut br = ByteReader::new(&mut mr); | |
275 | ||
276 | if seq == 0 { | |
277 | for el in self.pic.frm1.iter_mut() { | |
278 | *el = bg_clr; | |
279 | } | |
280 | for el in self.pic.frm2.iter_mut() { | |
281 | *el = bg_clr; | |
282 | } | |
283 | } | |
284 | ||
285 | match compr { | |
286 | 0 => { | |
287 | for row in self.pic.frm0.chunks_mut(self.pic.width).take(h) { | |
288 | for el in row[..w].iter_mut() { | |
289 | *el = br.read_u16le()?; | |
290 | } | |
291 | } | |
292 | }, | |
293 | 1 => { unimplemented!(); }, //decode half-res and interpolate | |
294 | 2 => { | |
295 | let bdata = BlockData { | |
296 | glyphs4: &self.glyphs4, | |
297 | glyphs8: &self.glyphs8, | |
298 | frm1: &self.pic.frm1, | |
299 | frm2: &self.pic.frm2, | |
300 | stride: self.pic.width, | |
301 | clr4, | |
302 | cb: &cb, | |
303 | }; | |
304 | let dst = &mut self.pic.frm0; | |
305 | let stride = self.pic.width; | |
306 | for (row_no, row) in dst.chunks_mut(stride * 8).take((h + 7) / 8).enumerate() { | |
307 | for col in (0..w).step_by(8) { | |
308 | do_block2(&mut br, &mut row[col..], col, row_no * 8, 8, &bdata)?; | |
309 | } | |
310 | } | |
311 | }, | |
312 | 3 => { | |
313 | self.pic.frm0.copy_from_slice(&self.pic.frm2); | |
314 | }, | |
315 | 4 => { | |
316 | self.pic.frm0.copy_from_slice(&self.pic.frm1); | |
317 | }, | |
318 | 5 => { | |
319 | let size = w * h * 2; | |
320 | self.rle_buf.resize(size, 0); | |
321 | decode_rle(&mut br, &mut self.rle_buf)?; | |
322 | for (drow, srow) in self.pic.frm0.chunks_mut(self.pic.width).zip(self.rle_buf.chunks(w * 2)) { | |
323 | for (dst, src) in drow.iter_mut().zip(srow.chunks_exact(2)) { | |
324 | *dst = read_u16le(src)?; | |
325 | } | |
326 | } | |
327 | }, | |
328 | 6 => { | |
329 | for row in self.pic.frm0.chunks_mut(self.pic.width).take(h) { | |
330 | for el in row[..w].iter_mut() { | |
331 | let idx = br.read_byte()? as usize; | |
332 | *el = cb[idx]; | |
333 | } | |
334 | } | |
335 | }, | |
336 | 7 => { unimplemented!(); }, //decode half-res using codebook indices and interpolate | |
337 | 8 => { | |
338 | let size = w * h; | |
339 | self.rle_buf.resize(size, 0); | |
340 | decode_rle(&mut br, &mut self.rle_buf)?; | |
341 | for (row, src) in self.pic.frm0.chunks_mut(self.pic.width).zip(self.rle_buf.chunks(w)) { | |
342 | for (el, &idx) in row.iter_mut().zip(src.iter()) { | |
343 | *el = cb[idx as usize]; | |
344 | } | |
345 | } | |
346 | }, | |
347 | _ => return Err(DecoderError::NotImplemented), | |
348 | }; | |
349 | } | |
350 | }, | |
351 | _ => br.read_skip(size)?, | |
352 | }; | |
353 | } | |
354 | ||
355 | let ret = self.pic.get_frame(pkt); | |
356 | ||
357 | if reorder == 2 { | |
358 | std::mem::swap(&mut self.pic.frm1, &mut self.pic.frm2); | |
359 | } | |
360 | if reorder != 0 { | |
361 | std::mem::swap(&mut self.pic.frm0, &mut self.pic.frm2); | |
362 | } | |
363 | ||
364 | ret | |
365 | } | |
366 | fn flush(&mut self) { | |
367 | } | |
368 | } | |
369 | ||
370 | impl NAOptionHandler for Smush2Decoder { | |
371 | fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } | |
372 | fn set_options(&mut self, _options: &[NAOption]) { } | |
373 | fn query_option_value(&self, _name: &str) -> Option<NAValue> { None } | |
374 | } | |
375 | ||
376 | ||
377 | pub fn get_decoder_video_v2() -> Box<dyn NADecoder + Send> { | |
378 | Box::new(Smush2Decoder::new()) | |
379 | } | |
380 | ||
381 | #[cfg(test)] | |
382 | mod test { | |
383 | use nihav_core::codecs::RegisteredDecoders; | |
384 | use nihav_core::demuxers::RegisteredDemuxers; | |
385 | use nihav_codec_support::test::dec_video::*; | |
386 | use crate::game_register_all_decoders; | |
387 | use crate::game_register_all_demuxers; | |
388 | // sample from Grim Fandango | |
389 | #[test] | |
390 | fn test_smush_sanm() { | |
391 | let mut dmx_reg = RegisteredDemuxers::new(); | |
392 | game_register_all_demuxers(&mut dmx_reg); | |
393 | let mut dec_reg = RegisteredDecoders::new(); | |
394 | game_register_all_decoders(&mut dec_reg); | |
395 | ||
396 | // sample from Grim Fandango | |
397 | test_decoding("smush", "smushv2", "assets/Game/smush/lol.snm", Some(4), &dmx_reg, &dec_reg, | |
398 | ExpectedTestResult::MD5Frames(vec![ | |
399 | [0x408e4dc9, 0x4483d7d8, 0xc9fae314, 0x3bb45ec9], | |
400 | [0x83548952, 0x0b4a6ccb, 0x42609794, 0x59d3c7d4], | |
401 | [0x5349f6ca, 0x56361199, 0x7194439f, 0x90df21b8], | |
402 | [0x0c359bab, 0xed69f862, 0x9c899813, 0x3f6aac2a], | |
403 | [0x58870617, 0x97c5f3a6, 0x1b2c761c, 0x6ec1cd0e]])); | |
404 | } | |
405 | } | |
406 | ||
407 | const C47_MV: [[i8; 2]; 255] = [ | |
408 | [ 0, 0], [ -1, -43], [ 6, -43], [ -9, -42], [ 13, -41], | |
409 | [-16, -40], [ 19, -39], [-23, -36], [ 26, -34], [ -2, -33], | |
410 | [ 4, -33], [-29, -32], [ -9, -32], [ 11, -31], [-16, -29], | |
411 | [ 32, -29], [ 18, -28], [-34, -26], [-22, -25], [ -1, -25], | |
412 | [ 3, -25], [ -7, -24], [ 8, -24], [ 24, -23], [ 36, -23], | |
413 | [-12, -22], [ 13, -21], [-38, -20], [ 0, -20], [-27, -19], | |
414 | [ -4, -19], [ 4, -19], [-17, -18], [ -8, -17], [ 8, -17], | |
415 | [ 18, -17], [ 28, -17], [ 39, -17], [-12, -15], [ 12, -15], | |
416 | [-21, -14], [ -1, -14], [ 1, -14], [-41, -13], [ -5, -13], | |
417 | [ 5, -13], [ 21, -13], [-31, -12], [-15, -11], [ -8, -11], | |
418 | [ 8, -11], [ 15, -11], [ -2, -10], [ 1, -10], [ 31, -10], | |
419 | [-23, -9], [-11, -9], [ -5, -9], [ 4, -9], [ 11, -9], | |
420 | [ 42, -9], [ 6, -8], [ 24, -8], [-18, -7], [ -7, -7], | |
421 | [ -3, -7], [ -1, -7], [ 2, -7], [ 18, -7], [-43, -6], | |
422 | [-13, -6], [ -4, -6], [ 4, -6], [ 8, -6], [-33, -5], | |
423 | [ -9, -5], [ -2, -5], [ 0, -5], [ 2, -5], [ 5, -5], | |
424 | [ 13, -5], [-25, -4], [ -6, -4], [ -3, -4], [ 3, -4], | |
425 | [ 9, -4], [-19, -3], [ -7, -3], [ -4, -3], [ -2, -3], | |
426 | [ -1, -3], [ 0, -3], [ 1, -3], [ 2, -3], [ 4, -3], | |
427 | [ 6, -3], [ 33, -3], [-14, -2], [-10, -2], [ -5, -2], | |
428 | [ -3, -2], [ -2, -2], [ -1, -2], [ 0, -2], [ 1, -2], | |
429 | [ 2, -2], [ 3, -2], [ 5, -2], [ 7, -2], [ 14, -2], | |
430 | [ 19, -2], [ 25, -2], [ 43, -2], [ -7, -1], [ -3, -1], | |
431 | [ -2, -1], [ -1, -1], [ 0, -1], [ 1, -1], [ 2, -1], | |
432 | [ 3, -1], [ 10, -1], [ -5, 0], [ -3, 0], [ -2, 0], | |
433 | [ -1, 0], [ 1, 0], [ 2, 0], [ 3, 0], [ 5, 0], | |
434 | [ 7, 0], [-10, 1], [ -7, 1], [ -3, 1], [ -2, 1], | |
435 | [ -1, 1], [ 0, 1], [ 1, 1], [ 2, 1], [ 3, 1], | |
436 | [-43, 2], [-25, 2], [-19, 2], [-14, 2], [ -5, 2], | |
437 | [ -3, 2], [ -2, 2], [ -1, 2], [ 0, 2], [ 1, 2], | |
438 | [ 2, 2], [ 3, 2], [ 5, 2], [ 7, 2], [ 10, 2], | |
439 | [ 14, 2], [-33, 3], [ -6, 3], [ -4, 3], [ -2, 3], | |
440 | [ -1, 3], [ 0, 3], [ 1, 3], [ 2, 3], [ 4, 3], | |
441 | [ 19, 3], [ -9, 4], [ -3, 4], [ 3, 4], [ 7, 4], | |
442 | [ 25, 4], [-13, 5], [ -5, 5], [ -2, 5], [ 0, 5], | |
443 | [ 2, 5], [ 5, 5], [ 9, 5], [ 33, 5], [ -8, 6], | |
444 | [ -4, 6], [ 4, 6], [ 13, 6], [ 43, 6], [-18, 7], | |
445 | [ -2, 7], [ 0, 7], [ 2, 7], [ 7, 7], [ 18, 7], | |
446 | [-24, 8], [ -6, 8], [-42, 9], [-11, 9], [ -4, 9], | |
447 | [ 5, 9], [ 11, 9], [ 23, 9], [-31, 10], [ -1, 10], | |
448 | [ 2, 10], [-15, 11], [ -8, 11], [ 8, 11], [ 15, 11], | |
449 | [ 31, 12], [-21, 13], [ -5, 13], [ 5, 13], [ 41, 13], | |
450 | [ -1, 14], [ 1, 14], [ 21, 14], [-12, 15], [ 12, 15], | |
451 | [-39, 17], [-28, 17], [-18, 17], [ -8, 17], [ 8, 17], | |
452 | [ 17, 18], [ -4, 19], [ 0, 19], [ 4, 19], [ 27, 19], | |
453 | [ 38, 20], [-13, 21], [ 12, 22], [-36, 23], [-24, 23], | |
454 | [ -8, 24], [ 7, 24], [ -3, 25], [ 1, 25], [ 22, 25], | |
455 | [ 34, 26], [-18, 28], [-32, 29], [ 16, 29], [-11, 31], | |
456 | [ 9, 32], [ 29, 32], [ -4, 33], [ 2, 33], [-26, 34], | |
457 | [ 23, 36], [-19, 39], [ 16, 40], [-13, 41], [ 9, 42], | |
458 | [ -6, 43], [ 1, 43], [ 0, 0], [ 0, 0], [ 0, 0], | |
459 | ]; |