/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli */ #include #include "BLI_bounds_types.hh" #include "BLI_index_mask.hh" #include "BLI_math_matrix.hh" #include "BLI_math_vector.hh" #include "BLI_span.hh" #include "BLI_task.hh" #include "BLI_virtual_array.hh" #include "PRF_profile.hh" namespace blender { namespace bounds { template [[nodiscard]] inline Bounds merge(const Bounds &a, const Bounds &b) { return {math::min(a.min, b.min), math::max(a.max, b.max)}; } template [[nodiscard]] inline std::optional> merge(const std::optional> &a, const std::optional> &b) { if (a.has_value() && b.has_value()) { return merge(*a, *b); } if (a.has_value()) { return a; } if (b.has_value()) { return b; } return std::nullopt; } template [[nodiscard]] inline std::optional> merge(const std::optional> &a, const Bounds &b) { return merge(a, std::optional>(b)); } template [[nodiscard]] inline std::optional> min_max(const std::optional> &a, const T &b) { if (a.has_value()) { return merge(*a, {b, b}); } return Bounds{b, b}; } /** * Find the smallest and largest values element-wise in the span. */ template [[nodiscard]] inline std::optional> min_max(const Span values) { if (values.is_empty()) { return std::nullopt; } PRF_scope_with_name("bounds::min_max_with_radii", ProfileCategory::Default); const Bounds init{values.first(), values.first()}; return threading::parallel_reduce( values.index_range(), 1024, init, [&](const IndexRange range, const Bounds &init) { Bounds result = init; for (const int i : range) { math::min_max(values[i], result.min, result.max); } return result; }, [](const Bounds &a, const Bounds &b) { return merge(a, b); }); } template [[nodiscard]] inline std::optional> min_max(const IndexMask &mask, const Span values) { if (values.is_empty() || mask.is_empty()) { return std::nullopt; } if (mask.size() == values.size()) { /* To avoid mask slice/lookup. */ return min_max(values); } PRF_scope_with_name("bounds::min_max_with_radii", ProfileCategory::Default); const Bounds init{values[mask.first()], values[mask.first()]}; return threading::parallel_reduce( mask.index_range().drop_front(1), 1024, init, [&](const IndexRange range, const Bounds &init) { Bounds result = init; mask.slice(range).foreach_index_optimized( [&](const int i) { math::min_max(values[i], result.min, result.max); }); return result; }, [](const Bounds &a, const Bounds &b) { return merge(a, b); }); } /** * Find the smallest and largest values element-wise in the span, adding the radius to each element * first. The template type T is expected to have an addition operator implemented with RadiusT. */ template [[nodiscard]] inline std::optional> min_max_with_radii(const Span values, const Span radii) { BLI_assert(values.size() == radii.size()); if (values.is_empty()) { return std::nullopt; } PRF_scope_with_name("bounds::min_max_with_radii", ProfileCategory::Default); const Bounds init{values.first(), values.first()}; return threading::parallel_reduce( values.index_range(), 1024, init, [&](const IndexRange range, const Bounds &init) { Bounds result = init; for (const int i : range) { result.min = math::min(values[i] - radii[i], result.min); result.max = math::max(values[i] + radii[i], result.max); } return result; }, [](const Bounds &a, const Bounds &b) { return merge(a, b); }); } /** * Returns a new bound that contains the intersection of the two given bound. * Returns no box if there are no overlap. */ template [[nodiscard]] inline std::optional> intersect(const Bounds &a, const Bounds &b) { const Bounds result{math::max(a.min, b.min), math::min(a.max, b.max)}; if (result.is_empty()) { return std::nullopt; } return result; } template [[nodiscard]] inline std::optional> intersect(const std::optional> &a, const std::optional> &b) { if (!a.has_value() || !b.has_value()) { return std::nullopt; } return intersect(*a, *b); } /** * Finds the maximum value for elements in the array. */ template inline std::optional max(const VArray &values) { if (values.is_empty()) { return std::nullopt; } PRF_scope_with_name("bounds::max", ProfileCategory::Default); if (const std::optional value = values.get_if_single()) { return value; } const VArraySpan values_span = values; return threading::parallel_reduce( values_span.index_range(), 2048, std::numeric_limits::min(), [&](const IndexRange range, int current_max) { for (const int value : values_span.slice(range)) { current_max = std::max(current_max, value); } return current_max; }, [](const int a, const int b) { return std::max(a, b); }); } /** * Return the eight corners of a 3D bounding box. *
 *
 * Z  Y
 * | /
 * |/
 * .-----X
 *     2----------6
 *    /|         /|
 *   / |        / |
 *  1----------5  |
 *  |  |       |  |
 *  |  3-------|--7
 *  | /        | /
 *  |/         |/
 *  0----------4
 * 
*/ template inline std::array, 8> corners(const Bounds> &bounds) { return { VecBase{bounds.min[0], bounds.min[1], bounds.min[2]}, VecBase{bounds.min[0], bounds.min[1], bounds.max[2]}, VecBase{bounds.min[0], bounds.max[1], bounds.max[2]}, VecBase{bounds.min[0], bounds.max[1], bounds.min[2]}, VecBase{bounds.max[0], bounds.min[1], bounds.min[2]}, VecBase{bounds.max[0], bounds.min[1], bounds.max[2]}, VecBase{bounds.max[0], bounds.max[1], bounds.max[2]}, VecBase{bounds.max[0], bounds.max[1], bounds.min[2]}, }; } /** * Return the four corners of a 2D bounding box. *
 *
 * Y
 * |
 * |
 * .-----X
 *
 *  3----------2
 *  |          |
 *  |          |
 *  |          |
 *  |          |
 *  0----------1
 * 
*/ template inline std::array, 4> corners(const Bounds> &bounds) { return { bounds.min, VecBase{bounds.max.x, bounds.min.y}, bounds.max, VecBase{bounds.min.x, bounds.max.y}, }; } /** * Transform a 3D bounding box. * * Note: this necessarily grows the bounding box, to ensure that the transformed * bounding box fully contains the original. Therefore, calling this iteratively * to transform from space A to space B, and then from space B to space C, etc., * will also iteratively grow the bounding box on each call. Try to avoid doing * that, and instead first compose the transform matrices and then use that to * transform the bounding box. */ template inline Bounds> transform_bounds(const MatBase &matrix, const Bounds> &bounds) { std::array, 8> points = corners(bounds); for (VecBase &p : points) { p = math::transform_point(matrix, p); } return {math::min(Span(points)), math::max(Span(points))}; } /** * Transform a 2D bounding box. * * See the note on the 3D variant. */ template inline Bounds> transform_bounds(const MatBase &matrix, const Bounds> &bounds) { std::array, 4> points = corners(bounds); for (VecBase &p : points) { p = math::transform_point(matrix, p); } return {math::min(Span(points)), math::max(Span(points))}; } namespace detail { template [[nodiscard]] inline bool any_less_than_v(const VecBase &a, const VecBase &b) { for (int i = 0; i < Size; i++) { if (a[i] < b[i]) { return true; } } return false; } template [[nodiscard]] inline bool any_greater_than_v(const VecBase &a, const VecBase &b) { for (int i = 0; i < Size; i++) { if (a[i] > b[i]) { return true; } } return false; } template [[nodiscard]] inline bool any_less_or_equal_than_v(const VecBase &a, const VecBase &b) { for (int i = 0; i < Size; i++) { if (a[i] <= b[i]) { return true; } } return false; } template [[nodiscard]] inline bool any_less_than(const T &a, const T &b) { if constexpr (std::is_integral_v || std::is_floating_point_v) { return a < b; } else { return any_less_than_v(a, b); } } template [[nodiscard]] inline bool any_greater_than(const T &a, const T &b) { if constexpr (std::is_integral_v || std::is_floating_point_v) { return a > b; } else { return any_greater_than_v(a, b); } } template [[nodiscard]] inline bool any_less_or_equal_than(const T &a, const T &b) { if constexpr (std::is_integral_v || std::is_floating_point_v) { return a <= b; } else { return any_less_or_equal_than_v(a, b); } } template [[nodiscard]] inline Bounds segment_bounds(const T &start, const T &end) { Bounds bounds{start, start}; math::min_max(end, bounds.min, bounds.max); return bounds; } /** Returns true if (p1 / q1) > (p2 / q2). */ template [[nodiscard]] inline bool rational_greater_than(const T &p1, const T &q1, const T &p2, const T &q2) { BLI_assert(q1 > T(0) && q2 > T(0)); return p1 * q2 > p2 * q1; } /** Returns true if (p1 / q1) < (p2 / q2). */ template [[nodiscard]] inline bool rational_less_than(const T &p1, const T &q1, const T &p2, const T &q2) { BLI_assert(q1 > T(0) && q2 > T(0)); return p1 * q2 < p2 * q1; } /** Adaptation of Liang-Barsky for N dimensions. */ template [[nodiscard]] inline bool segment_enter_exit_bounds(const Bounds> &bounds, const VecBase &start, const VecBase &end) { T p_enter = T(0); T q_enter = T(1); T p_exit = T(1); T q_exit = T(1); /* t_enter = p_enter / q_enter */ /* t_exit = p_exit / q_exit */ for (int i = 0; i < Size; i++) { const T di = end[i] - start[i]; if (di == T(0)) { /* Segment is parallel to i-th axis. */ if (start[i] < bounds.min[i] || start[i] > bounds.max[i]) { return false; } continue; } /* Note: We flip the sign here to ensure the denominator is positive. This doesn't change the * value of the rational number. */ const T p_low = (di > T(0)) ? bounds.min[i] - start[i] : -(bounds.max[i] - start[i]); const T p_high = (di > T(0)) ? bounds.max[i] - start[i] : -(bounds.min[i] - start[i]); const T di_abs = (di > T(0)) ? di : -di; /* t_low = p_low / di_abs */ /* t_high = p_high / di_abs */ if (rational_greater_than(p_low, di_abs, p_enter, q_enter)) { /* t_low > t_enter */ p_enter = p_low; q_enter = di_abs; } if (rational_less_than(p_high, di_abs, p_exit, q_exit)) { /* t_high < t_exit */ p_exit = p_high; q_exit = di_abs; } if (rational_greater_than(p_enter, q_enter, p_exit, q_exit)) { /* t_enter > t_exit */ return false; } } /* t_exit >= 0 and t_enter <= 1 */ return p_exit >= T(0) && p_enter <= q_enter; } } // namespace detail } // namespace bounds template inline bool Bounds::is_empty() const { return bounds::detail::any_less_or_equal_than(this->max, this->min); } template inline T Bounds::center() const { return math::midpoint(this->min, this->max); } template inline T Bounds::size() const { return math::abs(max - min); } template inline void Bounds::translate(const T &offset) { this->min += offset; this->max += offset; } template inline void Bounds::scale_from_center(const T &scale) { const T center = this->center(); const T new_half_size = this->size() / T(2) * scale; this->min = center - new_half_size; this->max = center + new_half_size; } template inline void Bounds::resize(const T &new_size) { this->min = this->center() - (new_size / T(2)); this->max = this->min + new_size; } template inline void Bounds::recenter(const T &new_center) { const T offset = new_center - this->center(); this->translate(offset); } template template inline void Bounds::pad(const PaddingT &padding) { this->min = this->min - padding; this->max = this->max + padding; } template inline bool Bounds::contains(const T &point) const { if (bounds::detail::any_less_than(point, this->min)) { return false; } if (bounds::detail::any_greater_than(point, this->max)) { return false; } return true; } template inline bool Bounds::intersects(const Bounds &other) const { if (bounds::intersect(*this, other)) { return true; } return false; } template inline bool Bounds::intersects_segment(const T &start, const T &end) const { /* Check end points first to properly handle degenerate case where the segment is a point. */ if (this->contains(start) || this->contains(end)) { return true; } if constexpr (std::is_integral_v || std::is_floating_point_v) { return this->intersects(bounds::detail::segment_bounds(start, end)); } else { /* Check if the segment is entering and exiting the bounds. */ return bounds::detail::segment_enter_exit_bounds(*this, start, end); } } } // namespace blender