Skip to main content

rakata_formats/bwm/
writer.rs

1//! BWM binary writer.
2
3use std::io::{Cursor, Write};
4
5use super::{
6    binary::{write_f32, write_i32, write_u32},
7    checked_add, checked_mul, usize_to_u32, Bwm, BwmBinaryError, BwmVec3, AABB_ENTRY_SIZE,
8    ADJACENCY_ENTRY_SIZE, BWM_MAGIC, BWM_VERSION_V10, DISTANCE_ENTRY_SIZE, EDGE_ENTRY_SIZE,
9    FACE_INDEX_ENTRY_SIZE, FILE_HEADER_SIZE, MATERIAL_ENTRY_SIZE, NORMAL_ENTRY_SIZE,
10    VERTEX_ENTRY_SIZE,
11};
12
13/// Writes BWM data to a writer.
14///
15/// # Errors
16///
17/// [`BwmBinaryError::InvalidData`] when a face names a vertex index past the
18/// end of the vertex list, checked for every face before anything is written,
19/// and [`BwmBinaryError::ValueOverflow`] when a section count or the size it
20/// implies will not fit the `u32` the header gives it. [`BwmBinaryError::Io`]
21/// comes from the writer and can leave a partial file.
22#[cfg_attr(
23    feature = "tracing",
24    tracing::instrument(level = "debug", skip(writer, bwm))
25)]
26pub fn write_bwm<W: Write>(writer: &mut W, bwm: &Bwm) -> Result<(), BwmBinaryError> {
27    let vertex_count = bwm.vertices.len();
28    let face_count = bwm.faces.len();
29    let aabb_count = bwm.aabb_nodes.len();
30    let adjacency_count = bwm.adjacencies.len();
31    let edge_count = bwm.edges.len();
32    let perimeter_count = bwm.perimeters.len();
33
34    for (index, face) in bwm.faces.iter().enumerate() {
35        for (slot, vertex_index) in face.vertex_indices.iter().enumerate() {
36            let vertex_index = crate::binary::checked_to_usize(*vertex_index, "face_vertex_index")?;
37            if vertex_index >= vertex_count {
38                return Err(BwmBinaryError::InvalidData(format!(
39                    "faces[{index}].vertex_indices[{slot}] out of bounds for {vertex_count} vertices"
40                )));
41            }
42        }
43    }
44
45    // --- Compute contiguous table offsets (each table follows the previous) ---
46    let vertex_offset = FILE_HEADER_SIZE;
47    let vertex_size = checked_mul(vertex_count, VERTEX_ENTRY_SIZE, "vertex_size")?;
48    let face_indices_offset = checked_add(vertex_offset, vertex_size, "face_indices_offset")?;
49    let face_indices_size = checked_mul(face_count, FACE_INDEX_ENTRY_SIZE, "face_indices_size")?;
50    let materials_offset = checked_add(face_indices_offset, face_indices_size, "materials_offset")?;
51    let materials_size = checked_mul(face_count, MATERIAL_ENTRY_SIZE, "materials_size")?;
52    let normals_offset = checked_add(materials_offset, materials_size, "normals_offset")?;
53    let normals_size = checked_mul(face_count, NORMAL_ENTRY_SIZE, "normals_size")?;
54    let planar_distances_offset =
55        checked_add(normals_offset, normals_size, "planar_distances_offset")?;
56    let distances_size = checked_mul(face_count, DISTANCE_ENTRY_SIZE, "distances_size")?;
57    let aabb_offset = checked_add(planar_distances_offset, distances_size, "aabb_offset")?;
58    let aabb_size = checked_mul(aabb_count, AABB_ENTRY_SIZE, "aabb_size")?;
59    let adjacency_offset = checked_add(aabb_offset, aabb_size, "adjacency_offset")?;
60    let adjacency_size = checked_mul(adjacency_count, ADJACENCY_ENTRY_SIZE, "adjacency_size")?;
61    let edges_offset = checked_add(adjacency_offset, adjacency_size, "edges_offset")?;
62    let edge_size = checked_mul(edge_count, EDGE_ENTRY_SIZE, "edge_size")?;
63    let perimeters_offset = checked_add(edges_offset, edge_size, "perimeters_offset")?;
64
65    // --- Write header: magic + version (0x00..0x08) ---
66    writer.write_all(&BWM_MAGIC)?;
67    writer.write_all(&BWM_VERSION_V10)?;
68
69    // --- Write header: properties (0x08..0x48) ---
70    write_u32(writer, bwm.walkmesh_type.raw())?; // 0x08
71    write_vec3(writer, bwm.relative_hook1)?; // 0x0C
72    write_vec3(writer, bwm.relative_hook2)?; // 0x18
73    write_vec3(writer, bwm.absolute_hook1)?; // 0x24
74    write_vec3(writer, bwm.absolute_hook2)?; // 0x30
75    write_vec3(writer, bwm.position)?; // 0x3C
76
77    // --- Write header: table counts and offsets (0x48..0x88) ---
78    write_u32(writer, usize_to_u32(vertex_count, "vertex_count")?)?; // 0x48
79    write_u32(writer, usize_to_u32(vertex_offset, "vertex_offset")?)?; // 0x4C
80    write_u32(writer, usize_to_u32(face_count, "face_count")?)?; // 0x50
81    write_u32(
82        writer,
83        usize_to_u32(face_indices_offset, "face_indices_offset")?,
84    )?; // 0x54
85    write_u32(writer, usize_to_u32(materials_offset, "materials_offset")?)?; // 0x58
86    write_u32(writer, usize_to_u32(normals_offset, "normals_offset")?)?; // 0x5C
87    write_u32(
88        writer,
89        usize_to_u32(planar_distances_offset, "planar_distances_offset")?,
90    )?; // 0x60
91    write_u32(writer, usize_to_u32(aabb_count, "aabb_count")?)?; // 0x64
92    write_u32(writer, usize_to_u32(aabb_offset, "aabb_offset")?)?; // 0x68
93    write_u32(writer, bwm.unknown)?; // 0x6C
94    write_u32(writer, usize_to_u32(adjacency_count, "adjacency_count")?)?; // 0x70
95    write_u32(writer, usize_to_u32(adjacency_offset, "adjacency_offset")?)?; // 0x74
96    write_u32(writer, usize_to_u32(edge_count, "edge_count")?)?; // 0x78
97    write_u32(writer, usize_to_u32(edges_offset, "edges_offset")?)?; // 0x7C
98    write_u32(writer, usize_to_u32(perimeter_count, "perimeter_count")?)?; // 0x80
99    write_u32(
100        writer,
101        usize_to_u32(perimeters_offset, "perimeters_offset")?,
102    )?; // 0x84
103
104    // --- Write vertex table ---
105    for vertex in &bwm.vertices {
106        write_vec3(writer, *vertex)?;
107    }
108
109    // --- Write face tables (indices, materials, normals, planar distances) ---
110    for face in &bwm.faces {
111        write_u32(writer, face.vertex_indices[0])?;
112        write_u32(writer, face.vertex_indices[1])?;
113        write_u32(writer, face.vertex_indices[2])?;
114    }
115    for face in &bwm.faces {
116        write_u32(writer, face.material_id)?;
117    }
118    for face in &bwm.faces {
119        write_vec3(writer, face.normal)?;
120    }
121    for face in &bwm.faces {
122        write_f32(writer, face.planar_distance)?;
123    }
124
125    // --- Write AABB tree nodes ---
126    for node in &bwm.aabb_nodes {
127        write_vec3(writer, node.bb_min)?;
128        write_vec3(writer, node.bb_max)?;
129        write_u32(writer, node.face_index)?;
130        write_u32(writer, node.unknown)?;
131        write_u32(writer, node.split_axis)?;
132        write_u32(writer, node.left_child)?;
133        write_u32(writer, node.right_child)?;
134    }
135
136    // --- Write adjacency, edge, and perimeter tables ---
137    for adjacency in &bwm.adjacencies {
138        write_i32(writer, adjacency.edge_refs[0])?;
139        write_i32(writer, adjacency.edge_refs[1])?;
140        write_i32(writer, adjacency.edge_refs[2])?;
141    }
142    for edge in &bwm.edges {
143        write_i32(writer, edge.edge_index)?;
144        write_i32(writer, edge.transition)?;
145    }
146    for perimeter in &bwm.perimeters {
147        write_u32(writer, *perimeter)?;
148    }
149
150    Ok(())
151}
152
153/// Serializes BWM data to a byte vector.
154///
155/// # Errors
156///
157/// [`BwmBinaryError::InvalidData`] and [`BwmBinaryError::ValueOverflow`] on
158/// the terms [`write_bwm`] gives. The `Vec` target has no I/O to fail at.
159#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(bwm)))]
160pub fn write_bwm_to_vec(bwm: &Bwm) -> Result<Vec<u8>, BwmBinaryError> {
161    let mut cursor = Cursor::new(Vec::new());
162    write_bwm(&mut cursor, bwm)?;
163    Ok(cursor.into_inner())
164}
165
166fn write_vec3<W: Write>(writer: &mut W, value: BwmVec3) -> Result<(), BwmBinaryError> {
167    write_f32(writer, value.x)?;
168    write_f32(writer, value.y)?;
169    write_f32(writer, value.z)?;
170    Ok(())
171}