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.is_multiple_of(2) {
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 { x1 - x2 } else { x2 - x1 }
334}
335
336pub fn clamp_sub_zero<T>(x1: T, x2: T) -> T
337where
338 T: Calc,
339{
340 if x1 < x2 { T::zero() } else { x1 - x2 }
341}
342#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
343pub struct Point<T> {
344 pub x: T,
345 pub y: T,
346}
347
348impl<T> Point<T>
349where
350 T: Calc,
351{
352 pub fn len_square(&self) -> T {
353 self.x * self.x + self.y * self.y
354 }
355 pub fn dist_square(&self, other: &Self) -> T
356 where
357 T: PartialOrd,
358 {
359 <(T, T) as Into<Point<T>>>::into((
360 unsigned_dist(self.x, other.x),
362 unsigned_dist(self.y, other.y),
363 ))
364 .len_square()
365 }
366 pub fn dot(&self, rhs: &Self) -> T {
367 self.x * rhs.x + self.y * rhs.y
368 }
369
370 fn rot90(&self, w: u32) -> Self
371 where
372 T: CoordinateBox,
373 {
374 Self {
375 x: self.y,
376 y: T::from(w) - self.x - T::size_addon(),
377 }
378 }
379
380 pub fn rot90_with_image(&self, shape: ShapeI) -> Self
382 where
383 T: Neg<Output = T> + CoordinateBox,
384 {
385 self.rot90(shape.w)
386 }
387 pub fn rot90_with_image_ntimes(&self, shape: ShapeI, n: u8) -> Self
388 where
389 T: Neg<Output = T> + CoordinateBox,
390 {
391 if n > 0 {
392 let mut p = self.rot90_with_image(shape);
393 for i in 1..n {
394 let shape = shape.rot90_with_image_ntimes(i);
395 p = p.rot90_with_image(shape);
396 }
397 p
398 } else {
399 *self
400 }
401 }
402
403 pub fn is_close_to(&self, other: Self) -> bool
404 where
405 T: CoordinateBox,
406 {
407 self.x.is_close_to(other.x) && self.y.is_close_to(other.y)
408 }
409}
410
411impl<T> Mul<T> for Point<T>
412where
413 T: Calc,
414{
415 type Output = Self;
416 fn mul(self, rhs: T) -> Self::Output {
417 Point {
418 x: self.x * rhs,
419 y: self.y * rhs,
420 }
421 }
422}
423impl<T> Mul for Point<T>
424where
425 T: Calc,
426{
427 type Output = Self;
428 fn mul(self, rhs: Self) -> Self::Output {
429 Point {
430 x: self.x * rhs.x,
431 y: self.y * rhs.y,
432 }
433 }
434}
435impl<T> Div<T> for Point<T>
436where
437 T: Calc,
438{
439 type Output = Self;
440 fn div(self, rhs: T) -> Self::Output {
441 Point {
442 x: self.x / rhs,
443 y: self.y / rhs,
444 }
445 }
446}
447impl<T> Div for Point<T>
448where
449 T: Calc,
450{
451 type Output = Self;
452 fn div(self, rhs: Self) -> Self::Output {
453 Point {
454 x: self.x / rhs.x,
455 y: self.y / rhs.y,
456 }
457 }
458}
459
460impl<T> Sub for Point<T>
461where
462 T: Calc,
463{
464 type Output = Point<T>;
465 fn sub(self, rhs: Self) -> Self::Output {
466 Point {
467 x: self.x - rhs.x,
468 y: self.y - rhs.y,
469 }
470 }
471}
472impl<T> Add for Point<T>
473where
474 T: Calc,
475{
476 type Output = Point<T>;
477 fn add(self, rhs: Self) -> Self::Output {
478 Point {
479 x: self.x + rhs.x,
480 y: self.y + rhs.y,
481 }
482 }
483}
484
485impl<T> From<(T, T)> for Point<T>
486where
487 T: Calc,
488{
489 fn from(value: (T, T)) -> Self {
490 Self {
491 x: value.0,
492 y: value.1,
493 }
494 }
495}
496impl<T> From<Point<T>> for (T, T)
497where
498 T: Calc,
499{
500 fn from(p: Point<T>) -> (T, T) {
501 (p.x, p.y)
502 }
503}
504impl_point_into!(i32);
505pub type TPtF = f64;
506pub type TPtI = u32;
507pub type TPtS = i64;
508pub type PtF = Point<TPtF>;
509pub type PtI = Point<TPtI>;
510pub type PtS = Point<TPtS>;
511
512impl PtF {
513 #[must_use]
514 pub fn round_signed(&self) -> Point<i32> {
515 Point {
516 x: self.x.round() as i32,
517 y: self.y.round() as i32,
518 }
519 }
520}
521
522impl PtI {
523 pub fn from_signed(p: (i32, i32)) -> RvResult<Self> {
524 if p.0 < 0 || p.1 < 0 {
525 Err(rverr!(
526 "cannot create point with negative coordinates, {:?}",
527 p
528 ))
529 } else {
530 Ok(Self {
531 x: p.0 as u32,
532 y: p.1 as u32,
533 })
534 }
535 }
536 pub fn equals<U>(&self, other: (U, U)) -> bool
537 where
538 U: PartialEq,
539 PtI: Into<(U, U)>,
540 {
541 <Self as Into<(U, U)>>::into(*self) == other
542 }
543}
544
545impl From<PtI> for PtF {
546 fn from(p: PtI) -> Self {
547 (f64::from(p.x), f64::from(p.y)).into()
548 }
549}
550impl From<PtI> for PtS {
551 fn from(p: PtI) -> Self {
552 (TPtS::from(p.x), TPtS::from(p.y)).into()
553 }
554}
555impl From<PtS> for PtI {
556 fn from(p: PtS) -> Self {
557 ((p.x as u32), (p.y as u32)).into()
558 }
559}
560impl From<PtF> for PtI {
561 fn from(p: PtF) -> Self {
562 ((p.x as u32), (p.y as u32)).into()
563 }
564}
565impl From<(u32, u32)> for PtF {
566 fn from(x: (u32, u32)) -> Self {
567 (f64::from(x.0), f64::from(x.1)).into()
568 }
569}
570impl From<(f32, f32)> for PtI {
571 fn from(x: (f32, f32)) -> Self {
572 ((x.0 as u32), (x.1 as u32)).into()
573 }
574}
575impl From<(usize, usize)> for PtI {
576 fn from(x: (usize, usize)) -> Self {
577 ((x.0 as u32), (x.1 as u32)).into()
578 }
579}
580
581impl From<PtI> for (usize, usize) {
582 fn from(p: PtI) -> Self {
583 (p.x as usize, p.y as usize)
584 }
585}
586
587#[derive(Clone, Copy, Debug, PartialEq)]
588pub struct Circle {
589 pub center: PtF,
590 pub radius: TPtF,
591}
592
593pub fn color_with_intensity<CLR>(mut color: CLR, intensity: f64) -> CLR
594where
595 CLR: Pixel<Subpixel = u8>,
596{
597 let channels = color.channels_mut();
598 for channel in channels {
599 *channel = (f64::from(*channel) * intensity) as u8;
600 }
601 color
602}
603#[test]
604fn test_rot() {
605 let shape = Shape::new(5, 3);
606 let p = PtS { x: 2, y: 1 };
607 let p_rot_1 = p.rot90_with_image(shape);
608 assert!(p_rot_1.is_close_to(PtS { x: 1, y: 2 }));
609 let p_rot_2 = p.rot90_with_image_ntimes(shape, 2);
610 let p_rot_2_ = p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1));
611 assert_eq!(p_rot_2, p_rot_2_);
612
613 let p = PtF { x: 2.5, y: 1.0 };
614 let p_rot_1 = p.rot90_with_image(shape);
615 assert!(p_rot_1.is_close_to(PtF { x: 1.0, y: 2.5 }));
616 assert!(
617 p.rot90_with_image_ntimes(shape, 2)
618 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
619 );
620
621 let shape = Shape::new(5, 10);
622 let p = PtS { x: 1, y: 2 };
623 let p_rot_1 = p.rot90_with_image(shape);
624 assert!(p_rot_1.is_close_to(PtS { x: 2, y: 3 }));
625 assert!(
626 p.rot90_with_image_ntimes(shape, 2)
627 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
628 );
629 let p = PtF { x: 1.0, y: 2.0 };
630 let p_rot_1 = p.rot90_with_image(shape);
631 assert!(p_rot_1.is_close_to(PtF { x: 2.0, y: 4.0 }));
632 assert!(
633 p.rot90_with_image_ntimes(shape, 2)
634 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
635 );
636 let p = PtF { x: 2.0, y: 4.0 };
637 let p_rot_1 = p.rot90_with_image(shape);
638 assert!(p_rot_1.is_close_to(PtF { x: 4.0, y: 3.0 }));
639 assert!(
640 p.rot90_with_image_ntimes(shape, 2)
641 .is_close_to(p_rot_1.rot90_with_image(shape.rot90_with_image_ntimes(1)))
642 );
643}