bevy_single_variable_function_mesh/
lib.rs

1use bevy::math::Vec3;
2use bevy::render::mesh::{Indices, Mesh, PrimitiveTopology};
3use bevy::render::render_asset::RenderAssetUsages;
4
5/// A 2D or 3D mesh (`bevy::render::mesh::Mesh`) generated from a single-variable function
6/// `f(f32) -> f32`.
7#[derive(Debug, Clone, Copy)]
8pub struct SingleVariableFunctionMesh {
9    /// The function to be used as the upper half of the generated polygon.
10    /// The function will be mirrored to the x-axis to generate the lower half of the polygon.
11    /// If the mesh is 3D, the function will also be applied to the
12    /// side vertices. Default = squircle.
13    pub f1: fn(f32) -> f32,
14    /// `f1` starts here. Together with `x_end`, this determines the size of the mesh.
15    /// Must be lower than `x_end`. Default = -1.0.
16    pub f1_x_start: f32,
17    /// `f1` ends here. Together with `x_start`, this determines the size of the mesh.
18    /// Must be bigger than `x_start`. Default = 1.0.
19    pub f1_x_end: f32,
20    /// The amount of vertices that are used for each upper half of the polygon.
21    /// Should be at least 3. There will be (n - 2) * (2 * n - 2) + 2 vertices in total
22    /// if 3D.  Default = 30.0.
23    pub f1_vertices: usize,
24    pub f2: fn(f32) -> f32,
25    pub f2_x_start: f32,
26    pub f2_x_end: f32,
27    pub f2_vertices: usize,
28}
29
30impl Default for SingleVariableFunctionMesh {
31    fn default() -> Self {
32        SingleVariableFunctionMesh {
33            f1: |_x: f32| -> f32 { 1.0 },
34            f1_x_start: -1.0,
35            f1_x_end: 1.0,
36            f1_vertices: 18,
37            f2: |_x: f32| -> f32 { 1.0 },
38            f2_x_start: -1.0,
39            f2_x_end: 1.0,
40            f2_vertices: 18,
41        }
42    }
43}
44
45impl From<SingleVariableFunctionMesh> for Mesh {
46    fn from(mathfunction: SingleVariableFunctionMesh) -> Self {
47        debug_assert!(mathfunction.f1_x_start <= mathfunction.f1_x_end);
48        debug_assert!(mathfunction.f2_x_start <= mathfunction.f2_x_end);
49        let (ring_horizontal, maximum1) = calculate_ring_of_vertices(
50            mathfunction.f1,
51            mathfunction.f1_x_start,
52            mathfunction.f1_x_end,
53            mathfunction.f1_vertices,
54            true,
55        );
56        let (ring_vertical, _) = calculate_ring_of_vertices(
57            mathfunction.f2,
58            mathfunction.f2_x_start,
59            mathfunction.f2_x_end,
60            mathfunction.f2_vertices,
61            false,
62        );
63        let width_maximum = mathfunction.f1_x_end - mathfunction.f1_x_start;
64        let amount = ring_horizontal.len();
65        let mut amount_layers = mathfunction.f2_vertices - 1;
66        if mathfunction.f2_x_start == mathfunction.f2_x_end {
67            amount_layers = 1;
68        }
69
70        let mut vertices: Vec<([f32; 3], [f32; 3], [f32; 2])> =
71            Vec::with_capacity(amount * amount_layers + 2);
72        vertices.push((
73            [0.0, mathfunction.f2_x_start, 0.0],
74            [0.0, -1.0, 0.0],
75            [0.5, 0.5],
76        ));
77        for i in ring_vertical.iter().take(amount_layers) {
78            for (k, j) in ring_horizontal.iter().enumerate().take(amount) {
79                // Place vertices.
80                let (mut x, mut z) = (j.x, j.y);
81                if amount_layers > 1 {
82                    (x, z) = (x.signum() * (x.abs() * i.y), z.signum() * (z.abs() * i.y));
83                }
84                let y = i.x;
85
86                // Create normals.
87                let mut normal_horizontally =
88                    Vec3::new(-j.slope_in_percentage.tan(), 0.0, 1.0).normalize();
89
90                if k >= amount / 2 {
91                    normal_horizontally[2] = -normal_horizontally[2];
92                }
93                let normal_vertical = Vec3::new(1.0, -i.slope_in_percentage.tan(), 1.0).normalize();
94                let mut normals = [
95                    normal_horizontally[0] / 3.0 * 2.0,
96                    normal_vertical[1],
97                    normal_horizontally[2] / 3.0 * 2.0,
98                ];
99                if amount_layers == 1 {
100                    normals = [0.0, 1.0, 0.0];
101                }
102                let uv_x = (x + mathfunction.f1_x_start.abs()) / width_maximum;
103                let uv_y = (z + maximum1) / (maximum1 * 2.0);
104                vertices.push(([x, y, z], normals, [uv_x, uv_y]));
105            }
106        }
107        vertices.push((
108            [0.0, mathfunction.f2_x_end, 0.0],
109            [0.0, 1.0, 0.0],
110            [0.5, 0.5],
111        ));
112
113        // Create faces (indeces).
114        let mut indeces: Vec<u32> = Vec::with_capacity((amount * amount_layers + 2) * 3);
115        for i in 0..amount {
116            if amount_layers > 1 {
117                indeces.append(&mut vec![
118                    ((i + 1) % amount + 1).try_into().unwrap(),
119                    (i + 1).try_into().unwrap(),
120                    0,
121                ]);
122            }
123            indeces.append(&mut vec![
124                ((amount_layers - 1) * amount + i + 1).try_into().unwrap(),
125                ((amount_layers - 1) * amount + (i + 1) % amount + 1)
126                    .try_into()
127                    .unwrap(),
128                (amount_layers * amount + 1).try_into().unwrap(),
129            ]);
130        }
131        for segment in 1..amount_layers {
132            for i in 0..amount {
133                let tl = (segment * amount + i + 1) as u32;
134                let mut tr = (segment * amount + i + 2) as u32;
135                let bl = ((segment - 1) * amount + i + 1) as u32;
136                let mut br = ((segment - 1) * amount + i + 2) as u32;
137                if i == amount - 1 {
138                    tr = (segment * amount + 1) as u32;
139                    br = ((segment - 1) * amount + 1) as u32;
140                }
141                indeces.append(&mut vec![br, tr, tl]);
142                indeces.append(&mut vec![bl, br, tl]);
143            }
144        }
145
146        let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect();
147        let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect();
148        let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect();
149        let mut mesh = Mesh::new(
150            PrimitiveTopology::TriangleList,
151            RenderAssetUsages::default(),
152        );
153        mesh.insert_indices(Indices::U32(indeces));
154        mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
155        mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
156        mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
157        mesh
158    }
159}
160
161#[derive(Copy, Clone, Debug)]
162struct Position {
163    x: f32,
164    y: f32,
165    slope_in_percentage: f32,
166}
167
168fn calculate_ring_of_vertices(
169    f: fn(f32) -> f32,
170    x_start: f32,
171    x_end: f32,
172    vertices: usize,
173    generate_lower_half: bool,
174) -> (Vec<Position>, f32) {
175    let delta = 0.000001;
176    let start = Position {
177        x: x_start,
178        y: f(x_start),
179        slope_in_percentage: ((f(x_start + delta) - f(x_start)) / (delta)).atan(),
180    };
181    let end = Position {
182        x: x_end,
183        y: f(x_end),
184        slope_in_percentage: ((f(x_end) - f(x_end - delta)) / (delta)).atan(),
185    };
186    let mut vec: Vec<Position> = Vec::with_capacity(vertices);
187    let mut maximum = 0.0;
188    vec.push(start);
189    vec.push(end);
190    for _ in 2..vertices {
191        let (mut index, mut max_slope_difference, mut max_x_difference) = (1, 0.0, 0.0);
192        for j in 1..vec.len() {
193            let new_x = vec[j - 1].x + (vec[j].x - vec[j - 1].x) / 2.0;
194            let new_m = ((f(new_x + delta) - f(new_x)) / (delta)).atan();
195            let x_difference = vec[j].x - vec[j - 1].x;
196            let slope_difference = (new_m - vec[j].slope_in_percentage).abs()
197                + (new_m - vec[j - 1].slope_in_percentage).abs();
198            if slope_difference > max_slope_difference
199                || (slope_difference == max_slope_difference && x_difference > max_x_difference)
200            {
201                (index, max_slope_difference, max_x_difference) =
202                    (j, slope_difference, x_difference);
203            }
204        }
205        let new_x = vec[index - 1].x + (vec[index].x - vec[index - 1].x) / 2.0;
206        vec.insert(
207            index,
208            Position {
209                x: new_x,
210                y: f(new_x),
211                slope_in_percentage: ((f(new_x + delta) - f(new_x)) / (delta)).atan(),
212            },
213        );
214        if f(new_x) > maximum {
215            maximum = f(new_x);
216        }
217    }
218    if !generate_lower_half {
219        return (vec, maximum);
220    }
221    let mut lower_half = vec.clone();
222    if f(lower_half[0].x) != 0.0 {
223        lower_half.remove(0);
224    }
225    lower_half.reverse();
226    if f(lower_half[0].x) != 0.0 {
227        lower_half.remove(0);
228    }
229    for vertex in &lower_half {
230        vec.push(Position {
231            x: vertex.x,
232            y: -vertex.y,
233            slope_in_percentage: vertex.slope_in_percentage,
234        });
235    }
236    (vec, maximum)
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    fn square(_x: f32) -> f32 {
244        1.0
245    }
246
247    #[test]
248    fn test_amount_of_vertices() {
249        let square_2d_mesh: Mesh = SingleVariableFunctionMesh {
250            f1: square,
251            f1_x_start: -1.0,
252            f1_x_end: 1.0,
253            f1_vertices: 20,
254            f2: square,
255            f2_x_start: 0.0,
256            f2_x_end: 0.0,
257            f2_vertices: 20,
258        }
259        .into();
260        let circle_2d_mesh: Mesh = SingleVariableFunctionMesh {
261            f1: circle,
262            f1_x_start: -1.0,
263            f1_x_end: 1.0,
264            f1_vertices: 20,
265            f2: circle,
266            f2_x_start: 0.0,
267            f2_x_end: 0.0,
268            f2_vertices: 20,
269        }
270        .into();
271        assert_eq!(
272            square_2d_mesh.count_vertices(),
273            circle_2d_mesh.count_vertices() - 2
274        );
275    }
276}