1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution. |
3 | // |
4 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
5 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
6 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
7 | // option. This file may not be copied, modified, or distributed |
8 | // except according to those terms. |
9 | |
10 | use super::UnknownUnit; |
11 | use crate::approxord::{max, min}; |
12 | use crate::num::*; |
13 | use crate::point::{point2, Point2D}; |
14 | use crate::rect::Rect; |
15 | use crate::scale::Scale; |
16 | use crate::side_offsets::SideOffsets2D; |
17 | use crate::size::Size2D; |
18 | use crate::vector::{vec2, Vector2D}; |
19 | |
20 | use num_traits::{NumCast, Float}; |
21 | #[cfg (feature = "serde" )] |
22 | use serde::{Deserialize, Serialize}; |
23 | #[cfg (feature = "bytemuck" )] |
24 | use bytemuck::{Zeroable, Pod}; |
25 | |
26 | use core::borrow::Borrow; |
27 | use core::cmp::PartialOrd; |
28 | use core::fmt; |
29 | use core::hash::{Hash, Hasher}; |
30 | use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub, Range}; |
31 | |
32 | /// A 2d axis aligned rectangle represented by its minimum and maximum coordinates. |
33 | /// |
34 | /// # Representation |
35 | /// |
36 | /// This struct is similar to [`Rect`], but stores rectangle as two endpoints |
37 | /// instead of origin point and size. Such representation has several advantages over |
38 | /// [`Rect`] representation: |
39 | /// - Several operations are more efficient with `Box2D`, including [`intersection`], |
40 | /// [`union`], and point-in-rect. |
41 | /// - The representation is less susceptible to overflow. With [`Rect`], computation |
42 | /// of second point can overflow for a large range of values of origin and size. |
43 | /// However, with `Box2D`, computation of [`size`] cannot overflow if the coordinates |
44 | /// are signed and the resulting size is unsigned. |
45 | /// |
46 | /// A known disadvantage of `Box2D` is that translating the rectangle requires translating |
47 | /// both points, whereas translating [`Rect`] only requires translating one point. |
48 | /// |
49 | /// # Empty box |
50 | /// |
51 | /// A box is considered empty (see [`is_empty`]) if any of the following is true: |
52 | /// - it's area is empty, |
53 | /// - it's area is negative (`min.x > max.x` or `min.y > max.y`), |
54 | /// - it contains NaNs. |
55 | /// |
56 | /// [`Rect`]: struct.Rect.html |
57 | /// [`intersection`]: #method.intersection |
58 | /// [`is_empty`]: #method.is_empty |
59 | /// [`union`]: #method.union |
60 | /// [`size`]: #method.size |
61 | #[repr (C)] |
62 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
63 | #[cfg_attr ( |
64 | feature = "serde" , |
65 | serde(bound(serialize = "T: Serialize" , deserialize = "T: Deserialize<'de>" )) |
66 | )] |
67 | pub struct Box2D<T, U> { |
68 | pub min: Point2D<T, U>, |
69 | pub max: Point2D<T, U>, |
70 | } |
71 | |
72 | impl<T: Hash, U> Hash for Box2D<T, U> { |
73 | fn hash<H: Hasher>(&self, h: &mut H) { |
74 | self.min.hash(state:h); |
75 | self.max.hash(state:h); |
76 | } |
77 | } |
78 | |
79 | impl<T: Copy, U> Copy for Box2D<T, U> {} |
80 | |
81 | impl<T: Clone, U> Clone for Box2D<T, U> { |
82 | fn clone(&self) -> Self { |
83 | Self::new(self.min.clone(), self.max.clone()) |
84 | } |
85 | } |
86 | |
87 | impl<T: PartialEq, U> PartialEq for Box2D<T, U> { |
88 | fn eq(&self, other: &Self) -> bool { |
89 | self.min.eq(&other.min) && self.max.eq(&other.max) |
90 | } |
91 | } |
92 | |
93 | impl<T: Eq, U> Eq for Box2D<T, U> {} |
94 | |
95 | impl<T: fmt::Debug, U> fmt::Debug for Box2D<T, U> { |
96 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
97 | f&mut DebugTuple<'_, '_>.debug_tuple(name:"Box2D" ) |
98 | .field(&self.min) |
99 | .field(&self.max) |
100 | .finish() |
101 | } |
102 | } |
103 | |
104 | #[cfg (feature = "bytemuck" )] |
105 | unsafe impl<T: Zeroable, U> Zeroable for Box2D<T, U> {} |
106 | |
107 | #[cfg (feature = "bytemuck" )] |
108 | unsafe impl<T: Pod, U: 'static> Pod for Box2D<T, U> {} |
109 | |
110 | impl<T, U> Box2D<T, U> { |
111 | /// Constructor. |
112 | #[inline ] |
113 | pub const fn new(min: Point2D<T, U>, max: Point2D<T, U>) -> Self { |
114 | Box2D { min, max } |
115 | } |
116 | |
117 | /// Constructor. |
118 | #[inline ] |
119 | pub fn from_origin_and_size(origin: Point2D<T, U>, size: Size2D<T, U>) -> Self |
120 | where |
121 | T: Copy + Add<T, Output = T> |
122 | { |
123 | Box2D { |
124 | min: origin, |
125 | max: point2(origin.x + size.width, origin.y + size.height), |
126 | } |
127 | } |
128 | |
129 | /// Creates a Box2D of the given size, at offset zero. |
130 | #[inline ] |
131 | pub fn from_size(size: Size2D<T, U>) -> Self where T: Zero { |
132 | Box2D { |
133 | min: Point2D::zero(), |
134 | max: point2(size.width, size.height), |
135 | } |
136 | } |
137 | } |
138 | |
139 | impl<T, U> Box2D<T, U> |
140 | where |
141 | T: PartialOrd, |
142 | { |
143 | /// Returns true if the box has a negative area. |
144 | /// |
145 | /// The common interpretation for a negative box is to consider it empty. It can be obtained |
146 | /// by calculating the intersection of two boxes that do not intersect. |
147 | #[inline ] |
148 | pub fn is_negative(&self) -> bool { |
149 | self.max.x < self.min.x || self.max.y < self.min.y |
150 | } |
151 | |
152 | /// Returns true if the size is zero, negative or NaN. |
153 | #[inline ] |
154 | pub fn is_empty(&self) -> bool { |
155 | !(self.max.x > self.min.x && self.max.y > self.min.y) |
156 | } |
157 | |
158 | /// Returns `true` if the two boxes intersect. |
159 | #[inline ] |
160 | pub fn intersects(&self, other: &Self) -> bool { |
161 | self.min.x < other.max.x |
162 | && self.max.x > other.min.x |
163 | && self.min.y < other.max.y |
164 | && self.max.y > other.min.y |
165 | } |
166 | |
167 | /// Returns `true` if this box contains the point. Points are considered |
168 | /// in the box if they are on the front, left or top faces, but outside if they |
169 | /// are on the back, right or bottom faces. |
170 | #[inline ] |
171 | pub fn contains(&self, p: Point2D<T, U>) -> bool { |
172 | self.min.x <= p.x && p.x < self.max.x && self.min.y <= p.y && p.y < self.max.y |
173 | } |
174 | |
175 | /// Returns `true` if this box contains the interior of the other box. Always |
176 | /// returns `true` if other is empty, and always returns `false` if other is |
177 | /// nonempty but this box is empty. |
178 | #[inline ] |
179 | pub fn contains_box(&self, other: &Self) -> bool { |
180 | other.is_empty() |
181 | || (self.min.x <= other.min.x |
182 | && other.max.x <= self.max.x |
183 | && self.min.y <= other.min.y |
184 | && other.max.y <= self.max.y) |
185 | } |
186 | } |
187 | |
188 | impl<T, U> Box2D<T, U> |
189 | where |
190 | T: Copy + PartialOrd, |
191 | { |
192 | #[inline ] |
193 | pub fn to_non_empty(&self) -> Option<Self> { |
194 | if self.is_empty() { |
195 | return None; |
196 | } |
197 | |
198 | Some(*self) |
199 | } |
200 | |
201 | /// Computes the intersection of two boxes, returning `None` if the boxes do not intersect. |
202 | #[inline ] |
203 | pub fn intersection(&self, other: &Self) -> Option<Self> { |
204 | let b = self.intersection_unchecked(other); |
205 | |
206 | if b.is_empty() { |
207 | return None; |
208 | } |
209 | |
210 | Some(b) |
211 | } |
212 | |
213 | /// Computes the intersection of two boxes without check whether they do intersect. |
214 | /// |
215 | /// The result is a negative box if the boxes do not intersect. |
216 | /// This can be useful for computing the intersection of more than two boxes, as |
217 | /// it is possible to chain multiple intersection_unchecked calls and check for |
218 | /// empty/negative result at the end. |
219 | #[inline ] |
220 | pub fn intersection_unchecked(&self, other: &Self) -> Self { |
221 | Box2D { |
222 | min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)), |
223 | max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)), |
224 | } |
225 | } |
226 | |
227 | /// Computes the union of two boxes. |
228 | /// |
229 | /// If either of the boxes is empty, the other one is returned. |
230 | #[inline ] |
231 | pub fn union(&self, other: &Self) -> Self { |
232 | if other.is_empty() { |
233 | return *self; |
234 | } |
235 | if self.is_empty() { |
236 | return *other; |
237 | } |
238 | |
239 | Box2D { |
240 | min: point2(min(self.min.x, other.min.x), min(self.min.y, other.min.y)), |
241 | max: point2(max(self.max.x, other.max.x), max(self.max.y, other.max.y)), |
242 | } |
243 | } |
244 | } |
245 | |
246 | impl<T, U> Box2D<T, U> |
247 | where |
248 | T: Copy + Add<T, Output = T>, |
249 | { |
250 | /// Returns the same box, translated by a vector. |
251 | #[inline ] |
252 | pub fn translate(&self, by: Vector2D<T, U>) -> Self { |
253 | Box2D { |
254 | min: self.min + by, |
255 | max: self.max + by, |
256 | } |
257 | } |
258 | } |
259 | |
260 | impl<T, U> Box2D<T, U> |
261 | where |
262 | T: Copy + Sub<T, Output = T>, |
263 | { |
264 | #[inline ] |
265 | pub fn size(&self) -> Size2D<T, U> { |
266 | (self.max - self.min).to_size() |
267 | } |
268 | |
269 | /// Change the size of the box by adjusting the max endpoint |
270 | /// without modifying the min endpoint. |
271 | #[inline ] |
272 | pub fn set_size(&mut self, size: Size2D<T, U>) { |
273 | let diff = (self.size() - size).to_vector(); |
274 | self.max -= diff; |
275 | } |
276 | |
277 | #[inline ] |
278 | pub fn width(&self) -> T { |
279 | self.max.x - self.min.x |
280 | } |
281 | |
282 | #[inline ] |
283 | pub fn height(&self) -> T { |
284 | self.max.y - self.min.y |
285 | } |
286 | |
287 | #[inline ] |
288 | pub fn to_rect(&self) -> Rect<T, U> { |
289 | Rect { |
290 | origin: self.min, |
291 | size: self.size(), |
292 | } |
293 | } |
294 | } |
295 | |
296 | impl<T, U> Box2D<T, U> |
297 | where |
298 | T: Copy + Add<T, Output = T> + Sub<T, Output = T>, |
299 | { |
300 | /// Inflates the box by the specified sizes on each side respectively. |
301 | #[inline ] |
302 | #[must_use ] |
303 | pub fn inflate(&self, width: T, height: T) -> Self { |
304 | Box2D { |
305 | min: point2(self.min.x - width, self.min.y - height), |
306 | max: point2(self.max.x + width, self.max.y + height), |
307 | } |
308 | } |
309 | |
310 | /// Calculate the size and position of an inner box. |
311 | /// |
312 | /// Subtracts the side offsets from all sides. The horizontal, vertical |
313 | /// and applicate offsets must not be larger than the original side length. |
314 | pub fn inner_box(&self, offsets: SideOffsets2D<T, U>) -> Self { |
315 | Box2D { |
316 | min: self.min + vec2(offsets.left, offsets.top), |
317 | max: self.max - vec2(offsets.right, offsets.bottom), |
318 | } |
319 | } |
320 | |
321 | /// Calculate the b and position of an outer box. |
322 | /// |
323 | /// Add the offsets to all sides. The expanded box is returned. |
324 | pub fn outer_box(&self, offsets: SideOffsets2D<T, U>) -> Self { |
325 | Box2D { |
326 | min: self.min - vec2(offsets.left, offsets.top), |
327 | max: self.max + vec2(offsets.right, offsets.bottom), |
328 | } |
329 | } |
330 | } |
331 | |
332 | impl<T, U> Box2D<T, U> |
333 | where |
334 | T: Copy + Zero + PartialOrd, |
335 | { |
336 | /// Returns the smallest box containing all of the provided points. |
337 | pub fn from_points<I>(points: I) -> Self |
338 | where |
339 | I: IntoIterator, |
340 | I::Item: Borrow<Point2D<T, U>>, |
341 | { |
342 | let mut points = points.into_iter(); |
343 | |
344 | let (mut min_x, mut min_y) = match points.next() { |
345 | Some(first) => first.borrow().to_tuple(), |
346 | None => return Box2D::zero(), |
347 | }; |
348 | |
349 | let (mut max_x, mut max_y) = (min_x, min_y); |
350 | for point in points { |
351 | let p = point.borrow(); |
352 | if p.x < min_x { |
353 | min_x = p.x |
354 | } |
355 | if p.x > max_x { |
356 | max_x = p.x |
357 | } |
358 | if p.y < min_y { |
359 | min_y = p.y |
360 | } |
361 | if p.y > max_y { |
362 | max_y = p.y |
363 | } |
364 | } |
365 | |
366 | Box2D { |
367 | min: point2(min_x, min_y), |
368 | max: point2(max_x, max_y), |
369 | } |
370 | } |
371 | } |
372 | |
373 | impl<T, U> Box2D<T, U> |
374 | where |
375 | T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>, |
376 | { |
377 | /// Linearly interpolate between this box and another box. |
378 | #[inline ] |
379 | pub fn lerp(&self, other: Self, t: T) -> Self { |
380 | Self::new(self.min.lerp(other.min, t), self.max.lerp(other:other.max, t)) |
381 | } |
382 | } |
383 | |
384 | impl<T, U> Box2D<T, U> |
385 | where |
386 | T: Copy + One + Add<Output = T> + Div<Output = T>, |
387 | { |
388 | pub fn center(&self) -> Point2D<T, U> { |
389 | let two: T = T::one() + T::one(); |
390 | (self.min + self.max.to_vector()) / two |
391 | } |
392 | } |
393 | |
394 | impl<T, U> Box2D<T, U> |
395 | where |
396 | T: Copy + Mul<T, Output = T> + Sub<T, Output = T>, |
397 | { |
398 | #[inline ] |
399 | pub fn area(&self) -> T { |
400 | let size: Size2D = self.size(); |
401 | size.width * size.height |
402 | } |
403 | } |
404 | |
405 | impl<T, U> Box2D<T, U> |
406 | where |
407 | T: Zero, |
408 | { |
409 | /// Constructor, setting all sides to zero. |
410 | pub fn zero() -> Self { |
411 | Box2D::new(min:Point2D::zero(), max:Point2D::zero()) |
412 | } |
413 | } |
414 | |
415 | impl<T: Copy + Mul, U> Mul<T> for Box2D<T, U> { |
416 | type Output = Box2D<T::Output, U>; |
417 | |
418 | #[inline ] |
419 | fn mul(self, scale: T) -> Self::Output { |
420 | Box2D::new(self.min * scale, self.max * scale) |
421 | } |
422 | } |
423 | |
424 | impl<T: Copy + MulAssign, U> MulAssign<T> for Box2D<T, U> { |
425 | #[inline ] |
426 | fn mul_assign(&mut self, scale: T) { |
427 | *self *= Scale::new(scale); |
428 | } |
429 | } |
430 | |
431 | impl<T: Copy + Div, U> Div<T> for Box2D<T, U> { |
432 | type Output = Box2D<T::Output, U>; |
433 | |
434 | #[inline ] |
435 | fn div(self, scale: T) -> Self::Output { |
436 | Box2D::new(self.min / scale, self.max / scale) |
437 | } |
438 | } |
439 | |
440 | impl<T: Copy + DivAssign, U> DivAssign<T> for Box2D<T, U> { |
441 | #[inline ] |
442 | fn div_assign(&mut self, scale: T) { |
443 | *self /= Scale::new(scale); |
444 | } |
445 | } |
446 | |
447 | impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Box2D<T, U1> { |
448 | type Output = Box2D<T::Output, U2>; |
449 | |
450 | #[inline ] |
451 | fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output { |
452 | Box2D::new(self.min * scale, self.max * scale) |
453 | } |
454 | } |
455 | |
456 | impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Box2D<T, U> { |
457 | #[inline ] |
458 | fn mul_assign(&mut self, scale: Scale<T, U, U>) { |
459 | self.min *= scale; |
460 | self.max *= scale; |
461 | } |
462 | } |
463 | |
464 | impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Box2D<T, U2> { |
465 | type Output = Box2D<T::Output, U1>; |
466 | |
467 | #[inline ] |
468 | fn div(self, scale: Scale<T, U1, U2>) -> Self::Output { |
469 | Box2D::new(self.min / scale, self.max / scale) |
470 | } |
471 | } |
472 | |
473 | impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Box2D<T, U> { |
474 | #[inline ] |
475 | fn div_assign(&mut self, scale: Scale<T, U, U>) { |
476 | self.min /= scale; |
477 | self.max /= scale; |
478 | } |
479 | } |
480 | |
481 | impl<T, U> Box2D<T, U> |
482 | where |
483 | T: Copy, |
484 | { |
485 | #[inline ] |
486 | pub fn x_range(&self) -> Range<T> { |
487 | self.min.x..self.max.x |
488 | } |
489 | |
490 | #[inline ] |
491 | pub fn y_range(&self) -> Range<T> { |
492 | self.min.y..self.max.y |
493 | } |
494 | |
495 | /// Drop the units, preserving only the numeric value. |
496 | #[inline ] |
497 | pub fn to_untyped(&self) -> Box2D<T, UnknownUnit> { |
498 | Box2D::new(self.min.to_untyped(), self.max.to_untyped()) |
499 | } |
500 | |
501 | /// Tag a unitless value with units. |
502 | #[inline ] |
503 | pub fn from_untyped(c: &Box2D<T, UnknownUnit>) -> Box2D<T, U> { |
504 | Box2D::new(Point2D::from_untyped(c.min), Point2D::from_untyped(c.max)) |
505 | } |
506 | |
507 | /// Cast the unit |
508 | #[inline ] |
509 | pub fn cast_unit<V>(&self) -> Box2D<T, V> { |
510 | Box2D::new(self.min.cast_unit(), self.max.cast_unit()) |
511 | } |
512 | |
513 | #[inline ] |
514 | pub fn scale<S: Copy>(&self, x: S, y: S) -> Self |
515 | where |
516 | T: Mul<S, Output = T>, |
517 | { |
518 | Box2D { |
519 | min: point2(self.min.x * x, self.min.y * y), |
520 | max: point2(self.max.x * x, self.max.y * y), |
521 | } |
522 | } |
523 | } |
524 | |
525 | impl<T: NumCast + Copy, U> Box2D<T, U> { |
526 | /// Cast from one numeric representation to another, preserving the units. |
527 | /// |
528 | /// When casting from floating point to integer coordinates, the decimals are truncated |
529 | /// as one would expect from a simple cast, but this behavior does not always make sense |
530 | /// geometrically. Consider using round(), round_in or round_out() before casting. |
531 | #[inline ] |
532 | pub fn cast<NewT: NumCast>(&self) -> Box2D<NewT, U> { |
533 | Box2D::new(self.min.cast(), self.max.cast()) |
534 | } |
535 | |
536 | /// Fallible cast from one numeric representation to another, preserving the units. |
537 | /// |
538 | /// When casting from floating point to integer coordinates, the decimals are truncated |
539 | /// as one would expect from a simple cast, but this behavior does not always make sense |
540 | /// geometrically. Consider using round(), round_in or round_out() before casting. |
541 | pub fn try_cast<NewT: NumCast>(&self) -> Option<Box2D<NewT, U>> { |
542 | match (self.min.try_cast(), self.max.try_cast()) { |
543 | (Some(a), Some(b)) => Some(Box2D::new(a, b)), |
544 | _ => None, |
545 | } |
546 | } |
547 | |
548 | // Convenience functions for common casts |
549 | |
550 | /// Cast into an `f32` box. |
551 | #[inline ] |
552 | pub fn to_f32(&self) -> Box2D<f32, U> { |
553 | self.cast() |
554 | } |
555 | |
556 | /// Cast into an `f64` box. |
557 | #[inline ] |
558 | pub fn to_f64(&self) -> Box2D<f64, U> { |
559 | self.cast() |
560 | } |
561 | |
562 | /// Cast into an `usize` box, truncating decimals if any. |
563 | /// |
564 | /// When casting from floating point boxes, it is worth considering whether |
565 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to |
566 | /// obtain the desired conversion behavior. |
567 | #[inline ] |
568 | pub fn to_usize(&self) -> Box2D<usize, U> { |
569 | self.cast() |
570 | } |
571 | |
572 | /// Cast into an `u32` box, truncating decimals if any. |
573 | /// |
574 | /// When casting from floating point boxes, it is worth considering whether |
575 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to |
576 | /// obtain the desired conversion behavior. |
577 | #[inline ] |
578 | pub fn to_u32(&self) -> Box2D<u32, U> { |
579 | self.cast() |
580 | } |
581 | |
582 | /// Cast into an `i32` box, truncating decimals if any. |
583 | /// |
584 | /// When casting from floating point boxes, it is worth considering whether |
585 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to |
586 | /// obtain the desired conversion behavior. |
587 | #[inline ] |
588 | pub fn to_i32(&self) -> Box2D<i32, U> { |
589 | self.cast() |
590 | } |
591 | |
592 | /// Cast into an `i64` box, truncating decimals if any. |
593 | /// |
594 | /// When casting from floating point boxes, it is worth considering whether |
595 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to |
596 | /// obtain the desired conversion behavior. |
597 | #[inline ] |
598 | pub fn to_i64(&self) -> Box2D<i64, U> { |
599 | self.cast() |
600 | } |
601 | } |
602 | |
603 | impl<T: Float, U> Box2D<T, U> { |
604 | /// Returns true if all members are finite. |
605 | #[inline ] |
606 | pub fn is_finite(self) -> bool { |
607 | self.min.is_finite() && self.max.is_finite() |
608 | } |
609 | } |
610 | |
611 | impl<T, U> Box2D<T, U> |
612 | where |
613 | T: Round, |
614 | { |
615 | /// Return a box with edges rounded to integer coordinates, such that |
616 | /// the returned box has the same set of pixel centers as the original |
617 | /// one. |
618 | /// Values equal to 0.5 round up. |
619 | /// Suitable for most places where integral device coordinates |
620 | /// are needed, but note that any translation should be applied first to |
621 | /// avoid pixel rounding errors. |
622 | /// Note that this is *not* rounding to nearest integer if the values are negative. |
623 | /// They are always rounding as floor(n + 0.5). |
624 | #[must_use ] |
625 | pub fn round(&self) -> Self { |
626 | Box2D::new(self.min.round(), self.max.round()) |
627 | } |
628 | } |
629 | |
630 | impl<T, U> Box2D<T, U> |
631 | where |
632 | T: Floor + Ceil, |
633 | { |
634 | /// Return a box with faces/edges rounded to integer coordinates, such that |
635 | /// the original box contains the resulting box. |
636 | #[must_use ] |
637 | pub fn round_in(&self) -> Self { |
638 | let min: Point2D = self.min.ceil(); |
639 | let max: Point2D = self.max.floor(); |
640 | Box2D { min, max } |
641 | } |
642 | |
643 | /// Return a box with faces/edges rounded to integer coordinates, such that |
644 | /// the original box is contained in the resulting box. |
645 | #[must_use ] |
646 | pub fn round_out(&self) -> Self { |
647 | let min: Point2D = self.min.floor(); |
648 | let max: Point2D = self.max.ceil(); |
649 | Box2D { min, max } |
650 | } |
651 | } |
652 | |
653 | impl<T, U> From<Size2D<T, U>> for Box2D<T, U> |
654 | where |
655 | T: Copy + Zero + PartialOrd, |
656 | { |
657 | fn from(b: Size2D<T, U>) -> Self { |
658 | Self::from_size(b) |
659 | } |
660 | } |
661 | |
662 | impl<T: Default, U> Default for Box2D<T, U> { |
663 | fn default() -> Self { |
664 | Box2D { |
665 | min: Default::default(), |
666 | max: Default::default(), |
667 | } |
668 | } |
669 | } |
670 | |
671 | #[cfg (test)] |
672 | mod tests { |
673 | use crate::default::Box2D; |
674 | use crate::side_offsets::SideOffsets2D; |
675 | use crate::{point2, size2, vec2, Point2D}; |
676 | //use super::*; |
677 | |
678 | #[test ] |
679 | fn test_size() { |
680 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); |
681 | assert_eq!(b.size().width, 20.0); |
682 | assert_eq!(b.size().height, 20.0); |
683 | } |
684 | |
685 | #[test ] |
686 | fn test_width_height() { |
687 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); |
688 | assert!(b.width() == 20.0); |
689 | assert!(b.height() == 20.0); |
690 | } |
691 | |
692 | #[test ] |
693 | fn test_center() { |
694 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); |
695 | assert_eq!(b.center(), Point2D::zero()); |
696 | } |
697 | |
698 | #[test ] |
699 | fn test_area() { |
700 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); |
701 | assert_eq!(b.area(), 400.0); |
702 | } |
703 | |
704 | #[test ] |
705 | fn test_from_points() { |
706 | let b = Box2D::from_points(&[point2(50.0, 160.0), point2(100.0, 25.0)]); |
707 | assert_eq!(b.min, point2(50.0, 25.0)); |
708 | assert_eq!(b.max, point2(100.0, 160.0)); |
709 | } |
710 | |
711 | #[test ] |
712 | fn test_round_in() { |
713 | let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_in(); |
714 | assert_eq!(b.min.x, -25.0); |
715 | assert_eq!(b.min.y, -40.0); |
716 | assert_eq!(b.max.x, 60.0); |
717 | assert_eq!(b.max.y, 36.0); |
718 | } |
719 | |
720 | #[test ] |
721 | fn test_round_out() { |
722 | let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_out(); |
723 | assert_eq!(b.min.x, -26.0); |
724 | assert_eq!(b.min.y, -41.0); |
725 | assert_eq!(b.max.x, 61.0); |
726 | assert_eq!(b.max.y, 37.0); |
727 | } |
728 | |
729 | #[test ] |
730 | fn test_round() { |
731 | let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round(); |
732 | assert_eq!(b.min.x, -25.0); |
733 | assert_eq!(b.min.y, -40.0); |
734 | assert_eq!(b.max.x, 60.0); |
735 | assert_eq!(b.max.y, 37.0); |
736 | } |
737 | |
738 | #[test ] |
739 | fn test_from_size() { |
740 | let b = Box2D::from_size(size2(30.0, 40.0)); |
741 | assert!(b.min == Point2D::zero()); |
742 | assert!(b.size().width == 30.0); |
743 | assert!(b.size().height == 40.0); |
744 | } |
745 | |
746 | #[test ] |
747 | fn test_inner_box() { |
748 | let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]); |
749 | let b = b.inner_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0)); |
750 | assert_eq!(b.max.x, 80.0); |
751 | assert_eq!(b.max.y, 155.0); |
752 | assert_eq!(b.min.x, 60.0); |
753 | assert_eq!(b.min.y, 35.0); |
754 | } |
755 | |
756 | #[test ] |
757 | fn test_outer_box() { |
758 | let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]); |
759 | let b = b.outer_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0)); |
760 | assert_eq!(b.max.x, 120.0); |
761 | assert_eq!(b.max.y, 165.0); |
762 | assert_eq!(b.min.x, 40.0); |
763 | assert_eq!(b.min.y, 15.0); |
764 | } |
765 | |
766 | #[test ] |
767 | fn test_translate() { |
768 | let size = size2(15.0, 15.0); |
769 | let mut center = (size / 2.0).to_vector().to_point(); |
770 | let b = Box2D::from_size(size); |
771 | assert_eq!(b.center(), center); |
772 | let translation = vec2(10.0, 2.5); |
773 | let b = b.translate(translation); |
774 | center += translation; |
775 | assert_eq!(b.center(), center); |
776 | assert_eq!(b.max.x, 25.0); |
777 | assert_eq!(b.max.y, 17.5); |
778 | assert_eq!(b.min.x, 10.0); |
779 | assert_eq!(b.min.y, 2.5); |
780 | } |
781 | |
782 | #[test ] |
783 | fn test_union() { |
784 | let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(0.0, 20.0)]); |
785 | let b2 = Box2D::from_points(&[point2(0.0, 20.0), point2(20.0, -20.0)]); |
786 | let b = b1.union(&b2); |
787 | assert_eq!(b.max.x, 20.0); |
788 | assert_eq!(b.max.y, 20.0); |
789 | assert_eq!(b.min.x, -20.0); |
790 | assert_eq!(b.min.y, -20.0); |
791 | } |
792 | |
793 | #[test ] |
794 | fn test_intersects() { |
795 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); |
796 | let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); |
797 | assert!(b1.intersects(&b2)); |
798 | } |
799 | |
800 | #[test ] |
801 | fn test_intersection_unchecked() { |
802 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); |
803 | let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); |
804 | let b = b1.intersection_unchecked(&b2); |
805 | assert_eq!(b.max.x, 10.0); |
806 | assert_eq!(b.max.y, 20.0); |
807 | assert_eq!(b.min.x, -10.0); |
808 | assert_eq!(b.min.y, -20.0); |
809 | } |
810 | |
811 | #[test ] |
812 | fn test_intersection() { |
813 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); |
814 | let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); |
815 | assert!(b1.intersection(&b2).is_some()); |
816 | |
817 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(-10.0, 20.0)]); |
818 | let b2 = Box2D::from_points(&[point2(10.0, 20.0), point2(15.0, -20.0)]); |
819 | assert!(b1.intersection(&b2).is_none()); |
820 | } |
821 | |
822 | #[test ] |
823 | fn test_scale() { |
824 | let b = Box2D::from_points(&[point2(-10.0, -10.0), point2(10.0, 10.0)]); |
825 | let b = b.scale(0.5, 0.5); |
826 | assert_eq!(b.max.x, 5.0); |
827 | assert_eq!(b.max.y, 5.0); |
828 | assert_eq!(b.min.x, -5.0); |
829 | assert_eq!(b.min.y, -5.0); |
830 | } |
831 | |
832 | #[test ] |
833 | fn test_lerp() { |
834 | let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(-10.0, -10.0)]); |
835 | let b2 = Box2D::from_points(&[point2(10.0, 10.0), point2(20.0, 20.0)]); |
836 | let b = b1.lerp(b2, 0.5); |
837 | assert_eq!(b.center(), Point2D::zero()); |
838 | assert_eq!(b.size().width, 10.0); |
839 | assert_eq!(b.size().height, 10.0); |
840 | } |
841 | |
842 | #[test ] |
843 | fn test_contains() { |
844 | let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); |
845 | assert!(b.contains(point2(-15.3, 10.5))); |
846 | } |
847 | |
848 | #[test ] |
849 | fn test_contains_box() { |
850 | let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); |
851 | let b2 = Box2D::from_points(&[point2(-14.3, -16.5), point2(6.7, 17.6)]); |
852 | assert!(b1.contains_box(&b2)); |
853 | } |
854 | |
855 | #[test ] |
856 | fn test_inflate() { |
857 | let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); |
858 | let b = b.inflate(10.0, 5.0); |
859 | assert_eq!(b.size().width, 60.0); |
860 | assert_eq!(b.size().height, 50.0); |
861 | assert_eq!(b.center(), Point2D::zero()); |
862 | } |
863 | |
864 | #[test ] |
865 | fn test_is_empty() { |
866 | for i in 0..2 { |
867 | let mut coords_neg = [-20.0, -20.0]; |
868 | let mut coords_pos = [20.0, 20.0]; |
869 | coords_neg[i] = 0.0; |
870 | coords_pos[i] = 0.0; |
871 | let b = Box2D::from_points(&[Point2D::from(coords_neg), Point2D::from(coords_pos)]); |
872 | assert!(b.is_empty()); |
873 | } |
874 | } |
875 | |
876 | #[test ] |
877 | fn test_nan_empty() { |
878 | use std::f32::NAN; |
879 | assert!(Box2D { min: point2(NAN, 2.0), max: point2(1.0, 3.0) }.is_empty()); |
880 | assert!(Box2D { min: point2(0.0, NAN), max: point2(1.0, 2.0) }.is_empty()); |
881 | assert!(Box2D { min: point2(1.0, -2.0), max: point2(NAN, 2.0) }.is_empty()); |
882 | assert!(Box2D { min: point2(1.0, -2.0), max: point2(0.0, NAN) }.is_empty()); |
883 | } |
884 | |
885 | #[test ] |
886 | fn test_from_origin_and_size() { |
887 | let b = Box2D::from_origin_and_size(point2(1.0, 2.0), size2(3.0, 4.0)); |
888 | assert_eq!(b.min, point2(1.0, 2.0)); |
889 | assert_eq!(b.size(), size2(3.0, 4.0)); |
890 | } |
891 | |
892 | #[test ] |
893 | fn test_set_size() { |
894 | let mut b = Box2D { |
895 | min: point2(1.0, 2.0), |
896 | max: point2(3.0, 4.0), |
897 | }; |
898 | b.set_size(size2(5.0, 6.0)); |
899 | |
900 | assert_eq!(b.min, point2(1.0, 2.0)); |
901 | assert_eq!(b.size(), size2(5.0, 6.0)); |
902 | } |
903 | } |
904 | |