1use crate::model::structure::Structure;
6use crate::model::types::Point;
7use crate::utils::parallel::*;
8use nalgebra::{Rotation3, Vector3};
9
10pub struct Transform;
16
17impl Transform {
18 pub fn translate(structure: &mut Structure, x: f64, y: f64, z: f64) {
27 let translation = Vector3::new(x, y, z);
28 structure.par_residues_mut().for_each(|residue| {
29 for atom in residue.iter_atoms_mut() {
30 atom.translate_by(&translation);
31 }
32 });
33 }
34
35 pub fn center_geometry(structure: &mut Structure, target: Option<Point>) {
44 let current_center = structure.geometric_center();
45 let target_point = target.unwrap_or(Point::origin());
46 let translation = target_point - current_center;
47
48 structure.par_residues_mut().for_each(|residue| {
49 for atom in residue.iter_atoms_mut() {
50 atom.translate_by(&translation);
51 }
52 });
53 }
54
55 pub fn center_mass(structure: &mut Structure, target: Option<Point>) {
65 let current_com = structure.center_of_mass();
66 let target_point = target.unwrap_or(Point::origin());
67 let translation = target_point - current_com;
68
69 structure.par_residues_mut().for_each(|residue| {
70 for atom in residue.iter_atoms_mut() {
71 atom.translate_by(&translation);
72 }
73 });
74 }
75
76 pub fn rotate_x(structure: &mut Structure, radians: f64) {
83 let rotation = Rotation3::from_axis_angle(&Vector3::x_axis(), radians);
84 Self::apply_rotation(structure, rotation);
85 }
86
87 pub fn rotate_y(structure: &mut Structure, radians: f64) {
94 let rotation = Rotation3::from_axis_angle(&Vector3::y_axis(), radians);
95 Self::apply_rotation(structure, rotation);
96 }
97
98 pub fn rotate_z(structure: &mut Structure, radians: f64) {
105 let rotation = Rotation3::from_axis_angle(&Vector3::z_axis(), radians);
106 Self::apply_rotation(structure, rotation);
107 }
108
109 pub fn rotate_euler(structure: &mut Structure, x_rad: f64, y_rad: f64, z_rad: f64) {
118 let rotation = Rotation3::from_euler_angles(x_rad, y_rad, z_rad);
119 Self::apply_rotation(structure, rotation);
120 }
121
122 fn apply_rotation(structure: &mut Structure, rotation: Rotation3<f64>) {
124 structure.par_residues_mut().for_each(|residue| {
125 for atom in residue.iter_atoms_mut() {
126 atom.pos = rotation * atom.pos;
127 }
128 });
129
130 if let Some(box_vecs) = structure.box_vectors {
131 let v1 = Vector3::from(box_vecs[0]);
132 let v2 = Vector3::from(box_vecs[1]);
133 let v3 = Vector3::from(box_vecs[2]);
134
135 let v1_rot = rotation * v1;
136 let v2_rot = rotation * v2;
137 let v3_rot = rotation * v3;
138
139 structure.box_vectors = Some([v1_rot.into(), v2_rot.into(), v3_rot.into()]);
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::Transform;
147 use crate::model::{
148 atom::Atom,
149 chain::Chain,
150 residue::Residue,
151 structure::Structure,
152 types::{Element, Point, ResidueCategory, StandardResidue},
153 };
154
155 fn structure_with_points(points: &[Point]) -> Structure {
156 let mut chain = Chain::new("A");
157 let mut residue = Residue::new(
158 1,
159 None,
160 "GLY",
161 Some(StandardResidue::GLY),
162 ResidueCategory::Standard,
163 );
164
165 for (idx, point) in points.iter().enumerate() {
166 let name = format!("C{}", idx);
167 residue.add_atom(Atom::new(&name, Element::C, *point));
168 }
169
170 chain.add_residue(residue);
171 let mut structure = Structure::new();
172 structure.add_chain(chain);
173 structure
174 }
175
176 fn assert_point_close(actual: &Point, expected: &Point) {
177 assert!((actual.x - expected.x).abs() < 1e-6);
178 assert!((actual.y - expected.y).abs() < 1e-6);
179 assert!((actual.z - expected.z).abs() < 1e-6);
180 }
181
182 #[test]
183 fn translate_moves_all_atoms_by_vector() {
184 let mut structure =
185 structure_with_points(&[Point::new(0.0, 0.0, 0.0), Point::new(1.0, 2.0, 3.0)]);
186
187 Transform::translate(&mut structure, 5.0, -2.0, 1.5);
188
189 let mut atoms = structure.iter_atoms();
190 assert_point_close(&atoms.next().unwrap().pos, &Point::new(5.0, -2.0, 1.5));
191 assert_point_close(&atoms.next().unwrap().pos, &Point::new(6.0, 0.0, 4.5));
192 }
193
194 #[test]
195 fn center_geometry_moves_geometric_center_to_target() {
196 let mut structure =
197 structure_with_points(&[Point::new(2.0, 0.0, 0.0), Point::new(4.0, 0.0, 0.0)]);
198
199 Transform::center_geometry(&mut structure, Some(Point::new(10.0, 0.0, 0.0)));
200
201 let center = structure.geometric_center();
202 assert_point_close(¢er, &Point::new(10.0, 0.0, 0.0));
203 }
204
205 #[test]
206 fn center_mass_moves_center_of_mass_to_origin_by_default() {
207 let mut structure =
208 structure_with_points(&[Point::new(2.0, 0.0, 0.0), Point::new(4.0, 0.0, 0.0)]);
209
210 {
211 let chain = structure.chain_mut("A").unwrap();
212 let residue = chain.residue_mut(1, None).unwrap();
213 residue
214 .iter_atoms_mut()
215 .enumerate()
216 .for_each(|(idx, atom)| {
217 atom.element = if idx == 0 { Element::H } else { Element::O };
218 });
219 }
220
221 Transform::center_mass(&mut structure, None);
222
223 let com = structure.center_of_mass();
224 assert_point_close(&com, &Point::origin());
225 }
226
227 #[test]
228 fn rotate_z_rotates_atoms_about_origin() {
229 let mut structure =
230 structure_with_points(&[Point::new(1.0, 0.0, 0.0), Point::new(0.0, 2.0, 0.0)]);
231
232 Transform::rotate_z(&mut structure, std::f64::consts::FRAC_PI_2);
233
234 let mut atoms = structure.iter_atoms();
235 assert_point_close(&atoms.next().unwrap().pos, &Point::new(0.0, 1.0, 0.0));
236 assert_point_close(&atoms.next().unwrap().pos, &Point::new(-2.0, 0.0, 0.0));
237 }
238
239 #[test]
240 fn rotate_euler_updates_box_vectors() {
241 let mut structure = structure_with_points(&[Point::new(1.0, 0.0, 0.0)]);
242 structure.box_vectors = Some([[1.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 3.0]]);
243
244 Transform::rotate_euler(&mut structure, 0.0, 0.0, std::f64::consts::FRAC_PI_2);
245
246 let box_vectors = structure.box_vectors.unwrap();
247 assert_point_close(&Point::from(box_vectors[0]), &Point::new(0.0, 1.0, 0.0));
248 assert_point_close(&Point::from(box_vectors[1]), &Point::new(-2.0, 0.0, 0.0));
249 assert_point_close(&Point::from(box_vectors[2]), &Point::new(0.0, 0.0, 3.0));
250 }
251}