├── meson_options.txt ├── docs ├── tests │ ├── test.hpp │ ├── nonCopyable.cpp │ ├── connection.cpp │ ├── flags.cpp │ ├── functionTraits.cpp │ ├── tmp.cpp │ ├── approx.cpp │ ├── meson.build │ ├── utf.cpp │ ├── span.cpp │ ├── callback.cpp │ ├── rect.cpp │ ├── scope.cpp │ ├── clone.cpp │ ├── mat.cpp │ ├── rcallback.cpp │ ├── vec.cpp │ └── bugged.hpp ├── conan │ ├── test_package │ │ ├── main.cpp │ │ ├── CMakeLists.txt │ │ └── conanfile.py │ ├── FindNYTL.cmake │ ├── .gitignore │ └── conanfile.py ├── callback.md └── todo.md ├── .gitignore ├── .editorconfig ├── .travis.yml ├── nytl ├── fwd │ ├── flags.hpp │ ├── span.hpp │ ├── simplex.hpp │ ├── rect.hpp │ ├── mat.hpp │ └── vec.hpp ├── stringParam.hpp ├── nonCopyable.hpp ├── fwd.hpp ├── rect.hpp ├── vec3.hpp ├── vec2.hpp ├── approxVec.hpp ├── math.hpp ├── scope.hpp ├── simplex.hpp ├── approx.hpp ├── vec.hpp ├── flags.hpp ├── tmpUtil.hpp ├── functionTraits.hpp ├── utf.hpp ├── mat.hpp ├── rectOps.hpp ├── bytes.hpp ├── connection.hpp ├── callback.hpp ├── clone.hpp ├── recursiveCallback.hpp ├── vecOps.hpp └── quaternion.hpp ├── LICENSE ├── meson.build └── README.md /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('tests', type: 'boolean', value: false) 2 | -------------------------------------------------------------------------------- /docs/tests/test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nytl::approxOps {} 4 | using namespace nytl::approxOps; 5 | 6 | #include "bugged.hpp" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | obj/ 3 | bin/ 4 | *.o 5 | *.gch 6 | *.swp 7 | .clang_complete 8 | *.exe 9 | *.out 10 | *.sublime* 11 | compile_commands.json 12 | .clangd 13 | .cache 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 4 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /docs/conan/test_package/main.cpp: -------------------------------------------------------------------------------- 1 | #include "nytl/flags.hpp" 2 | 3 | enum testEnum {one, two, three}; 4 | 5 | 6 | int main (){ 7 | nytl::Flags flags; 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /docs/conan/FindNYTL.cmake: -------------------------------------------------------------------------------- 1 | FIND_PATH( 2 | NYTL_INCLUDE_DIR 3 | NAMES 4 | nytl 5 | PATHS 6 | include) 7 | 8 | INCLUDE(FindPackageHandleStandardArgs) 9 | 10 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(NYTL REQUIRED_VARS NYTL_INCLUDE_DIR) 11 | -------------------------------------------------------------------------------- /docs/conan/.gitignore: -------------------------------------------------------------------------------- 1 | #Backup files 2 | *\~ 3 | *swp 4 | 5 | #OSX 6 | Thumbs.db 7 | .DS_Store 8 | 9 | #Emacs buffers 10 | \#*\# 11 | \.#* 12 | 13 | #Conan 14 | test_package/build 15 | conanfile.pyc 16 | conaninfo.txt 17 | conanbuildinfo.txt 18 | conanbuildinfo.cmake 19 | 20 | -------------------------------------------------------------------------------- /docs/conan/test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(NytlTest) 2 | cmake_minimum_required(VERSION 3.0.0) 3 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 4 | conan_basic_setup() 5 | 6 | if(MSVC) 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") 8 | endif(MSVC) 9 | 10 | add_executable(testNytl main.cpp) 11 | 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: bionic 3 | language: python 4 | 5 | matrix: 6 | include: 7 | - env: CC=gcc 8 | - env: CC=clang 9 | 10 | install: 11 | - pip3 install meson ninja 12 | - sudo apt-get -y install valgrind 13 | 14 | script: 15 | - meson build -Dtests=true 16 | - ninja -C build 17 | - meson test -C build --wrapper 'valgrind --leak-check=full --error-exitcode=1' --print-errorlogs 18 | -------------------------------------------------------------------------------- /nytl/fwd/flags.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_FWD_FLAGS 8 | #define NYTL_INCLUDE_FWD_FLAGS 9 | 10 | #include // std::underlaying_type_t 11 | 12 | namespace nytl { 13 | template> class Flags; 14 | } 15 | 16 | #endif //header guard 17 | -------------------------------------------------------------------------------- /docs/conan/test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile, CMake 2 | import os 3 | 4 | class TestNytl(ConanFile): 5 | settings = "os", "compiler", "build_type", "arch" 6 | generators = "cmake" 7 | 8 | def build(self): 9 | cmake = CMake(self) 10 | self.run('cmake "%s" %s' % (self.conanfile_directory, cmake.command_line)) 11 | self.run("cmake --build . %s" % cmake.build_config) 12 | 13 | def test(self): 14 | self.run(os.sep.join([".","bin", "testNytl"])) 15 | 16 | -------------------------------------------------------------------------------- /nytl/fwd/span.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_FWD_SPAN 8 | #define NYTL_INCLUDE_FWD_SPAN 9 | 10 | #include // std::size_t 11 | 12 | namespace nytl { 13 | constexpr const std::size_t dynamic_extent = -1; 14 | template class span; 15 | template 16 | using Span = span; 17 | } 18 | 19 | #ifdef NYTL_SPAN_STD 20 | 21 | // undefined behavior 22 | // span will be removed here with c++20 anyways 23 | namespace std { 24 | using nytl::span; 25 | } 26 | 27 | #endif // NYTL_SPAN_STD 28 | #endif // header guard 29 | -------------------------------------------------------------------------------- /docs/conan/conanfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | from conans import ConanFile 3 | 4 | class NytlConan(ConanFile): 5 | name = "nytl" 6 | version = "master" 7 | generators = "txt" 8 | url="https://github.com/nyorain/nytl" 9 | description="Modern C++ generic header-only template library." 10 | license = "https://github.com/nyorain/nytl/blob/master/LICENSE" 11 | exports_sources = ["FindNYTL.cmake", os.sep.join(["..", "nytl*"])] 12 | exports = os.sep.join(["..", "LICENSE"]) 13 | 14 | def build(self): 15 | self.output.info("No compilation necessary for nytl") 16 | 17 | def package(self): 18 | self.copy("FindNYTL.cmake", ".", ".") 19 | self.copy("*", src="nytl", dst=os.sep.join([".", "include", "nytl"])) 20 | self.copy("LICENSE", dst="licenses", ignore_case=True, keep_path=False) 21 | -------------------------------------------------------------------------------- /nytl/fwd/simplex.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_FWD_SIMPLEX 8 | #define NYTL_INCLUDE_FWD_SIMPLEX 9 | 10 | #include // std::size_t 11 | #include // std::enable_if_t 12 | 13 | namespace nytl { 14 | 15 | template 16 | class Simplex; 17 | 18 | template 19 | using Line = Simplex; 20 | 21 | template 22 | using Triangle = Simplex; 23 | 24 | template 25 | using Tetrahedron = Simplex; 26 | 27 | } 28 | 29 | #endif //header guard 30 | -------------------------------------------------------------------------------- /docs/tests/nonCopyable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct MyNonCopyable : public nytl::NonCopyable {}; 5 | struct MyNonMovable : public nytl::NonMovable {}; 6 | 7 | template T&& rref(); 8 | template const T& cref(); 9 | 10 | template using Copyable = decltype(T{cref()}); 11 | template using Movable = decltype(T{rref()}); 12 | 13 | // - noncopyable 14 | int main() 15 | { 16 | static_assert(nytl::validExpression == false, 17 | "NonCopyable is copyable"); 18 | static_assert(nytl::validExpression == true, 19 | "NonCopyable is not movable"); 20 | 21 | static_assert(nytl::validExpression == false, 22 | "NonMovable is copyable"); 23 | static_assert(nytl::validExpression == false, 24 | "NonMovable is movable"); 25 | } 26 | -------------------------------------------------------------------------------- /docs/tests/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | 5 | TEST(basic) { 6 | nytl::Connection basic; 7 | nytl::UniqueConnection unique; 8 | 9 | basic = {}; 10 | unique = {}; 11 | 12 | unique = basic; 13 | // basic = unique; // should NOT compile! 14 | 15 | EXPECT(basic.connected(), false); 16 | EXPECT(unique.connected(), false); 17 | EXPECT(basic.connectable(), nullptr); 18 | EXPECT(unique.connectable(), nullptr); 19 | } 20 | 21 | TEST(tracked) { 22 | nytl::TrackedConnection tracked; 23 | nytl::TrackedConnection trackedCopy; 24 | 25 | { 26 | nytl::Callback connectable; 27 | tracked = connectable.add([]{}); 28 | trackedCopy = tracked; 29 | EXPECT(tracked.connected(), true); 30 | EXPECT(tracked.connectable(), &connectable); 31 | EXPECT(trackedCopy.connected(), true); 32 | } 33 | 34 | EXPECT(tracked.connected(), false); 35 | EXPECT(trackedCopy.connected(), false); 36 | } 37 | -------------------------------------------------------------------------------- /docs/tests/flags.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | 4 | // TODO: static assert invalid expressions (i.e. for enums without ops enabled) 5 | 6 | enum class Enum { 7 | entry0 = 0, 8 | entry1 = 1, 9 | entry2 = 2, 10 | entry3 = 4 11 | }; 12 | 13 | NYTL_FLAG_OPS(Enum) 14 | 15 | TEST(flags) { 16 | constexpr auto entry23 = Enum::entry2 | Enum::entry3; 17 | static_assert(entry23.value() == 6, "flags test #1"); 18 | 19 | constexpr auto entry3 = entry23 & Enum::entry3; 20 | static_assert(entry3.value() == 4, "flags test #2"); 21 | 22 | constexpr auto entryNot3 = ~Enum::entry3; 23 | static_assert((entryNot3 & Enum::entry0) == Enum::entry0, "flags test #3"); 24 | static_assert((entryNot3 & Enum::entry1) == Enum::entry1, "flags test #4"); 25 | static_assert((entryNot3 & Enum::entry2) == Enum::entry2, "flags test #5"); 26 | 27 | static_assert(static_cast(entry23) == 6, "flags cast test 1"); 28 | static_assert(static_cast(entry3) == 4, "flags cast test 1"); 29 | } 30 | -------------------------------------------------------------------------------- /nytl/fwd/rect.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_FWD_RECT 8 | #define NYTL_INCLUDE_FWD_RECT 9 | 10 | #include // std::size_t 11 | #include // std::uint8_t 12 | 13 | namespace nytl { 14 | 15 | template class Rect; 16 | 17 | template using Rect2 = Rect<2, T>; 18 | template using Rect3 = Rect<3, T>; 19 | template using Rect4 = Rect<4, T>; 20 | 21 | using Rect2i = Rect<2, int>; 22 | using Rect2ui = Rect<2, unsigned int>; 23 | using Rect2f = Rect<2, float>; 24 | using Rect2d = Rect<2, double>; 25 | 26 | using Rect3i = Rect<3, int>; 27 | using Rect3ui = Rect<3, unsigned int>; 28 | using Rect3d = Rect<3, double>; 29 | using Rect3f = Rect<3, float>; 30 | 31 | using Rect4i = Rect<4, int>; 32 | using Rect4ui = Rect<4, unsigned int>; 33 | using Rect4d = Rect<4, double>; 34 | using Rect4f = Rect<4, float>; 35 | 36 | } 37 | 38 | #endif // header guard 39 | -------------------------------------------------------------------------------- /docs/tests/functionTraits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | void foo(); 9 | int bar(int, char, int); 10 | 11 | // utility typedefs to use with validExpression checks 12 | template using ZerothArg = typename T::template ArgsType<0>; 13 | template using FirstArg = typename T::template ArgsType<1>; 14 | 15 | int main() 16 | { 17 | auto lambda = []{}; 18 | 19 | static_assert(!nytl::isCallable); 20 | static_assert(!nytl::isCallable); 21 | static_assert(nytl::isCallable); 22 | static_assert(nytl::isCallable); 23 | static_assert(nytl::isCallable); 24 | static_assert(nytl::isCallable>); 25 | static_assert(!nytl::isCallable); 26 | static_assert(nytl::isCallable>); 27 | 28 | using FooTraits = nytl::FunctionTraits; 29 | 30 | static_assert(!nytl::validExpression); 31 | static_assert(!nytl::validExpression); 32 | } 33 | -------------------------------------------------------------------------------- /docs/tests/tmp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void bar(); 7 | void foo(std::string); 8 | 9 | template using Expression1 = decltype(bar(std::declval())); 10 | template using Expression2 = decltype(foo(std::declval())); 11 | template using Expression3 = decltype(std::round(std::declval())); 12 | 13 | int main() { 14 | static_assert(nytl::validExpression == false, "tmp:1"); 15 | static_assert(nytl::validExpression == false, "tmp:2"); 16 | static_assert(nytl::validExpression == false, "tmp:3"); 17 | 18 | static_assert(nytl::validExpression == false, "tmp:4"); 19 | static_assert(nytl::validExpression == false, "tmp:5"); 20 | static_assert(nytl::validExpression == true, "tmp:6"); 21 | static_assert(nytl::validExpression == true, "tmp:7"); 22 | 23 | static_assert(nytl::validExpression == true, "tmp:8"); 24 | static_assert(nytl::validExpression == false, "tmp:9"); 25 | } 26 | -------------------------------------------------------------------------------- /nytl/stringParam.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_STRING_PARAM 8 | #define NYTL_INCLUDE_STRING_PARAM 9 | 10 | #include 11 | #include 12 | 13 | namespace nytl { 14 | 15 | /// A null-terminated not-owned string. 16 | /// Like std::string_view but guaranteed to be null-terminated. 17 | /// Makes it actually useful as string type parameter. 18 | template 19 | class BasicStringParam : public std::basic_string_view { 20 | public: 21 | using string_view = std::basic_string_view; 22 | using string = std::basic_string; 23 | 24 | public: 25 | constexpr BasicStringParam() = default; 26 | constexpr BasicStringParam(const Char* cstr) : string_view(cstr) {} 27 | BasicStringParam(const string& str) : string_view(str.c_str()) {} 28 | 29 | const Char* c_str() const { return this->data(); } 30 | }; 31 | 32 | using StringParam = BasicStringParam; 33 | using StringParam32 = BasicStringParam; 34 | using StringParam16 = BasicStringParam; 35 | 36 | } // namespace nytl 37 | 38 | #endif // header guard 39 | -------------------------------------------------------------------------------- /docs/tests/approx.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | TEST(basic) { 10 | EXPECT(nytl::approx(3.1, 0.2), 3.0); 11 | EXPECT(nytl::approx(3.1f), 3.1f); 12 | EXPECT(nytl::approx(0.0), -0.0); 13 | 14 | EXPECT(nytl::approx(1.0 + nytl::defaultApproxEpsilon), 1.0); 15 | EXPECT(nytl::approx(1.0 - nytl::defaultApproxEpsilon), 1.0); 16 | 17 | // NOTE: should result in descriptive error 18 | // auto a = nytl::approx(1); 19 | } 20 | 21 | TEST(complex) { 22 | EXPECT(nytl::approx(std::complex(0.0, 0.0)), std::complex(-0.0, 0.0)); 23 | EXPECT(nytl::approx(std::complex(1.0, 1.0), 0.2), std::complex(1.1, 0.9)); 24 | } 25 | 26 | TEST(vec) { 27 | nytl::Vec3f v3f {1.f, 2.f, 3.f}; 28 | EXPECT(nytl::approx(v3f), v3f); 29 | EXPECT(nytl::approx(v3f), (nytl::Vec3d {1.0, 2.0, 3.0})); 30 | EXPECT(nytl::approx(v3f, 0.1), (nytl::Vec3d {1.1, 2.1, 3.1})); 31 | EXPECT(nytl::approx(v3f, 0.2), (nytl::Vec3d {0.9, 2.2, 2.8})); 32 | } 33 | 34 | TEST(mat) { 35 | nytl::Mat2d m2d {1.0, 2.0, 3.0, 4.0}; 36 | EXPECT(nytl::approx(m2d), m2d); 37 | EXPECT(nytl::approx(m2d, 0.1), (nytl::Mat2d {1.1, 1.9, 3.1, 4.1})); 38 | EXPECT(nytl::approx(m2d, 10.0), (nytl::Mat2d {11.0, 12.0, 13.0, 14.0})); 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /nytl/nonCopyable.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Defines the helper base classes NonCopyable and NonMovable. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_NON_COPYABLE 10 | #define NYTL_INCLUDE_NON_COPYABLE 11 | 12 | namespace nytl { 13 | 14 | /// Derive from this class to make it impossible to copy objects of the derived class. 15 | /// \module utility 16 | class NonCopyable { 17 | constexpr NonCopyable(const NonCopyable&) = delete; 18 | constexpr NonCopyable& operator =(const NonCopyable&) = delete; 19 | 20 | protected: 21 | constexpr NonCopyable() noexcept = default; 22 | constexpr NonCopyable(NonCopyable&&) noexcept = default; 23 | constexpr NonCopyable& operator=(NonCopyable&&) noexcept = default; 24 | }; 25 | 26 | /// Derive from this class to make it impossible to copy or move objects of the derived class. 27 | /// \module utility 28 | class NonMovable { 29 | constexpr NonMovable(const NonMovable&) = delete; 30 | constexpr NonMovable& operator =(const NonMovable&) = delete; 31 | constexpr NonMovable(NonMovable&&) = delete; 32 | constexpr NonMovable& operator=(NonMovable&&) = delete; 33 | 34 | protected: 35 | constexpr NonMovable() noexcept = default; 36 | }; 37 | 38 | } // namespace nytl 39 | 40 | #endif // header guard 41 | -------------------------------------------------------------------------------- /nytl/fwd/mat.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_FWD_MAT 8 | #define NYTL_INCLUDE_FWD_MAT 9 | 10 | #include // std::size_t 11 | 12 | namespace nytl { 13 | 14 | using std::size_t; 15 | 16 | template struct Mat; 17 | template using SquareMat = Mat; 18 | 19 | template using Mat2 = SquareMat<2,T>; 20 | template using Mat3 = SquareMat<3,T>; 21 | template using Mat4 = SquareMat<4,T>; 22 | 23 | template using Mat23 = Mat<2,3,T>; 24 | template using Mat24 = Mat<2,4,T>; 25 | 26 | template using Mat32 = Mat<3,2,T>; 27 | template using Mat34 = Mat<3,4,T>; 28 | 29 | template using Mat42 = Mat<4,2,T>; 30 | template using Mat43 = Mat<4,3,T>; 31 | 32 | using Mat2f = Mat2; 33 | using Mat2d = Mat2; 34 | using Mat2i = Mat2; 35 | using Mat2ui = Mat2; 36 | 37 | using Mat3f = Mat3; 38 | using Mat3d = Mat3; 39 | using Mat3i = Mat3; 40 | using Mat3ui = Mat3; 41 | 42 | using Mat4f = Mat4; 43 | using Mat4d = Mat4; 44 | using Mat4i = Mat4; 45 | using Mat4ui = Mat4; 46 | 47 | } // namespace nytl 48 | 49 | #endif //header guard 50 | -------------------------------------------------------------------------------- /docs/tests/meson.build: -------------------------------------------------------------------------------- 1 | tvec = executable('vec', 'vec.cpp', dependencies: nytl_dep) 2 | test('vec', tvec) 3 | 4 | tmat = executable('mat', 'mat.cpp', dependencies: nytl_dep) 5 | test('mat', tmat) 6 | 7 | tcallback = executable('callback', 'callback.cpp', dependencies: nytl_dep) 8 | test('callback', tcallback) 9 | 10 | trcallback = executable('rcallback', 'rcallback.cpp', dependencies: nytl_dep) 11 | test('rcallback', trcallback) 12 | 13 | tclone = executable('clone', 'clone.cpp', dependencies: nytl_dep) 14 | test('clone', tclone) 15 | 16 | tspan = executable('span', 'span.cpp', dependencies: nytl_dep) 17 | test('span', tspan) 18 | 19 | trect = executable('rect', 'rect.cpp', dependencies: nytl_dep) 20 | test('rect', trect) 21 | 22 | tconnection = executable('connection', 'connection.cpp', dependencies: nytl_dep) 23 | test('connection', tconnection) 24 | 25 | tapprox = executable('approx', 'approx.cpp', dependencies: nytl_dep) 26 | test('approx', tapprox) 27 | 28 | tutf = executable('utf', 'utf.cpp', dependencies: nytl_dep) 29 | test('utf', tutf) 30 | 31 | tflags = executable('flags', 'flags.cpp', dependencies: nytl_dep) 32 | test('flags', tflags) 33 | 34 | tscope = executable('scope', 'scope.cpp', dependencies: nytl_dep) 35 | test('scope', tscope) 36 | 37 | # compile-time tests only 38 | executable('nonCopyable', 'nonCopyable.cpp', dependencies: nytl_dep) 39 | executable('tmp', 'tmp.cpp', dependencies: nytl_dep) 40 | executable('functionTraits', 'functionTraits.cpp', dependencies: nytl_dep) 41 | -------------------------------------------------------------------------------- /nytl/fwd.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Contains forward declarations for the major ny classes and templates. 6 | /// This file only contains fwd declarations for classes where they might be needed, not 7 | /// for pure meta templates. Classes with special forward declarations (such as multiple 8 | /// typedefs or default template parameters) are excluded to special files in nytl/fwd/. 9 | 10 | #pragma once 11 | 12 | #ifndef NYTL_INCLUDE_FWD 13 | #define NYTL_INCLUDE_FWD 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace nytl { 22 | 23 | // connection.hpp 24 | template class ConnectableT; 25 | template class ConnectionT; 26 | template class UniqueConnectionT; 27 | 28 | struct ConnectionID; 29 | struct TrackedConnectionID; 30 | 31 | using Connectable = ConnectableT; 32 | using Connection = ConnectionT; 33 | using UniqueConnection = UniqueConnectionT; 34 | 35 | using TrackedConnectable = ConnectableT; 36 | using TrackedConnection = ConnectionT; 37 | using TrackedUniqueConnection = UniqueConnectionT; 38 | 39 | } // namespace nytl 40 | 41 | #endif // header guad 42 | -------------------------------------------------------------------------------- /docs/tests/utf.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | 5 | std::string utf8a = u8"äöüßabêéè"; // some multi-char utf8 string 6 | std::string utf8b = u8"百川生犬虫"; // some random asian chars 7 | 8 | TEST(conversion) { 9 | EXPECT(nytl::charCount(utf8a), 9u); 10 | EXPECT(nytl::toUtf16(utf8a), u"äöüßabêéè"); 11 | EXPECT(nytl::toUtf32(utf8a), U"äöüßabêéè"); 12 | EXPECT(nytl::toUtf8(nytl::toUtf16(utf8a)), u8"äöüßabêéè"); 13 | EXPECT(nytl::toUtf8(nytl::toUtf32(utf8a)), utf8a); 14 | } 15 | 16 | TEST(asian) { 17 | EXPECT(nytl::charCount(utf8b), 5u); 18 | EXPECT(nytl::toUtf16(utf8b), u"百川生犬虫"); 19 | EXPECT(nytl::toUtf32(utf8b), U"百川生犬虫"); 20 | EXPECT(nytl::toUtf8(nytl::toUtf32(utf8b)), utf8b); 21 | EXPECT(std::string(nytl::nth(utf8b, 0).data()), u8"百"); 22 | EXPECT(std::string(nytl::nth(utf8b, 1).data()), u8"川"); 23 | ERROR(std::string(nytl::nth(utf8b, 5).data()), std::out_of_range); 24 | 25 | // change a char 26 | std::uint8_t size; 27 | auto& c2 = nytl::nth(utf8b, 2u, size); 28 | auto sub = u8"気"; // expect it to have the same byte size 29 | std::memcpy(&c2, sub, size); 30 | 31 | EXPECT(nytl::charCount(utf8b), 5u); 32 | EXPECT(std::string(nytl::nth(utf8b, 2u).data()), sub); 33 | } 34 | 35 | TEST(nth) { 36 | std::uint8_t size; 37 | auto& a = nytl::nth(utf8a, 4, size); // retrieve the char at pos 4 (teh 5th) ('a') 38 | EXPECT(size, 1u); 39 | EXPECT(std::string(&a, (unsigned int) size), u8"a"); 40 | 41 | auto& b = nytl::nth(utf8a, 1, size); 42 | EXPECT(size, 2); 43 | EXPECT(std::string(&b, (unsigned int) size), u8"ö"); 44 | 45 | ERROR(nytl::nth(utf8a, 10, size), std::out_of_range); 46 | EXPECT(std::string(nytl::nth(utf8a, 0).data()), std::string(u8"ä")); 47 | } 48 | -------------------------------------------------------------------------------- /nytl/rect.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | ///\file Defines the Rect template class. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_RECT 10 | #define NYTL_INCLUDE_RECT 11 | 12 | #include // nytl::Rect typedefs 13 | #include // nytl::Vec 14 | #include // std::size_t 15 | 16 | namespace nytl { 17 | 18 | /// \brief Templated class that represents the hyperrectangle (n-box) concept. 19 | /// \tparam D The dimension of the hyperrectangle. 20 | /// \tparam P The precision of the hyperrectangle. 21 | /// \details The hyperrectangle is the generalization of a rectangle for higher dimensions. 22 | /// It represents an area that is aligned with the spaces dimensions at a given position with 23 | /// a given size. There exist various operators for the Rect template class e.g. to check for 24 | /// intersection, compute unions or differences. 25 | /// See nytl/vecOps.hpp for various operations on rectangles. 26 | /// \module rect 27 | template 28 | class Rect { 29 | public: 30 | static constexpr std::size_t dim = D; 31 | 32 | using Value = T; 33 | using Size = std::size_t; 34 | using VecType = Vec; 35 | 36 | public: 37 | VecType position; 38 | VecType size; 39 | 40 | public: 41 | /// Converts the Rect to another Rect object of different dimension and/or precision. 42 | template 43 | explicit operator Rect() const 44 | { return Rect{static_cast>(position), static_cast>(size)}; } 45 | }; 46 | 47 | template 48 | Rect(const nytl::Vec&, const nytl::Vec&) 49 | -> Rect>; 50 | 51 | } // namespace nytl 52 | 53 | #endif // header guard 54 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('nytl', 'cpp', 2 | license: 'BSL-1.0', 3 | version: '0.6.0', 4 | meson_version: '>=0.43', 5 | default_options: [ 6 | 'cpp_std=c++17', 7 | 'warning_level=3']) 8 | 9 | warnings = [ 10 | # extra 11 | '-Wduplicated-cond', 12 | '-Wrestrict', 13 | '-Wnull-dereference', 14 | '-Wundef', 15 | '-Wcast-align=strict', 16 | '-Wlogical-op', 17 | 18 | # clang 19 | '-Wno-missing-braces' 20 | ] 21 | 22 | # default arguments 23 | cc = meson.get_compiler('cpp') 24 | add_project_arguments( 25 | cc.get_supported_arguments(warnings), 26 | language: 'cpp') 27 | 28 | headers = [ 29 | 'nytl/approx.hpp', 30 | 'nytl/approxVec.hpp', 31 | 'nytl/callback.hpp', 32 | 'nytl/clone.hpp', 33 | 'nytl/connection.hpp', 34 | 'nytl/flags.hpp', 35 | 'nytl/functionTraits.hpp', 36 | 'nytl/fwd.hpp', 37 | 'nytl/mat.hpp', 38 | 'nytl/matOps.hpp', 39 | 'nytl/math.hpp', 40 | 'nytl/nonCopyable.hpp', 41 | 'nytl/rect.hpp', 42 | 'nytl/rectOps.hpp', 43 | 'nytl/recursiveCallback.hpp', 44 | 'nytl/scope.hpp', 45 | 'nytl/simplex.hpp', 46 | 'nytl/span.hpp', 47 | 'nytl/tmpUtil.hpp', 48 | 'nytl/utf.hpp', 49 | 'nytl/vec.hpp', 50 | 'nytl/vec2.hpp', 51 | 'nytl/vec3.hpp', 52 | 'nytl/vecOps.hpp' 53 | ] 54 | 55 | fwd_headers = [ 56 | 'nytl/fwd/flags.hpp', 57 | 'nytl/fwd/mat.hpp', 58 | 'nytl/fwd/rect.hpp', 59 | 'nytl/fwd/simplex.hpp', 60 | 'nytl/fwd/span.hpp', 61 | 'nytl/fwd/vec.hpp', 62 | ] 63 | 64 | inc_dir = include_directories('.') 65 | nytl_dep = declare_dependency( 66 | version: meson.project_version(), 67 | include_directories: inc_dir) 68 | 69 | test = get_option('tests') 70 | if test 71 | subdir('docs/tests') 72 | endif 73 | 74 | install_headers(headers, subdir: 'nytl') 75 | install_headers(fwd_headers, subdir: 'nytl/fwd') 76 | 77 | pkg = import('pkgconfig') 78 | pkg_dirs = ['.', 'nytl'] 79 | pkg.generate( 80 | name: 'nytl', 81 | filebase: 'nytl', 82 | subdirs: pkg_dirs, 83 | version: meson.project_version(), 84 | description: 'C++17 utility headers') 85 | -------------------------------------------------------------------------------- /docs/callback.md: -------------------------------------------------------------------------------- 1 | ConnectableID concept: 2 | 3 | - default constructor 4 | - constructor only with std::int64_t (or sth compatible) for the id (uniform init is enough) 5 | - the constructor might throw 6 | - noexcept desctructor 7 | - copyable/copy-assignable 8 | - noexcept movable (constructor/assignment) 9 | - set(int64_t) noexcept: Will be called to reset the connection id. 10 | Might be called multiple times. This will only be called with a negative value 11 | from the callback which signals that new callback calls will not trigger the 12 | function anymore. The function might still be called from pending callbacks. 13 | This is called when the represented connection is formally removed, e.g. 14 | by disconnect or clear but the callback cannot yet remove the connection 15 | because it is still iterating. 16 | - removed() noexcept: Will be called before the connection is completely removed, i.e. 17 | the connectable will not touch it anymore in any way. This might be called 18 | directly with a call to reset before or without a call to reset. 19 | This will not be called mutliple times. 20 | After this call returns the connection id will not be accessed anymore. 21 | - get() const noexcept: Returns the underlaying id. 22 | - Note how none of the above function are allowed to throw. Otherwise 23 | the behvaiour is undefined. 24 | - The connection id is still not allowed to recursively access the callback 25 | object accessing it. This means set/removed/get and the constructor/destructor 26 | must NOT access the callback object they are called from in any way. 27 | In a nutshell: Just don't abuse them. They are only there to allow 28 | different notification methods about the connection and callback lifetime. 29 | - Reasoning for this: It's really hard to argue about 'correct' semantics 30 | in this case. What should happen if 'clear() calls id.removed() calls 31 | callback.add(...)' on the same object? It's additionally really hard 32 | to get completly right and would probably involve runtime overhead 33 | which is totally not worth it. 34 | -------------------------------------------------------------------------------- /nytl/fwd/vec.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_FWD_VEC 8 | #define NYTL_INCLUDE_FWD_VEC 9 | 10 | #include // std::size_t 11 | #include // std::uint8_t 12 | 13 | namespace nytl { 14 | 15 | using std::size_t; 16 | 17 | template class Vec; 18 | 19 | template class Vec<2, T>; // nytl/vec2.hpp 20 | template class Vec<3, T>; // nytl/vec3.hpp 21 | 22 | template using Vec2 = Vec<2, T>; 23 | template using Vec3 = Vec<3, T>; 24 | template using Vec4 = Vec<4, T>; 25 | 26 | using Vec2f = Vec2; 27 | using Vec2i = Vec2; 28 | using Vec2ui = Vec2; 29 | using Vec2d = Vec2; 30 | using Vec2b = Vec2; 31 | using Vec2u8 = Vec2; 32 | using Vec2u16 = Vec2; 33 | using Vec2u32 = Vec2; 34 | using Vec2u64 = Vec2; 35 | using Vec2i8 = Vec2; 36 | using Vec2i16 = Vec2; 37 | using Vec2i32 = Vec2; 38 | using Vec2i64 = Vec2; 39 | 40 | using Vec3f = Vec3; 41 | using Vec3i = Vec3; 42 | using Vec3ui = Vec3; 43 | using Vec3d = Vec3; 44 | using Vec3c = Vec3; 45 | using Vec3u8 = Vec3; 46 | using Vec3u16 = Vec3; 47 | using Vec3u32 = Vec3; 48 | using Vec3u64 = Vec3; 49 | using Vec3i8 = Vec3; 50 | using Vec3i16 = Vec3; 51 | using Vec3i32 = Vec3; 52 | using Vec3i64 = Vec3; 53 | 54 | using Vec4f = Vec4; 55 | using Vec4i = Vec4; 56 | using Vec4ui = Vec4; 57 | using Vec4d = Vec4; 58 | using Vec4b = Vec4; 59 | using Vec4u8 = Vec4; 60 | using Vec4u16 = Vec4; 61 | using Vec4u32 = Vec4; 62 | using Vec4u64 = Vec4; 63 | using Vec4i8 = Vec4; 64 | using Vec4i16 = Vec4; 65 | using Vec4i32 = Vec4; 66 | using Vec4i64 = Vec4; 67 | 68 | } 69 | 70 | #endif //header guard 71 | -------------------------------------------------------------------------------- /docs/tests/span.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void foo(nytl::Span names, int& count) { 8 | for(auto& name : names) count += name.size(); 9 | 10 | if(!names.empty()) { 11 | *names.begin() = "first name"; 12 | *names.rbegin() = "last name"; 13 | } 14 | 15 | if(names.size() <= 2) return; 16 | 17 | for(auto& name : names.subspan(2)) ++count, nytl::unused(name); 18 | for(auto& name : names.subspan<0, 2>()) ++count, nytl::unused(name); 19 | } 20 | 21 | void bar(nytl::Span) {} 22 | void baz(nytl::Span) {} 23 | 24 | TEST(span) { 25 | auto emptySpan = nytl::Span(); 26 | emptySpan = {}; 27 | 28 | int count {}; 29 | 30 | std::array namesArray {{"foo", "bar", "baz"}}; 31 | foo(namesArray, count); 32 | 33 | EXPECT(namesArray.front(), "first name"); 34 | EXPECT(namesArray.back(), "last name"); 35 | EXPECT(count, 3 * 3 + 1 + 2); 36 | 37 | bar(namesArray); 38 | // baz(namesArray) // NOTE: should trigger compile time error 39 | 40 | std::vector namesVector {"foo", "bar", "baz", "abz", "bla"}; 41 | bar({namesVector.data(), 3}); 42 | 43 | auto slice = nytl::span(namesVector).subspan(3); 44 | EXPECT(slice[0], "abz"); 45 | EXPECT(slice[1], "bla"); 46 | 47 | const std::vector cnv {1, 2, 3}; 48 | EXPECT(nytl::span(cnv)[0], 1); 49 | 50 | auto ded1 = nytl::span(namesVector.data(), 2); 51 | EXPECT(ded1[0], "foo"); 52 | EXPECT(ded1.size(), 2u); 53 | 54 | std::array arr {5, 6}; 55 | auto ded2 = nytl::span(arr); 56 | EXPECT(ded2.size(), 2u); 57 | EXPECT(ded2[0], 5); 58 | 59 | auto span4 = nytl::Span(arr); 60 | auto ded3 = nytl::span(span4); 61 | EXPECT(ded3.size(), 2u); 62 | 63 | 64 | count = 0; 65 | foo(namesVector, count); 66 | EXPECT(namesVector.front(), "first name"); 67 | EXPECT(namesVector.back(), "last name"); 68 | EXPECT(count, 5 * 3 + 3 + 2); 69 | 70 | baz(namesVector); 71 | // NOTE: per c++20 this is undefined behavior. So we no longer 72 | // check it 73 | // ERROR(bar(namesVector), std::exception); 74 | 75 | count = 0; 76 | foo({namesVector.data(), 4}, count); 77 | foo({namesVector.data(), namesVector.size()}, count); 78 | } 79 | -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | TODO for nytl 2 | ============= 3 | 4 | - more testing !important 5 | - improve: more vec/vecOps, rectOps testing needed 6 | - fix todos at the top of unit tests 7 | - test more rectOps/flags (basic ops) 8 | - utf stress test 9 | - https://stackoverflow.com/questions/1319022/really-good-bad-utf-8-example-test-data 10 | - test more matrix operations 11 | - simplify them? 12 | - practical field tests for already unit tested interfaces 13 | - optimize performance (if possible) (?) 14 | 15 | Not too important 16 | =============== 17 | 18 | - add more rectOps and vecOps (port utility from other projects) 19 | - Vec specilizations stl-like utility functions 20 | - Vec4 specialization? Use x,y,z,w? 21 | - more/better vecOps/matrixOps 22 | - submatrix and subvector functions (possible as generic? otherwise only for Vec/Mat) 23 | - more lu decomp algorithms (like e.g. crout) 24 | - non-square lu decomp? 25 | - various matrix checks: (positive/negative/in-) definite? 26 | - noexcept fixes 27 | - seperation interface/implemetation where neeeded (callback/connection/typemap) 28 | - ? (to be potentially reintroduced (with rework) later on) 29 | - system 30 | - tmp (more tuple ops, integer sequence ops) 31 | - cache 32 | - compFunc (more descriptive name would be good if possible) 33 | - simplexOps (see commit afd548957748a6218f1d4c4ae77abba533c34809) 34 | - pretty bad (ops only working for square matrices!) 35 | - barycentric <-> world space 36 | - some more work... specializations? 37 | - easier isCallable (using FunctionTraits & constexpr if) 38 | - possible? 39 | - think about nytl/convert (checkout commit fa1c07ba599e2adc590521d981243f290754f9f5) 40 | - is `int a = nytl::convert(1.f)` really a good idea? not sure... [AutoCastable] 41 | - arrayCast was probably a good idea (use it in simplex cast operator then again) 42 | - integration (glm, eigen) 43 | - rework the following files (and readd them) (?) 44 | - line/triangle/tetrahedron (with ops) 45 | - misc (printVars, real memberCallback) 46 | - transform (rework!) 47 | - not too generic this time, it is ok 48 | - matrix operation probably enough, no need for a transform class 49 | - tuple (see commit fa1c07ba599e2adc590521d981243f290754f9f5) 50 | - rename validExpression as it is named in the stl 51 | - extend/improve vecOps cw/ip 52 | - test! 53 | - for scalar functions 54 | -------------------------------------------------------------------------------- /nytl/vec3.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file vec3 specialization (x,y,z members) 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_VEC3 10 | #define NYTL_INCLUDE_VEC3 11 | 12 | #include // nytl::Vec declaration 13 | #include // std::min 14 | #include // std::out_of_range 15 | 16 | namespace nytl { 17 | 18 | /// Specialization for 3 component Vec. 19 | /// Holds x,y,z members that are more convenient to access. 20 | /// Compatible with the default class definition. 21 | template 22 | class Vec<3, T> { 23 | public: 24 | T x; 25 | T y; 26 | T z; 27 | 28 | public: 29 | static constexpr size_t size() { return 3; } 30 | 31 | // constexpr Vec() = default; 32 | // constexpr explicit Vec(const T& all) : x(all), y(all), z(all) {} 33 | // constexpr Vec(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {} 34 | 35 | constexpr const T* begin() const { return &x; } 36 | constexpr const T* end() const { return &z + 1; } 37 | constexpr T* begin() { return &x; } 38 | constexpr T* end() { return &z + 1; } 39 | 40 | constexpr T& front() { return x; } 41 | constexpr T& back() { return z; } 42 | 43 | constexpr const T& front() const { return x; } 44 | constexpr const T& back() const { return z; } 45 | 46 | constexpr T* data() { return &x; } 47 | constexpr const T* data() const { return &x; } 48 | 49 | // See the vec2 implementation for implementation reasoning. 50 | constexpr T& operator[](size_t i) { 51 | switch(i) { 52 | case 0: return x; 53 | case 1: return y; 54 | case 2: return z; 55 | default: throw std::out_of_range("Vec3[]"); 56 | } 57 | } 58 | 59 | constexpr const T& operator[](size_t i) const { 60 | switch(i) { 61 | case 0: return x; 62 | case 1: return y; 63 | case 2: return z; 64 | default: throw std::out_of_range("Vec3[]"); 65 | } 66 | } 67 | 68 | // implemented in vec.hpp for all specializations 69 | template 70 | constexpr explicit operator Vec() const { 71 | auto ret = Vec {}; 72 | for(auto i = 0u; i < std::min(size(), OD); ++i) 73 | ret[i] = (*this)[i]; 74 | return ret; 75 | } 76 | }; 77 | 78 | } // namespace nytl 79 | 80 | #endif // header guard 81 | -------------------------------------------------------------------------------- /nytl/vec2.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file vec2 specialization (x,y members) 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_VEC2 10 | #define NYTL_INCLUDE_VEC2 11 | 12 | #include // nytl::Vec declaration 13 | #include // std::min 14 | #include // std::out_of_range 15 | 16 | namespace nytl { 17 | 18 | /// Specialization for 2 component Vec. 19 | /// Holds x,y members that are more convenient to access. 20 | /// Compatible with the default class definition. 21 | template 22 | class Vec<2, T> { 23 | public: 24 | T x; 25 | T y; 26 | 27 | public: 28 | static constexpr size_t size() { return 2; } 29 | 30 | // constexpr Vec() = default; 31 | // constexpr explicit Vec(const T& all) : x(all), y(all) {} 32 | // constexpr Vec(const T& x_, const T& y_) : x(x_), y(y_) {} 33 | 34 | constexpr const T* begin() const { return &x; } 35 | constexpr const T* end() const { return &y + 1; } 36 | constexpr T* begin() { return &x; } 37 | constexpr T* end() { return &y + 1; } 38 | 39 | constexpr T& front() { return x; } 40 | constexpr T& back() { return y; } 41 | 42 | constexpr const T& front() const { return x; } 43 | constexpr const T& back() const { return y; } 44 | 45 | constexpr T* data() { return &x; } 46 | constexpr const T* data() const { return &x; } 47 | 48 | // We could use (data()[i]) but this conflicts constexpr (gcc 7). 49 | // Stl convention is not to check bounds and therefore never throw 50 | // from operator[] (and so does the default Vec implementation). 51 | // But we implicitly have to check bounds here and all other alternatives 52 | // are worse so we throw in the case of out-of-range. It's almost free. 53 | constexpr T& operator[](size_t i) { 54 | switch(i) { 55 | case 0: return x; 56 | case 1: return y; 57 | default: throw std::out_of_range("Vec2[]"); 58 | } 59 | } 60 | 61 | constexpr const T& operator[](size_t i) const { 62 | switch(i) { 63 | case 0: return x; 64 | case 1: return y; 65 | default: throw std::out_of_range("Vec2[]"); 66 | } 67 | } 68 | 69 | // implemented in vec.hpp for all specializations 70 | template 71 | constexpr explicit operator Vec() const { 72 | auto ret = Vec {}; 73 | for(auto i = 0u; i < std::min(size(), OD); ++i) 74 | ret[i] = (*this)[i]; 75 | return ret; 76 | } 77 | }; 78 | 79 | } // namespace nytl 80 | 81 | #endif // header guard 82 | -------------------------------------------------------------------------------- /docs/tests/callback.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | 5 | 6 | // TODO: more testing with custom id type and stuff 7 | // - e.g. throw in id constructor 8 | 9 | // basic connection and callback functionality 10 | TEST(basic) { 11 | nytl::Callback cb; 12 | auto called = 0u; 13 | cb(); 14 | EXPECT(called, 0u); 15 | 16 | // add, operator += 17 | called = 0u; 18 | auto inc = [&]{ ++called; }; 19 | cb += inc; 20 | cb.add(inc); 21 | cb(); 22 | EXPECT(called, 2u); 23 | 24 | // operator = 25 | called = 0u; 26 | cb = inc; 27 | cb(); 28 | EXPECT(called, 1u); 29 | 30 | // clear 31 | called = 0u; 32 | cb.clear(); 33 | cb(); 34 | EXPECT(called, 0u); 35 | 36 | // disconnect 37 | called = 0u; 38 | auto conn1 = cb.add(inc); 39 | cb(); 40 | EXPECT(called, 1u); 41 | conn1.disconnect(); 42 | cb(); 43 | EXPECT(called, 1u); 44 | 45 | // errors 46 | cb.clear(); 47 | ERROR(cb.add(std::function{}), std::invalid_argument); 48 | ERROR(cb = std::function{}, std::invalid_argument); 49 | ERROR(cb += std::function{}, std::invalid_argument); 50 | } 51 | 52 | TEST(exception) { 53 | // check arguments get passed 54 | nytl::Callback cb; 55 | cb.add([&](auto i){ EXPECT(i, 42); }); 56 | cb(42); 57 | 58 | // check exception is propagated 59 | cb = [&](auto i){ if(i < 5) throw 42; }; 60 | cb(7); 61 | ERROR(cb(3), int); 62 | 63 | // check no more handlers after exception is called 64 | auto called = 0u; 65 | cb.add([&](int){ ++called; }); 66 | cb(42); 67 | EXPECT(called, 1u); 68 | ERROR(cb(2), int); 69 | EXPECT(called, 1u); 70 | 71 | // check callback still works as expected 72 | cb(69); 73 | EXPECT(called, 2u); 74 | } 75 | 76 | TEST(retval) { 77 | // basic check that return value is correct 78 | nytl::Callback cb; 79 | cb.add([&]{ return 1; }); 80 | cb.add([&]{ return 2; }); 81 | auto vec = cb(); 82 | 83 | EXPECT(vec.size(), 2u); 84 | EXPECT(vec[0], 1); 85 | EXPECT(vec[1], 2); 86 | } 87 | 88 | TEST(tracked) { 89 | // checking the tracked connection 90 | nytl::TrackedConnection conn; 91 | 92 | { 93 | nytl::TrackedCallback cb; 94 | auto c1 = cb.add([]{}); 95 | EXPECT(c1.connected(), true); 96 | auto c2 = c1; 97 | EXPECT(c2.connected(), true); 98 | EXPECT(c1.id().get(), c2.id().get()); 99 | c2.disconnect(); 100 | EXPECT(c1.connected(), false); 101 | 102 | conn = cb.add([]{}); 103 | EXPECT(conn.connected(), true); 104 | } 105 | 106 | EXPECT(conn.connected(), false); 107 | } -------------------------------------------------------------------------------- /docs/tests/rect.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | // TODO: many more tests needed 7 | 8 | TEST(deduction) { 9 | auto a = nytl::Rect {nytl::Vec{1, 2, 3}, nytl::Vec{4, 5, 6}}; 10 | static_assert(std::is_same_v); 11 | 12 | auto b = nytl::Rect {nytl::Vec{1.f, 2.f}, nytl::Vec{1.f, 1.f}}; 13 | static_assert(std::is_same_v); 14 | 15 | auto c = nytl::Rect {nytl::Vec{1., 2.}, nytl::Vec{1., 1.}}; 16 | static_assert(std::is_same_v); 17 | } 18 | 19 | // The interface of difference(rect, rect) does not say anything about 20 | // the order of the returend rectangles. Therefore we have to test all 21 | // valid orders of difference rects. We also don't assume the method 22 | // of splitting the rectangles. We therefore test for a 23 | // 2 dimensional intersection all 4 valid solutions. 24 | 25 | // ----------- 26 | // | A | 27 | // | -----|------ 28 | // | | | | 29 | // -----|----- | 30 | // | B | 31 | // ------------ 32 | TEST(rect) { 33 | nytl::Rect2i a {{0, 0}, {100, 100}}; 34 | nytl::Rect2i b {{50, 50}, {100, 100}}; 35 | 36 | auto is = nytl::intersection(a, b); 37 | EXPECT(is.position, (nytl::Vec2i{50, 50})); 38 | EXPECT(is.size, (nytl::Vec2i{50, 50})); 39 | 40 | // a - b 41 | auto diffab = nytl::difference(a, b); 42 | EXPECT(diffab.size(), 2u); 43 | 44 | if(diffab.size() == 2u) { 45 | nytl::Rect2i da1 {{0, 0}, {50, 100}}; 46 | nytl::Rect2i da2 {{50, 0}, {50, 50}}; 47 | 48 | nytl::Rect2i db1 {{0, 0}, {100, 50}}; 49 | nytl::Rect2i db2 {{0, 50}, {50, 50}}; 50 | 51 | bool valid = ((diffab[0] == da1) && (diffab[1] == da2)); 52 | valid |= ((diffab[0] == db1) && (diffab[1] == db2)); 53 | valid |= ((diffab[1] == da1) && (diffab[0] == da2)); 54 | valid |= ((diffab[1] == db1) && (diffab[0] == db2)); 55 | 56 | EXPECT(valid, true); 57 | } 58 | 59 | // b - a 60 | auto diffba = nytl::difference(b, a); 61 | EXPECT(diffba.size(), 2u); 62 | 63 | if(diffba.size() == 2u) { 64 | // nytl::print(std::cout, diffba[0]); 65 | // nytl::print(std::cout, diffba[1]); 66 | 67 | nytl::Rect2i da1 {{50, 100}, {100, 50}}; 68 | nytl::Rect2i da2 {{100, 50}, {50, 50}}; 69 | 70 | nytl::Rect2i db1 {{100, 50}, {50, 100}}; 71 | nytl::Rect2i db2 {{50, 100}, {50, 50}}; 72 | 73 | bool valid = ((diffba[0] == da1) && (diffba[1] == da2)); 74 | valid |= ((diffba[0] == db1) && (diffba[1] == db2)); 75 | valid |= ((diffba[1] == da1) && (diffba[0] == da2)); 76 | valid |= ((diffba[1] == db1) && (diffba[0] == db2)); 77 | 78 | EXPECT(valid, true); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /docs/tests/scope.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | 5 | TEST(general) { 6 | auto i = 0u; 7 | 8 | { 9 | auto guard1 = nytl::ScopeGuard {[&]{ 10 | ++i; 11 | throw std::runtime_error(""); 12 | }}; 13 | 14 | auto guard2 = nytl::ScopeGuard {[&]{ 15 | ++i; 16 | }}; 17 | } 18 | 19 | EXPECT(i, 2u); 20 | } 21 | 22 | TEST(success) { 23 | auto i = 0u; 24 | 25 | try { 26 | auto guard = nytl::SuccessGuard {[&]{ 27 | EXPECT(true, false); 28 | ++i; 29 | }}; 30 | 31 | throw 42; 32 | } catch(...) {} 33 | 34 | EXPECT(i, 0u); 35 | try { 36 | auto guard = nytl::SuccessGuard {[&]{ 37 | ++i; 38 | }}; 39 | } catch(...) {} 40 | 41 | EXPECT(i, 1u); 42 | } 43 | 44 | TEST(exception) { 45 | auto i = 0u; 46 | 47 | try { 48 | auto guard = nytl::ExceptionGuard {[&]{ 49 | EXPECT(true, false); 50 | ++i; 51 | }}; 52 | } catch(...) {} 53 | 54 | EXPECT(i, 0u); 55 | try { 56 | auto guard = nytl::ExceptionGuard {[&]{ 57 | ++i; 58 | }}; 59 | throw 42; 60 | } catch(...) {} 61 | 62 | EXPECT(i, 1u); 63 | } 64 | 65 | TEST(nested) { 66 | auto i = 0u; 67 | 68 | try { 69 | auto guard = nytl::ScopeGuard{[&]{ 70 | auto guard = nytl::ScopeGuard{[&]{ 71 | i += 1u; 72 | }}; 73 | 74 | auto eguard = nytl::ExceptionGuard{[&]{ 75 | i += 10u; 76 | }}; 77 | 78 | auto sguard = nytl::SuccessGuard{[&]{ 79 | i += 100u; 80 | EXPECT(true, false); 81 | }}; 82 | 83 | throw std::runtime_error(""); 84 | }}; 85 | 86 | throw std::runtime_error(""); 87 | } catch(...) { 88 | } 89 | 90 | EXPECT(i, 11u); 91 | } 92 | 93 | TEST(lvalue_scopeguard) { 94 | auto foo = false; 95 | auto bar = false; 96 | 97 | { 98 | std::function f = [&]{ foo = true; }; 99 | nytl::ScopeGuard fooOrBar(f); 100 | f = [&]{ bar = true; }; 101 | } 102 | 103 | EXPECT(foo, false); 104 | EXPECT(bar, true); 105 | } 106 | 107 | TEST(lvalue_successguard) { 108 | auto foo = false; 109 | auto bar = false; 110 | 111 | { 112 | std::function f = [&]{ foo = true; }; 113 | nytl::SuccessGuard fooOrBar(f); 114 | f = [&]{ bar = true; }; 115 | } 116 | 117 | EXPECT(foo, false); 118 | EXPECT(bar, true); 119 | } 120 | 121 | TEST(lvalue_exceptionguard) { 122 | auto foo = false; 123 | auto bar = false; 124 | 125 | try { 126 | std::function f = [&]{ foo = true; }; 127 | nytl::ExceptionGuard fooOrBar(f); 128 | f = [&]{ bar = true; }; 129 | throw "nope"; 130 | } catch(...) {} 131 | 132 | EXPECT(foo, false); 133 | EXPECT(bar, true); 134 | } 135 | -------------------------------------------------------------------------------- /nytl/approxVec.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | // TODO: better name for the header would be appreciated 6 | /// Approx specialization for nytl math types like nytl::Vec or nytl::Mat. 7 | 8 | #pragma once 9 | 10 | #ifndef NYTL_INCLUDE_APPROX_VEC 11 | #define NYTL_INCLUDE_APPROX_VEC 12 | 13 | #include // nytl::Approx 14 | #include // nytl::Vec 15 | #include // nytl::Mat 16 | 17 | namespace nytl { 18 | 19 | /// Approx specialization for nytl::Vec 20 | template 21 | class Approx> { 22 | public: 23 | template 24 | friend bool operator==(const nytl::Vec& lhs, const Approx& rhs) { 25 | if(lhs.size() != rhs.value.size()) { 26 | return false; 27 | } 28 | 29 | for(auto i = 0u; i < lhs.size(); ++i) { 30 | if(lhs[i] != approx(rhs.value[i], rhs.epsilon)) { 31 | return false; 32 | } 33 | } 34 | 35 | return true; 36 | } 37 | 38 | template 39 | friend bool operator==(const Approx& lhs, const nytl::Vec& rhs) { 40 | return operator==(rhs, lhs); 41 | } 42 | 43 | template 44 | friend bool operator!=(const nytl::Vec& lhs, const Approx& rhs) { 45 | return !operator==(lhs, rhs); 46 | } 47 | 48 | template 49 | friend bool operator!=(const Approx& lhs, const nytl::Vec& rhs) { 50 | return !operator==(lhs, rhs); 51 | } 52 | 53 | public: 54 | nytl::Vec value {}; 55 | double epsilon {defaultApproxEpsilon}; 56 | }; 57 | 58 | /// Approx specialization for nytl::Mat 59 | template 60 | class Approx> { 61 | public: 62 | template 63 | friend bool operator==(const nytl::Mat& lhs, const Approx& rhs) { 64 | if(lhs.rows() != rhs.value.rows()) { 65 | return false; 66 | } 67 | 68 | for(auto i = 0u; i < lhs.rows(); ++i) { 69 | if(lhs[i] != approx(rhs.value[i], rhs.epsilon)) { 70 | return false; 71 | } 72 | } 73 | 74 | return true; 75 | } 76 | 77 | template 78 | friend bool operator==(const Approx& lhs, const nytl::Mat& rhs) { 79 | return operator==(rhs, lhs); 80 | } 81 | 82 | template 83 | friend bool operator!=(const nytl::Mat& lhs, const Approx& rhs) { 84 | return !operator==(lhs, rhs); 85 | } 86 | 87 | template 88 | friend bool operator!=(const Approx& lhs, const nytl::Mat& rhs) { 89 | return !operator==(lhs, rhs); 90 | } 91 | 92 | public: 93 | nytl::Mat value {}; 94 | double epsilon {defaultApproxEpsilon}; 95 | }; 96 | 97 | } // namesapce nytl 98 | 99 | #endif // header guard 100 | -------------------------------------------------------------------------------- /nytl/math.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Various simple scalar related utility helpers. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_SCALAR 10 | #define NYTL_INCLUDE_SCALAR 11 | 12 | #include // std::clamp 13 | 14 | namespace nytl { 15 | namespace constants { 16 | constexpr const auto pi = 3.14159265359; 17 | constexpr const auto degree = pi / 180.0; // one degree as radian 18 | constexpr const auto e = 2.71828182845; 19 | } // namespace constants 20 | 21 | /// \brief Converts the given angle in radians to degrees. 22 | /// \requires P must represent a mathematical field. 23 | template 24 | constexpr auto degrees(P rad) { 25 | return rad / constants::degree; 26 | } 27 | 28 | /// \brief Converts the given angle in degrees to radians. 29 | /// \requires P must represent a mathematical field. 30 | template 31 | constexpr auto radians(P deg) { 32 | return deg * constants::degree; 33 | } 34 | 35 | /// \brief Returns the linear mix of x and y with factor a. 36 | /// \requires P must represent a mathematical field. 37 | template 38 | constexpr auto mix(P x, P y, T a) { 39 | return (1 - a) * x + a * y; 40 | } 41 | 42 | /// \brief Returns a smooth interpolation between 0 and 1 depending on value 43 | /// in the given range of [min, max]. Uses hermite interpolation. 44 | template 45 | constexpr auto smoothstep(P min, P max, P x) { 46 | auto t = std::clamp((x - min) / (max - min), P {0}, P {1}); 47 | return t * t * (3 - 2 * t); 48 | } 49 | 50 | /// \brief Constexpr factorial implementation 51 | /// Will overflow the returned value quickly (for sth. like n > 20). 52 | /// \returns The factorial of 'n' 53 | constexpr unsigned long factorial(unsigned int n) { 54 | return (n <= 1ul) ? 1ul : n * factorial(n - 1); 55 | } 56 | 57 | /// Maps the given signed number onto a unique unsigned one. 58 | /// Maps 0 to 0, 1 to 2, 2 to 4, -1 to 1, -2 to 3 and so on. 59 | constexpr unsigned int mapUnsigned(int x) { 60 | return (x < 0) ? -x * 2 - 1 : x * 2; 61 | } 62 | 63 | /// Reverses the mapUnsigned function. 64 | /// Returns the signed number that would be mapped onto the given unique 65 | /// unsigned number. 66 | constexpr int unmapUnsigned(unsigned int x) { 67 | auto ix = static_cast(x); 68 | return (ix % 2) ? -(ix + 1) / 2 : ix / 2; 69 | } 70 | 71 | /// Combines the two given unsigned numbers into a single unique one 72 | /// using the cantorsche pairing function. Combine it with calls 73 | /// to mapUnsigned to enable it for signed x,y inputs. 74 | constexpr unsigned int pair(unsigned int x, unsigned int y) { 75 | return (x + y) * (x + y + 1) / 2 + y; 76 | } 77 | 78 | /// Returns clamp(value, min, max), i.e. value or min/max if value exceeds those bounds. 79 | /// Stores in store if the value was clamped, i.e. sets store to 0 if it was not clamped, 80 | /// to -1 if it was clamped to min or to 1 if is was clamped to max. 81 | constexpr auto clampStore(float value, float min, float max, int& store) { 82 | store = 0; 83 | if(value <= min) { 84 | store = -1; 85 | return min; 86 | } else if(value >= max) { 87 | store = 1; 88 | return max; 89 | } 90 | 91 | return value; 92 | } 93 | 94 | } // namespace nytl 95 | 96 | #endif // header guard 97 | -------------------------------------------------------------------------------- /nytl/scope.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_SCOPE 8 | #define NYTL_INCLUDE_SCOPE 9 | 10 | #include // std::uncaught_exceptions 11 | #include // std::cerr 12 | 13 | namespace nytl { 14 | 15 | /// \brief Can be used to execute code when going out of the current scope. 16 | /// This enables to handle non-RAII resources in a pseudo-RAII manner without 17 | /// having to define own wrapper functions. 18 | /// Example for how this could be useful for e.g. file descriptors. 19 | /// Note that no matter which way we take out of the shown scope 20 | /// (exception, early return or just coming to the scopes end) the given scope 21 | /// guard will be executed and the fd closed. 22 | /// All exceptions will always be caught and outputted to cerr. 23 | /// ```cpp 24 | /// { 25 | /// auto fd = ::open("test.txt"); 26 | /// auto fdGuard = nytl::ScopeGuard([=]{ ::close(fd); }}; 27 | /// 28 | /// if(condition()) return; 29 | /// functionThatMightThrow(); 30 | /// } 31 | /// ``` 32 | template 33 | class ScopeGuard { 34 | public: 35 | static_assert(OnSuccess || OnException); 36 | 37 | public: 38 | ScopeGuard(F&& func) : 39 | func_(std::forward(func)), 40 | exceptions_(std::uncaught_exceptions()) {} 41 | 42 | ScopeGuard(ScopeGuard&&) = delete; 43 | ScopeGuard& operator =(ScopeGuard&&) = delete; 44 | 45 | ~ScopeGuard() noexcept { 46 | if(exceptions_ == -1) { 47 | return; 48 | } 49 | 50 | try { 51 | auto thrown = exceptions_ < std::uncaught_exceptions(); 52 | if((OnSuccess && !thrown) || (OnException && thrown)) { 53 | func_(); 54 | } 55 | } catch(const std::exception& err) { 56 | std::cerr << "~nytl::ScopeGuard: exception while unwinding: "; 57 | std::cerr << err.what() << std::endl; 58 | } catch(...) { 59 | std::cerr << "~nytl::ScopeGuard: caught non-exception while "; 60 | std::cerr << "unwinding" << std::endl; 61 | } 62 | } 63 | 64 | void unset() { exceptions_ = -1; } 65 | 66 | protected: 67 | F func_; 68 | int exceptions_; 69 | }; 70 | 71 | /// Utility shortcuts to only execute a function when the scope is left 72 | /// with/without exceptions. See ScopeGuard for details. 73 | template 74 | class SuccessGuard : public ScopeGuard { 75 | public: 76 | using ScopeGuard::ScopeGuard; 77 | }; 78 | 79 | template 80 | class ExceptionGuard : public ScopeGuard { 81 | public: 82 | using ScopeGuard::ScopeGuard; 83 | }; 84 | 85 | 86 | // While the deduction guides are only needed for the derived classes, 87 | // they also make sure that ScopeGuard can be constructed with 88 | // an lvalue, which will result in storing the passed function by reference: 89 | // ```cpp 90 | // std::function f = []{ std::cout << "foo\n"; }; 91 | // ScopeGuard fooOrBar(f); // this captures by reference 92 | // f = []{ std::cout << "bar\n"; }; 93 | // // the ScopeGuard will output bar when leaving the scope 94 | // ``` 95 | template ScopeGuard(F&&) -> ScopeGuard; 96 | template SuccessGuard(F&&) -> SuccessGuard; 97 | template ExceptionGuard(F&&) -> ExceptionGuard; 98 | 99 | } // namespace nytl 100 | 101 | #endif // header guard 102 | -------------------------------------------------------------------------------- /nytl/simplex.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Includes the unspecialized nytl::Simplex template class. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_SIMPLEX 10 | #define NYTL_INCLUDE_SIMPLEX 11 | 12 | #include // nytl::Simplex default template parameter 13 | #include // nytl::Vec 14 | 15 | #include // std::enable_if 16 | #include // std::size_t 17 | #include // std::array 18 | 19 | namespace nytl { 20 | 21 | /// \brief Templated abstraction of a simplex (the generalization of a triangle). 22 | /// \details The Simplex template class defines an unique area with 'A' dimensions 23 | /// of 'P' precision in an 'D' dimensional space. 24 | /// So e.g. Simplex<3, float, 2> describes a Triangle in a 3-dimensional space. 25 | /// This template class does only work if 'D' >= 'A', since the dimension of the simplex 26 | /// can not be higher than the dimension of the space that contains this simplex. You cannot 27 | /// e.g. have a triangle in one dimensional space. 28 | /// The area described by a simplex is the most trivial space-filling unique and unambiguous 29 | /// geometrical figure in a space with the same dimensions as the simplex (e.g. a triangle 30 | /// in 2 dimensions). 31 | /// \requires 'P' must be a mathematical field over which the space is defined. 32 | /// \requires 'D' must be greater or equal than 'A'. 33 | /// \module simplex 34 | template 35 | class Simplex { 36 | public: 37 | static_assert(D >= A, 38 | "The Dimension of the Simplex cannot exceed the room dimension"); 39 | 40 | static constexpr auto spaceDim = D; // dimensions of the space the simplex is in 41 | static constexpr auto simplexDim = A; // dimensions of the simplex itself 42 | static constexpr auto pointCount = A + 1; // number of points the simplex is defined by 43 | 44 | using Precision = P; 45 | using Point = Vec; 46 | using PointArray = std::array; 47 | using Size = std::size_t; 48 | 49 | public: 50 | PointArray points_ {}; 51 | 52 | public: 53 | constexpr Simplex() noexcept = default; // use aggregate initialization 54 | ~Simplex() noexcept = default; 55 | 56 | /// Returns the array of points this simplex is defined by. 57 | /// References A + 1 points. 58 | /// Can be used to access (read/change/manipulate) the points. 59 | /// Should be implemented by specializations. 60 | constexpr Point* points() noexcept { return points_.data(); } 61 | 62 | /// Returns the array of points this simplex is defined by. 63 | /// References A + 1 points. 64 | /// Can be used to const_iterate/read the points. 65 | /// Should be implemented by specializations. 66 | constexpr const Point* points() const noexcept { return points_.data(); } 67 | 68 | /// Converts the object to a Simplex with a different dimension or precision. 69 | /// Note that the area dimension A cannot be changed, only the space dimension D. 70 | /// This means simply that e.g. a Triangle cannot be converted into a line, but a triangle 71 | /// in 3 dimensional space can be converted to a triangle in 2 dimensional space (by simply 72 | /// stripping the 3rd dimension). 73 | /// Works only if the new D is still greater equal A. 74 | /// Should be implemented by specializations. 75 | template 76 | constexpr explicit operator Simplex() const noexcept { 77 | Simplex ret {}; 78 | for(auto i = 0u; i < points_.size(); ++i) { 79 | ret[i] = static_cast>(points_[i]); 80 | } 81 | return ret; 82 | } 83 | }; 84 | 85 | } // namespace nytl 86 | 87 | #endif //header guard 88 | -------------------------------------------------------------------------------- /nytl/approx.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// Can be used for approximating floating point numbers. 6 | /// Example: 3.0 == approx(3.1, 0.2) will result in true. 7 | /// By default uses a small (like 10^-10) epsilon value. 8 | /// Can be easily extended to custom types with floating-point 9 | /// components. 10 | 11 | #pragma once 12 | 13 | #ifndef NYTL_INCLUDE_APPROX_APPROX 14 | #define NYTL_INCLUDE_APPROX_APPROX 15 | 16 | #include // std::complex 17 | #include // std::ostream 18 | #include // nytl::templatize 19 | 20 | namespace nytl { 21 | 22 | /// The default epsilon used if no custom epsilon is given. 23 | /// Note that Approx does not use this epsilon hardcoded but will mutiply 24 | /// it with the maximum of the compared values. 25 | constexpr auto defaultApproxEpsilon = 0.00000001; 26 | 27 | /// Represents an approximite value of type T. 28 | /// Usually type T is a floating-point value or something related to it, like 29 | /// a fp vector or matrix. 30 | template 31 | class Approx; 32 | 33 | /// Creates an Approx object for the given value and epsilon. 34 | template 35 | Approx approx(const T& value, double epsilon = defaultApproxEpsilon); 36 | 37 | /// Default approx implementation for floating point types. 38 | /// Most other specializations use this implementation for their comparisons. 39 | template 40 | class Approx { 41 | public: 42 | static_assert(std::is_floating_point_v, 43 | "nytl::Approx only works for floating point types"); 44 | 45 | friend bool operator==(T lhs, const Approx& rhs) { 46 | auto max = std::max(std::abs(lhs), std::abs(rhs.value)); 47 | return std::abs(lhs - rhs.value) < rhs.epsilon * (1 + max); 48 | } 49 | 50 | friend bool operator==(const Approx& lhs, double rhs) { 51 | return operator==(rhs, lhs); 52 | } 53 | friend bool operator!=(double lhs, const Approx& rhs) { 54 | return !operator==(lhs, rhs); 55 | } 56 | friend bool operator!=(const Approx& lhs, double rhs) { 57 | return !operator==(lhs, rhs); 58 | } 59 | 60 | public: 61 | T value {}; 62 | double epsilon {defaultApproxEpsilon}; 63 | }; 64 | 65 | /// Approx specialization for std::complex types. 66 | /// Will simply approximate the real and imaginary part separately. 67 | template 68 | class Approx> { 69 | public: 70 | template 71 | friend bool operator==(std::complex lhs, const Approx& rhs) { 72 | return lhs.real() == approx(rhs.value.real(), rhs.epsilon) 73 | && lhs.imag() == approx(rhs.value.imag(), rhs.epsilon); 74 | } 75 | 76 | template 77 | friend bool operator==(const Approx& lhs, std::complex rhs) { 78 | return operator==(rhs, lhs); 79 | } 80 | 81 | template 82 | friend bool operator!=(std::complex lhs, const Approx& rhs) { 83 | return !operator==(lhs, rhs); 84 | } 85 | 86 | template 87 | friend bool operator!=(const Approx& lhs, std::complex rhs) { 88 | return !operator==(lhs, rhs); 89 | } 90 | 91 | public: 92 | std::complex value {}; 93 | double epsilon {defaultApproxEpsilon}; 94 | }; 95 | 96 | template 97 | Approx approx(const T& value, double epsilon) { 98 | return {value, epsilon}; 99 | } 100 | 101 | /// Use this namespace to enable approx printing. 102 | namespace approxOps { 103 | 104 | template 105 | std::ostream& operator<<(std::ostream& os, const Approx& approx) { 106 | templatize(os) << "Approx(" << approx.value << ")"; 107 | return os; 108 | } 109 | 110 | } // namespace approxOps 111 | } // namespace nytl 112 | 113 | #endif // header guard 114 | -------------------------------------------------------------------------------- /nytl/vec.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file A Vector implementation class. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_VEC 10 | #define NYTL_INCLUDE_VEC 11 | 12 | #include // nytl::Vec typedefs 13 | 14 | // specializations 15 | #include // nytl::Vec<2, T> 16 | #include // nytl::Vec<3, T> 17 | 18 | #include // std::array 19 | #include // std::min 20 | 21 | namespace nytl { 22 | 23 | /// \brief Basic Vector template class. 24 | /// Basically a std::array with vector semantics. There are various operators 25 | /// and utility provided for dealing with it. 26 | /// \module vec 27 | template 28 | class Vec : public std::array { 29 | public: 30 | /// The (static/fixed) size of the type 31 | static constexpr size_t size() { return D; } 32 | 33 | /// Explicitly casts the Vec to another Vec that may have 34 | /// a different precision or dimension. Will default construct 35 | /// any values that cannot be filled (e.g. vec3 -> vec4) or leave 36 | /// out the last values when the size of vector is shrinked (e.g. 37 | /// {1, 2, 3} -> {1, 2}). 38 | template 39 | constexpr explicit operator Vec() const { 40 | auto ret = Vec {}; 41 | for(auto i = 0u; i < std::min(D, OD); ++i) 42 | ret[i] = (*this)[i]; 43 | return ret; 44 | } 45 | }; 46 | 47 | template 48 | Vec(Args&&... args) -> 49 | Vec>; 50 | 51 | // - implementation/operators - 52 | // - free operators - 53 | template 54 | constexpr Vec& operator+=(Vec& a, const Vec& b) { 55 | for(size_t i = 0; i < D; ++i) 56 | a[i] += b[i]; 57 | return a; 58 | } 59 | 60 | template 61 | constexpr Vec& operator-=(Vec& a, const Vec& b) { 62 | for(size_t i = 0; i < D; ++i) 63 | a[i] -= b[i]; 64 | return a; 65 | } 66 | 67 | template 68 | constexpr Vec& operator*=(Vec& vec, const F& fac) { 69 | for(auto i = 0u; i < D; ++i) 70 | vec[i] *= fac; 71 | return vec; 72 | } 73 | 74 | template 75 | constexpr auto operator+(const Vec& a, const Vec& b) { 76 | Vec ret {}; 77 | for(auto i = 0u; i < D; ++i) 78 | ret[i] = a[i] + b[i]; 79 | return ret; 80 | } 81 | 82 | template 83 | constexpr auto operator-(const Vec& a, const Vec& b) { 84 | Vec ret {}; 85 | for(auto i = 0u; i < D; ++i) 86 | ret[i] = a[i] - b[i]; 87 | return ret; 88 | } 89 | 90 | template 91 | constexpr auto operator-(Vec a) { 92 | for(auto i = 0u; i < D; ++i) 93 | a[i] = -a[i]; 94 | return a; 95 | } 96 | 97 | template 98 | constexpr auto operator+(const Vec& a) { 99 | return a; 100 | } 101 | 102 | template 103 | constexpr auto operator*(const F& f, const Vec& a) 104 | -> Vec { 105 | Vec ret {}; 106 | for(auto i = 0u; i < D; ++i) 107 | ret[i] = f * a[i]; 108 | return ret; 109 | } 110 | 111 | template 112 | constexpr auto operator==(const Vec& a, const Vec& b) { 113 | for(auto i = 0u; i < D; ++i) 114 | if(a[i] != b[i]) 115 | return false; 116 | return true; 117 | } 118 | 119 | template 120 | constexpr auto operator!=(const Vec& a, const Vec& b) { 121 | return !(a == b); 122 | } 123 | 124 | } // namespace nytl 125 | 126 | #endif // header guard 127 | -------------------------------------------------------------------------------- /nytl/flags.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Defines binary operators for enums as well as the nytl::Flags class. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_FLAGS 10 | #define NYTL_INCLUDE_FLAGS 11 | 12 | #include // nytl::Flags default template parameter 13 | 14 | namespace nytl { 15 | 16 | /// \brief Can be used to invert the given value on Flags construction 17 | /// Can be used like this: `nytl::Flags(nytl::invertFlags, Enum::value)`. 18 | /// \module utility 19 | struct InvertFlags {}; 20 | constexpr InvertFlags invertFlags {}; 21 | 22 | /// \brief Can be used to combine multiple values from the same enumeration. 23 | /// \details Use the [NYTL_FLAG_OPS]() macro to define binary operations on the 24 | /// enumeration that result in a nytl::Flags object for it. 25 | /// \requires Each value in the enumerations should have exactly one bit set and 26 | /// all values should have different bits set so they can be combined. 27 | /// \tparam T The enum type from which values should be combined. 28 | /// \tparam U The raw type to store the values in. By default the underlying type of 29 | /// the enum as reported by std::underlying_type 30 | /// \module utility 31 | template 32 | class Flags { 33 | public: 34 | constexpr Flags() noexcept = default; 35 | constexpr Flags(T bit) noexcept : value_(static_cast(bit)) {} 36 | constexpr Flags(InvertFlags, T bit) noexcept : value_(~static_cast(bit)) {} 37 | ~Flags() noexcept = default; 38 | 39 | constexpr Flags& operator|=(const Flags& r) noexcept { value_ |= r.value(); return *this; } 40 | constexpr Flags& operator&=(const Flags& r) noexcept { value_ &= r.value_; return *this; } 41 | constexpr Flags& operator^=(const Flags& r) noexcept { value_ ^= r.value(); return *this; } 42 | 43 | constexpr Flags operator|(const Flags& r) const noexcept { return Flags(r) |= *this; } 44 | constexpr Flags operator&(const Flags& r) const noexcept { return Flags(r) &= *this; } 45 | constexpr Flags operator^(const Flags& r) const noexcept { return Flags(r) ^= *this; } 46 | 47 | constexpr bool operator==(const Flags& rhs) const noexcept { return value_ == rhs.value(); } 48 | constexpr bool operator!=(const Flags& rhs) const noexcept { return value_ != rhs.value(); } 49 | 50 | constexpr const U& value() const noexcept { return value_; } 51 | constexpr operator U() const noexcept { return value_; } 52 | 53 | public: // public to make it a standard layout type 54 | U value_ {}; 55 | }; 56 | 57 | // - binary flags operators - 58 | template constexpr 59 | Flags operator|(T bit, const Flags& flags) noexcept 60 | { return flags | bit; } 61 | 62 | template constexpr 63 | Flags operator&(T bit, const Flags& flags) noexcept 64 | { return flags & bit; } 65 | 66 | template constexpr 67 | Flags operator^(T bit, const Flags& flags) noexcept 68 | { return flags ^ bit; } 69 | 70 | } // namespace nytl 71 | 72 | /// \brief Can be used for an enum to generate binary operations resulting in nytl::Flags. 73 | /// Can be used like this: `enum class Enum {}; NYTL_FLAG_OPS(Enum)` which will 74 | /// make results like `Enum::value1 | Enum::value2` automatically result in a 75 | /// `nytl::Flags` object holding the union of the given values. 76 | /// \note Inversion of flags or enum values will actually the underlaying value. 77 | /// Therefore equal comparisions with flags can be error prone and one should prefer to 78 | /// just check whether flags contain a specific value. The follwing static_assertion will fail: 79 | /// ```cpp 80 | /// enum class Enum { value1 = 1, value2 = 2 }; 81 | /// NYTL_FLAG_OPS(Enum) 82 | /// static_assert(~Enum::value1 == Enum::value2, "will fail due to nytl::Flags"); 83 | /// ``` 84 | /// \module utility 85 | #define NYTL_FLAG_OPS(T) \ 86 | constexpr nytl::Flags operator|(T a, T b) noexcept { return nytl::Flags(a) | b; } \ 87 | constexpr nytl::Flags operator&(T a, T b) noexcept { return nytl::Flags(a) & b; } \ 88 | constexpr nytl::Flags operator^(T a, T b) noexcept { return nytl::Flags(a) ^ b; } \ 89 | constexpr nytl::Flags operator~(T bit) noexcept { return {nytl::invertFlags, bit}; } 90 | 91 | #endif // header guard 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nytl 2 | 3 | A lightweight and generic header-only template library for C++17. 4 | Includes various utility of all kind that i needed across multiple projects: 5 | 6 | - Extremely lightweight [vector](nytl/vec.hpp) and [matrix](nytl/mat.hpp) templates 7 | - Basically just std::array with mathematical vector/matrix semantics 8 | - Additionally various useful operations ([mat](nytl/matOps.hpp) | [vec](nytl/vecOps.hpp)) 9 | - Simple utf conversion and utf8 parsing helpers: [nytl/utf.hpp](nytl/utf.hpp) 10 | - A [Callback](nytl/callback.hpp) implementation for high-level and fast function callbacks. 11 | - Also a more functional [RecursiveCallback](nytl/recursiveCallback.hpp) 12 | - Easily make virtual classes cloneable: [nytl/clone.hpp](nytl/clone.hpp) 13 | - Pseudo-RAII handling with scope guards: [nytl/scope.hpp](nytl/scope.hpp) 14 | - Lightweight and independent span template: [nytl/span.hpp](nytl/span.hpp) 15 | - Combining c++ class enums into flags: [nytl/flags.hpp](nytl/flags.hpp) 16 | 17 | All headers were written as modular, independent and generic as possible. Most 18 | utilities can be used indenpendently from each other. The only required 19 | dependency is a compiler supporting full C++17 and its stl (this means no suppport 20 | for msvc at the moment). 21 | All files are licensed under the Boost License. 22 | 23 | ## Contributing 24 | 25 | Contributions to library, tests, documentation, examples as well as 26 | all suggestions and ideas are always appreciated. 27 | Just start a pull request or an issue on [github](https://github.com/nyorain/nytl). 28 | 29 | ## Using nytl 30 | 31 | There are multiple ways to use nytl. Either install all of its headers on your system and make 32 | sure the install path can be found by the compiler. 33 | If you need just a few header files (or even just a few functions), just copy those files into 34 | your project folder. 35 | It can also be used as meson subproject. 36 | 37 | Remember that nytl requires a solid C++17 compiler, only recent versions of gcc and clang 38 | are tested. 39 | Below some basic code examples for (only a) few nytl features to give you an idea. 40 | 41 | ### nytl::Callback and nytl::RecursiveCallback 42 | 43 | Callbacks mirror the signal/slot principle in modern c++ with many useful features. 44 | For the full documentation, see [nytl/callback.hpp](nytl/callback.hpp). 45 | If you want to modify (call/register/disconnect) the callback from 46 | within a handler, see [nytl/recursiveCallback.hpp](nytl/recursiveCallback.hpp). 47 | 48 | ```cpp 49 | // Example callback 50 | auto onEvent = nytl::RecursiveCallback {}; 51 | 52 | // Adds a callback listener 53 | auto connection = onEvent.add([]{ std::cout << "called\n"; }); 54 | connection.disconnect(); // unregisters the listener 55 | 56 | onEvent = &foo; // sets foo as only listener 57 | onEvent += []{}; // same as onEvent.add 58 | 59 | // listener functions can also take an additional Connection argument that 60 | // allows them to unregister themself from within the listener 61 | onEvent += [&](nytl::Connection selfConnection) { 62 | selfConnection.disconnect(); // will unregister itself on first call 63 | onEvent += &bar; // one can also add new listeners from inside a listener 64 | }; 65 | 66 | onEvent(); // calls all registered listener functions 67 | onEvent.call(); // can also be done more explicit 68 | ``` 69 | 70 | ### nytl::ScopeGuard 71 | 72 | ScopeGuards are another utility concept implemented by nytl. The mirror finally syntax from 73 | other languages and allow safe RAII-like handling of non-RAII resources. 74 | For the full documentation, see [nytl/scope.hpp](nytl/scope.hpp). 75 | 76 | ```cpp 77 | // open a file descriptor we want to close later on 78 | auto fd = ::open("test.txt"); 79 | 80 | // create a scope guard that will execute the passed functions as soon 81 | // as this scope is left, no matter in which way. Makes closing the fd 82 | // exception safe and also more maintainable since early returns can be 83 | // added without having to care about the fd. 84 | auto fdGuard = nytl::ScopeGuard([&]{ ::close(fd); }) 85 | 86 | // there are also classes that only execute the passed functions if the 87 | // scope was left normally or due to an exception 88 | auto successGuard = nytl::SuccessGuard([&]{ std::cout << "scope left normally\n"; }); 89 | auto exceptionGuard = nytl::ExceptionGuard([&]{ std::cout << "exception thrown\n"; }); 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/tests/clone.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | 4 | // Abstract 5 | struct CloneBase : public nytl::AbstractCloneable { 6 | virtual int value() const = 0; 7 | }; 8 | 9 | struct CloneDerived : public nytl::DeriveCloneable { 10 | int value_; 11 | int value() const override { return value_; } 12 | }; 13 | 14 | 15 | TEST(clone_abstract) { 16 | auto derived = CloneDerived {}; 17 | derived.value_ = 42; 18 | 19 | auto ptr = static_cast(&derived); 20 | 21 | auto copy = nytl::clone(*ptr); 22 | auto moved = nytl::cloneMove(*ptr); 23 | 24 | EXPECT(copy->value(), 42); 25 | EXPECT(moved->value(), 42); 26 | } 27 | 28 | // Non-Abstract 29 | struct CloneBase2 : public nytl::Cloneable {}; 30 | struct CloneDerived2 : public nytl::DeriveCloneable { 31 | int value2_; 32 | }; 33 | 34 | struct CloneDerived3 : public nytl::DeriveCloneable { 35 | int value3_; 36 | }; 37 | 38 | struct CloneDerived4 : public nytl::DeriveCloneable { 39 | int value_; 40 | int value() const override { return value_; } 41 | }; 42 | 43 | TEST(clone2) { 44 | // #1 45 | auto derived2 = CloneDerived2 {}; 46 | derived2.value2_ = 11; 47 | auto& ref2 = static_cast(derived2); 48 | 49 | auto copy2 = nytl::clone(ref2); 50 | auto casted2 = dynamic_cast(copy2.get()); 51 | EXPECT(casted2 == nullptr, false); 52 | EXPECT(casted2->value2_, 11); 53 | 54 | // #2 55 | auto derived3 = CloneDerived3 {}; 56 | derived3.value2_ = 21; 57 | derived3.value3_ = 22; 58 | auto& ref3 = static_cast(derived3); 59 | 60 | auto copy3 = nytl::clone(ref3); 61 | auto casted3 = dynamic_cast(copy3.get()); 62 | EXPECT(casted3 == nullptr, false); 63 | EXPECT(casted3->value2_, 21); 64 | EXPECT(casted3->value3_, 22); 65 | 66 | // #3 67 | auto o4 = CloneDerived4 {}; 68 | o4.value_ = 23; 69 | auto o5c = nytl::clone(o4); 70 | EXPECT(o5c->value_, o4.value_); 71 | } 72 | 73 | // constructors 74 | struct Base2 : public nytl::Cloneable { 75 | Base2(int o) : val1(o) {} 76 | int val1; 77 | }; 78 | 79 | struct Base3 : public nytl::AbstractCloneable { 80 | Base3(float o) : val2(o) {} 81 | virtual void dosth() = 0; 82 | float val2; 83 | }; 84 | 85 | // just using one base class has pros and cons: 86 | // - pro: we can use their constructors (through nytl::DeriveCloneable) 87 | // - con: we have to manually select the doClone/doCloneMove functions 88 | // - it doesn't matter which one we choose, but we have to also add 89 | // the friend declaration. 90 | // Basically don't do it this way if you don't really need 91 | // the base class constructors. You could also just manually override 92 | // doClone() and doCloneMove() the same way the nytl helper classes do in 93 | // this case. 94 | struct Derived1 : 95 | public nytl::DeriveCloneable, 96 | public nytl::DeriveCloneable { 97 | Derived1(int a, float b) : 98 | nytl::DeriveCloneable(a), 99 | nytl::DeriveCloneable(b) {} 100 | 101 | void dosth() override {}; 102 | 103 | private: 104 | using nytl::DeriveCloneable::doClone; 105 | using nytl::DeriveCloneable::doCloneMove; 106 | 107 | template friend std::unique_ptr nytl::cloneMove(O&); 108 | template friend std::unique_ptr nytl::clone(const O&); 109 | }; 110 | 111 | TEST(clone3) { 112 | Derived1 a(1, 2.f); 113 | EXPECT(a.val1, 1); 114 | EXPECT(a.val2, 2.f); 115 | 116 | auto b = nytl::clone(a); 117 | EXPECT(b->val1, 1); 118 | EXPECT(b->val2, 2.f); 119 | } 120 | 121 | // test that cloneMove really moves 122 | struct Base4 : public nytl::AbstractCloneMovable { 123 | virtual const std::vector& get() const = 0; 124 | }; 125 | 126 | struct Derived4 : public nytl::DeriveCloneMovable { 127 | std::vector vals {}; 128 | const std::vector& get() const override { return vals; } 129 | }; 130 | 131 | TEST(clone4) { 132 | Derived4 a {}; 133 | a.vals = {1, 2, 3, 4, 5}; 134 | EXPECT(a.vals.size(), 5u); 135 | 136 | auto b = nytl::cloneMove(a); 137 | EXPECT(b->get().size(), 5u); 138 | EXPECT(a.vals.empty(), true); 139 | } -------------------------------------------------------------------------------- /docs/tests/mat.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | TEST(basic) { 19 | auto r1 = nytl::Vec<1, double>{2.0}; 20 | auto r2 = nytl::Vec<1, double>{1.0}; 21 | 22 | nytl::Mat<2, 1, double> x {{r1, r2}}; 23 | nytl::Mat<1, 3, double> y {1.0, 2.0, 3.0}; 24 | nytl::Mat<4, 1, double> z {-1.0, 0.0, 1.0, -2.0}; 25 | nytl::Mat<2, 3, double> a {{nytl::Vec3d{1.0, 2.0, -1.0}, nytl::Vec3d{0.0, 2.0, 1.0}}}; 26 | nytl::Mat<4, 2, double> b {{nytl::Vec2d{1.0, 1.0}, {0.0, 2.0}, {3.0, -1.0}, {-1.0, 2.0}}}; 27 | 28 | nytl::Mat<4, 1, double> r2bx {6.0, 4.0, 10.0, 0.0}; 29 | nytl::Mat<4, 3, double> rba {{ 30 | nytl::Vec3d{1.0, 4.0, 0.0}, 31 | {0.0, 4.0, 2.0}, 32 | {3.0, 4.0, -4.0}, 33 | {-1.0, 2.0, 3.0} 34 | }}; 35 | nytl::Mat<4, 4, double> rzzt { 36 | 1.0, 0.0, -1.0, 2.0, 37 | 0.0, 0.0, 0.0, 0.0, 38 | -1.0, 0.0, 1.0, -2.0, 39 | 2.0, 0.0, -2.0, 4.0 40 | }; 41 | nytl::Mat<2, 1, double> rayt {2, 7}; 42 | nytl::Mat<4, 3, double> rzy { 43 | -1.0, -2.0, -3.0, 44 | 0.0, 0.0, 0.0, 45 | 1.0, 2.0, 3.0, 46 | -2.0, -4.0, -6.0 47 | }; 48 | 49 | EXPECT(2 * (b * x), nytl::approx(r2bx)); 50 | EXPECT(b * a, nytl::approx(rba)); 51 | EXPECT(z * nytl::transpose(z), nytl::approx(rzzt)); 52 | EXPECT(a * nytl::transpose(y), nytl::approx(rayt)); 53 | EXPECT(z * y, nytl::approx(rzy)); 54 | 55 | // - should not compile - 56 | // a * r1; 57 | // y * a; 58 | // a + nytl::mat::transpose(b); 59 | // y * y; 60 | // 3 - nytl::mat::transpose(x) * x; 61 | } 62 | 63 | TEST(echolon) { 64 | nytl::Mat<3, 5, double> a { 65 | 2.0, 1.0, -1.0, 8.0, 80.0, 66 | -3.0, -1.0, 2.0, -11.0, -110.0, 67 | -2.0, 1.0, 2.0, -3.0, -30.0 68 | }; 69 | 70 | nytl::Mat<3, 5, double> reduced { 71 | 1.0, 0.0, 0.0, 2.0, 20.0, 72 | 0.0, 1.0, 0.0, 3.0, 30.0, 73 | 0.0, 0.0, 1.0, -1.0, -10.0 74 | }; 75 | 76 | nytl::reducedRowEcholon(a); 77 | EXPECT(a, nytl::approx(reduced)); 78 | } 79 | 80 | // tests the lu decomposition operations 81 | TEST(lu_decomp_1) { 82 | nytl::Mat<3, 3, double> a { 83 | 2.0, 2.0, 3.0, 84 | 1.0, 1.0, -1.0, 85 | 1.0, 0.0, 2.0, 86 | }; 87 | 88 | auto lups = nytl::luDecomp(a); 89 | const auto& l = lups.lower; 90 | const auto& u = lups.upper; 91 | const auto& p = lups.perm; 92 | 93 | EXPECT(l * u, nytl::approx(p * a)); 94 | 95 | auto lups2 = nytl::luDecomp(p * a); 96 | const auto& l2 = lups2.lower; 97 | const auto& u2 = lups2.upper; 98 | const auto& p2 = lups2.perm; 99 | 100 | EXPECT(l2 * u2, nytl::approx(p * a)); 101 | EXPECT(p2, (nytl::identity<3, double>())); 102 | } 103 | 104 | TEST(lu_decomp_2) { 105 | nytl::Mat<3, 3, double> a { 106 | 3.0, -.1, -.2, 107 | 0.1, 7, -.3, 108 | .3, -.2, 10 109 | }; 110 | 111 | nytl::Vec<3, double> b {7.85, -19.3, 71.4}; 112 | 113 | auto lups = nytl::luDecomp(a); 114 | const auto& l = lups.lower; 115 | const auto& u = lups.upper; 116 | const auto& p = lups.perm; 117 | 118 | EXPECT(l * u, nytl::approx(p * a)); 119 | 120 | auto x2 = nytl::luEvaluate(l, u, b); 121 | EXPECT(a * x2, nytl::approx(p * b)); 122 | 123 | // the manually (right) computed solution 124 | nytl::Vec<3, double> x {3.0, -2.5, 7.0}; 125 | EXPECT(a * x, nytl::approx(p * b)); 126 | } 127 | 128 | // tests the inverse and determinant operations 129 | TEST(inverse) { 130 | { 131 | nytl::Mat<5, 5, double> a { 132 | 1, -2, 3, 5, 8, 133 | 0, -1, -1, 2, 3, 134 | 2, 4, -1, 3, 1, 135 | 0, 0, 5, 0, 0, 136 | 1, 3, 0, 4, -1 137 | }; 138 | 139 | auto lups = nytl::luDecomp(a); 140 | const auto& l = lups.lower; 141 | const auto& u = lups.upper; 142 | const auto& p = lups.perm; 143 | EXPECT(l * u, nytl::approx(p * a)); 144 | 145 | EXPECT(nytl::determinant(a), nytl::approx(-135.0)); 146 | EXPECT(nytl::invertible(a), true); 147 | 148 | auto inv = nytl::inverse(a); 149 | auto inv1 = nytl::inverse(lups); 150 | EXPECT(inv, nytl::approx(inv1)); 151 | 152 | auto iinv = nytl::inverse(inv); 153 | EXPECT(a, nytl::approx(iinv)); 154 | 155 | auto id = nytl::identity<5, double>(); 156 | EXPECT(a * inv, nytl::approx(id)); 157 | EXPECT(inv * a, nytl::approx(id)); 158 | } 159 | 160 | { 161 | nytl::Mat<5, 5, double> a {{ 162 | nytl::Vec<5, double>{1, -2, 3, 5, 8}, 163 | {0, -1, -1, 0, 3}, 164 | {2, 4, -1, 10, 1}, 165 | {0, 0, 5, 0, 0}, 166 | {1, 3, 0, 5, -1} 167 | }}; 168 | 169 | auto lups = nytl::luDecomp(a); 170 | const auto& l = lups.lower; 171 | const auto& u = lups.upper; 172 | const auto& p = lups.perm; 173 | EXPECT(l * u, nytl::approx(p * a)); 174 | 175 | EXPECT(nytl::determinant(a), nytl::approx(0.0)); 176 | EXPECT(nytl::invertible(a), false); 177 | 178 | // undefined behavior now 179 | // ERROR(nytl::inverse(a), std::invalid_argument); 180 | // ERROR(nytl::inverse(lups), std::invalid_argument); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /nytl/tmpUtil.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Various useful template helpers without dependencies. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_TMP_UTIL 10 | #define NYTL_INCLUDE_TMP_UTIL 11 | 12 | #include // std::forward 13 | 14 | namespace nytl { 15 | namespace detail { 16 | template using void_t = void; // to not include type_traits 17 | 18 | template 19 | struct LastTypeT { 20 | using type = typename LastTypeT::type; 21 | }; 22 | 23 | template 24 | struct LastTypeT { 25 | using type = First; 26 | }; 27 | } // namespace detail 28 | 29 | /// Useful typedef for the last type in the template parameter list. 30 | /// Can be used to trigger SFINAE on the first types, i.e. just pass 31 | /// types to check whether they are valid. 32 | template using LastType = typename detail::LastTypeT::type; 33 | 34 | /// \brief Useful typedef for expanding the use of variadic template arguments. 35 | /// In C++17, most of the Expand shortcuts can be done with fold expressions. 36 | /// ```cpp 37 | /// // Prints the variadic template pack args to std::cout. 38 | /// (void) nytl::Expand {((void) std::cout << args , 0), ...}; 39 | /// ``` 40 | /// \module utility 41 | using Expand = int[]; 42 | 43 | /// \brief Class that can be derived from to check if given template parameters are valid. 44 | /// \details Really useful for template classes that use SFINAE. 45 | /// ```cpp 46 | /// class D : public nytl::DeriveDummy> {}; 47 | /// ``` 48 | /// \module utility 49 | template struct DeriveDummy {}; 50 | 51 | /// Utility template function that can be used to hide unused compiler warnings. 52 | /// Has usually no additional cost. Is meant as placeholder for future code. 53 | /// ```cpp 54 | /// void foo(int a, int b, int c) { nytl::unused(a, b, c); } // to be implemented later on 55 | /// ``` 56 | /// \module utility 57 | template void unused(T&&...) {} 58 | 59 | /// \brief Can be used to assure that variadic arguments have the same type. 60 | /// ```cpp 61 | /// // implementation function that takes a variable number of integers 62 | /// template 63 | /// void funcImpl(nytl::VariadicDummy... ints); 64 | /// 65 | /// // public interface function that is called 66 | /// // results in compile time error if not all arguments are convertible to integers 67 | /// template 68 | /// void func(Args... args) { funcImpl(args...); } 69 | /// ``` 70 | /// \module utility 71 | template using Variadic = A; 72 | 73 | /// \brief Assures that the returned value can be used as template-dependent expressions. 74 | /// This allows e.g. to defer operator of function lookup so that specific headers don't 75 | /// have to be included by a template functions if a function is not used. 76 | /// \module utility 77 | template 78 | decltype(auto) constexpr templatize(T&& value) { return std::forward(value); } 79 | 80 | 81 | namespace detail { 82 | template typename E, typename C, typename... T> 83 | struct ValidExpressionT; 84 | } // namespace detail 85 | 86 | /// \brief Can be used to determine whether a given expression (a templated type) is valid. 87 | /// \tparam E The expression that should be tested. Usually a templated 'using' typedef. 88 | /// \tparam T The types for which the expression should be tested. 89 | /// Note that the size of the T parameter pack should match the template parameters required by the 90 | /// given expression. 91 | /// Really useful when the templated typedef 'E' is/contains a decltype declaration since 92 | /// then SFINAE can be used to detect that the expression is invalid. 93 | /// Can be used with C++17 constexpr if for way easier templated branches. 94 | /// Example: 95 | /// ```cpp 96 | /// // Some declarations for callables (e.g. functions) foo and bar here 97 | /// 98 | /// template using FooCallbable = decltype(foo(std::declval())); 99 | /// template using BarCallbable = decltype(bar(std::declval())); 100 | /// 101 | /// template auto dispatch(const T& obj) 102 | /// { 103 | /// if constexpr(nytl::validExpression>) return foo(obj); 104 | /// else if constexpr(nytl::validExpression>) return bar(obj); 105 | /// else return fallback(obj); // otherwise templated static_assert to generate error 106 | /// } 107 | /// ``` 108 | template typename E, typename... T> 109 | constexpr auto validExpression = detail::ValidExpressionT::value; 110 | 111 | // ValidExpression impl 112 | namespace detail { 113 | template typename E, typename C, typename... T> struct ValidExpressionT { 114 | static constexpr auto value = false; 115 | }; 116 | 117 | template typename E, typename... T> 118 | struct ValidExpressionT>, T...> { 119 | static constexpr auto value = true; 120 | }; 121 | 122 | } // namespace detail 123 | } // namespace nytl 124 | 125 | #endif //header guard 126 | -------------------------------------------------------------------------------- /nytl/functionTraits.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Defines utility templates to get information about callable types. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_FUNCTION_TRAITS 10 | #define NYTL_INCLUDE_FUNCTION_TRAITS 11 | 12 | #include // std::tuple 13 | #include // std::false_type 14 | 15 | namespace nytl { 16 | namespace detail { 17 | template struct IsCallableImpl; 18 | template struct FunctionTraitsBase; 19 | } // namespace detail 20 | 21 | /// Meta-programming template to check if a type can be called. 22 | /// \module function 23 | template 24 | constexpr bool isCallable = detail::IsCallableImpl::value; 25 | 26 | /// Meta-programming template to retrieve information about a function type. 27 | /// \module function 28 | template struct FunctionTraits; 29 | 30 | /// Default FunctionTraits specializations for a raw signature. 31 | /// All other (valid) FunctionTraits will inherit from this type. 32 | template 33 | struct FunctionTraits : 34 | public detail::FunctionTraitsBase { 35 | }; 36 | 37 | template 38 | struct FunctionTraits : 39 | public detail::FunctionTraitsBase { 40 | }; 41 | 42 | // - implementation - 43 | // Function pointer 44 | template 45 | struct FunctionTraits : public FunctionTraits {}; 46 | 47 | // Member function pointer 48 | template 49 | struct FunctionTraits : public FunctionTraits { 50 | using Class = C; 51 | }; 52 | 53 | // Const member function pointer 54 | template 55 | struct FunctionTraits : public FunctionTraits { 56 | using Class = const C; 57 | }; 58 | 59 | // functor, class 60 | template 61 | struct FunctionTraits : public FunctionTraits {}; 62 | 63 | // various additional qualifiers 64 | template 65 | struct FunctionTraits : public FunctionTraits {}; 66 | 67 | template 68 | struct FunctionTraits : public FunctionTraits {}; 69 | 70 | 71 | namespace detail { 72 | 73 | // default implementation for not callable non-class types such as int 74 | template 75 | struct IsCallableImpl : public std::false_type {}; 76 | 77 | // specialization for int. Contains SFINAE check 78 | template 79 | struct IsCallableImpl::value>::type> { 80 | template static decltype(&U::operator(), void(), std::true_type()) test(int); 81 | template static std::false_type test(...); 82 | static constexpr auto value = decltype(test(0))::value; 83 | }; 84 | 85 | // member function pointer 86 | template 87 | struct IsCallableImpl : public std::true_type {}; 88 | 89 | // const member function pointer 90 | template 91 | struct IsCallableImpl : public std::true_type {}; 92 | 93 | // function pointer 94 | template 95 | struct IsCallableImpl : public std::true_type {}; 96 | 97 | // function reference 98 | template 99 | struct IsCallableImpl : public std::true_type {}; 100 | 101 | // raw signature 102 | // treated as callable 103 | template 104 | struct IsCallableImpl : public std::true_type {}; 105 | 106 | // FunctionTraitsBase 107 | template 108 | struct FunctionTraitsBase { 109 | /// A tuple containing all arguments the function takes with all its qualifiers. 110 | using ArgTuple = std::tuple; 111 | 112 | /// The type the function returns 113 | using ReturnType = Ret; 114 | 115 | /// The whole signature of the function. 116 | /// For e.g. `int foo(const int&, std::string)` this would be 117 | /// `int(const int&, std::string) const` 118 | using Signature = std::conditional_t; 119 | 120 | /// The number of arguments this function takes 121 | constexpr static auto NumArgs = std::tuple_size::value; 122 | 123 | /// Whether the signature is const, i.e. the function const-callable. 124 | constexpr static auto IsConst = ConstSig; 125 | 126 | /// Can be used to retrieve the ith function parameter type. 127 | /// Example: `typename nytl::FunctionTraits::template ArgType<1>` is an 128 | /// alias for `const void*` since this is the second std::memcpy parameter 129 | /// \note Indexing of parameters starts (as usual) with 0, so ArgType<1> refers to the 130 | /// second argument. 131 | template 132 | using ArgType = typename std::tuple_element_t; 133 | }; 134 | 135 | } // namespace detail 136 | } // namespace nytl 137 | 138 | #endif // header guard 139 | -------------------------------------------------------------------------------- /nytl/utf.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Small helper functions for using and converting utf-8 encoded strings. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_UTF 10 | #define NYTL_INCLUDE_UTF 11 | 12 | #include // std::string 13 | #include // std::string_view 14 | #include // std::array 15 | #include // std::wstring_convert 16 | #include // std::codecvt_utf8 17 | #include // std::out_of_range 18 | 19 | // all operations assume correct utf8 string and don't perform any sanity checks. 20 | // for implementation details see https://en.wikipedia.org/wiki/UTF-8 21 | 22 | namespace nytl { 23 | 24 | /// \brief Returns the number of characters in a utf8-encoded unicode string. 25 | /// This differs from std::string::size because it does not return the bytes in the 26 | /// string, but the count of utf8-encoded characters. 27 | inline std::size_t charCount(std::string_view utf8) { 28 | std::size_t count = 0u; 29 | auto it = utf8.begin(); 30 | 31 | while(it != utf8.end()) { 32 | auto length = 1u; 33 | if((*it) & (1 << 7)) { 34 | ++length; 35 | if((*it) & (1 << 5)) { 36 | ++length; 37 | if((*it) & (1 << 4)) 38 | ++length; 39 | } 40 | } 41 | 42 | ++count; 43 | it += length; 44 | } 45 | 46 | return count; 47 | } 48 | 49 | /// \brief Returns the character at position n (started at 0) from the given utf8 string. 50 | /// \details In difference to the builtin std::string access function this does not return 51 | /// the nth byte, but the nth utf8 character. 52 | /// Since every (unicode) utf8 character can take up to 4 bytes, the last char in the 53 | /// returned array will always be '\0', which allows to treat its data as null-terminated 54 | /// const char pointer. 55 | /// Example: `nytl::nth(u8"äüß", 1)` returns the char "ü" as utf8 array, 56 | /// i.e. {0xc3, 0xbc ,0, 0} since u8"ü"[0] == 0xc3 and u8"ü"[1] == 0xbc. 57 | /// \note Indexing starts at zero! `nth(utf8, 1)` returns actually the second char. 58 | /// \throws std::out_of_range if n > charCount(utf8) 59 | inline std::array nth(std::string_view utf8, std::size_t n) { 60 | std::size_t count = 0u; 61 | auto it = utf8.begin(); 62 | 63 | while(it != utf8.end()) { 64 | auto length = 1u; 65 | if((*it) & (1 << 7)) { 66 | ++length; 67 | if((*it) & (1 << 5)) { 68 | ++length; 69 | if((*it) & (1 << 4)) 70 | ++length; 71 | } 72 | } 73 | 74 | if(count == n) { 75 | std::array ret {}; 76 | for(auto i = 0u; i < length; ++i) ret[i] = *(it + i); 77 | return ret; 78 | } 79 | 80 | ++count; 81 | it += length; 82 | } 83 | 84 | throw std::out_of_range("ny::nth(utf8, n)"); 85 | } 86 | 87 | /// \brief Retrieves the character at position n (started at 0) of the given utf8 string. 88 | /// \returns A reference to the character at position n which can together with the 89 | /// retrieved byte size of the requested character be used to read it. 90 | /// \param size [out] Will hold the number of bytes of the requested character. 91 | /// Will be not greater than 4 for a valid utf8 string. 92 | /// \note Indexing starts at zero! `nth(utf8, 1)` returns actually the second char. 93 | /// \throws std::out_of_range if n > charCount(utf8) 94 | inline const char& nth(std::string_view utf8, std::size_t n, std::uint8_t& size) { 95 | auto count = 0u; 96 | auto it = utf8.begin(); 97 | 98 | while(it != utf8.end()) { 99 | auto length = 1u; 100 | if((*it) & (1 << 7)) { 101 | ++length; 102 | if((*it) & (1 << 5)) { 103 | ++length; 104 | if((*it) & (1 << 4)) 105 | ++length; 106 | } 107 | } 108 | 109 | if(count == n) { 110 | size = length; 111 | return *it; 112 | } 113 | 114 | it += length; 115 | ++count; 116 | } 117 | 118 | size = 0u; 119 | throw std::out_of_range("ny::nth(const utf8, n, size)"); 120 | } 121 | 122 | /// \brief Retrieves the character at position n (started at 0) of the given utf8 string. 123 | /// \returns A reference to the character at position n which can together with the 124 | /// retrieved byte size of the requested character be used to read it. 125 | /// \param size [out] Will hold the number of bytes of the requested character. 126 | /// Will be not greater than 4. 127 | /// \note Indexing starts at zero! `nth(utf8, 1)` returns actually the second char. 128 | /// \throws std::out_of_range if n > charCount(utf8) 129 | inline char& nth(std::string& utf8, std::size_t n, std::uint8_t& size) { 130 | auto count = 0u; 131 | auto it = utf8.begin(); 132 | 133 | while(it != utf8.end()) { 134 | auto length = 1u; 135 | if((*it) & (1 << 7)) { 136 | ++length; 137 | if((*it) & (1 << 5)) { 138 | ++length; 139 | if((*it) & (1 << 4)) 140 | ++length; 141 | } 142 | } 143 | 144 | if(count == n) { 145 | size = length; 146 | return *it; 147 | } 148 | 149 | it += length; 150 | ++count; 151 | } 152 | 153 | size = 0u; 154 | throw std::out_of_range("nytl::nth(utf8, n, size)"); 155 | } 156 | 157 | /// \brief Converts the given utf16 string to a utf8 string. 158 | inline std::string toUtf8(std::u16string_view utf16) { 159 | std::wstring_convert, char16_t> converter; 160 | return converter.to_bytes(utf16.begin(), utf16.end()); 161 | } 162 | 163 | /// \brief Converts the given utf32 string to a utf8 string. 164 | inline std::string toUtf8(std::u32string_view utf32) { 165 | std::wstring_convert, char32_t> converter; 166 | return converter.to_bytes(utf32.begin(), utf32.end()); 167 | } 168 | 169 | /// \brief Converts the given utf8 string to a utf16 string. 170 | inline std::u16string toUtf16(std::string_view utf8) { 171 | std::wstring_convert, char16_t> converter; 172 | return converter.from_bytes(utf8.begin(), utf8.end()); 173 | } 174 | 175 | /// \brief Converts the given utf32 string to a utf16 string. 176 | inline std::u16string toUtf16(std::u32string_view utf32) { 177 | return toUtf16(toUtf8(utf32)); 178 | } 179 | 180 | /// \brief Converts the given utf8 string to a utf32 string. 181 | inline std::u32string toUtf32(std::string_view utf8) { 182 | std::wstring_convert, char32_t> converter; 183 | return converter.from_bytes(utf8.begin(), utf8.end()); 184 | } 185 | 186 | /// \brief Converts the given utf16 string to a utf32 string. 187 | inline std::u32string toUtf32(std::u16string_view utf16) { 188 | return toUtf32(toUtf8(utf16)); 189 | } 190 | 191 | } // namespace nytl 192 | 193 | #endif // header guard 194 | -------------------------------------------------------------------------------- /nytl/mat.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// Defines the nytl::Mat Matrix template class. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_MAT 10 | #define NYTL_INCLUDE_MAT 11 | 12 | #include // nytl::Mat forward declaration 13 | #include // nytl::Vec 14 | #include // nytl::dot 15 | 16 | namespace nytl { 17 | 18 | /// A Matrix with 'R' rows and 'C' columns over type 'T' on the stack. 19 | /// Data is stored (and accessed) with row-major semantics. 20 | /// - T: The value type of the matrix. 21 | /// - R: The rows of the matrix. 22 | /// - C: The columns of the matrix. 23 | template 24 | struct Mat { 25 | /// The (static/fixed) dimensions of the matrix 26 | static constexpr auto rows() { return R; } 27 | static constexpr auto cols() { return C; } 28 | 29 | public: 30 | /// Returns the ith row of the matrix. 31 | /// Since this is of type nytl::Vec, mat[i][j] works. 32 | constexpr auto& operator[](size_t i){ return rows_[i]; } 33 | constexpr const auto& operator[](size_t i) const { return rows_[i]; } 34 | 35 | /// Returns the ith row of the matrix. 36 | /// If these exceeds the size of the matrix, throws std::out_of_range. 37 | constexpr auto& at(size_t r) { checkRow(r); return rows_[r]; } 38 | constexpr const auto& at(size_t r) const { checkRow(r); return rows_[r]; } 39 | 40 | /// Returns the value at position (r, c). 41 | /// If this position exceeds the size of the matrix, throws std::out_of_range. 42 | constexpr auto& at(size_t r, size_t c) { return at(r).at(c); } 43 | constexpr const auto& at(size_t r, size_t c) const { return at(r).at(c); } 44 | 45 | /// Explicitly casts the Mat to another Mat that may have 46 | /// a different precision or dimensions. Will default construct 47 | /// any values that cannot be filled (e.g. mat33 -> mat44) or leave 48 | /// out the last values when the size of vector is shrinked (e.g. 49 | /// {11, 12, 13, 21, 22, 23} -> {11, 12, 21, 22}). 50 | template 51 | constexpr explicit operator Mat() const; 52 | 53 | /// Utility function that throws std::out_of_range if the matrix does not have 54 | /// the given row. 55 | constexpr void checkRow(size_t r) { 56 | if(r >= rows()) 57 | throw std::out_of_range("nytl::Mat::at"); 58 | } 59 | 60 | public: 61 | std::array, R> rows_; 62 | }; 63 | 64 | // - implementation/operators - 65 | template 66 | template 67 | constexpr Mat::operator Mat() const { 68 | Mat ret {}; 69 | for(auto r = 0u; r < std::min(R, OR); ++r) 70 | for(auto c = 0u; c < std::min(C, OC); ++c) 71 | ret[r][c] = (*this)[r][c]; 72 | return ret; 73 | } 74 | 75 | // mat * mat 76 | template 77 | constexpr auto operator*(const Mat& a, const Mat& b) { 78 | Mat ret {}; 79 | for(auto r = 0u; r < R; ++r) // ret: rows 80 | for(auto c = 0u; c < C; ++c) // ret: cols 81 | for(auto i = 0u; i < M; ++i) // row + col dot 82 | ret[r][c] += a[r][i] * b[i][c]; 83 | return ret; 84 | } 85 | 86 | // mat * vec 87 | template 88 | constexpr auto operator*(const Mat& a, const Vec& b) { 89 | Vec ret {}; 90 | for(auto r = 0u; r < R; ++r) 91 | ret[r] = dot(a[r], b); 92 | return ret; 93 | } 94 | 95 | // mat *= mat (quadratic) 96 | template 97 | constexpr auto& operator*=(Mat& a, const Mat& b) { 98 | auto tmp = a; // needed since we write to a 99 | a = {}; 100 | for(auto r = 0u; r < D; ++r) // ret: rows 101 | for(auto c = 0u; c < D; ++c) // ret: cols 102 | for(auto i = 0u; i < D; ++i) // row + col dot 103 | a[r][c] += tmp[r][i] * b[i][c]; 104 | return a; 105 | } 106 | 107 | // fac * mat 108 | template 109 | constexpr auto operator*(const F& f, const Mat& a) { 110 | Mat ret {}; 111 | for(auto r = 0u; r < R; ++r) 112 | for(auto c = 0u; c < C; ++c) 113 | ret[r][c] = f * a[r][c]; 114 | return ret; 115 | } 116 | 117 | // mat *= fac 118 | template 119 | constexpr auto& operator*=(Mat& a, const F& f) { 120 | for(auto r = 0u; r < R; ++r) 121 | for(auto c = 0u; c < C; ++c) 122 | a[r][c] *= f; 123 | return a; 124 | } 125 | 126 | // mat + mat 127 | template 128 | constexpr auto operator+(const Mat& a, const Mat& b) { 129 | Mat ret {}; 130 | for(auto r = 0u; r < R; ++r) 131 | for(auto c = 0u; c < C; ++c) 132 | ret[r][c] = a[r][c] + b[r][c]; 133 | return ret; 134 | } 135 | 136 | // mat += mat 137 | template 138 | constexpr auto& operator+=(Mat& a, const Mat& b) { 139 | for(auto r = 0u; r < R; ++r) 140 | for(auto c = 0u; c < C; ++c) 141 | a[r][c] += b[r][c]; 142 | return a; 143 | } 144 | 145 | // mat - mat 146 | template 147 | constexpr auto operator-(const Mat& a, const Mat& b) { 148 | Mat ret {}; 149 | for(auto r = 0u; r < R; ++r) 150 | for(auto c = 0u; c < C; ++c) 151 | ret[r][c] = a[r][c] - b[r][c]; 152 | return ret; 153 | } 154 | 155 | // mat -= mat 156 | template 157 | constexpr auto& operator-=(Mat& a, const Mat& b) { 158 | for(auto r = 0u; r < R; ++r) 159 | for(auto c = 0u; c < C; ++c) 160 | a[r][c] -= b[r][c]; 161 | return a; 162 | } 163 | 164 | // -mat 165 | template 166 | constexpr auto operator-(Mat a) { 167 | for(auto r = 0u; r < R; ++r) 168 | for(auto c = 0u; c < C; ++c) 169 | a[r][c] = -a[r][c]; 170 | return a; 171 | } 172 | 173 | template 174 | constexpr auto operator==(const Mat& a, const Mat& b) { 175 | for(auto i = 0u; i < R; ++i) 176 | if(a.rows_[i] != b.rows_[i]) 177 | return false; 178 | return true; 179 | } 180 | 181 | template 182 | constexpr auto operator!=(const Mat& a, const Mat& b) { 183 | return !(a == b); 184 | } 185 | 186 | } // namespace nytl 187 | 188 | #undef nytl_assure 189 | #endif // header guard 190 | -------------------------------------------------------------------------------- /nytl/rectOps.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Various operations and checks for Rects. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_RECT_OPS 10 | #define NYTL_INCLUDE_RECT_OPS 11 | 12 | #include // nytl::templatize 13 | #include // nytl::Rect 14 | #include // nytl::sum 15 | #include // nytl::Simplex 16 | 17 | #include // std::pair 18 | #include // std::vector 19 | #include // std::ostream 20 | 21 | namespace nytl { 22 | 23 | /// \brief Prints the given Rect. 24 | /// Simply outputs the position and size of the Rect. 25 | /// \module rectOps 26 | template 27 | std::ostream& print(std::ostream& os, const Rect& rect) { 28 | auto& tos = templatize(os); // we don't want to include ostream 29 | tos << "{"; 30 | print(tos, rect.position); 31 | print(tos, rect.size); 32 | tos << "}"; 33 | return tos; 34 | } 35 | 36 | /// \brief Prints the given Rect to a std::ostream. 37 | /// Simply uses nytl::print(os, rect). 38 | /// \module rectOps 39 | template 40 | std::ostream& operator<<(std::ostream& os, const Rect& rect) { 41 | return print(os, rect); 42 | } 43 | 44 | /// \brief Tests two rects for equality. 45 | /// They are equal when their position and size vectors are equal. 46 | /// \module rectOps 47 | template 48 | bool operator==(const Rect& a, const Rect& b) { 49 | return a.position == b.position && a.size == b.size; 50 | } 51 | 52 | template 53 | bool operator!=(const Rect& a, const Rect& b) { 54 | return a.position != b.position || a.size != b.size; 55 | } 56 | 57 | /// \brief Returns the total size of a given Rect. 58 | /// \module rectOps 59 | template 60 | constexpr auto size(const Rect& rect) { 61 | return multiply(rect.size); 62 | } 63 | 64 | /// \brief Returns the center of a given Rect 65 | /// Returns a vector in the same space of position and size of the rect. 66 | /// \module rectOps 67 | template 68 | constexpr auto center(const Rect& rect) { 69 | return rect.position + (0.5 * rect.size); 70 | } 71 | 72 | /// \brief Returns whether the given Rect contains the given point (as Vec). 73 | /// Returns true if the lays on the outline of the given rect. 74 | /// \requires Types 'T1' and 'T2' must describe the same field and be comparable. 75 | /// \module rectOps 76 | template 77 | constexpr bool contains(const Rect& rect, const Vec& point) { 78 | for(auto i = 0u; i < point.size(); ++i) { 79 | if(rect.position[i] > point[i]) return false; 80 | if(rect.position[i] + rect.size[i] < point[i]) return false; 81 | } 82 | 83 | return true; 84 | } 85 | 86 | /// \brief Returns whether the given Rect contains the given point (as Vec). 87 | /// Returns false if the lays on the outline of the given rect. 88 | /// \requires Types 'T1' and 'T2' must describe the same field and be comparable. 89 | /// \module rectOps 90 | template 91 | constexpr bool containsReal(const Rect& rect, const Vec& point) { 92 | for(auto i = 0u; i < point.size(); ++i) { 93 | if(rect.position[i] >= point[i]) return false; 94 | if(rect.position[i] + rect.size[i] <= point[i]) return false; 95 | } 96 | 97 | return true; 98 | } 99 | 100 | /// \brief Returns whether the the two given Rects intersect. 101 | /// Will return true if they only touch each other. 102 | /// \requires Types 'T1' and 'T2' must describe the same field and be comparable. 103 | /// \module rectOps 104 | template 105 | constexpr bool intersects(const Rect& a, const Rect& b) { 106 | return contains(a, b.position) || contains(a, b.position + b.size) || 107 | contains(b, a.position) || contains(b, a.position + a.size); 108 | } 109 | 110 | /// \brief Returns whether the the two given Rects intersect. 111 | /// Will return false if they only touch each other. 112 | /// \requires Types 'T1' and 'T2' must describe the same field and be comparable. 113 | /// \module rectOps 114 | template 115 | constexpr bool intersectsReal(const Rect& a, const Rect& b) { 116 | return containsReal(a, b.position) || containsReal(a, b.position + b.size) || 117 | containsReal(b, a.position) || containsReal(b, a.position + a.size); 118 | } 119 | 120 | /// \brief Returns the intersection between the given Rects. 121 | /// Note that two Rects in any dimensions can only interset in another Rect. 122 | /// \module rectOps 123 | template 124 | constexpr Rect intersection(const Rect& a, const Rect& b) { 125 | auto pos = vec::cw::max(a.position, b.position); 126 | auto end = vec::cw::min(a.position + a.size, b.position + b.size); 127 | 128 | // check if there is no intersection 129 | for(auto i = 0u; i < pos.size(); ++i) { 130 | if(pos[i] > end[i]) { 131 | return {{}, {}}; 132 | } 133 | } 134 | 135 | return {pos, end - pos}; 136 | } 137 | 138 | /// \brief Returns the difference of the first Rect to the second Rect (a -b). 139 | /// Effectively returns the parts of the first Rect that are not part of the second one. 140 | /// Returns the resulting Rects as a vector since the count depends on the layout 141 | /// of the Rects. If the Rects have no intersection, just returns a vector with 142 | /// only the first Rect and if they are the same, returns an empty vector. 143 | /// \notes This operations is not symmetric, i.e. difference(a, b) != difference(b, a). 144 | /// \notes Also see [nytl::rectOps::symmetricDifference](). 145 | /// In general is symmetricDifference(a, b) = difference(a, b) | difference(b, a); 146 | /// \module rectOps 147 | template 148 | std::vector> difference(const Rect& a, const Rect& b) { 149 | static constexpr auto inRange = [](T start, T size, T value) { 150 | return (start < value && value < start + size); 151 | }; 152 | 153 | std::vector> ret; 154 | ret.reserve(2 * D); 155 | 156 | for(std::size_t i(0); i < D; ++i) { 157 | 158 | // rect before intersection 159 | if(inRange(a.position[i], a.size[i], b.position[i])) { 160 | auto pos = a.position; 161 | for(std::size_t o(0); o < i; ++o) 162 | pos[o] = b.position[o]; 163 | 164 | auto size = (a.position + a.size) - pos; 165 | size[i] = b.position[i] - pos[i]; 166 | 167 | ret.push_back({pos, size}); 168 | } 169 | 170 | // rect after intersection 171 | // if(inRange(b.position[i] + b.size[i], a.position[i], a.size[i])) { 172 | if(inRange(a.position[i], a.size[i], b.position[i] + b.size[i])) { 173 | auto pos = a.position; 174 | pos[i] = b.position[i] + b.size[i]; 175 | for(std::size_t o(0); o < i; ++o) 176 | pos[o] = std::max(b.position[o], a.position[o]); 177 | 178 | auto size = (a.position + a.size) - pos; 179 | for(std::size_t o(0); o < i; ++o) 180 | size[o] = (b.position[o] + b.size[o]) - pos[o]; 181 | 182 | ret.push_back({pos, size}); 183 | } 184 | } 185 | 186 | return ret; 187 | } 188 | 189 | } // namespace nytl 190 | 191 | #endif // header guard 192 | -------------------------------------------------------------------------------- /nytl/bytes.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_BYTES 8 | #define NYTL_INCLUDE_BYTES 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace nytl { 19 | 20 | // Define NYTL_BYTES_ASSERT before including this header to define a custom 21 | // assert handler, e.g. like this: 22 | // #define NYTL_BYTES_ASSERT(x) assert(x) 23 | // #define NYTL_BYTES_ASSERT(x) custom_assert(x) 24 | // #define NYTL_BYTES_ASSERT(x) if(!(x)) { throw std::out_of_range(); } 25 | #ifndef NYTL_BYTES_ASSERT 26 | #define NYTL_BYTES_ASSERT(x) assert(x) 27 | #endif // NYTL_BITS_ASSERT 28 | 29 | // Converting objects to a readable or writable range of bytes only 30 | // works for objects that have standard layout: 31 | // - all non-static members have same access type 32 | // - no virtual functions 33 | // - no non-static reference members 34 | // - all base classes and non-static members have standard layout 35 | // - no two baseclasses of the same type 36 | // - all non-static data members declared in same class/base 37 | // - [some additional less relevant restrictions on members/bases] 38 | // See https://en.cppreference.com/w/cpp/named_req/StandardLayoutType 39 | 40 | // Buffer of fixed size that can be read but not written. 41 | // The data is not owned. Reading will advance the buffer and seeking backwards 42 | // is not possible (skipping section efficiently is, though). 43 | using ReadBuf = span; 44 | 45 | // Buffer of fixed maximum size that can be written. 46 | // Can be transformed into a ReadBuf as well. The data is not owned. 47 | // Writing will advance the buffer and backwards seeking is not possible. 48 | using WriteBuf = span; 49 | 50 | // Dynamically resizing write buffer. 51 | using DynWriteBuf = std::vector; 52 | 53 | // ProhibitByteConversion can be specialized to prohibit byte conversion 54 | // for types that would otherwise qualify. 55 | template struct ProhibitByteConversion : 56 | public std::false_type {}; 57 | template struct ProhibitByteConversion> : 58 | public std::true_type {}; 59 | template struct ProhibitByteConversion> : 60 | public std::true_type {}; 61 | 62 | template constexpr auto BytesConvertible = 63 | std::is_trivially_copyable_v && 64 | std::is_standard_layout_v && 65 | !ProhibitByteConversion::value; 66 | 67 | // Convert objects to their ReadBuf/WriteBuf representation. 68 | template 69 | std::enable_if_t, ReadBuf> 70 | bytes(const T& val) { 71 | return {reinterpret_cast(&val), sizeof(val)}; 72 | } 73 | 74 | template 75 | std::enable_if_t, WriteBuf> 76 | bytes(T& val) { 77 | return {reinterpret_cast(&val), sizeof(val)}; 78 | } 79 | 80 | template 81 | std::enable_if_t, ReadBuf> 82 | bytes(span span) { 83 | auto ptr = reinterpret_cast(span.data()); 84 | return {ptr, span.size() * sizeof(T)}; 85 | } 86 | 87 | template 88 | std::enable_if_t, WriteBuf> 89 | bytes(span span) { 90 | auto ptr = reinterpret_cast(span.data()); 91 | return {ptr, span.size() * sizeof(T)}; 92 | } 93 | 94 | template 95 | std::enable_if_t, ReadBuf> 96 | bytes(const std::vector& val) { 97 | return nytl::as_bytes(span(val)); 98 | } 99 | 100 | template 101 | std::enable_if_t, WriteBuf> 102 | bytes(std::vector& val) { 103 | return nytl::as_writeable_bytes(span(val)); 104 | } 105 | 106 | // There is no non-const overload for initializer list since we can't 107 | // ever modify data in it. 108 | template 109 | std::enable_if_t, ReadBuf> 110 | bytes(const std::initializer_list& val) { 111 | return nytl::as_bytes(span(val)); 112 | } 113 | 114 | inline void skip(ReadBuf& buf, std::size_t bytes) { 115 | NYTL_BYTES_ASSERT(buf.size() >= bytes); 116 | buf = buf.last(buf.size() - bytes); 117 | } 118 | 119 | inline void skip(WriteBuf& buf, std::size_t bytes) { 120 | NYTL_BYTES_ASSERT(buf.size() >= bytes); 121 | buf = buf.last(buf.size() - bytes); 122 | } 123 | 124 | inline void write(DynWriteBuf& dst, ReadBuf src) { 125 | dst.resize(dst.size() + src.size()); 126 | auto dstData = dst.data() + dst.size() - src.size(); 127 | std::memcpy(dstData, src.data(), src.size()); 128 | } 129 | 130 | inline void write(WriteBuf& dst, ReadBuf src) { 131 | NYTL_BYTES_ASSERT(dst.size() >= src.size()); 132 | std::memcpy(dst.data(), src.data(), src.size()); 133 | dst = dst.last(dst.size() - src.size()); 134 | } 135 | 136 | template 137 | void write(WriteBuf& dst, const T& obj) { 138 | write(dst, ReadBuf(bytes(obj))); 139 | } 140 | 141 | template 142 | void write(DynWriteBuf& dst, const T& obj) { 143 | write(dst, ReadBuf(bytes(obj))); 144 | } 145 | 146 | inline void read(ReadBuf& src, WriteBuf dst) { 147 | NYTL_BYTES_ASSERT(src.size() >= dst.size()); 148 | std::memcpy(dst.data(), src.data(), dst.size()); 149 | src = src.last(src.size() - dst.size()); 150 | } 151 | 152 | template 153 | std::enable_if_t, T> 154 | read(ReadBuf& src) { 155 | T ret; 156 | read(src, bytes(ret)); 157 | return ret; 158 | } 159 | 160 | template 161 | std::void_t()))> 162 | read(ReadBuf& src, T& obj) { 163 | read(src, bytes(obj)); 164 | } 165 | 166 | // Does not advance any of the buffers 167 | inline void copy(ReadBuf src, WriteBuf dst) { 168 | NYTL_BYTES_ASSERT(src.size() == dst.size()); 169 | std::memcpy(dst.data(), src.data(), dst.size()); 170 | } 171 | 172 | template 173 | std::enable_if_t, T> 174 | copy(ReadBuf src) { 175 | T ret; 176 | NYTL_BYTES_ASSERT(src.size() >= sizeof(ret)); 177 | std::memcpy(&ret, src.data(), sizeof(ret)); 178 | return ret; 179 | } 180 | 181 | template 182 | std::enable_if_t, T> 183 | copyExact(ReadBuf src) { 184 | T ret; 185 | NYTL_BYTES_ASSERT(src.size() == sizeof(ret)); 186 | std::memcpy(&ret, src.data(), sizeof(ret)); 187 | return ret; 188 | } 189 | 190 | // Example for writing a fixed-size data segment: 191 | // 192 | // WriteBuf dst; 193 | // nytl::write(dst, 1.f); 194 | // nytl::write(dst, someStdLayoutStruct); 195 | // nytl::write(dst, nytl::Vec3f{5.f, 6.f, 1.f}); 196 | // nytl::write(dst, vectorOfInt.size()); 197 | // nytl::write(dst, vectorOfInts); 198 | // 199 | // And reading it afterwards: 200 | // 201 | // ReadBuf src = dst; 202 | // auto f1 = nytl::read(src); 203 | // auto someStdLayoutStruct = nytl::read<...>(src); 204 | // auto vec = nytl::read(src); 205 | // auto size = nytl::read(src); 206 | // std::vector vectorOfInt(size); 207 | // nytl::read(src, vectorOfInt); 208 | // 209 | // The symmetry between the write and read apis is quite obvious. 210 | // Cases such as dynamically-sized data must be handled differently though, 211 | // see the reading vectorOfInt example above. 212 | // When instead of a fixed-size data segment, a dynamically resizing 213 | // buffer is desired for writing, just use nytl::DynWriteBuf. Works 214 | // exactly the same and can be converted to ReadBuf as well. 215 | 216 | } // namespace nytl 217 | 218 | #endif // NYTL_INCLUDE_BYTES 219 | -------------------------------------------------------------------------------- /nytl/connection.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Defines the Connection and Connectable classes needed e.g. for callbacks. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_CONNECTION 10 | #define NYTL_INCLUDE_CONNECTION 11 | 12 | #include // std::cerr 13 | #include // std::exception 14 | #include // std::shared_ptr 15 | 16 | namespace nytl { 17 | 18 | /// \brief Interface for classes that can be connected to in some way. 19 | /// An example (in nytl) is nytl::Callback. 20 | /// The way for obtaining such a connection if class-defined, this interface defines 21 | /// only a common way to disconnect again, which can then be used by the connection classes. 22 | /// Using this abstraction makes e.g. the Connection class possible as generic 23 | /// connection, separated from the type of the class it has a connection for. 24 | template 25 | class ConnectableT { 26 | public: 27 | virtual ~ConnectableT() = default; 28 | virtual bool disconnect(const ID& id) = 0; 29 | }; 30 | 31 | /// \brief Associates a BasicConnectable implementation with one of its connection ids. 32 | /// Note that this does not automatically destroy the connection, nor does is 33 | /// track the lifetime of the BasicConnectable implementation object. 34 | template 35 | class ConnectionT { 36 | public: 37 | ConnectionT(C& connectable, ID id) 38 | : connectable_(&connectable), id_(id) {} 39 | 40 | ConnectionT() noexcept = default; 41 | ~ConnectionT() = default; 42 | 43 | ConnectionT(const ConnectionT&) noexcept = default; 44 | ConnectionT& operator=(const ConnectionT&) noexcept = default; 45 | 46 | /// Disconnects the represented connection. 47 | void disconnect() 48 | { 49 | if(connectable_) 50 | connectable_->disconnect(id_); 51 | connectable_ = {}; 52 | id_ = {}; 53 | } 54 | 55 | /// Returns whether this connection object is valid. 56 | /// Note that depending on the connection id and callable type and if 57 | /// the callback was destroyed before the connection, this may 58 | /// not represent the status of the connection. 59 | bool connected() const noexcept { return connectable_ && id_.get() > 0; } 60 | 61 | /// The associated connectable object. 62 | C* connectable() const { return connectable_; } 63 | 64 | /// The associated connection id. 65 | ID id() const { return id_; } 66 | 67 | protected: 68 | C* connectable_ {}; 69 | ID id_ {}; 70 | }; 71 | 72 | /// \brief RAII wrapper around a connection id. 73 | /// Note that this does not observe the lifetime of the object the connection id 74 | /// was received from. Destroying the associated Connectable object during the lifetime 75 | /// of the Connection object without then explicitly releasing the Connection id results 76 | /// in undefined behavior. Same as BasicConnection, but owns the connection 77 | /// it holds, i.e. disconnects it on destruction. Therefore there should never be multiple 78 | /// BasicConnectionGuards for the same connection id. If there exists a connection guard 79 | /// for a connection this connection should not be disconnected in any other way than 80 | /// the destruction of the guard (except the guard is explicitly released). 81 | /// \reqruies Type 'C' shall be Disconnectable, i.e. implement disconnect() member function, e.g 82 | /// derived from ConnectableT 83 | /// \reqruies Type 'ID' shall fulfill the ConntectableID concept. 84 | template 85 | class UniqueConnectionT { 86 | public: 87 | UniqueConnectionT() = default; 88 | 89 | UniqueConnectionT(C& connectable, ID id) 90 | : connectable_(&connectable), id_(id) {} 91 | 92 | UniqueConnectionT(ConnectionT lhs) 93 | : connectable_(lhs.connectable()), id_(lhs.id()) {} 94 | 95 | ~UniqueConnectionT() 96 | { 97 | try { 98 | disconnect(); 99 | } catch(const std::exception& error) { 100 | std::cerr << "nytl::~UniqueConnectionT: disconnect failed: " << error.what() << "\n"; 101 | } 102 | } 103 | 104 | UniqueConnectionT(UniqueConnectionT&& lhs) noexcept 105 | : connectable_(lhs.connectable_), id_(lhs.id_) 106 | { 107 | lhs.id_ = {}; 108 | lhs.connectable_ = {}; 109 | } 110 | 111 | UniqueConnectionT& operator=(UniqueConnectionT&& lhs) noexcept 112 | { 113 | try { 114 | disconnect(); 115 | } catch(const std::exception& error) { 116 | std::cerr << "nytl::UniqueConnectionT: disconnect failed: " << error.what() << "\n"; 117 | } 118 | 119 | connectable_ = lhs.connectable_; 120 | id_ = lhs.id_; 121 | lhs.connectable_ = {}; 122 | lhs.id_ = {}; 123 | return *this; 124 | } 125 | 126 | /// Disconnects the represented connection. 127 | void disconnect() 128 | { 129 | if(connected()) 130 | connectable_->disconnect(id_); 131 | connectable_ = {}; 132 | id_ = {}; 133 | } 134 | 135 | /// The associated connectable object. 136 | C* connectable() const { return connectable_; } 137 | 138 | /// The associated connection id. 139 | ID id() const { return id_; } 140 | 141 | /// Returns whether this connection object is valid. 142 | /// Note that depending on the connection id and callable type and if 143 | /// the callback was destroyed before the connection, this may 144 | /// not represent the status of the connection. 145 | bool connected() const noexcept { return connectable_ && id_.get() > 0; } 146 | 147 | /// Releases ownership of the associated connection and returns a non-owned 148 | /// connection object. 149 | /// After the call this object will be empty. 150 | ID release() 151 | { 152 | ConnectionT ret{connectable_, id_}; 153 | id_ = {}; 154 | connectable_ = {}; 155 | return ret; 156 | } 157 | 158 | protected: 159 | C* connectable_ {}; 160 | ID id_ {}; 161 | }; 162 | 163 | /// Default ID for a connection that is entirely defined over its value. 164 | struct ConnectionID { 165 | std::int64_t value; 166 | 167 | void set(std::int64_t val) noexcept { value = val; } 168 | auto get() const noexcept { return value; } 169 | void removed() const noexcept {} 170 | }; 171 | 172 | /// Shares the id value between all connections so that disconnections from 173 | /// another connection or the callback itself can be observed. 174 | struct TrackedConnectionID { 175 | std::shared_ptr value; 176 | 177 | TrackedConnectionID() = default; 178 | TrackedConnectionID(std::int64_t val) : value(std::make_shared(val)) {} 179 | 180 | void set(std::int64_t val) noexcept { if(value) *value = val; } 181 | auto get() const noexcept { return (value) ? *value : 0; } 182 | void removed() noexcept { if(value) *value = 0; value.reset(); } 183 | }; 184 | 185 | using Connectable = ConnectableT; 186 | using Connection = ConnectionT; 187 | using UniqueConnection = UniqueConnectionT; 188 | 189 | using TrackedConnectable = ConnectableT; 190 | using TrackedConnection = ConnectionT; 191 | using TrackedUniqueConnection = UniqueConnectionT; 192 | 193 | // TODO: remove 194 | /* 195 | constexpr inline bool operator==(ConnectionID a, ConnectionID b) 196 | { return a.value == b.value; } 197 | constexpr inline bool operator!=(ConnectionID a, ConnectionID b) 198 | { return a.value != b.value; } 199 | bool inline operator==(const TrackedConnectionID& a, const TrackedConnectionID& b) 200 | { return a.value == b.value; } 201 | bool inline operator!=(const TrackedConnectionID& a, const TrackedConnectionID& b) 202 | { return a.value != b.value; } 203 | */ 204 | 205 | } // namespace nytl 206 | 207 | #endif // header guard 208 | -------------------------------------------------------------------------------- /docs/tests/rcallback.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | 5 | // TODO: simple tests that varify the semantics of mixing/recursing 6 | // operations. Also test with custom id type 7 | 8 | // basic connection and callback functionality 9 | TEST(basic) { 10 | nytl::RecursiveCallback cb; 11 | auto called = 0u; 12 | cb(); 13 | EXPECT(called, 0u); 14 | 15 | // add, operator += 16 | called = 0u; 17 | auto inc = [&]{ ++called; }; 18 | cb += inc; 19 | cb.add(inc); 20 | cb(); 21 | EXPECT(called, 2u); 22 | 23 | // operator = 24 | called = 0u; 25 | cb = inc; 26 | cb(); 27 | EXPECT(called, 1u); 28 | 29 | // clear 30 | called = 0u; 31 | cb.clear(); 32 | cb(); 33 | EXPECT(called, 0u); 34 | 35 | // disconnect 36 | called = 0u; 37 | auto conn1 = cb.add(inc); 38 | cb(); 39 | EXPECT(called, 1u); 40 | conn1.disconnect(); 41 | cb(); 42 | EXPECT(called, 1u); 43 | 44 | // errors 45 | cb.clear(); 46 | ERROR(cb.add(std::function{}), std::invalid_argument); 47 | ERROR(cb = std::function{}, std::invalid_argument); 48 | ERROR(cb += std::function{}, std::invalid_argument); 49 | } 50 | 51 | TEST(exception) { 52 | // check arguments get passed 53 | nytl::RecursiveCallback cb; 54 | cb.add([&](auto i){ EXPECT(i, 42); }); 55 | cb(42); 56 | 57 | // check exception is propagated 58 | cb = [&](auto i){ if(i < 5) throw 42; }; 59 | cb(7); 60 | ERROR(cb(3), int); 61 | 62 | // check no more handlers after exception is called 63 | auto called = 0u; 64 | cb.add([&](int){ ++called; }); 65 | cb(42); 66 | EXPECT(called, 1u); 67 | ERROR(cb(2), int); 68 | EXPECT(called, 1u); 69 | 70 | // check callback still works as expected 71 | cb(69); 72 | EXPECT(called, 2u); 73 | } 74 | 75 | TEST(retval) { 76 | // basic check that return value is correct 77 | nytl::RecursiveCallback cb; 78 | cb.add([&]{ return 1; }); 79 | cb.add([&]{ return 2; }); 80 | auto vec = cb(); 81 | 82 | EXPECT(vec.size(), 2u); 83 | EXPECT(vec[0], 1); 84 | EXPECT(vec[1], 2); 85 | } 86 | 87 | TEST(recursive) { 88 | nytl::RecursiveCallback cb; 89 | auto called = 0u; 90 | 91 | // 92 | cb.add([&](nytl::Connection conn){ 93 | ++called; 94 | EXPECT(conn.connected(), true); 95 | EXPECT(conn.connectable(), &cb); 96 | conn.disconnect(); 97 | EXPECT(conn.connected(), false); 98 | }); 99 | cb(); 100 | EXPECT(called, 1u); 101 | 102 | // 103 | called = 0u; 104 | cb(); 105 | EXPECT(called, 0u); 106 | 107 | // 108 | cb.clear(); 109 | ERROR(cb.add(std::function{}), std::logic_error); 110 | ERROR(cb.add(std::function{}), std::logic_error); 111 | ERROR(cb = std::function{}, std::logic_error); 112 | ERROR(cb += std::function{}, std::logic_error); 113 | } 114 | 115 | // tests correct return values,order and exception propagation 116 | TEST(retval2) { 117 | nytl::RecursiveCallback cb; 118 | 119 | // #1 120 | cb.add([]{ return 0u; }); 121 | cb.add([]{ return 1u; }); 122 | cb.add([]{ return 2u; }); 123 | auto ret = cb(); 124 | EXPECT(ret.size(), 3u); 125 | EXPECT(ret.at(0), 0u); 126 | EXPECT(ret.at(1), 1u); 127 | EXPECT(ret.at(2), 2u); 128 | 129 | // #2 130 | cb.clear(); 131 | cb.add([]() -> unsigned int { throw 0; }); 132 | cb.add([]() -> unsigned int { throw 1; }); 133 | cb.add([]() -> unsigned int { throw 2; }); 134 | 135 | auto caught = false; 136 | try { cb(); } 137 | catch(int i) { 138 | caught = true; 139 | EXPECT(i, 0); 140 | } 141 | EXPECT(caught, true); 142 | 143 | // #3 144 | cb.clear(); 145 | ret = cb(); 146 | EXPECT(ret.empty(), true); 147 | } 148 | 149 | // adds and remove callback during the call 150 | TEST(interfer) { 151 | nytl::RecursiveCallback cb; 152 | auto called = 0u; 153 | 154 | // #1 155 | called = 0; 156 | cb.add([&]{ cb.add([&]{ ++called; }); }); 157 | cb(); 158 | EXPECT(called, 0u); 159 | cb(); 160 | EXPECT(called, 1u); 161 | 162 | // #2 163 | called = 0; 164 | nytl::Connection conn3; 165 | cb.clear(); 166 | cb.add([&](nytl::Connection conn){ ++called; conn.disconnect(); }); 167 | auto conn2 = cb.add([&]{ ++called; conn3.disconnect(); }); 168 | conn3 = cb.add([&]{ ++called; }); 169 | cb(); 170 | EXPECT(called, 3u); 171 | conn2.disconnect(); 172 | cb(); 173 | EXPECT(called, 3u); 174 | 175 | // #3 176 | called = 0; 177 | cb = [&]{ ++called; }; 178 | cb += [&](nytl::Connection conn){ 179 | ++called; 180 | conn.disconnect(); 181 | 182 | cb(); 183 | EXPECT(called, 3u); 184 | }; 185 | 186 | cb(); 187 | EXPECT(called, 3u); 188 | } 189 | 190 | 191 | TEST(inter_callback) { 192 | nytl::RecursiveCallback cb; 193 | int called {}; 194 | 195 | nytl::Connection c4; 196 | 197 | // more complex example 198 | // use the call times to calculate the overall called number 199 | 200 | auto c1 = cb.add([&]{ ++called; }); // 1 call 201 | cb.add([&](nytl::Connection conn){ conn.disconnect(); called++; }); // 1 call 202 | cb.add([&]{ ++called; c1.disconnect(); }); // 1 + 7 + 1 calls 203 | cb.add([&]{ if(called < 10) { cb(); c4.disconnect(); } }); // 1 + 7 + 1 calls 204 | c4 = cb.add([&]{ if(called < 11) cb(); }); // 1 + 7 + 1 calls 205 | 206 | // special case: although the connection is destroyed the first time 207 | // this will be called 1 + 7 + 1 times as well since it is disconnected 208 | // in the deepest recall, while the other calls were already started, so 209 | // they will call the function as well. This behaviour is explicityly wanted 210 | cb.add([&](nytl::Connection conn){ ++called; conn.disconnect(); }); 211 | 212 | cb(); 213 | EXPECT(called, 20); 214 | } 215 | 216 | 217 | // - old, rather random tests - 218 | // - might break when working on callback - 219 | TEST(callback_1) { 220 | nytl::RecursiveCallback a; 221 | 222 | int called {}; 223 | auto inc = [&]{ ++called; }; 224 | a += inc; 225 | a(); 226 | 227 | EXPECT(called, 1); 228 | called = 0; 229 | 230 | a = inc; 231 | a += inc; 232 | auto conn1 = a.add(inc); 233 | a.add([&](nytl::Connection conn){ ++called; conn.disconnect(); }); 234 | 235 | a(); 236 | EXPECT(called, 4); 237 | called = 0; 238 | 239 | conn1.disconnect(); 240 | a(); 241 | EXPECT(called, 2); 242 | called = 0; 243 | 244 | a = inc; 245 | a(); 246 | EXPECT(called, 1); 247 | } 248 | 249 | TEST(clusterfuck) { 250 | nytl::RecursiveCallback cb; 251 | int called {}; 252 | 253 | cb.add([&]{ ++called; if(called < 2) cb(); }); 254 | cb.add([&]{ cb.add([&]{ if(called < 3) cb(); }); }); 255 | cb.add([&]{ cb.add([&](nytl::Connection conn){ conn.disconnect(); if(called < 4) cb(); }); }); 256 | cb.add([&](nytl::Connection conn){ conn.disconnect(); cb(); }); 257 | nytl::Connection conn1 = cb.add([&]{ conn1.disconnect(); cb(); }); 258 | 259 | auto conn2 = cb.add([&]{ cb.add([&]{ ++called; }); }); 260 | cb.add([&]{ conn2.disconnect(); }); 261 | 262 | cb.add([&](nytl::Connection){ cb.clear(); }); 263 | cb.add([&] { cb.clear(); }); 264 | 265 | cb(); 266 | 267 | EXPECT(called, 4); // this was manually checked once... 268 | // EXPECT(cb.size(), 0u); 269 | } 270 | 271 | TEST(removeOld) { 272 | nytl::RecursiveCallback cb; 273 | cb.add([&](auto conn){ conn.disconnect(); }); 274 | cb(); 275 | cb(); 276 | } 277 | 278 | TEST(connection) { 279 | nytl::RecursiveCallback cb; 280 | 281 | auto called = 0u; 282 | nytl::Connection conn1 = cb.add([&]{ ++called; }); 283 | cb(); 284 | EXPECT(called, 1u); 285 | 286 | { 287 | auto conn2 = nytl::UniqueConnection(cb.add([&]{ ++called; })); 288 | auto conn3 = cb.add([&]{ ++called; }); 289 | nytl::unused(conn3); 290 | 291 | cb(); 292 | EXPECT(called, 4u); 293 | } 294 | 295 | cb(); 296 | EXPECT(called, 6u); 297 | 298 | conn1.disconnect(); 299 | 300 | cb(); 301 | EXPECT(called, 7u); 302 | } 303 | -------------------------------------------------------------------------------- /nytl/callback.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Defines the Callback template class. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_CALLBACK 10 | #define NYTL_INCLUDE_CALLBACK 11 | 12 | #include // nytl::BasicConnection 13 | #include // nytl::NonCopyable 14 | #include // nytl::ScopeGuard 15 | 16 | #include // std::function 17 | #include // std::move 18 | #include // std::uint64_t 19 | #include // std::size_t 20 | #include // std::is_same 21 | #include // std::vector 22 | #include // std::numeric_limits 23 | #include // std::cerr 24 | #include // std::logic_error 25 | 26 | namespace nytl { 27 | 28 | /// A Callback is a collection of functions with the given signature. 29 | /// Everyone can add functions or remove his registered function using 30 | /// the id returned from registering it. 31 | /// All callbacks can then be called with the required arguments 32 | /// This callback class does not allow ANY recursive operations on it, 33 | /// so e.g. removing a registered function or register a new function 34 | /// from within a callback call will lead to 35 | /// undefined behvaiour. If you need this recursive functionality (in any way, 36 | /// even if from the connectionID class), see nytl/recursiveCallback. 37 | /// The public interface of this class is mostly identical with RecursiveCallback 38 | /// so they can be used interchangeably (exceptions documented). 39 | /// The class is not thread-safe in any way. 40 | /// All exceptions from calls are just propagated. 41 | /// The class can not be copied or moved. 42 | /// 43 | /// \tparam Signature The signature of the registered functions. 44 | /// Uses the same syntax and semantics as std::function. 45 | /// \tparam ID A connectionID class, see nytl/connection.hpp for examples. 46 | /// See docs/callback.md for specification. 47 | template 48 | class Callback; 49 | 50 | /// Callback class typedef using TrackedConnectionID. Enables connections 51 | /// to see when their associated function is unregistered by another 52 | /// connection or because the callback was destroyed. 53 | template using TrackedCallback = 54 | Callback; 55 | 56 | // Callback specialization to enable the Ret(Args...) Signature format. 57 | template 58 | class Callback 59 | : public ConnectableT, public NonCopyable { 60 | public: 61 | /// ! Definition not present in RecursiveCallback 62 | /// Represents one callback subscription entry. 63 | /// Invalid (formally removed) when id is not valid. 64 | /// Note that these means that alternative ID classes can 65 | /// remove subscriptions from the outside without actively 66 | /// calling disconnect. 67 | struct Subscription { 68 | std::function func; 69 | ID id; 70 | }; 71 | 72 | using Signature = Ret(Args...); 73 | using Connection = ConnectionT, ID>; 74 | 75 | public: 76 | Callback() = default; 77 | ~Callback(); 78 | 79 | /// \brief Registers a new Callback function. 80 | /// \returns A connection id for the registered function which can be used to 81 | /// unregister it. 82 | /// \throws std::invalid_argument If an empty function target is registered. 83 | Connection add(std::function); 84 | 85 | /// Calls all registered functions and returns a vector with the returned objects, 86 | /// or void when this is a void callback. 87 | /// Will call all the functions registered at the moment of calling, i.e. additional 88 | /// functions added from within are not called. 89 | /// If a registered function throws, the exception is not caught, i.e. the following 90 | /// handlers will not be called. 91 | auto call(Args...); 92 | 93 | /// Clears all registered functions. 94 | void clear() noexcept; 95 | 96 | /// Removes the callback function registered with the given id. 97 | /// Returns whether the function could be found. If the id is invalid or the 98 | /// associated function was already removed, returns false. 99 | /// Prefer to use this indirectly using some Connection object. 100 | /// Propagates exceptions from ID::removed() but always removes the associated 101 | /// function. Passing an invalid id or an id that was not returned from this 102 | /// callback is undefined behvaiour. 103 | bool disconnect(const ID&) noexcept override; 104 | 105 | /// Operator version of add 106 | template 107 | Connection operator+=(F&& func) { 108 | return add(std::forward(func)); 109 | } 110 | 111 | /// Operator version of add that previously calls clear. 112 | template 113 | Connection operator=(F&& func) { 114 | clear(); 115 | return add(std::forward(func)); 116 | } 117 | 118 | /// Operator version of call. 119 | auto operator() (Args... a) { 120 | return call(std::forward(a)...); 121 | } 122 | 123 | /// ! Not present in RecursiveCallback 124 | /// Returns the internal vector of registered subscriptions 125 | /// Can be used to e.g. call it more efficiently (without creating a vector) or 126 | /// with custom exception handling. 127 | const auto& subscriptions() const { 128 | return subs_; 129 | } 130 | 131 | protected: 132 | std::vector subs_ {}; // all subscriptions, ordered by id 133 | std::int64_t subID_ {}; // the highest subscription id given 134 | }; 135 | 136 | // - implementation 137 | template 138 | Callback::~Callback() 139 | { 140 | for(auto& sub : subs_) { 141 | sub.id.removed(); 142 | } 143 | } 144 | 145 | template 146 | ConnectionT, ID> Callback:: 147 | add(std::function func) { 148 | if(!func) { 149 | throw std::invalid_argument("nytl::Callback::add: empty function"); 150 | } 151 | 152 | // output at least a warning when subID_ has to be wrapped 153 | // Usually this should not happen. Bad things can happen then. 154 | if(subID_ == std::numeric_limits::max()) { 155 | std::cerr << "nytl::Callback::emplace: wrapping subID_\n"; 156 | subID_ = 0; 157 | } 158 | 159 | // this expression might throw. In this case we have not changed 160 | // our own state in any bad way 161 | ID id = {subID_ + 1}; 162 | 163 | ++subID_; 164 | subs_.emplace_back(); 165 | subs_.back().id = id; 166 | subs_.back().func = std::move(func); 167 | return {*this, id}; 168 | } 169 | 170 | template 171 | auto Callback::call(Args... a) 172 | { 173 | // the first continue check is needed to not call functions that were 174 | // removed before this call started but call functions that were removed 175 | // only by some other callback function 176 | if constexpr(std::is_same::value) { 177 | for(auto& func : subs_) 178 | func.func(std::forward(a)...); 179 | } else { 180 | std::vector ret; 181 | ret.reserve(subs_.size()); 182 | 183 | for(auto& func : subs_) { 184 | ret.push_back(func.func(std::forward(a)...)); 185 | } 186 | 187 | return ret; 188 | } 189 | } 190 | 191 | template 192 | void Callback::clear() noexcept 193 | { 194 | // notify the ids of removal 195 | for(auto& sub : subs_) { 196 | sub.id.removed(); 197 | } 198 | subs_.clear(); 199 | } 200 | 201 | template 202 | bool Callback::disconnect(const ID& id) noexcept 203 | { 204 | constexpr auto pred = [](const auto& s1, const auto& s2) { 205 | return s1.id.get() < s2.id.get(); 206 | }; 207 | 208 | // we know that id's are ordered 209 | auto ds = Subscription{{}, id}; // dummy 210 | auto range = std::equal_range(subs_.begin(), subs_.end(), ds, pred); 211 | if(range.first == range.second) { 212 | return false; 213 | } 214 | 215 | // we can assume that there is only one item in the range 216 | range.first->id.removed(); 217 | subs_.erase(range.first); 218 | return true; 219 | } 220 | 221 | } // namespace nytl 222 | 223 | #endif // header guard 224 | -------------------------------------------------------------------------------- /nytl/clone.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #pragma once 6 | 7 | #ifndef NYTL_INCLUDE_CLONE 8 | #define NYTL_INCLUDE_CLONE 9 | 10 | #include // std::unique_ptr 11 | 12 | namespace nytl { 13 | 14 | /// \brief Can be used to clone a Cloneable object. 15 | /// Will perform a copy of the actual type the interface reference references. 16 | /// \requires Type 'T' shall be Cloneable (i.e. derived from a nytl::Cloneable or 17 | /// nytl::AbstractCloneable class using nytl::DeriveCloneable). 18 | /// \module utility 19 | template 20 | std::unique_ptr clone(const T& obj) { 21 | return std::unique_ptr(static_cast(obj.doClone())); 22 | } 23 | 24 | /// \brief Can be used to cloneMove a CloneMovable object. 25 | /// Will move from the given object and return a moved copy of it preserving the actual 26 | /// implementation type the interface reference references. 27 | /// \requires Type 'T' shall be derived from a nytl::CloneMovable or 28 | /// nytl::AbstractCloneMovable class using nytl::DeriveCloneMovable. 29 | /// \module utility 30 | template 31 | std::unique_ptr cloneMove(T& obj) { 32 | return std::unique_ptr(static_cast(obj.doCloneMove())); 33 | } 34 | 35 | /// \brief Can be derived from to implement the cloneMove member function for 'Derived'. 36 | /// \requires Type 'Derived' shall derive from this class using the CRTP idiom. 37 | /// For this to work, Base must have defined a virtual cloneMove member function in its interface. 38 | /// Usually used together with [nytl::CloneMovable]() or [nytl::AbstractCloneMovable]() 39 | /// in some class Derived is derived from (i.e. Base or ancestors). 40 | /// \module utility 41 | template 42 | class DeriveCloneMovable : public Bases... { 43 | protected: 44 | void* doCloneMove() override; // Base return type since CRTP 45 | template friend std::unique_ptr cloneMove(O&); 46 | }; 47 | 48 | template 49 | class DeriveCloneMovable : public Base { 50 | protected: 51 | using Base::Base; 52 | void* doCloneMove() override; // Base return type since CRTP 53 | template friend std::unique_ptr cloneMove(O&); 54 | }; 55 | 56 | /// \brief Can be derived from to implement the clone/cloneMove member functions for 'Derived'. 57 | /// \requires Type 'Derived' shall derive from this class using the CRTP idiom. 58 | /// For this to work, Base must have defined virtual clone/cloneMove member functions in 59 | /// its interface. Usually used together with [nytl::Cloneable]() or [nytl::AbstractCloneable]() 60 | /// in some class Derived is derived from (i.e. Base or ancestors). 61 | /// \module utility 62 | template 63 | class DeriveCloneable : public DeriveCloneMovable { 64 | protected: 65 | using DeriveCloneMovable::DeriveCloneMovable; 66 | void* doClone() const override; // Base return type since CRTP 67 | template friend std::unique_ptr clone(const O&); 68 | }; 69 | 70 | /// \brief Can be derived from to make clone-moving for interfaces possible. 71 | /// If some interface class derived from this type using the CRTP idiom (i.e. 'T' is the 72 | /// deriving class), objects of this interface will be able to be clone-moved, i.e. they can 73 | /// be move-copied into a new object without knowing their exact implementation type. 74 | /// Implementing classes usually use DeriveCloneMovable to implement it. 75 | /// To additionally enable normal cloning (i.e. copying without moving), see 76 | /// [nytl::AbstractCloneable](). 77 | /// \module utility 78 | template 79 | class AbstractCloneMovable { 80 | protected: 81 | virtual void* doCloneMove() = 0; 82 | virtual ~AbstractCloneMovable() = default; 83 | template friend std::unique_ptr cloneMove(O&); 84 | }; 85 | 86 | /// \brief Can be derived from to make cloning for abstract classes possible. 87 | /// If some interface class derived from this type using the CRTP idiom (i.e. 'T' is the 88 | /// deriving class), objects of this interface will be able to be cloned, i.e. they can 89 | /// be duplicated to a new object without knowing their exact implementation type. 90 | /// Implementing classes usually use DeriveCloneable to implement it. 91 | /// To only enable clone-moving (i.e. don't require implementations to actually duplicate 92 | /// objects), see [nytl::AbstractCloneMovable](). 93 | /// \module utility 94 | template 95 | class AbstractCloneable : public AbstractCloneMovable { 96 | protected: 97 | virtual void* doClone() const = 0; 98 | template friend std::unique_ptr clone(const O&); 99 | }; 100 | 101 | 102 | /// \brief Can be used to add the clone-move interface to a class as well as already implement it. 103 | /// \module utility 104 | template 105 | class CloneMovable : public AbstractCloneMovable { 106 | protected: 107 | void* doCloneMove() override { return new T(std::move(static_cast(*this))); } 108 | template friend std::unique_ptr cloneMove(O&); 109 | }; 110 | 111 | /// \brief Can be used to add the clone interface to a class as well as already implement it. 112 | /// \module utility 113 | template 114 | class Cloneable : public AbstractCloneable { 115 | protected: 116 | void* doCloneMove() override { return new T(std::move(static_cast(*this))); } 117 | void* doClone() const override { return new T(static_cast(*this)); } 118 | template friend std::unique_ptr clone(const O&); 119 | template friend std::unique_ptr cloneMove(const O&); 120 | }; 121 | 122 | // - derive class implementation - 123 | template 124 | void* DeriveCloneMovable::doCloneMove() 125 | { return new Derived(std::move(static_cast(*this))); } 126 | 127 | template 128 | void* DeriveCloneMovable::doCloneMove() 129 | { return new Derived(std::move(static_cast(*this))); } 130 | 131 | template 132 | void* DeriveCloneable::doClone() const 133 | { return new Derived(static_cast(*this)); } 134 | 135 | } 136 | 137 | #endif //header guard 138 | 139 | /// \file 140 | /// \brief Small utilities for defining/using Cloneable classes. 141 | /// \details Small (full copyable) example for using this header: 142 | /// 143 | /// ```cpp 144 | /// // We have to use nytl::AbstractCloneable sine Base is already abstract. 145 | /// // Otherwise, just use nytl::Cloneable. If you only want to nytl::cloneMove 146 | /// // and don't care about nytl::clone, use AbstractCloneMovable/CloneMovable 147 | /// // and the associated derivation helpers. 148 | /// struct Base : public nytl::AbstractCloneable { 149 | /// virtual int value() const = 0; 150 | /// }; 151 | /// 152 | /// struct Derived : public nytl::DeriveCloneable { 153 | /// int value_; 154 | /// int value() const override { return value_; } 155 | /// }; 156 | /// 157 | /// int main() / { 158 | /// auto derived = Derived {}; // some dummy object with value 42 159 | /// derived.value_ = 42; 160 | /// 161 | /// auto ptr = static_cast(&derived); // a Base pointer to it loosing actual type info 162 | /// 163 | /// auto copy = nytl::clone(*ptr); // we duplicate the Derived object by the base ptr 164 | /// auto moved = nytl::cloneMove(*ptr); // we move from ptr (-> derived) into moved 165 | /// 166 | /// // copy and moved are both objects of type Derived (wrapped in a unique_ptr) that were 167 | /// // copied from derived, so both have the value 42. We could copy this value 168 | /// // by just having a Base pointer. 169 | /// std::cout << copy->value() << "\n"; // will output 42 170 | /// std::cout << moved->value() << "\n"; // will output 42 171 | /// } 172 | /// ``` 173 | /// 174 | /// Instead of `Base` you could also pass multiple bases from which to derive. 175 | /// If you only derive from one type you can still use its constructor, it is 176 | /// also used by DeriveCloneable/DeriveCloneMovable. 177 | 178 | // implementation notes: The rather complex doClone/doCloneMove virtual implementation 179 | // is needed since one cannot return covariant return types from virtual functions when using CRTP. 180 | // we have to use void* since we allow multiple base classes. 181 | -------------------------------------------------------------------------------- /docs/tests/vec.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // testing both, vec and vecOps 10 | #include 11 | #include 12 | #include 13 | 14 | #pragma GCC diagnostic push 15 | #pragma GCC diagnostic ignored "-Wunused-const-variable" 16 | 17 | // TODO: test component-wise (nytl::vcw) operations 18 | // TODO: more testvectors, use other sizes and types 19 | 20 | // NOTE: for a real test, uncomment the should not compile statements 21 | // one-by-one and assure that they not compile 22 | 23 | using namespace nytl; 24 | 25 | // collection of 3d vecs 26 | 27 | // just a few random vectors with different types 28 | // no special meaning 29 | // double 30 | constexpr Vec2d d2a {1.0, 2.0}; 31 | constexpr Vec2d d2b {0.0, 12.0}; 32 | constexpr Vec2d d2c {-5.0, 2.5}; 33 | 34 | constexpr Vec3d d3a {1.0, 2.0, 3.0}; 35 | constexpr Vec3d d3b {0.0, 0.0, 0.0}; 36 | constexpr Vec3d d3c {1.0, 0.0, 0.0}; 37 | constexpr Vec3d d3d {-1.0, 1.0, 1.0}; 38 | constexpr Vec3d d3e {0.0001, 1.0, -1.0}; 39 | constexpr Vec3d d3f {100.0, 500.0, -4.0}; 40 | constexpr Vec3d d3g {2.0, 3.0, 5.0}; 41 | constexpr Vec3d d3h {4.0, -3.0, 2.0}; 42 | constexpr Vec3d d3i {0.0, 10.0, 1.0}; 43 | 44 | constexpr Vec4d d4a{1.0, 2.7, 3.87, 8.22}; 45 | constexpr Vec4d d4b{0.0, -2.334, 0.0, -103.4}; 46 | constexpr Vec4d d4c{1.0, 2.7, 3.87, 8.22}; 47 | 48 | // int 49 | constexpr Vec2i i2a {1, 2}; 50 | constexpr Vec3i i3a {-1, 0, 2}; 51 | constexpr Vec4i i4a {5, -2, 12, 0}; 52 | 53 | constexpr nytl::Vec<5, int> i5a {1, 2, 3, 4, 5}; 54 | constexpr nytl::Vec<5, int> i5b {10, 20, -10, -20, 0}; 55 | constexpr nytl::Vec<7, int> i7a {1, 2, 3, 4, 5, 6, 7}; 56 | constexpr nytl::Vec<7, int> i7b {-1, 0, 0, 0, 1, 4, 5}; 57 | 58 | constexpr auto c1 = static_cast(nytl::Vec2u64{1243u, 432u}); 59 | constexpr auto c2 = static_cast(nytl::Vec4u64{1243u, 432u, 1u, 2u}); 60 | 61 | template using AddT = 62 | std::void_t() + std::declval())>; 63 | template using DotT = 64 | std::void_t(), std::declval()))>; 65 | template using CrossT = 66 | std::void_t(), std::declval()))>; 67 | 68 | static_assert(validExpression); 69 | static_assert(validExpression); 70 | static_assert(!validExpression); 71 | static_assert(!validExpression); 72 | static_assert(!validExpression); 73 | static_assert(!validExpression); 74 | 75 | TEST(deduction) { 76 | auto a = nytl::Vec {1.f, 2, 3, 4.}; 77 | static_assert(std::is_same_v); 78 | 79 | auto b = nytl::Vec {1, 2}; 80 | static_assert(std::is_same_v); 81 | 82 | auto c = nytl::Vec {1u, 2u}; 83 | static_assert(std::is_same_v); 84 | } 85 | 86 | TEST(basic) { 87 | auto cpy = d3a; 88 | cpy += d3c; 89 | cpy -= d3d; 90 | EXPECT(cpy, (Vec3d{3.0, 1.0, 2.0})); 91 | 92 | cpy = -cpy; 93 | EXPECT(cpy, (Vec3d{-3.0, -1.0, -2.0})); 94 | 95 | cpy *= -4; 96 | EXPECT(cpy, (Vec3d{12.0, 4.0, 8.0})); 97 | EXPECT(cpy != (Vec3d{12.0, 4.0, 1.0}), true); 98 | } 99 | 100 | TEST(vec_addition) { 101 | EXPECT(-d3a, nytl::approx(Vec3d{-1.0, -2.0, -3.0})); 102 | EXPECT(d3a + d3b, nytl::approx(d3a)); 103 | EXPECT(d3a - d3b, nytl::approx(d3a)); 104 | EXPECT(d3a + d3a, nytl::approx(Vec3d{2.0, 4.0, 6.0})); 105 | EXPECT(d3g + d3i, nytl::approx(Vec3d{2.0, 13.0, 6.0})); 106 | EXPECT(d3b - d3g + d3g - d3g + d3b, nytl::approx(-d3g)); 107 | EXPECT(d3f - d3f, nytl::approx(d3b)); 108 | EXPECT(d3b, nytl::approx(-d3b)); 109 | EXPECT(d2a + i2a, nytl::approx(Vec2d{2.0, 4.0})); 110 | EXPECT(d3b - i3a, nytl::approx(Vec3d{1.0, 0.0, -2.0})); 111 | EXPECT(i5a + i5b, (Vec<5, int>{11, 22, -7, -16, 5})); 112 | 113 | // - should not compile - 114 | static_assert(!validExpression); 115 | static_assert(!validExpression); 116 | static_assert(!validExpression); 117 | } 118 | 119 | TEST(scalar_mult) { 120 | EXPECT(2 * d3a, nytl::approx(d3a + d3a)); 121 | EXPECT(5 * d3b, nytl::approx(d3b)); 122 | EXPECT(-1 * d3f, nytl::approx(-d3f)); 123 | EXPECT(0 * d3e, nytl::approx(d3b)); 124 | EXPECT(0.5 * d3h, nytl::approx(Vec3f{2.0, -1.5, 1.0})); 125 | EXPECT(0.2 * d3i, nytl::approx(d3i - 0.8 * d3i)); 126 | EXPECT(2 * d3g + d3h, nytl::approx(Vec3f{8.0, 3.0, 12.0})); 127 | EXPECT(2 * i5a, (Vec<5, int>{2, 4, 6, 8, 10})); 128 | } 129 | 130 | TEST(multiplies) { 131 | EXPECT(nytl::multiply(d3a), nytl::approx(6.0)); 132 | EXPECT(nytl::multiply(d3b), nytl::approx(0.0)); 133 | EXPECT(nytl::multiply(d3c), nytl::approx(0.0)); 134 | EXPECT(nytl::multiply(d3d), nytl::approx(-1.0)); 135 | EXPECT(nytl::multiply(d3e), nytl::approx(-0.0001)); 136 | EXPECT(nytl::multiply(d3f), nytl::approx(-200000.0)); 137 | EXPECT(static_cast(nytl::multiply(i7a)), nytl::factorial(7)); 138 | } 139 | 140 | TEST(sums) { 141 | EXPECT(nytl::sum(d3a), nytl::approx(6.0)); 142 | EXPECT(nytl::sum(d3b), nytl::approx(0.0)); 143 | EXPECT(nytl::sum(d3c), nytl::approx(1.0)); 144 | EXPECT(nytl::sum(d3d), nytl::approx(1.0)); 145 | EXPECT(nytl::sum(d3e), nytl::approx(0.0001)); 146 | EXPECT(nytl::sum(d3f), nytl::approx(596.0)); 147 | EXPECT(nytl::sum(i7a), 1 + 2 + 3 + 4 + 5 + 6 + 7); 148 | } 149 | 150 | TEST(dot) { 151 | EXPECT(nytl::dot(d3a, d3b), nytl::approx(0.0)); 152 | EXPECT(nytl::dot(d3a, d3c), nytl::approx(1.0)); 153 | EXPECT(nytl::dot(d3a, d3d), nytl::approx(4.0)); 154 | EXPECT(nytl::dot(d3d, d3a), nytl::approx(4.0)); 155 | EXPECT(nytl::dot(d3g, d3a), nytl::approx(23.0)); 156 | EXPECT(nytl::dot(d3g, d3h), nytl::approx(9.0)); 157 | EXPECT(nytl::dot(d3i, d3g), nytl::approx(35.0)); 158 | EXPECT(nytl::dot(d3g, d3f), nytl::approx(1680.0)); 159 | EXPECT(nytl::dot(d3h, d3d), nytl::approx(-5.0)); 160 | 161 | // - should not compile - 162 | static_assert(!validExpression); 163 | } 164 | 165 | TEST(length) { 166 | EXPECT(nytl::length(d3b), nytl::approx(0.0)); 167 | EXPECT(nytl::length(d3a), nytl::approx(std::sqrt(14.0))); 168 | EXPECT(nytl::length(d3f), nytl::approx(nytl::length(-d3f))); 169 | EXPECT(nytl::length(2 * d3a), nytl::approx(2 * nytl::length(d3a))); 170 | EXPECT(nytl::length(1232 * d3a), nytl::approx(1232 * nytl::length(d3a))); 171 | EXPECT(nytl::length(-5 * d3a), nytl::approx(5.0 * nytl::length(d3a))); 172 | EXPECT(nytl::length(d3b), nytl::approx(0.0)); 173 | EXPECT(nytl::length(d3c), nytl::approx(1.0)); 174 | EXPECT(nytl::length(d3g), nytl::approx(std::sqrt(38.0))); 175 | EXPECT(nytl::length(d3h), nytl::approx(std::sqrt(29.0))); 176 | EXPECT(nytl::length(d3i), nytl::approx(std::sqrt(nytl::dot(d3i, d3i)))); 177 | EXPECT(nytl::length(d3g - d3a), nytl::approx(std::sqrt(6.0))); 178 | EXPECT(nytl::length(1.5 * (d3a + d3b + d3c)), nytl::approx(1.5 * std::sqrt(17.0))); 179 | EXPECT(nytl::length(Vec3i{1, 2, 3}), nytl::approx(std::sqrt(14.0))); 180 | } 181 | 182 | TEST(angles) { 183 | // custom angle vectors 184 | constexpr Vec2d a {1.0, 0.0}; 185 | constexpr Vec2d b {0.0, 1.0}; 186 | constexpr Vec2i c {1, 1}; 187 | 188 | EXPECT(nytl::angle(a, b), nytl::approx(nytl::radians(90.0))); 189 | EXPECT(nytl::angle(b, a), nytl::approx(nytl::constants::pi / 2)); 190 | EXPECT(nytl::angle(a, c), nytl::approx(nytl::radians(45.0))); 191 | EXPECT(nytl::angle(c, b), nytl::approx(nytl::constants::pi / 4)); 192 | 193 | constexpr Vec3f d {1.0, 0.0, -1.0}; 194 | constexpr Vec3i e {1, 0, 0}; 195 | constexpr Vec3ui f {0u, 1u, 0u}; 196 | 197 | EXPECT(nytl::angle(d, e), nytl::approx(nytl::constants::pi / 4)); 198 | EXPECT(nytl::angle(f, e), nytl::approx(nytl::radians(90.0))); 199 | EXPECT(nytl::angle(e, f), nytl::approx(nytl::constants::pi / 2)); 200 | 201 | EXPECT(nytl::angle(d3g, d3g), nytl::approx(0.0)); 202 | EXPECT(nytl::angle(d3g, d3h), nytl::approx(1.296246288593885243)); 203 | EXPECT(nytl::angle(d2a, d2b), nytl::approx(0.46364760900080614903)); 204 | EXPECT(nytl::angle(i5a, i5b), nytl::approx(1.8295137377985963845)); 205 | 206 | // - should not compile - TODO 207 | // nytl::angle(a3, a2); 208 | // nytl::angle(b2, c3); 209 | } 210 | 211 | TEST(distances) { 212 | EXPECT(nytl::distance(d3a, d3b), nytl::approx(nytl::length(d3a))); 213 | EXPECT(nytl::distance(d3f, d3f), nytl::approx(0.0)); 214 | EXPECT(nytl::distance(d3g, d3h), nytl::approx(nytl::length(d3g - d3h))); 215 | EXPECT(nytl::distance(d3h, d3g), nytl::approx(nytl::length(d3g - d3h))); 216 | } 217 | 218 | TEST(cross_product) { 219 | EXPECT(nytl::cross(d3a, d3b), nytl::approx(Vec3d{0.0, 0.0, 0.0})); 220 | EXPECT(nytl::cross(d3a, d3c), nytl::approx(Vec3d{0.0, 3.0, -2.0})); 221 | EXPECT(nytl::cross(d3c, d3a), nytl::approx(Vec3d{0.0, -3.0, 2.0})); 222 | EXPECT(nytl::cross(d3g, d3g), nytl::approx(Vec3d{0.0, 0.0, 0.0})); 223 | EXPECT(nytl::cross(d3f, d3h), nytl::approx(-nytl::cross(d3h, d3f))); 224 | EXPECT(nytl::cross(d3g, d3h), nytl::approx(Vec3d{21.0, 16.0, -18.0})); 225 | 226 | // - should not compile - TODO 227 | // nytl::cross(d3a, Vec2d{1.0, 2.0}); 228 | // nytl::cross(Vec2d{2.0, 3.0}, Vec2d{1.0, 2.0}); 229 | // nytl::cross(Vec4d{1.0, 2.0, 3.0, 4.0}, d3b); 230 | } 231 | 232 | TEST(component_wise) { 233 | using namespace nytl::vec::cw; 234 | 235 | // abs 236 | EXPECT(abs(d3a), nytl::approx(d3a)); 237 | EXPECT(abs(-d3a), nytl::approx(d3a)); 238 | 239 | auto v1 = nytl::Vec {-2.f, -10000.f}; 240 | EXPECT(abs(v1), nytl::approx(-v1)); 241 | EXPECT(abs(-v1), nytl::approx(-v1)); 242 | 243 | // clamp 244 | EXPECT(clamp(v1, -1.f, 1.f), (nytl::Vec {-1.f, -1.f})); 245 | EXPECT(clamp(d3a, 1., 1.), (nytl::Vec {1.f, 1.f, 1.f})); 246 | EXPECT(clamp(d2a, d2a, d2a), d2a); 247 | EXPECT(clamp(i5b, i5b, i5b), i5b); 248 | 249 | // pow 250 | EXPECT(pow(d3f, 1), d3f); 251 | } 252 | -------------------------------------------------------------------------------- /nytl/recursiveCallback.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Defines the RecursiveCallback template class. 6 | 7 | #pragma once 8 | 9 | #ifndef NYTL_INCLUDE_RECURSIVE_CALLBACK 10 | #define NYTL_INCLUDE_RECURSIVE_CALLBACK 11 | 12 | #include // nytl::BasicConnection 13 | #include // nytl::NonCopyable 14 | #include // nytl::ScopeGuard 15 | 16 | #include // std::function 17 | #include // std::forward_list 18 | #include // std::move 19 | #include // std::uint64_t 20 | #include // std::size_t 21 | #include // std::is_same 22 | #include // std::vector 23 | #include // std::numeric_limits 24 | #include // std::cerr 25 | #include // std::logic_error 26 | 27 | namespace nytl { 28 | 29 | // TODO (C++17): use std::pmr for more efficient memory allocations (?) 30 | 31 | /// List of callback functions. 32 | /// Everyone can register their functions in a callback object and 33 | /// then all registered functions together might be called. 34 | /// The special part about RecursiveCallback is that it is written for 35 | /// recursive scenarios, e.g. where the callback is called or accessed inside a 36 | /// function that was called because the callback was called. 37 | /// So the callback might be accessed from inside such a function. 38 | /// If you don't need this functionality, just see nytl::Callback. 39 | /// The public interface of this class is identical with nytl::Callback 40 | /// so they can be used interchangeably. 41 | /// The class is not thread-safe in any way. 42 | /// All exceptions from calls are just propagated. 43 | /// The class can not be copied or moved. 44 | /// 45 | /// \tparam Signature The signature of the registered functions. 46 | /// Uses the same syntax and semantics as std::function. 47 | /// \tparam ID A connectionID class, see nytl/connection.hpp for examples. 48 | /// See docs/callback.md for specification. 49 | template 50 | class RecursiveCallback; 51 | 52 | /// Callback class typedef using TrackedConnectionID. Enables connections 53 | /// to see when their associated function is unregistered by another 54 | /// connection or because the callback was destroyed. 55 | template using TrackedRecursiveCallback = 56 | RecursiveCallback; 57 | 58 | // Callback specialization to enable the Ret(Args...) Signature format. 59 | template 60 | class RecursiveCallback 61 | : public ConnectableT, public NonCopyable { 62 | public: 63 | using Signature = Ret(Args...); 64 | using Connection = ConnectionT, ID>; 65 | 66 | RecursiveCallback() = default; 67 | ~RecursiveCallback(); 68 | 69 | /// \brief Registers a new Callback function. 70 | /// \returns A connection id for the registered function which can be used to 71 | /// unregister it. 72 | /// \throws std::invalid_argument If an empty function target is registered. 73 | Connection add(std::function); 74 | 75 | /// \brief Registers a new Callback function with additional connection parameter. 76 | /// \returns A connection id for the registered function which can be used to 77 | /// unregister it. 78 | /// \throws std::invalid_argument If an empty function target is registered. 79 | Connection add(std::function); 80 | 81 | /// Calls all registered functions and returns a vector with the returned objects, 82 | /// or void when this is a void callback. 83 | /// Will call all the functions registered at the moment of calling, i.e. additional 84 | /// functions added from within are not called. 85 | /// Propagates all upcoming exceptions untouched. 86 | /// Note that this might also propagate exceptions from ID::remove() that are defered 87 | /// from disconnect. 88 | auto call(Args...); 89 | 90 | /// Clears all registered functions. 91 | void clear() noexcept; 92 | 93 | /// Removes the callback function registered with the given id. 94 | /// Returns whether the function could be found. If the id is invalid or the 95 | /// associated function was already removed, returns false. 96 | /// ID::remove() might be defered until all pending iterations (calls/removes) 97 | /// are done. 98 | /// Passing an invalid id or an id that was not returned from this 99 | /// callback is undefined behvaiour. 100 | bool disconnect(const ID&) noexcept override; 101 | 102 | /// Operator version of add 103 | template 104 | Connection operator+=(F&& func) { 105 | return add(std::forward(func)); 106 | } 107 | 108 | /// Operator version of add that previously calls clear. 109 | template 110 | Connection operator=(F&& func) { 111 | clear(); 112 | return add(std::forward(func)); 113 | } 114 | 115 | /// Operator version of call. 116 | auto operator() (Args... a) { 117 | return call(std::forward(a)...); 118 | } 119 | 120 | protected: 121 | // Represents one callback subscription entry. 122 | // Invalid (formally removed) when id is not valid. 123 | // Note that these means that alternative ID classes can 124 | // remove subscriptions from the outside without actively 125 | // calling disconnect. 126 | // We cannot touch func while any iteration is active. 127 | // If the 128 | struct Subscription { 129 | std::function func; 130 | ID id; 131 | }; 132 | 133 | // Emplaces a new subscription for the given function. 134 | Subscription& emplace() { 135 | // output at least a warning when subID_ has to be wrapped 136 | // Usually this should not happen. Bad things can happend then. 137 | if(subID_ == std::numeric_limits::max()) { 138 | std::cerr << "nytl::RecursiveCallback::emplace: wrapping subID_\n"; 139 | subID_ = 0; 140 | } 141 | 142 | // this expression might throw. In this case we have not changed 143 | // our own state in any bad way 144 | ID id = {subID_ + 1}; 145 | 146 | // emplace at the last position 147 | // might also throw 148 | if(subs_.empty()) { 149 | subs_.emplace_front(); 150 | last_ = subs_.begin(); 151 | } else { 152 | last_ = subs_.emplace_after(last_); 153 | } 154 | 155 | ++subID_; 156 | last_->id = {id}; 157 | return *last_; 158 | } 159 | 160 | // Removes all old functions that could previously 161 | // not be removed because of an active iteration. 162 | void removeOld() noexcept { 163 | ++iterationCount_; 164 | 165 | // just remove subscriptions with id <= 0 and update last_ 166 | auto it = subs_.begin(); 167 | while(it != subs_.end() && it->id.get() <= 0) { 168 | subs_.pop_front(); 169 | it = subs_.begin(); 170 | } 171 | 172 | if(it != subs_.end()) { 173 | for(auto next = std::next(it); next != subs_.end(); next = std::next(it)) { 174 | if(next->id.get() <= 0) { 175 | next->id.removed(); 176 | subs_.erase_after(it); 177 | } else { 178 | ++it; 179 | } 180 | } 181 | } 182 | 183 | last_ = it; 184 | --iterationCount_; 185 | } 186 | 187 | // TODO: maybe cache current size for performance? 188 | 189 | // Upper approximation of the current size. 190 | // May be larger than the actual size. 191 | std::size_t size() const { 192 | return std::distance(subs_.begin(), subs_.end()); 193 | } 194 | 195 | std::forward_list subs_ {}; // all registered subscriptions 196 | typename decltype(subs_)::iterator last_ {}; // the last entry in subs 197 | unsigned int iterationCount_ {}; // the number of active iterations (in call) 198 | std::int64_t subID_ {}; // the highest subscription id given 199 | std::int64_t callID_ {}; // the highest call id given (see the call function) 200 | }; 201 | 202 | // - implementation - 203 | template 204 | RecursiveCallback::~RecursiveCallback() 205 | { 206 | // Output warnings in bad cases. 207 | // The following can only happen if e.g. deleted from within a 208 | // call 209 | if(iterationCount_) { 210 | std::cerr << "nytl::~RecursiveCallback: iterationCount_: " << iterationCount_ << "\n"; 211 | } 212 | 213 | for(auto& sub : subs_) { 214 | sub.id.removed(); 215 | } 216 | } 217 | 218 | template 219 | ConnectionT, ID> RecursiveCallback:: 220 | add(std::function func) { 221 | if(!func) { 222 | throw std::invalid_argument("nytl::Callback::add: empty function"); 223 | } 224 | 225 | auto& sub = emplace(); 226 | sub.func = std::move(func); 227 | return {*this, sub.id}; 228 | } 229 | 230 | template 231 | ConnectionT, ID> RecursiveCallback:: 232 | add(std::function func) 233 | { 234 | if(!func) { 235 | throw std::invalid_argument("nytl::Callback::add: empty function"); 236 | } 237 | 238 | auto& sub = emplace(); 239 | Connection conn {*this, sub.id}; 240 | sub.func = [conn, f = std::move(func)](Args... args) { 241 | return f(conn, std::forward(args)...); 242 | }; 243 | 244 | return conn; 245 | } 246 | 247 | template 248 | auto RecursiveCallback::call(Args... a) 249 | { 250 | // wrap callID_ if needed. This is usually not critical (except when 251 | // there are 2^32 nested calls...) 252 | callID_ = (callID_ == std::numeric_limits::max()) ? 1 : callID_ + 1; 253 | std::int64_t callid = callID_; // the actual calling id (to include newly removed) 254 | auto last = last_; // store it (to not iterate over newly added subs) 255 | 256 | // make sure the no list iterators are invalidated while iterating 257 | ++iterationCount_; 258 | 259 | // make sure the iteration count and cleanup done if possible 260 | // even in the case of an exception. 261 | auto successGuard = ScopeGuard([&]{ 262 | if(--iterationCount_ == 0) { 263 | removeOld(); 264 | } 265 | }); 266 | 267 | if constexpr(std::is_same::value) { 268 | for(auto it = subs_.begin(); it != subs_.end(); ++it) { 269 | // if the first check if fasel, -it->id.get() represents the 270 | // callID during which it was removed. If this is >= than 271 | // the stored callID, it was removed during or after this 272 | // call and therefore we still call it 273 | if(it->id.get() > 0 || -it->id.get() >= callid) { 274 | it->func(std::forward(a)...); 275 | // we will not call functions that were registered after 276 | // this call started (this is why we store last above) 277 | if(it == last) { 278 | break; 279 | } 280 | } 281 | } 282 | 283 | } else { // the same with a return vector 284 | std::vector ret; 285 | ret.reserve(size()); 286 | 287 | for(auto it = subs_.begin(); it != subs_.end(); ++it) { 288 | if(it->id.get() > 0 || -it->id.get() >= callid) { 289 | ret.push_back(it->func(std::forward(a)...)); 290 | if(it == last) { 291 | break; 292 | } 293 | } 294 | } 295 | 296 | return ret; 297 | } 298 | } 299 | 300 | template 301 | void RecursiveCallback::clear() noexcept 302 | { 303 | bool remove = iterationCount_ == 0; 304 | 305 | // reset the ids or notify the ids of removal 306 | for(auto& sub : subs_) { 307 | if(remove) { 308 | sub.id.removed(); 309 | } else { 310 | sub.id.set(-callID_); 311 | } 312 | } 313 | 314 | // clear/remove only if no one is currently iterating 315 | // we only reset end_ (not really needed, cleanup for easier debugging) 316 | if(remove) { 317 | subs_.clear(); 318 | last_ = subs_.end(); 319 | } 320 | } 321 | 322 | // TODO: noexcept? 323 | template 324 | bool RecursiveCallback::disconnect(const ID& id) noexcept 325 | { 326 | if(subs_.empty()) { 327 | return false; 328 | } 329 | 330 | auto remove = (iterationCount_ == 0); 331 | 332 | // check for first one 333 | if(subs_.begin()->id.get() == id.get()) { 334 | if(remove) { 335 | subs_.begin()->id.removed(); 336 | subs_.pop_front(); 337 | if(subs_.empty()) { 338 | last_ = subs_.end(); 339 | } 340 | } else { 341 | subs_.begin()->id.set(-callID_); 342 | } 343 | 344 | return true; 345 | } 346 | 347 | // iterate through subs, always check the next elem (fwd linked list) 348 | // the end condition constructs a copy of it and increases it to check 349 | for(auto it = subs_.begin(); true; ++it) { 350 | auto next = it; 351 | if(++next == subs_.end()) 352 | break; 353 | 354 | if(next->id.get() == id.get()) { 355 | if(remove) { 356 | // set this before id.removed might change stuff 357 | if(++next == subs_.end()) { 358 | last_ = it; 359 | } 360 | next->id.removed(); 361 | subs_.erase_after(it); 362 | } else { 363 | next->id.set(-callID_); 364 | } 365 | 366 | return true; 367 | } 368 | } 369 | 370 | return false; 371 | } 372 | 373 | } // namespace nytl 374 | 375 | #endif // header guard 376 | -------------------------------------------------------------------------------- /docs/tests/bugged.hpp: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | // 3 | // Anyone is free to copy, modify, publish, use, compile, sell, or 4 | // distribute this software, either in source code form or as a compiled 5 | // binary, for any purpose, commercial or non-commercial, and by any 6 | // means. 7 | // 8 | // In jurisdictions that recognize copyright laws, the author or authors 9 | // of this software dedicate any and all copyright interest in the 10 | // software to the public domain. We make this dedication for the benefit 11 | // of the public at large and to the detriment of our heirs and 12 | // successors. We intend this dedication to be an overt act of 13 | // relinquishment in perpetuity of all present and future rights to this 14 | // software under copyright law. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | // OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | // For more information, please refer to 25 | 26 | // Written by nyorain. 27 | // For issues and (appreciated) help, see https://github.com/nyorain/bugged. 28 | // For more information and usage, see example.cpp or README.md 29 | // Extremely lightweight header-only unit testing for C++. 30 | // Use the TEST(name), EXPECT(expression, expected) and ERROR(expression, error) macros. 31 | // Configuration useful for inline testing (define before including the file): 32 | // - BUGGED_NO_MAIN: don't include the main function that just executes all tests. 33 | // - BUGGED_NO_IMPL: don't include the implementation. 34 | 35 | #pragma once 36 | 37 | #include // std::string 38 | #include // std::strrchr 39 | #include // std::type_traits 40 | #include // std::vector 41 | #include // std::cout 42 | #include // std::stringstream 43 | #include // std::exception 44 | 45 | namespace bugged { 46 | 47 | // Utility 48 | namespace { 49 | template constexpr void unused(T&&...) {} 50 | template using void_t = void; 51 | } 52 | 53 | /// Class used for printing objects to the debug output. 54 | /// Provided implementations just return the object when it is printable 55 | /// a string with type and address of the object. 56 | /// The call operations retrieves something from the object that can be printed 57 | /// ot an ostream (the object itself or an alternative descriptive string). 58 | /// \tparam T The type of objects to print. 59 | template 60 | struct Printable { 61 | static std::string call(const T&) 62 | { 63 | static auto txt = std::string(""); 64 | return txt; 65 | } 66 | }; 67 | 68 | /// Default printable specialization for types that can itself 69 | template 70 | struct Printable() << std::declval())>> { 71 | static const T& call(const T& obj) { return obj; } 72 | }; 73 | 74 | /// Uses the Printable template to retrieve something printable to an ostream 75 | /// from the given object. 76 | template 77 | auto printable(const T& obj) -> decltype(Printable::call(obj)) 78 | { return Printable::call(obj); } 79 | 80 | /// Strips the path from the given filename 81 | const char* stripPath(const char* path) 82 | { 83 | auto pos = std::strrchr(path, '/'); 84 | return pos ? pos + 1 : path; 85 | } 86 | 87 | /// Static class that holds all test to be run. 88 | class Testing { 89 | public: 90 | /// Holds all the available info for a failed test. 91 | struct FailInfo { 92 | int line; 93 | const char* file; 94 | }; 95 | 96 | /// Represents a unit to test. 97 | struct Unit { 98 | using FuncPtr = void(*)(); 99 | std::string name; 100 | FuncPtr func; 101 | const char* file; 102 | unsigned int line; 103 | }; 104 | 105 | public: 106 | // Config variables 107 | // the ostream to output to. Must not be set to nullptr. Defaulted to std::cout 108 | static std::ostream* output; 109 | 110 | // The width of the failure separator. Defaulted to 70. 111 | static unsigned int separationWidth; 112 | 113 | // The char to use in the failure separator line. Defaulted to '-' 114 | static char failSeparator; 115 | 116 | // The char to use in the bottom separator line. Defaulted to '=' 117 | static char bottomSeparator; 118 | 119 | // The escape sequences to use to style the output. 120 | // Will all be empty if not on unix. 121 | struct Escape { 122 | static const char* testName; 123 | static const char* checkExpected; 124 | static const char* checkActual; 125 | static const char* errorExpected; 126 | static const char* exception; 127 | static const char* source; 128 | static const char* reset; 129 | }; 130 | 131 | public: 132 | /// Called when a check expect fails. 133 | /// Will increase the current fail count and print a debug message for 134 | /// the given information. 135 | /// Should be only called inside of a testing unit. 136 | template 137 | static inline void expectFailed(const FailInfo&, const V& value, const E& expected); 138 | 139 | /// Called when a check error fails. 140 | /// Should be only called inside of a testing unit. 141 | /// \param error The name of the expected error type 142 | /// \param other empty if there was no other error, the type and message of the 143 | /// other error thrown otherwise. 144 | static inline void errorFailed(const FailInfo&, const char* error, const std::string& other); 145 | 146 | /// Adds the given unit to the list of units to test. 147 | /// Always returns 0, useful for static calling. 148 | static inline int add(const Unit&); 149 | 150 | /// Tests all registered units. 151 | /// Returns the number of units failed. 152 | static inline unsigned int run(); 153 | 154 | /// Outputs a separation line. 155 | static void separationLine(bool beginning); 156 | 157 | protected: 158 | /// Returns a string for the given number of failed tests. 159 | static inline std::string failString(unsigned int failCount, const char* type); 160 | 161 | /// Prints the error for an unexpected exception 162 | static inline void unexpectedException(const std::string& errorString); 163 | 164 | static std::vector units; 165 | static unsigned int currentFailed; 166 | static unsigned int totalFailed; 167 | static unsigned int unitsFailed; 168 | static Unit* currentUnit; 169 | }; 170 | 171 | } // namespace bugged 172 | 173 | /// Declares a new testing unit. After this macro the function body should follow like this: 174 | /// ``` TEST(SampleTest) { EXPECT(1 + 1, 2); } ``` 175 | #define TEST(name) \ 176 | static void BUGGED_##name##_U(); \ 177 | namespace { static auto BUGGED_##name = ::bugged::Testing::add({#name, BUGGED_##name##_U, \ 178 | ::bugged::stripPath(__FILE__), __LINE__}); } \ 179 | static void BUGGED_##name##_U() 180 | 181 | /// Expects the two given values to be equal. 182 | #define EXPECT(expr, expected) \ 183 | { ::bugged::checkExpect({__LINE__, ::bugged::stripPath(__FILE__)}, expr, expected); } 184 | 185 | /// Expects the given expression to throw an error of the given type when 186 | /// evaluated. 187 | #define ERROR(expr, error) { \ 188 | std::string TEST_altMsg {}; \ 189 | if(!::bugged::detail::ErrorTest::call([&]{ expr; }, TEST_altMsg)) \ 190 | ::bugged::Testing::errorFailed({__LINE__, ::bugged::stripPath(__FILE__)}, \ 191 | #error, TEST_altMsg.c_str()); \ 192 | } 193 | 194 | // Implementation 195 | #ifndef BUGGED_NO_IMPL 196 | 197 | namespace bugged { 198 | 199 | unsigned int Testing::separationWidth = 55; 200 | char Testing::failSeparator = '-'; 201 | char Testing::bottomSeparator = '='; 202 | 203 | #if defined(__unix__) || defined(__linux__) || defined(__APPLE__) 204 | const char* Testing::Escape::testName = "\033[33m"; 205 | const char* Testing::Escape::checkExpected = "\033[32m"; 206 | const char* Testing::Escape::checkActual = "\033[31m"; 207 | const char* Testing::Escape::errorExpected = "\033[32m"; 208 | const char* Testing::Escape::exception = "\033[31m"; 209 | const char* Testing::Escape::source = "\033[36m"; 210 | const char* Testing::Escape::reset = "\033[0m"; 211 | #else 212 | const char* Testing::Escape::testName = ""; 213 | const char* Testing::Escape::checkExpected = ""; 214 | const char* Testing::Escape::checkActual = ""; 215 | const char* Testing::Escape::errorExpected = ""; 216 | const char* Testing::Escape::exception = ""; 217 | const char* Testing::Escape::source = ""; 218 | const char* Testing::Escape::reset = ""; 219 | #endif 220 | 221 | 222 | std::vector Testing::units {}; 223 | unsigned int Testing::currentFailed {}; 224 | unsigned int Testing::totalFailed {}; 225 | unsigned int Testing::unitsFailed {}; 226 | Testing::Unit* Testing::currentUnit {}; 227 | std::ostream* Testing::output = &std::cout; 228 | 229 | // Utility method used by EXPECT to assure the given expressions are evaluated 230 | // exactly once 231 | template 232 | void checkExpect(const Testing::FailInfo& info, const V& value, const E& expected) 233 | { 234 | if(value != expected) 235 | Testing::expectFailed(info, value, expected); 236 | } 237 | 238 | void Testing::separationLine(bool beginning) 239 | { 240 | if(beginning && !totalFailed && !currentFailed && !unitsFailed) 241 | return; 242 | 243 | for(auto i = 0u; i < separationWidth; ++i) 244 | std::cout << failSeparator; 245 | 246 | std::cout << "\n"; 247 | } 248 | 249 | template 250 | void Testing::expectFailed(const FailInfo& info, const V& value, const E& expected) 251 | { 252 | separationLine(true); 253 | 254 | std::cout << "[" << Escape::source << info.file << ":" << info.line 255 | << Escape::reset << " | " << Escape::testName << currentUnit->name 256 | << Escape::reset << "] Check expect failed:\nGot: '" 257 | << Escape::checkActual << printable(value) 258 | << Escape::reset << "' instead of '" << Escape::checkExpected << printable(expected) 259 | << Escape::reset << "'\n"; 260 | 261 | ++currentFailed; 262 | } 263 | 264 | void Testing::errorFailed(const FailInfo& info, const char* error, const std::string& other) 265 | { 266 | separationLine(true); 267 | 268 | std::cout << "[" << Escape::source << info.file << ":" << info.line 269 | << Escape::reset << " | " << Escape::testName << currentUnit->name 270 | << Escape::reset << "] Check error failed:\n" 271 | << "Expected '" << Escape::errorExpected << error << Escape::reset << "', "; 272 | 273 | if(!other.empty()) { 274 | std::cout << "got other error: \n" 275 | << Escape::exception << other << Escape::reset << "\n"; 276 | } else { 277 | std::cout << "no error was thrown\n"; 278 | } 279 | 280 | ++currentFailed; 281 | } 282 | 283 | void Testing::unexpectedException(const std::string& errorString) 284 | { 285 | separationLine(true); 286 | 287 | std::cout << "[" << Escape::source << currentUnit->file << ":" << currentUnit->line 288 | << Escape::reset << " | " << Escape::testName << currentUnit->name 289 | << Escape::reset << "] Unexpected exception:\n" 290 | << Escape::exception << errorString << Escape::reset << "\n"; 291 | } 292 | 293 | 294 | namespace detail { 295 | 296 | /// Tries to catch an error of the given type in the given function. 297 | /// Specialization needed to surpress warnings when E == std::exception. 298 | template 299 | struct ErrorTest { 300 | template 301 | static bool call(const F& func, std::string& msg) 302 | { 303 | try { 304 | func(); 305 | } catch(const E&) { 306 | return true; 307 | } catch(const std::exception& err) { 308 | msg = "std::exception: "; 309 | msg += err.what(); 310 | } catch(...) { 311 | msg = ""; 312 | } 313 | 314 | return false; 315 | } 316 | }; 317 | 318 | template<> 319 | struct ErrorTest { 320 | template 321 | static bool call(const F& func, std::string& msg) 322 | { 323 | try { 324 | func(); 325 | } catch(const std::exception&) { 326 | return true; 327 | } catch(...) { 328 | msg = ""; 329 | } 330 | 331 | return false; 332 | } 333 | }; 334 | 335 | } // namespace detail 336 | 337 | int Testing::add(const Unit& unit) 338 | { 339 | units.push_back(unit); 340 | return 0; 341 | } 342 | 343 | unsigned int Testing::run() 344 | { 345 | for(auto unit : units) { 346 | currentFailed = 0; 347 | 348 | currentUnit = &unit; 349 | auto thrown = false; 350 | 351 | try { 352 | unit.func(); 353 | } catch(const std::exception& exception) { 354 | thrown = true; 355 | unexpectedException(std::string("std::exception: ") + exception.what()); 356 | } catch(...) { 357 | thrown = true; 358 | unexpectedException(""); 359 | } 360 | 361 | if(thrown || currentFailed) 362 | ++unitsFailed; 363 | totalFailed += currentFailed; 364 | } 365 | 366 | if(totalFailed) { 367 | for(auto i = 0u; i < separationWidth; ++i) 368 | *output << bottomSeparator; 369 | *output << "\n"; 370 | } 371 | 372 | *output << failString(unitsFailed, "unit") << ", " 373 | << failString(totalFailed, "call") << "\n"; 374 | return unitsFailed; 375 | } 376 | 377 | std::string Testing::failString(unsigned int failCount, const char* type) 378 | { 379 | if(failCount == 0) { 380 | return std::string("All ").append(type).append("s succeeded"); 381 | } else if(failCount == 1) { 382 | return std::string("1 ").append(type).append(" failed"); 383 | } else { 384 | return std::to_string(failCount).append(" ").append(type).append("s failed"); 385 | } 386 | } 387 | 388 | } // namespace bugged 389 | 390 | #endif // BUGGED_NO_IMPL 391 | 392 | // Main function 393 | #ifndef BUGGED_NO_MAIN 394 | int main() { return bugged::Testing::run(); } 395 | #endif // BUGGED_NO_MAIN 396 | -------------------------------------------------------------------------------- /nytl/vecOps.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | /// \file Various operations for real vectors. 6 | /// Over the whole files, template types names V or V* must fulfil the 7 | /// vector concept specified in doc/vec.md 8 | 9 | #pragma once 10 | 11 | #ifndef NYTL_INCLUDE_VEC_OPS 12 | #define NYTL_INCLUDE_VEC_OPS 13 | 14 | #include 15 | #include // nytl::templatize 16 | #include // nytl::accumulate 17 | 18 | #include // std::acos 19 | #include // std::ostream 20 | #include // std::clamp 21 | 22 | namespace nytl { 23 | 24 | /// Sums up all values of the given vector using the + operator. 25 | template 26 | constexpr auto sum(const Vec& a) { 27 | decltype(a[0] + a[1]) ret {0}; 28 | for(auto i = 0u; i < D; ++i) 29 | ret += a[i]; 30 | return ret; 31 | } 32 | 33 | /// Multiplies all values of the given vector using the * operator. 34 | template 35 | constexpr auto multiply(const Vec& a) { 36 | decltype(a[0] * a[1]) ret {1}; 37 | for(auto i = 0u; i < D; ++i) 38 | ret *= a[i]; 39 | return ret; 40 | } 41 | 42 | /// Calculates the default (real) dot product for the given vectors. 43 | /// Note that this follows the dot definition for real numbers and does 44 | /// not automatically handle the dot definition for other structures. 45 | template 46 | constexpr auto dot(const Vec& a, const Vec& b) { 47 | decltype(a[0] * b[0] + a[0] * b[0]) ret {0}; 48 | for(auto i = 0u; i < D; ++i) 49 | ret += a[i] * b[i]; 50 | 51 | return ret; 52 | } 53 | 54 | /// Returns the euclidean norm (or length) of the given vector. 55 | template 56 | constexpr auto length(const Vec& a) { 57 | return std::sqrt(dot(a, a)); 58 | } 59 | 60 | /// Returns the euclidean distance between two vectors. 61 | /// Another way to describe this operation is the length between the 62 | /// difference of the given vectors. 63 | template 64 | constexpr auto distance(const Vec& a, const Vec& b) { 65 | return length(a - b); 66 | } 67 | 68 | /// Calculates the angle in radians between two vectors using the dot product. 69 | /// Therefore it will always return the smaller angle between the both vectors 70 | /// on a plane in which both vectors lay. 71 | /// For two equal vectors, it will return always 0. 72 | /// Does only work for real numbers and does not handle complex vectors. 73 | /// Undefined if either vector is the nullvector. 74 | template 75 | constexpr auto angle(const Vec& a, const Vec& b) { 76 | auto l = length(a) * length(b); 77 | 78 | // we do the clamp to work against rounding errors 79 | // (dot(a, b) / l) cannot return anything out of range [-1, 1] 80 | auto v = dot(a, b) / l; 81 | return std::acos(std::clamp(v, -1.0, 1.0)); 82 | } 83 | 84 | /// Computes the angle between two normalized vectors. 85 | template 86 | constexpr auto angleNormed(const Vec& a, const Vec& b) { 87 | auto v = dot(a, b); 88 | return std::acos(std::clamp(v, -1.0, 1.0)); 89 | } 90 | 91 | /// Returns a normalization of the given vector for the euclidean norm. 92 | /// Undefined if the given vector is the nullvector. 93 | template 94 | constexpr auto normalized(const Vec& a) { 95 | auto l = length(a); 96 | return (T{1.0} / l) * a; 97 | } 98 | 99 | /// Normalizes the given vector in place. Note that this may 100 | /// not work out as expected if its value type does not have the needed 101 | /// precision (e.g. for an int vector). 102 | /// Undefined if the given vector is the nullvector. 103 | template 104 | constexpr void normalize(Vec& a) { 105 | auto l = length(a); 106 | a *= T{1.0} / l; 107 | } 108 | 109 | /// Mirrors the given point on the given mirror. 110 | template 111 | constexpr Vec mirror(const Vec& mirror, const Vec& point) { 112 | return mirror + (mirror - point); 113 | } 114 | 115 | /// Prints the given vector to the given ostream. 116 | /// If this function is used, header must be included. 117 | /// This function does not implement operator<< since this operator should only implemented 118 | /// for the Vector implementation types. 119 | /// \requires There must be an implementation of operator<<(std::ostream&, V::Value). 120 | template 121 | std::ostream& print(std::ostream& os, const V& vec, 122 | const char* start = "(", const char* end = ")", const char* sep = ", ") { 123 | 124 | auto& tos = templatize(os); // we don't want to include ostream 125 | tos << start; 126 | 127 | auto it = vec.begin(); 128 | tos << *it; 129 | while(++it != vec.end()) 130 | tos << sep << *it; 131 | 132 | tos << end; 133 | return os; 134 | } 135 | 136 | template 137 | std::ostream& operator<<(std::ostream& os, const Vec& a) { 138 | return print(os, a); 139 | } 140 | 141 | // - dimension specific - 142 | /// Calculates the cross product for two 3-dimensional vectors. 143 | template 144 | constexpr auto cross(const Vec<3, T1>& a, const Vec<3, T2>& b) { 145 | Vec<3, decltype(a[1] * b[2] - a[2] * b[1])> ret {}; 146 | ret[0] = (a[1] * b[2]) - (a[2] * b[1]); 147 | ret[1] = (a[2] * b[0]) - (a[0] * b[2]); 148 | ret[2] = (a[0] * b[1]) - (a[1] * b[0]); 149 | return ret; 150 | } 151 | 152 | /// 2-dimensional cross product (aka normal-dot). 153 | /// Is the same as the dot of a with the normal of b. 154 | template 155 | constexpr auto cross(const Vec<2, T1>& a, const Vec<2, T2>& b) { 156 | return a[0] * b[1] - a[1] * b[0]; 157 | } 158 | 159 | /// Operations that assume a right-hand orientad coordinate system. 160 | namespace rho { 161 | 162 | /// Returns the left normal of a 2 dimensional vector. 163 | template 164 | [[deprecated]] Vec2 lnormal(Vec2 vec) { 165 | return {-vec[1], vec[0]}; 166 | } 167 | 168 | /// Returns the right normal of a 2 dimensional vector. 169 | template 170 | [[deprecated]] Vec2 rnormal(Vec2 vec) { 171 | return {vec[1], -vec[0]}; 172 | } 173 | 174 | } // namespace rho 175 | 176 | /// Operations that assume a left-hand orientad coordinate system. 177 | namespace lho { 178 | 179 | /// Returns the left normal of a 2 dimensional vector. 180 | template 181 | [[deprecated]] Vec2 lnormal(Vec2 vec) { 182 | return {vec[1], -vec[0]}; 183 | } 184 | 185 | /// Returns the right normal of a 2 dimensional vector. 186 | template 187 | [[deprecated]] Vec2 rnormal(Vec2 vec) { 188 | return {-vec[1], vec[0]}; 189 | } 190 | 191 | } // namespace rho 192 | 193 | // additional utility operators 194 | namespace vec { 195 | namespace operators { 196 | 197 | template 198 | constexpr auto operator*(const Vec& a, const F& f) 199 | -> Vec { 200 | auto ret = Vec {}; 201 | for(auto i = 0u; i < D; ++i) 202 | ret[i] = a[i] * f; 203 | return ret; 204 | } 205 | 206 | template 207 | constexpr auto operator/(const Vec& a, const F& f) 208 | -> Vec { 209 | auto ret = Vec {}; 210 | for(auto i = 0u; i < D; ++i) 211 | ret[i] = a[i] / f; 212 | return ret; 213 | } 214 | 215 | template 216 | constexpr auto operator/(const F& f, const Vec& a) 217 | -> Vec { 218 | auto ret = Vec {}; 219 | for(auto i = 0u; i < D; ++i) 220 | ret[i] = f / a[i]; 221 | return ret; 222 | } 223 | 224 | } // namespace operators 225 | 226 | namespace cw { // vec component-wise operations 227 | 228 | /// Returns a vector holding the component-wise maximum of the given vectors. 229 | template 230 | constexpr auto max(Vec a, const Vec& b) { 231 | for(auto i = 0u; i < D; ++i) 232 | if(b[i] > a[i]) 233 | a[i] = b[i]; 234 | return a; 235 | } 236 | 237 | template 238 | constexpr auto max(Vec a, const T& b) { 239 | for(auto i = 0u; i < D; ++i) 240 | if(b > a[i]) 241 | a[i] = b; 242 | return a; 243 | } 244 | 245 | /// Returns a vector holding the component-wise minimum of the given vectors. 246 | template 247 | constexpr auto min(Vec a, const Vec& b) { 248 | for(auto i = 0u; i < D; ++i) 249 | if(b[i] < a[i]) 250 | a[i] = b[i]; 251 | return a; 252 | } 253 | 254 | template 255 | constexpr auto min(Vec a, const T& b) { 256 | for(auto i = 0u; i < D; ++i) 257 | if(b < a[i]) 258 | a[i] = b; 259 | return a; 260 | } 261 | 262 | /// Returns the component-wise product of the given vectors. 263 | template 264 | constexpr auto multiply(const Vec& a, const Vec& b) { 265 | Vec ret {}; 266 | for(auto i = 0u; i < D; ++i) 267 | ret[i] = a[i] * b[i]; 268 | return ret; 269 | } 270 | 271 | /// Returns the component-wise quotient of the given vectors. 272 | template 273 | constexpr auto divide(const Vec& a, const Vec& b) { 274 | Vec ret {}; 275 | for(auto i = 0u; i < D; ++i) 276 | ret[i] = a[i] / b[i]; 277 | return ret; 278 | } 279 | 280 | namespace operators { 281 | 282 | template 283 | constexpr Vec& operator*=(Vec& a, const Vec& b) { 284 | for(size_t i = 0; i < D; ++i) 285 | a[i] *= b[i]; 286 | return a; 287 | } 288 | 289 | template 290 | constexpr Vec& operator/=(Vec& a, const Vec& b) { 291 | for(size_t i = 0; i < D; ++i) 292 | a[i] /= b[i]; 293 | return a; 294 | } 295 | 296 | template 297 | constexpr auto operator*(const Vec& a, const Vec& b) { 298 | return multiply(a, b); 299 | } 300 | 301 | template 302 | constexpr auto operator/(const Vec& a, const Vec& b) { 303 | return divide(a, b); 304 | } 305 | 306 | template 307 | constexpr auto operator+(const Vec& a, const T2& b) { 308 | Vec res; 309 | for(size_t i = 0u; i < D; ++i) { 310 | res[i] = a[i] + b; 311 | } 312 | return res; 313 | } 314 | 315 | template 316 | constexpr auto operator+(const T2& a, const Vec& b) { 317 | Vec res; 318 | for(size_t i = 0u; i < D; ++i) { 319 | res[i] = b[i] + a; 320 | } 321 | return res; 322 | } 323 | 324 | template 325 | constexpr auto operator-(const Vec& a, const T2& b) { 326 | Vec res; 327 | for(size_t i = 0u; i < D; ++i) { 328 | res[i] = a[i] - b; 329 | } 330 | return res; 331 | } 332 | 333 | template 334 | constexpr auto operator-(const T2& a, const Vec& b) { 335 | Vec res; 336 | for(size_t i = 0u; i < D; ++i) { 337 | res[i] = a - b[i]; 338 | } 339 | return res; 340 | } 341 | 342 | } // namespace operators 343 | 344 | namespace ip { // inplace operations 345 | 346 | // Various utility functions. 347 | // Always modify the given vector in place and simply apply the similar 348 | // named stl or nytl function on all components. 349 | #define NYTL_VEC_IP_UTIL_FUNC(func) \ 350 | template \ 351 | constexpr void func(Vec& vec) { \ 352 | for(auto i = 0u; i < D; ++i) \ 353 | vec[i] = std::func(vec[i]); \ 354 | } 355 | 356 | NYTL_VEC_IP_UTIL_FUNC(abs) 357 | NYTL_VEC_IP_UTIL_FUNC(sin) 358 | NYTL_VEC_IP_UTIL_FUNC(cos) 359 | NYTL_VEC_IP_UTIL_FUNC(tan) 360 | NYTL_VEC_IP_UTIL_FUNC(asin) 361 | NYTL_VEC_IP_UTIL_FUNC(acos) 362 | NYTL_VEC_IP_UTIL_FUNC(atan) 363 | NYTL_VEC_IP_UTIL_FUNC(sqrt) 364 | NYTL_VEC_IP_UTIL_FUNC(log) 365 | NYTL_VEC_IP_UTIL_FUNC(exp) 366 | NYTL_VEC_IP_UTIL_FUNC(exp2) 367 | NYTL_VEC_IP_UTIL_FUNC(floor) 368 | NYTL_VEC_IP_UTIL_FUNC(ceil) 369 | 370 | #undef NYTL_VEC_IP_UTIL 371 | 372 | template 373 | constexpr void pow(Vec& a, T2 exp) { 374 | for(auto i = 0u; i < D; ++i) { 375 | a[i] = std::pow(a[i], exp); 376 | } 377 | } 378 | 379 | template 380 | constexpr void pow(Vec& a, const Vec& b) { 381 | for(auto i = 0u; i < D; ++i) { 382 | a[i] = std::pow(a[i], b[i]); 383 | } 384 | } 385 | 386 | template 387 | constexpr void clamp(Vec& a, T low, T high) { 388 | for(auto i = 0u; i < D; ++i) { 389 | a[i] = std::clamp(a[i], low, high); 390 | } 391 | } 392 | 393 | template 394 | constexpr void clamp(Vec& a, const Vec& low, const Vec& high) { 395 | for(auto i = 0u; i < D; ++i) { 396 | a[i] = std::clamp(a[i], low[i], high[i]); 397 | } 398 | } 399 | 400 | } // namespace ip 401 | 402 | #define NYTL_VEC_UTIL_FUNC(func) \ 403 | template \ 404 | constexpr auto func(Vec vec) { \ 405 | ip::func(vec); \ 406 | return vec; \ 407 | } 408 | 409 | NYTL_VEC_UTIL_FUNC(abs) 410 | NYTL_VEC_UTIL_FUNC(sin) 411 | NYTL_VEC_UTIL_FUNC(cos) 412 | NYTL_VEC_UTIL_FUNC(tan) 413 | NYTL_VEC_UTIL_FUNC(asin) 414 | NYTL_VEC_UTIL_FUNC(acos) 415 | NYTL_VEC_UTIL_FUNC(atan) 416 | NYTL_VEC_UTIL_FUNC(sqrt) 417 | NYTL_VEC_UTIL_FUNC(log) 418 | NYTL_VEC_UTIL_FUNC(exp) 419 | NYTL_VEC_UTIL_FUNC(exp2) 420 | NYTL_VEC_UTIL_FUNC(floor) 421 | NYTL_VEC_UTIL_FUNC(ceil) 422 | 423 | #undef NYTL_VEC_UTIL_FUNC 424 | 425 | template 426 | constexpr auto pow(Vec a, T2 exp) { 427 | ip::pow(a, exp); 428 | return a; 429 | } 430 | 431 | template 432 | constexpr auto pow(Vec a, const Vec& exp) { 433 | ip::pow(a, exp); 434 | return a; 435 | } 436 | 437 | template 438 | constexpr auto clamp(Vec a, T low, T high) { 439 | ip::clamp(a, low, high); 440 | return a; 441 | } 442 | 443 | template 444 | constexpr auto clamp(Vec a, const Vec& low, const Vec& high) { 445 | ip::clamp(a, low, high); 446 | return a; 447 | } 448 | 449 | } // namespace cw 450 | } // namespace vec 451 | } // namespace nytl 452 | 453 | #endif // header guard 454 | -------------------------------------------------------------------------------- /nytl/quaternion.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // std::sin 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // Simple, lightweight and independent Quaternion implementation. 11 | // Mostly put together from snippets and implementation notes on the internet. 12 | 13 | // What makes quaternions somewhat hard to work with is that once again 14 | // everybody has a different convention regarding roll, pitch, yaw and 15 | // attitude, heading, bank (i.e. the order of rotations, relevant 16 | // to convert from/to euler angles as it is sometimes needed). 17 | // No convention is implicitly assumed here, when creating a quaternion 18 | // from euler angles (rotation sequence) or the other way around, 19 | // the convention has to be specified. 20 | 21 | // Just as a reference (but this should not have any impact for this 22 | // header) to inuitively implement rotations in a standard 23 | // graphics coordinate system (e.g. for the camera, where y is up, 24 | // -z is front and x is the right, i.e. right-handed coordinate 25 | // system), the yxz rotation sequence (euler/tait-bryan angles) 26 | // is used. 27 | 28 | // Articles about quaternions usually just implicitly assume one of 29 | // the euler-angles rotation sequences when it's needed (without ever 30 | // specifying it). Googling something like "quaternion to euler 31 | // angles" or "euler angles to quaternions" gives many different formulas, 32 | // usually without people specififying for *which* euler rotation 33 | // sequence this formula actually works. Even worse, the association 34 | // between pitch, yaw, roll and the x, y, z axes (which is all quaternions 35 | // know about, there are no inherently attached semantics!) is usually 36 | // implicitly assumed as well. 37 | 38 | // Getting euler angles out of quaternions is not trivial, that's why it's 39 | // implemented below. Creating a quaternion out of euler angles can always 40 | // easily be done using the 'axisAngle' constructor and multiplication, 41 | // a yxz rotation by (yaw, pitch, roll) can for instance easily 42 | // be achieved by 43 | // Quaternion::axisAngle(0, 1, 0, yaw) * 44 | // Quaternion::axisAngle(1, 0, 0, pitch) * 45 | // Quaternion::axisAngle(0, 0, 1, roll). 46 | 47 | namespace nytl { 48 | 49 | // fwd 50 | class Quaternion; 51 | [[nodiscard]] inline Quaternion conjugated(const Quaternion& q); 52 | [[nodiscard]] inline Quaternion normalized(const Quaternion& q); 53 | 54 | // Represents a mathematical quaternion, useful for representing 3D rotations. 55 | class Quaternion { 56 | public: 57 | double x {0.f}; 58 | double y {0.f}; 59 | double z {0.f}; 60 | double w {1.f}; 61 | 62 | public: 63 | // Constructs a Quaternion from an axis (given by x,y,z) and an angle (in radians) 64 | // to rotate around the axis. 65 | [[nodiscard]] static 66 | Quaternion axisAngle(double ax, double ay, double az, double angle) { 67 | auto ha = std::sin(angle / 2); 68 | return {ax * ha, ay * ha, az * ha, std::cos(angle / 2)}; 69 | } 70 | 71 | [[nodiscard]] static 72 | Quaternion axisAngle(const Vec3f& axis, double angle) { 73 | return axisAngle(axis.x, axis.y, axis.z, angle); 74 | } 75 | 76 | // Creates a quaternion from a yxz rotation sequence, where 77 | // 'yaw' is the rotation around the y axis, 'pitch' around 78 | // the x axis and 'roll' around the z axis. 79 | // Same as 80 | // Quaternion::axisAngle(0, 1, 0, yaw) * 81 | // Quaternion::axisAngle(1, 0, 0, pitch) * 82 | // Quaternion::axisAngle(0, 0, 1, roll). 83 | // Utility constructor provided for this rotation sequence since we 84 | // often need it, constructing quaternions for other, custom, 85 | // rotation sequences can be done with chaining axisAngle 86 | // rotations as seen above (or by looking up the optimized 87 | // formula, as done here). 88 | [[nodiscard]] static 89 | Quaternion yxz(double yaw, double pitch, double roll) { 90 | double cy = std::cos(yaw * 0.5); 91 | double sy = std::sin(yaw * 0.5); 92 | double cp = std::cos(pitch * 0.5); 93 | double sp = std::sin(pitch * 0.5); 94 | double cr = std::cos(roll * 0.5); 95 | double sr = std::sin(roll * 0.5); 96 | 97 | return { 98 | cy * sp * cr + sy * cp * sr, 99 | sy * cp * cr - cy * sp * sr, 100 | cy * cp * sr - sy * sp * cr, 101 | cy * cp * cr + sy * sp * sr, 102 | }; 103 | } 104 | 105 | // Creates a quaternion from an orthogonal 3x3 rotation matrix. 106 | // Undefined if the given matrix is not orthogonal. 107 | // Can be used to create a quaternion that transforms the standard 108 | // base from/to a given (orthogonal) vector base by setting the new 109 | // base vectors as rows/columns. 110 | template static 111 | Quaternion fromMat(const Mat3

& m) { 112 | assert(std::abs(dot(m[0], m[1])) < 0.05); 113 | assert(std::abs(dot(m[0], m[2])) < 0.05); 114 | assert(std::abs(dot(m[1], m[2])) < 0.05); 115 | 116 | // d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2015/01/matrix-to-quat.pdf 117 | double t; 118 | Quaternion q; 119 | if(m[2][2] < 0) { 120 | if(m[0][0] > m[1][1]){ 121 | t = 1.0 + m[0][0] - m[1][1] - m[2][2]; 122 | q = {t, m[1][0] + m[0][1], m[0][2] + m[2][0], m[2][1] - m[1][2]}; 123 | } else{ 124 | t= 1.0 - m[0][0] + m[1][1] - m[2][2]; 125 | q = {m[1][0] + m[0][1], t, m[2][1] + m[1][2], m[0][2] - m[2][0]}; 126 | } 127 | } else { 128 | if(m[0][0] < -m[1][1]){ 129 | t = 1.0 - m[0][0] - m[1][1] + m[2][2]; 130 | q = {m[0][2] + m[2][0], m[2][1] + m[1][2], t, m[1][0] - m[0][1]}; 131 | } else{ 132 | t = 1.0 + m[0][0] + m[1][1] + m[2][2]; 133 | q = {m[2][1] - m[1][2], m[0][2] - m[2][0], m[1][0] - m[0][1], t}; 134 | } 135 | } 136 | 137 | double f = 0.5 / std::sqrt(t); 138 | q.x *= f; 139 | q.y *= f; 140 | q.z *= f; 141 | q.w *= f; 142 | return q; 143 | } 144 | 145 | public: 146 | // Default-constructs the Quaternion to a zero rotation. 147 | // Quaternion() noexcept = default; 148 | 149 | // hamilton product of quaternions 150 | Quaternion& operator*=(const Quaternion& rhs) { 151 | auto nx = w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y; 152 | auto ny = w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x; 153 | auto nz = w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w; 154 | auto nw = w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z; 155 | *this = {nx, ny, nz, nw}; 156 | return *this; 157 | } 158 | }; 159 | 160 | // - operators and functions - 161 | inline Quaternion operator*(Quaternion a, const Quaternion& b) { 162 | return (a *= b); 163 | } 164 | 165 | inline Quaternion operator*(double a, Quaternion b) { 166 | b.x *= a; 167 | b.y *= a; 168 | b.z *= a; 169 | b.w *= a; 170 | return b; 171 | } 172 | 173 | inline Quaternion operator-(Quaternion a, const Quaternion& b) { 174 | a.x -= b.x; 175 | a.y -= b.y; 176 | a.z -= b.z; 177 | a.w -= b.w; 178 | return a; 179 | } 180 | 181 | inline Quaternion operator+(Quaternion a, const Quaternion& b) { 182 | a.x += b.x; 183 | a.y += b.y; 184 | a.z += b.z; 185 | a.w += b.w; 186 | return a; 187 | } 188 | 189 | inline bool operator==(const Quaternion& a, const Quaternion& b) { 190 | return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; 191 | } 192 | 193 | inline bool operator!=(const Quaternion& a, const Quaternion& b) { 194 | return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; 195 | } 196 | 197 | // Returns a row-major NxN matrix that represents the given Quaternion. 198 | // Same as an identity matrix with the first 3 colums being 199 | // apply(q, {1, 0, 0}), apply(q, {0, 1, 0}), apply(q, {0, 0, 1}) 200 | template 201 | [[nodiscard]] SquareMat toMat(const Quaternion& q) { 202 | static_assert(N >= 3); 203 | SquareMat ret {}; 204 | 205 | auto wz = q.w * q.z; 206 | auto wy = q.w * q.y; 207 | auto wx = q.w * q.x; 208 | auto xx = q.x * q.x; 209 | auto xy = q.x * q.y; 210 | auto xz = q.x * q.z; 211 | auto yy = q.y * q.y; 212 | auto yz = q.y * q.z; 213 | auto zz = q.z * q.z; 214 | 215 | ret[0][0] = 1 - 2 * (yy + zz); 216 | ret[0][1] = 2 * (xy - wz); 217 | ret[0][2] = 2 * (wy + xz); 218 | 219 | ret[1][0] = 2 * (xy + wz); 220 | ret[1][1] = 1 - 2 * (xx + zz); 221 | ret[1][2] = 2 * (yz - wx); 222 | 223 | ret[2][0] = 2 * (xz - wy); 224 | ret[2][1] = 2 * (wx + yz); 225 | ret[2][2] = 1 - 2 * (xx + yy); 226 | 227 | // Make sure the remaining rows/cols are initialized like identiy matrix 228 | for(auto i = 3u; i < N; ++i) { 229 | ret[i][i] = 1.f; 230 | } 231 | 232 | return ret; 233 | } 234 | 235 | // Returns the conjugate of the given Quaternion (simply taking the 236 | // negative of the non-real parts). 237 | [[nodiscard]] inline Quaternion conjugated(const Quaternion& q) { 238 | return {-q.x, -q.y, -q.z, q.w}; 239 | } 240 | 241 | // Returns the norm of the given Quaternion. 242 | [[nodiscard]] inline double norm(const Quaternion& q) { 243 | return std::sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z); 244 | } 245 | 246 | // Returns a unit quaternion for the given quaternion. 247 | [[nodiscard]] inline Quaternion normalized(const Quaternion& q) { 248 | auto l = norm(q); 249 | if(l <= 0.0) return {0.0, 0.0, 0.0, 1.0}; 250 | return {q.x / l, q.y / l, q.z / l, q.w / l}; 251 | } 252 | 253 | // Returns the given vector rotated by the rotation represented by the given 254 | // Quaternion. 255 | template 256 | [[nodiscard]] Vec3 apply(const Quaternion& q, const Vec3& v) { 257 | // optimized version, see 258 | // https://gamedev.stackexchange.com/questions/28395 259 | Vec3d u {q.x, q.y, q.z}; 260 | auto r = 2.0 * dot(u, v) * u 261 | + (q.w * q.w - dot(u, u)) * v 262 | + 2.0 * q.w * cross(u, v); 263 | return Vec3(r); 264 | 265 | // Reference implementation, using the mathematical definition. 266 | // Same as above but less efficient. 267 | // auto qv = Quaternion{v.x, v.y, v.z, 0.f}; 268 | // auto qr = (q * qv) * conjugated(q); 269 | // // assert(std::abs(qr.w) < 0.01f); 270 | // return {T(qr.x), T(qr.y), T(qr.z)}; 271 | } 272 | 273 | [[nodiscard]] inline double dot(const Quaternion& a, const Quaternion& b) { 274 | return a.x * b.x + a.y * b.y + a.z * b.y * a.w * b.y; 275 | } 276 | 277 | // https://en.wikipedia.org/wiki/Slerp 278 | // Assumes q0 and q1 to be normalized. Result isn't normalized. 279 | [[nodiscard]] inline Quaternion slerp(Quaternion v0, Quaternion v1, double t) { 280 | // Compute the cosine of the angle between the two vectors. 281 | double d = dot(v0, v1); 282 | 283 | // If the dot product is negative, slerp won't take 284 | // the shorter path. Note that v1 and -v1 are equivalent when 285 | // the negation is applied to all four components. Fix by 286 | // reversing one quaternion. 287 | if (d < 0.0f) { 288 | v1.x = v1.x; 289 | v1.y = v1.y; 290 | v1.z = v1.z; 291 | v1.w = v1.w; 292 | d = -d; 293 | } 294 | 295 | constexpr auto dotThreshold = 0.9995; 296 | if (d > dotThreshold) { 297 | // If the inputs are too close, linearly interpolate and normalize 298 | Quaternion result = v0 + t * (v1 - v0); 299 | return normalized(result); 300 | } 301 | 302 | // acos is safe, d is in range [0, dotThreshold] 303 | double theta0 = std::acos(d); // angle between input vectors 304 | double theta = theta0 * t; // angle between v0 and result 305 | double sinTheta = std::sin(theta); 306 | double sinTheta0 = std::sin(theta0); 307 | 308 | double s0 = cos(theta) - d * sinTheta / sinTheta0; // sin(theta0 - theta) / sin(theta0) 309 | double s1 = sinTheta / sinTheta0; 310 | 311 | return (s0 * v0) + (s1 * v1); 312 | } 313 | 314 | // Sequences of rotation around axes. 315 | // https://en.wikipedia.org/wiki/Euler_angles 316 | enum class RotationSequence { 317 | // classic euler angles 318 | xyx, 319 | xzx, 320 | yzy, 321 | yxy, 322 | zxz, 323 | zyz, 324 | 325 | // tait-bryan angles 326 | xyz, 327 | xzy, 328 | yxz, 329 | yzx, 330 | zxy, 331 | zyx, 332 | }; 333 | 334 | // Returns the angles (in radians) for the given rotation sequence 335 | // to reach the orientation of the given quaternion (using intrinsic 336 | // rotation axis definition). For instance, for 337 | // `res = eulerAngles(q, RotationSequence::yxz)`, the rotation 338 | // rotY(res[0]) * rotX(res[1]) * rotZ(res[2]) (for global axes, notice how 339 | // this means global Z rotation is applied first) is the same as 340 | // the given quaternion. 341 | [[nodiscard]] inline std::array 342 | eulerAngles(const Quaternion& q, RotationSequence seq) { 343 | // TODO: 'indet' handling somewhat hacky atm. We need that value 344 | // in case the middle rotation is zero (indeterminite case) 345 | auto classicEuler = [](double a, double b, double c, double d, double e, 346 | double indet) { 347 | auto res = std::array { 348 | std::atan2(d, e), 349 | std::acos(c), 350 | std::atan2(a, b), 351 | }; 352 | 353 | if(std::abs(res[1]) < 0.001) { 354 | // need different handling in this case. 355 | // (basically middle angle is zero, getting undefined 356 | // results atm). 357 | return std::array{std::asin(indet), 0.0, 0.0}; 358 | } 359 | 360 | return res; 361 | }; 362 | 363 | auto taitBryan = [](double a, double b, double c, double d, double e) { 364 | return std::array { 365 | std::atan2(a, b), 366 | std::asin(c), 367 | std::atan2(d, e), 368 | }; 369 | }; 370 | 371 | switch(seq){ 372 | case RotationSequence::xyx: 373 | return classicEuler( 374 | 2 * (q.x * q.y + q.w * q.z), 375 | -2 * (q.x * q.z - q.w * q.y), 376 | q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z, 377 | 2 * (q.x * q.y - q.w * q.z), 378 | 2 * (q.x * q.z + q.w * q.y), 379 | 2 * (q.w * q.x + q.y * q.z)); 380 | case RotationSequence::xzx: 381 | return classicEuler( 382 | 2 * (q.x * q.z - q.w * q.y), 383 | 2 * (q.x * q.y + q.w * q.z), 384 | q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z, 385 | 2 * (q.x * q.z + q.w * q.y), 386 | -2 * (q.x * q.y - q.w * q.z), 387 | 2 * (q.w * q.x + q.y * q.z)); 388 | case RotationSequence::yxy: 389 | return classicEuler( 390 | 2 * (q.x * q.y - q.w * q.z), 391 | 2 * (q.y * q.z + q.w * q.x), 392 | q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z, 393 | 2 * (q.x * q.y + q.w * q.z), 394 | -2 * (q.y * q.z - q.w * q.x), 395 | 2 * (q.w * q.y + q.x * q.z)); 396 | case RotationSequence::yzy: 397 | return classicEuler( 398 | 2 * (q.y * q.z + q.w * q.x), 399 | -2 * (q.x * q.y - q.w * q.z), 400 | q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z, 401 | 2 * (q.y * q.z - q.w * q.x), 402 | 2 * (q.x * q.y + q.w * q.z), 403 | 2 * (q.w * q.y + q.x * q.z)); 404 | case RotationSequence::zxz: 405 | return classicEuler( 406 | 2 * (q.x * q.z + q.w * q.y), 407 | -2 * (q.y * q.z - q.w * q.x), 408 | q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z, 409 | 2 * (q.x * q.z - q.w * q.y), 410 | 2 * (q.y * q.z + q.w * q.x), 411 | 2 * (q.x * q.y + q.w * q.z)); 412 | case RotationSequence::zyz: 413 | return classicEuler( 414 | 2 * (q.y * q.z - q.w * q.x), 415 | 2 * (q.x * q.z + q.w * q.y), 416 | q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z, 417 | 2 * (q.y * q.z + q.w * q.x), 418 | -2 * (q.x * q.z - q.w * q.y), 419 | 2 * (q.x * q.y + q.w * q.z)); 420 | 421 | case RotationSequence::xyz: 422 | return taitBryan( 423 | -2 * (q.y * q.z - q.w * q.x), 424 | q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z, 425 | 2 * (q.x * q.z + q.w * q.y), 426 | -2 * (q.x * q.y - q.w * q.z), 427 | q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z); 428 | case RotationSequence::xzy: 429 | return taitBryan( 430 | 2 * (q.y * q.z + q.w * q.x), 431 | q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z, 432 | -2 *(q.x * q.y - q.w * q.z), 433 | 2 *(q.x * q.z + q.w * q.y), 434 | q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z); 435 | case RotationSequence::yxz: 436 | return taitBryan( 437 | 2 * (q.x * q.z + q.w * q.y), 438 | q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z, 439 | -2 * (q.y * q.z - q.w * q.x), 440 | 2 * (q.x * q.y + q.w * q.z), 441 | q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z); 442 | case RotationSequence::yzx: 443 | return taitBryan( 444 | -2 * (q.x * q.z - q.w * q.y), 445 | q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z, 446 | 2 * (q.x * q.y + q.w * q.z), 447 | -2 * (q.y * q.z - q.w * q.x), 448 | q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z); 449 | case RotationSequence::zxy: 450 | return taitBryan( 451 | -2 * (q.x * q.y - q.w * q.z), 452 | q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z, 453 | 2 * (q.y * q.z + q.w * q.x), 454 | -2 * (q.x * q.z - q.w * q.y), 455 | q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z); 456 | case RotationSequence::zyx: 457 | return taitBryan( 458 | 2 * (q.x * q.y + q.w * q.z), 459 | q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z, 460 | -2 * (q.x * q.z - q.w * q.y), 461 | 2 * (q.y * q.z + q.w * q.x), 462 | q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z); 463 | 464 | default: 465 | assert(false && "Invalid rotation sequence"); 466 | return {}; 467 | } 468 | } 469 | 470 | 471 | } // namespace 472 | 473 | // NOTE: implementation sources: 474 | // - https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles 475 | --------------------------------------------------------------------------------