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