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