Skip to main content

del_msh_cpu/
polyloop2.rs

1//! methods for 2D poly loop
2
3use num_traits::AsPrimitive;
4use rand::RngExt;
5
6pub fn winding_number<Real>(vtx2xy: &[Real], p: &[Real; 2]) -> Real
7where
8    Real: num_traits::Float + num_traits::FloatConst,
9{
10    let num_vtx = vtx2xy.len() / 2;
11    let mut wn: Real = Real::zero();
12    for i in 0..num_vtx {
13        let j = (i + 1) % num_vtx;
14        wn = wn
15            + del_geo_core::edge2::winding_number(
16                arrayref::array_ref![vtx2xy, i * 2, 2],
17                arrayref::array_ref![vtx2xy, j * 2, 2],
18                p,
19            );
20    }
21    wn
22}
23
24pub fn is_include_a_point<Real>(vtx2xy: &[Real], p: &[Real; 2]) -> bool
25where
26    Real: num_traits::Float + num_traits::FloatConst,
27{
28    let wn = winding_number(vtx2xy, p);
29    let one = Real::one();
30    let thres = one / (one + one + one + one + one);
31    if (wn - one).abs() < thres {
32        return true;
33    }
34    false
35}
36
37pub fn is_include_polyloop2<Real>(vtx2xy_outside: &[Real], vtx2xy_inside: &[Real]) -> bool
38where
39    Real: num_traits::Float + num_traits::FloatConst + std::fmt::Debug,
40{
41    dbg!("todo: make");
42    let mut is_out = false;
43    let one = Real::one();
44    let thres = one / (one + one + one + one + one);
45    for xy_in in vtx2xy_inside.chunks(2) {
46        let xy_in = [xy_in[0], xy_in[1]];
47        let wn = winding_number(vtx2xy_outside, &xy_in);
48        if (wn - one).abs() > thres {
49            is_out = true
50        }
51    }
52    is_out
53}
54
55pub fn maximum_penetration_of_included_point2s<Real>(
56    vtx2xy_outside: &[Real],
57    vtx2xy_inside: &[Real],
58) -> Option<([Real; 2], [Real; 2])>
59where
60    Real: num_traits::Float + num_traits::FloatConst + 'static + std::fmt::Debug,
61    usize: AsPrimitive<Real>,
62{
63    let zero = Real::zero();
64    let one = Real::one();
65    let thres = one / (one + one + one + one + one);
66    let mut dist_min: Option<Real> = None;
67    let mut pos_outside_min = [zero; 2];
68    let mut pos_inside_min = [zero; 2];
69    for xy_in in vtx2xy_inside.chunks(2) {
70        let xy_in = [xy_in[0], xy_in[1]];
71        let wn = winding_number(vtx2xy_outside, &xy_in);
72        if (wn - one).abs() < thres {
73            continue;
74        }
75        let (_lcoord, po) = nearest_to_point(vtx2xy_outside, &xy_in).unwrap();
76        let dist = del_geo_core::edge2::length(&xy_in, &po);
77        let is_update = if let Some(dist_min) = dist_min {
78            dist > dist_min
79        } else {
80            true
81        };
82        if is_update {
83            dist_min = Some(dist);
84            pos_outside_min = po;
85            pos_inside_min = xy_in;
86        }
87    }
88    let _dist_min = dist_min?;
89    Some((pos_outside_min, pos_inside_min))
90}
91
92/// area
93pub fn area<T>(vtx2xy: &[T]) -> T
94where
95    T: num_traits::Float,
96{
97    let num_vtx = vtx2xy.len() / 2;
98    assert_eq!(vtx2xy.len(), num_vtx * 2);
99    let zero = [T::zero(), T::zero()];
100    let mut area = T::zero();
101    for i_edge in 0..num_vtx {
102        let i0 = i_edge;
103        let i1 = (i_edge + 1) % num_vtx;
104        let p0 = arrayref::array_ref![vtx2xy, i0 * 2, 2];
105        let p1 = arrayref::array_ref![vtx2xy, i1 * 2, 2];
106        area = area + del_geo_core::tri2::area(&zero, p0, p1);
107    }
108    area
109}
110
111/// center of the gravity of a area bounded by this polyloop
112pub fn cog_as_face<T>(vtx2xy: &[T]) -> [T; 2]
113where
114    T: num_traits::Float + std::ops::AddAssign + std::ops::DivAssign,
115{
116    let frac_three = T::one() / (T::one() + T::one() + T::one());
117    let num_vtx = vtx2xy.len() / 2;
118    assert_eq!(vtx2xy.len(), num_vtx * 2);
119    let zero = [T::zero(); 2];
120    let mut area = T::zero();
121    let mut cog = [T::zero(); 2];
122    for i_edge in 0..num_vtx {
123        let i0 = i_edge;
124        let i1 = (i_edge + 1) % num_vtx;
125        let p0 = arrayref::array_ref![vtx2xy, i0 * 2, 2];
126        let p1 = arrayref::array_ref![vtx2xy, i1 * 2, 2];
127        let area0 = del_geo_core::tri2::area(&zero, p0, p1);
128        area += area0;
129        cog[0] += (p0[0] + p1[0]) * frac_three * area0;
130        cog[1] += (p0[1] + p1[1]) * frac_three * area0;
131    }
132    cog[0] /= area;
133    cog[1] /= area;
134    cog
135}
136
137#[test]
138fn test_cog_() {
139    let vtx2xy: Vec<f32> = vec![
140        -1.0, -5.0, -0.5, -5.0, 0.5, -5.0, 1.0, -5.0, 1.0, 5.0, -1.0, 5.0,
141    ];
142    let cog = cog_as_face(&vtx2xy);
143    assert!(cog[0].abs() < 1.0e-8);
144    assert!(cog[1].abs() < 1.0e-8);
145}
146
147/// star shape
148pub fn from_pentagram<Real>(center: &[Real], scale: Real) -> Vec<Real>
149where
150    Real: num_traits::Float + num_traits::FloatConst,
151{
152    let one = Real::one();
153    let two = one + one;
154    let three = two + one;
155    let five = two + three;
156    let dt: Real = Real::PI() / five;
157    let hp: Real = Real::FRAC_PI_2();
158    let ratio = two / (three + five.sqrt());
159    let mut xys = Vec::<Real>::new();
160    for i in 0..10usize {
161        let rad = if i % 2 == 0 { scale } else { ratio * scale };
162        let i = Real::from(rad).unwrap();
163        xys.push((dt * i + hp).cos() * rad + center[0]);
164        xys.push((dt * i + hp).sin() * rad + center[1]);
165    }
166    xys
167}
168
169pub fn from_circle(rad: f32, n: usize) -> Vec<f32> {
170    let mut vtx2xy = vec![0f32; 2 * n];
171    for i in 0..n {
172        let theta = std::f32::consts::PI * 2_f32 * i as f32 / n as f32;
173        vtx2xy[i * 2] = rad * f32::cos(theta);
174        vtx2xy[i * 2 + 1] = rad * f32::sin(theta);
175    }
176    vtx2xy
177}
178
179pub fn distance_to_point<Real>(vtx2xy: &[Real], g: &[Real; 2]) -> Option<Real>
180where
181    Real: num_traits::Float + std::fmt::Debug + 'static,
182    usize: AsPrimitive<Real>,
183{
184    let (_local_coord, pos) = nearest_to_point(vtx2xy, g)?;
185    let dist = del_geo_core::edge2::length(&pos, g);
186    Some(dist)
187}
188
189pub fn nearest_to_point<Real>(vtx2xy: &[Real], g: &[Real; 2]) -> Option<(Real, [Real; 2])>
190where
191    Real: num_traits::Float + std::fmt::Debug + 'static,
192    usize: AsPrimitive<Real>,
193{
194    // visit all the boudnary
195    let np = vtx2xy.len() / 2;
196    let mut dist_min: Option<Real> = None;
197    let mut p_near = [Real::zero(), Real::zero()];
198    let mut i_edge_min = usize::MAX;
199    let mut ratio_min = Real::zero();
200    for ip in 0..np {
201        let jp = (ip + 1) % np;
202        let pi = crate::vtx2xy::to_vec2(vtx2xy, ip);
203        let pj = crate::vtx2xy::to_vec2(vtx2xy, jp);
204        let (ratio, pos) = del_geo_core::edge2::nearest_to_point(pi, pj, g);
205        let dist = del_geo_core::edge2::length(&pos, g);
206        let is_update = if let Some(dist_min) = dist_min {
207            dist < dist_min
208        } else {
209            true
210        };
211        if is_update {
212            dist_min = Some(dist);
213            p_near = pos;
214            i_edge_min = ip;
215            ratio_min = ratio;
216        };
217    }
218    dist_min.map(|_dist_min| (i_edge_min.as_() + ratio_min, p_near))
219}
220
221pub fn moment_of_inertia(vtx2xy: &[f32], pivot: &[f32; 2]) -> f32 {
222    use del_geo_core::vec2;
223    let ne = vtx2xy.len() / 2;
224    let mut sum_i = 0.0;
225    for ie in 0..ne {
226        let ip0 = ie;
227        let ip1 = (ie + 1) % ne;
228        let p0 = [vtx2xy[ip0 * 2] - pivot[0], vtx2xy[ip0 * 2 + 1] - pivot[1]];
229        let p1 = [vtx2xy[ip1 * 2] - pivot[0], vtx2xy[ip1 * 2 + 1] - pivot[1]];
230        let a0 = vec2::area_quadrilateral(&p0, &p1) * 0.5;
231        sum_i += a0 * (vec2::dot(&p0, &p0) + vec2::dot(&p0, &p1) + vec2::dot(&p1, &p1));
232    }
233    sum_i * (1.0 / 6.0)
234}
235
236/// signed distance function
237/// * `vtx2xy` - flat array of coordinates
238/// * `q` - pont to be evaluated
239pub fn wdw_sdf(vtx2xy: &[f32], q: &[f32; 2]) -> (f32, [f32; 2]) {
240    use del_geo_core::vec2;
241    let nej = vtx2xy.len() / 2;
242    let mut min_dist = -1.0;
243    let mut winding_number = 0f32;
244    let mut pos_near = [0f32; 2];
245    let mut ie_near = 0;
246    for iej in 0..nej {
247        let ps = arrayref::array_ref!(vtx2xy, (iej % nej) * 2, 2);
248        let pe = arrayref::array_ref!(vtx2xy, ((iej + 1) % nej) * 2, 2);
249        winding_number += del_geo_core::edge2::winding_number(ps, pe, q);
250        let (_rm, pm) = del_geo_core::edge2::nearest_to_point(ps, pe, q);
251        let dist0 = del_geo_core::edge2::length(&pm, q);
252        if min_dist > 0. && dist0 > min_dist {
253            continue;
254        }
255        min_dist = dist0;
256        pos_near = pm;
257        ie_near = iej;
258    }
259    //
260    let normal_out = {
261        // if distance is small use edge's normal
262        let ps = arrayref::array_ref!(vtx2xy, (ie_near % nej) * 2, 2);
263        let pe = arrayref::array_ref!(vtx2xy, ((ie_near + 1) % nej) * 2, 2);
264        let ne = vec2::sub(pe, ps);
265        let ne = vec2::rotate(&ne, -std::f32::consts::PI * 0.5);
266        vec2::normalize(&ne)
267    };
268    //
269    // dbg!(winding_number);
270    if (winding_number - 1.0).abs() < 0.5 {
271        // inside
272        let normal = if min_dist < 1.0e-5 {
273            normal_out
274        } else {
275            vec2::normalize(&vec2::sub(&pos_near, q))
276        };
277        (-min_dist, normal)
278    } else {
279        let normal = if min_dist < 1.0e-5 {
280            normal_out
281        } else {
282            vec2::normalize(&vec2::sub(q, &pos_near))
283        };
284        (min_dist, normal)
285    }
286}
287
288#[test]
289fn test_polygon2_sdf() {
290    let vtx2xy = vec![0., 0., 1.0, 0.0, 1.0, 0.2, 0.0, 0.2];
291    use del_geo_core::vec2;
292    {
293        let (sdf, normal) = wdw_sdf(&vtx2xy, &[0.01, 0.1]);
294        assert!((sdf + 0.01).abs() < 1.0e-5);
295        assert!(vec2::length(&vec2::sub(&normal, &[-1., 0.])) < 1.0e-5);
296    }
297    {
298        let (sdf, normal) = wdw_sdf(&vtx2xy, &[-0.01, 0.1]);
299        assert!((sdf - 0.01).abs() < 1.0e-5);
300        assert!(vec2::length(&vec2::sub(&normal, &[-1., 0.])) < 1.0e-5);
301    }
302}
303
304pub fn to_uniform_density_random_points<Real>(
305    vtx2xy: &[Real],
306    cell_len: Real,
307    rng: &mut rand::rngs::StdRng,
308) -> Vec<Real>
309where
310    Real: num_traits::Float + num_traits::FloatConst + AsPrimitive<usize>,
311    rand::distr::StandardUniform: rand::distr::Distribution<Real>,
312    usize: AsPrimitive<Real>,
313{
314    let aabb = crate::vtx2xy::aabb2(vtx2xy);
315    use rand::RngExt;
316    let base_pos = [
317        aabb[0] - cell_len * rng.random::<Real>(),
318        aabb[1] - cell_len * rng.random::<Real>(),
319    ];
320    let nx = ((aabb[2] - base_pos[0]) / cell_len).as_() + 1;
321    let ny = ((aabb[3] - base_pos[1]) / cell_len).as_() + 1;
322    let mut res = vec![];
323    for ix in 0..nx {
324        for iy in 0..ny {
325            let x = base_pos[0] + (ix.as_() + rng.random::<Real>()) * cell_len;
326            let y = base_pos[1] + (iy.as_() + rng.random::<Real>()) * cell_len;
327            let is_inside = is_include_a_point(vtx2xy, &[x, y]);
328            if !is_inside {
329                continue;
330            }
331            res.push(x);
332            res.push(y);
333        }
334    }
335    res
336}
337
338#[allow(clippy::identity_op)]
339pub fn to_svg<Real>(vtx2xy: &[Real], transform: &[Real; 9]) -> String
340where
341    Real: std::fmt::Display + Copy + num_traits::Float,
342{
343    let mut res = String::new();
344    for ivtx in 0..vtx2xy.len() / 2 {
345        let x = vtx2xy[ivtx * 2 + 0];
346        let y = vtx2xy[ivtx * 2 + 1];
347        let a = del_geo_core::mat3_col_major::transform_homogeneous(transform, &[x, y]).unwrap();
348        res += format!("{} {}", a[0], a[1]).as_str();
349        if ivtx != vtx2xy.len() / 2 - 1 {
350            res += ",";
351        }
352    }
353    res
354}
355
356#[test]
357fn test_circle() {
358    let vtx2xy0 = from_circle(1.0, 300);
359    let arclen0 = crate::polyloop::arclength::<f32, 2>(&vtx2xy0);
360    assert!((arclen0 - 2. * std::f32::consts::PI).abs() < 1.0e-3);
361    //
362    {
363        let ndiv1 = 330;
364        let vtx2xy1 = crate::polyloop::resample::<f32, 2>(vtx2xy0.as_slice(), ndiv1);
365        assert_eq!(vtx2xy1.len(), ndiv1 * 2);
366        let arclen1 = crate::polyloop::arclength::<f32, 2>(vtx2xy1.as_slice());
367        assert!((arclen0 - arclen1).abs() < 1.0e-3);
368        let edge2length1 = crate::polyloop::edge2length::<f32, 2>(vtx2xy1.as_slice());
369        let min_edge_len1 = edge2length1
370            .iter()
371            .min_by(|a, b| a.partial_cmp(b).unwrap())
372            .unwrap();
373        assert!((min_edge_len1 - arclen1 / ndiv1 as f32).abs() < 1.0e-3);
374    }
375    {
376        let ndiv2 = 156;
377        let vtx2xy2 = crate::polyloop::resample::<f32, 2>(vtx2xy0.as_slice(), ndiv2);
378        assert_eq!(vtx2xy2.len(), ndiv2 * 2);
379        let arclen2 = crate::polyloop::arclength::<f32, 2>(vtx2xy2.as_slice());
380        assert!((arclen0 - arclen2).abs() < 1.0e-3);
381        let edge2length2 = crate::polyloop::edge2length::<f32, 2>(vtx2xy2.as_slice());
382        let min_edge_len2 = edge2length2
383            .iter()
384            .min_by(|a, b| a.partial_cmp(b).unwrap())
385            .unwrap();
386        assert!((min_edge_len2 - arclen2 / ndiv2 as f32).abs() < 1.0e-3);
387    }
388}
389
390pub fn meshing_to_trimesh2<Index, Real>(
391    vtxl2xy: &[Real],
392    edge_length_boundary: Real,
393    edge_length_internal: Real,
394) -> (Vec<Index>, Vec<Real>)
395where
396    Real: Copy
397        + 'static
398        + num_traits::Float
399        + AsPrimitive<usize>
400        + std::fmt::Display
401        + std::fmt::Debug,
402    Index: Copy + 'static,
403    f64: AsPrimitive<Real>,
404    usize: AsPrimitive<Real> + AsPrimitive<Index>,
405{
406    crate::trimesh2_dynamic::meshing_from_polyloop2::<Index, Real>(
407        vtxl2xy,
408        edge_length_boundary,
409        edge_length_internal,
410    )
411}
412
413pub fn poisson_disk_sampling<RNG>(
414    vtxl2xy: &[f32],
415    radius: f32,
416    num_iteration: usize,
417    reng: &mut RNG,
418) -> Vec<f32>
419where
420    RNG: rand::Rng,
421{
422    use del_geo_core::vec2::Vec2;
423    let (tri2vtx, vtx2xyz) =
424        crate::trimesh2_dynamic::meshing_from_polyloop2::<usize, f32>(vtxl2xy, -1., -1.);
425    let tri2cumarea = crate::trimesh::tri2cumsumarea(&tri2vtx, &vtx2xyz, 2);
426    let mut vtx2vectwo: Vec<[f32; 2]> = vec![];
427    for _iter in 0..num_iteration {
428        let (i_tri, r0, r1) =
429            crate::trimesh::sample_uniformly(&tri2cumarea, reng.random(), reng.random());
430        let pos = crate::trimesh::position_from_barycentric_coordinate::<f32, 2>(
431            &tri2vtx, &vtx2xyz, i_tri, r0, r1,
432        );
433        let mut is_near = false;
434        for pos0 in &vtx2vectwo {
435            // TODO: use kd-tree to accelerate this process
436            if pos0.sub(&pos).norm() > radius {
437                continue;
438            }
439            is_near = true;
440            break;
441        }
442        if is_near {
443            continue;
444        }
445        vtx2vectwo.push(pos);
446    }
447    use slice_of_array::SliceFlatExt;
448    vtx2vectwo.flat().to_vec()
449}
450
451#[test]
452fn test_poisson_disk_sampling() {
453    let mut reng = rand::rng();
454    let vtxl2xy = vec![0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
455    let vtx2xy = poisson_disk_sampling(&vtxl2xy, 0.1, 2000, &mut reng);
456    {
457        // write boundary and
458        let mut vtxl2xy = vtxl2xy.clone();
459        vtxl2xy.extend(vtx2xy);
460        crate::io_wavefront_obj::save_edge2vtx_vtx2xyz(
461            "../target/poisson_disk.obj",
462            &[0, 1, 1, 2, 2, 3, 3, 0],
463            &vtxl2xy,
464            2,
465        )
466        .unwrap();
467    }
468}