1use std::io::{BufRead, Cursor, Read};
7use std::str::FromStr;
8
9use super::{Bwm, BwmAabbNode, BwmFace, BwmType, BwmTypeCode, BwmVec3, SurfaceMaterial};
10
11#[derive(Debug, thiserror::Error)]
13pub enum BwmAsciiError {
14 #[error(transparent)]
16 Io(#[from] std::io::Error),
17 #[error("parse error: {0}")]
19 Parse(String),
20 #[error("structure error: {0}")]
22 Structure(String),
23 #[error("invalid data: {0}")]
25 InvalidData(String),
26}
27
28pub fn read_bwm_ascii<R: Read>(reader: &mut R) -> Result<Bwm, BwmAsciiError> {
36 let mut buffer = Vec::new();
37 reader.read_to_end(&mut buffer)?;
38 let cursor = Cursor::new(buffer);
39
40 let mut lines = cursor
42 .lines()
43 .map(|l| l.map_err(BwmAsciiError::Io))
44 .peekable();
45
46 let mut bwm = Bwm::new();
47 bwm.walkmesh_type = BwmTypeCode::from(BwmType::PlaceableOrDoor); let mut in_aabb_node = false;
50 let mut vertices = Vec::new();
51 let mut faces_raw = Vec::new(); let mut aabb_nodes = Vec::new();
53
54 while let Some(line_res) = lines.next() {
56 let line = line_res?;
57 let trimmed = line.trim_start();
58
59 if trimmed.is_empty() {
60 continue;
61 }
62
63 if trimmed.starts_with("node") {
66 if trimmed.contains("aabb") {
67 in_aabb_node = true;
68 }
69 continue;
70 }
71
72 if trimmed.starts_with("endnode") {
73 in_aabb_node = false;
74 continue;
75 }
76
77 if !in_aabb_node {
78 continue;
79 }
80
81 if trimmed.starts_with("position") {
84 let parts: Vec<&str> = trimmed.split_whitespace().collect();
85 if parts.len() >= 4 {
86 bwm.position = parse_vec3(&parts[1..4])?;
87 }
88 } else if trimmed.starts_with("orientation") {
89 } else if trimmed.starts_with("verts") {
91 let parts: Vec<&str> = trimmed.split_whitespace().collect();
92 if parts.len() >= 2 {
93 let count: usize = parts[1]
94 .parse()
95 .map_err(|e| BwmAsciiError::Parse(format!("vertex count: {}", e)))?;
96 for _ in 0..count {
97 if let Some(v_line_res) = lines.next() {
98 let v_line = v_line_res?;
99 let v_parts: Vec<&str> = v_line.split_whitespace().collect();
100 if v_parts.len() >= 3 {
101 vertices.push(parse_vec3(&v_parts[0..3])?);
102 }
103 } else {
104 return Err(BwmAsciiError::Structure("unexpected EOF in verts".into()));
105 }
106 }
107 }
108 } else if trimmed.starts_with("faces") {
109 let parts: Vec<&str> = trimmed.split_whitespace().collect();
110 if parts.len() >= 2 {
111 let count: usize = parts[1]
112 .parse()
113 .map_err(|e| BwmAsciiError::Parse(format!("face count: {}", e)))?;
114 for _ in 0..count {
115 if let Some(f_line_res) = lines.next() {
116 let f_line = f_line_res?;
117 let f_parts: Vec<&str> = f_line.split_whitespace().collect();
118 if f_parts.len() >= 8 {
119 let v1: u32 = f_parts[0]
120 .parse()
121 .map_err(|_| BwmAsciiError::Parse("face v1".into()))?;
122 let v2: u32 = f_parts[1]
123 .parse()
124 .map_err(|_| BwmAsciiError::Parse("face v2".into()))?;
125 let v3: u32 = f_parts[2]
126 .parse()
127 .map_err(|_| BwmAsciiError::Parse("face v3".into()))?;
128
129 let mat: u32 = f_parts[7]
131 .parse()
132 .map_err(|_| BwmAsciiError::Parse("face mat".into()))?;
133
134 faces_raw.push((v1, v2, v3, mat));
135 }
136 } else {
137 return Err(BwmAsciiError::Structure("unexpected EOF in faces".into()));
138 }
139 }
140 }
141 } else if let Some(stripped) = trimmed.strip_prefix("aabb") {
142 let remainder = stripped.trim();
144 if !remainder.is_empty() {
145 if let Ok(node) = parse_aabb_node(remainder) {
146 aabb_nodes.push(node);
147 }
148 }
149
150 loop {
152 let should_break = if let Some(Ok(next_line)) = lines.peek() {
153 let next_trimmed = next_line.trim_start();
154 next_trimmed.starts_with("node")
155 || next_trimmed.starts_with("endnode")
156 || next_trimmed.starts_with("position")
157 || next_trimmed.starts_with("orientation")
158 || next_trimmed.starts_with("verts")
159 || next_trimmed.starts_with("faces")
160 } else {
161 true };
163
164 if should_break {
165 break;
166 }
167
168 if let Some(line_res) = lines.next() {
170 let line = line_res?;
171 let trimmed = line.trim_start();
172 if trimmed.is_empty() {
173 continue;
174 }
175
176 if let Ok(node) = parse_aabb_node(trimmed) {
177 aabb_nodes.push(node);
178 }
179 }
180 }
181 }
182 }
183
184 bwm.vertices = vertices;
186
187 let mut walkable = Vec::new();
192 let mut unwalkable = Vec::new();
193
194 for (v1, v2, v3, mat_id) in faces_raw {
195 let (normal, planar_distance) = match (
196 bwm.vertices
197 .get(usize::try_from(v1).expect("vertex index fits in usize"))
198 .copied(),
199 bwm.vertices
200 .get(usize::try_from(v2).expect("vertex index fits in usize"))
201 .copied(),
202 bwm.vertices
203 .get(usize::try_from(v3).expect("vertex index fits in usize"))
204 .copied(),
205 ) {
206 (Some(a), Some(b), Some(c)) => face_normal_and_distance(a, b, c),
207 _ => (BwmVec3::new(0.0, 0.0, 1.0), 0.0),
208 };
209 let face = BwmFace {
210 vertex_indices: [v1, v2, v3],
211 material_id: mat_id,
212 normal,
213 planar_distance,
214 };
215
216 let is_walkable = SurfaceMaterial::try_from(mat_id)
217 .map(|m| m.is_walkable())
218 .unwrap_or(true); if is_walkable {
221 walkable.push(face);
222 } else {
223 unwalkable.push(face);
224 }
225 }
226
227 bwm.faces = walkable;
229 let walkable_count = bwm.faces.len();
230 bwm.faces.extend(unwalkable);
231
232 for _ in 0..walkable_count {
235 bwm.adjacencies.push(super::BwmAdjacency {
236 edge_refs: [-1, -1, -1],
237 });
238 }
239
240 for (min, max, face_idx) in aabb_nodes {
242 bwm.aabb_nodes.push(BwmAabbNode {
243 bb_min: min,
244 bb_max: max,
245 face_index: u32::try_from(face_idx).expect("AABB face index is non-negative"),
246 unknown: 4,
247 split_axis: 0,
248 left_child: 0xFFFFFFFF,
249 right_child: 0xFFFFFFFF,
250 });
251 }
252
253 if !bwm.aabb_nodes.is_empty() {
254 bwm.walkmesh_type = BwmTypeCode::from(BwmType::AreaModel);
255 }
256
257 Ok(bwm)
258}
259
260fn face_normal_and_distance(a: BwmVec3, b: BwmVec3, c: BwmVec3) -> (BwmVec3, f32) {
263 let ab = BwmVec3::new(b.x - a.x, b.y - a.y, b.z - a.z);
264 let ac = BwmVec3::new(c.x - a.x, c.y - a.y, c.z - a.z);
265 let nx = ab.y * ac.z - ab.z * ac.y;
266 let ny = ab.z * ac.x - ab.x * ac.z;
267 let nz = ab.x * ac.y - ab.y * ac.x;
268 let len = (nx * nx + ny * ny + nz * nz).sqrt();
269 if len > 0.0 {
270 let n = BwmVec3::new(nx / len, ny / len, nz / len);
271 let d = n.x * a.x + n.y * a.y + n.z * a.z;
272 (n, d)
273 } else {
274 (BwmVec3::new(0.0, 0.0, 1.0), 0.0)
276 }
277}
278
279fn parse_vec3(parts: &[&str]) -> Result<BwmVec3, BwmAsciiError> {
280 let x = f32::from_str(parts[0]).map_err(|_| BwmAsciiError::Parse("x".into()))?;
281 let y = f32::from_str(parts[1]).map_err(|_| BwmAsciiError::Parse("y".into()))?;
282 let z = f32::from_str(parts[2]).map_err(|_| BwmAsciiError::Parse("z".into()))?;
283 Ok(BwmVec3::new(x, y, z))
284}
285
286fn parse_aabb_node(line: &str) -> Result<(BwmVec3, BwmVec3, i32), BwmAsciiError> {
287 let parts: Vec<&str> = line.split_whitespace().collect();
288 if parts.len() < 7 {
289 return Err(BwmAsciiError::Parse("aabb fields".into()));
290 }
291 let min = parse_vec3(&parts[0..3])?;
292 let max = parse_vec3(&parts[3..6])?;
293 let face: i32 = parts[6]
294 .parse()
295 .map_err(|_| BwmAsciiError::Parse("aabb face".into()))?;
296
297 let epsilon = 0.01;
299 let bb_min = BwmVec3::new(min.x - epsilon, min.y - epsilon, min.z - epsilon);
300 let bb_max = BwmVec3::new(max.x + epsilon, max.y + epsilon, max.z + epsilon);
301
302 Ok((bb_min, bb_max, face))
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::bwm::ascii_writer::write_bwm_ascii;
309 use std::io::Cursor;
310
311 #[test]
312 fn test_roundtrip_ascii() {
313 let mut bwm = Bwm::new();
314 bwm.walkmesh_type = BwmTypeCode::from(BwmType::AreaModel);
315 bwm.vertices.push(BwmVec3::new(0.0, 0.0, 0.0));
316 bwm.vertices.push(BwmVec3::new(10.0, 0.0, 0.0));
317 bwm.vertices.push(BwmVec3::new(0.0, 10.0, 0.0));
318 bwm.faces.push(BwmFace {
319 vertex_indices: [0, 1, 2],
320 material_id: 1, normal: BwmVec3::new(0.0, 0.0, 1.0),
322 planar_distance: 0.0,
323 });
324
325 let mut buffer = Vec::new();
326 write_bwm_ascii(&mut buffer, &bwm).expect("write failed");
327
328 let text = String::from_utf8(buffer.clone()).expect("utf8");
329 println!("Generated ASCII:\n{}", text);
330
331 let mut cursor = Cursor::new(buffer);
332 let parsed = read_bwm_ascii(&mut cursor).expect("read failed");
333
334 assert_eq!(parsed.vertices.len(), 3);
335 assert_eq!(parsed.faces.len(), 1);
336 assert_eq!(parsed.faces[0].material_id, 1);
337
338 assert!((parsed.vertices[1].x - 10.0).abs() < 0.001);
340 }
341}