1use std::ops::{Add, Sub};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq)]
8#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
9pub struct Point {
10 pub x: f32,
12 pub y: f32,
14}
15
16#[cfg_attr(feature = "live", ravenrs_live::expose)]
17impl Point {
18 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
20
21 pub const fn new(x: f32, y: f32) -> Self {
23 Self { x, y }
24 }
25}
26
27impl Add for Point {
28 type Output = Self;
29 fn add(self, rhs: Self) -> Self {
30 Self::new(self.x + rhs.x, self.y + rhs.y)
31 }
32}
33
34impl Sub for Point {
35 type Output = Self;
36 fn sub(self, rhs: Self) -> Self {
37 Self::new(self.x - rhs.x, self.y - rhs.y)
38 }
39}
40
41#[derive(Clone, Copy, Debug, Default, PartialEq)]
43#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
44pub struct Size {
45 pub width: f32,
47 pub height: f32,
49}
50
51#[cfg_attr(feature = "live", ravenrs_live::expose)]
52impl Size {
53 pub const ZERO: Self = Self { width: 0.0, height: 0.0 };
55
56 pub const fn new(width: f32, height: f32) -> Self {
58 Self { width, height }
59 }
60
61 pub fn scale(self, factor: f32) -> Self {
63 Self::new(self.width * factor, self.height * factor)
64 }
65}
66
67#[derive(Clone, Copy, Debug, Default, PartialEq)]
69#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
70pub struct Rect {
71 pub origin: Point,
73 pub size: Size,
75}
76
77#[cfg_attr(feature = "live", ravenrs_live::expose)]
78impl Rect {
79 pub const ZERO: Self = Self { origin: Point::ZERO, size: Size::ZERO };
81
82 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
84 Self { origin: Point::new(x, y), size: Size::new(width, height) }
85 }
86
87 pub const fn from_origin_size(origin: Point, size: Size) -> Self {
89 Self { origin, size }
90 }
91
92 pub fn x(&self) -> f32 {
94 self.origin.x
95 }
96
97 pub fn y(&self) -> f32 {
99 self.origin.y
100 }
101
102 pub fn width(&self) -> f32 {
104 self.size.width
105 }
106
107 pub fn height(&self) -> f32 {
109 self.size.height
110 }
111
112 pub fn max_x(&self) -> f32 {
114 self.origin.x + self.size.width
115 }
116
117 pub fn max_y(&self) -> f32 {
119 self.origin.y + self.size.height
120 }
121
122 pub fn center(&self) -> Point {
124 Point::new(self.origin.x + self.size.width * 0.5, self.origin.y + self.size.height * 0.5)
125 }
126
127 pub fn contains(&self, p: Point) -> bool {
129 p.x >= self.origin.x && p.x < self.max_x() && p.y >= self.origin.y && p.y < self.max_y()
130 }
131
132 pub fn inflate(&self, amount: f32) -> Self {
134 Self::new(
135 self.origin.x - amount,
136 self.origin.y - amount,
137 (self.size.width + amount * 2.0).max(0.0),
138 (self.size.height + amount * 2.0).max(0.0),
139 )
140 }
141
142 pub fn intersect(&self, other: &Rect) -> Self {
145 let x0 = self.x().max(other.x());
146 let y0 = self.y().max(other.y());
147 let x1 = self.max_x().min(other.max_x());
148 let y1 = self.max_y().min(other.max_y());
149 if x1 <= x0 || y1 <= y0 {
150 return Self::from_origin_size(self.origin, Size::ZERO);
151 }
152 Self::new(x0, y0, x1 - x0, y1 - y0)
153 }
154
155 pub fn is_empty(&self) -> bool {
157 self.size.width <= 0.0 || self.size.height <= 0.0
158 }
159
160 pub fn translate(&self, offset: Point) -> Self {
162 Self::from_origin_size(self.origin + offset, self.size)
163 }
164}
165
166#[derive(Clone, Copy, Debug, Default, PartialEq)]
168#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
169pub struct Insets {
170 pub top: f32,
172 pub right: f32,
174 pub bottom: f32,
176 pub left: f32,
178}
179
180#[cfg_attr(feature = "live", ravenrs_live::expose)]
181impl Insets {
182 pub const ZERO: Self = Self::uniform(0.0);
184
185 pub const fn uniform(v: f32) -> Self {
187 Self { top: v, right: v, bottom: v, left: v }
188 }
189
190 pub const fn symmetric(vertical: f32, horizontal: f32) -> Self {
192 Self { top: vertical, right: horizontal, bottom: vertical, left: horizontal }
193 }
194}
195
196#[cfg(feature = "live")]
198impl ravenrs_live::FromLive for Insets {
199 fn from_live(value: ravenrs_live::Value) -> Result<Self, String> {
200 match value.read() {
201 ravenrs_live::Value::Host(host) => host.take_as::<Insets>(),
202 ravenrs_live::Value::Tuple(items) if items.len() == 2 => {
203 let (a, b) =
204 <(f32, f32) as ravenrs_live::FromLive>::from_live(ravenrs_live::Value::Tuple(items))?;
205 Ok(Self::from((a, b)))
206 }
207 other => <f32 as ravenrs_live::FromLive>::from_live(other).map(Self::from),
208 }
209 }
210}
211
212impl From<f32> for Insets {
213 fn from(v: f32) -> Self {
214 Self::uniform(v)
215 }
216}
217
218impl From<(f32, f32)> for Insets {
220 fn from((vertical, horizontal): (f32, f32)) -> Self {
221 Self::symmetric(vertical, horizontal)
222 }
223}
224
225pub fn monotone_slopes(points: &[Point]) -> Vec<f32> {
230 let secant = |a: Point, b: Point| {
231 let run = b.x - a.x;
232 if run.abs() < f32::EPSILON { 0.0 } else { (b.y - a.y) / run }
233 };
234 let n = points.len();
235 let mut slopes = vec![0.0; n];
236 if n < 2 {
237 return slopes;
238 }
239 slopes[0] = secant(points[0], points[1]);
240 slopes[n - 1] = secant(points[n - 2], points[n - 1]);
241 for i in 1..n - 1 {
242 let (before, after) = (secant(points[i - 1], points[i]), secant(points[i], points[i + 1]));
243 slopes[i] = if before * after <= 0.0 { 0.0 } else { 2.0 * before * after / (before + after) };
244 }
245 slopes
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn monotone_slopes_flatten_at_a_turn_and_keep_a_straight_line() {
254 let line = [Point::new(0.0, 0.0), Point::new(10.0, 5.0), Point::new(20.0, 10.0)];
255 assert_eq!(monotone_slopes(&line), [0.5, 0.5, 0.5]);
256 let peak = [Point::new(0.0, 0.0), Point::new(10.0, 10.0), Point::new(20.0, 0.0)];
257 assert_eq!(monotone_slopes(&peak), [1.0, 0.0, -1.0]);
258 let uneven = [Point::new(0.0, 0.0), Point::new(10.0, 10.0), Point::new(30.0, 20.0)];
259 assert_eq!(monotone_slopes(&uneven)[1], 2.0 * 1.0 * 0.5 / 1.5);
260 assert_eq!(monotone_slopes(&[Point::ZERO]), [0.0]);
261 assert!(monotone_slopes(&[]).is_empty());
262 }
263
264 #[test]
265 fn contains_is_half_open() {
266 let r = Rect::new(10.0, 10.0, 20.0, 10.0);
267 assert!(r.contains(Point::new(10.0, 10.0)));
268 assert!(r.contains(Point::new(29.9, 19.9)));
269 assert!(!r.contains(Point::new(30.0, 15.0)));
270 assert!(!r.contains(Point::new(15.0, 20.0)));
271 }
272
273 #[test]
274 fn intersection() {
275 let a = Rect::new(0.0, 0.0, 10.0, 10.0);
276 assert_eq!(a.intersect(&Rect::new(5.0, 5.0, 10.0, 10.0)), Rect::new(5.0, 5.0, 5.0, 5.0));
277 assert!(a.intersect(&Rect::new(20.0, 0.0, 5.0, 5.0)).is_empty());
278 }
279
280 #[test]
281 fn inflate_never_goes_negative() {
282 let r = Rect::new(0.0, 0.0, 4.0, 4.0).inflate(-5.0);
283 assert_eq!(r.size, Size::ZERO);
284 let g = Rect::new(10.0, 10.0, 4.0, 4.0).inflate(3.0);
285 assert_eq!(g, Rect::new(7.0, 7.0, 10.0, 10.0));
286 }
287}