// L============================================================================= // L This software is distributed under the MIT license. // L Copyright 2021 Péter Kardos // L============================================================================= #pragma once #include "../Common/Definitions.hpp" #include "../Common/DeterministicInitializer.hpp" #include "../Common/Traits.hpp" #include #include #include #include #include #include #if MATHTER_USE_XSIMD #include #endif namespace mathter { //------------------------------------------------------------------------------ // Swizzle //------------------------------------------------------------------------------ /// Enables element swizzling (reordering elements) for vectors. /// /// To access swizzlers, use the xx, xy, xyz and similar elements of vectors. /// Swizzlers can be used with assignments, concatenation, casting and constructors. /// To perform arithmetic, cast swizzlers to corresponding vector type. /// template class Swizzle { static constexpr int IndexTable[] = { Indices... }; T* data() { return reinterpret_cast(this); } const T* data() const { return reinterpret_cast(this); } public: Swizzle(const Swizzle&) = delete; Swizzle(Swizzle&&) = delete; /// Builds the swizzled vector object. template operator Vector() const; /// Sets the parent vector's elements from the right-side argument. /// /// Example: b = {1,2,3}; a.yxz = b; -> a contains {2,1,3}. /// You don't have to worry about aliasing (a.xyz = a is totally fine). /// template Swizzle& operator=(const Vector& rhs); /// Sets the parent vector's elements from the right-side argument. /// /// Example: b = {1,2,3}; a.yxz = b.xyz; -> a contains {2,1,3}. /// You don't have to worry about aliasing (a.xyz = a.zyx is totally fine). /// template ::type = 0> Swizzle& operator=(const Swizzle& rhs) { *this = Vector(rhs); return *this; } /// Returns the nth element of the swizzled vector. Example: v.zxy[2] returns y. T& operator[](int idx) { assert(idx < Dim); return data()[IndexTable[idx]]; } /// Returns the nth element of the swizzled vector. Example: v.zxy[2] returns y. T operator[](int idx) const { assert(idx < Dim); return data()[IndexTable[idx]]; } /// Returns the nth element of the swizzled vector. Example: v.zxy(2) returns y. T& operator()(int idx) { assert(idx < Dim); return data()[IndexTable[idx]]; } /// Returns the nth element of the swizzled vector. Example: v.zxy(2) returns y. T operator()(int idx) const { assert(idx < Dim); return data()[IndexTable[idx]]; } /// Builds the swizzled vector object. template const auto ToVector() const { return Vector(*this); } }; //------------------------------------------------------------------------------ // Vectorization utilities //------------------------------------------------------------------------------ template constexpr int ExtendedDim() { if constexpr (!Packed) { if (Dim == 3) { return 4; } if (Dim == 6 || Dim == 7) { return 8; } } return Dim; } template constexpr int IsBatched() { #if MATHTER_USE_XSIMD if constexpr (!Packed) { constexpr auto extendedSize = ExtendedDim(); using BatchT = typename xsimd::make_sized_batch::type; return !std::is_void_v; } #endif return false; } template struct BatchTHelper { static auto GetType() { if constexpr (IsBatched()) { #if MATHTER_USE_XSIMD using B = typename xsimd::make_sized_batch()>::type; return static_cast(nullptr); #else return static_cast(nullptr); #endif } else { return static_cast(nullptr); } } }; template using Batch = std::decay_t::GetType())>>; template constexpr int Alignment() { if constexpr (IsBatched()) { using B = Batch; static_assert(!std::is_void_v, "IsBatched should prevent this case from ever happening."); return alignof(B); } return alignof(T); } //------------------------------------------------------------------------------ // VectorData //------------------------------------------------------------------------------ // General case template struct VectorData { VectorData() {} union { /// A potentially larger array extended to the next SIMD size. alignas(Alignment()) std::array()> extended; }; }; // Small vectors with x,y,z,w members template struct VectorData { VectorData() {} VectorData(const VectorData& rhs) { extended = rhs.extended; } VectorData& operator=(const VectorData& rhs) { extended = rhs.extended; return *this; } union { /// A potentially larger array extended to the next SIMD size. alignas(Alignment()) std::array()> extended; struct { T x, y; }; #define Dim 2 #include "../Swizzle/Swizzle_2.inc.hpp" #undef Dim }; }; template struct VectorData { VectorData() {} VectorData(const VectorData& rhs) { extended = rhs.extended; } VectorData& operator=(const VectorData& rhs) { extended = rhs.extended; return *this; } union { /// A potentially larger array extended to the next SIMD size. alignas(Alignment()) std::array()> extended; struct { T x, y, z; }; #define Dim 3 #include "../Swizzle/Swizzle_4.inc.hpp" #undef Dim }; }; template struct VectorData { VectorData() {} VectorData(const VectorData& rhs) { extended = rhs.extended; } VectorData& operator=(const VectorData& rhs) { extended = rhs.extended; return *this; } union { /// A potentially larger array extended to the next SIMD size. alignas(Alignment()) std::array()> extended; struct { T x, y, z, w; }; #define Dim 4 #include "../Swizzle/Swizzle_4.inc.hpp" #undef Dim }; }; //------------------------------------------------------------------------------ // Tuple utilities //------------------------------------------------------------------------------ template auto AsTuple(const Indexable& value, std::index_sequence) { return std::tuple{ value[Indices]... }; } template auto AsTuple(const Vector& value, std::nullptr_t) { return AsTuple(value, std::make_index_sequence()); } template auto AsTuple(const Swizzle& value, std::nullptr_t) { return AsTuple(value, std::make_index_sequence()); } template auto AsTuple(const T& value, const void*) { return std::tuple{ value }; } template auto AsTuple(const T& value) { return AsTuple(value, nullptr); } //------------------------------------------------------------------------------ // General vector class //------------------------------------------------------------------------------ /// Represents a vector in N-dimensional space. /// The scalar type on which the vector is based. /// You can use builtin floating point or integer types. User-defined types and std::complex /// may also work, but are not yet officially supported. /// The dimension of the vector-space. Must be a positive integer. /// Dynamically sized vectors are not supported yet, but you'll have to use /// to define dynamically sized vectors. /// Set to true to tightly pack vector elements and /// avoid padding of the vector struct. Disables SIMD optimizations. /// /// There is not much extraordinary to vectors, they work as you would expect. /// - you can use common vector space airhtmetic /// - you have common function like normalization /// - you can multiply them with from either side /// - you can concatenate and swizzle them. /// template class Vector : public VectorData { static_assert(Dim >= 1, "Dimension must be positive integer."); public: using VectorData::extended; //-------------------------------------------- // Properties //-------------------------------------------- /// Returns the number of dimensions of the vector. constexpr int Dimension() const { return Dim; } //-------------------------------------------- // Basic constructors //-------------------------------------------- /// Constructs the vector. Does NOT zero-initialize elements. Vector() MATHTER_VECTOR_INITIALIZER(T) {} Vector(const Vector&) = default; Vector& operator=(const Vector&) = default; /// Constructs the vector by converting elements of . template , int> = 0> Vector(const Vector& other) { for (int i = 0; i < Dim; ++i) { data()[i] = (T)other.data()[i]; } } /// Construct the vector using the SIMD batch type as content. template >, int> = 0> explicit Vector(const B& batch) { batch.store_unaligned(data()); } //-------------------------------------------- // Homogeneous up- and downcast //-------------------------------------------- /// Creates a homogeneous vector by appending a 1. template = 2), T2>::type> explicit Vector(const Vector& rhs) : Vector(rhs, 1) {} /// Truncates last coordinate of homogenous vector to create non-homogeneous. template explicit Vector(const Vector& rhs) : Vector(rhs.data()) {} //-------------------------------------------- // Data constructors //-------------------------------------------- /// Constructs the vector from an array of elements. /// The number of elements must be the same as the vector's dimension. template , int> = 0> explicit Vector(const U* elements) { std::copy(elements, elements + Dimension(), begin()); } /// Sets all elements to the same value. template , int> = 0> explicit Vector(U all) { if constexpr (IsBatched()) { Batch(T(all)).store_unaligned(data()); } else { std::fill(begin(), end(), T(all)); } } /// Initializes the vector by concatenating given scalar, vector or swizzle arguments. /// Sum of the dimension of arguments must equal vector dimension. /// Types of arguments may differ from vector's underlying type, in which case cast is forced without a warning. template 1), int>::type = 0> Vector(const Args&... mixed) { auto scalars = std::tuple_cat(AsTuple(mixed)...); auto fun = [this](const auto&... args) { extended = { T(args)... }; }; std::apply(fun, scalars); } //-------------------------------------------- // Accessors //-------------------------------------------- /// Returns the nth element of the vector. T operator[](int idx) const { return data()[idx]; } /// Returns the nth element of the vector. T& operator[](int idx) { return data()[idx]; } /// Returns the nth element of the vector. T operator()(int idx) const { return data()[idx]; } /// Returns the nth element of the vector. T& operator()(int idx) { return data()[idx]; } /// Returns an iterator to the first element. auto cbegin() const { return extended.cbegin(); } /// Returns an iterator to the first element. auto begin() const { return extended.begin(); } /// Returns an iterator to the first element. auto begin() { return extended.begin(); } /// Returns an iterator to the end of the vector (works like STL). auto cend() const { return cbegin() + Dimension(); } /// Returns an iterator to the end of the vector (works like STL). auto end() const { return begin() + Dimension(); } /// Returns an iterator to the end of the vector (works like STL). auto end() { return begin() + Dimension(); } /// Returns a pointer to the underlying array of elements. auto Data() const { return data(); } /// Returns a pointer to the underlying array of elements. auto Data() { return data(); } /// Returns a pointer to the underlying array of elements. auto data() const { return extended.data(); } /// Returns a pointer to the underlying array of elements. auto data() { return extended.data(); } }; template struct SwizzleGenerator { template static constexpr unsigned get_helper(unsigned, unsigned, const void*) { return 0; } template static constexpr unsigned get_helper(unsigned idx, unsigned size, std::nullptr_t) { return idx == 0 ? First : get_helper(idx - 1, size - 1, nullptr); } static constexpr unsigned get(unsigned idx, unsigned size) { return get_helper(idx, size, nullptr); } }; template template Swizzle::operator Vector() const { constexpr auto Dim2 = int(sizeof...(Indices)); using V = Vector; #if MATHTER_USE_XSIMD if constexpr (IsBatched() && Dim2 <= Dim) { using TI = traits::same_size_int_t; static_assert(!std::is_void_v && sizeof(TI) == sizeof(T)); using B = Batch; constexpr auto batchSize = B::size; using BI = xsimd::batch; const auto batch = B::load_unaligned(data()); using G = SwizzleGenerator; constexpr auto first = G::get(0, 9); const auto mask = xsimd::make_batch_constant(); const auto swizzled = xsimd::swizzle(batch, mask); if constexpr (std::is_convertible_v>) { return V{ Vector(swizzled) }; } else { alignas(decltype(swizzled)) std::array extended; swizzled.store_aligned(extended.data()); return V{ Vector(extended.data()) }; } } #endif return V(data()[Indices]...); } template template Swizzle& Swizzle::operator=(const Vector& rhs) { if (data() != rhs.data()) { std::tie((*this)[Indices]...) = AsTuple(rhs); } else { Vector tmp = rhs; *this = tmp; } return *this; } } // namespace mathter