1use std::io::Cursor;
5
6use crate::ByteBuffer;
7use crate::ByteSpan;
8use binrw::BinRead;
9use binrw::BinWrite;
10use binrw::binrw;
11
12#[binrw]
13#[derive(Debug, Clone, Copy)]
14#[brw(little)]
15struct PlatePosition {
16 x: i16,
17 y: i16,
18}
19
20#[binrw]
21#[derive(Debug)]
22#[brw(little)]
23struct TerrainHeader {
24 version: u32,
26 plate_count: u32,
27 plate_size: u32,
28 clip_distance: f32,
29
30 unknown: f32,
31
32 #[brw(pad_before = 32)]
33 #[br(count = plate_count)]
34 positions: Vec<PlatePosition>,
35}
36
37#[derive(Debug)]
38pub struct PlateModel {
39 pub position: (f32, f32),
40 pub filename: String,
41}
42
43#[derive(Debug)]
44pub struct Terrain {
45 pub plates: Vec<PlateModel>,
46}
47
48impl Terrain {
49 pub fn from_existing(buffer: ByteSpan) -> Option<Terrain> {
51 let mut cursor = Cursor::new(buffer);
52 let header = TerrainHeader::read(&mut cursor).ok()?;
53
54 let mut plates = vec![];
55
56 for i in 0..header.plate_count {
57 plates.push(PlateModel {
58 position: (
59 header.plate_size as f32 * (header.positions[i as usize].x as f32 + 0.5),
60 header.plate_size as f32 * (header.positions[i as usize].y as f32 + 0.5),
61 ),
62 filename: format!("{:04}.mdl", i),
63 })
64 }
65
66 Some(Terrain { plates })
67 }
68
69 pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
70 let mut buffer = ByteBuffer::new();
71
72 {
73 let mut cursor = Cursor::new(&mut buffer);
74
75 let plate_size = 128;
76
77 let header = TerrainHeader {
78 version: 0x1000003,
79 plate_count: self.plates.len() as u32,
80 plate_size,
81 clip_distance: 0.0, unknown: 1.0, positions: self
84 .plates
85 .iter()
86 .map(|model| PlatePosition {
87 x: ((model.position.0 / plate_size as f32) - 0.5) as i16,
88 y: ((model.position.1 / plate_size as f32) - 0.5) as i16,
89 })
90 .collect(),
91 };
92 header.write_le(&mut cursor).ok()?;
93 }
94
95 Some(buffer)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use std::fs::read;
102 use std::path::PathBuf;
103
104 use super::*;
105
106 #[test]
107 fn test_invalid() {
108 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
109 d.push("resources/tests");
110 d.push("random");
111
112 Terrain::from_existing(&read(d).unwrap());
114 }
115}