1#[allow(dead_code)]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct SymmetryAxis {
14 pub axis: u8,
15}
16
17impl SymmetryAxis {
18 pub const X: Self = Self { axis: 0 };
19 pub const Y: Self = Self { axis: 1 };
20 pub const Z: Self = Self { axis: 2 };
21}
22
23#[allow(dead_code)]
25#[derive(Debug, Clone)]
26pub struct SymmetryConfig {
27 pub axis: SymmetryAxis,
28 pub tolerance: f32,
30 pub blend: f32,
32}
33
34impl Default for SymmetryConfig {
35 fn default() -> Self {
36 Self {
37 axis: SymmetryAxis::X,
38 tolerance: 0.001,
39 blend: 1.0,
40 }
41 }
42}
43
44#[allow(dead_code)]
46#[derive(Debug, Clone)]
47pub struct AsymmetryConfig {
48 pub strength: f32,
50 pub frequency: f32,
52 pub seed: u64,
53}
54
55impl Default for AsymmetryConfig {
56 fn default() -> Self {
57 Self {
58 strength: 0.05,
59 frequency: 1.0,
60 seed: 42,
61 }
62 }
63}
64
65#[allow(dead_code)]
67#[derive(Debug, Clone)]
68pub struct SymmetryReport {
69 pub paired_vertices: usize,
70 pub unpaired_vertices: usize,
71 pub symmetry_error: f32,
73 pub is_symmetric: bool,
75}
76
77#[allow(dead_code)]
83pub fn mirror_position(p: [f32; 3], axis: &SymmetryAxis) -> [f32; 3] {
84 let mut q = p;
85 q[axis.axis as usize] = -q[axis.axis as usize];
86 q
87}
88
89#[allow(dead_code)]
92pub fn asymmetry_noise(x: f32, y: f32, z: f32, seed: u64) -> f32 {
93 let ix = (x * 1000.0) as i64 as u64;
95 let iy = (y * 1000.0) as i64 as u64;
96 let iz = (z * 1000.0) as i64 as u64;
97
98 let mut state: u64 = seed
99 .wrapping_add(ix.wrapping_mul(2_654_435_761))
100 .wrapping_add(iy.wrapping_mul(2_246_822_519))
101 .wrapping_add(iz.wrapping_mul(3_266_489_917));
102
103 state = state
105 .wrapping_mul(6_364_136_223_846_793_005)
106 .wrapping_add(1_442_695_040_888_963_407);
107 state = state
108 .wrapping_mul(6_364_136_223_846_793_005)
109 .wrapping_add(1_442_695_040_888_963_407);
110 state = state
111 .wrapping_mul(6_364_136_223_846_793_005)
112 .wrapping_add(1_442_695_040_888_963_407);
113
114 let u = (state >> 11) as f32 / (1u64 << 53) as f32; u * 2.0 - 1.0
117}
118
119#[allow(dead_code)]
123pub fn find_symmetry_pairs_x(positions: &[[f32; 3]], tolerance: f32) -> Vec<(usize, usize)> {
124 let mut pairs = Vec::new();
125 let mut used_neg: Vec<bool> = vec![false; positions.len()];
126
127 for (i, &pi) in positions.iter().enumerate() {
128 if pi[0] <= 0.0 {
129 continue;
130 }
131 let tx = -pi[0];
133 let ty = pi[1];
134 let tz = pi[2];
135
136 let mut best_j = None;
137 let mut best_d = tolerance;
138
139 for (j, &pj) in positions.iter().enumerate() {
140 if used_neg[j] {
141 continue;
142 }
143 let dx = pj[0] - tx;
144 let dy = pj[1] - ty;
145 let dz = pj[2] - tz;
146 let d = (dx * dx + dy * dy + dz * dz).sqrt();
147 if d <= best_d {
148 best_d = d;
149 best_j = Some(j);
150 }
151 }
152
153 if let Some(j) = best_j {
154 used_neg[j] = true;
155 pairs.push((i, j));
156 }
157 }
158
159 pairs
160}
161
162#[allow(dead_code)]
164pub fn enforce_symmetry(
165 positions: &mut [[f32; 3]],
166 pairs: &[(usize, usize)],
167 cfg: &SymmetryConfig,
168) {
169 let ax = cfg.axis.axis as usize;
170 for &(a, b) in pairs {
171 if a >= positions.len() || b >= positions.len() {
172 continue;
173 }
174
175 let pa = positions[a];
177 let pb = positions[b];
178
179 let avg_non_ax_y = if ax != 1 { (pa[1] + pb[1]) * 0.5 } else { 0.0 };
181 let avg_non_ax_z = if ax != 2 { (pa[2] + pb[2]) * 0.5 } else { 0.0 };
182
183 let sym_ax = (pa[ax].abs() + pb[ax].abs()) * 0.5;
186
187 let mut new_a = pa;
188 let mut new_b = pb;
189
190 let b_factor = cfg.blend;
192
193 if ax == 0 {
194 new_a[0] = pa[0] * (1.0 - b_factor) + sym_ax * b_factor;
195 new_b[0] = pb[0] * (1.0 - b_factor) + (-sym_ax) * b_factor;
196 new_a[1] = pa[1] * (1.0 - b_factor) + avg_non_ax_y * b_factor;
197 new_b[1] = pb[1] * (1.0 - b_factor) + avg_non_ax_y * b_factor;
198 new_a[2] = pa[2] * (1.0 - b_factor) + avg_non_ax_z * b_factor;
199 new_b[2] = pb[2] * (1.0 - b_factor) + avg_non_ax_z * b_factor;
200 } else {
201 let avg_ax = (pa[ax] + pb[ax]) * 0.5;
203 new_a[ax] = pa[ax] * (1.0 - b_factor) + avg_ax * b_factor;
204 new_b[ax] = pb[ax] * (1.0 - b_factor) + avg_ax * b_factor;
205 }
206
207 positions[a] = new_a;
208 positions[b] = new_b;
209 }
210}
211
212#[allow(dead_code)]
214pub fn symmetry_report(
215 positions: &[[f32; 3]],
216 pairs: &[(usize, usize)],
217 axis: &SymmetryAxis,
218) -> SymmetryReport {
219 let tolerance = 0.001;
220 let paired_vertices = pairs.len() * 2;
221 let unpaired_vertices = positions.len().saturating_sub(paired_vertices);
222
223 let symmetry_error = if pairs.is_empty() {
224 0.0
225 } else {
226 let total: f32 = pairs
227 .iter()
228 .filter_map(|&(a, b)| {
229 if a < positions.len() && b < positions.len() {
230 let mirrored = mirror_position(positions[b], axis);
231 let dx = positions[a][0] - mirrored[0];
232 let dy = positions[a][1] - mirrored[1];
233 let dz = positions[a][2] - mirrored[2];
234 Some((dx * dx + dy * dy + dz * dz).sqrt())
235 } else {
236 None
237 }
238 })
239 .sum();
240 total / pairs.len() as f32
241 };
242
243 SymmetryReport {
244 paired_vertices,
245 unpaired_vertices,
246 symmetry_error,
247 is_symmetric: symmetry_error < tolerance,
248 }
249}
250
251#[allow(dead_code)]
253pub fn inject_asymmetry(positions: &mut [[f32; 3]], cfg: &AsymmetryConfig) {
254 if cfg.strength <= 0.0 {
255 return;
256 }
257 for p in positions.iter_mut() {
258 let nx = asymmetry_noise(
259 p[0] * cfg.frequency,
260 p[1] * cfg.frequency,
261 p[2] * cfg.frequency,
262 cfg.seed,
263 );
264 let ny = asymmetry_noise(
265 p[1] * cfg.frequency,
266 p[2] * cfg.frequency,
267 p[0] * cfg.frequency,
268 cfg.seed.wrapping_add(1),
269 );
270 let nz = asymmetry_noise(
271 p[2] * cfg.frequency,
272 p[0] * cfg.frequency,
273 p[1] * cfg.frequency,
274 cfg.seed.wrapping_add(2),
275 );
276 p[0] += nx * cfg.strength;
277 p[1] += ny * cfg.strength;
278 p[2] += nz * cfg.strength;
279 }
280}
281
282#[allow(dead_code)]
284pub fn symmetrize_morph_deltas(
285 deltas: &mut [[f32; 3]],
286 pairs: &[(usize, usize)],
287 axis: &SymmetryAxis,
288) {
289 for &(a, b) in pairs {
290 if a >= deltas.len() || b >= deltas.len() {
291 continue;
292 }
293 let da = deltas[a];
294 let db = deltas[b];
295
296 let db_mirrored = mirror_position(db, axis);
298
299 let sym_a = [
300 (da[0] + db_mirrored[0]) * 0.5,
301 (da[1] + db_mirrored[1]) * 0.5,
302 (da[2] + db_mirrored[2]) * 0.5,
303 ];
304 let sym_b = mirror_position(sym_a, axis);
305
306 deltas[a] = sym_a;
307 deltas[b] = sym_b;
308 }
309}
310
311#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_mirror_position_x_axis() {
321 let p = [1.0f32, 2.0, 3.0];
322 let m = mirror_position(p, &SymmetryAxis::X);
323 assert!((m[0] - (-1.0)).abs() < 1e-6);
324 assert!((m[1] - 2.0).abs() < 1e-6);
325 assert!((m[2] - 3.0).abs() < 1e-6);
326 }
327
328 #[test]
329 fn test_mirror_position_y_axis() {
330 let p = [1.0f32, 2.0, 3.0];
331 let m = mirror_position(p, &SymmetryAxis::Y);
332 assert!((m[0] - 1.0).abs() < 1e-6);
333 assert!((m[1] - (-2.0)).abs() < 1e-6);
334 assert!((m[2] - 3.0).abs() < 1e-6);
335 }
336
337 #[test]
338 fn test_mirror_position_z_axis() {
339 let p = [1.0f32, 2.0, 3.0];
340 let m = mirror_position(p, &SymmetryAxis::Z);
341 assert!((m[0] - 1.0).abs() < 1e-6);
342 assert!((m[1] - 2.0).abs() < 1e-6);
343 assert!((m[2] - (-3.0)).abs() < 1e-6);
344 }
345
346 #[test]
347 fn test_symmetry_report_no_pairs() {
348 let positions = vec![[0.0f32; 3]; 4];
349 let report = symmetry_report(&positions, &[], &SymmetryAxis::X);
350 assert_eq!(report.paired_vertices, 0);
351 assert_eq!(report.symmetry_error, 0.0);
352 assert!(report.is_symmetric);
353 }
354
355 #[test]
356 fn test_find_symmetry_pairs_x_symmetric_mesh() {
357 let positions: Vec<[f32; 3]> = vec![
359 [1.0, 0.0, 0.0],
360 [2.0, 0.0, 0.0],
361 [-1.0, 0.0, 0.0],
362 [-2.0, 0.0, 0.0],
363 ];
364 let pairs = find_symmetry_pairs_x(&positions, 0.01);
365 assert_eq!(pairs.len(), 2);
366 }
367
368 #[test]
369 fn test_find_symmetry_pairs_x_asymmetric_mesh() {
370 let positions: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [3.0, 0.0, 0.0]];
372 let pairs = find_symmetry_pairs_x(&positions, 0.01);
373 assert_eq!(pairs.len(), 0);
374 }
375
376 #[test]
377 fn test_enforce_symmetry_makes_positions_symmetric() {
378 let mut positions: Vec<[f32; 3]> = vec![
379 [1.0, 0.0, 0.0], [-1.1, 0.0, 0.0], ];
382 let pairs = vec![(0usize, 1usize)];
383 let cfg = SymmetryConfig::default(); enforce_symmetry(&mut positions, &pairs, &cfg);
385 assert!(positions[0][0] > 0.0);
387 assert!(positions[1][0] < 0.0);
388 assert!((positions[0][0] + positions[1][0]).abs() < 1e-4);
389 }
390
391 #[test]
392 fn test_inject_asymmetry_changes_positions() {
393 let original = vec![[0.0f32, 0.0, 0.0]; 4];
394 let mut positions = original.clone();
395 let cfg = AsymmetryConfig {
396 strength: 0.1,
397 frequency: 1.0,
398 seed: 1234,
399 };
400 inject_asymmetry(&mut positions, &cfg);
401 let changed = positions.iter().any(|&p| p != [0.0, 0.0, 0.0]);
402 assert!(changed);
403 }
404
405 #[test]
406 fn test_inject_asymmetry_zero_strength_no_change() {
407 let original = vec![[1.0f32, 2.0, 3.0]; 4];
408 let mut positions = original.clone();
409 let cfg = AsymmetryConfig {
410 strength: 0.0,
411 frequency: 1.0,
412 seed: 99,
413 };
414 inject_asymmetry(&mut positions, &cfg);
415 assert_eq!(positions, original);
416 }
417
418 #[test]
419 fn test_symmetrize_morph_deltas_makes_deltas_mirror() {
420 let mut deltas: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [0.5, 0.0, 0.0]];
421 let pairs = vec![(0usize, 1usize)];
422 symmetrize_morph_deltas(&mut deltas, &pairs, &SymmetryAxis::X);
423 assert!(
427 (deltas[0][0] - 0.25).abs() < 1e-5,
428 "deltas[0]={:?}",
429 deltas[0]
430 );
431 assert!(
432 (deltas[1][0] - (-0.25)).abs() < 1e-5,
433 "deltas[1]={:?}",
434 deltas[1]
435 );
436 assert!((deltas[0][0] + deltas[1][0]).abs() < 1e-5);
438 }
439
440 #[test]
441 fn test_asymmetry_noise_deterministic() {
442 let n1 = asymmetry_noise(1.0, 2.0, 3.0, 42);
443 let n2 = asymmetry_noise(1.0, 2.0, 3.0, 42);
444 assert_eq!(n1, n2);
445 }
446
447 #[test]
448 fn test_asymmetry_noise_different_seeds_differ() {
449 let n1 = asymmetry_noise(1.0, 2.0, 3.0, 42);
450 let n2 = asymmetry_noise(1.0, 2.0, 3.0, 43);
451 assert_ne!(n1, n2);
452 }
453
454 #[test]
455 fn test_asymmetry_noise_in_range() {
456 let n = asymmetry_noise(0.5, 1.5, -0.3, 7);
457 assert!((-1.0..=1.0).contains(&n), "n={n}");
458 }
459
460 #[test]
461 fn test_symmetry_report_with_pairs() {
462 let positions: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]];
464 let pairs = vec![(0usize, 1usize)];
465 let report = symmetry_report(&positions, &pairs, &SymmetryAxis::X);
466 assert_eq!(report.paired_vertices, 2);
467 assert!(
468 report.symmetry_error < 0.001,
469 "err={}",
470 report.symmetry_error
471 );
472 assert!(report.is_symmetric);
473 }
474}