1use geop_core_math::{primitives::Motion, vector::Vector3};
2
3use crate::{aabb::compute_aabb, nurb_curve::transform_control_point};
4
5use super::NurbSurface;
6
7impl NurbSurface<4> {
8 pub fn translate(&self, offset: Vector3) -> Self {
13 let control_points: Vec<_> = self
14 .control_points
15 .iter()
16 .map(|cp| {
17 let w = cp[3];
18 geop_core_math::vector::Vector4::from_array([
19 cp[0].add(offset[0].mul(w)),
20 cp[1].add(offset[1].mul(w)),
21 cp[2].add(offset[2].mul(w)),
22 w,
23 ])
24 })
25 .collect();
26 let aabb = compute_aabb(&control_points);
27 Self {
28 degree_u: self.degree_u,
29 degree_v: self.degree_v,
30 num_u: self.num_u,
31 num_v: self.num_v,
32 control_points,
33 knot_vector_u: self.knot_vector_u.clone(),
34 knot_vector_v: self.knot_vector_v.clone(),
35 aabb,
39 }
40 }
41
42 pub fn transform(&self, motion: &Motion) -> Self {
53 let mut surface = Self {
54 degree_u: self.degree_u,
55 degree_v: self.degree_v,
56 num_u: self.num_u,
57 num_v: self.num_v,
58 control_points: self
59 .control_points
60 .iter()
61 .map(|cp| transform_control_point(cp, motion))
62 .collect(),
63 knot_vector_u: self.knot_vector_u.clone(),
64 knot_vector_v: self.knot_vector_v.clone(),
65 aabb: self.aabb,
66 };
67 surface.recompute_aabb();
68 surface
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use geop_core_math::{primitives::Motion, scalars::Scalar, vector::Vector3};
75
76 use super::super::NurbSurface3D;
77
78 #[test]
81 fn mirror_moves_points_and_turns_normals() {
82 let p = |x: f64, y: f64, z: f64| {
83 geop_core_math::vector::Vector4::from_array([
84 Scalar::from_f64(x),
85 Scalar::from_f64(y),
86 Scalar::from_f64(z),
87 Scalar::ONE,
88 ])
89 };
90 let surface = NurbSurface3D::try_new(
91 1,
92 1,
93 vec![p(0., 0., 0.), p(0., 2., 1.), p(2., 0., 1.), p(2., 2., 0.)],
94 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
95 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
96 )
97 .unwrap();
98 let v = |x: f64, y: f64, z: f64| Vector3::from_array([x, y, z].map(Scalar::from_f64));
99 let mirror = Motion::mirror(&v(1.0, 0.5, -0.25), &v(1.0, 2.0, -0.5)).unwrap();
100 let mirrored = surface.transform(&mirror);
101 let (u, w) = (Scalar::from_f64(0.3), Scalar::from_f64(0.7));
102 let a = mirrored.evaluate(u, w).unwrap();
103 let b = mirror.apply(&surface.evaluate(u, w).unwrap());
104 assert!(a.could_be_equal(&b), "{a:?} vs {b:?}");
105 let n = mirrored.normal(u, w).unwrap();
106 let m = mirror.rotate(&surface.normal(u, w).unwrap()).neg();
107 assert!(n.could_be_equal(&m), "{n:?} vs {m:?}");
108 }
109
110 #[test]
114 fn a_surface_shifted_boxes_its_points_as_shifted() {
115 let p = |x: f64, y: f64, z: f64, w: f64| {
116 geop_core_math::vector::Vector4::from_array(
117 [x * w, y * w, z * w, w].map(Scalar::from_f64),
118 )
119 };
120 let surface = NurbSurface3D::try_new(
121 1,
122 1,
123 vec![
124 p(0.1, 0., 0.3, 0.7),
125 p(0., 2.2, 1., 1.3),
126 p(1.9, 0., 1., 0.9),
127 p(2., 2., 0.1, 1.),
128 ],
129 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
130 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
131 )
132 .unwrap();
133 let by = Vector3::from_array([1.7, -0.3, 1e3].map(Scalar::from_f64));
134 let shifted = surface.translate(by);
135 let mut boxed = shifted.clone();
136 boxed.recompute_aabb();
137 assert_eq!(shifted, boxed);
138 }
139
140 #[test]
141 fn translate_shifts_evaluated_points() {
142 let p = |x: f64, y: f64, z: f64| {
143 geop_core_math::vector::Vector4::from_array([
144 Scalar::from_f64(x),
145 Scalar::from_f64(y),
146 Scalar::from_f64(z),
147 Scalar::ONE,
148 ])
149 };
150 let surface = NurbSurface3D::try_new(
151 1,
152 1,
153 vec![
154 p(0.0, 0.0, 0.0),
155 p(0.0, 1.0, 0.0),
156 p(1.0, 0.0, 0.0),
157 p(1.0, 1.0, 0.0),
158 ],
159 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
160 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
161 )
162 .unwrap();
163 let offset = Vector3::from_array([
164 Scalar::from_f64(2.0),
165 Scalar::from_f64(3.0),
166 Scalar::from_f64(-1.0),
167 ]);
168 let shifted = surface.translate(offset);
169 let a = shifted
170 .evaluate(Scalar::from_f64(0.3), Scalar::from_f64(0.7))
171 .unwrap();
172 let b = surface
173 .evaluate(Scalar::from_f64(0.3), Scalar::from_f64(0.7))
174 .unwrap()
175 .add(&offset);
176 assert!(a.could_be_equal(&b));
177 }
178
179 #[test]
181 fn place_moves_evaluated_points() {
182 use geop_core_math::{primitives::Pose, scalars::Scalar};
183 let p = |x: f64, y: f64, z: f64, w: f64| {
184 geop_core_math::vector::Vector4::from_array(
185 [x * w, y * w, z * w, w].map(Scalar::from_f64),
186 )
187 };
188 let surface = NurbSurface3D::try_new(
189 1,
190 1,
191 vec![
192 p(0.0, 0.0, 0.0, 1.0),
193 p(0.0, 1.0, 0.0, 2.0),
194 p(1.0, 0.0, 1.0, 1.0),
195 p(1.0, 1.0, 0.0, 1.0),
196 ],
197 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
198 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
199 )
200 .unwrap();
201 let pose = Pose::from_euler(
202 Vector3::from_array([1.0, -2.0, 0.5].map(Scalar::from_f64)),
203 [30.0, 10.0, -45.0].map(Scalar::from_f64),
204 )
205 .unwrap();
206 let placed = surface.transform(&pose.motion());
207 let (u, v) = (Scalar::from_f64(0.3), Scalar::from_f64(0.7));
208 let a = placed.evaluate(u, v).unwrap();
209 let b = pose.apply(&surface.evaluate(u, v).unwrap());
210 assert!(a.could_be_equal(&b));
211 }
212}