/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2022 Blender Foundation. */ #pragma once /** \file * \ingroup bli */ #include #include #include "BLI_math_base.hh" #include "BLI_math_vector_types.hh" #include "BLI_span.hh" #include "BLI_utildefines.h" namespace blender::math { /** * Returns true if all components are exactly equal to 0. */ template [[nodiscard]] inline bool is_zero(const VecBase &a) { for (int i = 0; i < Size; i++) { if (a[i] != T(0)) { return false; } } return true; } /** * Returns true if at least one component is exactly equal to 0. */ template [[nodiscard]] inline bool is_any_zero(const VecBase &a) { for (int i = 0; i < Size; i++) { if (a[i] == T(0)) { return true; } } return false; } /** * Returns true if the given vectors are equal within the given epsilon. * The epsilon is scaled for each component by magnitude of the matching component of `a`. */ template [[nodiscard]] inline bool almost_equal_relative(const VecBase &a, const VecBase &b, const T &epsilon_factor) { for (int i = 0; i < Size; i++) { const float epsilon = epsilon_factor * math::abs(a[i]); if (math::distance(a[i], b[i]) > epsilon) { return false; } } return true; } template [[nodiscard]] inline VecBase abs(const VecBase &a) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = a[i] >= 0 ? a[i] : -a[i]; } return result; } /** * Returns -1 if \a a is less than 0, 0 if \a a is equal to 0, and +1 if \a a is greater than 0. */ template [[nodiscard]] inline VecBase sign(const VecBase &a) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = math::sign(a[i]); } return result; } template [[nodiscard]] inline VecBase min(const VecBase &a, const VecBase &b) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = a[i] < b[i] ? a[i] : b[i]; } return result; } template [[nodiscard]] inline VecBase max(const VecBase &a, const VecBase &b) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = a[i] > b[i] ? a[i] : b[i]; } return result; } template [[nodiscard]] inline VecBase clamp(const VecBase &a, const VecBase &min, const VecBase &max) { VecBase result = a; for (int i = 0; i < Size; i++) { result[i] = std::clamp(result[i], min[i], max[i]); } return result; } template [[nodiscard]] inline VecBase clamp(const VecBase &a, const T &min, const T &max) { VecBase result = a; for (int i = 0; i < Size; i++) { result[i] = std::clamp(result[i], min, max); } return result; } template [[nodiscard]] inline VecBase mod(const VecBase &a, const VecBase &b) { VecBase result; for (int i = 0; i < Size; i++) { BLI_assert(b[i] != 0); result[i] = std::fmod(a[i], b[i]); } return result; } template [[nodiscard]] inline VecBase mod(const VecBase &a, const T &b) { BLI_assert(b != 0); VecBase result; for (int i = 0; i < Size; i++) { result[i] = std::fmod(a[i], b); } return result; } /** * Safe version of mod(a, b) that returns 0 if b is equal to 0. */ template [[nodiscard]] inline VecBase safe_mod(const VecBase &a, const VecBase &b) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = (b[i] != 0) ? std::fmod(a[i], b[i]) : 0; } return result; } /** * Safe version of mod(a, b) that returns 0 if b is equal to 0. */ template [[nodiscard]] inline VecBase safe_mod(const VecBase &a, const T &b) { if (b == 0) { return VecBase(0); } VecBase result; for (int i = 0; i < Size; i++) { result[i] = std::fmod(a[i], b); } return result; } /** * Returns \a a if it is a multiple of \a b or the next multiple or \a b after \b a . * In other words, it is equivalent to `divide_ceil(a, b) * b`. * It is undefined if \a a is negative or \b b is not strictly positive. */ template [[nodiscard]] inline VecBase ceil_to_multiple(const VecBase &a, const VecBase &b) { VecBase result; for (int i = 0; i < Size; i++) { BLI_assert(a[i] >= 0); BLI_assert(b[i] > 0); result[i] = ((a[i] + b[i] - 1) / b[i]) * b[i]; } return result; } /** * Integer division that returns the ceiling, instead of flooring like normal C division. * It is undefined if \a a is negative or \b b is not strictly positive. */ template [[nodiscard]] inline VecBase divide_ceil(const VecBase &a, const VecBase &b) { VecBase result; for (int i = 0; i < Size; i++) { BLI_assert(a[i] >= 0); BLI_assert(b[i] > 0); result[i] = (a[i] + b[i] - 1) / b[i]; } return result; } template void min_max(const VecBase &vector, VecBase &min, VecBase &max) { min = math::min(vector, min); max = math::max(vector, max); } /** * Returns 0 if denominator is exactly equal to 0. */ template [[nodiscard]] inline VecBase safe_divide(const VecBase &a, const VecBase &b) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = (b[i] == 0) ? 0 : a[i] / b[i]; } return result; } /** * Returns 0 if denominator is exactly equal to 0. */ template [[nodiscard]] inline VecBase safe_divide(const VecBase &a, const T &b) { return (b != 0) ? a / b : VecBase(0.0f); } template [[nodiscard]] inline VecBase floor(const VecBase &a) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = std::floor(a[i]); } return result; } template [[nodiscard]] inline VecBase ceil(const VecBase &a) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = std::ceil(a[i]); } return result; } template [[nodiscard]] inline VecBase fract(const VecBase &a) { VecBase result; for (int i = 0; i < Size; i++) { result[i] = a[i] - std::floor(a[i]); } return result; } /** * Dot product between two vectors. * Equivalent to component wise multiplication followed by summation of the result. * Equivalent to the cosine of the angle between the two vectors if the vectors are normalized. * \note prefer using `length_manhattan(a)` than `dot(a, vec(1))` to get the sum of all components. */ template [[nodiscard]] inline T dot(const VecBase &a, const VecBase &b) { T result = a[0] * b[0]; for (int i = 1; i < Size; i++) { result += a[i] * b[i]; } return result; } /** * Returns summation of all components. */ template [[nodiscard]] inline T length_manhattan(const VecBase &a) { T result = std::abs(a[0]); for (int i = 1; i < Size; i++) { result += std::abs(a[i]); } return result; } template [[nodiscard]] inline T length_squared(const VecBase &a) { return dot(a, a); } template [[nodiscard]] inline T length(const VecBase &a) { return std::sqrt(length_squared(a)); } /** Return true if each individual column is unit scaled. Mainly for assert usage. */ template [[nodiscard]] inline bool is_unit_scale(const VecBase &v) { /* Checks are flipped so NAN doesn't assert because we're making sure the value was * normalized and in the case we don't want NAN to be raising asserts since there * is nothing to be done in that case. */ const T test_unit = math::length_squared(v); return (!(std::abs(test_unit - T(1)) >= AssertUnitEpsilon::value) || !(std::abs(test_unit) >= AssertUnitEpsilon::value)); } template [[nodiscard]] inline T distance_manhattan(const VecBase &a, const VecBase &b) { return length_manhattan(a - b); } template [[nodiscard]] inline T distance_squared(const VecBase &a, const VecBase &b) { return length_squared(a - b); } template [[nodiscard]] inline T distance(const VecBase &a, const VecBase &b) { return length(a - b); } template [[nodiscard]] inline VecBase reflect(const VecBase &incident, const VecBase &normal) { BLI_assert(is_unit_scale(normal)); return incident - 2.0 * dot(normal, incident) * normal; } template [[nodiscard]] inline VecBase refract(const VecBase &incident, const VecBase &normal, const T &eta) { float dot_ni = dot(normal, incident); float k = 1.0f - eta * eta * (1.0f - dot_ni * dot_ni); if (k < 0.0f) { return VecBase(0.0f); } return eta * incident - (eta * dot_ni + sqrt(k)) * normal; } /** * Project \a p onto \a v_proj . * Returns zero vector if \a v_proj is degenerate (zero length). */ template [[nodiscard]] inline VecBase project(const VecBase &p, const VecBase &v_proj) { if (UNLIKELY(is_zero(v_proj))) { return VecBase(0.0f); } return v_proj * (dot(p, v_proj) / dot(v_proj, v_proj)); } template [[nodiscard]] inline VecBase normalize_and_get_length(const VecBase &v, T &out_length) { out_length = length_squared(v); /* A larger value causes normalize errors in a scaled down models with camera extreme close. */ constexpr T threshold = std::is_same_v ? 1.0e-70 : 1.0e-35f; if (out_length > threshold) { out_length = sqrt(out_length); return v / out_length; } /* Either the vector is small or one of it's values contained `nan`. */ out_length = 0.0; return VecBase(0.0); } template [[nodiscard]] inline VecBase normalize(const VecBase &v) { T len; return normalize_and_get_length(v, len); } /** * \return cross perpendicular vector to \a a and \a b. * \note Return zero vector if \a a and \a b are collinear. * \note The length of the resulting vector is equal to twice the area of the triangle between \a a * and \a b ; and it is equal to the sine of the angle between \a a and \a b if they are * normalized. * \note Blender 3D space uses right handedness: \a a = thumb, \a b = index, return = middle. */ template [[nodiscard]] inline VecBase cross(const VecBase &a, const VecBase &b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; } /** * Same as `cross(a, b)` but use double float precision for the computation. */ [[nodiscard]] inline VecBase cross_high_precision(const VecBase &a, const VecBase &b) { return {float(double(a.y) * double(b.z) - double(a.z) * double(b.y)), float(double(a.z) * double(b.x) - double(a.x) * double(b.z)), float(double(a.x) * double(b.y) - double(a.y) * double(b.x))}; } /** * \param poly: Array of points around a polygon. They don't have to be co-planar. * \return Best fit plane normal for the given polygon loop or zero vector if point * array is too short. Not normalized. */ template [[nodiscard]] inline VecBase cross_poly(Span> poly) { /* Newell's Method. */ int nv = int(poly.size()); if (nv < 3) { return VecBase(0, 0, 0); } const VecBase *v_prev = &poly[nv - 1]; const VecBase *v_curr = &poly[0]; VecBase n(0, 0, 0); for (int i = 0; i < nv;) { n[0] = n[0] + ((*v_prev)[1] - (*v_curr)[1]) * ((*v_prev)[2] + (*v_curr)[2]); n[1] = n[1] + ((*v_prev)[2] - (*v_curr)[2]) * ((*v_prev)[0] + (*v_curr)[0]); n[2] = n[2] + ((*v_prev)[0] - (*v_curr)[0]) * ((*v_prev)[1] + (*v_curr)[1]); v_prev = v_curr; ++i; if (i < nv) { v_curr = &poly[i]; } } return n; } /** * Per component linear interpolation. * \param t: interpolation factor. Return \a a if equal 0. Return \a b if equal 1. * Outside of [0..1] range, use linear extrapolation. */ template [[nodiscard]] inline VecBase interpolate(const VecBase &a, const VecBase &b, const FactorT &t) { return a * (1 - t) + b * t; } /** * \return Point halfway between \a a and \a b. */ template [[nodiscard]] inline VecBase midpoint(const VecBase &a, const VecBase &b) { return (a + b) * 0.5; } /** * Return `vector` if `incident` and `reference` are pointing in the same direction. */ template [[nodiscard]] inline VecBase faceforward(const VecBase &vector, const VecBase &incident, const VecBase &reference) { return (dot(reference, incident) < 0) ? vector : -vector; } /** * \return Index of the component with the greatest magnitude. */ template [[nodiscard]] inline int dominant_axis(const VecBase &a) { VecBase b = abs(a); return ((b.x > b.y) ? ((b.x > b.z) ? 0 : 2) : ((b.y > b.z) ? 1 : 2)); } /** * Calculates a perpendicular vector to \a v. * \note Returned vector can be in any perpendicular direction. * \note Returned vector might not the same length as \a v. */ template [[nodiscard]] inline VecBase orthogonal(const VecBase &v) { const int axis = dominant_axis(v); switch (axis) { case 0: return {-v.y - v.z, v.x, v.x}; case 1: return {v.y, -v.x - v.z, v.y}; case 2: return {v.z, v.z, -v.x - v.y}; } return v; } /** * Calculates a perpendicular vector to \a v. * \note Returned vector can be in any perpendicular direction. */ template [[nodiscard]] inline VecBase orthogonal(const VecBase &v) { return {-v.y, v.x}; } /** * Returns true if vectors are equal within the given epsilon. */ template [[nodiscard]] inline bool is_equal(const VecBase &a, const VecBase &b, const T epsilon = T(0)) { for (int i = 0; i < Size; i++) { if (std::abs(a[i] - b[i]) > epsilon) { return false; } } return true; } /** Intersections. */ template struct isect_result { enum { LINE_LINE_COLINEAR = -1, LINE_LINE_NONE = 0, LINE_LINE_EXACT = 1, LINE_LINE_CROSS = 2, } kind; typename T::base_type lambda; }; template [[nodiscard]] isect_result> isect_seg_seg(const VecBase &v1, const VecBase &v2, const VecBase &v3, const VecBase &v4); } // namespace blender::math