1use crate::mesh_edit::EditableMesh;
14use glam::Vec3;
15use std::collections::HashMap;
16
17fn key(p: [f32; 3]) -> [i64; 3] {
18 p.map(|v| (v as f64 * 10_000.0).round() as i64)
19}
20
21impl EditableMesh {
22 pub fn project_planar(&mut self, axis: Vec3, scale: f32) {
25 let n = axis.normalize_or_zero();
26 if n == Vec3::ZERO || scale <= 0.0 {
27 return;
28 }
29 let u = n.any_orthonormal_vector();
30 let v = n.cross(u);
31 for vert in &mut self.vertices {
32 let p = Vec3::from(vert.pos);
33 vert.uv = [p.dot(u) / scale, p.dot(v) / scale];
34 }
35 self.changed();
36 }
37
38 pub fn project_cylindrical(&mut self, scale: f32) {
41 if scale <= 0.0 {
42 return;
43 }
44 for vert in &mut self.vertices {
45 let p = Vec3::from(vert.pos);
46 vert.uv = [p.z.atan2(p.x) / std::f32::consts::TAU + 0.5, p.y / scale];
47 }
48 self.changed();
49 }
50
51 pub fn project_spherical(&mut self) {
53 for vert in &mut self.vertices {
54 let p = Vec3::from(vert.pos).normalize_or_zero();
55 vert.uv = [p.z.atan2(p.x) / std::f32::consts::TAU + 0.5, p.y.clamp(-1.0, 1.0).asin() / std::f32::consts::PI + 0.5];
56 }
57 self.changed();
58 }
59
60 pub fn normalize_uv(&mut self, margin: f32) {
62 if self.vertices.is_empty() {
63 return;
64 }
65 let (mut lo, mut hi) = ([f32::MAX; 2], [f32::MIN; 2]);
66 for v in &self.vertices {
67 for k in 0..2 {
68 lo[k] = lo[k].min(v.uv[k]);
69 hi[k] = hi[k].max(v.uv[k]);
70 }
71 }
72 let margin = margin.clamp(0.0, 0.45);
73 let size = (hi[0] - lo[0]).max(hi[1] - lo[1]).max(1e-6);
74 let k = (1.0 - 2.0 * margin) / size;
75 for v in &mut self.vertices {
76 v.uv = [(v.uv[0] - lo[0]) * k + margin, (v.uv[1] - lo[1]) * k + margin];
77 }
78 self.changed();
79 }
80
81 pub fn transform_uv(&mut self, scale: [f32; 2], rotation_degrees: f32, offset: [f32; 2]) {
83 let (s, c) = rotation_degrees.to_radians().sin_cos();
84 for v in &mut self.vertices {
85 let (x, y) = ((v.uv[0] - 0.5) * scale[0], (v.uv[1] - 0.5) * scale[1]);
86 v.uv = [c * x - s * y + 0.5 + offset[0], s * x + c * y + 0.5 + offset[1]];
87 }
88 self.changed();
89 }
90
91 pub fn flat_normals(&mut self) {
93 let mut vertices = Vec::with_capacity(self.indices.len());
94 for t in self.indices.chunks_exact(3) {
95 let (a, b, c) = (self.vertices[t[0] as usize], self.vertices[t[1] as usize], self.vertices[t[2] as usize]);
96 let n = (Vec3::from(b.pos) - Vec3::from(a.pos)).cross(Vec3::from(c.pos) - Vec3::from(a.pos)).normalize_or_zero().to_array();
97 for mut v in [a, b, c] {
98 v.normal = n;
99 vertices.push(v);
100 }
101 }
102 self.indices = (0..vertices.len() as u32).collect();
103 self.vertices = vertices;
104 self.changed();
105 }
106
107 pub fn smooth_normals(&mut self, angle_degrees: f32) {
110 let faces: Vec<(Vec3, [usize; 3])> = self
111 .indices
112 .chunks_exact(3)
113 .map(|t| {
114 let p = |i: u32| Vec3::from(self.vertices[i as usize].pos);
115 ((p(t[1]) - p(t[0])).cross(p(t[2]) - p(t[0])), [t[0] as usize, t[1] as usize, t[2] as usize])
116 })
117 .collect();
118 let mut at_point = HashMap::<[i64; 3], Vec<usize>>::new();
119 for (f, (_, ids)) in faces.iter().enumerate() {
120 for &i in ids {
121 at_point.entry(key(self.vertices[i].pos)).or_default().push(f);
122 }
123 }
124 let cos = angle_degrees.to_radians().cos();
125 let mut vertices = Vec::with_capacity(self.indices.len());
126 for (f, (normal, ids)) in faces.iter().enumerate() {
127 let n0 = normal.normalize_or_zero();
128 for &i in ids {
129 let mut sum = Vec3::ZERO;
130 for &other in &at_point[&key(self.vertices[i].pos)] {
131 if other == f || n0.dot(faces[other].0.normalize_or_zero()) >= cos {
132 sum += faces[other].0;
133 }
134 }
135 let mut v = self.vertices[i];
136 v.normal = sum.normalize_or(if n0 == Vec3::ZERO { Vec3::from(v.normal) } else { n0 }).to_array();
137 vertices.push(v);
138 }
139 }
140 self.indices = (0..vertices.len() as u32).collect();
141 self.vertices = vertices;
142 self.changed();
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::mesh::MeshData;
150
151 fn primitive(name: &str) -> EditableMesh {
152 EditableMesh::from_mesh(&MeshData::primitive(name).unwrap()).unwrap()
153 }
154
155 #[test]
156 fn projections_stay_finite_and_normalize_into_the_unit_square() {
157 let mut m = primitive("cube");
158 m.project_planar(Vec3::Y, 2.0);
159 m.normalize_uv(0.05);
160 assert!(m.vertices.iter().all(|v| v.uv.iter().all(|c| (0.0..=1.0).contains(c))));
161 m.project_spherical();
162 m.project_cylindrical(1.0);
163 m.transform_uv([2.0, 2.0], 45.0, [0.1, 0.0]);
164 assert!(m.vertices.iter().all(|v| v.uv.iter().all(|c| c.is_finite())));
165 m.project_planar(Vec3::ZERO, 1.0);
166 }
167
168 #[test]
169 fn flat_and_crease_normals() {
170 let mut m = primitive("cube");
171 let faces = m.indices.len() / 3;
172 m.flat_normals();
173 assert_eq!(m.vertex_count(), faces * 3);
174 let mut sphere = primitive("sphere");
175 sphere.smooth_normals(60.0);
176 assert!(sphere.vertices.iter().all(|v| (Vec3::from(v.normal).length() - 1.0).abs() < 1e-3));
177 let mut hard = primitive("cube");
178 hard.smooth_normals(30.0);
179 assert!(hard.vertices.iter().all(|v| Vec3::from(v.normal).abs().max_element() > 0.99), "90 degree edges stay hard");
180 }
181}