use bevy::asset::RenderAssetUsages;
use bevy::math::Vec3;
use bevy::mesh::{Indices, Mesh, PrimitiveTopology};
#[derive(Debug, Clone, Copy)]
pub struct SingleVariableFunctionMesh {
pub f1: fn(f32) -> f32,
pub f1_x_start: f32,
pub f1_x_end: f32,
pub f1_vertices: usize,
pub f2: fn(f32) -> f32,
pub f2_x_start: f32,
pub f2_x_end: f32,
pub f2_vertices: usize,
}
impl Default for SingleVariableFunctionMesh {
fn default() -> Self {
SingleVariableFunctionMesh {
f1: |_x: f32| -> f32 { 1.0 },
f1_x_start: -1.0,
f1_x_end: 1.0,
f1_vertices: 18,
f2: |_x: f32| -> f32 { 1.0 },
f2_x_start: -1.0,
f2_x_end: 1.0,
f2_vertices: 18,
}
}
}
impl From<SingleVariableFunctionMesh> for Mesh {
fn from(mathfunction: SingleVariableFunctionMesh) -> Self {
debug_assert!(mathfunction.f1_x_start <= mathfunction.f1_x_end);
debug_assert!(mathfunction.f2_x_start <= mathfunction.f2_x_end);
let (ring_horizontal, maximum1) = calculate_ring_of_vertices(
mathfunction.f1,
mathfunction.f1_x_start,
mathfunction.f1_x_end,
mathfunction.f1_vertices,
true,
);
let (ring_vertical, _) = calculate_ring_of_vertices(
mathfunction.f2,
mathfunction.f2_x_start,
mathfunction.f2_x_end,
mathfunction.f2_vertices,
false,
);
let width_maximum = mathfunction.f1_x_end - mathfunction.f1_x_start;
let amount = ring_horizontal.len();
let mut amount_layers = mathfunction.f2_vertices - 1;
if mathfunction.f2_x_start == mathfunction.f2_x_end {
amount_layers = 1;
}
let mut vertices: Vec<([f32; 3], [f32; 3], [f32; 2])> =
Vec::with_capacity(amount * amount_layers + 2);
vertices.push((
[0.0, mathfunction.f2_x_start, 0.0],
[0.0, -1.0, 0.0],
[0.5, 0.5],
));
for i in ring_vertical.iter().take(amount_layers) {
for (k, j) in ring_horizontal.iter().enumerate().take(amount) {
let (mut x, mut z) = (j.x, j.y);
if amount_layers > 1 {
(x, z) = (x.signum() * (x.abs() * i.y), z.signum() * (z.abs() * i.y));
}
let y = i.x;
let mut normal_horizontally =
Vec3::new(-j.slope_in_percentage.tan(), 0.0, 1.0).normalize();
if k >= amount / 2 {
normal_horizontally[2] = -normal_horizontally[2];
}
let normal_vertical = Vec3::new(1.0, -i.slope_in_percentage.tan(), 1.0).normalize();
let mut normals = [
normal_horizontally[0] / 3.0 * 2.0,
normal_vertical[1],
normal_horizontally[2] / 3.0 * 2.0,
];
if amount_layers == 1 {
normals = [0.0, 1.0, 0.0];
}
let uv_x = (x + mathfunction.f1_x_start.abs()) / width_maximum;
let uv_y = (z + maximum1) / (maximum1 * 2.0);
vertices.push(([x, y, z], normals, [uv_x, uv_y]));
}
}
vertices.push((
[0.0, mathfunction.f2_x_end, 0.0],
[0.0, 1.0, 0.0],
[0.5, 0.5],
));
let mut indeces: Vec<u32> = Vec::with_capacity((amount * amount_layers + 2) * 3);
for i in 0..amount {
if amount_layers > 1 {
indeces.append(&mut vec![
((i + 1) % amount + 1).try_into().unwrap(),
(i + 1).try_into().unwrap(),
0,
]);
}
indeces.append(&mut vec![
((amount_layers - 1) * amount + i + 1).try_into().unwrap(),
((amount_layers - 1) * amount + (i + 1) % amount + 1)
.try_into()
.unwrap(),
(amount_layers * amount + 1).try_into().unwrap(),
]);
}
for segment in 1..amount_layers {
for i in 0..amount {
let tl = (segment * amount + i + 1) as u32;
let mut tr = (segment * amount + i + 2) as u32;
let bl = ((segment - 1) * amount + i + 1) as u32;
let mut br = ((segment - 1) * amount + i + 2) as u32;
if i == amount - 1 {
tr = (segment * amount + 1) as u32;
br = ((segment - 1) * amount + 1) as u32;
}
indeces.append(&mut vec![br, tr, tl]);
indeces.append(&mut vec![bl, br, tl]);
}
}
let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect();
let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect();
let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect();
let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
);
mesh.insert_indices(Indices::U32(indeces));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh
}
}
#[derive(Copy, Clone, Debug)]
struct Position {
x: f32,
y: f32,
slope_in_percentage: f32,
}
fn calculate_ring_of_vertices(
f: fn(f32) -> f32,
x_start: f32,
x_end: f32,
vertices: usize,
generate_lower_half: bool,
) -> (Vec<Position>, f32) {
let delta = 0.000001;
let start = Position {
x: x_start,
y: f(x_start),
slope_in_percentage: ((f(x_start + delta) - f(x_start)) / (delta)).atan(),
};
let end = Position {
x: x_end,
y: f(x_end),
slope_in_percentage: ((f(x_end) - f(x_end - delta)) / (delta)).atan(),
};
let mut vec: Vec<Position> = Vec::with_capacity(vertices);
let mut maximum = 0.0;
vec.push(start);
vec.push(end);
for _ in 2..vertices {
let (mut index, mut max_slope_difference, mut max_x_difference) = (1, 0.0, 0.0);
for j in 1..vec.len() {
let new_x = vec[j - 1].x + (vec[j].x - vec[j - 1].x) / 2.0;
let new_m = ((f(new_x + delta) - f(new_x)) / (delta)).atan();
let x_difference = vec[j].x - vec[j - 1].x;
let slope_difference = (new_m - vec[j].slope_in_percentage).abs()
+ (new_m - vec[j - 1].slope_in_percentage).abs();
if slope_difference > max_slope_difference
|| (slope_difference == max_slope_difference && x_difference > max_x_difference)
{
(index, max_slope_difference, max_x_difference) =
(j, slope_difference, x_difference);
}
}
let new_x = vec[index - 1].x + (vec[index].x - vec[index - 1].x) / 2.0;
vec.insert(
index,
Position {
x: new_x,
y: f(new_x),
slope_in_percentage: ((f(new_x + delta) - f(new_x)) / (delta)).atan(),
},
);
if f(new_x) > maximum {
maximum = f(new_x);
}
}
if !generate_lower_half {
return (vec, maximum);
}
let mut lower_half = vec.clone();
if f(lower_half[0].x) != 0.0 {
lower_half.remove(0);
}
lower_half.reverse();
if f(lower_half[0].x) != 0.0 {
lower_half.remove(0);
}
for vertex in &lower_half {
vec.push(Position {
x: vertex.x,
y: -vertex.y,
slope_in_percentage: vertex.slope_in_percentage,
});
}
(vec, maximum)
}
#[cfg(test)]
mod tests {
use super::*;
fn circle(x: f32) -> f32 {
(1.0 - x.powf(2.0)).powf(0.5)
}
fn square(_x: f32) -> f32 {
1.0
}
#[test]
fn test_amount_of_vertices() {
let square_2d_mesh: Mesh = SingleVariableFunctionMesh {
f1: square,
f1_x_start: -1.0,
f1_x_end: 1.0,
f1_vertices: 20,
f2: square,
f2_x_start: 0.0,
f2_x_end: 0.0,
f2_vertices: 20,
}
.into();
let circle_2d_mesh: Mesh = SingleVariableFunctionMesh {
f1: circle,
f1_x_start: -1.0,
f1_x_end: 1.0,
f1_vertices: 20,
f2: circle,
f2_x_start: 0.0,
f2_x_end: 0.0,
f2_vertices: 20,
}
.into();
assert_eq!(
square_2d_mesh.count_vertices(),
circle_2d_mesh.count_vertices() - 2
);
}
}