use serde::{Deserialize, Serialize};
use crate::texture3d::Material3D;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vec3 {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn zero() -> Self {
Self::new(0.0, 0.0, 0.0)
}
pub fn dot(&self, other: &Vec3) -> f32 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn cross(&self, other: &Vec3) -> Vec3 {
Vec3::new(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x,
)
}
pub fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalize(&self) -> Vec3 {
let len = self.length();
if len > 0.0 {
Vec3::new(self.x / len, self.y / len, self.z / len)
} else {
*self
}
}
pub fn distance(&self, other: &Vec3) -> f32 {
let dx = self.x - other.x;
let dy = self.y - other.y;
let dz = self.z - other.z;
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn lerp(&self, other: &Vec3, t: f32) -> Vec3 {
Vec3::new(
self.x + (other.x - self.x) * t,
self.y + (other.y - self.y) * t,
self.z + (other.z - self.z) * t,
)
}
}
impl std::ops::Add for Vec3 {
type Output = Vec3;
fn add(self, other: Vec3) -> Vec3 {
Vec3::new(self.x + other.x, self.y + other.y, self.z + other.z)
}
}
impl std::ops::Sub for Vec3 {
type Output = Vec3;
fn sub(self, other: Vec3) -> Vec3 {
Vec3::new(self.x - other.x, self.y - other.y, self.z - other.z)
}
}
impl std::ops::Mul<f32> for Vec3 {
type Output = Vec3;
fn mul(self, scalar: f32) -> Vec3 {
Vec3::new(self.x * scalar, self.y * scalar, self.z * scalar)
}
}
impl std::ops::Div<f32> for Vec3 {
type Output = Vec3;
fn div(self, scalar: f32) -> Vec3 {
Vec3::new(self.x / scalar, self.y / scalar, self.z / scalar)
}
}
impl std::ops::Neg for Vec3 {
type Output = Vec3;
fn neg(self) -> Vec3 {
Vec3::new(-self.x, -self.y, -self.z)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vertex {
pub position: Vec3,
pub normal: Vec3,
pub uv: (f32, f32),
pub color: [u8; 4],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mesh3D {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub position: Vec3,
pub rotation: Vec3,
pub scale: Vec3,
#[serde(default)]
pub material: Option<Material3D>,
#[serde(default = "default_true")]
pub cast_shadow: bool,
#[serde(default = "default_true")]
pub receive_shadow: bool,
}
fn default_true() -> bool {
true
}
impl Mesh3D {
pub fn new() -> Self {
Self {
vertices: Vec::new(),
indices: Vec::new(),
position: Vec3::zero(),
rotation: Vec3::zero(),
scale: Vec3::new(1.0, 1.0, 1.0),
material: None,
cast_shadow: true,
receive_shadow: true,
}
}
pub fn with_material(mut self, material: Material3D) -> Self {
self.material = Some(material);
self
}
pub fn set_material(&mut self, material: Material3D) {
self.material = Some(material);
}
pub fn finalize(vertices: Vec<Vertex>, indices: Vec<u32>) -> Self {
Self {
vertices,
indices,
position: Vec3::zero(),
rotation: Vec3::zero(),
scale: Vec3::new(1.0, 1.0, 1.0),
material: None,
cast_shadow: true,
receive_shadow: true,
}
}
pub fn cube(size: f32) -> Self {
let s = size / 2.0;
let vertices = vec![
Vertex { position: Vec3::new(-s, -s, s), normal: Vec3::new(0.0, 0.0, 1.0), uv: (0.0, 0.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(s, -s, s), normal: Vec3::new(0.0, 0.0, 1.0), uv: (1.0, 0.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(s, s, s), normal: Vec3::new(0.0, 0.0, 1.0), uv: (1.0, 1.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(-s, s, s), normal: Vec3::new(0.0, 0.0, 1.0), uv: (0.0, 1.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(s, -s, -s), normal: Vec3::new(0.0, 0.0, -1.0), uv: (0.0, 0.0), color: [200, 200, 200, 255] },
Vertex { position: Vec3::new(-s, -s, -s), normal: Vec3::new(0.0, 0.0, -1.0), uv: (1.0, 0.0), color: [200, 200, 200, 255] },
Vertex { position: Vec3::new(-s, s, -s), normal: Vec3::new(0.0, 0.0, -1.0), uv: (1.0, 1.0), color: [200, 200, 200, 255] },
Vertex { position: Vec3::new(s, s, -s), normal: Vec3::new(0.0, 0.0, -1.0), uv: (0.0, 1.0), color: [200, 200, 200, 255] },
];
let indices = vec![
0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 5, 0, 3, 3, 6, 5, 1, 4, 7, 7, 2, 1, 3, 2, 7, 7, 6, 3, 5, 4, 1, 1, 0, 5, ];
Self {
vertices,
indices,
position: Vec3::zero(),
rotation: Vec3::zero(),
scale: Vec3::new(1.0, 1.0, 1.0),
material: None,
cast_shadow: true,
receive_shadow: true,
}
}
pub fn sphere(radius: f32, segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for lat in 0..=segments {
let theta = lat as f32 * std::f32::consts::PI / segments as f32;
let sin_theta = theta.sin();
let cos_theta = theta.cos();
for lon in 0..=segments {
let phi = lon as f32 * 2.0 * std::f32::consts::PI / segments as f32;
let sin_phi = phi.sin();
let cos_phi = phi.cos();
let x = cos_phi * sin_theta;
let y = cos_theta;
let z = sin_phi * sin_theta;
vertices.push(Vertex {
position: Vec3::new(x * radius, y * radius, z * radius),
normal: Vec3::new(x, y, z),
uv: (lon as f32 / segments as f32, lat as f32 / segments as f32),
color: [255, 255, 255, 255],
});
}
}
for lat in 0..segments {
for lon in 0..segments {
let first = lat * (segments + 1) + lon;
let second = first + segments + 1;
indices.push(first);
indices.push(second);
indices.push(first + 1);
indices.push(second);
indices.push(second + 1);
indices.push(first + 1);
}
}
Self::finalize(vertices, indices)
}
pub fn plane(width: f32, height: f32) -> Self {
let hw = width / 2.0;
let hh = height / 2.0;
let vertices = vec![
Vertex {
position: Vec3::new(-hw, 0.0, -hh),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (0.0, 0.0),
color: [255, 255, 255, 255],
},
Vertex {
position: Vec3::new(hw, 0.0, -hh),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (1.0, 0.0),
color: [255, 255, 255, 255],
},
Vertex {
position: Vec3::new(hw, 0.0, hh),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (1.0, 1.0),
color: [255, 255, 255, 255],
},
Vertex {
position: Vec3::new(-hw, 0.0, hh),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (0.0, 1.0),
color: [255, 255, 255, 255],
},
];
let indices = vec![0, 1, 2, 2, 3, 0];
Self::finalize(vertices, indices)
}
pub fn cylinder(radius: f32, height: f32, segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
let half_height = height / 2.0;
for i in 0..=segments {
let angle = i as f32 * 2.0 * std::f32::consts::PI / segments as f32;
let x = angle.cos() * radius;
let z = angle.sin() * radius;
vertices.push(Vertex {
position: Vec3::new(x, half_height, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / segments as f32, 0.0),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(x, -half_height, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / segments as f32, 1.0),
color: [255, 255, 255, 255],
});
}
for i in 0..segments {
let top1 = i * 2;
let bottom1 = i * 2 + 1;
let top2 = (i + 1) * 2;
let bottom2 = (i + 1) * 2 + 1;
indices.push(top1);
indices.push(bottom1);
indices.push(top2);
indices.push(bottom1);
indices.push(bottom2);
indices.push(top2);
}
Self::finalize(vertices, indices)
}
pub fn pyramid(size: f32) -> Self {
let s = size / 2.0;
let h = size;
let vertices = vec![
Vertex { position: Vec3::new(-s, 0.0, -s), normal: Vec3::new(0.0, -1.0, 0.0), uv: (0.0, 0.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(s, 0.0, -s), normal: Vec3::new(0.0, -1.0, 0.0), uv: (1.0, 0.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(s, 0.0, s), normal: Vec3::new(0.0, -1.0, 0.0), uv: (1.0, 1.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(-s, 0.0, s), normal: Vec3::new(0.0, -1.0, 0.0), uv: (0.0, 1.0), color: [255, 255, 255, 255] },
Vertex { position: Vec3::new(0.0, h, 0.0), normal: Vec3::new(0.0, 1.0, 0.0), uv: (0.5, 0.5), color: [255, 255, 255, 255] },
];
let indices = vec![
0, 1, 2, 2, 3, 0, 0, 4, 1, 1, 4, 2, 2, 4, 3, 3, 4, 0, ];
Self::finalize(vertices, indices)
}
pub fn cone(radius: f32, height: f32, segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
vertices.push(Vertex {
position: Vec3::new(0.0, 0.0, 0.0),
normal: Vec3::new(0.0, -1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
for i in 0..=segments {
let angle = i as f32 * 2.0 * std::f32::consts::PI / segments as f32;
let x = angle.cos() * radius;
let z = angle.sin() * radius;
vertices.push(Vertex {
position: Vec3::new(x, 0.0, z),
normal: Vec3::new(0.0, -1.0, 0.0),
uv: (angle.cos() * 0.5 + 0.5, angle.sin() * 0.5 + 0.5),
color: [255, 255, 255, 255],
});
}
let apex_index = vertices.len() as u32;
vertices.push(Vertex {
position: Vec3::new(0.0, height, 0.0),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
for i in 1..=segments {
indices.push(0);
indices.push(i + 1);
indices.push(i);
}
for i in 1..=segments {
indices.push(i);
indices.push(i + 1);
indices.push(apex_index);
}
Self::finalize(vertices, indices)
}
pub fn capsule(radius: f32, height: f32, segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
let half_height = height / 2.0;
for i in 0..=segments {
let angle = i as f32 * 2.0 * std::f32::consts::PI / segments as f32;
let x = angle.cos() * radius;
let z = angle.sin() * radius;
vertices.push(Vertex {
position: Vec3::new(x, half_height, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / segments as f32, 0.0),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(x, -half_height, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / segments as f32, 1.0),
color: [255, 255, 255, 255],
});
}
for i in 0..segments {
let top1 = i * 2;
let bottom1 = i * 2 + 1;
let top2 = (i + 1) * 2;
let bottom2 = (i + 1) * 2 + 1;
indices.push(top1);
indices.push(bottom1);
indices.push(top2);
indices.push(bottom1);
indices.push(bottom2);
indices.push(top2);
}
Self::finalize(vertices, indices)
}
pub fn quad(width: f32, height: f32) -> Self {
let hw = width / 2.0;
let hh = height / 2.0;
let vertices = vec![
Vertex {
position: Vec3::new(-hw, -hh, 0.0),
normal: Vec3::new(0.0, 0.0, 1.0),
uv: (0.0, 1.0),
color: [255, 255, 255, 255],
},
Vertex {
position: Vec3::new(hw, -hh, 0.0),
normal: Vec3::new(0.0, 0.0, 1.0),
uv: (1.0, 1.0),
color: [255, 255, 255, 255],
},
Vertex {
position: Vec3::new(hw, hh, 0.0),
normal: Vec3::new(0.0, 0.0, 1.0),
uv: (1.0, 0.0),
color: [255, 255, 255, 255],
},
Vertex {
position: Vec3::new(-hw, hh, 0.0),
normal: Vec3::new(0.0, 0.0, 1.0),
uv: (0.0, 0.0),
color: [255, 255, 255, 255],
},
];
let indices = vec![0, 1, 2, 2, 3, 0];
Self::finalize(vertices, indices)
}
pub fn torus(major_radius: f32, minor_radius: f32, major_segments: u32, minor_segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for i in 0..=major_segments {
let u = i as f32 * 2.0 * std::f32::consts::PI / major_segments as f32;
let cos_u = u.cos();
let sin_u = u.sin();
for j in 0..=minor_segments {
let v = j as f32 * 2.0 * std::f32::consts::PI / minor_segments as f32;
let cos_v = v.cos();
let sin_v = v.sin();
let x = (major_radius + minor_radius * cos_v) * cos_u;
let y = minor_radius * sin_v;
let z = (major_radius + minor_radius * cos_v) * sin_u;
let nx = cos_v * cos_u;
let ny = sin_v;
let nz = cos_v * sin_u;
vertices.push(Vertex {
position: Vec3::new(x, y, z),
normal: Vec3::new(nx, ny, nz),
uv: (i as f32 / major_segments as f32, j as f32 / minor_segments as f32),
color: [255, 255, 255, 255],
});
}
}
for i in 0..major_segments {
for j in 0..minor_segments {
let a = i * (minor_segments + 1) + j;
let b = a + minor_segments + 1;
indices.push(a);
indices.push(b);
indices.push(a + 1);
indices.push(b);
indices.push(b + 1);
indices.push(a + 1);
}
}
Self::finalize(vertices, indices)
}
pub fn grid(width: f32, depth: f32, width_segments: u32, depth_segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
let hw = width / 2.0;
let hd = depth / 2.0;
for z in 0..=depth_segments {
for x in 0..=width_segments {
let px = (x as f32 / width_segments as f32) * width - hw;
let pz = (z as f32 / depth_segments as f32) * depth - hd;
vertices.push(Vertex {
position: Vec3::new(px, 0.0, pz),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (x as f32 / width_segments as f32, z as f32 / depth_segments as f32),
color: [255, 255, 255, 255],
});
}
}
for z in 0..depth_segments {
for x in 0..width_segments {
let a = z * (width_segments + 1) + x;
let b = a + width_segments + 1;
indices.push(a);
indices.push(b);
indices.push(a + 1);
indices.push(b);
indices.push(b + 1);
indices.push(a + 1);
}
}
Self::finalize(vertices, indices)
}
pub fn icosphere(radius: f32, subdivisions: u32) -> Self {
let t = (1.0 + 5.0_f32.sqrt()) / 2.0;
let mut vertices = vec![
Vec3::new(-1.0, t, 0.0).normalize(),
Vec3::new(1.0, t, 0.0).normalize(),
Vec3::new(-1.0, -t, 0.0).normalize(),
Vec3::new(1.0, -t, 0.0).normalize(),
Vec3::new(0.0, -1.0, t).normalize(),
Vec3::new(0.0, 1.0, t).normalize(),
Vec3::new(0.0, -1.0, -t).normalize(),
Vec3::new(0.0, 1.0, -t).normalize(),
Vec3::new(t, 0.0, -1.0).normalize(),
Vec3::new(t, 0.0, 1.0).normalize(),
Vec3::new(-t, 0.0, -1.0).normalize(),
Vec3::new(-t, 0.0, 1.0).normalize(),
];
let mut indices = vec![
0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11,
1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8,
3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9,
4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1,
];
for _ in 0..subdivisions.min(2) {
let mut new_indices = Vec::new();
for i in (0..indices.len()).step_by(3) {
let v1 = vertices[indices[i] as usize];
let v2 = vertices[indices[i + 1] as usize];
let v3 = vertices[indices[i + 2] as usize];
let a = Vec3::new(
(v1.x + v2.x) / 2.0,
(v1.y + v2.y) / 2.0,
(v1.z + v2.z) / 2.0,
).normalize();
let b = Vec3::new(
(v2.x + v3.x) / 2.0,
(v2.y + v3.y) / 2.0,
(v2.z + v3.z) / 2.0,
).normalize();
let c = Vec3::new(
(v3.x + v1.x) / 2.0,
(v3.y + v1.y) / 2.0,
(v3.z + v1.z) / 2.0,
).normalize();
let ai = vertices.len() as u32;
vertices.push(a);
let bi = vertices.len() as u32;
vertices.push(b);
let ci = vertices.len() as u32;
vertices.push(c);
new_indices.extend_from_slice(&[
indices[i], ai, ci,
indices[i + 1], bi, ai,
indices[i + 2], ci, bi,
ai, bi, ci,
]);
}
indices = new_indices;
}
let mesh_vertices: Vec<Vertex> = vertices
.iter()
.map(|v| Vertex {
position: Vec3::new(v.x * radius, v.y * radius, v.z * radius),
normal: *v,
uv: (
0.5 + v.x.atan2(v.z) / (2.0 * std::f32::consts::PI),
0.5 - v.y.asin() / std::f32::consts::PI,
),
color: [255, 255, 255, 255],
})
.collect();
Self::finalize(mesh_vertices, indices)
}
pub fn prism(radius: f32, height: f32, sides: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
let half_height = height / 2.0;
vertices.push(Vertex {
position: Vec3::new(0.0, half_height, 0.0),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(0.0, -half_height, 0.0),
normal: Vec3::new(0.0, -1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
for i in 0..=sides {
let angle = i as f32 * 2.0 * std::f32::consts::PI / sides as f32;
let x = angle.cos() * radius;
let z = angle.sin() * radius;
vertices.push(Vertex {
position: Vec3::new(x, half_height, z),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (angle.cos() * 0.5 + 0.5, angle.sin() * 0.5 + 0.5),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(x, -half_height, z),
normal: Vec3::new(0.0, -1.0, 0.0),
uv: (angle.cos() * 0.5 + 0.5, angle.sin() * 0.5 + 0.5),
color: [255, 255, 255, 255],
});
}
for i in 0..sides {
indices.push(0);
indices.push(2 + i * 2);
indices.push(2 + (i + 1) * 2);
}
for i in 0..sides {
indices.push(1);
indices.push(3 + (i + 1) * 2);
indices.push(3 + i * 2);
}
for i in 0..sides {
let top1 = 2 + i * 2;
let bottom1 = 3 + i * 2;
let top2 = 2 + (i + 1) * 2;
let bottom2 = 3 + (i + 1) * 2;
indices.push(top1);
indices.push(bottom1);
indices.push(top2);
indices.push(bottom1);
indices.push(bottom2);
indices.push(top2);
}
Self::finalize(vertices, indices)
}
pub fn set_color(&mut self, color: [u8; 4]) {
for vertex in &mut self.vertices {
vertex.color = color;
}
}
pub fn calculate_normals(&mut self) {
for i in (0..self.indices.len()).step_by(3) {
let i0 = self.indices[i] as usize;
let i1 = self.indices[i + 1] as usize;
let i2 = self.indices[i + 2] as usize;
let v0 = &self.vertices[i0].position;
let v1 = &self.vertices[i1].position;
let v2 = &self.vertices[i2].position;
let edge1 = Vec3::new(v1.x - v0.x, v1.y - v0.y, v1.z - v0.z);
let edge2 = Vec3::new(v2.x - v0.x, v2.y - v0.y, v2.z - v0.z);
let normal = edge1.cross(&edge2).normalize();
self.vertices[i0].normal = normal;
self.vertices[i1].normal = normal;
self.vertices[i2].normal = normal;
}
}
pub fn merge(&mut self, other: &Mesh3D) {
let offset = self.vertices.len() as u32;
self.vertices.extend_from_slice(&other.vertices);
self.indices.extend(other.indices.iter().map(|i| i + offset));
}
pub fn flip_normals(&mut self) {
for vertex in &mut self.vertices {
vertex.normal.x = -vertex.normal.x;
vertex.normal.y = -vertex.normal.y;
vertex.normal.z = -vertex.normal.z;
}
}
pub fn translate(&mut self, offset: Vec3) {
for vertex in &mut self.vertices {
vertex.position.x += offset.x;
vertex.position.y += offset.y;
vertex.position.z += offset.z;
}
}
pub fn scale(&mut self, scale: Vec3) {
for vertex in &mut self.vertices {
vertex.position.x *= scale.x;
vertex.position.y *= scale.y;
vertex.position.z *= scale.z;
}
}
pub fn rotate_y(&mut self, angle: f32) {
let cos = angle.cos();
let sin = angle.sin();
for vertex in &mut self.vertices {
let x = vertex.position.x;
let z = vertex.position.z;
vertex.position.x = x * cos - z * sin;
vertex.position.z = x * sin + z * cos;
}
}
pub fn box_sized(width: f32, height: f32, depth: f32) -> Self {
let mut mesh = Self::cube(1.0);
mesh.scale(Vec3::new(width, height, depth));
mesh
}
pub fn cylinder_sized(radius: f32, height: f32, segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
vertices.push(Vertex {
position: Vec3::new(0.0, -height / 2.0, 0.0),
normal: Vec3::new(0.0, -1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(0.0, height / 2.0, 0.0),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
for i in 0..=segments {
let angle = (i as f32 / segments as f32) * std::f32::consts::PI * 2.0;
let x = angle.cos() * radius;
let z = angle.sin() * radius;
vertices.push(Vertex {
position: Vec3::new(x, -height / 2.0, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / segments as f32, 1.0),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(x, height / 2.0, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / segments as f32, 0.0),
color: [255, 255, 255, 255],
});
}
for i in 0..segments {
let base = 2 + i * 2;
indices.extend_from_slice(&[
base, base + 2, base + 1,
base + 1, base + 2, base + 3,
]);
indices.extend_from_slice(&[0, base + 2, base]);
indices.extend_from_slice(&[1, base + 1, base + 3]);
}
Self::finalize(vertices, indices)
}
pub fn sphere_sized(radius: f32, segments: u32) -> Self {
let mut mesh = Self::sphere(1.0, segments);
mesh.scale(Vec3::new(radius, radius, radius));
mesh
}
pub fn ring(outer_radius: f32, inner_radius: f32, segments: u32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for i in 0..=segments {
let angle = (i as f32 / segments as f32) * std::f32::consts::PI * 2.0;
let cos = angle.cos();
let sin = angle.sin();
vertices.push(Vertex {
position: Vec3::new(cos * outer_radius, 0.0, sin * outer_radius),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (i as f32 / segments as f32, 0.0),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(cos * inner_radius, 0.0, sin * inner_radius),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (i as f32 / segments as f32, 1.0),
color: [255, 255, 255, 255],
});
}
for i in 0..segments {
let base = i * 2;
indices.extend_from_slice(&[
base, base + 2, base + 1,
base + 1, base + 2, base + 3,
]);
}
Self::finalize(vertices, indices)
}
pub fn star(outer_radius: f32, inner_radius: f32, points: u32, height: f32) -> Self {
let mut vertices = Vec::new();
let mut indices = Vec::new();
vertices.push(Vertex {
position: Vec3::new(0.0, height / 2.0, 0.0),
normal: Vec3::new(0.0, 1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(0.0, -height / 2.0, 0.0),
normal: Vec3::new(0.0, -1.0, 0.0),
uv: (0.5, 0.5),
color: [255, 255, 255, 255],
});
for i in 0..(points * 2) {
let angle = (i as f32 / (points * 2) as f32) * std::f32::consts::PI * 2.0;
let radius = if i % 2 == 0 { outer_radius } else { inner_radius };
let x = angle.cos() * radius;
let z = angle.sin() * radius;
vertices.push(Vertex {
position: Vec3::new(x, height / 2.0, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / (points * 2) as f32, 0.0),
color: [255, 255, 255, 255],
});
vertices.push(Vertex {
position: Vec3::new(x, -height / 2.0, z),
normal: Vec3::new(x, 0.0, z).normalize(),
uv: (i as f32 / (points * 2) as f32, 1.0),
color: [255, 255, 255, 255],
});
}
for i in 0..(points * 2) {
let base = 2 + i * 2;
let next = 2 + ((i + 1) % (points * 2)) * 2;
indices.extend_from_slice(&[0, base, next]);
indices.extend_from_slice(&[1, next + 1, base + 1]);
indices.extend_from_slice(&[
base, next, base + 1,
base + 1, next, next + 1,
]);
}
Self::finalize(vertices, indices)
}
pub fn get_center(&self) -> Vec3 {
if self.vertices.is_empty() {
return Vec3::zero();
}
let mut min = self.vertices[0].position;
let mut max = self.vertices[0].position;
for vertex in &self.vertices {
min.x = min.x.min(vertex.position.x);
min.y = min.y.min(vertex.position.y);
min.z = min.z.min(vertex.position.z);
max.x = max.x.max(vertex.position.x);
max.y = max.y.max(vertex.position.y);
max.z = max.z.max(vertex.position.z);
}
Vec3::new(
(min.x + max.x) / 2.0,
(min.y + max.y) / 2.0,
(min.z + max.z) / 2.0,
)
}
pub fn get_size(&self) -> Vec3 {
if self.vertices.is_empty() {
return Vec3::zero();
}
let mut min = self.vertices[0].position;
let mut max = self.vertices[0].position;
for vertex in &self.vertices {
min.x = min.x.min(vertex.position.x);
min.y = min.y.min(vertex.position.y);
min.z = min.z.min(vertex.position.z);
max.x = max.x.max(vertex.position.x);
max.y = max.y.max(vertex.position.y);
max.z = max.z.max(vertex.position.z);
}
Vec3::new(max.x - min.x, max.y - min.y, max.z - min.z)
}
pub fn subdivide(&mut self) {
let mut new_vertices = self.vertices.clone();
let mut new_indices = Vec::new();
for i in (0..self.indices.len()).step_by(3) {
let i0 = self.indices[i] as usize;
let i1 = self.indices[i + 1] as usize;
let i2 = self.indices[i + 2] as usize;
let v0 = &self.vertices[i0];
let v1 = &self.vertices[i1];
let v2 = &self.vertices[i2];
let mid01 = Vertex {
position: Vec3::new(
(v0.position.x + v1.position.x) / 2.0,
(v0.position.y + v1.position.y) / 2.0,
(v0.position.z + v1.position.z) / 2.0,
),
normal: Vec3::new(
(v0.normal.x + v1.normal.x) / 2.0,
(v0.normal.y + v1.normal.y) / 2.0,
(v0.normal.z + v1.normal.z) / 2.0,
).normalize(),
uv: ((v0.uv.0 + v1.uv.0) / 2.0, (v0.uv.1 + v1.uv.1) / 2.0),
color: v0.color,
};
let mid12 = Vertex {
position: Vec3::new(
(v1.position.x + v2.position.x) / 2.0,
(v1.position.y + v2.position.y) / 2.0,
(v1.position.z + v2.position.z) / 2.0,
),
normal: Vec3::new(
(v1.normal.x + v2.normal.x) / 2.0,
(v1.normal.y + v2.normal.y) / 2.0,
(v1.normal.z + v2.normal.z) / 2.0,
).normalize(),
uv: ((v1.uv.0 + v2.uv.0) / 2.0, (v1.uv.1 + v2.uv.1) / 2.0),
color: v1.color,
};
let mid20 = Vertex {
position: Vec3::new(
(v2.position.x + v0.position.x) / 2.0,
(v2.position.y + v0.position.y) / 2.0,
(v2.position.z + v0.position.z) / 2.0,
),
normal: Vec3::new(
(v2.normal.x + v0.normal.x) / 2.0,
(v2.normal.y + v0.normal.y) / 2.0,
(v2.normal.z + v0.normal.z) / 2.0,
).normalize(),
uv: ((v2.uv.0 + v0.uv.0) / 2.0, (v2.uv.1 + v0.uv.1) / 2.0),
color: v2.color,
};
let idx_mid01 = new_vertices.len() as u32;
new_vertices.push(mid01);
let idx_mid12 = new_vertices.len() as u32;
new_vertices.push(mid12);
let idx_mid20 = new_vertices.len() as u32;
new_vertices.push(mid20);
new_indices.extend_from_slice(&[
self.indices[i], idx_mid01, idx_mid20,
idx_mid01, self.indices[i + 1], idx_mid12,
idx_mid20, idx_mid12, self.indices[i + 2],
idx_mid01, idx_mid12, idx_mid20,
]);
}
self.vertices = new_vertices;
self.indices = new_indices;
}
pub fn smooth(&mut self) {
let mut normals = vec![Vec3::zero(); self.vertices.len()];
let mut counts = vec![0; self.vertices.len()];
for i in (0..self.indices.len()).step_by(3) {
let i0 = self.indices[i] as usize;
let i1 = self.indices[i + 1] as usize;
let i2 = self.indices[i + 2] as usize;
let v0 = &self.vertices[i0].position;
let v1 = &self.vertices[i1].position;
let v2 = &self.vertices[i2].position;
let edge1 = Vec3::new(v1.x - v0.x, v1.y - v0.y, v1.z - v0.z);
let edge2 = Vec3::new(v2.x - v0.x, v2.y - v0.y, v2.z - v0.z);
let normal = edge1.cross(&edge2);
normals[i0] = normals[i0] + normal;
normals[i1] = normals[i1] + normal;
normals[i2] = normals[i2] + normal;
counts[i0] += 1;
counts[i1] += 1;
counts[i2] += 1;
}
for i in 0..self.vertices.len() {
if counts[i] > 0 {
self.vertices[i].normal = (normals[i] / counts[i] as f32).normalize();
}
}
}
pub fn mirror(&self, axis: MirrorAxis) -> Self {
let mut mirrored = self.clone();
for vertex in &mut mirrored.vertices {
match axis {
MirrorAxis::X => vertex.position.x = -vertex.position.x,
MirrorAxis::Y => vertex.position.y = -vertex.position.y,
MirrorAxis::Z => vertex.position.z = -vertex.position.z,
}
}
for i in (0..mirrored.indices.len()).step_by(3) {
mirrored.indices.swap(i + 1, i + 2);
}
mirrored
}
pub fn extrude(&mut self, distance: f32) {
let original_vertices = self.vertices.clone();
for vertex in &mut self.vertices {
vertex.position = vertex.position + vertex.normal * distance;
}
let offset = original_vertices.len() as u32;
self.vertices.extend(original_vertices);
let original_indices = self.indices.clone();
for i in (0..original_indices.len()).step_by(3) {
let i0 = original_indices[i];
let i1 = original_indices[i + 1];
let i2 = original_indices[i + 2];
self.indices.extend_from_slice(&[
i0, i1, i0 + offset,
i1, i1 + offset, i0 + offset,
i1, i2, i1 + offset,
i2, i2 + offset, i1 + offset,
i2, i0, i2 + offset,
i0, i0 + offset, i2 + offset,
]);
}
}
pub fn apply_noise(&mut self, amplitude: f32, frequency: f32) {
use rand::Rng;
let mut rng = rand::thread_rng();
for vertex in &mut self.vertices {
let noise = rng.gen_range(-1.0..1.0) * amplitude;
let offset = vertex.normal * noise * frequency;
vertex.position = vertex.position + offset;
}
}
pub fn optimize(&mut self) {
let mut unique_vertices = Vec::new();
let mut index_map = Vec::new();
for vertex in &self.vertices {
if let Some(idx) = unique_vertices.iter().position(|v: &Vertex| {
(v.position.x - vertex.position.x).abs() < 0.001
&& (v.position.y - vertex.position.y).abs() < 0.001
&& (v.position.z - vertex.position.z).abs() < 0.001
}) {
index_map.push(idx as u32);
} else {
index_map.push(unique_vertices.len() as u32);
unique_vertices.push(vertex.clone());
}
}
for index in &mut self.indices {
*index = index_map[*index as usize];
}
self.vertices = unique_vertices;
}
pub fn surface_area(&self) -> f32 {
let mut area = 0.0;
for i in (0..self.indices.len()).step_by(3) {
let v0 = &self.vertices[self.indices[i] as usize].position;
let v1 = &self.vertices[self.indices[i + 1] as usize].position;
let v2 = &self.vertices[self.indices[i + 2] as usize].position;
let edge1 = *v1 - *v0;
let edge2 = *v2 - *v0;
let cross = edge1.cross(&edge2);
area += cross.length() / 2.0;
}
area
}
pub fn volume(&self) -> f32 {
let mut volume = 0.0;
for i in (0..self.indices.len()).step_by(3) {
let v0 = &self.vertices[self.indices[i] as usize].position;
let v1 = &self.vertices[self.indices[i + 1] as usize].position;
let v2 = &self.vertices[self.indices[i + 2] as usize].position;
volume += v0.dot(&v1.cross(v2)) / 6.0;
}
volume.abs()
}
}
#[derive(Debug, Clone, Copy)]
pub enum MirrorAxis {
X,
Y,
Z,
}