1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SymmetryAxis {
10 X,
11 Y,
12 Z,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone, PartialEq)]
18pub struct BodySymmetryV2Params {
19 pub symmetry_weight: f32,
21 pub axis: SymmetryAxis,
23 pub position_tolerance: f32,
25 pub positive_side_master: bool,
27}
28
29impl Default for BodySymmetryV2Params {
30 fn default() -> Self {
31 Self {
32 symmetry_weight: 1.0,
33 axis: SymmetryAxis::X,
34 position_tolerance: 0.001,
35 positive_side_master: true,
36 }
37 }
38}
39
40#[allow(dead_code)]
42pub fn default_body_symmetry_v2_params() -> BodySymmetryV2Params {
43 BodySymmetryV2Params::default()
44}
45
46#[allow(dead_code)]
48pub fn mirror_position(pos: [f32; 3], axis: SymmetryAxis) -> [f32; 3] {
49 match axis {
50 SymmetryAxis::X => [-pos[0], pos[1], pos[2]],
51 SymmetryAxis::Y => [pos[0], -pos[1], pos[2]],
52 SymmetryAxis::Z => [pos[0], pos[1], -pos[2]],
53 }
54}
55
56#[allow(dead_code)]
58pub fn are_mirror_pair(a: [f32; 3], b: [f32; 3], axis: SymmetryAxis, tol: f32) -> bool {
59 let mirrored = mirror_position(a, axis);
60 let dx = (mirrored[0] - b[0]).abs();
61 let dy = (mirrored[1] - b[1]).abs();
62 let dz = (mirrored[2] - b[2]).abs();
63 dx < tol && dy < tol && dz < tol
64}
65
66#[allow(dead_code)]
68pub fn symmetrize_position(
69 master: [f32; 3],
70 slave: [f32; 3],
71 axis: SymmetryAxis,
72 weight: f32,
73) -> [f32; 3] {
74 let w = weight.clamp(0.0, 1.0);
75 let target = mirror_position(master, axis);
76 [
77 slave[0] + (target[0] - slave[0]) * w,
78 slave[1] + (target[1] - slave[1]) * w,
79 slave[2] + (target[2] - slave[2]) * w,
80 ]
81}
82
83#[allow(dead_code)]
87pub fn apply_symmetry(
88 positions: &mut [[f32; 3]],
89 pairs: &[(usize, usize)],
90 params: &BodySymmetryV2Params,
91) {
92 for &(master, slave) in pairs {
93 if master < positions.len() && slave < positions.len() {
94 let m_pos = positions[master];
95 let s_pos = positions[slave];
96 let axis = params.axis;
97 let w = params.symmetry_weight;
98 if params.positive_side_master {
99 positions[slave] = symmetrize_position(m_pos, s_pos, axis, w);
100 } else {
101 positions[master] = symmetrize_position(s_pos, m_pos, axis, w);
102 }
103 }
104 }
105}
106
107#[allow(dead_code)]
109pub fn set_symmetry_weight(params: &mut BodySymmetryV2Params, value: f32) {
110 params.symmetry_weight = value.clamp(0.0, 1.0);
111}
112
113#[allow(dead_code)]
115pub fn reset_body_symmetry_v2(params: &mut BodySymmetryV2Params) {
116 *params = BodySymmetryV2Params::default();
117}
118
119#[allow(dead_code)]
121pub fn body_symmetry_v2_to_json(params: &BodySymmetryV2Params) -> String {
122 let axis = match params.axis {
123 SymmetryAxis::X => "X",
124 SymmetryAxis::Y => "Y",
125 SymmetryAxis::Z => "Z",
126 };
127 format!(
128 r#"{{"symmetry_weight":{:.4},"axis":"{}","tolerance":{:.6},"positive_master":{}}}"#,
129 params.symmetry_weight, axis, params.position_tolerance, params.positive_side_master
130 )
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_default() {
139 let p = BodySymmetryV2Params::default();
140 assert!((p.symmetry_weight - 1.0).abs() < 1e-6);
141 }
142
143 #[test]
144 fn test_mirror_x() {
145 let m = mirror_position([1.0, 2.0, 3.0], SymmetryAxis::X);
146 assert!((m[0] + 1.0).abs() < 1e-6);
147 assert!((m[1] - 2.0).abs() < 1e-6);
148 }
149
150 #[test]
151 fn test_mirror_y() {
152 let m = mirror_position([1.0, 2.0, 3.0], SymmetryAxis::Y);
153 assert!((m[1] + 2.0).abs() < 1e-6);
154 }
155
156 #[test]
157 fn test_mirror_z() {
158 let m = mirror_position([1.0, 2.0, 3.0], SymmetryAxis::Z);
159 assert!((m[2] + 3.0).abs() < 1e-6);
160 }
161
162 #[test]
163 fn test_are_mirror_pair_true() {
164 let a = [1.0f32, 0.0, 0.0];
165 let b = [-1.0f32, 0.0, 0.0];
166 assert!(are_mirror_pair(a, b, SymmetryAxis::X, 0.001));
167 }
168
169 #[test]
170 fn test_are_mirror_pair_false() {
171 let a = [1.0f32, 0.0, 0.0];
172 let b = [1.0f32, 0.0, 0.0];
173 assert!(!are_mirror_pair(a, b, SymmetryAxis::X, 0.001));
174 }
175
176 #[test]
177 fn test_symmetrize_full() {
178 let master = [1.0f32, 0.5, 0.5];
179 let slave = [0.0f32, 0.5, 0.5];
180 let result = symmetrize_position(master, slave, SymmetryAxis::X, 1.0);
181 assert!((result[0] + 1.0).abs() < 1e-6);
182 }
183
184 #[test]
185 fn test_apply_symmetry() {
186 let mut positions = [[1.0f32, 0.0, 0.0], [0.5f32, 0.0, 0.0]];
187 let params = BodySymmetryV2Params::default();
188 let pairs = vec![(0usize, 1usize)];
189 apply_symmetry(&mut positions, &pairs, ¶ms);
190 assert!((positions[1][0] + 1.0).abs() < 1e-6);
191 }
192
193 #[test]
194 fn test_set_weight_clamp() {
195 let mut p = BodySymmetryV2Params::default();
196 set_symmetry_weight(&mut p, 5.0);
197 assert!((p.symmetry_weight - 1.0).abs() < 1e-6);
198 }
199
200 #[test]
201 fn test_to_json() {
202 let j = body_symmetry_v2_to_json(&BodySymmetryV2Params::default());
203 assert!(j.contains("symmetry_weight"));
204 assert!(j.contains("axis"));
205 }
206}