1use 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
92pub 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
111pub 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
147pub 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 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
236pub 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 let normal_out = {
261 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 if (winding_number - 1.0).abs() < 0.5 {
271 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 {
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 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 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}