1use crate::{result::RvResult, rverr};
2use image::{GenericImage, Pixel};
3use serde::{Deserialize, Serialize};
4use std::{
5 cmp::Ordering,
6 ops::{Add, Div, Mul, Neg, Sub},
7};
8
9pub trait Abs {
10 fn abs(self) -> Self;
11}
12pub trait Min {
13 fn min(self, other: Self) -> Self;
14}
15pub trait Max {
16 fn max(self, other: Self) -> Self;
17}
18
19impl<T> Min for T
20where
21 T: Calc,
22{
23 fn min(self, other: Self) -> Self {
24 min(self, other)
25 }
26}
27impl<T> Max for T
28where
29 T: Calc,
30{
31 fn max(self, other: Self) -> Self {
32 max(self, other)
33 }
34}
35
36macro_rules! impl_trait {
37 ($trait_name:ident, $method:ident, $($T:ty),+) => {
38 $(impl $trait_name for $T {
39 fn $method(self) -> Self {
40 self.$method()
41 }
42 })+
43 };
44}
45impl Abs for TPtI {
46 fn abs(self) -> Self {
47 self
48 }
49}
50impl_trait!(Abs, abs, f32, f64, i32, i64);
51
52pub trait CoordinateBox {
53 fn size_addon() -> Self;
54 fn is_close_to(&self, other: Self) -> bool;
55}
56
57impl CoordinateBox for TPtI {
58 fn size_addon() -> Self {
59 Self::one()
60 }
61 fn is_close_to(&self, other: Self) -> bool {
62 *self == other
63 }
64}
65impl CoordinateBox for TPtS {
66 fn size_addon() -> Self {
67 1
68 }
69 fn is_close_to(&self, other: Self) -> bool {
70 *self == other
71 }
72}
73impl CoordinateBox for TPtF {
74 fn size_addon() -> Self {
75 TPtF::zero()
76 }
77 fn is_close_to(&self, other: Self) -> bool {
78 floats_close(*self, other)
79 }
80}
81
82pub trait Calc:
83 Add<Output = Self>
84 + Sub<Output = Self>
85 + Mul<Output = Self>
86 + Div<Output = Self>
87 + Sized
88 + PartialOrd
89 + Abs
90 + From<u32>
91 + Clone
92 + Copy
93{
94 #[must_use]
95 fn one() -> Self {
96 Self::from(1)
97 }
98 #[must_use]
99 fn zero() -> Self {
100 Self::from(0)
101 }
102}
103impl<T> Calc for T where
104 T: Add<Output = Self>
105 + Sub<Output = Self>
106 + Mul<Output = Self>
107 + Div<Output = Self>
108 + Sized
109 + PartialOrd
110 + Abs
111 + From<u32>
112 + Clone
113 + Copy
114{
115}
116
117fn floats_close(x: TPtF, y: TPtF) -> bool {
118 (x - y).abs() < 1e-10
119}
120
121pub fn min_from_partial<T>(x1: &T, x2: &T) -> Ordering
122where
123 T: PartialOrd,
124{
125 match x1.partial_cmp(x2) {
126 Some(o) => o,
127 None => Ordering::Less,
128 }
129}
130pub fn max_from_partial<T>(x1: &T, x2: &T) -> Ordering
131where
132 T: PartialOrd,
133{
134 match x1.partial_cmp(x2) {
135 Some(o) => o,
136 None => Ordering::Greater,
137 }
138}
139
140pub fn min<T>(x1: T, x2: T) -> T
141where
142 T: PartialOrd,
143{
144 match min_from_partial(&x1, &x2) {
145 Ordering::Greater => x2,
146 _ => x1,
147 }
148}
149pub fn max<T>(x1: T, x2: T) -> T
150where
151 T: PartialOrd,
152{
153 match max_from_partial(&x1, &x2) {
154 Ordering::Less => x2,
155 _ => x1,
156 }
157}
158
159pub type ShapeI = Shape<u32>;
160pub type ShapeF = Shape<f64>;
161
162impl From<ShapeI> for ShapeF {
163 fn from(value: ShapeI) -> Self {
164 Self {
165 w: f64::from(value.w),
166 h: f64::from(value.h),
167 }
168 }
169}
170impl From<ShapeF> for ShapeI {
171 fn from(value: ShapeF) -> Self {
172 Self {
173 w: value.w as u32,
174 h: value.h as u32,
175 }
176 }
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
180pub struct Shape<T>
181where
182 T: Calc,
183{
184 pub w: T,
185 pub h: T,
186}
187impl<T> Shape<T>
188where
189 T: Calc,
190{
191 pub fn new(w: T, h: T) -> Self {
192 Self { w, h }
193 }
194 pub fn rot90_with_image_ntimes(&self, n: u8) -> Self {
195 if n % 2 == 0 {
196 *self
197 } else {
198 Self {
199 w: self.h,
200 h: self.w,
201 }
202 }
203 }
204}
205
206impl ShapeI {
207 pub fn from_im<I>(im: &I) -> Self
208 where
209 I: GenericImage,
210 {
211 Self {
212 w: im.width(),
213 h: im.height(),
214 }
215 }
216}
217
218impl From<[usize; 2]> for ShapeI {
219 fn from(value: [usize; 2]) -> Self {
220 Self::new(value[0] as u32, value[1] as u32)
221 }
222}
223
224impl<T> From<(T, T)> for Shape<T>
225where
226 T: Calc,
227{
228 fn from(value: (T, T)) -> Self {
229 Self {
230 w: value.0,
231 h: value.1,
232 }
233 }
234}
235
236#[derive(Clone, Copy)]
237pub enum OutOfBoundsMode<T>
238where
239 T: Calc,
240{
241 Deny,
242 Resize(Shape<T>), }
244
245#[must_use]
246pub fn dist_lineseg_point(ls: &(PtF, PtF), p: PtF) -> f64 {
247 let (p1, p2) = ls;
248 let p1 = *p1;
249 let p2 = *p2;
250 let d = (p1 - p2).len_square().sqrt();
251 let n = (p1 - p2) / d;
252 let proj = p1 + n * (p - p1).dot(&n);
253 if proj.x >= p1.x.min(p2.x)
254 && proj.x <= p1.x.max(p2.x)
255 && proj.y >= p1.y.min(p2.y)
256 && proj.y <= p1.y.max(p2.y)
257 {
258 (p - proj).len_square().sqrt()
259 } else {
260 (p - p1).len_square().min((p - p2).len_square()).sqrt()
261 }
262}
263pub fn max_squaredist<'a, T, I1, I2>(points1: I1, points2: I2) -> (Point<T>, Point<T>, T)
264where
265 T: Calc,
266 I1: Iterator<Item = Point<T>> + 'a + Clone,
267 I2: Iterator<Item = Point<T>> + 'a + Clone,
268{
269 points1
270 .map(|p1| {
271 points2
272 .clone()
273 .map(|p2| {
274 let dist_x = unsigned_dist(p2.x, p1.x);
275 let dist_y = unsigned_dist(p2.y, p1.y);
276 let d = dist_x * dist_x + dist_y * dist_y;
277 (p1, p2, d)
278 })
279 .max_by(|(_, _, d1), (_, _, d2)| max_from_partial(d1, d2))
280 .unwrap()
281 })
282 .max_by(|(_, _, d1), (_, _, d2)| max_from_partial(d1, d2))
283 .unwrap()
284}
285
286#[cfg(test)]
287#[macro_export]
288macro_rules! point {
289 ($x:literal, $y:literal) => {{
290 if $x < 0.0 || $y < 0.0 {
291 panic!("cannot create point from negative coords, {}, {}", $x, $y);
292 }
293 $crate::domain::PtF { x: $x, y: $y }
294 }};
295}
296
297#[macro_export]
298macro_rules! impl_point_into {
299 ($T:ty) => {
300 impl From<PtI> for ($T, $T) {
301 fn from(p: PtI) -> Self {
302 (p.x as $T, p.y as $T)
303 }
304 }
305 impl From<PtF> for ($T, $T) {
306 fn from(p: PtF) -> Self {
307 (p.x as $T, p.y as $T)
308 }
309 }
310 impl From<($T, $T)> for PtF {
311 fn from((x, y): ($T, $T)) -> Self {
312 Self {
313 x: x as f64,
314 y: y as f64,
315 }
316 }
317 }
318 impl From<($T, $T)> for PtI {
319 fn from((x, y): ($T, $T)) -> Self {
320 Self {
321 x: x as u32,
322 y: y as u32,
323 }
324 }
325 }
326 };
327}
328
329fn unsigned_dist<T>(x1: T, x2: T) -> T
330where
331 T: Sub<Output = T> + PartialOrd,
332{
333 if x1 > x2 {
334 x1 - x2
335 } else {
336 x2 - x1
337 }
338}
339
340pub fn clamp_sub_zero<T>(x1: T, x2: T) -> T
341where
342 T: Calc,
343{
344 if x1 < x2 {
345 T::zero()
346 } else {
347 x1 - x2
348 }
349}
350#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
351pub struct Point<T> {
352 pub x: T,
353 pub y: T,
354}
355
356impl<T> Point<T>
357where
358 T: Calc,
359{
360 pub fn len_square(&self) -> T {
361 self.x * self.x + self.y * self.y
362 }
363 pub fn dist_square(&self, other: &Self) -> T
364 where
365 T: PartialOrd,
366 {
367 <(T, T) as Into<Point<T>>>::into((
368 unsigned_dist(self.x, other.x),
370 unsigned_dist(self.y, other.y),
371 ))
372 .len_square()
373 }
374 pub fn dot(&self, rhs: &Self) -> T {
375 self.x * rhs.x + self.y * rhs.y
376 }
377
378 fn rot90(&self, w: u32) -> Self
379 where
380 T: CoordinateBox,
381 {
382 Self {
383 x: self.y,
384 y: T::from(w) - self.x - T::size_addon(),
385 }
386 }
387
388 pub fn rot90_with_image(&self, shape: ShapeI) -> Self
390 where
391 T: Neg<Output = T> + CoordinateBox,
392 {
393 self.rot90(shape.w)
394 }
395 pub fn rot90_with_image_ntimes(&self, shape: ShapeI, n: u8) -> Self
396 where
397 T: Neg<Output = T> + CoordinateBox,
398 {
399 if n > 0 {
400 let mut p = self.rot90_with_image(shape);
401 for i in 1..n {
402 let shape = shape.rot90_with_image_ntimes(i);
403 p = p.rot90_with_image(shape);
404 }
405 p
406 } else {
407 *self
408 }
409 }
410
411 pub fn is_close_to(&self, other: Self) -> bool
412 where
413 T: CoordinateBox,
414 {
415 self.x.is_close_to(other.x) && self.y.is_close_to(other.y)
416 }
417}
418
419impl<T> Mul<T> for Point<T>
420where
421 T: Calc,
422{
423 type Output = Self;
424 fn mul(self, rhs: T) -> Self::Output {
425 Point {
426 x: self.x * rhs,
427 y: self.y * rhs,
428 }
429 }
430}
431impl<T> Mul for Point<T>
432where
433 T: Calc,
434{
435 type Output = Self;
436 fn mul(self, rhs: Self) -> Self::Output {
437 Point {
438 x: self.x * rhs.x,
439 y: self.y * rhs.y,
440 }
441 }
442}
443impl<T> Div<T> for Point<T>
444where
445 T: Calc,
446{
447 type Output = Self;
448 fn div(self, rhs: T) -> Self::Output {
449 Point {
450 x: self.x / rhs,
451 y: self.y / rhs,
452 }
453 }
454}
455impl<T> Div for Point<T>
456where
457 T: Calc,
458{
459 type Output = Self;
460 fn div(self, rhs: Self) -> Self::Output {
461 Point {
462 x: self.x / rhs.x,
463 y: self.y / rhs.y,
464 }
465 }
466}
467
468impl<T> Sub for Point<T>
469where
470 T: Calc,
471{
472 type Output = Point<T>;
473 fn sub(self, rhs: Self) -> Self::Output {
474 Point {
475 x: self.x - rhs.x,
476 y: self.y - rhs.y,
477 }
478 }
479}
480impl<T> Add for Point<T>
481where
482 T: Calc,
483{
484 type Output = Point<T>;
485 fn add(self, rhs: Self) -> Self::Output {
486 Point {
487 x: self.x + rhs.x,
488 y: self.y + rhs.y,
489 }
490 }
491}
492
493impl<T> From<(T, T)> for Point<T>
494where
495 T: Calc,
496{
497 fn from(value: (T, T)) -> Self {
498 Self {
499 x: value.0,
500 y: value.1,
501 }
502 }
503}
504impl<T> From<Point<T>> for (T, T)
505where
506 T: Calc,
507{
508 fn from(p: Point<T>) -> (T, T) {
509 (p.x, p.y)
510 }
511}
512impl_point_into!(i32);
513pub type TPtF = f64;
514pub type TPtI = u32;
515pub type TPtS = i64;
516pub type PtF = Point<TPtF>;
517pub type PtI = Point<TPtI>;
518pub type PtS = Point<TPtS>;
519
520impl PtF {
521 #[must_use]
522 pub fn round_signed(&self) -> Point<i32> {
523 Point {
524 x: self.x.round() as i32,
525 y: self.y.round() as i32,
526 }
527 }
528}
529
530impl PtI {
531 pub fn from_signed(p: (i32, i32)) -> RvResult<Self> {
532 if p.0 < 0 || p.1 < 0 {
533 Err(rverr!(
534 "cannot create point with negative coordinates, {:?}",
535 p
536 ))
537 } else {
538 Ok(Self {
539 x: p.0 as u32,
540 y: p.1 as u32,
541 })
542 }
543 }
544 pub fn equals<U>(&self, other: (U, U)) -> bool
545 where
546 U: PartialEq,
547 PtI: Into<(U, U)>,
548 {
549 <Self as Into<(U, U)>>::into(*self) == other
550 }
551}
552
553impl From<PtI> for PtF {
554 fn from(p: PtI) -> Self {
555 (f64::from(p.x), f64::from(p.y)).into()
556 }
557}
558impl From<PtI> for PtS {
559 fn from(p: PtI) -> Self {
560 (TPtS::from(p.x), TPtS::from(p.y)).into()
561 }
562}
563impl From<PtS> for PtI {
564 fn from(p: PtS) -> Self {
565 ((p.x as u32), (p.y as u32)).into()
566 }
567}
568impl From<PtF> for PtI {
569 fn from(p: PtF) -> Self {
570 ((p.x as u32), (p.y as u32)).into()
571 }
572}
573impl From<(u32, u32)> for PtF {
574 fn from(x: (u32, u32)) -> Self {
575 (f64::from(x.0), f64::from(x.1)).into()
576 }
577}
578impl From<(f32, f32)> for PtI {
579 fn from(x: (f32, f32)) -> Self {
580 ((x.0 as u32), (x.1 as u32)).into()
581 }
582}
583impl From<(usize, usize)> for PtI {
584 fn from(x: (usize, usize)) -> Self {
585 ((x.0 as u32), (x.1 as u32)).into()
586 }
587}
588
589impl From<PtI> for (usize, usize) {
590 fn from(p: PtI) -> Self {
591 (p.x as usize, p.y as usize)
592 }
593}
594
595#[derive(Clone, Copy, Debug, PartialEq)]
596pub struct Circle {
597 pub center: PtF,
598 pub radius: TPtF,
599}
600
601pub fn color_with_intensity<CLR>(mut color: CLR, intensity: f64) -> CLR
602where
603 CLR: Pixel<Subpixel = u8>,
604{
605 let channels = color.channels_mut();
606 for channel in channels {
607 *channel = (f64::from(*channel) * intensity) as u8;
608 }
609 color
610}
611#[test]
612fn test_rot() {
613 let shape = Shape::new(5, 3);
614 let p = PtS { x: 2, y: 1 };
615 let p_rot_1 = p.rot90_with_image(shape);
616 assert!(p_rot_1.is_close_to(PtS { x: 1, y: 2 }));
617 let p_rot_2 = p.rot90_with_image_ntimes(shape, 2);
618 let p_rot_2_ = p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1));
619 assert_eq!(p_rot_2, p_rot_2_);
620
621 let p = PtF { x: 2.5, y: 1.0 };
622 let p_rot_1 = p.rot90_with_image(shape);
623 assert!(p_rot_1.is_close_to(PtF { x: 1.0, y: 2.5 }));
624 assert!(p
625 .rot90_with_image_ntimes(shape, 2)
626 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
627
628 let shape = Shape::new(5, 10);
629 let p = PtS { x: 1, y: 2 };
630 let p_rot_1 = p.rot90_with_image(shape);
631 assert!(p_rot_1.is_close_to(PtS { x: 2, y: 3 }));
632 assert!(p
633 .rot90_with_image_ntimes(shape, 2)
634 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
635 let p = PtF { x: 1.0, y: 2.0 };
636 let p_rot_1 = p.rot90_with_image(shape);
637 assert!(p_rot_1.is_close_to(PtF { x: 2.0, y: 4.0 }));
638 assert!(p
639 .rot90_with_image_ntimes(shape, 2)
640 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
641 let p = PtF { x: 2.0, y: 4.0 };
642 let p_rot_1 = p.rot90_with_image(shape);
643 assert!(p_rot_1.is_close_to(PtF { x: 4.0, y: 3.0 }));
644 assert!(p
645 .rot90_with_image_ntimes(shape, 2)
646 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1))));
647}