commit c2f44d2bb74ec962c4f83719d0a43ad3b3a761a9 Author: Slendi Date: Sun Aug 24 19:20:40 2025 +0300 Initial commit Signed-off-by: Slendi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b225f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +[Bb]uild* +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2c83bb9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.15) +project(SmathExamples CXX) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(smath INTERFACE) +target_include_directories(smath INTERFACE ${CMAKE_SOURCE_DIR}/include) +add_library(smath::smath ALIAS smath) + +option(BUILD_EXAMPLES "Build example programs" ON) +if(BUILD_EXAMPLES) + file(GLOB EXAMPLE_SOURCES "${CMAKE_SOURCE_DIR}/examples/*.cpp") + foreach(EXAMPLE_FILE ${EXAMPLE_SOURCES}) + get_filename_component(EXAMPLE_NAME ${EXAMPLE_FILE} NAME_WE) + add_executable(${EXAMPLE_NAME} ${EXAMPLE_FILE}) + target_link_libraries(${EXAMPLE_NAME} PRIVATE smath::smath) + endforeach() +endif() diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4fb4c30 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# smath + +Single-file linear algebra math library for C++23. + diff --git a/examples/tour.cpp b/examples/tour.cpp new file mode 100644 index 0000000..589f900 --- /dev/null +++ b/examples/tour.cpp @@ -0,0 +1,66 @@ +/* Copyright 2025 Slendi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +// #define SMATH_IMPLICIT_CONVERSIONS + +#include + +int main() { + using namespace smath; + Vec3 v{1, 2, 3}; + std::println("v: {}", v); + auto v2 = swizzle<"zyx">(v); + std::println("v2: {}", v2); + std::println("+: {}", v + v2); + std::println("-: {}", v - v2); + std::println("*: {}", v * v2); + std::println("/: {}", v / v2); + std::println("dot: {}", v.dot(v2)); + std::println("rrggbb: {}", swizzle<"rrggbb">(v)); + std::println("Magnitude: {}", v.magnitude()); + std::println("Normalized: {}", v.normalized()); + std::println("(alias) Unit: {}", v.unit()); + std::println("(alias) Normalize: {}", v.normalize()); + std::println("(alias) Length: {}", v.length()); + std::println("std::get<1>(v): {}", std::get<1>(v)); + auto [x, y, z] = v; + std::println("Bindings: [{}, {}, {}]", x, y, z); + + // Let's mix and match! + Vec<6> v3(v, 7, swizzle<"zy">(v2)); + std::println("{{v, 7, XZ(v2)}}: {}", v3); + + // Scalar operations + std::println("v + 3: {}", v + 3); + std::println("v - 3: {}", v - 3); + std::println("v * 3: {}", v * 3); + std::println("v / 3: {}", v / 3); + + std::println("3 + v: {}", 3 + v); + std::println("3 - v: {}", 3 - v); + std::println("3 * v: {}", 3 * v); + std::println("3 / v: {}", 3 / v); + + // Casting + auto v4 = static_cast(v); +#ifdef SMATH_IMPLICIT_CONVERSIONS + Vec3d v5 = v; +#else + Vec3d v5 = static_cast(v); +#endif // SMATH_IMPLICIT_CONVERSIONS + std::println("Are v4 and v5 same? {}", v4 == v5); +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..013dd56 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1755615617, + "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "20075955deac2583bb12f07151c2df830ef346b4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..681c483 --- /dev/null +++ b/flake.nix @@ -0,0 +1,32 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + system = system; + }; + in + { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + cmake + ninja + clang-tools + lldb + pkg-config + ]; + }; + } + ); +} diff --git a/include/smath.hpp b/include/smath.hpp new file mode 100644 index 0000000..dc9c66e --- /dev/null +++ b/include/smath.hpp @@ -0,0 +1,436 @@ +#pragma once + +/* Copyright 2025 Slendi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You can define the following macros to change functionality: + * - SMATH_IMPLICIT_CONVERSIONS + */ + +#include +#include +#include +#include +#include + +namespace smath { + +template + requires std::is_arithmetic_v +struct VecV; + +namespace detail { + +template struct FixedString { + char data[N]{}; + static constexpr std::size_t size = N - 1; + constexpr FixedString(char const (&s)[N]) { + for (std::size_t i = 0; i < N; ++i) + data[i] = s[i]; + } + constexpr char operator[](std::size_t i) const { return data[i]; } +}; +template struct is_vecv : std::false_type {}; +template +struct is_vecv> : std::true_type {}; +template +inline constexpr bool is_vecv_v = is_vecv>::value; +template +inline constexpr bool is_scalar_v = + std::is_arithmetic_v>; +template struct vecv_size; +template +struct vecv_size> : std::integral_constant {}; + +} // namespace detail + +template + requires std::is_arithmetic_v +struct VecV : std::array { +private: + template static consteval std::size_t extent() { + if constexpr (detail::is_vecv_v) + return detail::vecv_size>::value; + else if constexpr (detail::is_scalar_v) + return 1; + else + return 0; // Should be unreachable + } + template static consteval std::size_t total_extent() { + return (extent() + ... + 0); + } + +public: + // Constructors + constexpr VecV() noexcept { + for (auto &v : *this) + v = T(0); + } + + explicit constexpr VecV(T const &s) noexcept { + for (auto &v : *this) + v = s; + } + + template + requires((detail::is_scalar_v || detail::is_vecv_v) && ...) && + (total_extent() == N) && + (!(sizeof...(Args) == 1 && (detail::is_vecv_v && ...))) + constexpr VecV(Args &&...args) noexcept { + std::size_t i = 0; + (fill_one(i, std::forward(args)), ...); + } + + // Member accesses + // NOTE: This can (probably) be improved with C++26 reflection in the future. +#define VEC_ACC(component, req, idx) \ + constexpr auto component() noexcept -> T &requires(N >= req) { \ + return (*this)[idx]; \ + } constexpr auto component() const->T const & \ + requires(N >= req) \ + { \ + return (*this)[idx]; \ + } + + VEC_ACC(r, 1, 0) + VEC_ACC(g, 2, 1) + VEC_ACC(b, 3, 2) + VEC_ACC(a, 4, 3) + + VEC_ACC(x, 1, 0) + VEC_ACC(y, 2, 1) + VEC_ACC(z, 3, 2) + VEC_ACC(w, 4, 3) + + VEC_ACC(s, 1, 0) + VEC_ACC(t, 2, 1) + VEC_ACC(p, 3, 2) + VEC_ACC(q, 4, 3) + + VEC_ACC(u, 1, 0) + VEC_ACC(v, 2, 1) +#undef VEC_ACC + + // RHS operations + friend constexpr auto operator+(T s, VecV const &v) noexcept -> VecV { + return v + s; + } + friend constexpr auto operator-(T s, VecV const &v) noexcept -> VecV { + return VecV(s) - v; + } + friend constexpr auto operator*(T s, VecV const &v) noexcept -> VecV { + return v * s; + } + friend constexpr auto operator/(T s, VecV const &v) noexcept -> VecV { + VecV r{}; + for (std::size_t i = 0; i < N; ++i) + r[i] = s / v[i]; + return r; + } + + // Members +#define VEC_OP(op) \ + constexpr auto operator op(VecV const &rhs) const noexcept -> VecV { \ + VecV result{}; \ + for (std::size_t i = 0; i < N; ++i) { \ + result[i] = (*this)[i] op rhs[i]; \ + } \ + return result; \ + } \ + constexpr auto operator op(T const &rhs) const noexcept -> VecV { \ + VecV result{}; \ + for (std::size_t i = 0; i < N; ++i) { \ + result[i] = (*this)[i] op rhs; \ + } \ + return result; \ + } + VEC_OP(+) + VEC_OP(-) + VEC_OP(*) + VEC_OP(/) +#undef VEC_OP +#define VEC_OP_ASSIGN(sym) \ + constexpr VecV &operator sym##=(VecV const &rhs) noexcept { \ + for (std::size_t i = 0; i < N; ++i) \ + (*this)[i] sym## = rhs[i]; \ + return *this; \ + } \ + constexpr VecV &operator sym##=(T const &s) noexcept { \ + for (std::size_t i = 0; i < N; ++i) \ + (*this)[i] sym## = s; \ + return *this; \ + } + VEC_OP_ASSIGN(+) + VEC_OP_ASSIGN(-) + VEC_OP_ASSIGN(*) + VEC_OP_ASSIGN(/) +#undef VEC_OP_ASSIGN + + constexpr auto magnitude() const noexcept -> T { + T total = 0; + for (auto const &v : *this) + total += v * v; + return std::sqrt(total); + } + constexpr auto length() const noexcept -> T { return this->magnitude(); } + + constexpr VecV normalized_safe(T eps = eps_default) const noexcept { + auto m = magnitude(); + return (m > eps) ? (*this) / m : VecV{}; + } + constexpr VecV normalize_safe(T eps = eps_default) const noexcept { + return normalized_safe(eps); + } + + [[nodiscard]] constexpr auto normalized() noexcept -> VecV const { + return (*this) / this->magnitude(); + } + [[nodiscard]] constexpr auto normalize() noexcept -> VecV const { + return this->normalized(); + } + [[nodiscard]] constexpr auto unit() noexcept -> VecV const { + return this->normalized(); + } + + [[nodiscard]] constexpr auto dot(VecV const &other) noexcept -> T { + T res = 0; + for (std::size_t i = 0; i < N; ++i) { + res += (*this)[i] * other[i]; + } + return res; + } + + static constexpr T eps_default = T(1e-6); + template + [[nodiscard]] constexpr auto + approx_equal(VecV const &rhs, U eps = eps_default) const noexcept { + using F = std::conditional_t, U, double>; + for (size_t i = 0; i < N; ++i) + if (std::abs(F((*this)[i] - rhs[i])) > F(eps)) + return false; + return true; + } + + template constexpr auto magnitude_promoted() const noexcept { + using F = std::conditional_t, U, double>; + F s = 0; + for (auto v : *this) + s += F(v) * F(v); + return std::sqrt(s); + } + + template + requires(N == 3) + constexpr VecV cross(const VecV &r) const noexcept { + return {(*this)[1] * r[2] - (*this)[2] * r[1], + (*this)[2] * r[0] - (*this)[0] * r[2], + (*this)[0] * r[1] - (*this)[1] * r[0]}; + } + + constexpr T distance(VecV const &r) const noexcept { + return (*this - r).magnitude(); + } + + constexpr VecV project_onto(VecV const &n) const noexcept { + auto d = this->dot(n); + auto nn = n.dot(n); + return (nn ? (d / nn) * n : VecV()); + } + + template + requires(std::is_arithmetic_v && N >= 1) + constexpr explicit(!std::is_convertible_v) + VecV(VecV const &other) noexcept { + for (std::size_t i = 0; i < N; ++i) + this->operator[](i) = static_cast(other[i]); + } + + template + requires(std::is_arithmetic_v && N >= 1) + constexpr explicit(!std::is_convertible_v) + operator VecV() const noexcept { + VecV r{}; + for (std::size_t i = 0; i < N; ++i) + r[i] = static_cast((*this)[i]); + return r; + } + + template + requires(std::is_arithmetic_v && !std::is_same_v) + constexpr VecV &operator=(VecV const &rhs) noexcept { + for (std::size_t i = 0; i < N; ++i) + (*this)[i] = static_cast(rhs[i]); + return *this; + } + +private: + constexpr void fill_one(std::size_t &i, const T &v) noexcept { + (*this)[i++] = v; + } +#ifdef SMATH_IMPLICIT_CONVERSIONS + template + requires std::is_arithmetic_v && (!std::is_same_v) + constexpr void fill_one(std::size_t &i, const U &v) noexcept { + (*this)[i++] = static_cast(v); + } + template + constexpr void fill_one(std::size_t &i, const VecV &v) noexcept { + for (std::size_t k = 0; k < M; ++k) + (*this)[i++] = static_cast(v[k]); + } +#endif // SMATH_IMPLICIT_CONVERSIONS + template + constexpr void fill_one(std::size_t &i, const VecV &v) noexcept { + for (std::size_t k = 0; k < M; ++k) + (*this)[i++] = static_cast(v[k]); + } +}; + +template +constexpr T &get(VecV &v) noexcept { + static_assert(I < N); + return v[I]; +} +template +constexpr const T &get(const VecV &v) noexcept { + static_assert(I < N); + return v[I]; +} +template +constexpr T &&get(VecV &&v) noexcept { + static_assert(I < N); + return std::move(v[I]); +} +template +constexpr const T &&get(const VecV &&v) noexcept { + static_assert(I < N); + return std::move(v[I]); +} + +template + requires std::is_arithmetic_v +using Vec = std::conditional_t>; + +namespace detail { + +consteval auto char_to_idx(char c) -> std::size_t { + if (c == 'r' || c == 'x' || c == 's' || c == 'u') + return 0; + else if (c == 'g' || c == 'y' || c == 't' || c == 'v') + return 1; + else if (c == 'b' || c == 'z' || c == 'p') + return 2; + else if (c == 'a' || c == 'w' || c == 'q') + return 3; + return static_cast(-1); +} + +constexpr auto is_valid(char c) -> bool { + switch (c) { + case 'r': + case 'g': + case 'b': + case 'a': + case 'x': + case 'y': + case 'z': + case 'w': + case 's': + case 't': + case 'p': + case 'q': + case 'u': + case 'v': + return true; + } + return false; +} + +template +constexpr auto swizzle_impl(VecV const &v, std::index_sequence) + -> Vec { + static_assert(((is_valid(S[I])) && ...), "Invalid swizzle component"); + static_assert(((char_to_idx(S[I]) < N) && ...), + "Pattern index out of bounds"); + Vec out{}; + std::size_t i = 0; + ((out[i++] = v[char_to_idx(S[I])]), ...); + return out; +} + +template +concept SwizzleCharsOK = [](std::index_sequence) { + return ((is_valid(S[I])) && ...); +}(std::make_index_sequence{}); + +template +concept SwizzleInBounds = [](std::index_sequence) { + return ((char_to_idx(S[I]) < N) && ...); +}(std::make_index_sequence{}); + +template +concept ValidSwizzle = + (S.size > 0) && SwizzleCharsOK && SwizzleInBounds; + +} // namespace detail + +template + requires detail::ValidSwizzle +constexpr auto swizzle(VecV const &v) -> Vec { + return detail::swizzle_impl(v, std::make_index_sequence{}); +} + +using Vec2 = VecV<2>; +using Vec3 = VecV<3>; +using Vec4 = VecV<4>; + +using Vec2d = VecV<2, double>; +using Vec3d = VecV<3, double>; +using Vec4d = VecV<4, double>; + +} // namespace smath + +template + requires std::formattable +struct std::formatter> : std::formatter { + constexpr auto parse(std::format_parse_context &ctx) { + return std::formatter::parse(ctx); + } + + template + auto format(smath::VecV const &v, Ctx &ctx) const { + auto out = ctx.out(); + *out++ = '{'; + for (std::size_t i = 0; i < N; ++i) { + if (i) { + *out++ = ','; + *out++ = ' '; + } + out = std::formatter::format(v[i], ctx); + } + *out++ = '}'; + return out; + } +}; + +namespace std { +template +struct tuple_size> : std::integral_constant {}; + +template +struct tuple_element> { + static_assert(I < N); + using type = T; +}; +} // namespace std