Skip to main content

box3d_rust/height_field/
factory.rs

1//! Height field factory helpers: grid, wave, dump, load.
2//!
3//! SPDX-FileCopyrightText: 2026 Erin Catto
4//! SPDX-License-Identifier: MIT
5
6use super::create::create_height_field;
7use super::types::{HeightFieldData, HeightFieldDef, HEIGHT_FIELD_HOLE};
8use crate::math_functions::{Vec3, PI};
9use std::fs::File;
10use std::io::{BufRead, BufReader, Write};
11use std::path::Path;
12
13/// Create a flat height field grid. (b3CreateGrid)
14pub fn create_grid(
15    row_count: i32,
16    column_count: i32,
17    scale: Vec3,
18    make_holes: bool,
19) -> HeightFieldData {
20    let height_count = (row_count * column_count) as usize;
21    let heights = vec![0.0f32; height_count];
22
23    let cell_count = ((row_count - 1) * (column_count - 1)) as usize;
24    let mut material_indices = vec![0u8; cell_count];
25
26    for i in 0..row_count - 1 {
27        for j in 0..column_count - 1 {
28            let k = (i * (column_count - 1) + j) as usize;
29            if make_holes && k > 0 && k % 16 == 0 {
30                material_indices[k] = HEIGHT_FIELD_HOLE;
31            }
32        }
33    }
34
35    let def = HeightFieldDef {
36        heights,
37        material_indices,
38        scale,
39        count_x: column_count,
40        count_z: row_count,
41        global_minimum_height: -256.0,
42        global_maximum_height: 256.0,
43        clockwise_winding: false,
44    };
45
46    create_height_field(&def)
47}
48
49/// Create a sinusoidal wave height field. (b3CreateWave)
50///
51/// Uses `f32::sin` (C `sinf`) to match the reference helpers, not Box3D's
52/// deterministic `sin` approximation.
53pub fn create_wave(
54    row_count: i32,
55    column_count: i32,
56    scale: Vec3,
57    row_frequency: f32,
58    column_frequency: f32,
59    make_holes: bool,
60) -> HeightFieldData {
61    let height_count = (row_count * column_count) as usize;
62    let mut heights = vec![0.0f32; height_count];
63
64    let omega_z = 2.0 * PI * row_frequency;
65    let omega_x = 2.0 * PI * column_frequency;
66
67    for i in 0..row_count {
68        let row_height = (omega_z * (i as f32)).sin();
69        for j in 0..column_count {
70            let k = (i * column_count + j) as usize;
71            let column_height = (omega_x * (j as f32)).sin();
72            heights[k] = row_height * column_height;
73        }
74    }
75
76    let cell_count = ((row_count - 1) * (column_count - 1)) as usize;
77    let mut material_indices = vec![0u8; cell_count];
78
79    for i in 0..row_count - 1 {
80        for j in 0..column_count - 1 {
81            let k = (i * (column_count - 1) + j) as usize;
82            if make_holes && k > 0 && k % 16 == 0 {
83                material_indices[k] = HEIGHT_FIELD_HOLE;
84            }
85        }
86    }
87
88    let def = HeightFieldDef {
89        heights,
90        material_indices,
91        scale,
92        count_x: column_count,
93        count_z: row_count,
94        global_minimum_height: -256.0,
95        global_maximum_height: 256.0,
96        clockwise_winding: false,
97    };
98
99    create_height_field(&def)
100}
101
102/// Dump height field definition to a text file. (b3DumpHeightData)
103pub fn dump_height_data(data: &HeightFieldDef, file_name: impl AsRef<Path>) {
104    let Ok(mut file) = File::create(file_name) else {
105        return;
106    };
107
108    let _ = writeln!(file, "{} {}", data.count_x, data.count_z);
109    let _ = writeln!(
110        file,
111        "{:.9} {:.9} {:.9}",
112        data.scale.x, data.scale.y, data.scale.z
113    );
114    let _ = writeln!(
115        file,
116        "{:.9} {:.9}",
117        data.global_minimum_height, data.global_maximum_height
118    );
119    let _ = writeln!(file, "{}", i32::from(data.clockwise_winding));
120
121    let height_count = (data.count_x * data.count_z) as usize;
122    for i in 0..height_count {
123        let _ = writeln!(file, "{:.9}", data.heights[i]);
124    }
125
126    let material_count = ((data.count_x - 1) * (data.count_z - 1)) as usize;
127    for i in 0..material_count {
128        let _ = writeln!(file, "{}", data.material_indices[i]);
129    }
130}
131
132/// Load a height field from a text file written by [`dump_height_data`].
133/// (b3LoadHeightField)
134pub fn load_height_field(file_name: impl AsRef<Path>) -> Option<HeightFieldData> {
135    let file = File::open(file_name).ok()?;
136    let mut lines = BufReader::new(file).lines().map_while(Result::ok);
137
138    let dim_line = lines.next()?;
139    let mut dim_parts = dim_line.split_whitespace();
140    let count_x: i32 = dim_parts.next()?.parse().ok()?;
141    let count_z: i32 = dim_parts.next()?.parse().ok()?;
142
143    let scale_line = lines.next()?;
144    let mut scale_parts = scale_line.split_whitespace();
145    let scale = Vec3 {
146        x: scale_parts.next()?.parse().ok()?,
147        y: scale_parts.next()?.parse().ok()?,
148        z: scale_parts.next()?.parse().ok()?,
149    };
150
151    let bounds_line = lines.next()?;
152    let mut bounds_parts = bounds_line.split_whitespace();
153    let global_minimum_height: f32 = bounds_parts.next()?.parse().ok()?;
154    let global_maximum_height: f32 = bounds_parts.next()?.parse().ok()?;
155
156    let clockwise_line = lines.next()?;
157    let clockwise: i32 = clockwise_line.trim().parse().ok()?;
158    let clockwise_winding = clockwise != 0;
159
160    let height_count = (count_x * count_z) as usize;
161    let mut heights = Vec::with_capacity(height_count);
162    for _ in 0..height_count {
163        let line = lines.next()?;
164        heights.push(line.trim().parse().ok()?);
165    }
166
167    let material_count = ((count_x - 1) * (count_z - 1)) as usize;
168    let mut material_indices = Vec::with_capacity(material_count);
169    for _ in 0..material_count {
170        let line = lines.next()?;
171        let material_index: i32 = line.trim().parse().ok()?;
172        material_indices.push(material_index as u8);
173    }
174
175    let def = HeightFieldDef {
176        heights,
177        material_indices,
178        scale,
179        count_x,
180        count_z,
181        global_minimum_height,
182        global_maximum_height,
183        clockwise_winding,
184    };
185
186    Some(create_height_field(&def))
187}