kepler_ra/techtron/
graphic.rs

1// MIT License
2
3// Copyright (c) 2023 Techtron-Lab
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23
24
25pub mod marching_squares;
26pub mod marching_cubes;
27pub mod line;
28pub mod contour;
29
30use std::{cmp, collections::HashMap, ops::Index};
31
32use approx::relative_eq;
33use nalgebra::partial_max;
34use num::traits::{real::Real, Num, Pow, Zero};
35
36// #[cfg(target_family = "wasm")]
37use wasm_bindgen::prelude::wasm_bindgen;
38
39#[cfg(target_family = "wasm")]
40use js_sys::{Array, ArrayBuffer, Float64Array};
41
42#[cfg(target_family = "wasm")]
43use wasm_bindgen::JsValue;
44
45use crate::console;
46
47#[derive(Debug, Clone, Copy, cmp::PartialEq)]
48pub struct Point<T = f32, const N: usize = 2>
49where
50    T: Num + cmp::PartialOrd + Copy,
51{
52    data: [T; N],
53}
54
55impl<T, const N: usize> Point<T, N>
56where
57    T: Num + cmp::PartialOrd + Copy,
58{
59    pub fn new(data: [T; N]) -> Point<T, N> {
60        Point { data }
61    }
62}
63
64#[cfg(target_family = "wasm")]
65impl<T, const N: usize> Point<T, N>
66where
67    T: Num + cmp::PartialOrd + Copy + Into<f64>,
68{
69    pub fn to_array(&self) -> Array {
70        let mut v = Array::new();
71        for i in 0..N {
72            v.push(&JsValue::from(Into::<f64>::into(self[i])));
73        }
74        v
75    }
76}
77
78pub enum PointLineSegmentRelation {
79    On,
80    OnRayAB,
81    OnRayBA,
82    Appart,
83}
84
85impl<T> Point<T, 2>
86where
87    T: num::Num + cmp::PartialOrd + Copy + Into<f64> + approx::AbsDiffEq,
88{
89    pub fn relative_to(
90        &self,
91        line_segment: &LineSegment<T, 2>,
92        epsilon: f64,
93    ) -> PointLineSegmentRelation {
94        let x0 = line_segment.p0[0];
95        let y0 = line_segment.p0[1];
96        let x1 = line_segment.p1[0];
97        let y1 = line_segment.p1[1];
98        let dx = x0 - x1;
99        let dy = y0 - y1;
100
101        if dx == T::zero() {
102            // parallel to y axis
103            if approx::abs_diff_eq!(self[0], x0) {
104                if y0 < y1 {
105                    if self[1] > y1 {
106                        return PointLineSegmentRelation::OnRayAB;
107                    } else if self[1] < y0 {
108                        return PointLineSegmentRelation::OnRayBA;
109                    } else {
110                        return PointLineSegmentRelation::On;
111                    }
112                } else if y0 > y1 {
113                    if self[1] > y0 {
114                        return PointLineSegmentRelation::OnRayBA;
115                    } else if self[1] < y1 {
116                        return PointLineSegmentRelation::OnRayAB;
117                    } else {
118                        return PointLineSegmentRelation::On;
119                    }
120                } else {
121                    unreachable!()
122                }
123            }
124        }
125
126        if dy == T::zero() {
127            // parallel to x axis
128            if approx::abs_diff_eq!(self[1], y0) {
129                if x0 < x1 {
130                    if self[0] > x1 {
131                        return PointLineSegmentRelation::OnRayAB;
132                    } else if self[0] < x0 {
133                        return PointLineSegmentRelation::OnRayBA;
134                    } else {
135                        return PointLineSegmentRelation::On;
136                    }
137                } else if x0 > x1 {
138                    if self[0] > x0 {
139                        return PointLineSegmentRelation::OnRayBA;
140                    } else if self[0] < x1 {
141                        return PointLineSegmentRelation::OnRayAB;
142                    } else {
143                        return PointLineSegmentRelation::On;
144                    }
145                } else {
146                    unreachable!()
147                }
148            }
149        }
150
151        // now k shall not be zero
152        //           dy
153        // y = y0 + ---- * (x - x0)
154        //           dx
155        let y: f64 = y0.into() + dy.into() / dx.into() * (self[0].into() - x0.into());
156        if approx::abs_diff_eq!(y, self[1].into(), epsilon = epsilon) {
157            if x0 < x1 {
158                if self[0] > x1 {
159                    return PointLineSegmentRelation::OnRayAB;
160                } else if self[0] < x0 {
161                    return PointLineSegmentRelation::OnRayBA;
162                } else {
163                    return PointLineSegmentRelation::On;
164                }
165            } else if x0 > x1 {
166                if self[0] > x0 {
167                    return PointLineSegmentRelation::OnRayBA;
168                } else if self[0] < x1 {
169                    return PointLineSegmentRelation::OnRayAB;
170                } else {
171                    return PointLineSegmentRelation::On;
172                }
173            } else {
174                unreachable!()
175            }
176        }
177        return PointLineSegmentRelation::Appart;
178    }
179
180    pub fn is_on(&self, line_segment: &LineSegment<T, 2>, epsilon: f64) -> bool {
181        if let PointLineSegmentRelation::On = self.relative_to(line_segment, epsilon) {
182            return true;
183        }
184        return false;
185    }
186}
187
188type Point2D<T> = Point<T, 2>;
189type Point3D<T> = Point<T, 3>;
190
191impl<T, const N: usize> Index<usize> for Point<T, N>
192where
193    T: Num + cmp::PartialOrd + Copy,
194{
195    type Output = T;
196
197    fn index(&self, index: usize) -> &Self::Output {
198        &self.data[index]
199    }
200}
201
202struct PointInPlace<'a, T = f32, const N: usize = 2>
203where
204    T: Num + cmp::PartialOrd + Copy,
205{
206    data: &'a [T; N],
207}
208
209impl<'a, T, const N: usize> PointInPlace<'a, T, N>
210where
211    T: Num + cmp::PartialOrd + Copy,
212{
213    pub fn new(data: &'a [T; N]) -> PointInPlace<'a, T, N> {
214        PointInPlace { data }
215    }
216}
217
218impl<'a, T, const N: usize> Index<usize> for PointInPlace<'a, T, N>
219where
220    T: Num + cmp::PartialOrd + Copy,
221{
222    type Output = T;
223
224    fn index(&self, index: usize) -> &Self::Output {
225        &self.data[index]
226    }
227}
228
229#[derive(Debug, Clone, Copy, cmp::PartialEq)]
230pub struct LineSegment<T = f32, const N: usize = 2>
231where
232    T: Num + cmp::PartialOrd + Copy,
233{
234    p0: Point<T, N>,
235    p1: Point<T, N>,
236}
237
238impl<T, const N: usize> LineSegment<T, N>
239where
240    T: Num + cmp::PartialOrd + Copy,
241{
242    pub fn new(p0: Point<T, N>, p1: Point<T, N>) -> LineSegment<T, N> {
243        LineSegment { p0, p1 }
244    }
245}
246
247impl<T, const N: usize> LineSegment<T, N>
248where
249    T: Num + cmp::PartialOrd + Copy + Into<f64>,
250{
251    #[cfg(target_family = "wasm")]
252    pub fn to_array(&self) -> Array {
253        let mut v = Array::new();
254        v.push(&self.p0.to_array());
255        v.push(&self.p1.to_array());
256        v
257    }
258}
259
260fn is_intersect_2d_impl<T>(p0: &[T; 2], p1: &[T; 2], p2: &[T; 2], p3: &[T; 2], epsilon: f64) -> bool
261where
262    T: num::Num + cmp::PartialOrd + Copy + Into<f64> + approx::AbsDiffEq,
263{
264    let x0 = p0[0];
265    let y0 = p0[1];
266    let x1 = p1[0];
267    let y1 = p1[1];
268    let x2 = p2[0];
269    let y2 = p2[1];
270    let x3 = p3[0];
271    let y3 = p3[1];
272    let dx0 = x0 - x1;
273    let dy0 = y0 - y1;
274    let dx1 = x2 - x3;
275    let dy1 = y2 - y3;
276
277    if approx::abs_diff_eq!(p0[0], p1[0]) && approx::abs_diff_eq!(p2[0], p3[0]) {
278        // parallel to y axis
279        if approx::abs_diff_ne!(x0, x2) {
280            return false;
281        } else {
282            return !(partial_ord_max(y0, y1) < partial_ord_min(y2, y3)
283                || partial_ord_min(y0, y1) > partial_ord_max(y2, y3));
284        }
285    }
286
287    if approx::abs_diff_eq!(p0[1], p1[1]) && approx::abs_diff_eq!(p2[1], p3[1]) {
288        // parallel to x axis
289        return !(partial_ord_max(x0, x1) < partial_ord_min(x2, x3)
290            || partial_ord_min(x0, x1) > partial_ord_max(x2, x3));
291    }
292
293    let y0_: f64 = y2.into() + dy1.into() / dx1.into() * (x0.into() - x2.into());
294    let y1_: f64 = y2.into() + dy1.into() / dx1.into() * (x1.into() - x2.into());
295    let y2_: f64 = y0.into() + dy0.into() / dx0.into() * (x2.into() - x0.into());
296    let y3_: f64 = y0.into() + dy0.into() / dx0.into() * (x3.into() - x0.into());
297    if (y0.into() > y0_ && y1.into() > y1_) || (y0.into() < y0_ && y1.into() < y1_) {
298        return false;
299    }
300    if (y2.into() > y2_ && y3.into() > y3_) || (y2.into() < y2_ && y3.into() < y3_) {
301        return false;
302    }
303    return true;
304}
305
306impl<T> LineSegment<T, 2>
307where
308    T: num::Num + cmp::PartialOrd + Copy + Into<f64> + approx::AbsDiffEq,
309{
310    pub fn is_intersect(&self, rhs: &LineSegment<T, 2>, epsilon: f64) -> bool {
311        is_intersect_2d_impl(
312            &self.p0.data,
313            &self.p1.data,
314            &rhs.p0.data,
315            &rhs.p1.data,
316            epsilon,
317        )
318    }
319}
320
321fn partial_ord_max<T>(a: T, b: T) -> T
322where
323    T: PartialOrd,
324{
325    if a > b {
326        a
327    } else {
328        b
329    }
330}
331
332fn partial_ord_min<T>(a: T, b: T) -> T
333where
334    T: PartialOrd,
335{
336    if a < b {
337        a
338    } else {
339        b
340    }
341}
342
343#[derive(Debug)]
344struct LineSegmentInPlace<'a, T = f32, const N: usize = 2>
345where
346    T: Num + cmp::PartialOrd + Copy,
347{
348    p0: &'a Point<T, N>,
349    p1: &'a Point<T, N>,
350}
351
352impl<'a, T, const N: usize> LineSegmentInPlace<'a, T, N>
353where
354    T: Num + cmp::PartialOrd + Copy,
355{
356    pub fn new(p0: &'a Point<T, N>, p1: &'a Point<T, N>) -> LineSegmentInPlace<'a, T, N> {
357        LineSegmentInPlace { p0, p1 }
358    }
359}
360
361impl<'a, T> LineSegmentInPlace<'a, T, 2>
362where
363    T: Num + cmp::PartialOrd + Copy + Into<f64> + approx::AbsDiffEq,
364{
365    pub fn is_intersect(&self, rhs: &LineSegmentInPlace<'a, T, 2>, epsilon: f64) -> bool {
366        is_intersect_2d_impl(
367            &self.p0.data,
368            &self.p1.data,
369            &rhs.p0.data,
370            &rhs.p1.data,
371            epsilon,
372        )
373    }
374}
375
376pub trait Distance {
377    type Output;
378    fn distance(&self, rhs: &Self) -> Self::Output;
379}
380
381impl<T, const N: usize> Distance for Point<T, N>
382where
383    T: Real,
384{
385    type Output = T;
386    fn distance(&self, rhs: &Self) -> Self::Output {
387        let sum: T = T::zero();
388        for i in 0..N {
389            sum == sum + (self[i] - rhs[i]).powi(2);
390        }
391        sum.sqrt()
392    }
393}
394
395struct Base<T = f32, const N: usize = 2> {
396    // Row major
397    data: [[T; N]; N],
398}
399
400#[inline]
401pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
402    if relative_eq!(t, 0.) || t < 0. {
403        return a;
404    } else if relative_eq!(t, 1.) || t > 1. {
405        return b;
406    } else {
407        a + (b - a) * t
408    }
409}
410
411#[derive(Debug, Clone)]
412pub struct LineSegments<T = f32, const N: usize = 2>
413where
414    T: Copy + Num + cmp::PartialOrd,
415{
416    data: Vec<LineSegment<T, N>>,
417}
418
419impl<T, const N: usize> LineSegments<T, N>
420where
421    T: Copy + Num + cmp::PartialOrd,
422{
423    pub fn new() -> LineSegments<T, N> {
424        LineSegments {
425            data: Vec::<LineSegment<T, N>>::new(),
426        }
427    }
428
429    pub fn push(&mut self, line_segment: LineSegment<T, N>) {
430        self.data.push(line_segment);
431    }
432}
433
434
435#[cfg(test)]
436mod test {
437    use approx::{assert_abs_diff_eq, assert_relative_eq};
438
439    use super::*;
440    macro_rules! setup_line_segment {
441        ($p0: expr, $p1: expr, $p2: expr, $p3: expr, $epsilon: expr, $t: expr) => {
442            let p0 = Point2D::new($p0);
443            let p1 = Point2D::new($p1);
444            let p2 = Point2D::new($p2);
445            let p3 = Point2D::new($p3);
446            let line_segment0 = LineSegment::new(p0, p1);
447            let line_segment1 = LineSegment::new(p2, p3);
448            assert_eq!(line_segment0.is_intersect(&line_segment1, $epsilon), $t);
449        };
450    }
451    #[test]
452    fn test_line_segments() {
453        let p0 = Point2D::new([0, 0]);
454        // let p0 = Point2D::<i32>::new([0, 0]);
455        let p1 = Point2D::<i32>::new([30, 30]);
456        let p2 = Point2D::<i32>::new([10, 15]);
457        let p3 = Point2D::<i32>::new([20, 21]);
458        let line_segment0 = LineSegment::new(p0, p1);
459        assert_eq!(p2.is_on(&line_segment0, 0.5), false);
460
461        let line_segment1 = LineSegment::new(p2, p3);
462        assert_eq!(line_segment0.is_intersect(&line_segment1, 0.5), false);
463
464        let p0 = Point2D::<f32>::new([0., 0.]);
465        let p1 = Point2D::<f32>::new([30., 30.]);
466        let p2 = Point2D::<f32>::new([15., 15.]);
467        let line_segment0 = LineSegment::new(p0, p1);
468        assert_eq!(p2.is_on(&line_segment0, 0.5), true);
469
470        setup_line_segment!([0, 0], [30, 30], [10, 15], [20, 21], 0.5, false);
471        setup_line_segment!([0., 0.], [30., 30.], [10., 15.], [20., 19.], 0.5, true);
472        setup_line_segment!([-10., -15.], [30., 25.], [10., 5.], [10., 19.], 0.1, true);
473        setup_line_segment!(
474            [-10., -15.],
475            [30., 25.],
476            [10., 5.01],
477            [10., 109.],
478            0.001,
479            false
480        );
481        setup_line_segment!([10., 0.], [10., 30.], [10., 30.001], [10., 50.], 0.5, false);
482        setup_line_segment!([10., 0.], [10., 30.], [10., 15.], [10., 19.], 0.5, true);
483
484        let p0 = Point2D::<f32>::new([1., 0.]);
485        let p1 = Point2D::<f32>::new([1., 30.]);
486        let p2 = Point2D::<f32>::new([1., 15.]);
487        let line_segment0 = LineSegment::new(p0, p1);
488        assert_eq!(p2.is_on(&line_segment0, 0.5), true);
489
490        let p0 = Point2D::<f32>::new([1., 0.]);
491        let p1 = Point2D::<f32>::new([1., 30.]);
492        let p2 = Point2D::<f32>::new([1., 30.001]);
493        let line_segment0 = LineSegment::new(p0, p1);
494        assert_eq!(p2.is_on(&line_segment0, 0.5), false);
495    }
496
497
498    
499
500}