Skip to main content

rakata_formats/bwm/
ascii_writer.rs

1//! BWM ASCII writer implementation.
2//!
3//! This module provides an ASCII serializer for the KotOR walkmesh format,
4//! compatible with the game's `LoadMeshText` parser.
5
6use std::io::Write;
7
8use super::ascii_reader::BwmAsciiError;
9use super::{Bwm, BwmType};
10
11/// Writes a BWM to an ASCII writer.
12///
13/// # Errors
14///
15/// [`BwmAsciiError::Io`] when the writer fails. The ASCII grammar has no
16/// widths to overflow and the adjacency columns are written as `-1`
17/// placeholders, so nothing in the walkmesh itself can be refused here.
18pub fn write_bwm_ascii<W: Write>(writer: &mut W, bwm: &Bwm) -> Result<(), BwmAsciiError> {
19    writeln!(writer, "node aabb")?;
20
21    // Position
22    writeln!(
23        writer,
24        "  position {} {} {}",
25        bwm.position.x, bwm.position.y, bwm.position.z
26    )?;
27
28    // Identity, since the binary form carries no orientation to round-trip.
29    writeln!(writer, "  orientation 0.0 0.0 0.0 1.0")?;
30
31    // Vertices
32    writeln!(writer, "  verts {}", bwm.vertices.len())?;
33    for v in &bwm.vertices {
34        writeln!(writer, "    {} {} {}", v.x, v.y, v.z)?;
35    }
36
37    // Faces
38    writeln!(writer, "  faces {}", bwm.faces.len())?;
39    for face in &bwm.faces {
40        // Output format: v1 v2 v3 adj1 adj2 adj3 adj4 material
41        // ASCII format requires 4 adjacency indices, but the engine recomputes
42        // them, so `-1` placeholders go out instead.
43
44        writeln!(
45            writer,
46            "    {} {} {} -1 -1 -1 -1 {}",
47            face.vertex_indices[0],
48            face.vertex_indices[1],
49            face.vertex_indices[2],
50            face.material_id
51        )?;
52    }
53
54    // AABB
55    // Only if AreaModel
56    if bwm.walkmesh_type.known() == Some(BwmType::AreaModel) && !bwm.aabb_nodes.is_empty() {
57        writeln!(writer, "  aabb")?;
58        for node in &bwm.aabb_nodes {
59            // Output only leaf nodes (containing faces) to the ASCII AABB list.
60            // The game rebuilds the tree structure from these leaves on load.
61            if node.face_index != 0xFFFFFFFF {
62                // Write raw bounding box values. The game applies epsilon expansion on load.
63                writeln!(
64                    writer,
65                    "    {} {} {} {} {} {} {}",
66                    node.bb_min.x,
67                    node.bb_min.y,
68                    node.bb_min.z,
69                    node.bb_max.x,
70                    node.bb_max.y,
71                    node.bb_max.z,
72                    node.face_index
73                )?;
74            }
75        }
76    }
77
78    writeln!(writer, "endnode")?;
79
80    Ok(())
81}