Skip to main content

del_msh_core/
polyloop2.rs

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