// L============================================================================= // L This software is distributed under the MIT license. // L Copyright 2021 Péter Kardos // L============================================================================= #pragma once #if _MSC_VER && defined(min) #pragma push_macro("min") #pragma push_macro("max") #undef min #undef max #define MATHTER_MINMAX #endif #include "../Common/MathUtil.hpp" #include "VectorImpl.hpp" #include namespace mathter { /// Returns true if the vector's length is too small for precise calculations (i.e. normalization). /// "Too small" means smaller than the square root of the smallest number representable by the underlying scalar. /// This value is ~10^-18 for floats and ~10^-154 for doubles. template bool IsNullvector(const Vector& v) { static constexpr T epsilon = T(1) / impl::ConstexprExp10(impl::ConstexprAbs(std::numeric_limits::min_exponent10) / 2); T length = Length(v); return length < epsilon; } /// Returns the squared length of the vector. template T LengthSquared(const Vector& v) { return Dot(v, v); } /// Returns the length of the vector. template T Length(const Vector& v) { return (T)std::sqrt((T)LengthSquared(v)); } /// Returns the length of the vector, avoids overflow and underflow, so it's more expensive. template T LengthPrecise(const Vector& v) { T maxElement = std::abs(v(0)); for (int i = 1; i < v.Dimension(); ++i) { maxElement = std::max(maxElement, std::abs(v(i))); } if (maxElement == T(0)) { return T(0); } auto scaled = v / maxElement; return std::sqrt(Dot(scaled, scaled)) * maxElement; } /// Returns the euclidean distance between to vectors. template auto Distance(const Vector& lhs, const Vector& rhs) { return Length(lhs - rhs); } /// Makes a unit vector, but keeps direction. template Vector Normalize(const Vector& v) { assert(!IsNullvector(v)); T l = Length(v); return v / l; } /// Checks if the vector is unit vector. There's some tolerance due to floating points. template bool IsNormalized(const Vector& v) { T n = LengthSquared(v); return T(0.9999) <= n && n <= T(1.0001); } /// Makes a unit vector, but keeps direction. Leans towards (1,0,0...) for nullvectors, costs more. template Vector SafeNormalize(const Vector& v) { Vector vmod = v; vmod(0) = std::abs(v(0)) > std::numeric_limits::denorm_min() ? v(0) : std::numeric_limits::denorm_min(); T l = LengthPrecise(vmod); return vmod / l; } /// Makes a unit vector, but keeps direction. Leans towards for nullvectors, costs more. /// Must be a unit vector. template Vector SafeNormalize(const Vector& v, const Vector& degenerate) { assert(IsNormalized(degenerate)); T length = LengthPrecise(v); if (length == 0) { return degenerate; } return v / length; } /// Sets all elements of the vector to the same value. template , int> = 0> void Fill(Vector& lhs, U all) { lhs = Vector((T)all); } /// Calculates the scalar product (dot product) of the two arguments. template T Dot(const Vector& lhs, const Vector& rhs) { #if MATHTER_USE_XSIMD if constexpr (IsBatched()) { struct G { static constexpr bool get(unsigned idx, unsigned size) noexcept { return idx < Dim; } }; using B = Batch; const auto lhsv = B::load_unaligned(lhs.data()); const auto rhsv = B::load_unaligned(rhs.data()); const auto zeros = B{ T(0) }; const auto mask = xsimd::make_batch_bool_constant(); const auto lhsvm = xsimd::select(mask, lhsv, zeros); const auto rhsvm = xsimd::select(mask, rhsv, zeros); const auto prod = lhsvm * rhsvm; return xsimd::reduce_add(prod); } #endif return std::inner_product(lhs.begin(), lhs.end(), rhs.begin(), T(0)); } /// Returns the generalized cross-product in N dimensions. /// You must supply N-1 arguments of type Vector<N>. /// The function returns the generalized cross product as defined by /// https://en.wikipedia.org/wiki/Cross_product#Multilinear_algebra. template auto Cross(const Vector& head, Args&&... args) -> Vector; /// Returns the generalized cross-product in N dimensions. /// See https://en.wikipedia.org/wiki/Cross_product#Multilinear_algebra for definition. template auto Cross(const std::array*, Dim - 1>& args) -> Vector; /// Returns the 2-dimensional cross prodct, which is a vector perpendicular to the argument. template Vector Cross(const Vector& arg) { return Vector(-arg.y, arg.x); } /// Returns the 2-dimensional cross prodct, which is a vector perpendicular to the argument. template Vector Cross(const std::array*, 1>& arg) { return Cross(*(arg[0])); } /// Returns the 3-dimensional cross-product. template Vector Cross(const Vector& lhs, const Vector& rhs) { using VecT = Vector; return VecT(lhs.yzx) * VecT(rhs.zxy) - VecT(lhs.zxy) * VecT(rhs.yzx); } /// Returns the 3-dimensional cross-product. template Vector Cross(const std::array*, 2>& args) { return Cross(*(args[0]), *(args[1])); } /// Returns the element-wise minimum of arguments template Vector Min(const Vector& lhs, const Vector& rhs) { #if MATHTER_USE_XSIMD if constexpr (IsBatched()) { using B = Batch; const auto lhsv = B::load_unaligned(lhs.data()); const auto rhsv = B::load_unaligned(rhs.data()); return Vector{ xsimd::min(lhsv, rhsv) }; } #endif Vector res; for (int i = 0; i < lhs.Dimension(); ++i) { res[i] = std::min(lhs[i], rhs[i]); } return res; } /// Returns the element-wise maximum of arguments template Vector Max(const Vector& lhs, const Vector& rhs) { #if MATHTER_USE_XSIMD if constexpr (IsBatched()) { using B = Batch; const auto lhsv = B::load_unaligned(lhs.data()); const auto rhsv = B::load_unaligned(rhs.data()); return Vector{ xsimd::max(lhsv, rhsv) }; } #endif Vector res; for (int i = 0; i < lhs.Dimension(); ++i) { res[i] = std::max(lhs[i], rhs[i]); } return res; } } // namespace mathter // Generalized cross-product unfortunately needs matrix determinant. #include "../Matrix.hpp" namespace mathter { template auto Cross(const std::array*, Dim - 1>& args) -> Vector { Vector result; Matrix detCalc; // Calculate elements of result on-by-one int sign = 2 * (Dim % 2) - 1; for (int base = 0; base < result.Dimension(); ++base, sign *= -1) { // Fill up sub-matrix the determinant of which yields the coefficient of base-vector. for (int j = 0; j < base; ++j) { for (int i = 0; i < detCalc.RowCount(); ++i) { detCalc(i, j) = (*(args[i]))[j]; } } for (int j = base + 1; j < result.Dimension(); ++j) { for (int i = 0; i < detCalc.RowCount(); ++i) { detCalc(i, j - 1) = (*(args[i]))[j]; } } T coefficient = T(sign) * Determinant(detCalc); result(base) = coefficient; } return result; } template auto Cross(const Vector& head, Args&&... args) -> Vector { static_assert(1 + sizeof...(args) == Dim - 1, "Number of arguments must be (Dimension - 1)."); std::array*, Dim - 1> vectors = { &head, &args... }; return Cross(vectors); } } // namespace mathter #if defined(MATHTER_MINMAX) #pragma pop_macro("min") #pragma pop_macro("max") #endif