diff options
author | Lenny Maiorani <lenny@colorado.edu> | 2022-01-06 19:36:07 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-03-04 12:56:05 +0100 |
commit | d144da3a62565702c0262a41f07457a3aa3a4fc1 (patch) | |
tree | ee7ab3691041934c4edcf3ff2963e78766686169 /Userland/Libraries/LibGfx/VectorN.h | |
parent | c2a66b77dff84d1e6f092e79edf8101c5a1e2019 (diff) | |
download | serenity-d144da3a62565702c0262a41f07457a3aa3a4fc1.zip |
LibGfx: Refactor Vector[2,3,4] to VectorN with specializations
`Gfx::Vector[2,3,4]` are nearly identical implementations. This code
redundancy does not follow the DRY (Don't Repeat Yourself) principle
leading to possible out-of-sync errors between the classes.
Combining these classes into a class template which can be specialized
for each needed size makes the differences obvious through
`constexpr-if` blocks and `requires` clauses.
Diffstat (limited to 'Userland/Libraries/LibGfx/VectorN.h')
-rw-r--r-- | Userland/Libraries/LibGfx/VectorN.h | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/Userland/Libraries/LibGfx/VectorN.h b/Userland/Libraries/LibGfx/VectorN.h new file mode 100644 index 0000000000..585fdea353 --- /dev/null +++ b/Userland/Libraries/LibGfx/VectorN.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@serenityos.org> + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Array.h> +#include <AK/Error.h> +#include <AK/Format.h> +#include <AK/Math.h> +#include <AK/StdLibExtras.h> +#include <AK/String.h> +#include <AK/StringView.h> + +#define LOOP_UNROLL_N 4 + +#define STRINGIFY_HELPER(x) #x +#define STRINGIFY(x) STRINGIFY_HELPER(x) + +#ifdef __clang__ +# define UNROLL_LOOP _Pragma(STRINGIFY(unroll)) +#else +# define UNROLL_LOOP _Pragma(STRINGIFY(GCC unroll(LOOP_UNROLL_N))) +#endif + +namespace Gfx { +template<size_t N, typename T> +requires(N >= 2 && N <= 4) class VectorN final { + static_assert(LOOP_UNROLL_N >= N, "Unroll the entire loop for performance."); + +public: + [[nodiscard]] constexpr VectorN() = default; + [[nodiscard]] constexpr VectorN(T x, T y) requires(N == 2) + : m_data { x, y } + { + } + [[nodiscard]] constexpr VectorN(T x, T y, T z) requires(N == 3) + : m_data { x, y, z } + { + } + [[nodiscard]] constexpr VectorN(T x, T y, T z, T w) requires(N == 4) + : m_data { x, y, z, w } + { + } + + [[nodiscard]] constexpr T x() const { return m_data[0]; } + [[nodiscard]] constexpr T y() const { return m_data[1]; } + [[nodiscard]] constexpr T z() const requires(N >= 3) { return m_data[2]; } + [[nodiscard]] constexpr T w() const requires(N >= 4) { return m_data[3]; } + + constexpr void set_x(T value) { m_data[0] = value; } + constexpr void set_y(T value) { m_data[1] = value; } + constexpr void set_z(T value) requires(N >= 3) { m_data[2] = value; } + constexpr void set_w(T value) requires(N >= 4) { m_data[3] = value; } + + constexpr VectorN& operator+=(const VectorN& other) + { + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + m_data[i] += other.m_data[i]; + return *this; + } + + constexpr VectorN& operator-=(const VectorN& other) + { + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + m_data[i] -= other.m_data[i]; + return *this; + } + + constexpr VectorN& operator*=(const T& t) + { + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + m_data[i] *= t; + return *this; + } + + [[nodiscard]] constexpr VectorN operator+(const VectorN& other) const + { + VectorN result; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result.m_data[i] = m_data[i] + other.m_data[i]; + return result; + } + + [[nodiscard]] constexpr VectorN operator-(const VectorN& other) const + { + VectorN result; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result.m_data[i] = m_data[i] - other.m_data[i]; + return result; + } + + [[nodiscard]] constexpr VectorN operator*(const VectorN& other) const + { + VectorN result; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result.m_data[i] = m_data[i] * other.m_data[i]; + return result; + } + + [[nodiscard]] constexpr VectorN operator-() const + { + VectorN result; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result.m_data[i] = -m_data[i]; + return result; + } + + [[nodiscard]] constexpr VectorN operator/(const VectorN& other) const + { + VectorN result; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result.m_data[i] = m_data[i] / other.m_data[i]; + return result; + } + + template<typename U> + [[nodiscard]] constexpr VectorN operator*(U f) const + { + VectorN result; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result.m_data[i] = m_data[i] * f; + return result; + } + + template<typename U> + [[nodiscard]] constexpr VectorN operator/(U f) const + { + VectorN result; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result.m_data[i] = m_data[i] / f; + return result; + } + + [[nodiscard]] constexpr T dot(const VectorN& other) const + { + T result {}; + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) + result += m_data[i] * other.m_data[i]; + return result; + } + + [[nodiscard]] constexpr VectorN cross(const VectorN& other) const requires(N == 3) + { + return VectorN( + y() * other.z() - z() * other.y(), + z() * other.x() - x() * other.z(), + x() * other.y() - y() * other.x()); + } + + [[nodiscard]] constexpr VectorN normalized() const + { + VectorN copy { *this }; + copy.normalize(); + return copy; + } + + [[nodiscard]] constexpr VectorN clamped(T m, T x) const + { + VectorN copy { *this }; + copy.clamp(m, x); + return copy; + } + + constexpr void clamp(T min_value, T max_value) + { + UNROLL_LOOP + for (auto i = 0u; i < N; ++i) { + m_data[i] = max(min_value, m_data[i]); + m_data[i] = min(max_value, m_data[i]); + } + } + + constexpr void normalize() + { + T const inv_length = 1 / length(); + operator*=(inv_length); + } + + [[nodiscard]] constexpr T length() const + { + if constexpr (N == 2) + return AK::hypot(m_data[0] * m_data[0] + m_data[1] * m_data[1]); + else if constexpr (N == 3) + return AK::sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1] + m_data[2] * m_data[2]); + else + return AK::sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1] + m_data[2] * m_data[2] + m_data[3] * m_data[3]); + } + + [[nodiscard]] constexpr VectorN<3, T> xyz() const requires(N == 4) + { + return VectorN<3, T>(x(), y(), z()); + } + + [[nodiscard]] String to_string() const + { + if constexpr (N == 2) + return String::formatted("[{},{}]", x(), y()); + else if constexpr (N == 3) + return String::formatted("[{},{},{}]", x(), y(), z()); + else + return String::formatted("[{},{},{},{}]", x(), y(), z(), w()); + } + +private: + AK::Array<T, N> m_data; +}; +} |