| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef FLUTTER_IMPELLER_GEOMETRY_RECT_H_ | |
| #define FLUTTER_IMPELLER_GEOMETRY_RECT_H_ | |
| #include <array> | |
| #include <optional> | |
| #include <ostream> | |
| #include <vector> | |
| #include "fml/logging.h" | |
| #include "impeller/geometry/matrix.h" | |
| #include "impeller/geometry/point.h" | |
| #include "impeller/geometry/saturated_math.h" | |
| #include "impeller/geometry/scalar.h" | |
| #include "impeller/geometry/size.h" | |
| namespace impeller { | |
| #define ONLY_ON_FLOAT_M(Modifiers, Return) \ | |
| template <typename U = T> \ | |
| Modifiers std::enable_if_t<std::is_floating_point_v<U>, Return> | |
| #define ONLY_ON_FLOAT(Return) DL_ONLY_ON_FLOAT_M(, Return) | |
| /// Templated struct for holding an axis-aligned rectangle. | |
| /// | |
| /// Rectangles are defined as 4 axis-aligned edges that might contain | |
| /// space. They can be viewed as 2 X coordinates that define the | |
| /// left and right edges and 2 Y coordinates that define the top and | |
| /// bottom edges; or they can be viewed as an origin and horizontal | |
| /// and vertical dimensions (width and height). | |
| /// | |
| /// When the left and right edges are equal or reversed (right <= left) | |
| /// or the top and bottom edges are equal or reversed (bottom <= top), | |
| /// the rectangle is considered empty. Considering the rectangle in XYWH | |
| /// form, the width and/or the height would be negative or zero. Such | |
| /// reversed/empty rectangles contain no space and act as such in the | |
| /// methods that operate on them (Intersection, Union, IntersectsWithRect, | |
| /// Contains, Cutout, etc.) | |
| /// | |
| /// Rectangles cannot be modified by any method and a new value can only | |
| /// be stored into an existing rect using assignment. This keeps the API | |
| /// clean compared to implementations that might have similar methods | |
| /// that produce the answer in place, or construct a new object with | |
| /// the answer, or place the result in an indicated result object. | |
| /// | |
| /// Methods that might fail to produce an answer will use |std::optional| | |
| /// to indicate that success or failure (see |Intersection| and |CutOut|). | |
| /// For convenience, |Intersection| and |Union| both have overloaded | |
| /// variants that take |std::optional| arguments and treat them as if | |
| /// the argument was an empty rect to allow chaining multiple such methods | |
| /// and only needing to check the optional condition of the final result. | |
| /// The primary methods also provide |...OrEmpty| overloaded variants that | |
| /// translate an empty optional answer into a simple empty rectangle of the | |
| /// same type. | |
| /// | |
| /// Rounding instance methods are not provided as the return value might | |
| /// be wanted as another floating point rectangle or sometimes as an integer | |
| /// rectangle. Instead a |RoundOut| factory, defined only for floating point | |
| /// input rectangles, is provided to provide control over the result type. | |
| /// | |
| /// NaN and Infinity values | |
| /// | |
| /// Constructing an LTRB rectangle using Infinity values should work as | |
| /// expected with either 0 or +Infinity returned as dimensions depending on | |
| /// which side the Infinity values are on and the sign. | |
| /// | |
| /// Constructing an XYWH rectangle using Infinity values will usually | |
| /// not work if the math requires the object to compute a right or bottom | |
| /// edge from ([xy] -Infinity + [wh] +Infinity). Other combinations might | |
| /// work. | |
| /// | |
| /// The special factory |MakeMaximum| is provided to construct a rectangle | |
| /// of the indicated coordinate type that covers all finite coordinates. | |
| /// It does not use infinity values, but rather the largest finite values | |
| /// to avoid math that might produce a NaN value from various getters. | |
| /// | |
| /// Any rectangle that is constructed with, or computed to have a NaN value | |
| /// will be considered the same as any empty rectangle. | |
| /// | |
| /// Empty Rectangle canonical results summary: | |
| /// | |
| /// Union will ignore any empty rects and return the other rect | |
| /// Intersection will return nullopt if either rect is empty | |
| /// IntersectsWithRect will return false if either rect is empty | |
| /// Cutout will return the source rect if the argument is empty | |
| /// Cutout will return nullopt if the source rectangle is empty | |
| /// Contains(Point) will return false if the source rectangle is empty | |
| /// Contains(Rect) will return false if the source rectangle is empty | |
| /// Contains(Rect) will otherwise return true if the argument is empty | |
| /// Specifically, EmptyRect.Contains(EmptyRect) returns false | |
| /// | |
| /// --------------- | |
| /// Special notes on problems using the XYWH form of specifying rectangles: | |
| /// | |
| /// It is possible to have integer rectangles whose dimensions exceed | |
| /// the maximum number that their coordinates can represent since | |
| /// (MAX_INT - MIN_INT) overflows the representable positive numbers. | |
| /// Floating point rectangles technically have a similar issue in that | |
| /// overflow can occur, but it will be automatically converted into | |
| /// either an infinity, or a finite-overflow value and still be | |
| /// representable, just with little to no precision. | |
| /// | |
| /// Secondly, specifying a rectangle using XYWH leads to cases where the | |
| /// math for (x+w) and/or (y+h) are also beyond the maximum representable | |
| /// coordinates. For N-bit integer rectangles declared as XYWH, the | |
| /// maximum right coordinate will require N+1 signed bits which cannot be | |
| /// stored in storage that uses N-bit integers. | |
| /// | |
| /// Saturated math is used when constructing a rectangle from XYWH values | |
| /// and when returning the dimensions of the rectangle. Constructing an | |
| /// integer rectangle from values such that xy + wh is beyond the range | |
| /// of the integer type will place the right or bottom edges at the maximum | |
| /// value for the integer type. Similarly, constructing an integer rectangle | |
| /// such that the distance from the left to the right (or top to bottom) is | |
| /// greater than the range of the integer type will simply return the | |
| /// maximum integer value as the dimension. Floating point rectangles are | |
| /// naturally saturated by the rules of IEEE arithmetic. | |
| template <class T> | |
| struct TRect { | |
| private: | |
| using Type = T; | |
| public: | |
| constexpr TRect() : left_(0), top_(0), right_(0), bottom_(0) {} | |
| constexpr static TRect MakeLTRB(Type left, | |
| Type top, | |
| Type right, | |
| Type bottom) { | |
| return TRect(left, top, right, bottom); | |
| } | |
| constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height) { | |
| return TRect(x, y, saturated::Add(x, width), saturated::Add(y, height)); | |
| } | |
| constexpr static TRect MakeOriginSize(const TPoint<Type>& origin, | |
| const TSize<Type>& size) { | |
| return MakeXYWH(origin.x, origin.y, size.width, size.height); | |
| } | |
| template <class U> | |
| constexpr static TRect MakeSize(const TSize<U>& size) { | |
| return TRect(0.0, 0.0, size.width, size.height); | |
| } | |
| template <typename U> | |
| constexpr static std::optional<TRect> MakePointBounds(const U& value) { | |
| return MakePointBounds(value.begin(), value.end()); | |
| } | |
| template <typename PointIter> | |
| constexpr static std::optional<TRect> MakePointBounds(const PointIter first, | |
| const PointIter last) { | |
| if (first == last) { | |
| return std::nullopt; | |
| } | |
| auto left = first->x; | |
| auto top = first->y; | |
| auto right = first->x; | |
| auto bottom = first->y; | |
| for (auto it = first + 1; it < last; ++it) { | |
| left = std::min(left, it->x); | |
| top = std::min(top, it->y); | |
| right = std::max(right, it->x); | |
| bottom = std::max(bottom, it->y); | |
| } | |
| return TRect::MakeLTRB(left, top, right, bottom); | |
| } | |
| [[nodiscard]] constexpr static TRect MakeMaximum() { | |
| return TRect::MakeLTRB(std::numeric_limits<Type>::lowest(), | |
| std::numeric_limits<Type>::lowest(), | |
| std::numeric_limits<Type>::max(), | |
| std::numeric_limits<Type>::max()); | |
| } | |
| [[nodiscard]] constexpr bool operator==(const TRect& r) const { | |
| return left_ == r.left_ && // | |
| top_ == r.top_ && // | |
| right_ == r.right_ && // | |
| bottom_ == r.bottom_; | |
| } | |
| [[nodiscard]] constexpr bool operator!=(const TRect& r) const { | |
| return !(*this == r); | |
| } | |
| [[nodiscard]] constexpr TRect Scale(Type scale) const { | |
| return TRect(left_ * scale, // | |
| top_ * scale, // | |
| right_ * scale, // | |
| bottom_ * scale); | |
| } | |
| [[nodiscard]] constexpr TRect Scale(Type scale_x, Type scale_y) const { | |
| return TRect(left_ * scale_x, // | |
| top_ * scale_y, // | |
| right_ * scale_x, // | |
| bottom_ * scale_y); | |
| } | |
| [[nodiscard]] constexpr TRect Scale(TPoint<T> scale) const { | |
| return Scale(scale.x, scale.y); | |
| } | |
| [[nodiscard]] constexpr TRect Scale(TSize<T> scale) const { | |
| return Scale(scale.width, scale.height); | |
| } | |
| /// @brief Returns true iff the provided point |p| is inside the | |
| /// half-open interior of this rectangle. | |
| /// | |
| /// For purposes of containment, a rectangle contains points | |
| /// along the top and left edges but not points along the | |
| /// right and bottom edges so that a point is only ever | |
| /// considered inside one of two abutting rectangles. | |
| [[nodiscard]] constexpr bool Contains(const TPoint<Type>& p) const { | |
| return !this->IsEmpty() && // | |
| p.x >= left_ && // | |
| p.y >= top_ && // | |
| p.x < right_ && // | |
| p.y < bottom_; | |
| } | |
| /// @brief Returns true iff the provided point |p| is inside the | |
| /// closed-range interior of this rectangle. | |
| /// | |
| /// Unlike the regular |Contains(TPoint)| method, this method | |
| /// considers all points along the boundary of the rectangle | |
| /// to be contained within the rectangle - useful for testing | |
| /// if vertices that define a filled shape would carry the | |
| /// interior of that shape outside the bounds of the rectangle. | |
| /// Since both geometries are defining half-open spaces, their | |
| /// defining geometry needs to consider their boundaries to | |
| /// be equivalent with respect to interior and exterior. | |
| [[nodiscard]] constexpr bool ContainsInclusive(const TPoint<Type>& p) const { | |
| return !this->IsEmpty() && // | |
| p.x >= left_ && // | |
| p.y >= top_ && // | |
| p.x <= right_ && // | |
| p.y <= bottom_; | |
| } | |
| /// @brief Returns true iff this rectangle is not empty and it also | |
| /// contains every point considered inside the provided | |
| /// rectangle |o| (as determined by |Contains(TPoint)|). | |
| /// | |
| /// This is similar to a definition where the result is true iff | |
| /// the union of the two rectangles is equal to this rectangle, | |
| /// ignoring precision issues with performing those operations | |
| /// and assuming that empty rectangles are never equal. | |
| /// | |
| /// An empty rectangle can contain no other rectangle. | |
| /// | |
| /// An empty rectangle is, however, contained within any | |
| /// other non-empy rectangle as the set of points it contains | |
| /// is an empty set and so there are no points to fail the | |
| /// containment criteria. | |
| [[nodiscard]] constexpr bool Contains(const TRect& o) const { | |
| return !this->IsEmpty() && // | |
| (o.IsEmpty() || (o.left_ >= left_ && // | |
| o.top_ >= top_ && // | |
| o.right_ <= right_ && // | |
| o.bottom_ <= bottom_)); | |
| } | |
| /// @brief Returns true if all of the fields of this floating point | |
| /// rectangle are finite. | |
| /// | |
| /// Note that the results of |GetWidth()| and |GetHeight()| may | |
| /// still be infinite due to overflow even if the fields themselves | |
| /// are finite. | |
| ONLY_ON_FLOAT_M([[nodiscard]] constexpr, bool) | |
| IsFinite() const { | |
| return std::isfinite(left_) && // | |
| std::isfinite(top_) && // | |
| std::isfinite(right_) && // | |
| std::isfinite(bottom_); | |
| } | |
| /// @brief Returns true if either of the width or height are 0, negative, | |
| /// or NaN. | |
| [[nodiscard]] constexpr bool IsEmpty() const { | |
| // Computing the non-empty condition and negating the result causes any | |
| // NaN value to return true - i.e. is considered empty. | |
| return !(left_ < right_ && top_ < bottom_); | |
| } | |
| /// @brief Returns true if width and height are equal and neither is NaN. | |
| [[nodiscard]] constexpr bool IsSquare() const { | |
| // empty rectangles can technically be "square", but would be | |
| // misleading to most callers. Using |IsEmpty| also prevents | |
| // "non-empty and non-overflowing" computations from happening | |
| // to be equal to "empty and overflowing" results. | |
| // (Consider LTRB(10, 15, MAX-2, MIN+2) which is empty, but both | |
| // w/h subtractions equal "5"). | |
| return !IsEmpty() && (right_ - left_) == (bottom_ - top_); | |
| } | |
| [[nodiscard]] constexpr bool IsMaximum() const { | |
| return *this == MakeMaximum(); | |
| } | |
| /// @brief Returns the upper left corner of the rectangle as specified | |
| /// by the left/top or x/y values when it was constructed. | |
| [[nodiscard]] constexpr TPoint<Type> GetOrigin() const { | |
| return {left_, top_}; | |
| } | |
| /// @brief Returns the size of the rectangle which may be negative in | |
| /// either width or height and may have been clipped to the | |
| /// maximum integer values for integer rects whose size overflows. | |
| [[nodiscard]] constexpr TSize<Type> GetSize() const { | |
| return {GetWidth(), GetHeight()}; | |
| } | |
| /// @brief Returns the X coordinate of the upper left corner, equivalent | |
| /// to |GetOrigin().x| | |
| [[nodiscard]] constexpr Type GetX() const { return left_; } | |
| /// @brief Returns the Y coordinate of the upper left corner, equivalent | |
| /// to |GetOrigin().y| | |
| [[nodiscard]] constexpr Type GetY() const { return top_; } | |
| /// @brief Returns the width of the rectangle, equivalent to | |
| /// |GetSize().width| | |
| [[nodiscard]] constexpr Type GetWidth() const { | |
| return saturated::Sub(right_, left_); | |
| } | |
| /// @brief Returns the height of the rectangle, equivalent to | |
| /// |GetSize().height| | |
| [[nodiscard]] constexpr Type GetHeight() const { | |
| return saturated::Sub(bottom_, top_); | |
| } | |
| [[nodiscard]] constexpr auto GetLeft() const { return left_; } | |
| [[nodiscard]] constexpr auto GetTop() const { return top_; } | |
| [[nodiscard]] constexpr auto GetRight() const { return right_; } | |
| [[nodiscard]] constexpr auto GetBottom() const { return bottom_; } | |
| [[nodiscard]] constexpr TPoint<T> GetLeftTop() const { // | |
| return {left_, top_}; | |
| } | |
| [[nodiscard]] constexpr TPoint<T> GetRightTop() const { | |
| return {right_, top_}; | |
| } | |
| [[nodiscard]] constexpr TPoint<T> GetLeftBottom() const { | |
| return {left_, bottom_}; | |
| } | |
| [[nodiscard]] constexpr TPoint<T> GetRightBottom() const { | |
| return {right_, bottom_}; | |
| } | |
| /// @brief Get the area of the rectangle, equivalent to |GetSize().Area()| | |
| [[nodiscard]] constexpr T Area() const { | |
| // TODO(flutter/flutter#141710) - Use saturated math to avoid overflow | |
| // https://github.com/flutter/flutter/issues/141710 | |
| return IsEmpty() ? 0 : (right_ - left_) * (bottom_ - top_); | |
| } | |
| /// @brief Get the center point as a |Point|. | |
| [[nodiscard]] constexpr Point GetCenter() const { | |
| return {saturated::AverageScalar(left_, right_), | |
| saturated::AverageScalar(top_, bottom_)}; | |
| } | |
| [[nodiscard]] constexpr std::array<T, 4> GetLTRB() const { | |
| return {left_, top_, right_, bottom_}; | |
| } | |
| /// @brief Get the x, y coordinates of the origin and the width and | |
| /// height of the rectangle in an array. | |
| [[nodiscard]] constexpr std::array<T, 4> GetXYWH() const { | |
| return {left_, top_, GetWidth(), GetHeight()}; | |
| } | |
| /// @brief Get a version of this rectangle that has a non-negative size. | |
| [[nodiscard]] constexpr TRect GetPositive() const { | |
| if (!IsEmpty()) { | |
| return *this; | |
| } | |
| return { | |
| std::min(left_, right_), | |
| std::min(top_, bottom_), | |
| std::max(left_, right_), | |
| std::max(top_, bottom_), | |
| }; | |
| } | |
| /// @brief Get the points that represent the 4 corners of this rectangle | |
| /// in a Z order that is compatible with triangle strips or a set | |
| /// of all zero points if the rectangle is empty. | |
| /// The order is: Top left, top right, bottom left, bottom right. | |
| [[nodiscard]] constexpr std::array<TPoint<T>, 4> GetPoints() const { | |
| if (IsEmpty()) { | |
| return {}; | |
| } | |
| return { | |
| TPoint{left_, top_}, | |
| TPoint{right_, top_}, | |
| TPoint{left_, bottom_}, | |
| TPoint{right_, bottom_}, | |
| }; | |
| } | |
| [[nodiscard]] constexpr std::array<TPoint<T>, 4> GetTransformedPoints( | |
| const Matrix& transform) const { | |
| auto points = GetPoints(); | |
| for (size_t i = 0; i < points.size(); i++) { | |
| points[i] = transform * points[i]; | |
| } | |
| return points; | |
| } | |
| /// @brief Creates a new bounding box that contains this transformed | |
| /// rectangle, clipped against the near clipping plane if | |
| /// necessary. | |
| [[nodiscard]] constexpr TRect TransformAndClipBounds( | |
| const Matrix& transform) const { | |
| if (!transform.HasPerspective2D()) { | |
| return TransformBounds(transform); | |
| } | |
| if (IsEmpty()) { | |
| return {}; | |
| } | |
| auto ul = transform.TransformHomogenous({left_, top_}); | |
| auto ur = transform.TransformHomogenous({right_, top_}); | |
| auto ll = transform.TransformHomogenous({left_, bottom_}); | |
| auto lr = transform.TransformHomogenous({right_, bottom_}); | |
| // It can probably be proven that we only ever have 5 points at most | |
| // which happens when only 1 corner is clipped and we get 2 points | |
| // in return for it as we interpolate against its neighbors. | |
| Point points[8]; | |
| int index = 0; | |
| // Process (clip and interpolate) each point against its 2 neighbors: | |
| // left, pt, right | |
| index = ClipAndInsert(points, index, ll, ul, ur); | |
| index = ClipAndInsert(points, index, ul, ur, lr); | |
| index = ClipAndInsert(points, index, ur, lr, ll); | |
| index = ClipAndInsert(points, index, lr, ll, ul); | |
| auto bounds = TRect::MakePointBounds(points, points + index); | |
| return bounds.value_or(TRect{}); | |
| } | |
| /// @brief Creates a new bounding box that contains this transformed | |
| /// rectangle. | |
| [[nodiscard]] constexpr TRect TransformBounds(const Matrix& transform) const { | |
| if (IsEmpty()) { | |
| return {}; | |
| } | |
| auto points = GetTransformedPoints(transform); | |
| auto bounds = TRect::MakePointBounds(points.begin(), points.end()); | |
| if (bounds.has_value()) { | |
| return bounds.value(); | |
| } | |
| FML_UNREACHABLE(); | |
| } | |
| /// @brief Constructs a Matrix that will map all points in the coordinate | |
| /// space of the rectangle into a new normalized coordinate space | |
| /// where the upper left corner of the rectangle maps to (0, 0) | |
| /// and the lower right corner of the rectangle maps to (1, 1). | |
| /// | |
| /// Empty and non-finite rectangles will return a zero-scaling | |
| /// transform that maps all points to (0, 0). | |
| [[nodiscard]] constexpr Matrix GetNormalizingTransform() const { | |
| if (!IsEmpty()) { | |
| Scalar sx = 1.0 / GetWidth(); | |
| Scalar sy = 1.0 / GetHeight(); | |
| Scalar tx = left_ * -sx; | |
| Scalar ty = top_ * -sy; | |
| // Exclude NaN and infinities and either scale underflowing to zero | |
| if (sx != 0.0 && sy != 0.0 && 0.0 * sx * sy * tx * ty == 0.0) { | |
| // clang-format off | |
| return Matrix( sx, 0.0f, 0.0f, 0.0f, | |
| 0.0f, sy, 0.0f, 0.0f, | |
| 0.0f, 0.0f, 1.0f, 0.0f, | |
| tx, ty, 0.0f, 1.0f); | |
| // clang-format on | |
| } | |
| } | |
| // Map all coordinates to the origin. | |
| return Matrix::MakeScale({0.0f, 0.0f, 1.0f}); | |
| } | |
| [[nodiscard]] constexpr TRect Union(const TRect& o) const { | |
| if (IsEmpty()) { | |
| return o; | |
| } | |
| if (o.IsEmpty()) { | |
| return *this; | |
| } | |
| return { | |
| std::min(left_, o.left_), | |
| std::min(top_, o.top_), | |
| std::max(right_, o.right_), | |
| std::max(bottom_, o.bottom_), | |
| }; | |
| } | |
| [[nodiscard]] constexpr std::optional<TRect> Intersection( | |
| const TRect& o) const { | |
| if (IntersectsWithRect(o)) { | |
| return TRect{ | |
| std::max(left_, o.left_), | |
| std::max(top_, o.top_), | |
| std::min(right_, o.right_), | |
| std::min(bottom_, o.bottom_), | |
| }; | |
| } else { | |
| return std::nullopt; | |
| } | |
| } | |
| [[nodiscard]] constexpr bool IntersectsWithRect(const TRect& o) const { | |
| return !IsEmpty() && // | |
| !o.IsEmpty() && // | |
| left_ < o.right_ && // | |
| top_ < o.bottom_ && // | |
| right_ > o.left_ && // | |
| bottom_ > o.top_; | |
| } | |
| /// @brief Returns the new boundary rectangle that would result from this | |
| /// rectangle being cut out by the specified rectangle. | |
| [[nodiscard]] constexpr std::optional<TRect<T>> Cutout(const TRect& o) const { | |
| if (IsEmpty()) { | |
| // This test isn't just a short-circuit, it also prevents the concise | |
| // math below from returning the wrong answer on empty rects. | |
| // Once we know that this rectangle is not empty, the math below can | |
| // only succeed in computing a value if o is also non-empty and non-nan. | |
| // Otherwise, the method returns *this by default. | |
| return std::nullopt; | |
| } | |
| const auto& [a_left, a_top, a_right, a_bottom] = GetLTRB(); // Source rect. | |
| const auto& [b_left, b_top, b_right, b_bottom] = o.GetLTRB(); // Cutout. | |
| if (b_left <= a_left && b_right >= a_right) { | |
| if (b_top <= a_top && b_bottom >= a_bottom) { | |
| // Full cutout. | |
| return std::nullopt; | |
| } | |
| if (b_top <= a_top && b_bottom > a_top) { | |
| // Cuts off the top. | |
| return TRect::MakeLTRB(a_left, b_bottom, a_right, a_bottom); | |
| } | |
| if (b_bottom >= a_bottom && b_top < a_bottom) { | |
| // Cuts off the bottom. | |
| return TRect::MakeLTRB(a_left, a_top, a_right, b_top); | |
| } | |
| } | |
| if (b_top <= a_top && b_bottom >= a_bottom) { | |
| if (b_left <= a_left && b_right > a_left) { | |
| // Cuts off the left. | |
| return TRect::MakeLTRB(b_right, a_top, a_right, a_bottom); | |
| } | |
| if (b_right >= a_right && b_left < a_right) { | |
| // Cuts off the right. | |
| return TRect::MakeLTRB(a_left, a_top, b_left, a_bottom); | |
| } | |
| } | |
| return *this; | |
| } | |
| [[nodiscard]] constexpr TRect CutoutOrEmpty(const TRect& o) const { | |
| return Cutout(o).value_or(TRect()); | |
| } | |
| /// @brief Returns a new rectangle translated by the given offset. | |
| [[nodiscard]] constexpr TRect<T> Shift(T dx, T dy) const { | |
| return { | |
| saturated::Add(left_, dx), // | |
| saturated::Add(top_, dy), // | |
| saturated::Add(right_, dx), // | |
| saturated::Add(bottom_, dy), // | |
| }; | |
| } | |
| /// @brief Returns a new rectangle translated by the given offset. | |
| [[nodiscard]] constexpr TRect<T> Shift(TPoint<T> offset) const { | |
| return Shift(offset.x, offset.y); | |
| } | |
| /// @brief Returns a rectangle with expanded edges. Negative expansion | |
| /// results in shrinking. | |
| [[nodiscard]] constexpr TRect<T> Expand(T left, | |
| T top, | |
| T right, | |
| T bottom) const { | |
| return { | |
| saturated::Sub(left_, left), // | |
| saturated::Sub(top_, top), // | |
| saturated::Add(right_, right), // | |
| saturated::Add(bottom_, bottom), // | |
| }; | |
| } | |
| /// @brief Returns a rectangle with expanded edges in all directions. | |
| /// Negative expansion results in shrinking. | |
| [[nodiscard]] constexpr TRect<T> Expand(T amount) const { | |
| return { | |
| saturated::Sub(left_, amount), // | |
| saturated::Sub(top_, amount), // | |
| saturated::Add(right_, amount), // | |
| saturated::Add(bottom_, amount), // | |
| }; | |
| } | |
| /// @brief Returns a rectangle with expanded edges in all directions. | |
| /// Negative expansion results in shrinking. | |
| [[nodiscard]] constexpr TRect<T> Expand(T horizontal_amount, | |
| T vertical_amount) const { | |
| return { | |
| saturated::Sub(left_, horizontal_amount), // | |
| saturated::Sub(top_, vertical_amount), // | |
| saturated::Add(right_, horizontal_amount), // | |
| saturated::Add(bottom_, vertical_amount), // | |
| }; | |
| } | |
| /// @brief Returns a rectangle with expanded edges in all directions. | |
| /// Negative expansion results in shrinking. | |
| [[nodiscard]] constexpr TRect<T> Expand(TPoint<T> amount) const { | |
| return Expand(amount.x, amount.y); | |
| } | |
| /// @brief Returns a rectangle with expanded edges in all directions. | |
| /// Negative expansion results in shrinking. | |
| [[nodiscard]] constexpr TRect<T> Expand(TSize<T> amount) const { | |
| return Expand(amount.width, amount.height); | |
| } | |
| /// @brief Returns a new rectangle that represents the projection of the | |
| /// source rectangle onto this rectangle. In other words, the source | |
| /// rectangle is redefined in terms of the coordinate space of this | |
| /// rectangle. | |
| [[nodiscard]] constexpr TRect<T> Project(TRect<T> source) const { | |
| if (IsEmpty()) { | |
| return {}; | |
| } | |
| return source.Shift(-left_, -top_) | |
| .Scale(1.0 / static_cast<Scalar>(GetWidth()), | |
| 1.0 / static_cast<Scalar>(GetHeight())); | |
| } | |
| ONLY_ON_FLOAT_M([[nodiscard]] constexpr static, TRect) | |
| RoundOut(const TRect<U>& r) { | |
| return TRect::MakeLTRB(saturated::Cast<U, Type>(floor(r.GetLeft())), | |
| saturated::Cast<U, Type>(floor(r.GetTop())), | |
| saturated::Cast<U, Type>(ceil(r.GetRight())), | |
| saturated::Cast<U, Type>(ceil(r.GetBottom()))); | |
| } | |
| ONLY_ON_FLOAT_M([[nodiscard]] constexpr static, TRect) | |
| Round(const TRect<U>& r) { | |
| return TRect::MakeLTRB(saturated::Cast<U, Type>(round(r.GetLeft())), | |
| saturated::Cast<U, Type>(round(r.GetTop())), | |
| saturated::Cast<U, Type>(round(r.GetRight())), | |
| saturated::Cast<U, Type>(round(r.GetBottom()))); | |
| } | |
| [[nodiscard]] constexpr static std::optional<TRect> Union( | |
| const TRect& a, | |
| const std::optional<TRect> b) { | |
| return b.has_value() ? a.Union(b.value()) : a; | |
| } | |
| [[nodiscard]] constexpr static std::optional<TRect> Union( | |
| const std::optional<TRect> a, | |
| const TRect& b) { | |
| return a.has_value() ? a->Union(b) : b; | |
| } | |
| [[nodiscard]] constexpr static std::optional<TRect> Union( | |
| const std::optional<TRect> a, | |
| const std::optional<TRect> b) { | |
| return a.has_value() ? Union(a.value(), b) : b; | |
| } | |
| [[nodiscard]] constexpr static std::optional<TRect> Intersection( | |
| const TRect& a, | |
| const std::optional<TRect> b) { | |
| return b.has_value() ? a.Intersection(b.value()) : a; | |
| } | |
| [[nodiscard]] constexpr static std::optional<TRect> Intersection( | |
| const std::optional<TRect> a, | |
| const TRect& b) { | |
| return a.has_value() ? a->Intersection(b) : b; | |
| } | |
| [[nodiscard]] constexpr static std::optional<TRect> Intersection( | |
| const std::optional<TRect> a, | |
| const std::optional<TRect> b) { | |
| return a.has_value() ? Intersection(a.value(), b) : b; | |
| } | |
| private: | |
| constexpr TRect(Type left, Type top, Type right, Type bottom) | |
| : left_(left), top_(top), right_(right), bottom_(bottom) {} | |
| Type left_; | |
| Type top_; | |
| Type right_; | |
| Type bottom_; | |
| static constexpr Scalar kMinimumHomogenous = 1.0f / (1 << 14); | |
| // Clip p against the near clipping plane (W = kMinimumHomogenous) | |
| // and interpolate a crossing point against the nearby neighbors | |
| // left and right if p is clipped and either of them is not. | |
| // This method can produce 0, 1, or 2 points per call depending on | |
| // how many of the points are clipped. | |
| // 0 - all points are clipped | |
| // 1 - p is unclipped OR | |
| // p is clipped and exactly one of the neighbors is not | |
| // 2 - p is clipped and both neighbors are not | |
| static constexpr int ClipAndInsert(Point clipped[], | |
| int index, | |
| const Vector3& left, | |
| const Vector3& p, | |
| const Vector3& right) { | |
| if (p.z >= kMinimumHomogenous) { | |
| clipped[index++] = {p.x / p.z, p.y / p.z}; | |
| } else { | |
| index = InterpolateAndInsert(clipped, index, p, left); | |
| index = InterpolateAndInsert(clipped, index, p, right); | |
| } | |
| return index; | |
| } | |
| // Interpolate (a clipped) point p against one of its neighbors | |
| // and insert the point into the array where the line between them | |
| // veers from clipped space to unclipped, if such a point exists. | |
| static constexpr int InterpolateAndInsert(Point clipped[], | |
| int index, | |
| const Vector3& p, | |
| const Vector3& neighbor) { | |
| if (neighbor.z >= kMinimumHomogenous) { | |
| auto t = (kMinimumHomogenous - p.z) / (neighbor.z - p.z); | |
| clipped[index++] = { | |
| (t * p.x + (1.0f - t) * neighbor.x) / kMinimumHomogenous, | |
| (t * p.y + (1.0f - t) * neighbor.y) / kMinimumHomogenous, | |
| }; | |
| } | |
| return index; | |
| } | |
| }; | |
| using Rect = TRect<Scalar>; | |
| using IRect32 = TRect<int32_t>; | |
| using IRect64 = TRect<int64_t>; | |
| using IRect = IRect64; | |
| #undef ONLY_ON_FLOAT | |
| #undef ONLY_ON_FLOAT_M | |
| } // namespace impeller | |
| namespace std { | |
| template <class T> | |
| inline std::ostream& operator<<(std::ostream& out, | |
| const impeller::TRect<T>& r) { | |
| out << "(" << r.GetOrigin() << ", " << r.GetSize() << ")"; | |
| return out; | |
| } | |
| } // namespace std | |
| #endif // FLUTTER_IMPELLER_GEOMETRY_RECT_H_ |