rust_3d/
functions.rs

1/*
2Copyright 2016 Martin Buck
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"),
6to deal in the Software without restriction, including without limitation the
7rights to use, copy, modify, merge, publish, distribute, sublicense,
8and/or sell copies of the Software, and to permit persons to whom the Software
9is furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall
12be included all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
20OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21*/
22
23//! utility functions
24
25use std::{cmp::Ordering, ops::Sub};
26
27use crate::*;
28
29//------------------------------------------------------------------------------
30
31//@todo move these functions to better fitting files or make them methods of the correct types
32
33/// Returns the center of two IsBuildable2D
34#[inline(always)]
35pub fn center_2d<P>(p1: &P, p2: &P) -> P
36where
37    P: IsBuildable2D,
38{
39    P::new(
40        p1.x() + (p2.x() - p1.x()) / 2.0,
41        p1.y() + (p2.y() - p1.y()) / 2.0,
42    )
43}
44
45/// Returns the center of two IsBuildable3D
46#[inline(always)]
47pub fn center_3d<P>(p1: &P, p2: &P) -> P
48where
49    P: IsBuildable3D,
50{
51    P::new(
52        p1.x() + (p2.x() - p1.x()) / 2.0,
53        p1.y() + (p2.y() - p1.y()) / 2.0,
54        p1.z() + (p2.z() - p1.z()) / 2.0,
55    )
56}
57
58/// Returns the cross product between a Is3D and a IsBuildable3D
59#[inline(always)]
60pub fn cross<P, U>(first: &P, other: &U) -> U
61where
62    P: Is3D,
63    U: IsBuildable3D,
64{
65    let x = first.y() * other.z() - first.z() * other.y();
66    let y = first.z() * other.x() - first.x() * other.z();
67    let z = first.x() * other.y() - first.y() * other.x();
68    U::new(x, y, z)
69}
70
71/// Compares two IsBuildable3D at a given dimensions
72pub fn dimension_compare<P1, P2>(lhs: &P1, rhs: &P2, dim: i8) -> Result<Ordering>
73where
74    P1: Is3D,
75    P2: Is3D,
76{
77    match dim {
78        0 => lhs
79            .x()
80            .partial_cmp(&rhs.x())
81            .ok_or(ErrorKind::ComparisionFailed),
82        1 => lhs
83            .y()
84            .partial_cmp(&rhs.y())
85            .ok_or(ErrorKind::ComparisionFailed),
86        2 => lhs
87            .z()
88            .partial_cmp(&rhs.z())
89            .ok_or(ErrorKind::ComparisionFailed),
90        _ => Err(ErrorKind::DimensionsDontMatch),
91    }
92}
93
94/// Calculates the distance within a given dimension between two IsBuildable3D
95pub fn dimension_dist<P1, P2>(lhs: &P1, rhs: &P2, dim: i8) -> Result<f64>
96where
97    P1: Is3D,
98    P2: Is3D,
99{
100    match dim {
101        0 => Ok((lhs.x() - rhs.x()).abs()),
102        1 => Ok((lhs.y() - rhs.y()).abs()),
103        2 => Ok((lhs.z() - rhs.z()).abs()),
104        _ => Err(ErrorKind::DimensionsDontMatch),
105    }
106}
107
108/// Helper function to sort a Vec of Is2D by x
109pub fn sort_vec_2d_x<P>(xs: &mut Vec<P>)
110where
111    P: Is2D,
112{
113    xs.sort_by(|a, b| {
114        a.x()
115            .partial_cmp(&b.x())
116            .or_else(|| a.y().partial_cmp(&b.y()))
117            .unwrap_or(Ordering::Equal)
118    });
119}
120
121/// Helper function to sort a Vec of Is2D by y
122pub fn sort_vec_2d_y<P>(xs: &mut Vec<P>)
123where
124    P: Is2D,
125{
126    xs.sort_by(|a, b| {
127        a.y()
128            .partial_cmp(&b.y())
129            .or_else(|| a.x().partial_cmp(&b.x()))
130            .unwrap_or(Ordering::Equal)
131    });
132}
133
134/// Helper function to sort a Vec of Is3D by x
135pub fn sort_vec_3d_x<P>(xs: &mut Vec<P>)
136where
137    P: Is3D,
138{
139    xs.sort_by(|a, b| {
140        a.x()
141            .partial_cmp(&b.x())
142            .or_else(|| a.y().partial_cmp(&b.y()))
143            .or_else(|| a.z().partial_cmp(&b.z()))
144            .unwrap_or(Ordering::Equal)
145    });
146}
147
148/// Helper function to sort a Vec of Is3D by y
149pub fn sort_vec_3d_y<P>(xs: &mut Vec<P>)
150where
151    P: Is3D,
152{
153    xs.sort_by(|a, b| {
154        a.y()
155            .partial_cmp(&b.y())
156            .or_else(|| a.z().partial_cmp(&b.z()))
157            .or_else(|| a.x().partial_cmp(&b.x()))
158            .unwrap_or(Ordering::Equal)
159    });
160}
161
162/// Helper function to sort a Vec of Is3D by z
163pub fn sort_vec_3d_z<P>(xs: &mut Vec<P>)
164where
165    P: Is3D,
166{
167    xs.sort_by(|a, b| {
168        a.z()
169            .partial_cmp(&b.z())
170            .or_else(|| a.x().partial_cmp(&b.x()))
171            .or_else(|| a.y().partial_cmp(&b.y()))
172            .unwrap_or(Ordering::Equal)
173    });
174}
175
176//@todo move to plane or use there
177/// Extrudes a 2D point cloud into 3D space with a given center and direction
178pub fn extrude<P2, P3>(pc2d: &Vec<P2>, dir: &P3) -> (PointCloud3D<P3>, PointCloud3D<P3>)
179where
180    P2: IsTransFormableTo3D,
181    P3: IsBuildable3D + IsMovable3D + Clone,
182{
183    let mut pc_3d_a = PointCloud3D::new();
184    let mut pc_3d_b = PointCloud3D::new();
185
186    for p in pc2d {
187        let p_transformed = p.transform_to_3d::<P3>(0.0);
188        pc_3d_a.push(p_transformed.clone());
189        pc_3d_b.push(p_transformed);
190    }
191
192    pc_3d_b.move_by(dir.x(), dir.y(), dir.z());
193    (pc_3d_a, pc_3d_b)
194}
195
196//@todo rename or overload operators
197//@todo implement for 2D as well, maybe move to traits
198/// Calculates the vector between two positions
199#[inline(always)]
200pub fn conn<P>(p_from: &P, p_to: &P) -> P
201where
202    P: IsBuildable3D,
203{
204    P::new(
205        p_to.x() - p_from.x(),
206        p_to.y() - p_from.y(),
207        p_to.z() - p_from.z(),
208    )
209}
210
211/// Positions the object in such a way that its center is at origin
212pub fn center<T>(x: &mut T)
213where
214    T: HasBoundingBox3DMaybe + IsMovable3D,
215{
216    if let Ok(bb) = x.bounding_box_maybe() {
217        let center = bb.center_bb();
218        x.move_by(-center.x(), -center.y(), -center.z());
219    }
220}
221
222/// Scales the object to the required size
223pub fn set_size<T>(x: &mut T, size: [Positive; 3])
224where
225    T: HasBoundingBox3DMaybe + IsMatrix4Transformable,
226{
227    if let Ok(bb) = x.bounding_box_maybe() {
228        let m = Matrix4::scale(
229            size[0].get() / bb.size_x().get(),
230            size[1].get() / bb.size_y().get(),
231            size[2].get() / bb.size_z().get(),
232        );
233        x.transform(&m);
234    }
235}
236
237/// Collects all intersections between a ray and mesh
238pub fn collect_intersections_ray_mesh<P, M>(ray: &Ray3D, mesh: &M, intersections: &mut Vec<P>)
239where
240    M: IsMesh<P, Face3>,
241    P: IsBuildable3D + Sub<Output = P> + Clone,
242{
243    let nf = mesh.num_faces();
244
245    for i in 0..nf {
246        let [v1, v2, v3] = mesh.face_vertices(FId { val: i }).unwrap(); // safe
247                                                                        //println!("face_vertices");
248        if let Some(intersection) = intersection_ray_triangle(ray, &v1, &v2, &v3) {
249            intersections.push(intersection);
250        }
251    }
252}
253
254//@todo more generic types?
255/// Finds the intersection between a ray and triangle
256pub fn intersection_ray_triangle<P>(ray: &Ray3D, v1: &P, v2: &P, v3: &P) -> Option<P>
257where
258    P: IsBuildable3D + Sub<Output = P> + Clone,
259{
260    let orig = &ray.line.anchor;
261    let dir = &ray.line.dir;
262    let n = normal_of_face(v1, v2, v3);
263
264    let w1 = orig - v1;
265    let a = -n.dot(&w1);
266    let b = n.dot(dir);
267
268    if b == 0.0 {
269        return None;
270    } //@todo eps
271
272    let r = a / b;
273
274    if r <= 0.0 {
275        return None;
276    }
277
278    let p = orig + (dir * r);
279
280    let e1 = v2.clone() - v1.clone();
281    let vp1 = &p - v1;
282    if n.dot(&cross(&e1, &vp1)) <= 0.0 {
283        return None;
284    }
285
286    let e2 = v3.clone() - v2.clone();
287    let vp2 = &p - v2;
288    if n.dot(&cross(&e2, &vp2)) <= 0.0 {
289        return None;
290    }
291
292    let e3 = v1.clone() - v3.clone();
293    let vp3 = &p - v3;
294    if n.dot(&cross(&e3, &vp3)) <= 0.0 {
295        return None;
296    }
297
298    Some(P::new_from(&p))
299}
300
301/// Applies the function to each intersection candidate
302pub fn for_each_intersecting<'c, I, HB>(
303    ray: &Ray3D,
304    hbs: I,
305    f: &mut dyn FnMut(&Point3D, &'c mut HB),
306) where
307    I: Iterator<Item = &'c mut HB>,
308    HB: HasBoundingBox3DMaybe,
309{
310    for hb in hbs {
311        if let Ok(bb) = hb.bounding_box_maybe() {
312            if let Some(i) = intersection(&ray.line, &bb) {
313                f(&i, hb)
314            }
315        }
316    }
317}
318
319/// Returns the closest intersection with the ray
320pub fn closest_intersecting_mut<'c, I, HB>(ray: &Ray3D, hbs: I) -> Option<(Point3D, &'c mut HB)>
321where
322    I: Iterator<Item = &'c mut HB>,
323    HB: HasBoundingBox3DMaybe,
324{
325    let mut result: Option<(Point3D, &'c mut HB)> = None;
326
327    for hb in hbs {
328        if let Ok(bb) = hb.bounding_box_maybe() {
329            if let Some(i) = intersection(&ray.line, &bb) {
330                if let Some(r) = &result {
331                    if sqr_dist_3d(&ray.line.anchor, &i) < sqr_dist_3d(&ray.line.anchor, &r.0) {
332                        result = Some((i, hb))
333                    }
334                } else {
335                    result = Some((i, hb))
336                }
337            }
338        }
339    }
340
341    result
342}
343
344/// Returns the closest intersection with the ray
345pub fn closest_intersecting<'c, I, HB>(ray: &Ray3D, hbs: I) -> Option<(Point3D, &'c HB)>
346where
347    I: Iterator<Item = &'c HB>,
348    HB: HasBoundingBox3DMaybe,
349{
350    let mut result: Option<(Point3D, &'c HB)> = None;
351
352    for hb in hbs {
353        if let Ok(bb) = hb.bounding_box_maybe() {
354            if let Some(i) = intersection(&ray.line, &bb) {
355                if let Some(r) = &result {
356                    if sqr_dist_3d(&ray.line.anchor, &i) < sqr_dist_3d(&ray.line.anchor, &r.0) {
357                        result = Some((i, hb))
358                    }
359                } else {
360                    result = Some((i, hb))
361                }
362            }
363        }
364    }
365
366    result
367}
368
369/// Returns the index of the closest intersection with the ray
370pub fn index_closest_intersecting<'c, I, HB>(ray: &Ray3D, hbs: I) -> Option<(Point3D, usize)>
371where
372    I: Iterator<Item = &'c HB>,
373    HB: 'c + HasBoundingBox3DMaybe,
374{
375    let mut result: Option<(Point3D, usize)> = None;
376
377    for (i, hb) in hbs.enumerate() {
378        if let Ok(bb) = hb.bounding_box_maybe() {
379            if let Some(inter) = intersection(&ray.line, &bb) {
380                if let Some(r) = &result {
381                    if sqr_dist_3d(&ray.line.anchor, &inter) < sqr_dist_3d(&ray.line.anchor, &r.0) {
382                        result = Some((inter, i))
383                    }
384                } else {
385                    result = Some((inter, i))
386                }
387            }
388        }
389    }
390
391    result
392}
393
394/// Calculates the normal of a face given by three vertices
395pub fn normal_of_face<P>(v1: &P, v2: &P, v3: &P) -> Norm3D
396where
397    P: IsBuildable3D,
398{
399    let v12 = conn(v1, v2);
400    let v23 = conn(v2, v3);
401    Norm3D::new(cross(&v12, &v23)).unwrap_or(Norm3D::norm_z())
402}
403
404/// Projects a point onto a plane
405pub fn project_point_on_plane<PL, P2, P3, N>(plane: &PL, point: &P3) -> P2
406where
407    PL: IsPlane3D<P3, N>,
408    P2: IsBuildable2D,
409    P3: IsBuildable3D + IsTransFormableTo2D,
410    N: IsNormalized3D,
411{
412    let relative = conn(&plane.origin(), point);
413    let mut p2transf = point.transform_to_2d::<P2>();
414    let mut tmp = Point2D::default();
415
416    tmp.set_x(plane.u().dot(&relative));
417    tmp.set_y(plane.v().dot(&relative));
418
419    p2transf.from(&tmp);
420    p2transf
421}
422
423/// Minimum of two f64 values
424pub fn min64(a: f64, b: f64) -> f64 {
425    if a < b {
426        a
427    } else {
428        b
429    }
430}
431
432/// Maximum of two f64 values
433pub fn max64(a: f64, b: f64) -> f64 {
434    if a > b {
435        a
436    } else {
437        b
438    }
439}
440
441//@todo better location and as trait?
442/// The intersection between a line and BoundingBox if there is any
443pub fn intersection(l: &Line3D, b: &BoundingBox3D) -> Option<Point3D> {
444    let inv_dir = [1.0 / l.dir.x(), 1.0 / l.dir.y(), 1.0 / l.dir.z()];
445    let min = b.min_p();
446    let max = b.max_p();
447
448    let tx1 = (min.x() - l.anchor.x()) * inv_dir[0];
449    let tx2 = (max.x() - l.anchor.x()) * inv_dir[0];
450
451    let mut tmin = min64(tx1, tx2);
452    let mut tmax = max64(tx1, tx2);
453
454    let ty1 = (min.y() - l.anchor.y()) * inv_dir[1];
455    let ty2 = (max.y() - l.anchor.y()) * inv_dir[1];
456
457    tmin = max64(tmin, min64(ty1, ty2));
458    tmax = min64(tmax, max64(ty1, ty2));
459
460    let tz1 = (min.z() - l.anchor.z()) * inv_dir[2];
461    let tz2 = (max.z() - l.anchor.z()) * inv_dir[2];
462
463    tmin = max64(tmin, min64(tz1, tz2));
464    tmax = min64(tmax, max64(tz1, tz2));
465
466    if tmax >= tmin && tmax >= 0.0 {
467        Some(&l.anchor + &l.dir * tmin)
468    } else {
469        None
470    }
471}