├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── dev.cmake ├── include └── yama │ ├── assert.hpp │ ├── box.hpp │ ├── dim.hpp │ ├── ext │ ├── matrix3x3_ostream.hpp │ ├── matrix3x4_ostream.hpp │ ├── matrix4x4_ostream.hpp │ ├── ostream.hpp │ ├── quaternion_ostream.hpp │ ├── vector2_ostream.hpp │ ├── vector3_ostream.hpp │ └── vector4_ostream.hpp │ ├── matrix3x3.hpp │ ├── matrix3x4.hpp │ ├── matrix4x4.hpp │ ├── quaternion.hpp │ ├── shorthand.hpp │ ├── type_traits.hpp │ ├── util.hpp │ ├── vector2.hpp │ ├── vector3.hpp │ ├── vector4.hpp │ ├── vector_xyzw.hpp │ └── yama.hpp └── test └── unit ├── CMakeLists.txt ├── common.hpp ├── get_cpm.cmake ├── matrix3x3.cpp ├── matrix3x4.cpp ├── matrix4x4.cpp ├── ostream.cpp ├── prerequisites.cpp ├── quaternion.cpp ├── utils.cpp ├── vector2.cpp ├── vector3.cpp ├── vector4.cpp └── vector_xyzw.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | jobs: 4 | build-and-test: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | matrix: 8 | os: [ubuntu-20.04, windows-latest, macos-latest] 9 | type: [Debug, RelWithDebInfo] 10 | steps: 11 | - name: Clone 12 | uses: actions/checkout@v2 13 | - name: Configure 14 | run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DUSE_ASAN=1 15 | - name: Build 16 | run: cmake --build . --config ${{ matrix.type }} 17 | - name: Test 18 | run: ctest -C ${{ matrix.type }} --output-on-failure 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | out/ 3 | 4 | # ides 5 | .vscode/ 6 | .vs/ 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Borislav Stanimirov 2 | # SPDX-License-Identifier: MIT 3 | # 4 | cmake_minimum_required(VERSION 3.14) 5 | 6 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 7 | # dev_mode is used below to make life simpler for developers 8 | # it enables some configurations and the defaults for building tests and examples 9 | # which typically wouldn't be built if confy is a subdirectory of another project 10 | set(dev_mode ON) 11 | else() 12 | set(dev_mode OFF) 13 | endif() 14 | 15 | project(yama) 16 | 17 | option(YAMA_BUILD_UNIT_TESTS "yama: build tests" ${dev_mode}) 18 | option(YAMA_BUILD_SCRATCH "yama: build scratch project for testing and experiments" ${dev_mode}) 19 | 20 | mark_as_advanced(YAMA_BUILD_UNIT_TESTS YAMA_BUILD_SCRATCH) 21 | 22 | if(dev_mode) 23 | include(./dev.cmake) 24 | endif() 25 | 26 | add_library(yama INTERFACE) 27 | add_library(yama::yama ALIAS yama) 28 | target_include_directories(yama INTERFACE include) 29 | 30 | if(${YAMA_BUILD_UNIT_TESTS}) 31 | enable_testing() 32 | add_subdirectory(test/unit) 33 | endif() 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2022 Borislav Stanimirov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yama 2 | 3 | [![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/) [![Standard](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) 4 | 5 | [![Build](https://github.com/iboB/yama/workflows/Build/badge.svg)](https://github.com/iboB/yama/actions?query=workflow%3ABuild) 6 | 7 | Yama (Yet Another MAthematical library) is simple header-only mathematical library for game programming. 8 | 9 | ## Usage 10 | 11 | The library is header-only. To use it, you need to add its include directory in your include paths, then include `` 12 | 13 | ## Contributing 14 | 15 | Contributions in the form of issues and pull requests are welcome. 16 | 17 | ## License 18 | The library is distributed under the MIT Software License. See LICENSE.txt for further details or copy [here](http://opensource.org/licenses/MIT). 19 | 20 | Copyright © 2016-2022 Borislav Stanimirov. 21 | -------------------------------------------------------------------------------- /dev.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) Borislav Stanimirov 2 | # SPDX-License-Identifier: MIT 3 | # 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_EXTENSIONS OFF) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | option(USE_TSAN "yama-dev: build with thread sanitizer on" OFF) 9 | option(USE_ASAN "yama-dev: build with address sanitizer on" OFF) 10 | option(USE_CLANG_TIDY "yama-dev: use clang tidy" OFF) 11 | 12 | set(DEMO_SAN_FLAGS "") 13 | if(NOT MSVC) 14 | set(DEMO_WARNING_FLAGS "-Wall -Wextra -Werror=return-type") 15 | if(USE_TSAN) 16 | set(DEMO_SAN_FLAGS "-fsanitize=thread -g") 17 | elseif(USE_ASAN) 18 | set(DEMO_SAN_FLAGS "-fsanitize=address,undefined -pthread -g") 19 | endif() 20 | 21 | if(USE_CLANG_TIDY) 22 | set(CMAKE_CXX_CLANG_TIDY clang-tidy) 23 | endif() 24 | endif() 25 | 26 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEMO_WARNING_FLAGS} ${DEMO_SAN_FLAGS}") 27 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEMO_WARNING_FLAGS} ${DEMO_SAN_FLAGS}") 28 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${DEMO_SAN_FLAGS}") 29 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${DEMO_SAN_FLAGS}") 30 | -------------------------------------------------------------------------------- /include/yama/assert.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | #define YAMA_ASSERT_LEVEL_NONE 0 9 | #define YAMA_ASSERT_LEVEL_CRITICAL 1 10 | #define YAMA_ASSERT_LEVEL_BAD 2 11 | #define YAMA_ASSERT_LEVEL_ALL 3 12 | 13 | #if !defined(YAMA_ASSERT_LEVEL) 14 | # define YAMA_ASSERT_LEVEL YAMA_ASSERT_LEVEL_ALL 15 | #endif 16 | 17 | #define _YAMA_NOOP(cond, msg) 18 | 19 | #if YAMA_ASSERT_LEVEL == YAMA_ASSERT_LEVEL_NONE 20 | # define YAMA_ASSERT_CRIT _YAMA_NOOP 21 | # define YAMA_ASSERT_BAD _YAMA_NOOP 22 | # define YAMA_ASSERT_WARN _YAMA_NOOP 23 | #elif YAMA_ASSERT_LEVEL == YAMA_ASSERT_LEVEL_CRITICAL 24 | # define YAMA_ASSERT_CRIT(condition, text) YAMA_ASSERT(condition, text) 25 | # define YAMA_ASSERT_BAD _YAMA_NOOP 26 | # define YAMA_ASSERT_WARN _YAMA_NOOP 27 | #elif YAMA_ASSERT_LEVEL == YAMA_ASSERT_LEVEL_BAD 28 | # define YAMA_ASSERT_CRIT(condition, text) YAMA_ASSERT(condition, text) 29 | # define YAMA_ASSERT_BAD(condition, text) YAMA_ASSERT(condition, text) 30 | # define YAMA_ASSERT_WARN _YAMA_NOOP 31 | #elif YAMA_ASSERT_LEVEL == YAMA_ASSERT_LEVEL_ALL 32 | # define YAMA_ASSERT_CRIT(condition, text) YAMA_ASSERT(condition, text) 33 | # define YAMA_ASSERT_BAD(condition, text) YAMA_ASSERT(condition, text) 34 | # define YAMA_ASSERT_WARN(condition, text) YAMA_ASSERT(condition, text) 35 | #else 36 | # error "Yama: Invalid assertion level." 37 | #endif 38 | 39 | #define YAMA_ASSERT(cond, msg) assert((cond) && msg) 40 | -------------------------------------------------------------------------------- /include/yama/box.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include "dim.hpp" 7 | 8 | namespace yama 9 | { 10 | 11 | template 12 | class boxnt 13 | { 14 | public: 15 | static const size_t dimension = D; 16 | using dim_vector = typename dim::template vector_t; 17 | 18 | //////////////////////////////////////////////////////// 19 | // named constructors 20 | 21 | static boxnt zero() 22 | { 23 | boxnt ret; 24 | 25 | ret.min = dim_vector::zero(); 26 | ret.max = dim_vector::zero(); 27 | 28 | return ret; 29 | } 30 | 31 | static boxnt inverted() 32 | { 33 | boxnt ret; 34 | 35 | ret.min = dim_vector::uniform(std::numeric_limits::max()); 36 | ret.max = dim_vector::uniform(std::numeric_limits::lowest());; 37 | 38 | return ret; 39 | } 40 | 41 | static boxnt min_max(const dim_vector& _min, const dim_vector& _max) 42 | { 43 | boxnt ret; 44 | 45 | ret.min = _min; 46 | ret.max = _max; 47 | 48 | return ret; 49 | } 50 | 51 | static boxnt pos_size(const dim_vector& pos, const dim_vector& size) 52 | { 53 | dim_vector end = pos + size; 54 | 55 | boxnt ret; 56 | 57 | ret.min = yama::min(pos, end); 58 | ret.max = yama::max(pos, end); 59 | 60 | return ret; 61 | } 62 | 63 | //////////////////////////////////////////////////////// 64 | // functions 65 | 66 | bool is_inside(const dim_vector& point) const 67 | { 68 | for(size_t i=0; i= max.at(i)) 71 | { 72 | return false; 73 | } 74 | } 75 | 76 | return true; 77 | } 78 | 79 | bool intersects(const boxnt& other) const 80 | { 81 | for(size_t i=0; i= other.max.at(i)) 84 | return false; 85 | 86 | if (max.at(i) <= other.min.at(i)) 87 | return false; 88 | } 89 | 90 | return true; 91 | } 92 | 93 | dim_vector size() const 94 | { 95 | return max - min; 96 | } 97 | 98 | void add_point(const dim_vector& point) 99 | { 100 | min = yama::min(min, point); 101 | max = yama::max(max, point); 102 | } 103 | 104 | void add_point_max_complement(const dim_vector& point, const dim_vector& complement = dim_vector::uniform(1)) 105 | { 106 | min = yama::min(min, point); 107 | max = yama::max(max, point + complement); 108 | } 109 | 110 | bool is_valid() const 111 | { 112 | for(size_t i=0; i 141 | bool operator==(const boxnt& a, const boxnt& b) { 142 | return a.min == b.min && a.max == b.max; 143 | } 144 | 145 | template 146 | boxnt intersection(const boxnt& a, const boxnt& b) 147 | { 148 | boxnt ret; 149 | 150 | YAMA_ASSERT_WARN(a.intersects(b), "can't find intersection of non-intersecting boxes"); 151 | 152 | ret.min = max(a.min, b.min); 153 | ret.max = min(a.max, b.max); 154 | 155 | return ret; 156 | } 157 | 158 | } // namespace yama 159 | -------------------------------------------------------------------------------- /include/yama/dim.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include "vector2.hpp" 7 | #include "vector3.hpp" 8 | #include "vector4.hpp" 9 | 10 | namespace yama 11 | { 12 | 13 | template 14 | struct dim {}; 15 | 16 | template <> 17 | struct dim<2> 18 | { 19 | template 20 | using vector_t = vector2_t; 21 | 22 | #if !defined(YAMA_NO_SHORTHAND) 23 | using vector = vector2; 24 | #endif 25 | }; 26 | 27 | template <> 28 | struct dim<3> 29 | { 30 | template 31 | using vector_t = vector3_t; 32 | 33 | #if !defined(YAMA_NO_SHORTHAND) 34 | using vector = vector3; 35 | #endif 36 | }; 37 | 38 | template <> 39 | struct dim<4> 40 | { 41 | template 42 | using vector_t = vector4_t; 43 | 44 | #if !defined(YAMA_NO_SHORTHAND) 45 | using vector = vector4; 46 | #endif 47 | }; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /include/yama/ext/matrix3x3_ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "../matrix3x3.hpp" 8 | 9 | namespace yama 10 | { 11 | 12 | template 13 | ::std::ostream& operator<<(::std::ostream& o, const matrix3x3_t& m) 14 | { 15 | o << "((" 16 | << m.m00 << ", " << m.m01 << ", " << m.m02 << "), (" 17 | << m.m10 << ", " << m.m11 << ", " << m.m12 << "), (" 18 | << m.m20 << ", " << m.m21 << ", " << m.m22 << "))"; 19 | return o; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /include/yama/ext/matrix3x4_ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "../matrix3x4.hpp" 8 | 9 | namespace yama 10 | { 11 | 12 | template 13 | ::std::ostream& operator<<(::std::ostream& o, const matrix3x4_t& m) 14 | { 15 | o << "((" 16 | << m.m00 << ", " << m.m01 << ", " << m.m02 << ", " << m.m03 << "), (" 17 | << m.m10 << ", " << m.m11 << ", " << m.m12 << ", " << m.m13 << "), (" 18 | << m.m20 << ", " << m.m21 << ", " << m.m22 << ", " << m.m23 << "))"; 19 | return o; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /include/yama/ext/matrix4x4_ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "../matrix4x4.hpp" 8 | 9 | namespace yama 10 | { 11 | 12 | template 13 | ::std::ostream& operator<<(::std::ostream& o, const matrix4x4_t& m) 14 | { 15 | o << "((" 16 | << m.m00 << ", " << m.m01 << ", " << m.m02 << ", " << m.m03 << "), (" 17 | << m.m10 << ", " << m.m11 << ", " << m.m12 << ", " << m.m13 << "), (" 18 | << m.m20 << ", " << m.m21 << ", " << m.m22 << ", " << m.m23 << "), (" 19 | << m.m30 << ", " << m.m31 << ", " << m.m32 << ", " << m.m33 << "))"; 20 | return o; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /include/yama/ext/ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include "vector2_ostream.hpp" 7 | #include "vector3_ostream.hpp" 8 | #include "vector4_ostream.hpp" 9 | #include "quaternion_ostream.hpp" 10 | #include "matrix3x3_ostream.hpp" 11 | #include "matrix3x4_ostream.hpp" 12 | #include "matrix4x4_ostream.hpp" 13 | -------------------------------------------------------------------------------- /include/yama/ext/quaternion_ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "../quaternion.hpp" 8 | 9 | namespace yama 10 | { 11 | 12 | template 13 | ::std::ostream& operator<<(::std::ostream& o, const quaternion_t& v) 14 | { 15 | o << '(' << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ')'; 16 | return o; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /include/yama/ext/vector2_ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "../vector2.hpp" 8 | 9 | namespace yama 10 | { 11 | 12 | template 13 | ::std::ostream& operator<<(::std::ostream& o, const vector2_t& v) 14 | { 15 | o << '(' << v.x << ", " << v.y << ')'; 16 | return o; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /include/yama/ext/vector3_ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "../vector3.hpp" 8 | 9 | namespace yama 10 | { 11 | 12 | template 13 | ::std::ostream& operator<<(::std::ostream& o, const vector3_t& v) 14 | { 15 | o << '(' << v.x << ", " << v.y << ", " << v.z << ')'; 16 | return o; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /include/yama/ext/vector4_ostream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "../vector4.hpp" 8 | 9 | namespace yama 10 | { 11 | 12 | template 13 | ::std::ostream& operator<<(::std::ostream& o, const vector4_t& v) 14 | { 15 | o << '(' << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ')'; 16 | return o; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /include/yama/quaternion.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "util.hpp" 12 | #include "shorthand.hpp" 13 | #include "type_traits.hpp" 14 | 15 | #include "vector3.hpp" 16 | #include "vector4.hpp" 17 | 18 | namespace yama 19 | { 20 | 21 | template 22 | class quaternion_t; 23 | 24 | template 25 | quaternion_t normalize(const quaternion_t& q); 26 | 27 | template 28 | class quaternion_t 29 | { 30 | public: 31 | T x, y, z, w; 32 | 33 | using value_type = T; 34 | using size_type = size_t; 35 | using iterator = T*; 36 | using const_iterator = const T*; 37 | using reverse_iterator = typename std::reverse_iterator; 38 | using const_reverse_iterator = typename std::reverse_iterator; 39 | 40 | static constexpr size_type value_count = 4; 41 | 42 | constexpr size_type max_size() const { return value_count; } 43 | constexpr size_type size() const { return max_size(); } 44 | 45 | /////////////////////////////////////////////////////////////////////////// 46 | // named constructors 47 | static constexpr quaternion_t xyzw(const value_type& x, const value_type& y, const value_type& z, const value_type& w) 48 | { 49 | return{ x, y, z, w}; 50 | } 51 | 52 | static constexpr quaternion_t identity() 53 | { 54 | return xyzw(0, 0, 0, 1); 55 | } 56 | 57 | static constexpr quaternion_t uniform(const value_type& s) 58 | { 59 | return xyzw(s, s, s, s); 60 | } 61 | 62 | static constexpr quaternion_t zero() 63 | { 64 | return uniform(value_type(0)); 65 | } 66 | 67 | static quaternion_t from_ptr(const value_type* ptr) 68 | { 69 | YAMA_ASSERT_CRIT(ptr, "Constructing yama::quaternion_t from nullptr"); 70 | return xyzw(ptr[0], ptr[1], ptr[2], ptr[3]); 71 | } 72 | 73 | static constexpr quaternion_t from_vector4(const vector4_t& v) 74 | { 75 | return xyzw(v.x, v.y, v.z, v.w); 76 | } 77 | 78 | // for when you're sure that the axis is normalized 79 | static quaternion_t rotation_naxis(const vector3_t& axis, value_type radians) 80 | { 81 | YAMA_ASSERT_BAD(axis.is_normalized(), "rotation axis should be normalized"); 82 | const value_type s = std::sin(radians / 2); 83 | return xyzw( 84 | axis.x * s, 85 | axis.y * s, 86 | axis.z * s, 87 | std::cos(radians / 2) 88 | ); 89 | } 90 | 91 | static quaternion_t rotation_axis(const vector3_t& axis, value_type radians) 92 | { 93 | auto naxis = yama::normalize(axis); 94 | return rotation_naxis(naxis, radians); 95 | } 96 | 97 | static quaternion_t rotation_x(value_type radians) 98 | { 99 | return xyzw( 100 | std::sin(radians / 2), 101 | 0, 102 | 0, 103 | std::cos(radians / 2) 104 | ); 105 | } 106 | 107 | static quaternion_t rotation_y(value_type radians) 108 | { 109 | return xyzw( 110 | 0, 111 | std::sin(radians / 2), 112 | 0, 113 | std::cos(radians / 2) 114 | ); 115 | } 116 | 117 | static quaternion_t rotation_z(value_type radians) 118 | { 119 | return xyzw( 120 | 0, 121 | 0, 122 | std::sin(radians / 2), 123 | std::cos(radians / 2) 124 | ); 125 | } 126 | 127 | static quaternion_t rotation_vectors(const vector3_t& src, const vector3_t& target) 128 | { 129 | YAMA_ASSERT_BAD(src.is_normalized(), "source vector should be normalized"); 130 | YAMA_ASSERT_BAD(target.is_normalized(), "target vector should be normalized"); 131 | YAMA_ASSERT_WARN(!close(src, vector3_t::zero()), "source vector shouldn't be zero"); 132 | YAMA_ASSERT_WARN(!close(target, vector3_t::zero()), "target vector shouldn't be zero"); 133 | 134 | auto axis = cross(src, target); 135 | auto axis_length_sq = axis.length_sq(); 136 | 137 | if (axis_length_sq > constants_t::EPSILON) // not collinear 138 | { 139 | auto d = dot(src, target); 140 | auto c = cross(src, target); 141 | return yama::normalize(xyzw(c.x, c.y, c.z, 1 + d)); 142 | } 143 | else 144 | { 145 | if (close(src, target)) 146 | { 147 | // collinear 148 | return identity(); 149 | } 150 | else 151 | { 152 | // opposite 153 | auto o = yama::normalize(src.get_orthogonal()); 154 | return xyzw(o.x, o.y, o.z, 0); 155 | } 156 | } 157 | } 158 | 159 | /////////////////////////// 160 | // attach 161 | static quaternion_t& attach_to_ptr(value_type* ptr) 162 | { 163 | YAMA_ASSERT_BAD(ptr, "Attaching yama::quaternion_t to nullptr"); 164 | return *reinterpret_cast(ptr); 165 | } 166 | 167 | static const quaternion_t& attach_to_ptr(const value_type* ptr) 168 | { 169 | YAMA_ASSERT_BAD(ptr, "Attaching yama::quaternion_t to nullptr"); 170 | return *reinterpret_cast(ptr); 171 | } 172 | 173 | static quaternion_t* attach_to_array(value_type* ptr) 174 | { 175 | YAMA_ASSERT_WARN(ptr, "Attaching yama::quaternion_t to nullptr"); 176 | return reinterpret_cast(ptr); 177 | } 178 | 179 | static const quaternion_t* attach_to_array(const value_type* ptr) 180 | { 181 | YAMA_ASSERT_WARN(ptr, "Attaching yama::quaternion_t to nullptr"); 182 | return reinterpret_cast(ptr); 183 | } 184 | 185 | /////////////////////////////////////////////////////////////////////////// 186 | // access 187 | value_type* data() 188 | { 189 | return reinterpret_cast(this); 190 | } 191 | 192 | constexpr const value_type* data() const 193 | { 194 | return reinterpret_cast(this); 195 | } 196 | 197 | value_type& at(size_type i) 198 | { 199 | YAMA_ASSERT_CRIT(i < value_count, "yama::quaternion_t index overflow"); 200 | return data()[i]; 201 | } 202 | 203 | constexpr const value_type& at(size_type i) const 204 | { 205 | YAMA_ASSERT_CRIT(i < value_count, "yama::quaternion_t index overflow"); 206 | return data()[i]; 207 | } 208 | 209 | value_type& operator[](size_type i) 210 | { 211 | return at(i); 212 | } 213 | 214 | constexpr const value_type& operator[](size_type i) const 215 | { 216 | return at(i); 217 | } 218 | 219 | /////////////////////////// 220 | // cast 221 | 222 | value_type* as_ptr() 223 | { 224 | return data(); 225 | } 226 | 227 | const value_type* as_ptr() const 228 | { 229 | return data(); 230 | } 231 | 232 | template 233 | quaternion_t as_quaternion_t() const 234 | { 235 | return quaternion_t::xyzw(S(x), S(y), S(z), S(w)); 236 | } 237 | 238 | /////////////////////////// 239 | // std 240 | 241 | iterator begin() 242 | { 243 | return data(); 244 | } 245 | 246 | iterator end() 247 | { 248 | return data() + value_count; 249 | } 250 | 251 | const_iterator begin() const 252 | { 253 | return data(); 254 | } 255 | 256 | const_iterator end() const 257 | { 258 | return data() + value_count; 259 | } 260 | 261 | value_type& front() 262 | { 263 | return at(0); 264 | } 265 | 266 | value_type& back() 267 | { 268 | return at(value_count - 1); 269 | } 270 | 271 | constexpr const value_type& front() const 272 | { 273 | return at(0); 274 | } 275 | 276 | constexpr const value_type& back() const 277 | { 278 | return at(value_count - 1); 279 | } 280 | 281 | reverse_iterator rbegin() 282 | { 283 | return reverse_iterator(end()); 284 | } 285 | 286 | const_reverse_iterator rbegin() const 287 | { 288 | return const_reverse_iterator(end()); 289 | } 290 | 291 | reverse_iterator rend() 292 | { 293 | return reverse_iterator(begin()); 294 | } 295 | 296 | const_reverse_iterator rend() const 297 | { 298 | return const_reverse_iterator(begin()); 299 | } 300 | 301 | const_iterator cbegin() const { return begin(); } 302 | const_iterator cend() const { return end(); } 303 | const_reverse_iterator crbegin() const { return rbegin(); } 304 | const_reverse_iterator crend() const { return rend(); } 305 | 306 | /////////////////////////////////////////////////////////////////////////// 307 | // arithmetic 308 | 309 | constexpr const quaternion_t& operator+() const 310 | { 311 | return *this; 312 | } 313 | 314 | constexpr quaternion_t operator-() const 315 | { 316 | return xyzw(-x, -y, -z, -w); 317 | } 318 | 319 | quaternion_t& operator+=(const quaternion_t& b) 320 | { 321 | x += b.x; 322 | y += b.y; 323 | z += b.z; 324 | w += b.w; 325 | return *this; 326 | } 327 | 328 | quaternion_t& operator-=(const quaternion_t& b) 329 | { 330 | x -= b.x; 331 | y -= b.y; 332 | z -= b.z; 333 | w -= b.w; 334 | return *this; 335 | } 336 | 337 | quaternion_t& operator*=(const value_type& s) 338 | { 339 | x *= s; 340 | y *= s; 341 | z *= s; 342 | w *= s; 343 | return *this; 344 | } 345 | 346 | quaternion_t& operator/=(const value_type& s) 347 | { 348 | YAMA_ASSERT_WARN(s != 0, "yama::quaternion_t division by zero"); 349 | x /= s; 350 | y /= s; 351 | z /= s; 352 | w /= s; 353 | return *this; 354 | } 355 | 356 | quaternion_t& operator*=(const quaternion_t& b) 357 | { 358 | auto rx = w*b.x + x*b.w + y*b.z - z*b.y; 359 | auto ry = w*b.y - x*b.z + y*b.w + z*b.x; 360 | auto rz = w*b.z + x*b.y - y*b.x + z*b.w; 361 | auto rw = w*b.w - x*b.x - y*b.y - z*b.z; 362 | x = rx; 363 | y = ry; 364 | z = rz; 365 | w = rw; 366 | return *this; 367 | } 368 | 369 | quaternion_t& operator/=(const quaternion_t& b) 370 | { 371 | auto ls = b.length_sq(); 372 | YAMA_ASSERT_WARN(!close(ls, T(0)), "Dividing by a zero-length yama::quaternion_t"); 373 | auto rx = (-w*b.x + x*b.w - y*b.z + z*b.y) / ls; 374 | auto ry = (-w*b.y + x*b.z + y*b.w - z*b.x) / ls; 375 | auto rz = (-w*b.z - x*b.y + y*b.x + z*b.w) / ls; 376 | auto rw = ( w*b.w + x*b.x + y*b.y + z*b.z) / ls; 377 | x = rx; 378 | y = ry; 379 | z = rz; 380 | w = rw; 381 | return *this; 382 | } 383 | 384 | quaternion_t& mul(const quaternion_t& b) 385 | { 386 | x *= b.x; 387 | y *= b.y; 388 | z *= b.z; 389 | w *= b.w; 390 | return *this; 391 | } 392 | 393 | quaternion_t& div(const quaternion_t& b) 394 | { 395 | x /= b.x; 396 | y /= b.y; 397 | z /= b.z; 398 | w /= b.w; 399 | return *this; 400 | } 401 | 402 | constexpr value_type length_sq() const 403 | { 404 | return sq(x) + sq(y) + sq(z) + sq(w); 405 | } 406 | 407 | value_type length() const 408 | { 409 | return std::sqrt(length_sq()); 410 | } 411 | 412 | quaternion_t& conjugate() 413 | { 414 | x = -x; 415 | y = -y; 416 | z = -z; 417 | return *this; 418 | } 419 | 420 | quaternion_t& inverse() 421 | { 422 | YAMA_ASSERT_WARN(!close(length_sq(), value_type(0)), "Invering a zero-length yama::quaternion_t"); 423 | auto ls = length_sq(); 424 | x /= -ls; 425 | y /= -ls; 426 | z /= -ls; 427 | w /= ls; 428 | return *this; 429 | } 430 | 431 | value_type normalize() 432 | { 433 | auto l = length(); 434 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::quaternion_t"); 435 | x /= l; 436 | y /= l; 437 | z /= l; 438 | w /= l; 439 | return l; 440 | } 441 | 442 | bool is_normalized() const 443 | { 444 | return close(length(), value_type(1)); 445 | } 446 | 447 | void to_axis_angle(vector3_t& out_axis, value_type& out_angle) const 448 | { 449 | out_angle = 2 * std::acos(w); 450 | 451 | value_type scale = value_type(1) - sq(w); 452 | 453 | if (scale < constants_t::EPSILON) 454 | { 455 | out_axis = vector3_t::coord(0, 0, 1); 456 | } 457 | else 458 | { 459 | scale = 1 / std::sqrt(scale); 460 | out_axis = vector3_t::coord(x*scale, y*scale, z*scale); 461 | } 462 | } 463 | }; 464 | 465 | template 466 | quaternion_t operator+(const quaternion_t& a, const quaternion_t& b) 467 | { 468 | return quaternion_t::xyzw(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); 469 | } 470 | 471 | template 472 | quaternion_t operator-(const quaternion_t& a, const quaternion_t& b) 473 | { 474 | return quaternion_t::xyzw(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); 475 | } 476 | 477 | template 478 | quaternion_t operator*(const quaternion_t& a, const T& s) 479 | { 480 | return quaternion_t::xyzw(a.x * s, a.y * s, a.z * s, a.w * s); 481 | } 482 | 483 | template 484 | quaternion_t operator*(const T& s, const quaternion_t& b) 485 | { 486 | return quaternion_t::xyzw(s * b.x, s * b.y, s * b.z, s * b.w); 487 | } 488 | 489 | template 490 | quaternion_t operator/(const quaternion_t& a, const T& s) 491 | { 492 | YAMA_ASSERT_WARN(s != 0, "yama::quaternion_t division by zero"); 493 | return quaternion_t::xyzw(a.x / s, a.y / s, a.z / s, a.w / s); 494 | } 495 | 496 | template 497 | quaternion_t operator/(const T& s, const quaternion_t& b) 498 | { 499 | return quaternion_t::xyzw(s / b.x, s / b.y, s / b.z, s / b.w); 500 | } 501 | 502 | template 503 | quaternion_t operator*(const quaternion_t& a, const quaternion_t& b) 504 | { 505 | return quaternion_t::xyzw( 506 | a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y, 507 | a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x, 508 | a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w, 509 | a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z 510 | ); 511 | } 512 | 513 | template 514 | quaternion_t operator/(const quaternion_t& a, const quaternion_t& b) 515 | { 516 | auto ls = b.length_sq(); 517 | YAMA_ASSERT_WARN(!close(ls, T(0)), "Dividing by a zero-length yama::quaternion_t"); 518 | return quaternion_t::xyzw( 519 | (-a.w*b.x + a.x*b.w - a.y*b.z + a.z*b.y) / ls, 520 | (-a.w*b.y + a.x*b.z + a.y*b.w - a.z*b.x) / ls, 521 | (-a.w*b.z - a.x*b.y + a.y*b.x + a.z*b.w) / ls, 522 | ( a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z) / ls 523 | ); 524 | } 525 | 526 | template 527 | bool operator==(const quaternion_t& a, const quaternion_t& b) 528 | { 529 | return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; 530 | } 531 | 532 | template 533 | bool operator!=(const quaternion_t& a, const quaternion_t& b) 534 | { 535 | return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; 536 | } 537 | 538 | template 539 | bool close(const quaternion_t& a, const quaternion_t& b, const T& epsilon = constants_t::EPSILON) 540 | { 541 | return close(a.x, b.x, epsilon) && close(a.y, b.y, epsilon) && close(a.z, b.z, epsilon) && close(a.w, b.w, epsilon); 542 | } 543 | 544 | template 545 | quaternion_t abs(const quaternion_t& a) 546 | { 547 | return quaternion_t::xyzw(std::abs(a.x), std::abs(a.y), std::abs(a.z), std::abs(a.w)); 548 | } 549 | 550 | template 551 | quaternion_t mul(const quaternion_t& a, const quaternion_t& b) 552 | { 553 | return quaternion_t::xyzw(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); 554 | } 555 | 556 | template 557 | quaternion_t div(const quaternion_t& a, const quaternion_t& b) 558 | { 559 | YAMA_ASSERT_WARN(b.x != 0, "yama::quaternion_t division by zero"); 560 | YAMA_ASSERT_WARN(b.y != 0, "yama::quaternion_t division by zero"); 561 | YAMA_ASSERT_WARN(b.z != 0, "yama::quaternion_t division by zero"); 562 | YAMA_ASSERT_WARN(b.w != 0, "yama::quaternion_t division by zero"); 563 | return quaternion_t::xyzw(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); 564 | } 565 | 566 | template 567 | typename std::enable_if::value, 568 | quaternion_t>::type mod(const quaternion_t& n, const quaternion_t& d) 569 | { 570 | YAMA_ASSERT_WARN(d.x != 0, "yama::quaternion_t division by zero"); 571 | YAMA_ASSERT_WARN(d.y != 0, "yama::quaternion_t division by zero"); 572 | YAMA_ASSERT_WARN(d.z != 0, "yama::quaternion_t division by zero"); 573 | YAMA_ASSERT_WARN(d.w != 0, "yama::quaternion_t division by zero"); 574 | return quaternion_t::xyzw(fmod(n.x, d.x), fmod(n.y, d.y), fmod(n.z, d.z), fmod(n.w, d.w)); 575 | } 576 | 577 | template 578 | typename std::enable_if::value, 579 | quaternion_t>::type mod(const quaternion_t& n, const quaternion_t& d) 580 | { 581 | YAMA_ASSERT_WARN(d.x != 0, "yama::quaternion_t division by zero"); 582 | YAMA_ASSERT_WARN(d.y != 0, "yama::quaternion_t division by zero"); 583 | YAMA_ASSERT_WARN(d.z != 0, "yama::quaternion_t division by zero"); 584 | YAMA_ASSERT_WARN(d.w != 0, "yama::quaternion_t division by zero"); 585 | 586 | return quaternion_t::xyzw(n.x % d.x, n.y % d.y, n.z % d.z, n.w % d.w); 587 | } 588 | 589 | template 590 | quaternion_t floor(const quaternion_t& a) 591 | { 592 | return quaternion_t::xyzw(::std::floor(a.x), ::std::floor(a.y), ::std::floor(a.z), ::std::floor(a.w)); 593 | } 594 | 595 | template 596 | quaternion_t ceil(const quaternion_t& a) 597 | { 598 | return quaternion_t::xyzw(::std::ceil(a.x), ::std::ceil(a.y), ::std::ceil(a.z), ::std::ceil(a.w)); 599 | } 600 | 601 | template 602 | quaternion_t round(const quaternion_t& a) 603 | { 604 | return quaternion_t::xyzw(::std::round(a.x), ::std::round(a.y), ::std::round(a.z), ::std::round(a.w)); 605 | } 606 | 607 | 608 | template 609 | quaternion_t frac(const quaternion_t& a) 610 | { 611 | auto x = ::std::abs(a.x); 612 | auto y = ::std::abs(a.y); 613 | auto z = ::std::abs(a.z); 614 | auto w = ::std::abs(a.w); 615 | return quaternion_t::xyzw(x - ::std::floor(x), y - ::std::floor(y), z - ::std::floor(z), w - ::std::floor(w)); 616 | } 617 | 618 | template 619 | bool isfinite(const quaternion_t& a) 620 | { 621 | return std::isfinite(a.x) && std::isfinite(a.y) && std::isfinite(a.z) && std::isfinite(a.w); 622 | } 623 | 624 | template 625 | quaternion_t sign(const quaternion_t& a) 626 | { 627 | return quaternion_t::xyzw(sign(a.x), sign(a.y), sign(a.z), sign(a.w)); 628 | } 629 | 630 | template 631 | quaternion_t clamp(const quaternion_t& v, const quaternion_t& min, const quaternion_t& max) 632 | { 633 | return quaternion_t::xyzw(clamp(v.x, min.x, max.x), clamp(v.y, min.y, max.y), clamp(v.z, min.z, max.z), clamp(v.w, min.w, max.w)); 634 | } 635 | 636 | #if !defined(min) 637 | template 638 | quaternion_t min(const quaternion_t& a, const quaternion_t& b) 639 | { 640 | return quaternion_t::xyzw(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z), std::min(a.w, b.w)); 641 | } 642 | #endif 643 | 644 | #if !defined(max) 645 | template 646 | quaternion_t max(const quaternion_t& a, const quaternion_t& b) 647 | { 648 | return quaternion_t::xyzw(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z), std::max(a.w, b.w)); 649 | } 650 | #endif 651 | 652 | template 653 | T dot(const quaternion_t& a, const quaternion_t& b) 654 | { 655 | return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; 656 | } 657 | 658 | template 659 | quaternion_t normalize(const quaternion_t& a) 660 | { 661 | auto l = a.length(); 662 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::quaternion_t"); 663 | return quaternion_t::xyzw(a.x / l, a.y / l, a.z / l, a.w / l); 664 | } 665 | 666 | template 667 | quaternion_t lerp(const quaternion_t& from, const quaternion_t& to, const T& ratio) 668 | { 669 | YAMA_ASSERT_WARN(ratio >= 0, "yama::quaternion_t lerp is defined between 0 and 1 "); 670 | YAMA_ASSERT_WARN(ratio <= 1, "yama::quaternion_t lerp is defined between 0 and 1 "); 671 | auto q = from + ratio * (to - from); 672 | return normalize(q); 673 | } 674 | 675 | template 676 | quaternion_t slerp(const quaternion_t& from, const quaternion_t& to, T ratio) 677 | { 678 | T cos_angle = dot(from, to); 679 | 680 | // if the angle is small (ie cos_angle is close to -1 or 1), 681 | // could use lerp, instead of slerp: 682 | // if(cos_angle < -0.99 || cos_angle > 0.99) return lerp(from, to, ratio) 683 | // need to test how this works 684 | 685 | T angle = std::acos(cos_angle); 686 | 687 | return (from*std::sin((1 - ratio)*angle) + to*std::sin(angle*ratio)) / std::sin(angle); 688 | } 689 | 690 | template 691 | quaternion_t conjugate(const quaternion_t& a) 692 | { 693 | return quaternion_t::xyzw(-a.x, -a.y, -a.z, a.w); 694 | } 695 | 696 | template 697 | quaternion_t inverse(const quaternion_t& a) 698 | { 699 | YAMA_ASSERT_WARN(!close(a.length_sq(), T(0)), "Invering a zero-length yama::quaternion_t"); 700 | auto ls = a.length_sq(); 701 | return quaternion_t::xyzw(-a.x/ls, -a.y/ls, -a.z/ls, a.w/ls); 702 | } 703 | 704 | template 705 | vector3_t rotate(const vector3_t& v, const quaternion_t& q) 706 | { 707 | // c1 = q x v 708 | auto c1 = vector3_t::coord( 709 | q.y*v.z - q.z*v.y, 710 | q.z*v.x - q.x*v.z, 711 | q.x*v.y - q.y*v.x 712 | ); 713 | // c2 = q x (q x v) 714 | auto c2 = vector3_t::coord( 715 | -sq(q.y)*v.x - sq(q.z)*v.x + q.x*q.y*v.y + q.x*q.z*v.z, 716 | q.x*q.y*v.x - sq(q.x)*v.y - sq(q.z)*v.y + q.y*q.z*v.z, 717 | q.x*q.z*v.x + q.y*q.z*v.y - sq(q.x)*v.z - sq(q.y)*v.z 718 | ); 719 | 720 | return v + ((c1 * q.w) + c2) * T(2); 721 | } 722 | 723 | // type traits 724 | template 725 | struct is_yama> : public std::true_type {}; 726 | 727 | // shorthand 728 | #if !defined(YAMA_NO_SHORTHAND) 729 | 730 | using quaternion = quaternion_t; 731 | 732 | #endif 733 | 734 | } 735 | -------------------------------------------------------------------------------- /include/yama/shorthand.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #if !defined(YAMA_NO_SHORTHAND) 7 | 8 | #if !defined(YAMA_PREFERRRED_TYPE) 9 | #define YAMA_PREFFERED_TYPE float 10 | #endif 11 | 12 | namespace yama 13 | { 14 | 15 | using preferred_type = YAMA_PREFFERED_TYPE; 16 | 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /include/yama/type_traits.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace yama 9 | { 10 | 11 | template 12 | struct is_yama : public std::false_type {}; 13 | 14 | template 15 | struct is_vector : public std::false_type {}; 16 | 17 | template 18 | struct is_matrix : public std::false_type {}; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /include/yama/util.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | 9 | #include "assert.hpp" 10 | #include "shorthand.hpp" 11 | 12 | namespace yama 13 | { 14 | 15 | template 16 | class constants_t 17 | { 18 | public: 19 | static inline constexpr T PI = T(3.1415926535897932384626433832795); 20 | static inline constexpr T PI_HALF = T(1.5707963267948966192313216916398); 21 | static inline constexpr T PI_D4 = T(0.78539816339744830961566084581988); // pi / 4 22 | static inline constexpr T PI_DBL = T(6.283185307179586476925286766559); // 2*pi 23 | static inline constexpr T OVER_PI = T(0.31830988618379067153776752674503); // 1/pi 24 | static inline constexpr T E = T(2.71828182845904523536028747135266249); //natural constant 25 | static inline constexpr T SQRT_2 = T(1.4142135623730950488016887242097); //sqrt(2) 26 | 27 | static inline constexpr T EPSILON = T(1e-5); //epsilon for floating point equalities 28 | static inline constexpr T EPSILON_LOW = T(1e-3); //low precision epsilon 29 | static inline constexpr T EPSILON_HIGH = T(1e-7); //high precision epsilon 30 | }; 31 | 32 | // shorthand 33 | #if !defined(YAMA_NO_SHORTHAND) 34 | using constants = constants_t; 35 | #endif 36 | 37 | template 38 | constexpr T sq(const T& a) 39 | { 40 | return a*a; 41 | } 42 | 43 | template 44 | typename std::enable_if::value, 45 | T>::type sign(const T& t) 46 | { 47 | return std::signbit(t) ? T(-1) : T(1); 48 | } 49 | 50 | template 51 | typename std::enable_if::value && std::is_integral::value, 52 | T>::type sign(const T& t) 53 | { 54 | return t < 0 ? T(-1) : T(1); 55 | } 56 | 57 | template 58 | typename std::enable_if::value, 59 | T>::type sign(const T& ) 60 | { 61 | return 1; 62 | } 63 | 64 | template 65 | void flip_sign(T& a) 66 | { 67 | a = -a; 68 | } 69 | 70 | template 71 | T lerp(const T& from, const T& to, const S& ratio) 72 | { 73 | return from + ratio * (to - from); 74 | } 75 | 76 | template 77 | T rad_to_deg(const T& radians) 78 | { 79 | return radians * (T(180) / constants_t::PI); 80 | } 81 | 82 | template 83 | T deg_to_rad(const T& degrees) 84 | { 85 | return degrees * (constants_t::PI / T(180)); 86 | } 87 | 88 | template 89 | typename std::enable_if::value, 90 | bool>::type close(const T& a, const T& b, const T& epsilon = constants_t::EPSILON) 91 | { 92 | return !(std::abs(a - b) > epsilon); 93 | } 94 | 95 | 96 | template 97 | typename std::enable_if::value, 98 | const T&>::type clamp(const T& v, const T& min, const T& max) 99 | { 100 | if (min < max) 101 | { 102 | if (v < min) 103 | return min; 104 | 105 | if (v > max) 106 | return max; 107 | } 108 | else 109 | { 110 | if (v < max) 111 | return max; 112 | 113 | if (v > min) 114 | return min; 115 | } 116 | 117 | return v; 118 | } 119 | 120 | // can be used to make a type with defined `U at(integral n)` be used as a key in a std::map or in a std::set 121 | struct strict_weak_ordering 122 | { 123 | template 124 | bool operator()(const T& a, const T& b) 125 | { 126 | YAMA_ASSERT_CRIT(a.size() == b.size(), "yama::strict_weak_ordering of types with no matching sizes"); 127 | for (size_t i = 0; i < a.size(); ++i) 128 | { 129 | if (a.at(i) < b.at(i)) 130 | return true; 131 | else if (a.at(i) > b.at(i)) 132 | return false; 133 | } 134 | 135 | return false; 136 | } 137 | }; 138 | 139 | } 140 | -------------------------------------------------------------------------------- /include/yama/vector2.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "util.hpp" 12 | #include "shorthand.hpp" 13 | #include "type_traits.hpp" 14 | 15 | namespace yama 16 | { 17 | 18 | template 19 | class vector3_t; 20 | 21 | template 22 | class vector4_t; 23 | 24 | template 25 | class vector2_t 26 | { 27 | public: 28 | T x, y; 29 | 30 | using value_type = T; 31 | using size_type = size_t; 32 | using iterator = T*; 33 | using const_iterator = const T*; 34 | using reverse_iterator = typename std::reverse_iterator; 35 | using const_reverse_iterator = typename std::reverse_iterator; 36 | 37 | static constexpr size_type value_count = 2; 38 | 39 | constexpr size_type max_size() const { return value_count; } 40 | constexpr size_type size() const { return max_size(); } 41 | 42 | /////////////////////////////////////////////////////////////////////////// 43 | // named constructors 44 | static constexpr vector2_t coord(const value_type& x, const value_type& y) 45 | { 46 | return{ x, y }; 47 | } 48 | 49 | static constexpr vector2_t uniform(const value_type& s) 50 | { 51 | return coord(s, s); 52 | } 53 | 54 | static constexpr vector2_t zero() 55 | { 56 | return uniform(value_type(0)); 57 | } 58 | 59 | static vector2_t from_ptr(const value_type* ptr) 60 | { 61 | YAMA_ASSERT_CRIT(ptr, "Constructing yama::vector2_t from nullptr"); 62 | return coord(ptr[0], ptr[1]); 63 | } 64 | 65 | static constexpr vector2_t unit_x() 66 | { 67 | return coord(1, 0); 68 | } 69 | 70 | static constexpr vector2_t unit_y() 71 | { 72 | return coord(0, 1); 73 | } 74 | 75 | /////////////////////////// 76 | // attach 77 | static vector2_t& attach_to_ptr(value_type* ptr) 78 | { 79 | YAMA_ASSERT_BAD(ptr, "Attaching yama::vector2_t to nullptr"); 80 | return *reinterpret_cast(ptr); 81 | } 82 | 83 | static const vector2_t& attach_to_ptr(const value_type* ptr) 84 | { 85 | YAMA_ASSERT_BAD(ptr, "Attaching yama::vector2_t to nullptr"); 86 | return *reinterpret_cast(ptr); 87 | } 88 | 89 | static vector2_t* attach_to_array(value_type* ptr) 90 | { 91 | YAMA_ASSERT_WARN(ptr, "Attaching yama::vector2_t to nullptr"); 92 | return reinterpret_cast(ptr); 93 | } 94 | 95 | static const vector2_t* attach_to_array(const value_type* ptr) 96 | { 97 | YAMA_ASSERT_WARN(ptr, "Attaching yama::vector2_t to nullptr"); 98 | return reinterpret_cast(ptr); 99 | } 100 | 101 | /////////////////////////////////////////////////////////////////////////// 102 | // access 103 | value_type* data() 104 | { 105 | return reinterpret_cast(this); 106 | } 107 | 108 | constexpr const value_type* data() const 109 | { 110 | return reinterpret_cast(this); 111 | } 112 | 113 | value_type& at(size_type i) 114 | { 115 | YAMA_ASSERT_CRIT(i < value_count, "yama::vector2_t index overflow"); 116 | return data()[i]; 117 | } 118 | 119 | constexpr const value_type& at(size_type i) const 120 | { 121 | YAMA_ASSERT_CRIT(i < value_count, "yama::vector2_t index overflow"); 122 | return data()[i]; 123 | } 124 | 125 | value_type& operator[](size_type i) 126 | { 127 | return at(i); 128 | } 129 | 130 | constexpr const value_type& operator[](size_type i) const 131 | { 132 | return at(i); 133 | } 134 | 135 | /////////////////////////// 136 | // cast 137 | 138 | value_type* as_ptr() 139 | { 140 | return data(); 141 | } 142 | 143 | const value_type* as_ptr() const 144 | { 145 | return data(); 146 | } 147 | 148 | template 149 | vector2_t as_vector2_t() const 150 | { 151 | return vector2_t::coord(S(x), S(y)); 152 | } 153 | 154 | 155 | vector2_t& xy() { return *this; } 156 | const vector2_t& xy() const { return *this; } 157 | vector2_t yx() const { coord(y, x); } 158 | vector3_t xyz(const value_type& z = 0) const; 159 | vector4_t xyzw(const value_type& z = 0, const value_type& w = 0) const; 160 | 161 | /////////////////////////// 162 | // std 163 | 164 | iterator begin() 165 | { 166 | return data(); 167 | } 168 | 169 | iterator end() 170 | { 171 | return data() + value_count; 172 | } 173 | 174 | const_iterator begin() const 175 | { 176 | return data(); 177 | } 178 | 179 | const_iterator end() const 180 | { 181 | return data() + value_count; 182 | } 183 | 184 | value_type& front() 185 | { 186 | return at(0); 187 | } 188 | 189 | value_type& back() 190 | { 191 | return at(value_count - 1); 192 | } 193 | 194 | constexpr const value_type& front() const 195 | { 196 | return at(0); 197 | } 198 | 199 | constexpr const value_type& back() const 200 | { 201 | return at(value_count - 1); 202 | } 203 | 204 | reverse_iterator rbegin() 205 | { 206 | return reverse_iterator(end()); 207 | } 208 | 209 | const_reverse_iterator rbegin() const 210 | { 211 | return const_reverse_iterator(end()); 212 | } 213 | 214 | reverse_iterator rend() 215 | { 216 | return reverse_iterator(begin()); 217 | } 218 | 219 | const_reverse_iterator rend() const 220 | { 221 | return const_reverse_iterator(begin()); 222 | } 223 | 224 | const_iterator cbegin() const { return begin(); } 225 | const_iterator cend() const { return end(); } 226 | const_reverse_iterator crbegin() const { return rbegin(); } 227 | const_reverse_iterator crend() const { return rend(); } 228 | 229 | /////////////////////////////////////////////////////////////////////////// 230 | // arithmetic 231 | 232 | constexpr const vector2_t& operator+() const 233 | { 234 | return *this; 235 | } 236 | 237 | constexpr vector2_t operator-() const 238 | { 239 | return coord(-x, -y); 240 | } 241 | 242 | vector2_t& operator+=(const vector2_t& b) 243 | { 244 | x += b.x; 245 | y += b.y; 246 | return *this; 247 | } 248 | 249 | vector2_t& operator-=(const vector2_t& b) 250 | { 251 | x -= b.x; 252 | y -= b.y; 253 | return *this; 254 | } 255 | 256 | vector2_t& operator*=(const value_type& s) 257 | { 258 | x *= s; 259 | y *= s; 260 | return *this; 261 | } 262 | 263 | vector2_t& operator/=(const value_type& s) 264 | { 265 | YAMA_ASSERT_WARN(s != 0, "yama::vector2_t division by zero"); 266 | x /= s; 267 | y /= s; 268 | return *this; 269 | } 270 | 271 | vector2_t& mul(const vector2_t& b) 272 | { 273 | x *= b.x; 274 | y *= b.y; 275 | return *this; 276 | } 277 | 278 | vector2_t& div(const vector2_t& b) 279 | { 280 | x /= b.x; 281 | y /= b.y; 282 | return *this; 283 | } 284 | 285 | constexpr value_type length_sq() const 286 | { 287 | return sq(x) + sq(y); 288 | } 289 | 290 | value_type length() const 291 | { 292 | return std::sqrt(length_sq()); 293 | } 294 | 295 | constexpr value_type manhattan_length() const 296 | { 297 | return std::abs(x) + std::abs(y); 298 | } 299 | 300 | value_type normalize() 301 | { 302 | auto l = length(); 303 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector2_t"); 304 | x /= l; 305 | y /= l; 306 | return l; 307 | } 308 | 309 | bool is_normalized() const 310 | { 311 | return close(length(), value_type(1)); 312 | } 313 | 314 | void homogenous_normalize() 315 | { 316 | YAMA_ASSERT_WARN(y != 0, "Homogenous normalization of yama::vector2_t with zero y"); 317 | x /= y; 318 | y = 1; 319 | } 320 | 321 | vector2_t reflection(const vector2_t& normal) const 322 | { 323 | YAMA_ASSERT_WARN(normal.is_normalized(), "Reflecting with a non-normalized normal yama::vector2_t"); 324 | auto dd = 2 * dot(*this, normal); 325 | return coord(x - dd * normal.x, y - dd * normal.y); 326 | } 327 | 328 | vector2_t get_orthogonal() const 329 | { 330 | YAMA_ASSERT_WARN(!close(*this, zero()), "finding an orthogonal of a zero vector2_t"); 331 | return coord(-y, x); 332 | } 333 | 334 | constexpr value_type product() const 335 | { 336 | return x * y; 337 | } 338 | 339 | constexpr value_type sum() const 340 | { 341 | return x + y; 342 | } 343 | }; 344 | 345 | template 346 | vector2_t operator+(const vector2_t& a, const vector2_t& b) 347 | { 348 | return vector2_t::coord(a.x + b.x, a.y + b.y); 349 | } 350 | 351 | template 352 | vector2_t operator-(const vector2_t& a, const vector2_t& b) 353 | { 354 | return vector2_t::coord(a.x - b.x, a.y - b.y); 355 | } 356 | 357 | template 358 | vector2_t operator*(const vector2_t& a, const T& s) 359 | { 360 | return vector2_t::coord(a.x * s, a.y * s); 361 | } 362 | 363 | template 364 | vector2_t operator*(const T& s, const vector2_t& b) 365 | { 366 | return vector2_t::coord(s * b.x, s * b.y); 367 | } 368 | 369 | template 370 | vector2_t operator/(const vector2_t& a, const T& s) 371 | { 372 | YAMA_ASSERT_WARN(s != 0, "yama::vector2_t division by zero"); 373 | return vector2_t::coord(a.x / s, a.y / s); 374 | } 375 | 376 | template 377 | vector2_t operator/(const T& s, const vector2_t& b) 378 | { 379 | return vector2_t::coord(s / b.x, s / b.y); 380 | } 381 | 382 | template 383 | bool operator==(const vector2_t& a, const vector2_t& b) 384 | { 385 | return a.x == b.x && a.y == b.y; 386 | } 387 | 388 | template 389 | bool operator!=(const vector2_t& a, const vector2_t& b) 390 | { 391 | return a.x != b.x || a.y != b.y; 392 | } 393 | 394 | template 395 | bool close(const vector2_t& a, const vector2_t& b, const T& epsilon = constants_t::EPSILON) 396 | { 397 | return close(a.x, b.x, epsilon) && close(a.y, b.y, epsilon); 398 | } 399 | 400 | template 401 | vector2_t abs(const vector2_t& a) 402 | { 403 | return vector2_t::coord(std::abs(a.x), std::abs(a.y)); 404 | } 405 | 406 | template 407 | vector2_t mul(const vector2_t& a, const vector2_t& b) 408 | { 409 | return vector2_t::coord(a.x * b.x, a.y * b.y); 410 | } 411 | 412 | template 413 | vector2_t div(const vector2_t& a, const vector2_t& b) 414 | { 415 | YAMA_ASSERT_WARN(b.x != 0, "yama::vector2_t division by zero"); 416 | YAMA_ASSERT_WARN(b.y != 0, "yama::vector2_t division by zero"); 417 | return vector2_t::coord(a.x / b.x, a.y / b.y); 418 | } 419 | 420 | template 421 | typename std::enable_if::value, 422 | vector2_t>::type mod(const vector2_t& n, const vector2_t& d) 423 | { 424 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector2_t division by zero"); 425 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector2_t division by zero"); 426 | return vector2_t::coord(fmod(n.x, d.x), fmod(n.y, d.y)); 427 | } 428 | 429 | template 430 | typename std::enable_if::value, 431 | vector2_t>::type mod(const vector2_t& n, const vector2_t& d) 432 | { 433 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector2_t division by zero"); 434 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector2_t division by zero"); 435 | return vector2_t::coord(n.x % d.x, n.y % d.y); 436 | } 437 | 438 | template 439 | vector2_t floor(const vector2_t& a) 440 | { 441 | return vector2_t::coord(::std::floor(a.x), ::std::floor(a.y)); 442 | } 443 | 444 | template 445 | vector2_t ceil(const vector2_t& a) 446 | { 447 | return vector2_t::coord(::std::ceil(a.x), ::std::ceil(a.y)); 448 | } 449 | 450 | template 451 | vector2_t round(const vector2_t& a) 452 | { 453 | return vector2_t::coord(::std::round(a.x), ::std::round(a.y)); 454 | } 455 | 456 | 457 | template 458 | vector2_t frac(const vector2_t& a) 459 | { 460 | auto x = ::std::abs(a.x); 461 | auto y = ::std::abs(a.y); 462 | return vector2_t::coord(x - ::std::floor(x), y - ::std::floor(y)); 463 | } 464 | 465 | template 466 | bool isfinite(const vector2_t& a) 467 | { 468 | return std::isfinite(a.x) && std::isfinite(a.y); 469 | } 470 | 471 | template 472 | vector2_t sign(const vector2_t& a) 473 | { 474 | return vector2_t::coord(sign(a.x), sign(a.y)); 475 | } 476 | 477 | template 478 | vector2_t clamp(const vector2_t& v, const vector2_t& min, const vector2_t& max) 479 | { 480 | return vector2_t::coord(clamp(v.x, min.x, max.x), clamp(v.y, min.y, max.y)); 481 | } 482 | 483 | #if !defined(min) 484 | template 485 | vector2_t min(const vector2_t& a, const vector2_t& b) 486 | { 487 | return vector2_t::coord(std::min(a.x, b.x), std::min(a.y, b.y)); 488 | } 489 | #endif 490 | 491 | #if !defined(max) 492 | template 493 | vector2_t max(const vector2_t& a, const vector2_t& b) 494 | { 495 | return vector2_t::coord(std::max(a.x, b.x), std::max(a.y, b.y)); 496 | } 497 | #endif 498 | 499 | template 500 | T dot(const vector2_t& a, const vector2_t& b) 501 | { 502 | return a.x * b.x + a.y * b.y; 503 | } 504 | 505 | template 506 | T cross_magnitude(const vector2_t& a, const vector2_t& b) 507 | { 508 | return a.x * b.y - a.y * b.x; 509 | } 510 | 511 | template 512 | T distance_sq(const vector2_t& a, const vector2_t& b) 513 | { 514 | return sq(a.x - b.x) + sq(a.y - b.y); 515 | } 516 | 517 | template 518 | T distance(const vector2_t& a, const vector2_t& b) 519 | { 520 | return std::sqrt(distance_sq(a, b)); 521 | } 522 | 523 | template 524 | vector2_t normalize(const vector2_t& a) 525 | { 526 | auto l = a.length(); 527 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector2_t"); 528 | return vector2_t::coord(a.x / l, a.y / l); 529 | } 530 | 531 | template 532 | bool orthogonal(const vector2_t& a, const vector2_t& b) 533 | { 534 | return close(dot(a, b), T(0)); 535 | } 536 | 537 | template 538 | bool collinear(const vector2_t& a, const vector2_t& b) 539 | { 540 | if (close(b.x, T(0))) 541 | { 542 | if (!close(a.x, T(0))) 543 | { 544 | return false; 545 | } 546 | 547 | if (close(b.y, T(0))) 548 | { 549 | return false; 550 | } 551 | 552 | return true; 553 | } 554 | else if (close(b.y, T(0))) 555 | { 556 | if (close(a.x, T(0))) 557 | { 558 | return false; 559 | } 560 | 561 | return close(a.y, T(0)); 562 | } 563 | else 564 | { 565 | return close(a.x / b.x, a.y / b.y); 566 | } 567 | } 568 | 569 | // type traits 570 | template 571 | struct is_yama> : public std::true_type {}; 572 | 573 | template 574 | struct is_vector> : public std::true_type {}; 575 | 576 | // casts 577 | template 578 | V2_U vector_cast(const vector2_t& v) 579 | { 580 | using U = typename V2_U::value_type; 581 | return {U(v.x), U(v.y)}; 582 | } 583 | 584 | // shorthand 585 | #if !defined(YAMA_NO_SHORTHAND) 586 | 587 | using vector2 = vector2_t; 588 | using point2 = vector2; 589 | 590 | constexpr vector2 v(preferred_type x, preferred_type y) 591 | { 592 | return vector2::coord(x, y); 593 | } 594 | 595 | template 596 | constexpr vector2_t vt(const T& x, const T& y) 597 | { 598 | return vector2_t::coord(x, y); 599 | } 600 | 601 | #endif 602 | 603 | } 604 | -------------------------------------------------------------------------------- /include/yama/vector3.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "util.hpp" 12 | #include "shorthand.hpp" 13 | #include "type_traits.hpp" 14 | 15 | namespace yama 16 | { 17 | 18 | template 19 | class vector2_t; 20 | 21 | template 22 | class vector4_t; 23 | 24 | template 25 | class vector3_t 26 | { 27 | public: 28 | T x, y, z; 29 | 30 | using value_type = T; 31 | using size_type = size_t; 32 | using iterator = T*; 33 | using const_iterator = const T*; 34 | using reverse_iterator = typename std::reverse_iterator; 35 | using const_reverse_iterator = typename std::reverse_iterator; 36 | 37 | static constexpr size_type value_count = 3; 38 | 39 | constexpr size_type max_size() const { return value_count; } 40 | constexpr size_type size() const { return max_size(); } 41 | 42 | /////////////////////////////////////////////////////////////////////////// 43 | // named constructors 44 | static constexpr vector3_t coord(const value_type& x, const value_type& y, const value_type& z) 45 | { 46 | return {x, y, z}; 47 | } 48 | 49 | static constexpr vector3_t uniform(const value_type& s) 50 | { 51 | return coord(s, s, s); 52 | } 53 | 54 | static constexpr vector3_t zero() 55 | { 56 | return uniform(value_type(0)); 57 | } 58 | 59 | static vector3_t from_ptr(const value_type* ptr) 60 | { 61 | YAMA_ASSERT_CRIT(ptr, "Constructing yama::vector3_t from nullptr"); 62 | return coord(ptr[0], ptr[1], ptr[2]); 63 | } 64 | 65 | static constexpr vector3_t unit_x() 66 | { 67 | return coord(1, 0, 0); 68 | } 69 | 70 | static constexpr vector3_t unit_y() 71 | { 72 | return coord(0, 1, 0); 73 | } 74 | 75 | static constexpr vector3_t unit_z() 76 | { 77 | return coord(0, 0, 1); 78 | } 79 | 80 | /////////////////////////// 81 | // attach 82 | static vector3_t& attach_to_ptr(value_type* ptr) 83 | { 84 | YAMA_ASSERT_BAD(ptr, "Attaching yama::vector3_t to nullptr"); 85 | return *reinterpret_cast(ptr); 86 | } 87 | 88 | static const vector3_t& attach_to_ptr(const value_type* ptr) 89 | { 90 | YAMA_ASSERT_BAD(ptr, "Attaching yama::vector3_t to nullptr"); 91 | return *reinterpret_cast(ptr); 92 | } 93 | 94 | static vector3_t* attach_to_array(value_type* ptr) 95 | { 96 | YAMA_ASSERT_WARN(ptr, "Attaching yama::vector3_t to nullptr"); 97 | return reinterpret_cast(ptr); 98 | } 99 | 100 | static const vector3_t* attach_to_array(const value_type* ptr) 101 | { 102 | YAMA_ASSERT_WARN(ptr, "Attaching yama::vector3_t to nullptr"); 103 | return reinterpret_cast(ptr); 104 | } 105 | 106 | /////////////////////////////////////////////////////////////////////////// 107 | // access 108 | value_type* data() 109 | { 110 | return reinterpret_cast(this); 111 | } 112 | 113 | constexpr const value_type* data() const 114 | { 115 | return reinterpret_cast(this); 116 | } 117 | 118 | value_type& at(size_type i) 119 | { 120 | YAMA_ASSERT_CRIT(i < value_count, "yama::vector3_t index overflow"); 121 | return data()[i]; 122 | } 123 | 124 | constexpr const value_type& at(size_type i) const 125 | { 126 | YAMA_ASSERT_CRIT(i < value_count, "yama::vector3_t index overflow"); 127 | return data()[i]; 128 | } 129 | 130 | value_type& operator[](size_type i) 131 | { 132 | return at(i); 133 | } 134 | 135 | constexpr const value_type& operator[](size_type i) const 136 | { 137 | return at(i); 138 | } 139 | 140 | /////////////////////////// 141 | // cast 142 | 143 | value_type* as_ptr() 144 | { 145 | return data(); 146 | } 147 | 148 | const value_type* as_ptr() const 149 | { 150 | return data(); 151 | } 152 | 153 | template 154 | vector3_t as_vector3_t() const 155 | { 156 | return vector3_t::coord(S(x), S(y), S(z)); 157 | } 158 | 159 | 160 | vector2_t& xy(); 161 | const vector2_t& xy() const; 162 | vector2_t xz() const; 163 | vector3_t& xyz() { return *this; } 164 | const vector3_t& xyz() const { return *this; } 165 | vector3_t zyx() const { return coord(z, y, x); } 166 | vector4_t xyzw(const value_type& w = 0) const; 167 | 168 | /////////////////////////// 169 | // std 170 | 171 | iterator begin() 172 | { 173 | return data(); 174 | } 175 | 176 | iterator end() 177 | { 178 | return data() + value_count; 179 | } 180 | 181 | const_iterator begin() const 182 | { 183 | return data(); 184 | } 185 | 186 | const_iterator end() const 187 | { 188 | return data() + value_count; 189 | } 190 | 191 | value_type& front() 192 | { 193 | return at(0); 194 | } 195 | 196 | value_type& back() 197 | { 198 | return at(value_count - 1); 199 | } 200 | 201 | constexpr const value_type& front() const 202 | { 203 | return at(0); 204 | } 205 | 206 | constexpr const value_type& back() const 207 | { 208 | return at(value_count - 1); 209 | } 210 | 211 | reverse_iterator rbegin() 212 | { 213 | return reverse_iterator(end()); 214 | } 215 | 216 | const_reverse_iterator rbegin() const 217 | { 218 | return const_reverse_iterator(end()); 219 | } 220 | 221 | reverse_iterator rend() 222 | { 223 | return reverse_iterator(begin()); 224 | } 225 | 226 | const_reverse_iterator rend() const 227 | { 228 | return const_reverse_iterator(begin()); 229 | } 230 | 231 | const_iterator cbegin() const { return begin(); } 232 | const_iterator cend() const { return end(); } 233 | const_reverse_iterator crbegin() const { return rbegin(); } 234 | const_reverse_iterator crend() const { return rend(); } 235 | 236 | /////////////////////////////////////////////////////////////////////////// 237 | // arithmetic 238 | 239 | constexpr const vector3_t& operator+() const 240 | { 241 | return *this; 242 | } 243 | 244 | constexpr vector3_t operator-() const 245 | { 246 | return coord(-x, -y, -z); 247 | } 248 | 249 | vector3_t& operator+=(const vector3_t& b) 250 | { 251 | x += b.x; 252 | y += b.y; 253 | z += b.z; 254 | return *this; 255 | } 256 | 257 | vector3_t& operator-=(const vector3_t& b) 258 | { 259 | x -= b.x; 260 | y -= b.y; 261 | z -= b.z; 262 | return *this; 263 | } 264 | 265 | vector3_t& operator*=(const value_type& s) 266 | { 267 | x *= s; 268 | y *= s; 269 | z *= s; 270 | return *this; 271 | } 272 | 273 | vector3_t& operator/=(const value_type& s) 274 | { 275 | YAMA_ASSERT_WARN(s != 0, "yama::vector3_t division by zero"); 276 | x /= s; 277 | y /= s; 278 | z /= s; 279 | return *this; 280 | } 281 | 282 | vector3_t& mul(const vector3_t& b) 283 | { 284 | x *= b.x; 285 | y *= b.y; 286 | z *= b.z; 287 | return *this; 288 | } 289 | 290 | vector3_t& div(const vector3_t& b) 291 | { 292 | x /= b.x; 293 | y /= b.y; 294 | z /= b.z; 295 | return *this; 296 | } 297 | 298 | constexpr value_type length_sq() const 299 | { 300 | return sq(x) + sq(y) + sq(z); 301 | } 302 | 303 | value_type length() const 304 | { 305 | return std::sqrt(length_sq()); 306 | } 307 | 308 | constexpr value_type manhattan_length() const 309 | { 310 | return std::abs(x) + std::abs(y) + std::abs(z); 311 | } 312 | 313 | value_type normalize() 314 | { 315 | auto l = length(); 316 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector3_t"); 317 | x /= l; 318 | y /= l; 319 | z /= l; 320 | return l; 321 | } 322 | 323 | bool is_normalized() const 324 | { 325 | return close(length(), value_type(1)); 326 | } 327 | 328 | void homogenous_normalize() 329 | { 330 | YAMA_ASSERT_WARN(z != 0, "Homogenous normalization of yama::vector3_t with zero y"); 331 | x /= z; 332 | y /= z; 333 | z = 1; 334 | } 335 | 336 | vector3_t reflection(const vector3_t& normal) const 337 | { 338 | YAMA_ASSERT_WARN(normal.is_normalized(), "Reflecting with a non-normalized normal yama::vector3_t"); 339 | auto dd = 2 * dot(*this, normal); 340 | return coord(x - dd * normal.x, y - dd * normal.y, z - dd * normal.z); 341 | } 342 | 343 | vector3_t get_orthogonal() const 344 | { 345 | size_t non_zeros = 0; 346 | size_t non_zero_index = 0; 347 | 348 | for (size_t i = 0; i<3; ++i) 349 | { 350 | if (std::abs(at(i)) > constants_t::EPSILON) 351 | { 352 | ++non_zeros; 353 | non_zero_index = i; 354 | } 355 | } 356 | 357 | YAMA_ASSERT_WARN(non_zeros, "finding an orthogonal of a zero vector3"); 358 | 359 | if (non_zeros >= 2) 360 | { 361 | return vector3_t::coord(y * z / 2, x * z / 2, -x * y); 362 | } 363 | else 364 | { 365 | auto ret = vector3_t::zero(); 366 | 367 | ret.at((non_zero_index + 1) % 3) = this->at(non_zero_index); 368 | return ret; 369 | } 370 | } 371 | 372 | constexpr value_type product() const 373 | { 374 | return x * y * z; 375 | } 376 | 377 | constexpr value_type sum() const 378 | { 379 | return x + y + z; 380 | } 381 | }; 382 | 383 | template 384 | vector3_t operator+(const vector3_t& a, const vector3_t& b) 385 | { 386 | return vector3_t::coord(a.x + b.x, a.y + b.y, a.z + b.z); 387 | } 388 | 389 | template 390 | vector3_t operator-(const vector3_t& a, const vector3_t& b) 391 | { 392 | return vector3_t::coord(a.x - b.x, a.y - b.y, a.z - b.z); 393 | } 394 | 395 | template 396 | vector3_t operator*(const vector3_t& a, const T& s) 397 | { 398 | return vector3_t::coord(a.x * s, a.y * s, a.z * s); 399 | } 400 | 401 | template 402 | vector3_t operator*(const T& s, const vector3_t& b) 403 | { 404 | return vector3_t::coord(s * b.x, s * b.y, s * b.z); 405 | } 406 | 407 | template 408 | vector3_t operator/(const vector3_t& a, const T& s) 409 | { 410 | YAMA_ASSERT_WARN(s != 0, "yama::vector3_t division by zero"); 411 | return vector3_t::coord(a.x / s, a.y / s, a.z / s); 412 | } 413 | 414 | template 415 | vector3_t operator/(const T& s, const vector3_t& b) 416 | { 417 | return vector3_t::coord(s / b.x, s / b.y, s / b.z); 418 | } 419 | 420 | template 421 | bool operator==(const vector3_t& a, const vector3_t& b) 422 | { 423 | return a.x == b.x && a.y == b.y && a.z == b.z; 424 | } 425 | 426 | template 427 | bool operator!=(const vector3_t& a, const vector3_t& b) 428 | { 429 | return a.x != b.x || a.y != b.y || a.z != b.z; 430 | } 431 | 432 | template 433 | bool close(const vector3_t& a, const vector3_t& b, const T& epsilon = constants_t::EPSILON) 434 | { 435 | return close(a.x, b.x, epsilon) && close(a.y, b.y, epsilon) && close(a.z, b.z, epsilon); 436 | } 437 | 438 | template 439 | vector3_t abs(const vector3_t& a) 440 | { 441 | return vector3_t::coord(std::abs(a.x), std::abs(a.y), std::abs(a.z)); 442 | } 443 | 444 | template 445 | vector3_t mul(const vector3_t& a, const vector3_t& b) 446 | { 447 | return vector3_t::coord(a.x * b.x, a.y * b.y, a.z * b.z); 448 | } 449 | 450 | template 451 | vector3_t div(const vector3_t& a, const vector3_t& b) 452 | { 453 | YAMA_ASSERT_WARN(b.x != 0, "yama::vector3_t division by zero"); 454 | YAMA_ASSERT_WARN(b.y != 0, "yama::vector3_t division by zero"); 455 | YAMA_ASSERT_WARN(b.z != 0, "yama::vector3_t division by zero"); 456 | return vector3_t::coord(a.x / b.x, a.y / b.y, a.z / b.z); 457 | } 458 | 459 | template 460 | typename std::enable_if::value, 461 | vector3_t>::type mod(const vector3_t& n, const vector3_t& d) 462 | { 463 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector3_t division by zero"); 464 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector3_t division by zero"); 465 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector3_t division by zero"); 466 | return vector3_t::coord(fmod(n.x, d.x), fmod(n.y, d.y), fmod(n.z, d.z)); 467 | } 468 | 469 | template 470 | typename std::enable_if::value, 471 | vector3_t>::type mod(const vector3_t& n, const vector3_t& d) 472 | { 473 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector3_t division by zero"); 474 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector3_t division by zero"); 475 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector3_t division by zero"); 476 | return vector3_t::coord(n.x % d.x, n.y % d.y, n.z % d.z); 477 | } 478 | 479 | template 480 | vector3_t floor(const vector3_t& a) 481 | { 482 | return vector3_t::coord(::std::floor(a.x), ::std::floor(a.y), ::std::floor(a.z)); 483 | } 484 | 485 | template 486 | vector3_t ceil(const vector3_t& a) 487 | { 488 | return vector3_t::coord(::std::ceil(a.x), ::std::ceil(a.y), ::std::ceil(a.z)); 489 | } 490 | 491 | template 492 | vector3_t round(const vector3_t& a) 493 | { 494 | return vector3_t::coord(::std::round(a.x), ::std::round(a.y), ::std::round(a.z)); 495 | } 496 | 497 | 498 | template 499 | vector3_t frac(const vector3_t& a) 500 | { 501 | auto x = ::std::abs(a.x); 502 | auto y = ::std::abs(a.y); 503 | auto z = ::std::abs(a.z); 504 | return vector3_t::coord(x - ::std::floor(x), y - ::std::floor(y), z - ::std::floor(z)); 505 | } 506 | 507 | template 508 | bool isfinite(const vector3_t& a) 509 | { 510 | return std::isfinite(a.x) && std::isfinite(a.y) && std::isfinite(a.z); 511 | } 512 | 513 | template 514 | vector3_t sign(const vector3_t& a) 515 | { 516 | return vector3_t::coord(sign(a.x), sign(a.y), sign(a.z)); 517 | } 518 | 519 | template 520 | vector3_t clamp(const vector3_t& v, const vector3_t& min, const vector3_t& max) 521 | { 522 | return vector3_t::coord(clamp(v.x, min.x, max.x), clamp(v.y, min.y, max.y), clamp(v.z, min.z, max.z)); 523 | } 524 | 525 | #if !defined(min) 526 | template 527 | vector3_t min(const vector3_t& a, const vector3_t& b) 528 | { 529 | return vector3_t::coord(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)); 530 | } 531 | #endif 532 | 533 | #if !defined(max) 534 | template 535 | vector3_t max(const vector3_t& a, const vector3_t& b) 536 | { 537 | return vector3_t::coord(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)); 538 | } 539 | #endif 540 | 541 | template 542 | T dot(const vector3_t& a, const vector3_t& b) 543 | { 544 | return a.x * b.x + a.y * b.y + a.z * b.z; 545 | } 546 | 547 | template 548 | vector3_t cross(const vector3_t& a, const vector3_t& b) 549 | { 550 | YAMA_ASSERT_WARN(!close(a, vector3_t::zero()), "Cross product with a zero vector3_t"); 551 | YAMA_ASSERT_WARN(!close(b, vector3_t::zero()), "Cross product with a zero vector3_t"); 552 | return vector3_t::coord( 553 | a.y*b.z - a.z*b.y, 554 | a.z*b.x - a.x*b.z, 555 | a.x*b.y - a.y*b.x 556 | ); 557 | } 558 | 559 | template 560 | T distance_sq(const vector3_t& a, const vector3_t& b) 561 | { 562 | return sq(a.x - b.x) + sq(a.y - b.y) + sq(a.z - b.z); 563 | } 564 | 565 | template 566 | T distance(const vector3_t& a, const vector3_t& b) 567 | { 568 | return std::sqrt(distance_sq(a, b)); 569 | } 570 | 571 | template 572 | vector3_t normalize(const vector3_t& a) 573 | { 574 | auto l = a.length(); 575 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector3_t"); 576 | return vector3_t::coord(a.x / l, a.y / l, a.z / l); 577 | } 578 | 579 | template 580 | bool orthogonal(const vector3_t& a, const vector3_t& b) 581 | { 582 | return close(dot(a, b), T(0)); 583 | } 584 | 585 | template 586 | bool collinear(const vector3_t& a, const vector3_t& b) 587 | { 588 | T div[3]; 589 | int non_zeroes = 0; 590 | 591 | for (int i = 0; i < 3; ++i) 592 | { 593 | if (close(a[i], 0.f)) 594 | { 595 | if (close(b[i], 0.f)) 596 | { 597 | continue; 598 | } 599 | else 600 | { 601 | return false; 602 | } 603 | } 604 | 605 | div[non_zeroes++] = a[i] / b[i]; 606 | } 607 | 608 | for (int i = 1; i < non_zeroes; ++i) 609 | { 610 | if (!close(div[0], div[i])) 611 | { 612 | return false; 613 | } 614 | } 615 | 616 | return true; 617 | } 618 | 619 | // type traits 620 | template 621 | struct is_yama> : public std::true_type {}; 622 | 623 | template 624 | struct is_vector> : public std::true_type {}; 625 | 626 | // casts 627 | template 628 | V3_U vector_cast(const vector3_t& v) 629 | { 630 | using U = typename V3_U::value_type; 631 | return {U(v.x), U(v.y), U(v.z)}; 632 | } 633 | 634 | // shorthand 635 | #if !defined(YAMA_NO_SHORTHAND) 636 | 637 | using vector3 = vector3_t; 638 | using point3 = vector3; 639 | 640 | constexpr vector3 v(preferred_type x, preferred_type y, preferred_type z) 641 | { 642 | return vector3::coord(x, y, z); 643 | } 644 | 645 | template 646 | constexpr vector3_t vt(const T& x, const T& y, const T& z) 647 | { 648 | return vector3_t::coord(x, y, z); 649 | } 650 | 651 | #endif 652 | 653 | } 654 | -------------------------------------------------------------------------------- /include/yama/vector4.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "util.hpp" 12 | #include "shorthand.hpp" 13 | #include "type_traits.hpp" 14 | 15 | namespace yama 16 | { 17 | 18 | template 19 | class vector2_t; 20 | 21 | template 22 | class vector3_t; 23 | 24 | template 25 | class vector4_t 26 | { 27 | public: 28 | T x, y, z, w; 29 | 30 | using value_type = T; 31 | using size_type = size_t; 32 | using iterator = T*; 33 | using const_iterator = const T*; 34 | using reverse_iterator = typename std::reverse_iterator; 35 | using const_reverse_iterator = typename std::reverse_iterator; 36 | 37 | static constexpr size_type value_count = 4; 38 | 39 | constexpr size_type max_size() const { return value_count; } 40 | constexpr size_type size() const { return max_size(); } 41 | 42 | /////////////////////////////////////////////////////////////////////////// 43 | // named constructors 44 | static constexpr vector4_t coord(const value_type& x, const value_type& y, const value_type& z, const value_type& w) 45 | { 46 | return{ x, y, z, w}; 47 | } 48 | 49 | static constexpr vector4_t uniform(const value_type& s) 50 | { 51 | return coord(s, s, s, s); 52 | } 53 | 54 | static constexpr vector4_t zero() 55 | { 56 | return uniform(value_type(0)); 57 | } 58 | 59 | static vector4_t from_ptr(const value_type* ptr) 60 | { 61 | YAMA_ASSERT_CRIT(ptr, "Constructing yama::vector4_t from nullptr"); 62 | return coord(ptr[0], ptr[1], ptr[2], ptr[3]); 63 | } 64 | 65 | static constexpr vector4_t unit_x() 66 | { 67 | return coord(1, 0, 0, 0); 68 | } 69 | 70 | static constexpr vector4_t unit_y() 71 | { 72 | return coord(0, 1, 0, 0); 73 | } 74 | 75 | static constexpr vector4_t unit_z() 76 | { 77 | return coord(0, 0, 1, 0); 78 | } 79 | 80 | static constexpr vector4_t unit_w() 81 | { 82 | return coord(0, 0, 0, 1); 83 | } 84 | 85 | /////////////////////////// 86 | // attach 87 | static vector4_t& attach_to_ptr(value_type* ptr) 88 | { 89 | YAMA_ASSERT_BAD(ptr, "Attaching yama::vector4_t to nullptr"); 90 | return *reinterpret_cast(ptr); 91 | } 92 | 93 | static const vector4_t& attach_to_ptr(const value_type* ptr) 94 | { 95 | YAMA_ASSERT_BAD(ptr, "Attaching yama::vector4_t to nullptr"); 96 | return *reinterpret_cast(ptr); 97 | } 98 | 99 | static vector4_t* attach_to_array(value_type* ptr) 100 | { 101 | YAMA_ASSERT_WARN(ptr, "Attaching yama::vector4_t to nullptr"); 102 | return reinterpret_cast(ptr); 103 | } 104 | 105 | static const vector4_t* attach_to_array(const value_type* ptr) 106 | { 107 | YAMA_ASSERT_WARN(ptr, "Attaching yama::vector4_t to nullptr"); 108 | return reinterpret_cast(ptr); 109 | } 110 | 111 | /////////////////////////////////////////////////////////////////////////// 112 | // access 113 | value_type* data() 114 | { 115 | return reinterpret_cast(this); 116 | } 117 | 118 | constexpr const value_type* data() const 119 | { 120 | return reinterpret_cast(this); 121 | } 122 | 123 | value_type& at(size_type i) 124 | { 125 | YAMA_ASSERT_CRIT(i < value_count, "yama::vector4_t index overflow"); 126 | return data()[i]; 127 | } 128 | 129 | constexpr const value_type& at(size_type i) const 130 | { 131 | YAMA_ASSERT_CRIT(i < value_count, "yama::vector4_t index overflow"); 132 | return data()[i]; 133 | } 134 | 135 | value_type& operator[](size_type i) 136 | { 137 | return at(i); 138 | } 139 | 140 | constexpr const value_type& operator[](size_type i) const 141 | { 142 | return at(i); 143 | } 144 | 145 | /////////////////////////// 146 | // cast 147 | 148 | value_type* as_ptr() 149 | { 150 | return data(); 151 | } 152 | 153 | const value_type* as_ptr() const 154 | { 155 | return data(); 156 | } 157 | 158 | template 159 | vector4_t as_vector4_t() const 160 | { 161 | return vector4_t::coord(S(x), S(y), S(z), S(w)); 162 | } 163 | 164 | 165 | vector2_t& xy(); 166 | const vector2_t& xy() const; 167 | vector2_t xz() const; 168 | vector2_t zw() const; 169 | vector3_t& xyz(); 170 | const vector3_t& xyz() const; 171 | vector3_t zyx() const; 172 | vector4_t& xyzw() { return *this; } 173 | const vector4_t& xyzw() const { return *this; } 174 | vector4_t zyxw() const { return coord(z, y, x, w); } 175 | vector4_t wzyx() const { return coord(w, z, y, x); } 176 | 177 | /////////////////////////// 178 | // std 179 | 180 | iterator begin() 181 | { 182 | return data(); 183 | } 184 | 185 | iterator end() 186 | { 187 | return data() + value_count; 188 | } 189 | 190 | const_iterator begin() const 191 | { 192 | return data(); 193 | } 194 | 195 | const_iterator end() const 196 | { 197 | return data() + value_count; 198 | } 199 | 200 | value_type& front() 201 | { 202 | return at(0); 203 | } 204 | 205 | value_type& back() 206 | { 207 | return at(value_count - 1); 208 | } 209 | 210 | constexpr const value_type& front() const 211 | { 212 | return at(0); 213 | } 214 | 215 | constexpr const value_type& back() const 216 | { 217 | return at(value_count - 1); 218 | } 219 | 220 | reverse_iterator rbegin() 221 | { 222 | return reverse_iterator(end()); 223 | } 224 | 225 | const_reverse_iterator rbegin() const 226 | { 227 | return const_reverse_iterator(end()); 228 | } 229 | 230 | reverse_iterator rend() 231 | { 232 | return reverse_iterator(begin()); 233 | } 234 | 235 | const_reverse_iterator rend() const 236 | { 237 | return const_reverse_iterator(begin()); 238 | } 239 | 240 | const_iterator cbegin() const { return begin(); } 241 | const_iterator cend() const { return end(); } 242 | const_reverse_iterator crbegin() const { return rbegin(); } 243 | const_reverse_iterator crend() const { return rend(); } 244 | 245 | /////////////////////////////////////////////////////////////////////////// 246 | // arithmetic 247 | 248 | constexpr const vector4_t& operator+() const 249 | { 250 | return *this; 251 | } 252 | 253 | constexpr vector4_t operator-() const 254 | { 255 | return coord(-x, -y, -z, -w); 256 | } 257 | 258 | vector4_t& operator+=(const vector4_t& b) 259 | { 260 | x += b.x; 261 | y += b.y; 262 | z += b.z; 263 | w += b.w; 264 | return *this; 265 | } 266 | 267 | vector4_t& operator-=(const vector4_t& b) 268 | { 269 | x -= b.x; 270 | y -= b.y; 271 | z -= b.z; 272 | w -= b.w; 273 | return *this; 274 | } 275 | 276 | vector4_t& operator*=(const value_type& s) 277 | { 278 | x *= s; 279 | y *= s; 280 | z *= s; 281 | w *= s; 282 | return *this; 283 | } 284 | 285 | vector4_t& operator/=(const value_type& s) 286 | { 287 | YAMA_ASSERT_WARN(s != 0, "yama::vector4_t division by zero"); 288 | x /= s; 289 | y /= s; 290 | z /= s; 291 | w /= s; 292 | return *this; 293 | } 294 | 295 | vector4_t& mul(const vector4_t& b) 296 | { 297 | x *= b.x; 298 | y *= b.y; 299 | z *= b.z; 300 | w *= b.w; 301 | return *this; 302 | } 303 | 304 | vector4_t& div(const vector4_t& b) 305 | { 306 | x /= b.x; 307 | y /= b.y; 308 | z /= b.z; 309 | w /= b.w; 310 | return *this; 311 | } 312 | 313 | constexpr value_type length_sq() const 314 | { 315 | return sq(x) + sq(y) + sq(z) + sq(w); 316 | } 317 | 318 | value_type length() const 319 | { 320 | return std::sqrt(length_sq()); 321 | } 322 | 323 | constexpr value_type manhattan_length() const 324 | { 325 | return std::abs(x) + std::abs(y) + std::abs(z) + std::abs(w); 326 | } 327 | 328 | value_type normalize() 329 | { 330 | auto l = length(); 331 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector4_t"); 332 | x /= l; 333 | y /= l; 334 | z /= l; 335 | w /= l; 336 | return l; 337 | } 338 | 339 | bool is_normalized() const 340 | { 341 | return close(length(), value_type(1)); 342 | } 343 | 344 | void homogenous_normalize() 345 | { 346 | YAMA_ASSERT_WARN(w != 0, "Homogenous normalization of yama::vector4_t with zero y"); 347 | x /= w; 348 | y /= w; 349 | z /= w; 350 | w = 1; 351 | } 352 | 353 | vector4_t reflection(const vector4_t& normal) const 354 | { 355 | YAMA_ASSERT_WARN(normal.is_normalized(), "Reflecting with a non-normalized normal yama::vector4_t"); 356 | auto dd = 2 * dot(*this, normal); 357 | return coord(x - dd * normal.x, y - dd * normal.y, z - dd * normal.z, w - dd * normal.w); 358 | } 359 | 360 | constexpr value_type product() const 361 | { 362 | return x * y * z * w; 363 | } 364 | 365 | constexpr value_type sum() const 366 | { 367 | return x + y + z + w; 368 | } 369 | }; 370 | 371 | template 372 | vector4_t operator+(const vector4_t& a, const vector4_t& b) 373 | { 374 | return vector4_t::coord(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); 375 | } 376 | 377 | template 378 | vector4_t operator-(const vector4_t& a, const vector4_t& b) 379 | { 380 | return vector4_t::coord(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); 381 | } 382 | 383 | template 384 | vector4_t operator*(const vector4_t& a, const T& s) 385 | { 386 | return vector4_t::coord(a.x * s, a.y * s, a.z * s, a.w * s); 387 | } 388 | 389 | template 390 | vector4_t operator*(const T& s, const vector4_t& b) 391 | { 392 | return vector4_t::coord(s * b.x, s * b.y, s * b.z, s * b.w); 393 | } 394 | 395 | template 396 | vector4_t operator/(const vector4_t& a, const T& s) 397 | { 398 | YAMA_ASSERT_WARN(s != 0, "yama::vector4_t division by zero"); 399 | return vector4_t::coord(a.x / s, a.y / s, a.z / s, a.w / (s)); 400 | } 401 | 402 | template 403 | vector4_t operator/(const T& s, const vector4_t& b) 404 | { 405 | return vector4_t::coord(s / b.x, s / b.y, s / b.z, s / b.w); 406 | } 407 | 408 | template 409 | bool operator==(const vector4_t& a, const vector4_t& b) 410 | { 411 | return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; 412 | } 413 | 414 | template 415 | bool operator!=(const vector4_t& a, const vector4_t& b) 416 | { 417 | return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; 418 | } 419 | 420 | template 421 | bool close(const vector4_t& a, const vector4_t& b, const T& epsilon = constants_t::EPSILON) 422 | { 423 | return close(a.x, b.x, epsilon) && close(a.y, b.y, epsilon) && close(a.z, b.z, epsilon) && close(a.w, b.w, epsilon); 424 | } 425 | 426 | template 427 | vector4_t abs(const vector4_t& a) 428 | { 429 | return vector4_t::coord(std::abs(a.x), std::abs(a.y), std::abs(a.z), std::abs(a.w)); 430 | } 431 | 432 | template 433 | vector4_t mul(const vector4_t& a, const vector4_t& b) 434 | { 435 | return vector4_t::coord(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); 436 | } 437 | 438 | template 439 | vector4_t div(const vector4_t& a, const vector4_t& b) 440 | { 441 | YAMA_ASSERT_WARN(b.x != 0, "yama::vector4_t division by zero"); 442 | YAMA_ASSERT_WARN(b.y != 0, "yama::vector4_t division by zero"); 443 | YAMA_ASSERT_WARN(b.z != 0, "yama::vector4_t division by zero"); 444 | YAMA_ASSERT_WARN(b.w != 0, "yama::vector4_t division by zero"); 445 | return vector4_t::coord(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); 446 | } 447 | 448 | template 449 | typename std::enable_if::value, 450 | vector4_t>::type mod(const vector4_t& n, const vector4_t& d) 451 | { 452 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector4_t division by zero"); 453 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector4_t division by zero"); 454 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector4_t division by zero"); 455 | YAMA_ASSERT_WARN(d.w != 0, "yama::vector4_t division by zero"); 456 | return vector4_t::coord(fmod(n.x, d.x), fmod(n.y, d.y), fmod(n.z, d.z), fmod(n.w, d.w)); 457 | } 458 | 459 | template 460 | typename std::enable_if::value, 461 | vector4_t>::type mod(const vector4_t& n, const vector4_t& d) 462 | { 463 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector4_t division by zero"); 464 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector4_t division by zero"); 465 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector4_t division by zero"); 466 | YAMA_ASSERT_WARN(d.w != 0, "yama::vector4_t division by zero"); 467 | 468 | return vector4_t::coord(n.x % d.x, n.y % d.y, n.z % d.z, n.w % d.w); 469 | } 470 | 471 | template 472 | vector4_t floor(const vector4_t& a) 473 | { 474 | return vector4_t::coord(::std::floor(a.x), ::std::floor(a.y), ::std::floor(a.z), ::std::floor(a.w)); 475 | } 476 | 477 | template 478 | vector4_t ceil(const vector4_t& a) 479 | { 480 | return vector4_t::coord(::std::ceil(a.x), ::std::ceil(a.y), ::std::ceil(a.z), ::std::ceil(a.w)); 481 | } 482 | 483 | template 484 | vector4_t round(const vector4_t& a) 485 | { 486 | return vector4_t::coord(::std::round(a.x), ::std::round(a.y), ::std::round(a.z), ::std::round(a.w)); 487 | } 488 | 489 | 490 | template 491 | vector4_t frac(const vector4_t& a) 492 | { 493 | auto x = ::std::abs(a.x); 494 | auto y = ::std::abs(a.y); 495 | auto z = ::std::abs(a.z); 496 | auto w = ::std::abs(a.w); 497 | return vector4_t::coord(x - ::std::floor(x), y - ::std::floor(y), z - ::std::floor(z), w - ::std::floor(w)); 498 | } 499 | 500 | template 501 | bool isfinite(const vector4_t& a) 502 | { 503 | return std::isfinite(a.x) && std::isfinite(a.y) && std::isfinite(a.z) && std::isfinite(a.w); 504 | } 505 | 506 | template 507 | vector4_t sign(const vector4_t& a) 508 | { 509 | return vector4_t::coord(sign(a.x), sign(a.y), sign(a.z), sign(a.w)); 510 | } 511 | 512 | template 513 | vector4_t clamp(const vector4_t& v, const vector4_t& min, const vector4_t& max) 514 | { 515 | return vector4_t::coord(clamp(v.x, min.x, max.x), clamp(v.y, min.y, max.y), clamp(v.z, min.z, max.z), clamp(v.w, min.w, max.w)); 516 | } 517 | 518 | #if !defined(min) 519 | template 520 | vector4_t min(const vector4_t& a, const vector4_t& b) 521 | { 522 | return vector4_t::coord(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z), std::min(a.w, b.w)); 523 | } 524 | #endif 525 | 526 | #if !defined(max) 527 | template 528 | vector4_t max(const vector4_t& a, const vector4_t& b) 529 | { 530 | return vector4_t::coord(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z), std::max(a.w, b.w)); 531 | } 532 | #endif 533 | 534 | template 535 | T dot(const vector4_t& a, const vector4_t& b) 536 | { 537 | return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; 538 | } 539 | 540 | template 541 | T distance_sq(const vector4_t& a, const vector4_t& b) 542 | { 543 | return sq(a.x - b.x) + sq(a.y - b.y) + sq(a.z - b.z) + sq(a.w - b.w); 544 | } 545 | 546 | template 547 | T distance(const vector4_t& a, const vector4_t& b) 548 | { 549 | return std::sqrt(distance_sq(a, b)); 550 | } 551 | 552 | template 553 | vector4_t normalize(const vector4_t& a) 554 | { 555 | auto l = a.length(); 556 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector4_t"); 557 | return vector4_t::coord(a.x / l, a.y / l, a.z / l, a.w / l); 558 | } 559 | 560 | template 561 | bool orthogonal(const vector4_t& a, const vector4_t& b) 562 | { 563 | return close(dot(a, b), T(0)); 564 | } 565 | 566 | template 567 | bool collinear(const vector4_t& a, const vector4_t& b) 568 | { 569 | T div[4]; 570 | int non_zeroes = 0; 571 | 572 | for (int i = 0; i < 4; ++i) 573 | { 574 | if (close(a[i], 0.f)) 575 | { 576 | if (close(b[i], 0.f)) 577 | { 578 | continue; 579 | } 580 | else 581 | { 582 | return false; 583 | } 584 | } 585 | 586 | div[non_zeroes++] = a[i] / b[i]; 587 | } 588 | 589 | for (int i = 1; i < non_zeroes; ++i) 590 | { 591 | if (!close(div[0], div[i])) 592 | { 593 | return false; 594 | } 595 | } 596 | 597 | return true; 598 | } 599 | 600 | // type traits 601 | template 602 | struct is_yama> : public std::true_type {}; 603 | 604 | template 605 | struct is_vector> : public std::true_type {}; 606 | 607 | // casts 608 | template 609 | V4_U vector_cast(const vector4_t& v) 610 | { 611 | using U = typename V4_U::value_type; 612 | return {U(v.x), U(v.y), U(v.z), U(v.w)}; 613 | } 614 | 615 | // shorthand 616 | #if !defined(YAMA_NO_SHORTHAND) 617 | 618 | using vector4 = vector4_t; 619 | using point4 = vector4; 620 | 621 | constexpr vector4 v(preferred_type x, preferred_type y, preferred_type z, preferred_type w) 622 | { 623 | return vector4::coord(x, y, z, w); 624 | } 625 | 626 | template 627 | constexpr vector4_t vt(const T& x, const T& y, const T& z, const T& w) 628 | { 629 | return vector4_t::coord(x, y, z, w); 630 | } 631 | 632 | #endif 633 | 634 | } 635 | -------------------------------------------------------------------------------- /include/yama/vector_xyzw.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include "vector2.hpp" 7 | #include "vector3.hpp" 8 | #include "vector4.hpp" 9 | 10 | namespace yama 11 | { 12 | 13 | template 14 | vector3_t vector2_t::xyz(const T& z) const 15 | { 16 | return vector3_t::coord(x, y, z); 17 | } 18 | 19 | template 20 | vector4_t vector2_t::xyzw(const T& z, const T& w) const 21 | { 22 | return vector4_t::coord(x, y, z, w); 23 | } 24 | 25 | template 26 | vector2_t& vector3_t::xy() 27 | { 28 | return vector2_t::attach_to_ptr(as_ptr()); 29 | } 30 | 31 | template 32 | const vector2_t& vector3_t::xy() const 33 | { 34 | return vector2_t::attach_to_ptr(as_ptr()); 35 | } 36 | 37 | template 38 | vector2_t vector3_t::xz() const 39 | { 40 | return vector2_t::coord(x, z); 41 | } 42 | 43 | template 44 | vector4_t vector3_t::xyzw(const value_type& w) const 45 | { 46 | return vector4_t::coord(x, y, z, w); 47 | } 48 | 49 | template 50 | vector2_t& vector4_t::xy() 51 | { 52 | return vector2_t::attach_to_ptr(as_ptr()); 53 | } 54 | 55 | template 56 | const vector2_t& vector4_t::xy() const 57 | { 58 | return vector2_t::attach_to_ptr(as_ptr()); 59 | } 60 | 61 | template 62 | vector2_t vector4_t::xz() const 63 | { 64 | return vector2_t::coord(x, z); 65 | } 66 | 67 | template 68 | vector2_t vector4_t::zw() const 69 | { 70 | return vector2_t::coord(z, w); 71 | } 72 | 73 | template 74 | vector3_t& vector4_t::xyz() 75 | { 76 | return vector3_t::attach_to_ptr(as_ptr()); 77 | } 78 | 79 | template 80 | const vector3_t& vector4_t::xyz() const 81 | { 82 | return vector3_t::attach_to_ptr(as_ptr()); 83 | } 84 | 85 | template 86 | vector3_t vector4_t::zyx() const 87 | { 88 | return vector3_t::coord(z, y, x); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /include/yama/yama.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | 6 | #include "dim.hpp" 7 | #include "vector_xyzw.hpp" 8 | #include "quaternion.hpp" 9 | #include "matrix3x4.hpp" 10 | #include "matrix4x4.hpp" 11 | -------------------------------------------------------------------------------- /test/unit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Borislav Stanimirov 2 | # SPDX-License-Identifier: MIT 3 | # 4 | include(./get_cpm.cmake) 5 | CPMAddPackage(gh:iboB/doctest-lib@2.4.9a) 6 | 7 | file(GLOB tests ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) 8 | source_group("tests" FILES ${tests}) 9 | 10 | add_executable(yama-unit-test 11 | ${tests} 12 | ) 13 | 14 | target_link_libraries(yama-unit-test 15 | yama 16 | doctest-main 17 | ) 18 | 19 | add_test(yama-unit-test yama-unit-test) 20 | -------------------------------------------------------------------------------- /test/unit/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #pragma once 5 | #include "yama/util.hpp" 6 | #include 7 | #include "doctest/doctest.h" 8 | #include // for memcmp 9 | 10 | template 11 | class YApprox 12 | { 13 | public: 14 | using value_type = typename Y::value_type; 15 | 16 | explicit YApprox(const Y& value) 17 | : e(yama::constants_t::EPSILON) 18 | , v(value) 19 | { 20 | 21 | } 22 | 23 | YApprox& epsilon(value_type newEpsilon) 24 | { 25 | e = newEpsilon; 26 | return *this; 27 | } 28 | 29 | value_type e; 30 | const Y& v; 31 | }; 32 | 33 | template 34 | inline bool operator==(const Y& lhs, const YApprox& rhs) 35 | { 36 | return yama::close(lhs, rhs.v, rhs.e); 37 | } 38 | 39 | template 40 | inline bool operator==(const YApprox& lhs, const Y& rhs) { return operator==(rhs, lhs); } 41 | template 42 | inline bool operator!=(const Y& lhs, const YApprox& rhs) { return !operator==(lhs, rhs); } 43 | template 44 | inline bool operator!=(const YApprox& lhs, const Y& rhs) { return !operator==(rhs, lhs); } 45 | 46 | template 47 | YApprox YamaApprox(const Y& y) 48 | { 49 | return YApprox(y); 50 | } 51 | 52 | template 53 | std::ostream& operator<<(std::ostream& o, const YApprox& a) 54 | { 55 | o << a.v; 56 | return o; 57 | } 58 | -------------------------------------------------------------------------------- /test/unit/get_cpm.cmake: -------------------------------------------------------------------------------- 1 | set(CPM_DOWNLOAD_VERSION 0.35.6) 2 | 3 | if(CPM_SOURCE_CACHE) 4 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 5 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 6 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 7 | else() 8 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 9 | endif() 10 | 11 | # Expand relative path. This is important if the provided path contains a tilde (~) 12 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 13 | if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) 14 | message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") 15 | file(DOWNLOAD 16 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 17 | ${CPM_DOWNLOAD_LOCATION} 18 | ) 19 | endif() 20 | 21 | include(${CPM_DOWNLOAD_LOCATION}) 22 | -------------------------------------------------------------------------------- /test/unit/matrix3x3.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/matrix3x3.hpp" 5 | #include "common.hpp" 6 | #include "yama/ext/ostream.hpp" 7 | 8 | using namespace yama; 9 | using doctest::Approx; 10 | 11 | TEST_SUITE_BEGIN("matrix3x3"); 12 | 13 | TEST_CASE("construction") 14 | { 15 | double d0[] = { 16 | 0, 0, 0, 17 | 0, 0, 0, 18 | 0, 0, 0, 19 | }; 20 | 21 | auto m0 = matrix3x3_t::zero(); 22 | CHECK(m0.m00 == 0); 23 | CHECK(m0.m10 == 0); 24 | CHECK(m0.m20 == 0); 25 | CHECK(m0.m01 == 0); 26 | CHECK(m0.m11 == 0); 27 | CHECK(m0.m21 == 0); 28 | CHECK(m0.m02 == 0); 29 | CHECK(m0.m12 == 0); 30 | CHECK(m0.m22 == 0); 31 | CHECK(memcmp(d0, &m0, 9 * sizeof(double)) == 0); 32 | 33 | float f1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 34 | auto m1 = matrix3::columns(1, 2, 3, 4, 5, 6, 7, 8, 9); 35 | CHECK(m1.m00 == 1); 36 | CHECK(m1.m10 == 2); 37 | CHECK(m1.m20 == 3); 38 | CHECK(m1.m01 == 4); 39 | CHECK(m1.m11 == 5); 40 | CHECK(m1.m21 == 6); 41 | CHECK(m1.m02 == 7); 42 | CHECK(m1.m12 == 8); 43 | CHECK(m1.m22 == 9); 44 | CHECK(memcmp(f1, &m1, 9 * sizeof(float)) == 0); 45 | 46 | auto m2 = matrix3::rows(1, 4, 7, 2, 5, 8, 3, 6, 9); 47 | CHECK(m2.m00 == 1); 48 | CHECK(m2.m10 == 2); 49 | CHECK(m2.m20 == 3); 50 | CHECK(m2.m01 == 4); 51 | CHECK(m2.m11 == 5); 52 | CHECK(m2.m21 == 6); 53 | CHECK(m2.m02 == 7); 54 | CHECK(m2.m12 == 8); 55 | CHECK(m2.m22 == 9); 56 | CHECK(memcmp(f1, &m2, 9 * sizeof(float)) == 0); 57 | 58 | auto m3 = matrix3x3_t::uniform(3); 59 | CHECK(m3.m00 == 3); 60 | CHECK(m3.m10 == 3); 61 | CHECK(m3.m20 == 3); 62 | CHECK(m3.m01 == 3); 63 | CHECK(m3.m11 == 3); 64 | CHECK(m3.m21 == 3); 65 | CHECK(m3.m02 == 3); 66 | CHECK(m3.m12 == 3); 67 | CHECK(m3.m22 == 3); 68 | 69 | auto m4 = matrix3::identity(); 70 | CHECK(m4.m00 == 1); 71 | CHECK(m4.m10 == 0); 72 | CHECK(m4.m20 == 0); 73 | CHECK(m4.m01 == 0); 74 | CHECK(m4.m11 == 1); 75 | CHECK(m4.m21 == 0); 76 | CHECK(m4.m02 == 0); 77 | CHECK(m4.m12 == 0); 78 | CHECK(m4.m22 == 1); 79 | 80 | const float f[] = { 81 | 9, 8, 7, 6, 5, 4, 3, 2, 1, 82 | 18, 17, 16, 15, 14, 13, 12, 11, 10, 83 | }; 84 | auto m5 = matrix3::from_ptr(f); 85 | CHECK(memcmp(&m5, f, 9 * sizeof(float)) == 0); 86 | 87 | // attach 88 | auto& m6 = matrix3::attach_to_ptr(f); 89 | CHECK(m6.m00 == 9); 90 | CHECK(m6.m10 == 8); 91 | CHECK(m6.m20 == 7); 92 | CHECK(m6.m01 == 6); 93 | CHECK(m6.m11 == 5); 94 | CHECK(m6.m21 == 4); 95 | CHECK(m6.m02 == 3); 96 | CHECK(m6.m12 == 2); 97 | CHECK(m6.m22 == 1); 98 | CHECK(reinterpret_cast(&m6) == f); 99 | 100 | auto m7 = matrix3::attach_to_array(f); 101 | CHECK(m7[0].m00 == 9); 102 | CHECK(m7[0].m10 == 8); 103 | CHECK(m7[0].m20 == 7); 104 | CHECK(m7[0].m01 == 6); 105 | CHECK(m7[0].m11 == 5); 106 | CHECK(m7[0].m21 == 4); 107 | CHECK(m7[0].m02 == 3); 108 | CHECK(m7[0].m12 == 2); 109 | CHECK(m7[0].m22 == 1); 110 | CHECK(m7 == &m6); 111 | //CHECK(m7[0] == m6); 112 | CHECK(reinterpret_cast(m7) == f); 113 | } 114 | 115 | 116 | TEST_CASE("compare") 117 | { 118 | auto m0 = matrix3::zero(); 119 | CHECK(m0 == matrix3::columns(0, 0, 0, 0, 0, 0, 0, 0, 0)); 120 | CHECK(m0 != matrix3::columns(1, 0, 0, 0, 0, 0, 0, 0, 0)); 121 | CHECK(m0 != matrix3::columns(0, 1, 0, 0, 0, 0, 0, 0, 0)); 122 | CHECK(m0 != matrix3::columns(0, 0, 1, 0, 0, 0, 0, 0, 0)); 123 | CHECK(m0 != matrix3::columns(0, 0, 0, 1, 0, 0, 0, 0, 0)); 124 | CHECK(m0 != matrix3::columns(0, 0, 0, 0, 1, 0, 0, 0, 0)); 125 | CHECK(m0 != matrix3::columns(0, 0, 0, 0, 0, 1, 0, 0, 0)); 126 | CHECK(m0 != matrix3::columns(0, 0, 0, 0, 0, 0, 1, 0, 0)); 127 | CHECK(m0 != matrix3::columns(0, 0, 0, 0, 0, 0, 0, 1, 0)); 128 | CHECK(m0 != matrix3::columns(0, 0, 0, 0, 0, 0, 0, 0, 1)); 129 | 130 | matrix3 m1; 131 | m1.m00 = 11; m1.m10 = 12; m1.m20 = 13; 132 | m1.m01 = 14; m1.m11 = 15; m1.m21 = 16; 133 | m1.m02 = 17; m1.m12 = 18; m1.m22 = 19; 134 | 135 | CHECK(m1 == matrix3::columns(11, 12, 13, 14, 15, 16, 17, 18, 19)); 136 | CHECK(m1 != m0); 137 | 138 | m0.m00 = 11; m0.m10 = 12; m0.m20 = 13; 139 | m0.m01 = 14; m0.m11 = 15; m0.m21 = 16; 140 | m0.m02 = 17; m0.m12 = 18; m0.m22 = 19; 141 | CHECK(m1 == m0); 142 | 143 | CHECK(close(m1, m0)); 144 | CHECK(close(m0, m1)); 145 | m0.m21 += 1; 146 | CHECK(!close(m0, m1)); 147 | CHECK(close(m0, m1, 2.f)); 148 | m0.m00 = 11.000001f; m0.m10 = 12.000001f; m0.m20 = 13.000001f; 149 | m0.m01 = 14.000001f; m0.m11 = 15.000001f; m0.m21 = 16.000001f; 150 | m0.m02 = 17.000001f; m0.m12 = 18.000001f; m0.m22 = 19.000001f; 151 | CHECK(close(m0, m1)); 152 | } 153 | 154 | TEST_CASE("special_construction") 155 | { 156 | auto m0 = matrix3::scaling_uniform(1); 157 | CHECK(m0 == matrix3::identity()); 158 | CHECK(m0 == matrix3::scaling(1, 1, 1)); 159 | 160 | m0.m00 = 5; m0.m11 = 5; m0.m22 = 5; 161 | CHECK(m0 == matrix3::scaling_uniform(5)); 162 | CHECK(m0 == matrix3::scaling(5, 5, 5)); 163 | CHECK(m0 == matrix3::scaling(v(5, 5, 5))); 164 | 165 | m0.m00 = 2; m0.m11 = 4; m0.m22 = 6; 166 | CHECK(m0 == matrix3::scaling(2, 4, 6)); 167 | CHECK(m0 == matrix3::scaling(v(2, 4, 6))); 168 | } 169 | 170 | TEST_CASE("access") 171 | { 172 | auto m0 = matrix3::columns(1, 2, 3, 4, 5, 6, 7, 8, 9); 173 | CHECK(m0.data() == reinterpret_cast(&m0)); 174 | CHECK(m0.data()[0] == 1); 175 | CHECK(m0.data()[1] == 2); 176 | CHECK(m0.data()[2] == 3); 177 | CHECK(m0[3] == 4); 178 | CHECK(m0[4] == 5); 179 | CHECK(m0[5] == 6); 180 | CHECK(m0.at(6) == 7); 181 | CHECK(m0.at(7) == 8); 182 | CHECK(m0.at(8) == 9); 183 | CHECK(m0.data() == m0.as_ptr()); 184 | CHECK(m0.data() == m0.column(0)); 185 | CHECK(m0.data() + 3 == m0.column(1)); 186 | CHECK(m0.data() + 6 == m0.column(2)); 187 | CHECK(m0(0, 2) == 7); 188 | CHECK(m0(1, 2) == 8); 189 | CHECK(m0.m(2, 1) == 6); 190 | CHECK(m0.m(2, 2) == 9); 191 | 192 | CHECK(m0.as_matrix3x3_t() == matrix3x3_t::columns(1, 2, 3, 4, 5, 6, 7, 8, 9)); 193 | 194 | m0.column_vector(0) = v(20, 30, 40); 195 | CHECK(m0.m00 == 20); 196 | CHECK(m0.m20 == 40); 197 | CHECK(m0.column_vector<2>(0) == v(20, 30)); 198 | m0.column_vector<3>(1) = v(-1, -2, -3); 199 | CHECK(m0.m01 == -1); 200 | CHECK(m0.m21 == -3); 201 | CHECK(m0.column_vector<2>(1, 1) == v(-2, -3)); 202 | 203 | CHECK(m0.row_vector(0) == v(20, -1, 7)); 204 | CHECK(m0.row_vector<2>(1, 1) == v(-2, 8)); 205 | 206 | CHECK(m0.main_diagonal() == v(20, -2, 9)); 207 | CHECK(m0.main_diagonal<2>(1) == v(-2, 9)); 208 | 209 | const auto m1 = matrix3::columns(1, 2, 3, 4, 5, 6, 7, 8, 9); 210 | CHECK(m1.data() == reinterpret_cast(&m1)); 211 | CHECK(m1.data()[0] == 1); 212 | CHECK(m1.data()[1] == 2); 213 | CHECK(m1.data()[2] == 3); 214 | CHECK(m1[3] == 4); 215 | CHECK(m1[4] == 5); 216 | CHECK(m1[5] == 6); 217 | CHECK(m1.at(6) == 7); 218 | CHECK(m1.at(7) == 8); 219 | CHECK(m1.at(8) == 9); 220 | CHECK(m1.data() == m1.as_ptr()); 221 | CHECK(m1.data() == m1.column(0)); 222 | CHECK(m1.data() + 3 == m1.column(1)); 223 | CHECK(m1.data() + 6 == m1.column(2)); 224 | CHECK(m1(0, 2) == 7); 225 | CHECK(m1(1, 2) == 8); 226 | CHECK(m1.m(2, 1) == 6); 227 | CHECK(m1.m(2, 2) == 9); 228 | 229 | CHECK(m1.as_matrix3x3_t() == matrix3x3_t::columns(1, 2, 3, 4, 5, 6, 7, 8, 9)); 230 | 231 | CHECK(m1.column_vector<2>(0) == v(1, 2)); 232 | CHECK(m1.column_vector<2>(1, 1) == v(5, 6)); 233 | 234 | CHECK(m1.row_vector(0) == v(1, 4, 7)); 235 | CHECK(m1.row_vector<2>(1, 1) == v(5, 8)); 236 | 237 | CHECK(m1.main_diagonal() == v(1, 5, 9)); 238 | CHECK(m1.main_diagonal<2>(1) == v(5, 9)); 239 | } 240 | 241 | 242 | TEST_CASE("std") 243 | { 244 | auto m0 = matrix3::columns(1, 2, 3, 4, 5, 6, 7, 8, 9); 245 | CHECK(m0.front() == 1); 246 | CHECK(m0.back() == 9); 247 | CHECK(m0.begin() == m0.data()); 248 | CHECK(m0.begin() + 9 == m0.end()); 249 | CHECK(m0.rbegin() + 9 == m0.rend()); 250 | auto i = m0.rbegin(); 251 | CHECK(*i == 9); 252 | ++i; 253 | CHECK(*i == 8); 254 | 255 | for (auto& e : m0) 256 | { 257 | e += 10; 258 | } 259 | 260 | const auto m1 = m0; 261 | 262 | CHECK(m1.front() == 11); 263 | CHECK(m1.back() == 19); 264 | CHECK(m1.begin() == m1.data()); 265 | CHECK(m1.begin() + 9 == m1.end()); 266 | CHECK(m1.rbegin() + 9 == m1.rend()); 267 | auto ci = m1.rbegin(); 268 | CHECK(*ci == 19); 269 | ++ci; 270 | CHECK(*ci == 18); 271 | } 272 | 273 | 274 | TEST_CASE("members") 275 | { 276 | auto m0 = matrix3::columns(1, 2, 3, 4, 5, 6, 7, 8, 9); 277 | CHECK(m0 == +m0); 278 | auto m1 = matrix3::columns(-1, -2, -3, -4, -5, -6, -7, -8, -9); 279 | CHECK(-m0 == m1); 280 | CHECK(-m1 == m0); 281 | 282 | m0 += m1; 283 | CHECK(m0 == matrix3::zero()); 284 | 285 | m0 -= m1; 286 | CHECK(m0 == -m1); 287 | 288 | m1 = m0; 289 | m0 *= 2; 290 | CHECK(m0 == matrix3::columns(2, 4, 6, 8, 10, 12, 14, 16, 18)); 291 | 292 | m0 /= 2; 293 | CHECK(m0 == m1); 294 | 295 | m0.div(m1); 296 | CHECK(m0 == matrix3::uniform(1)); 297 | 298 | m0.mul(m1); 299 | CHECK(m0 == m1); 300 | 301 | m0 *= matrix3::identity(); 302 | CHECK(m0 == m1); 303 | CHECK(matrix3::identity().determinant() == 1); 304 | 305 | auto m2 = matrix3::identity(); 306 | m2.inverse(); 307 | CHECK(m2 == matrix3::identity()); 308 | m2.transpose(); 309 | CHECK(m2 == matrix3::identity()); 310 | 311 | m0.transpose(); 312 | CHECK(m0 == matrix3::rows(1, 2, 3, 4, 5, 6, 7, 8, 9)); 313 | 314 | m0 = m1 = matrix3::rows( 315 | 1, 2, 3, 316 | 5, 3, 2, 317 | 2, 1, 1 318 | ); 319 | 320 | auto det = m1.inverse(); 321 | CHECK(det == -4); 322 | m0 *= m1; 323 | CHECK(YamaApprox(m0) == matrix3::identity()); 324 | 325 | m0 = m2 = matrix3::rows(3, 22, 12, 5, 17, 8, 24, 6, 19); 326 | det = m2.inverse(); 327 | CHECK(det == -1577); 328 | m0 *= m2; 329 | CHECK(YamaApprox(m0) == matrix3::identity()); 330 | } 331 | 332 | TEST_CASE("ops") 333 | { 334 | const auto m0 = matrix3::columns(1, 2, 3, 4, 5, 6, 7, 8, 9); 335 | auto m1 = matrix3::columns(-1, -2, -3, -4, -5, -6, -7, -8, -9); 336 | auto m3 = matrix3::columns(2, 4, 6, 8, 10, 12, 14, 16, 18); 337 | 338 | CHECK(m0 + m1 == matrix3::zero()); 339 | CHECK(m0 - m1 == m3); 340 | 341 | CHECK(abs(m1) == m0); 342 | CHECK(abs(m0) == m0); 343 | 344 | m1 = m0 * 2.f; 345 | CHECK(m1 == m3); 346 | 347 | m1 = 2.f * m0; 348 | CHECK(m1 == m3); 349 | 350 | m1 = m3 / 2.f; 351 | CHECK(m1 == m0); 352 | 353 | m1 = 16.f / m0; 354 | auto m2 = matrix3::columns( 355 | 16, 8, 5.333333f, 356 | 4, 3.2f, 2.666667f, 357 | 2.285714f, 2, 1.777778f); 358 | CHECK(YamaApprox(m1) == m2); 359 | 360 | m1 = div(m3, m0); 361 | CHECK(m1 == matrix3::uniform(2)); 362 | CHECK(mul(m0, m1) == m3); 363 | 364 | CHECK(isfinite(m0)); 365 | CHECK(isfinite(m1)); 366 | CHECK(isfinite(m2)); 367 | CHECK(isfinite(m3)); 368 | 369 | m1.m11 = std::numeric_limits::infinity(); 370 | CHECK(!isfinite(m1)); 371 | 372 | m2.m20 = std::numeric_limits::quiet_NaN(); 373 | CHECK(!isfinite(m2)); 374 | 375 | CHECK(!isfinite(m1 + m2)); 376 | 377 | m1 = matrix3::rows( 378 | 1, 2, 3, 379 | 5, 3, 2, 380 | 2, 1, 1 381 | ); 382 | 383 | CHECK(m1 * matrix3::identity() == m1); 384 | CHECK(matrix3::identity() * m1 == m1); 385 | 386 | float det; 387 | m2 = inverse(m1, det); 388 | CHECK(det == -4); 389 | CHECK(YamaApprox(m1 * m2) == matrix3::identity()); 390 | CHECK(YamaApprox(m2 * m1) == matrix3::identity()); 391 | } 392 | -------------------------------------------------------------------------------- /test/unit/matrix3x4.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/matrix3x4.hpp" 5 | #include "common.hpp" 6 | #include "yama/ext/ostream.hpp" 7 | 8 | using namespace yama; 9 | using doctest::Approx; 10 | 11 | TEST_SUITE_BEGIN("matrix4x4"); 12 | 13 | TEST_CASE("construction") 14 | { 15 | double d0[] = { 16 | 0, 0, 0, 17 | 0, 0, 0, 18 | 0, 0, 0, 19 | 0, 0, 0, 20 | }; 21 | 22 | auto m0 = matrix3x4_t::zero(); 23 | CHECK(m0.m00 == 0); 24 | CHECK(m0.m10 == 0); 25 | CHECK(m0.m20 == 0); 26 | CHECK(m0.m01 == 0); 27 | CHECK(m0.m11 == 0); 28 | CHECK(m0.m21 == 0); 29 | CHECK(m0.m02 == 0); 30 | CHECK(m0.m12 == 0); 31 | CHECK(m0.m22 == 0); 32 | CHECK(m0.m03 == 0); 33 | CHECK(m0.m13 == 0); 34 | CHECK(m0.m23 == 0); 35 | CHECK(memcmp(d0, &m0, 12 * sizeof(double)) == 0); 36 | 37 | float f1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 38 | auto m1 = matrix3x4::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); 39 | CHECK(m1.m00 == 1); 40 | CHECK(m1.m10 == 2); 41 | CHECK(m1.m20 == 3); 42 | CHECK(m1.m01 == 4); 43 | CHECK(m1.m11 == 5); 44 | CHECK(m1.m21 == 6); 45 | CHECK(m1.m02 == 7); 46 | CHECK(m1.m12 == 8); 47 | CHECK(m1.m22 == 9); 48 | CHECK(m1.m03 == 10); 49 | CHECK(m1.m13 == 11); 50 | CHECK(m1.m23 == 12); 51 | CHECK(memcmp(f1, &m1, 12 * sizeof(float)) == 0); 52 | 53 | auto m2 = matrix3x4::rows(1, 4, 7, 10, 2, 5, 8, 11, 3, 6, 9, 12); 54 | CHECK(m2.m00 == 1); 55 | CHECK(m2.m10 == 2); 56 | CHECK(m2.m20 == 3); 57 | CHECK(m2.m01 == 4); 58 | CHECK(m2.m11 == 5); 59 | CHECK(m2.m21 == 6); 60 | CHECK(m2.m02 == 7); 61 | CHECK(m2.m12 == 8); 62 | CHECK(m2.m22 == 9); 63 | CHECK(m2.m03 == 10); 64 | CHECK(m2.m13 == 11); 65 | CHECK(m2.m23 == 12); 66 | CHECK(memcmp(f1, &m1, 12 * sizeof(float)) == 0); 67 | 68 | auto m3 = matrix3x4_t::uniform(3); 69 | CHECK(m3.m00 == 3); 70 | CHECK(m3.m10 == 3); 71 | CHECK(m3.m20 == 3); 72 | CHECK(m3.m01 == 3); 73 | CHECK(m3.m11 == 3); 74 | CHECK(m3.m21 == 3); 75 | CHECK(m3.m02 == 3); 76 | CHECK(m3.m12 == 3); 77 | CHECK(m3.m22 == 3); 78 | CHECK(m3.m03 == 3); 79 | CHECK(m3.m13 == 3); 80 | CHECK(m3.m23 == 3); 81 | 82 | auto m4 = matrix3x4::identity(); 83 | CHECK(m4.m00 == 1); 84 | CHECK(m4.m10 == 0); 85 | CHECK(m4.m20 == 0); 86 | CHECK(m4.m01 == 0); 87 | CHECK(m4.m11 == 1); 88 | CHECK(m4.m21 == 0); 89 | CHECK(m4.m02 == 0); 90 | CHECK(m4.m12 == 0); 91 | CHECK(m4.m22 == 1); 92 | CHECK(m4.m03 == 0); 93 | CHECK(m4.m13 == 0); 94 | CHECK(m4.m23 == 0); 95 | 96 | const float f[] = { 97 | 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 98 | 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21 99 | }; 100 | auto m5 = matrix3x4::from_ptr(f); 101 | CHECK(memcmp(&m5, f, 12 * sizeof(float)) == 0); 102 | 103 | // attach 104 | auto& m6 = matrix3x4::attach_to_ptr(f); 105 | CHECK(m6.m00 == 12); 106 | CHECK(m6.m10 == 11); 107 | CHECK(m6.m20 == 10); 108 | CHECK(m6.m01 == 9); 109 | CHECK(m6.m11 == 8); 110 | CHECK(m6.m21 == 7); 111 | CHECK(m6.m02 == 6); 112 | CHECK(m6.m12 == 5); 113 | CHECK(m6.m22 == 4); 114 | CHECK(m6.m03 == 3); 115 | CHECK(m6.m13 == 2); 116 | CHECK(m6.m23 == 1); 117 | CHECK(reinterpret_cast(&m6) == f); 118 | 119 | auto m7 = matrix3x4::attach_to_array(f); 120 | CHECK(m7[0].m00 == 12); 121 | CHECK(m7[0].m10 == 11); 122 | CHECK(m7[0].m20 == 10); 123 | CHECK(m7[0].m01 == 9); 124 | CHECK(m7[0].m11 == 8); 125 | CHECK(m7[0].m21 == 7); 126 | CHECK(m7[0].m02 == 6); 127 | CHECK(m7[0].m12 == 5); 128 | CHECK(m7[0].m22 == 4); 129 | CHECK(m7[0].m03 == 3); 130 | CHECK(m7[0].m13 == 2); 131 | CHECK(m7[0].m23 == 1); 132 | CHECK(m7 == &m6); 133 | CHECK(reinterpret_cast(m7) == f); 134 | 135 | float ff[] = { 136 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 137 | 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 138 | }; 139 | auto& m8 = matrix3x4::attach_to_ptr(ff); 140 | m8.m11 = 34; 141 | m8.m03 = 77; 142 | CHECK(ff[4] == 34); 143 | CHECK(ff[9] == 77); 144 | 145 | auto m9 = matrix3x4::attach_to_array(ff); 146 | m9[0].m22 = 21; 147 | m9[1].m00 = 30; 148 | CHECK(ff[8] == 21); 149 | CHECK(ff[12] == 30); 150 | } 151 | 152 | 153 | TEST_CASE("compare") 154 | { 155 | auto m0 = matrix3x4::zero(); 156 | CHECK(m0 == matrix3x4::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 157 | CHECK(m0 != matrix3x4::columns(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 158 | CHECK(m0 != matrix3x4::columns(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 159 | CHECK(m0 != matrix3x4::columns(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 160 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)); 161 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); 162 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)); 163 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0)); 164 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0)); 165 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0)); 166 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0)); 167 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0)); 168 | CHECK(m0 != matrix3x4::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)); 169 | 170 | matrix3x4 m1; 171 | m1.m00 = 11; m1.m10 = 12; m1.m20 = 13; 172 | m1.m01 = 15; m1.m11 = 16; m1.m21 = 17; 173 | m1.m02 = 19; m1.m12 = 20; m1.m22 = 21; 174 | m1.m03 = 23; m1.m13 = 24; m1.m23 = 25; 175 | CHECK(m1 == matrix3x4::columns(11, 12, 13, 15, 16, 17, 19, 20, 21, 23, 24, 25)); 176 | CHECK(m1 != m0); 177 | 178 | m0.m00 = 11; m0.m10 = 12; m0.m20 = 13; 179 | m0.m01 = 15; m0.m11 = 16; m0.m21 = 17; 180 | m0.m02 = 19; m0.m12 = 20; m0.m22 = 21; 181 | m0.m03 = 23; m0.m13 = 24; m0.m23 = 25; 182 | CHECK(m1 == m0); 183 | 184 | CHECK(close(m1, m0)); 185 | CHECK(close(m0, m1)); 186 | m0.m21 += 1; 187 | CHECK(!close(m0, m1)); 188 | CHECK(close(m0, m1, 2.f)); 189 | m0.m00 = 11.000001f; m0.m10 = 12.000001f; m0.m20 = 13.000001f; 190 | m0.m01 = 15.000001f; m0.m11 = 16.000001f; m0.m21 = 17.000001f; 191 | m0.m02 = 19.000001f; m0.m12 = 20.000001f; m0.m22 = 21.000001f; 192 | m0.m03 = 23.000001f; m0.m13 = 24.000001f; m0.m23 = 25.000001f; 193 | CHECK(close(m0, m1)); 194 | } 195 | 196 | TEST_CASE("special_construction") 197 | { 198 | auto m0 = matrix3x4::translation(0, 0, 0); 199 | CHECK(m0 == matrix3x4::identity()); 200 | 201 | m0.m03 = 1; m0.m13 = 2; m0.m23 = 3; 202 | CHECK(m0 == matrix3x4::translation(v(1, 2, 3))); 203 | CHECK(m0 == matrix3x4::translation(1, 2, 3)); 204 | 205 | m0 = matrix3x4::scaling_uniform(1); 206 | CHECK(m0 == matrix3x4::identity()); 207 | CHECK(m0 == matrix3x4::scaling(1, 1, 1)); 208 | 209 | m0.m00 = 5; m0.m11 = 5; m0.m22 = 5; 210 | CHECK(m0 == matrix3x4::scaling_uniform(5)); 211 | CHECK(m0 == matrix3x4::scaling(5, 5, 5)); 212 | CHECK(m0 == matrix3x4::scaling(v(5, 5, 5))); 213 | 214 | m0.m00 = 2; m0.m11 = 4; m0.m22 = 6; 215 | CHECK(m0 == matrix3x4::scaling(2, 4, 6)); 216 | CHECK(m0 == matrix3x4::scaling(v(2, 4, 6))); 217 | } 218 | 219 | TEST_CASE("access") 220 | { 221 | auto m0 = matrix3x4::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); 222 | CHECK(m0.data() == reinterpret_cast(&m0)); 223 | CHECK(m0.data()[0] == 1); 224 | CHECK(m0.data()[1] == 2); 225 | CHECK(m0.data()[2] == 3); 226 | CHECK(m0.data()[3] == 4); 227 | CHECK(m0[4] == 5); 228 | CHECK(m0[5] == 6); 229 | CHECK(m0[6] == 7); 230 | CHECK(m0[7] == 8); 231 | CHECK(m0.at(8) == 9); 232 | CHECK(m0.at(9) == 10); 233 | CHECK(m0.at(10) == 11); 234 | CHECK(m0.at(11) == 12); 235 | CHECK(m0.data() == m0.as_ptr()); 236 | CHECK(m0.data() == m0.column(0)); 237 | CHECK(m0.data() + 3 == m0.column(1)); 238 | CHECK(m0.data() + 6 == m0.column(2)); 239 | CHECK(m0.data() + 9 == m0.column(3)); 240 | CHECK(m0(0, 2) == 7); 241 | CHECK(m0(1, 2) == 8); 242 | CHECK(m0.m(2, 2) == 9); 243 | 244 | CHECK(m0.as_matrix3x4_t() == matrix3x4_t::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); 245 | 246 | m0.column_vector(0) = v(20, 30, 40); 247 | CHECK(m0.m00 == 20); 248 | CHECK(m0.m20 == 40); 249 | CHECK(m0.column_vector<2>(0) == v(20, 30)); 250 | m0.column_vector<3>(1) = v(-1, -2, -3); 251 | CHECK(m0.m01 == -1); 252 | CHECK(m0.m21 == -3); 253 | CHECK(m0.column_vector<2>(1, 1) == v(-2, -3)); 254 | 255 | CHECK(m0.row_vector(0) == v(20, -1, 7, 10)); 256 | CHECK(m0.row_vector<2>(2, 2) == v(9, 12)); 257 | 258 | CHECK(m0.main_diagonal() == v(20, -2, 9)); 259 | CHECK(m0.main_diagonal<2>(1) == v(-2, 9)); 260 | 261 | const auto m1 = matrix3x4::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); 262 | CHECK(m1.data() == reinterpret_cast(&m1)); 263 | CHECK(m1.data()[0] == 1); 264 | CHECK(m1.data()[1] == 2); 265 | CHECK(m1.data()[2] == 3); 266 | CHECK(m1.data()[3] == 4); 267 | CHECK(m1[4] == 5); 268 | CHECK(m1[5] == 6); 269 | CHECK(m1[6] == 7); 270 | CHECK(m1[7] == 8); 271 | CHECK(m1.at(8) == 9); 272 | CHECK(m1.at(9) == 10); 273 | CHECK(m1.at(10) == 11); 274 | CHECK(m1.at(11) == 12); 275 | CHECK(m1.data() == m1.as_ptr()); 276 | CHECK(m1.data() == m1.column(0)); 277 | CHECK(m1.data() + 3 == m1.column(1)); 278 | CHECK(m1.data() + 6 == m1.column(2)); 279 | CHECK(m1.data() + 9 == m1.column(3)); 280 | CHECK(m1(0, 2) == 7); 281 | CHECK(m1(1, 2) == 8); 282 | CHECK(m1.m(2, 2) == 9); 283 | CHECK(m1.m(1, 3) == 11); 284 | 285 | CHECK(m1.as_matrix3x4_t() == matrix3x4_t::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); 286 | 287 | CHECK(m1.column_vector<2>(0) == v(1, 2)); 288 | CHECK(m1.column_vector<2>(1, 1) == v(5, 6)); 289 | 290 | CHECK(m1.row_vector(0) == v(1, 4, 7, 10)); 291 | CHECK(m1.row_vector<2>(2, 1) == v(6, 9)); 292 | 293 | CHECK(m1.main_diagonal() == v(1, 5, 9)); 294 | CHECK(m1.main_diagonal<2>(1) == v(5, 9)); 295 | } 296 | 297 | 298 | TEST_CASE("std") 299 | { 300 | auto m0 = matrix3x4::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); 301 | CHECK(m0.front() == 1); 302 | CHECK(m0.back() == 12); 303 | CHECK(m0.begin() == m0.data()); 304 | CHECK(m0.begin() + 12 == m0.end()); 305 | CHECK(m0.rbegin() + 12 == m0.rend()); 306 | auto i = m0.rbegin(); 307 | CHECK(*i == 12); 308 | ++i; 309 | CHECK(*i == 11); 310 | 311 | for (auto& e : m0) 312 | { 313 | e += 10; 314 | } 315 | 316 | const auto m1 = m0; 317 | 318 | CHECK(m1.front() == 11); 319 | CHECK(m1.back() == 22); 320 | CHECK(m1.begin() == m1.data()); 321 | CHECK(m1.begin() + 12 == m1.end()); 322 | CHECK(m1.rbegin() + 12 == m1.rend()); 323 | auto ci = m1.rbegin(); 324 | CHECK(*ci == 22); 325 | ++ci; 326 | CHECK(*ci == 21); 327 | } 328 | 329 | 330 | TEST_CASE("members") 331 | { 332 | auto m0 = matrix3x4::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); 333 | CHECK(m0 == +m0); 334 | auto m1 = matrix3x4::columns(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12); 335 | CHECK(-m0 == m1); 336 | CHECK(-m1 == m0); 337 | 338 | m0 += m1; 339 | CHECK(m0 == matrix3x4::zero()); 340 | 341 | m0 -= m1; 342 | CHECK(m0 == -m1); 343 | 344 | m1 = m0; 345 | m0 *= 2; 346 | CHECK(m0 == matrix3x4::columns(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24)); 347 | 348 | m0 /= 2; 349 | CHECK(m0 == m1); 350 | 351 | m0.div(m1); 352 | CHECK(m0 == matrix3x4::uniform(1)); 353 | 354 | m0.mul(m1); 355 | CHECK(m0 == m1); 356 | 357 | m0 *= matrix3x4::identity(); 358 | CHECK(m0 == m1); 359 | 360 | auto m2 = matrix3x4::identity(); 361 | m2.inverse(); 362 | CHECK(m2 == matrix3x4::identity()); 363 | m2.transpose(); 364 | CHECK(m2 == matrix3x4::identity()); 365 | 366 | m0.transpose(); 367 | CHECK(m0 == matrix3x4::rows(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0)); 368 | 369 | m1 = matrix3x4::rows( 370 | 1, 2, 3, 4, 371 | 5, 3, 2, 2, 372 | 2, 1, 1, 1 373 | ); 374 | m2 = matrix3x4::rows(1, 1, 1, 3, 1, 3, 4, 2, 0, 5, 1, 2); 375 | m1 *= m2; 376 | CHECK(m1 == matrix3x4::rows(3, 22, 12, 17, 8, 24, 19, 27, 3, 10, 7, 11)); 377 | 378 | m0 = m2; 379 | auto det = m2.inverse(); 380 | CHECK(det == -13); 381 | m0 *= m2; 382 | CHECK(YamaApprox(m0) == matrix3x4::identity()); 383 | 384 | m0 = m1; 385 | det = m1.inverse(); 386 | CHECK(det == 52); 387 | m0 *= m1; 388 | CHECK(YamaApprox(m0) == matrix3x4::identity()); 389 | } 390 | 391 | TEST_CASE("ops") 392 | { 393 | const auto m0 = matrix3x4::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); 394 | auto m1 = matrix3x4::columns(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12); 395 | auto m3 = matrix3x4::columns(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24); 396 | 397 | CHECK(m0 + m1 == matrix3x4::zero()); 398 | CHECK(m0 - m1 == m3); 399 | 400 | CHECK(abs(m1) == m0); 401 | CHECK(abs(m0) == m0); 402 | 403 | m1 = m0 * 2.f; 404 | CHECK(m1 == m3); 405 | 406 | m1 = 2.f * m0; 407 | CHECK(m1 == m3); 408 | 409 | m1 = m3 / 2.f; 410 | CHECK(m1 == m0); 411 | 412 | m1 = 16.f / m0; 413 | auto m2 = matrix3x4::columns( 414 | 16, 8, 5.333333f, 4, 3.2f, 415 | 2.666667f, 2.285714f, 2, 1.777778f, 416 | 1.6f, 1.454545f, 1.333333f); 417 | CHECK(YamaApprox(m1) == m2); 418 | 419 | m1 = div(m3, m0); 420 | CHECK(m1 == matrix3x4::uniform(2)); 421 | CHECK(mul(m0, m1) == m3); 422 | 423 | CHECK(isfinite(m0)); 424 | CHECK(isfinite(m1)); 425 | CHECK(isfinite(m2)); 426 | CHECK(isfinite(m3)); 427 | 428 | m1.m11 = std::numeric_limits::infinity(); 429 | CHECK(!isfinite(m1)); 430 | 431 | m2.m20 = std::numeric_limits::quiet_NaN(); 432 | CHECK(!isfinite(m2)); 433 | 434 | CHECK(!isfinite(m1 + m2)); 435 | 436 | m1 = matrix3x4::rows( 437 | 1, 2, 3, 4, 438 | 5, 3, 2, 2, 439 | 2, 1, 1, 1 440 | ); 441 | 442 | CHECK(m1 * matrix3x4::identity() == m1); 443 | CHECK(matrix3x4::identity() * m1 == m1); 444 | 445 | m2 = matrix3x4::rows(1, 1, 1, 3, 1, 3, 4, 2, 0, 5, 1, 2); 446 | CHECK(m1 * m2 == 447 | matrix3x4::rows(3, 22, 12, 17, 8, 24, 19, 27, 3, 10, 7, 11)); 448 | 449 | float det; 450 | m2 = inverse(m1, det); 451 | CHECK(det == -4); 452 | CHECK(YamaApprox(m1 * m2) == matrix3x4::identity()); 453 | CHECK(YamaApprox(m2 * m1) == matrix3x4::identity()); 454 | } 455 | 456 | TEST_CASE("transform") 457 | { 458 | const auto i = matrix3x4::identity(); 459 | vector3 vec = v(1, 2, 3); 460 | CHECK(transform_coord(vec, i) == vec); 461 | CHECK(transform_normal(vec, i) == vec); 462 | 463 | auto t = matrix3x4::translation(3, 1, -2); 464 | CHECK(transform_coord(vec, t) == v(4, 3, 1)); 465 | CHECK(transform_normal(vec, t) == vec); 466 | 467 | t = matrix3x4::translation(v(3, 1, -2)); 468 | CHECK(transform_coord(vec, t) == v(4, 3, 1)); 469 | 470 | auto s = matrix3x4::scaling_uniform(2); 471 | CHECK(transform_coord(vec, s) == v(2, 4, 6)); 472 | 473 | s = matrix3x4::scaling(2, 0.5f, 1); 474 | CHECK(transform_coord(vec, s) == v(2, 1, 3)); 475 | 476 | s = matrix3x4::scaling(v(1, 0.5f, 0.33333333f)); 477 | CHECK(YamaApprox(transform_coord(vec, s)) == v(1, 1, 1)); 478 | 479 | const auto ux = vector3::unit_x(); 480 | const auto uy = vector3::unit_y(); 481 | const auto uz = vector3::unit_z(); 482 | 483 | CHECK(YamaApprox(matrix3x4::rotation_axis(ux, 2.11f)) == matrix3x4::rotation_x(2.11f)); 484 | CHECK(YamaApprox(matrix3x4::rotation_axis(uy, 0.13f)) == matrix3x4::rotation_y(0.13f)); 485 | CHECK(YamaApprox(matrix3x4::rotation_axis(uz, 1.22f)) == matrix3x4::rotation_z(1.22f)); 486 | 487 | auto q0 = quaternion::rotation_x(constants::PI_HALF); 488 | auto m1 = matrix3x4::rotation_quaternion(q0); 489 | 490 | auto m0 = matrix3x4::rotation_x(constants::PI_HALF); 491 | CHECK(YamaApprox(m1) == m0); 492 | CHECK(YamaApprox(transform_coord(ux, m0)) == ux); 493 | CHECK(YamaApprox(transform_coord(uy, m0)) == uz); 494 | CHECK(YamaApprox(transform_coord(uz, m0)) == -uy); 495 | CHECK(YamaApprox(transform_normal(ux, m0)) == ux); 496 | CHECK(YamaApprox(transform_normal(uy, m0)) == uz); 497 | CHECK(YamaApprox(transform_normal(uz, m0)) == -uy); 498 | 499 | q0 = quaternion::rotation_y(constants::PI_HALF); 500 | m1 = matrix3x4::rotation_quaternion(q0); 501 | m0 = matrix3x4::rotation_y(constants::PI_HALF); 502 | CHECK(YamaApprox(m1) == m0); 503 | CHECK(YamaApprox(transform_coord(ux, m0)) == -uz); 504 | CHECK(YamaApprox(transform_coord(uy, m0)) == uy); 505 | CHECK(YamaApprox(transform_coord(uz, m0)) == ux); 506 | CHECK(YamaApprox(transform_normal(ux, m0)) == -uz); 507 | CHECK(YamaApprox(transform_normal(uy, m0)) == uy); 508 | CHECK(YamaApprox(transform_normal(uz, m0)) == ux); 509 | 510 | q0 = quaternion::rotation_z(constants::PI_HALF); 511 | m1 = matrix3x4::rotation_quaternion(q0); 512 | m0 = matrix3x4::rotation_z(constants::PI_HALF); 513 | CHECK(YamaApprox(m1) == m0); 514 | CHECK(YamaApprox(transform_coord(ux, m0)) == uy); 515 | CHECK(YamaApprox(transform_coord(uy, m0)) == -ux); 516 | CHECK(YamaApprox(transform_coord(uz, m0)) == uz); 517 | CHECK(YamaApprox(transform_normal(ux, m0)) == uy); 518 | CHECK(YamaApprox(transform_normal(uy, m0)) == -ux); 519 | CHECK(YamaApprox(transform_normal(uz, m0)) == uz); 520 | 521 | auto axis = v(1, 2, 3); 522 | auto angle = 2.66f; 523 | 524 | m0 = matrix3x4::rotation_axis(axis, angle); 525 | 526 | q0 = quaternion::rotation_axis(axis, angle); 527 | m1 = matrix3x4::rotation_quaternion(q0); 528 | CHECK(YamaApprox(m1) == m0); 529 | 530 | auto v0 = normalize(v(1, 2, 3)); 531 | 532 | CHECK(matrix3x4::rotation_vectors(v0, v0) == matrix3x4::identity()); 533 | 534 | auto v1 = normalize(v(-3, 5, 11)); 535 | 536 | m0 = matrix3x4::rotation_vectors(v0, v1); 537 | CHECK(YamaApprox(transform_coord(v0, m0)) == v1); 538 | CHECK(YamaApprox(transform_normal(v0, m0)) == v1); 539 | 540 | v1 = -v0; 541 | m0 = matrix3x4::rotation_vectors(v0, v1); 542 | CHECK(YamaApprox(transform_coord(v(1, 2, 3), m0)) == v(-1, -2, -3)); 543 | CHECK(YamaApprox(transform_normal(v(1, 2, 3), m0)) == v(-1, -2, -3)); 544 | } 545 | -------------------------------------------------------------------------------- /test/unit/matrix4x4.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/matrix4x4.hpp" 5 | #include "common.hpp" 6 | #include "yama/ext/ostream.hpp" 7 | 8 | using namespace yama; 9 | using doctest::Approx; 10 | 11 | TEST_SUITE_BEGIN("matrix4x4"); 12 | 13 | TEST_CASE("construction") 14 | { 15 | double d0[] = { 16 | 0, 0, 0, 0, 17 | 0, 0, 0, 0, 18 | 0, 0, 0, 0, 19 | 0, 0, 0, 0, 20 | }; 21 | 22 | auto m0 = matrix4x4_t::zero(); 23 | CHECK(m0.m00 == 0); 24 | CHECK(m0.m10 == 0); 25 | CHECK(m0.m20 == 0); 26 | CHECK(m0.m30 == 0); 27 | CHECK(m0.m01 == 0); 28 | CHECK(m0.m11 == 0); 29 | CHECK(m0.m21 == 0); 30 | CHECK(m0.m31 == 0); 31 | CHECK(m0.m02 == 0); 32 | CHECK(m0.m12 == 0); 33 | CHECK(m0.m22 == 0); 34 | CHECK(m0.m32 == 0); 35 | CHECK(m0.m03 == 0); 36 | CHECK(m0.m13 == 0); 37 | CHECK(m0.m23 == 0); 38 | CHECK(m0.m33 == 0); 39 | CHECK(memcmp(d0, &m0, 16 * sizeof(double)) == 0); 40 | 41 | float f1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 42 | auto m1 = matrix::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); 43 | CHECK(m1.m00 == 1); 44 | CHECK(m1.m10 == 2); 45 | CHECK(m1.m20 == 3); 46 | CHECK(m1.m30 == 4); 47 | CHECK(m1.m01 == 5); 48 | CHECK(m1.m11 == 6); 49 | CHECK(m1.m21 == 7); 50 | CHECK(m1.m31 == 8); 51 | CHECK(m1.m02 == 9); 52 | CHECK(m1.m12 == 10); 53 | CHECK(m1.m22 == 11); 54 | CHECK(m1.m32 == 12); 55 | CHECK(m1.m03 == 13); 56 | CHECK(m1.m13 == 14); 57 | CHECK(m1.m23 == 15); 58 | CHECK(m1.m33 == 16); 59 | CHECK(memcmp(f1, &m1, 16 * sizeof(float)) == 0); 60 | 61 | auto m2 = matrix::rows(1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16); 62 | CHECK(m2.m00 == 1); 63 | CHECK(m2.m10 == 2); 64 | CHECK(m2.m20 == 3); 65 | CHECK(m2.m30 == 4); 66 | CHECK(m2.m01 == 5); 67 | CHECK(m2.m11 == 6); 68 | CHECK(m2.m21 == 7); 69 | CHECK(m2.m31 == 8); 70 | CHECK(m2.m02 == 9); 71 | CHECK(m2.m12 == 10); 72 | CHECK(m2.m22 == 11); 73 | CHECK(m2.m32 == 12); 74 | CHECK(m2.m03 == 13); 75 | CHECK(m2.m13 == 14); 76 | CHECK(m2.m23 == 15); 77 | CHECK(m2.m33 == 16); 78 | CHECK(memcmp(f1, &m1, 16 * sizeof(float)) == 0); 79 | 80 | auto m3 = matrix4x4_t::uniform(3); 81 | CHECK(m3.m00 == 3); 82 | CHECK(m3.m10 == 3); 83 | CHECK(m3.m20 == 3); 84 | CHECK(m3.m30 == 3); 85 | CHECK(m3.m01 == 3); 86 | CHECK(m3.m11 == 3); 87 | CHECK(m3.m21 == 3); 88 | CHECK(m3.m31 == 3); 89 | CHECK(m3.m02 == 3); 90 | CHECK(m3.m12 == 3); 91 | CHECK(m3.m22 == 3); 92 | CHECK(m3.m32 == 3); 93 | CHECK(m3.m03 == 3); 94 | CHECK(m3.m13 == 3); 95 | CHECK(m3.m23 == 3); 96 | CHECK(m3.m33 == 3); 97 | 98 | auto m4 = matrix::identity(); 99 | CHECK(m4.m00 == 1); 100 | CHECK(m4.m10 == 0); 101 | CHECK(m4.m20 == 0); 102 | CHECK(m4.m30 == 0); 103 | CHECK(m4.m01 == 0); 104 | CHECK(m4.m11 == 1); 105 | CHECK(m4.m21 == 0); 106 | CHECK(m4.m31 == 0); 107 | CHECK(m4.m02 == 0); 108 | CHECK(m4.m12 == 0); 109 | CHECK(m4.m22 == 1); 110 | CHECK(m4.m32 == 0); 111 | CHECK(m4.m03 == 0); 112 | CHECK(m4.m13 == 0); 113 | CHECK(m4.m23 == 0); 114 | CHECK(m4.m33 == 1); 115 | 116 | const float f[] = { 117 | 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 118 | 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21 119 | }; 120 | auto m5 = matrix::from_ptr(f); 121 | CHECK(memcmp(&m5, f, 16 * sizeof(float)) == 0); 122 | 123 | // attach 124 | auto& m6 = matrix::attach_to_ptr(f); 125 | CHECK(m6.m00 == 16); 126 | CHECK(m6.m10 == 15); 127 | CHECK(m6.m20 == 14); 128 | CHECK(m6.m30 == 13); 129 | CHECK(m6.m01 == 12); 130 | CHECK(m6.m11 == 11); 131 | CHECK(m6.m21 == 10); 132 | CHECK(m6.m31 == 9); 133 | CHECK(m6.m02 == 8); 134 | CHECK(m6.m12 == 7); 135 | CHECK(m6.m22 == 6); 136 | CHECK(m6.m32 == 5); 137 | CHECK(m6.m03 == 4); 138 | CHECK(m6.m13 == 3); 139 | CHECK(m6.m23 == 2); 140 | CHECK(m6.m33 == 1); 141 | CHECK(reinterpret_cast(&m6) == f); 142 | 143 | auto m7 = matrix::attach_to_array(f); 144 | CHECK(m7[0].m00 == 16); 145 | CHECK(m7[0].m10 == 15); 146 | CHECK(m7[0].m20 == 14); 147 | CHECK(m7[0].m30 == 13); 148 | CHECK(m7[0].m01 == 12); 149 | CHECK(m7[0].m11 == 11); 150 | CHECK(m7[0].m21 == 10); 151 | CHECK(m7[0].m31 == 9); 152 | CHECK(m7[0].m02 == 8); 153 | CHECK(m7[0].m12 == 7); 154 | CHECK(m7[0].m22 == 6); 155 | CHECK(m7[0].m32 == 5); 156 | CHECK(m7[0].m03 == 4); 157 | CHECK(m7[0].m13 == 3); 158 | CHECK(m7[0].m23 == 2); 159 | CHECK(m7[0].m33 == 1); 160 | CHECK(m7 == &m6); 161 | //CHECK(m7[0] == m6); 162 | CHECK(reinterpret_cast(m7) == f); 163 | 164 | float ff[] = { 165 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 166 | 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 167 | }; 168 | auto& m8 = matrix::attach_to_ptr(ff); 169 | m8.m11 = 34; 170 | m8.m03 = 77; 171 | CHECK(ff[5] == 34); 172 | CHECK(ff[12] == 77); 173 | 174 | auto m9 = matrix::attach_to_array(ff); 175 | m9[0].m22 = 21; 176 | m9[1].m00 = 30; 177 | CHECK(ff[10] == 21); 178 | CHECK(ff[16] == 30); 179 | } 180 | 181 | 182 | TEST_CASE("compare") 183 | { 184 | auto m0 = matrix::zero(); 185 | CHECK(m0 == matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 186 | CHECK(m0 != matrix::columns(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 187 | CHECK(m0 != matrix::columns(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 188 | CHECK(m0 != matrix::columns(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 189 | CHECK(m0 != matrix::columns(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 190 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 191 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 192 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 193 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)); 194 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); 195 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)); 196 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0)); 197 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0)); 198 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0)); 199 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0)); 200 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0)); 201 | CHECK(m0 != matrix::columns(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)); 202 | 203 | matrix m1; 204 | m1.m00 = 11; m1.m10 = 12; m1.m20 = 13; m1.m30 = 14; 205 | m1.m01 = 15; m1.m11 = 16; m1.m21 = 17; m1.m31 = 18; 206 | m1.m02 = 19; m1.m12 = 20; m1.m22 = 21; m1.m32 = 22; 207 | m1.m03 = 23; m1.m13 = 24; m1.m23 = 25; m1.m33 = 26; 208 | CHECK(m1 == matrix::columns(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26)); 209 | CHECK(m1 != m0); 210 | 211 | m0.m00 = 11; m0.m10 = 12; m0.m20 = 13; m0.m30 = 14; 212 | m0.m01 = 15; m0.m11 = 16; m0.m21 = 17; m0.m31 = 18; 213 | m0.m02 = 19; m0.m12 = 20; m0.m22 = 21; m0.m32 = 22; 214 | m0.m03 = 23; m0.m13 = 24; m0.m23 = 25; m0.m33 = 26; 215 | CHECK(m1 == m0); 216 | 217 | CHECK(close(m1, m0)); 218 | CHECK(close(m0, m1)); 219 | m0.m21 += 1; 220 | CHECK(!close(m0, m1)); 221 | CHECK(close(m0, m1, 2.f)); 222 | m0.m00 = 11.000001f; m0.m10 = 12.000001f; m0.m20 = 13.000001f; m0.m30 = 14.000001f; 223 | m0.m01 = 15.000001f; m0.m11 = 16.000001f; m0.m21 = 17.000001f; m0.m31 = 18.000001f; 224 | m0.m02 = 19.000001f; m0.m12 = 20.000001f; m0.m22 = 21.000001f; m0.m32 = 22.000001f; 225 | m0.m03 = 23.000001f; m0.m13 = 24.000001f; m0.m23 = 25.000001f; m0.m33 = 26.000001f; 226 | CHECK(close(m0, m1)); 227 | } 228 | 229 | TEST_CASE("special_construction") 230 | { 231 | auto m0 = matrix::translation(0, 0, 0); 232 | CHECK(m0 == matrix::identity()); 233 | 234 | m0.m03 = 1; m0.m13 = 2; m0.m23 = 3; 235 | CHECK(m0 == matrix::translation(v(1, 2, 3))); 236 | CHECK(m0 == matrix::translation(1, 2, 3)); 237 | 238 | m0 = matrix::scaling_uniform(1); 239 | CHECK(m0 == matrix::identity()); 240 | CHECK(m0 == matrix::scaling(1, 1, 1)); 241 | 242 | m0.m00 = 5; m0.m11 = 5; m0.m22 = 5; 243 | CHECK(m0 == matrix::scaling_uniform(5)); 244 | CHECK(m0 == matrix::scaling(5, 5, 5)); 245 | CHECK(m0 == matrix::scaling(v(5, 5, 5))); 246 | 247 | m0.m00 = 2; m0.m11 = 4; m0.m22 = 6; 248 | CHECK(m0 == matrix::scaling(2, 4, 6)); 249 | CHECK(m0 == matrix::scaling(v(2, 4, 6))); 250 | 251 | m0 = matrix::basis_transform(vector3::zero(), vector3::unit_x(), vector3::unit_y(), vector3::unit_z()); 252 | CHECK(m0 == matrix::identity()); 253 | } 254 | 255 | TEST_CASE("access") 256 | { 257 | auto m0 = matrix::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); 258 | CHECK(m0.data() == reinterpret_cast(&m0)); 259 | CHECK(m0.data()[0] == 1); 260 | CHECK(m0.data()[1] == 2); 261 | CHECK(m0.data()[2] == 3); 262 | CHECK(m0.data()[3] == 4); 263 | CHECK(m0[4] == 5); 264 | CHECK(m0[5] == 6); 265 | CHECK(m0[6] == 7); 266 | CHECK(m0[7] == 8); 267 | CHECK(m0.at(8) == 9); 268 | CHECK(m0.at(9) == 10); 269 | CHECK(m0.at(10) == 11); 270 | CHECK(m0.at(11) == 12); 271 | CHECK(m0.data() == m0.as_ptr()); 272 | CHECK(m0.data() == m0.column(0)); 273 | CHECK(m0.data() + 4 == m0.column(1)); 274 | CHECK(m0.data() + 8 == m0.column(2)); 275 | CHECK(m0.data() + 12 == m0.column(3)); 276 | CHECK(m0(0, 3) == 13); 277 | CHECK(m0(1, 3) == 14); 278 | CHECK(m0.m(2, 3) == 15); 279 | CHECK(m0.m(3, 3) == 16); 280 | 281 | CHECK(m0.as_matrix4x4_t() == matrix4x4_t::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); 282 | 283 | m0.column_vector(0) = v(20, 30, 40, 50); 284 | CHECK(m0.m00 == 20); 285 | CHECK(m0.m30 == 50); 286 | CHECK(m0.column_vector<2>(0) == v(20, 30)); 287 | m0.column_vector<4>(1) = v(-1, -2, -3, -4); 288 | CHECK(m0.m01 == -1); 289 | CHECK(m0.m31 == -4); 290 | CHECK(m0.column_vector<3>(1, 1) == v(-2, -3, -4)); 291 | 292 | CHECK(m0.row_vector(0) == v(20, -1, 9, 13)); 293 | CHECK(m0.row_vector<2>(2, 2) == v(11, 15)); 294 | 295 | CHECK(m0.main_diagonal() == v(20, -2, 11, 16)); 296 | CHECK(m0.main_diagonal<3>(1) == v(-2, 11, 16)); 297 | 298 | const auto m1 = matrix::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); 299 | CHECK(m1.data() == reinterpret_cast(&m1)); 300 | CHECK(m1.data()[0] == 1); 301 | CHECK(m1.data()[1] == 2); 302 | CHECK(m1.data()[2] == 3); 303 | CHECK(m1.data()[3] == 4); 304 | CHECK(m1[4] == 5); 305 | CHECK(m1[5] == 6); 306 | CHECK(m1[6] == 7); 307 | CHECK(m1[7] == 8); 308 | CHECK(m1.at(8) == 9); 309 | CHECK(m1.at(9) == 10); 310 | CHECK(m1.at(10) == 11); 311 | CHECK(m1.at(11) == 12); 312 | CHECK(m1.data() == m1.as_ptr()); 313 | CHECK(m1.data() == m1.column(0)); 314 | CHECK(m1.data() + 4 == m1.column(1)); 315 | CHECK(m1.data() + 8 == m1.column(2)); 316 | CHECK(m1.data() + 12 == m1.column(3)); 317 | CHECK(m1(0, 3) == 13); 318 | CHECK(m1(1, 3) == 14); 319 | CHECK(m1.m(2, 3) == 15); 320 | CHECK(m1.m(3, 3) == 16); 321 | 322 | CHECK(m1.as_matrix4x4_t() == matrix4x4_t::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); 323 | 324 | CHECK(m1.column_vector<2>(0) == v(1, 2)); 325 | CHECK(m1.column_vector<3>(1, 1) == v(6, 7, 8)); 326 | 327 | CHECK(m1.row_vector(0) == v(1, 5, 9, 13)); 328 | CHECK(m1.row_vector<2>(2, 2) == v(11, 15)); 329 | 330 | CHECK(m1.main_diagonal() == v(1, 6, 11, 16)); 331 | CHECK(m1.main_diagonal<3>(1) == v(6, 11, 16)); 332 | } 333 | 334 | 335 | TEST_CASE("std") 336 | { 337 | auto m0 = matrix::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); 338 | CHECK(m0.front() == 1); 339 | CHECK(m0.back() == 16); 340 | CHECK(m0.begin() == m0.data()); 341 | CHECK(m0.begin() + 16 == m0.end()); 342 | CHECK(m0.rbegin() + 16 == m0.rend()); 343 | auto i = m0.rbegin(); 344 | CHECK(*i == 16); 345 | ++i; 346 | CHECK(*i == 15); 347 | 348 | for (auto& e : m0) 349 | { 350 | e += 10; 351 | } 352 | 353 | const auto m1 = m0; 354 | 355 | CHECK(m1.front() == 11); 356 | CHECK(m1.back() == 26); 357 | CHECK(m1.begin() == m1.data()); 358 | CHECK(m1.begin() + 16 == m1.end()); 359 | CHECK(m1.rbegin() + 16 == m1.rend()); 360 | auto ci = m1.rbegin(); 361 | CHECK(*ci == 26); 362 | ++ci; 363 | CHECK(*ci == 25); 364 | } 365 | 366 | 367 | TEST_CASE("members") 368 | { 369 | auto m0 = matrix::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); 370 | CHECK(m0 == +m0); 371 | auto m1 = matrix::columns(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16); 372 | CHECK(-m0 == m1); 373 | CHECK(-m1 == m0); 374 | 375 | m0 += m1; 376 | CHECK(m0 == matrix::zero()); 377 | 378 | m0 -= m1; 379 | CHECK(m0 == -m1); 380 | 381 | m1 = m0; 382 | m0 *= 2; 383 | CHECK(m0 == matrix::columns(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32)); 384 | 385 | m0 /= 2; 386 | CHECK(m0 == m1); 387 | 388 | m0.div(m1); 389 | CHECK(m0 == matrix::uniform(1)); 390 | 391 | m0.mul(m1); 392 | CHECK(m0 == m1); 393 | 394 | m0 *= matrix::identity(); 395 | CHECK(m0 == m1); 396 | CHECK(matrix::identity().determinant() == 1); 397 | 398 | auto m2 = matrix::identity(); 399 | m2.inverse(); 400 | CHECK(m2 == matrix::identity()); 401 | m2.transpose(); 402 | CHECK(m2 == matrix::identity()); 403 | 404 | m0.transpose(); 405 | CHECK(m0 == matrix::rows(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); 406 | 407 | m0 = m1 = matrix::rows( 408 | 1, 2, 3, 4, 409 | 5, 3, 2, 2, 410 | 2, 1, 1, 1, 411 | 5, 6, 10, 2 412 | ); 413 | 414 | auto det = m1.inverse(); 415 | CHECK(det == 43); 416 | m0 *= m1; 417 | CHECK(YamaApprox(m0) == matrix::identity()); 418 | 419 | m0 = m2 = matrix::rows(3, 22, 12, 5, 17, 8, 24, 6, 19, 27, 3, 7, 10, 7, 11, 8); 420 | det = m2.inverse(); 421 | CHECK(det == 47634); 422 | m0 *= m2; 423 | CHECK(YamaApprox(m0) == matrix::identity()); 424 | } 425 | 426 | TEST_CASE("ops") 427 | { 428 | const auto m0 = matrix::columns(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); 429 | auto m1 = matrix::columns(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16); 430 | auto m3 = matrix::columns(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32); 431 | 432 | CHECK(m0 + m1 == matrix::zero()); 433 | CHECK(m0 - m1 == m3); 434 | 435 | CHECK(abs(m1) == m0); 436 | CHECK(abs(m0) == m0); 437 | 438 | m1 = m0 * 2.f; 439 | CHECK(m1 == m3); 440 | 441 | m1 = 2.f * m0; 442 | CHECK(m1 == m3); 443 | 444 | m1 = m3 / 2.f; 445 | CHECK(m1 == m0); 446 | 447 | m1 = 16.f / m0; 448 | auto m2 = matrix::columns( 449 | 16, 8, 5.333333f, 4, 3.2f, 450 | 2.666667f, 2.285714f, 2, 1.777778f, 451 | 1.6f, 1.454545f, 1.333333f, 452 | 1.230769f, 1.142857f, 1.066667f, 1); 453 | CHECK(YamaApprox(m1) == m2); 454 | 455 | m1 = div(m3, m0); 456 | CHECK(m1 == matrix::uniform(2)); 457 | CHECK(mul(m0, m1) == m3); 458 | 459 | CHECK(isfinite(m0)); 460 | CHECK(isfinite(m1)); 461 | CHECK(isfinite(m2)); 462 | CHECK(isfinite(m3)); 463 | 464 | m1.m11 = std::numeric_limits::infinity(); 465 | CHECK(!isfinite(m1)); 466 | 467 | m2.m30 = std::numeric_limits::quiet_NaN(); 468 | CHECK(!isfinite(m2)); 469 | 470 | CHECK(!isfinite(m1 + m2)); 471 | 472 | m1 = matrix::rows( 473 | 1, 2, 3, 4, 474 | 5, 3, 2, 2, 475 | 2, 1, 1, 1, 476 | 5, 6, 10, 2 477 | ); 478 | 479 | CHECK(m1 * matrix::identity() == m1); 480 | CHECK(matrix::identity() * m1 == m1); 481 | 482 | float det; 483 | m2 = inverse(m1, det); 484 | CHECK(det == 43); 485 | CHECK(YamaApprox(m1 * m2) == matrix::identity()); 486 | CHECK(YamaApprox(m2 * m1) == matrix::identity()); 487 | } 488 | 489 | TEST_CASE("transform") 490 | { 491 | const auto i = matrix::identity(); 492 | vector3 vec = v(1, 2, 3); 493 | CHECK(transform_coord(vec, i) == vec); 494 | CHECK(transform_normal(vec, i) == vec); 495 | 496 | auto t = matrix::translation(3, 1, -2); 497 | CHECK(transform_coord(vec, t) == v(4, 3, 1)); 498 | CHECK(transform_normal(vec, t) == vec); 499 | 500 | t = matrix::translation(v(3, 1, -2)); 501 | CHECK(transform_coord(vec, t) == v(4, 3, 1)); 502 | 503 | auto s = matrix::scaling_uniform(2); 504 | CHECK(transform_coord(vec, s) == v(2, 4, 6)); 505 | 506 | s = matrix::scaling(2, 0.5f, 1); 507 | CHECK(transform_coord(vec, s) == v(2, 1, 3)); 508 | 509 | s = matrix::scaling(v(1, 0.5f, 0.33333333f)); 510 | CHECK(YamaApprox(transform_coord(vec, s)) == v(1, 1, 1)); 511 | 512 | const auto ux = vector3::unit_x(); 513 | const auto uy = vector3::unit_y(); 514 | const auto uz = vector3::unit_z(); 515 | 516 | CHECK(YamaApprox(matrix::rotation_axis(ux, 2.11f)) == matrix::rotation_x(2.11f)); 517 | CHECK(YamaApprox(matrix::rotation_axis(uy, 0.13f)) == matrix::rotation_y(0.13f)); 518 | CHECK(YamaApprox(matrix::rotation_axis(uz, 1.22f)) == matrix::rotation_z(1.22f)); 519 | 520 | auto q0 = quaternion::rotation_x(constants::PI_HALF); 521 | auto m1 = matrix::rotation_quaternion(q0); 522 | 523 | auto m0 = matrix::rotation_x(constants::PI_HALF); 524 | CHECK(YamaApprox(m1) == m0); 525 | CHECK(YamaApprox(transform_coord(ux, m0)) == ux); 526 | CHECK(YamaApprox(transform_coord(uy, m0)) == uz); 527 | CHECK(YamaApprox(transform_coord(uz, m0)) == -uy); 528 | CHECK(YamaApprox(transform_normal(ux, m0)) == ux); 529 | CHECK(YamaApprox(transform_normal(uy, m0)) == uz); 530 | CHECK(YamaApprox(transform_normal(uz, m0)) == -uy); 531 | 532 | q0 = quaternion::rotation_y(constants::PI_HALF); 533 | m1 = matrix::rotation_quaternion(q0); 534 | m0 = matrix::rotation_y(constants::PI_HALF); 535 | CHECK(YamaApprox(m1) == m0); 536 | CHECK(YamaApprox(transform_coord(ux, m0)) == -uz); 537 | CHECK(YamaApprox(transform_coord(uy, m0)) == uy); 538 | CHECK(YamaApprox(transform_coord(uz, m0)) == ux); 539 | CHECK(YamaApprox(transform_normal(ux, m0)) == -uz); 540 | CHECK(YamaApprox(transform_normal(uy, m0)) == uy); 541 | CHECK(YamaApprox(transform_normal(uz, m0)) == ux); 542 | 543 | q0 = quaternion::rotation_z(constants::PI_HALF); 544 | m1 = matrix::rotation_quaternion(q0); 545 | m0 = matrix::rotation_z(constants::PI_HALF); 546 | CHECK(YamaApprox(m1) == m0); 547 | CHECK(YamaApprox(transform_coord(ux, m0)) == uy); 548 | CHECK(YamaApprox(transform_coord(uy, m0)) == -ux); 549 | CHECK(YamaApprox(transform_coord(uz, m0)) == uz); 550 | CHECK(YamaApprox(transform_normal(ux, m0)) == uy); 551 | CHECK(YamaApprox(transform_normal(uy, m0)) == -ux); 552 | CHECK(YamaApprox(transform_normal(uz, m0)) == uz); 553 | 554 | auto axis = v(1, 2, 3); 555 | auto angle = 2.66f; 556 | 557 | m0 = matrix::rotation_axis(axis, angle); 558 | 559 | q0 = quaternion::rotation_axis(axis, angle); 560 | m1 = matrix::rotation_quaternion(q0); 561 | CHECK(YamaApprox(m1) == m0); 562 | 563 | auto v0 = normalize(v(1, 2, 3)); 564 | 565 | CHECK(matrix::rotation_vectors(v0, v0) == matrix::identity()); 566 | 567 | auto v1 = normalize(v(-3, 5, 11)); 568 | 569 | m0 = matrix::rotation_vectors(v0, v1); 570 | CHECK(YamaApprox(transform_coord(v0, m0)) == v1); 571 | CHECK(YamaApprox(transform_normal(v0, m0)) == v1); 572 | 573 | v1 = -v0; 574 | m0 = matrix::rotation_vectors(v0, v1); 575 | CHECK(YamaApprox(transform_coord(v(1, 2, 3), m0)) == v(-1, -2, -3)); 576 | CHECK(YamaApprox(transform_normal(v(1, 2, 3), m0)) == v(-1, -2, -3)); 577 | } 578 | 579 | TEST_CASE("camera") 580 | { 581 | auto p = matrix::ortho_lh(2, 2, 0, 1); 582 | CHECK(p == matrix::identity()); 583 | p = matrix::ortho_rh(2, 2, 0, 1); 584 | CHECK(abs(p) == matrix::identity()); 585 | 586 | p = matrix::ortho_lh_cube(2, 2, -1, 1); 587 | CHECK(p == matrix::identity()); 588 | p = matrix::ortho_rh_cube(2, 2, -1, 1); 589 | CHECK(abs(p) == matrix::identity()); 590 | CHECK(p == matrix::ortho_rh_cube(-1, 1, -1, 1, -1, 1)); 591 | 592 | auto vec0 = v(0.5f, 0.5f, 0.5f); 593 | p = matrix::ortho_lh(1, 1, 0.5f, 3); 594 | CHECK(transform_coord(vec0, p) == v(1, 1, 0)); 595 | 596 | p = matrix::ortho_lh_cube(1, 1, 0, 1); 597 | CHECK(transform_coord(vec0, p) == v(1, 1, 0)); 598 | 599 | p = matrix::ortho_rh(1, 1, 0, 1); 600 | CHECK(transform_coord(vec0, p) == v(1, 1, -0.5f)); 601 | 602 | p = matrix::ortho_rh_cube(1, 1, 0, 1); 603 | CHECK(transform_coord(vec0, p) == v(1, 1, -2)); 604 | CHECK(p == matrix::ortho_rh_cube(-0.5f, 0.5f, -0.5f, 0.5f, 0, 1)); 605 | 606 | p = matrix::ortho_lh(-1, 1, -1, 1, 0, 1); 607 | CHECK(p == matrix::identity()); 608 | 609 | p = matrix::ortho_lh_cube(-1, 1, -1, 1, -1, 1); 610 | CHECK(p == matrix::identity()); 611 | 612 | auto v0 = matrix::look_towards_lh(vector3::zero(), 2.f * vector3::unit_z(), 0.2f * vector3::unit_y()); 613 | CHECK(YamaApprox(v0) == matrix::identity()); 614 | 615 | v0 = matrix::look_at_lh(vector3::zero(), 2.f * vector3::unit_z(), 0.2f * vector3::unit_y()); 616 | CHECK(YamaApprox(v0) == matrix::identity()); 617 | 618 | p = matrix::ortho_rh(4, 4, 0, 1); 619 | v0 = matrix::look_towards_rh(v(1, 1, 1), v(1, 0, 0), v(0, -1, 0)); 620 | vec0 = v(1.5f, 2, 3); 621 | 622 | auto pv = p * v0; 623 | 624 | CHECK(transform_coord(vec0, pv) == v(-1, -0.5f, 0.5f)); 625 | 626 | p = matrix::ortho_rh_cube(4, 4, 0, 1); 627 | v0 = matrix::look_at_rh(v(1, 1, 1), v(5, 1, 1), v(0, 1, 0)); 628 | pv = p * v0; 629 | CHECK(transform_coord(vec0, pv) == v(1, 0.5f, 0)); 630 | 631 | p = matrix::perspective_lh(8, 6, 3, 10); 632 | CHECK(p == matrix::perspective_lh(-4, 4, -3, 3, 3, 10)); 633 | CHECK(YamaApprox(p) == matrix::perspective_fov_lh(constants::PI_HALF, 4.f / 3.f, 3, 10)); 634 | 635 | vec0 = v(8, 3, 6); 636 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(1, 0.5f, 0.71428571428f)); 637 | vec0 = v(0, 0, 3); 638 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, 0)); 639 | vec0 = v(0, 0, 10); 640 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, 1)); 641 | 642 | p = matrix::perspective_lh_cube(8, 6, 3, 10); 643 | CHECK(p == matrix::perspective_lh_cube(-4, 4, -3, 3, 3, 10)); 644 | CHECK(YamaApprox(p) == matrix::perspective_fov_lh_cube(constants::PI_HALF, 4.f / 3.f, 3, 10)); 645 | 646 | vec0 = v(8, 3, 6); 647 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(1, 0.5f, 0.4285714285f)); 648 | vec0 = v(0, 0, 3); 649 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, -1)); 650 | vec0 = v(0, 0, 10); 651 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, 1)); 652 | 653 | 654 | p = matrix::perspective_rh(8, 6, 3, 10); 655 | CHECK(p == matrix::perspective_rh(-4, 4, -3, 3, 3, 10)); 656 | CHECK(YamaApprox(p) == matrix::perspective_fov_rh(constants::PI_HALF, 4.f / 3.f, 3, 10)); 657 | p *= matrix::look_towards_rh(v(0, 0, 0), v(0, 0, -1), v(0, 1, 0)); 658 | 659 | vec0 = v(8, 3, -6); 660 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(1, 0.5f, 0.71428571428f)); 661 | vec0 = v(0, 0, -3); 662 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, 0)); 663 | vec0 = v(0, 0, -10); 664 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, 1)); 665 | 666 | p = matrix::perspective_rh_cube(8, 6, 3, 10); 667 | CHECK(p == matrix::perspective_rh_cube(-4, 4, -3, 3, 3, 10)); 668 | CHECK(YamaApprox(p) == matrix::perspective_fov_rh_cube(constants::PI_HALF, 4.f / 3.f, 3, 10)); 669 | p *= matrix::look_towards_rh(v(0, 0, 0), v(0, 0, -1), v(0, 1, 0)); 670 | 671 | vec0 = v(8, 3, -6); 672 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(1, 0.5f, 0.4285714285f)); 673 | vec0 = v(0, 0, -3); 674 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, -1)); 675 | vec0 = v(0, 0, -10); 676 | CHECK(YamaApprox(transform_coord(vec0, p)) == v(0, 0, 1)); 677 | } 678 | -------------------------------------------------------------------------------- /test/unit/ostream.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "common.hpp" 5 | #include "yama/ext/ostream.hpp" 6 | #include 7 | 8 | using namespace yama; 9 | 10 | TEST_SUITE_BEGIN("ext_ostream"); 11 | 12 | static void clr(std::ostringstream& o) 13 | { 14 | o.str(std::string()); 15 | o.clear(); 16 | } 17 | 18 | TEST_CASE("test") 19 | { 20 | std::ostringstream sout; 21 | 22 | auto v2 = v(11, 12); 23 | sout << v2; 24 | CHECK(sout.str() == "(11, 12)"); 25 | 26 | clr(sout); 27 | 28 | auto v3 = v(25, 88, 11); 29 | sout << v3; 30 | CHECK(sout.str() == "(25, 88, 11)"); 31 | 32 | clr(sout); 33 | 34 | auto v4 = v(-2, 3, 5, 11); 35 | sout << v4; 36 | CHECK(sout.str() == "(-2, 3, 5, 11)"); 37 | 38 | clr(sout); 39 | 40 | auto m4 = matrix::columns( 41 | 1, 5, 9, 13, 42 | 2, 6, 10, 14, 43 | 3, 7, 11, 15, 44 | 4, 8, 12, 16 45 | ); 46 | sout << m4; 47 | CHECK(sout.str() == "((1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16))"); 48 | } 49 | -------------------------------------------------------------------------------- /test/unit/prerequisites.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/yama.hpp" 5 | #include "doctest/doctest.h" 6 | 7 | using namespace yama; 8 | 9 | static_assert(constants_t::PI == 3, "yama constants must be constexpr"); 10 | static_assert(constants_t::SQRT_2 == 1.4142135623730950488016887242097, "yama constants must be constexpr"); 11 | 12 | static_assert(!is_yama::value, "int is not a yama type"); 13 | static_assert(!is_vector::value, "float is not a vector type"); 14 | static_assert(!is_matrix::value, "double is not a matrix type"); 15 | 16 | // funcs 17 | static_assert(sq(11) == 121, "yama::sq must be constexpr"); 18 | 19 | // vec2 20 | static_assert(sizeof(vector2_t) == 2 * sizeof(float), "yama types must be identical to ntuples of T in size"); 21 | static_assert(sizeof(vector2_t) == 2 * sizeof(double), "yama types must be identical to ntuples of T in size"); 22 | static_assert(is_yama>::value, "yama::vector2_t must be a yama type"); 23 | static_assert(is_vector>::value, "yama::vector2_t must be a vector type"); 24 | static_assert(is_vector::value, "yama::vector2 must be a vector type"); 25 | static_assert(!is_matrix::value, "yama::vector2 is not a matrix type"); 26 | static_assert(vector2_t::value_count == 2, "yama::vector2 has two elements"); 27 | static_assert(vector2_t::coord(1, 2).x == 1, "yama constructors must be constexpr"); 28 | static_assert(vector2_t::uniform(2).y == 2, "yama constructors must be constexpr"); 29 | static_assert(vector2_t::zero().y == 0, "yama constructors must be constexpr"); 30 | static_assert(v(1, 2).y == 2.f, "yama constructors must be constexpr"); 31 | static_assert(vt(1, 2).y == 2, "yama constructors must be constexpr"); 32 | static_assert(std::is_same::value, "vector2 must be the same as point2"); 33 | static_assert(std::is_same, dim<2>::vector_t>::value, "vector2_t must be the same as dim<2>::vector2_t"); 34 | static_assert(std::is_same::vector>::value, "vector2 must be the same as dim<2>::vector2"); 35 | 36 | // vec3 37 | static_assert(sizeof(vector3_t) == 3 * sizeof(float), "yama types must be identical to ntuples of T in size"); 38 | static_assert(sizeof(vector3_t) == 3 * sizeof(double), "yama types must be identical to ntuples of T in size"); 39 | static_assert(is_yama>::value, "yama::vector3_t must be a yama type"); 40 | static_assert(is_vector>::value, "yama::vector3_t must be a vector type"); 41 | static_assert(is_vector::value, "yama::vector3 must be a vector type"); 42 | static_assert(!is_matrix::value, "yama::vector3 is not a matrix type"); 43 | static_assert(vector3_t::value_count == 3, "yama::vector3 has three elements"); 44 | static_assert(vector3_t::coord(1, 2, 3).y == 2, "yama constructors must be constexpr"); 45 | static_assert(vector3_t::uniform(2).z == 2, "yama constructors must be constexpr"); 46 | static_assert(vector3_t::zero().z == 0, "yama constructors must be constexpr"); 47 | static_assert(v(1, 2, 3).z == 3.f, "yama constructors must be constexpr"); 48 | static_assert(vt(1, 2, 3).z == 3, "yama constructors must be constexpr"); 49 | static_assert(std::is_same, dim<3>::vector_t>::value, "vector3_t must be the same as dim<3>::vector3_t"); 50 | static_assert(std::is_same::value, "vector2 must be the same as point2"); 51 | static_assert(std::is_same::vector>::value, "vector3 must be the same as dim<3>::vector"); 52 | 53 | // vec4 54 | static_assert(sizeof(vector4_t) == 4 * sizeof(float), "yama types must be identical to ntuples of T in size"); 55 | static_assert(sizeof(vector4_t) == 4 * sizeof(double), "yama types must be identical to ntuples of T in size"); 56 | static_assert(is_yama>::value, "yama::vector4_t must be a yama type"); 57 | static_assert(is_vector>::value, "yama::vector4_t must be a vector type"); 58 | static_assert(is_vector::value, "yama::vector4 must be a vector type"); 59 | static_assert(!is_matrix::value, "yama::vector4 is not a matrix type"); 60 | static_assert(vector4_t::value_count == 4, "yama::vector4 has four elements"); 61 | static_assert(vector4_t::coord(1, 2, 3, 5).w == 5, "yama constructors must be constexpr"); 62 | static_assert(vector4_t::uniform(2).w == 2, "yama constructors must be constexpr"); 63 | static_assert(vector4_t::zero().z == 0, "yama constructors must be constexpr"); 64 | static_assert(v(1, 2, 3, 4).w == 4.f, "yama constructors must be constexpr"); 65 | static_assert(vt(1, 2, 3, 4).w == 4, "yama constructors must be constexpr"); 66 | static_assert(std::is_same, dim<4>::vector_t>::value, "vector4_t must be the same as dim<3>::vector4_t"); 67 | static_assert(std::is_same::value, "vector2 must be the same as point2"); 68 | static_assert(std::is_same::vector>::value, "vector4 must be the same as dim<3>::vector"); 69 | 70 | // quat 71 | static_assert(sizeof(quaternion_t) == 4 * sizeof(float), "yama types must be identical to ntuples of T in size"); 72 | static_assert(sizeof(quaternion_t) == 4 * sizeof(double), "yama types must be identical to ntuples of T in size"); 73 | static_assert(is_yama>::value, "yama::quaternion_t must be a yama type"); 74 | static_assert(!is_vector>::value, "yama::quaternion_t is not a vector type"); 75 | static_assert(!is_matrix::value, "yama::quaternion is not a matrix type"); 76 | static_assert(quaternion_t::value_count == 4, "yama::quaternion has four elements"); 77 | static_assert(quaternion_t::xyzw(1, 2, 3, 5).w == 5, "yama constructors must be constexpr"); 78 | static_assert(quaternion_t::uniform(2).w == 2, "yama constructors must be constexpr"); 79 | static_assert(quaternion_t::zero().z == 0, "yama constructors must be constexpr"); 80 | static_assert(v(1, 2, 3, 4).w == 4.f, "yama constructors must be constexpr"); 81 | static_assert(vt(1, 2, 3, 4).w == 4, "yama constructors must be constexpr"); 82 | 83 | // mat4x4 84 | static_assert(sizeof(matrix4x4_t) == 16 * sizeof(float), "yama types must be identical to ntuples of T in size"); 85 | static_assert(sizeof(matrix4x4_t) == 16 * sizeof(double), "yama types must be identical to ntuples of T in size"); 86 | static_assert(is_yama>::value, "yama::quaternion_t must be a yama type"); 87 | static_assert(!is_vector>::value, "yama::matrix4x4_t is not a vector type"); 88 | static_assert(is_matrix::value, "yama::matrix4x4_t must be a matrix type"); 89 | static_assert(matrix4x4_t::value_count == 16, "yama::matrix4x4_t has sixteen elements"); 90 | static_assert(matrix4x4_t::columns(1, 2, 3, 5, 10, 8, 7, 6, 22, 23, 24, 25, -1, -2, -3, -4).m30 == 5, "yama constructors must be constexpr"); 91 | static_assert(matrix4x4_t::uniform(2).m22 == 2, "yama constructors must be constexpr"); 92 | static_assert(matrix4x4_t::zero().m13 == 0, "yama constructors must be constexpr"); 93 | 94 | // mat4x4 95 | static_assert(sizeof(matrix3x4_t) == 12 * sizeof(float), "yama types must be identical to ntuples of T in size"); 96 | static_assert(sizeof(matrix3x4_t) == 12 * sizeof(double), "yama types must be identical to ntuples of T in size"); 97 | static_assert(is_yama>::value, "yama::quaternion_t must be a yama type"); 98 | static_assert(!is_vector>::value, "yama::matrix3x4_t is not a vector type"); 99 | static_assert(is_matrix::value, "yama::matrix3x4_t must be a matrix type"); 100 | static_assert(matrix3x4_t::value_count == 12, "yama::matrix3x4_t has sixteen elements"); 101 | static_assert(matrix3x4_t::columns(1, 2, 3, 5, 10, 8, 7, 6, 22, 23, 24, 25).m01 == 5, "yama constructors must be constexpr"); 102 | static_assert(matrix3x4_t::uniform(2).m22 == 2, "yama constructors must be constexpr"); 103 | static_assert(matrix3x4_t::zero().m13 == 0, "yama constructors must be constexpr"); 104 | 105 | // unions must compile 106 | union foo 107 | { 108 | matrix m4x4; 109 | matrix3x4 m3x4; 110 | vector2 v2; 111 | vector3 v3; 112 | vector4 v4; 113 | quaternion q; 114 | }; 115 | 116 | TEST_SUITE_BEGIN("prerequisites"); 117 | 118 | class nrvo_test 119 | { 120 | public: 121 | static size_t copies; 122 | 123 | nrvo_test(int d) 124 | : dummy(d) 125 | {} 126 | 127 | nrvo_test(const nrvo_test& b) 128 | : dummy(b.dummy) 129 | { 130 | ++copies; 131 | } 132 | 133 | int dummy; 134 | 135 | static nrvo_test seven() 136 | { 137 | return nrvo_test(7); 138 | } 139 | }; 140 | 141 | size_t nrvo_test::copies = 0; 142 | 143 | TEST_CASE("nrvo") 144 | { 145 | nrvo_test t = nrvo_test::seven(); 146 | 147 | CHECK(t.dummy == 7); 148 | 149 | // IF THIS FAILS: This build configuration (or your compiler) doesn't support named return 150 | // value optimizations. MathGP will suffer big performance penalties because of that 151 | WARN(nrvo_test::copies == 0); 152 | } 153 | -------------------------------------------------------------------------------- /test/unit/quaternion.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/quaternion.hpp" 5 | #include "common.hpp" 6 | #include "yama/ext/quaternion_ostream.hpp" 7 | #include "yama/ext/vector3_ostream.hpp" 8 | 9 | using namespace yama; 10 | using doctest::Approx; 11 | 12 | quaternion q(float x, float y, float z, float w) 13 | { 14 | return quaternion::xyzw(x, y, z, w); 15 | } 16 | 17 | TEST_SUITE_BEGIN("quaternion"); 18 | 19 | TEST_CASE("construction") 20 | { 21 | double d0[] = { 0, 0, 0, 0 }; 22 | auto q0 = quaternion_t::zero(); 23 | CHECK(q0.x == 0); 24 | CHECK(q0.y == 0); 25 | CHECK(q0.z == 0); 26 | CHECK(q0.w == 0); 27 | CHECK(memcmp(d0, &q0, 4 * sizeof(double)) == 0); 28 | 29 | float f1[] = { 1,2,3,4 }; 30 | auto q1 = quaternion::xyzw(1, 2, 3, 4); 31 | CHECK(q1.x == 1); 32 | CHECK(q1.y == 2); 33 | CHECK(q1.z == 3); 34 | CHECK(q1.w == 4); 35 | CHECK(memcmp(f1, &q1, 4 * sizeof(float)) == 0); 36 | 37 | auto q2 = quaternion_t::uniform(3); 38 | CHECK(q2.x == 3); 39 | CHECK(q2.x == q2.y); 40 | CHECK(q2.x == q2.z); 41 | CHECK(q2.x == q2.w); 42 | 43 | auto q3 = q(3, 1, 5, 2); 44 | CHECK(q3.x == 3); 45 | CHECK(q3.y == 1); 46 | CHECK(q3.z == 5); 47 | CHECK(q3.w == 2); 48 | 49 | const char* c4 = "1234"; 50 | auto q4 = vt('1', '2', '3', '4'); 51 | CHECK(q4.x == '1'); 52 | CHECK(q4.y == '2'); 53 | CHECK(q4.z == '3'); 54 | CHECK(q4.w == '4'); 55 | CHECK(sizeof(q4) == 4); 56 | CHECK(memcmp(c4, &q4, sizeof(q4)) == 0); 57 | 58 | const float f[] = { 10,9,8,7,6,5,4,3 }; 59 | auto q5 = quaternion::from_ptr(f); 60 | CHECK(memcmp(&q5, f, 4 * sizeof(float)) == 0); 61 | CHECK(q5.x == 10); 62 | CHECK(q5.y == 9); 63 | CHECK(q5.z == 8); 64 | CHECK(q5.w == 7); 65 | 66 | // attach 67 | auto& q6 = quaternion::attach_to_ptr(f); 68 | CHECK(q6.x == 10); 69 | CHECK(q6.y == 9); 70 | CHECK(q6.z == 8); 71 | CHECK(q6.w == 7); 72 | CHECK(reinterpret_cast(&q6) == f); 73 | 74 | auto q7 = quaternion::attach_to_array(f); 75 | CHECK(q7[0].x == 10); 76 | CHECK(q7[0].y == 9); 77 | CHECK(q7[0].z == 8); 78 | CHECK(q7[0].w == 7); 79 | CHECK(q7[1].x == 6); 80 | CHECK(q7[1].y == 5); 81 | CHECK(q7[1].z == 4); 82 | CHECK(q7[1].w == 3); 83 | CHECK(q7 == &q6); 84 | CHECK(q7[0] == q6); 85 | CHECK(reinterpret_cast(q7) == f); 86 | 87 | float ff[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; 88 | auto& q8 = quaternion::attach_to_ptr(ff); 89 | q8.x = 10; 90 | q8.w = 20; 91 | CHECK(ff[0] == 10); 92 | CHECK(ff[3] == 20); 93 | 94 | auto q9 = quaternion::attach_to_array(ff); 95 | q9[0].y = 21; 96 | q9[1].z = 30; 97 | CHECK(ff[1] == 21); 98 | CHECK(ff[6] == 30); 99 | } 100 | 101 | TEST_CASE("compare") 102 | { 103 | auto q0 = quaternion::zero(); 104 | CHECK(q0 == q(0, 0, 0, 0)); 105 | CHECK(q0 != q(1, 0, 0, 0)); 106 | CHECK(q0 != q(0, 1, 0, 0)); 107 | CHECK(q0 != q(0, 0, 1, 0)); 108 | CHECK(q0 != q(0, 0, 0, 1)); 109 | 110 | quaternion q1; 111 | q1.x = 10; 112 | q1.y = 20; 113 | q1.z = 30; 114 | q1.w = 40; 115 | CHECK(q1 == q(10, 20, 30, 40)); 116 | CHECK(q1 != q(1, 20, 30, 40)); 117 | CHECK(q1 != q(10, 1, 30, 40)); 118 | CHECK(q1 != q(10, 20, 1, 40)); 119 | CHECK(q1 != q(10, 20, 30, 1)); 120 | 121 | CHECK(q0 != q1); 122 | 123 | q0.x = 10; 124 | q0.y = 20; 125 | q0.z = 30; 126 | q0.w = 40; 127 | 128 | CHECK(q0 == q1); 129 | 130 | CHECK(close(q0, q1)); 131 | q1.x = 11; 132 | CHECK(!close(q0, q1)); 133 | q1.y = 21; 134 | CHECK(!close(q0, q1)); 135 | q1.z = 31; 136 | CHECK(!close(q0, q1)); 137 | q1.w = 41; 138 | CHECK(close(q0, q1, 2.f)); 139 | q1 = q(10.000001f, 20.000001f, 30.000001f, 40.000001f); 140 | CHECK(close(q0, q1)); 141 | } 142 | 143 | TEST_CASE("special_construction") 144 | { 145 | auto q0 = quaternion::identity(); 146 | CHECK(q0 == q(0, 0, 0, 1)); 147 | 148 | 149 | } 150 | 151 | TEST_CASE("access") 152 | { 153 | auto q0 = q(1, 2, 3, 4); 154 | CHECK(q0.data() == reinterpret_cast(&q0)); 155 | CHECK(q0.data()[0] == 1); 156 | CHECK(q0.data()[1] == 2); 157 | CHECK(q0.data()[2] == 3); 158 | CHECK(q0.data()[3] == 4); 159 | CHECK(q0[0] == 1); 160 | CHECK(q0[1] == 2); 161 | CHECK(q0[2] == 3); 162 | CHECK(q0[3] == 4); 163 | CHECK(q0.at(0) == 1); 164 | CHECK(q0.at(1) == 2); 165 | CHECK(q0.at(2) == 3); 166 | CHECK(q0.at(3) == 4); 167 | CHECK(q0.data() == q0.as_ptr()); 168 | 169 | q0[0] = 10; 170 | q0.at(1) = 20; 171 | q0.z = 30; 172 | q0.data()[3] = 40; 173 | 174 | const auto q1 = q0; 175 | CHECK(q1.data() == reinterpret_cast(&q1)); 176 | CHECK(q1.data()[0] == 10); 177 | CHECK(q1.data()[1] == 20); 178 | CHECK(q1.data()[2] == 30); 179 | CHECK(q1.data()[3] == 40); 180 | CHECK(q1[0] == 10); 181 | CHECK(q1[1] == 20); 182 | CHECK(q1[2] == 30); 183 | CHECK(q1[3] == 40); 184 | CHECK(q1.at(0) == 10); 185 | CHECK(q1.at(1) == 20); 186 | CHECK(q1.at(2) == 30); 187 | CHECK(q1.at(3) == 40); 188 | CHECK(q1.data() == q1.as_ptr()); 189 | 190 | q0.x = 0.3f; 191 | q0.y = 2.91f; 192 | q0.z = -11.123f; 193 | q0.w = -1.99f; 194 | 195 | CHECK(q0.as_quaternion_t() == quaternion_t::xyzw(0, 2, -11, -1)); 196 | } 197 | 198 | TEST_CASE("std") 199 | { 200 | auto q0 = q(11, 12, 13, 14); 201 | CHECK(q0.front() == 11); 202 | CHECK(q0.back() == 14); 203 | CHECK(q0.begin() == q0.data()); 204 | CHECK(q0.begin() + 4 == q0.end()); 205 | CHECK(q0.rbegin() + 4 == q0.rend()); 206 | auto i = q0.rbegin(); 207 | CHECK(*i == 14); 208 | ++i; 209 | CHECK(*i == 13); 210 | 211 | for (auto& e : q0) 212 | { 213 | e += 10; 214 | } 215 | 216 | const auto q1 = q0; 217 | 218 | CHECK(q1.front() == 21); 219 | CHECK(q1.back() == 24); 220 | CHECK(q1.begin() == q1.data()); 221 | CHECK(q1.begin() + 4 == q1.end()); 222 | CHECK(q1.rbegin() + 4 == q1.rend()); 223 | auto ci = q1.rbegin(); 224 | CHECK(*ci == 24); 225 | ++ci; 226 | CHECK(*ci == 23); 227 | } 228 | 229 | 230 | TEST_CASE("members") 231 | { 232 | auto q0 = q(1, 2, 3, 4); 233 | auto q1 = +q0; 234 | CHECK(q0 == q1); 235 | q1 = -q0; 236 | CHECK(q1.x == -q0.x); 237 | CHECK(q1.y == -q0.y); 238 | CHECK(q1.z == -q0.z); 239 | CHECK(q1.w == -q0.w); 240 | 241 | q0 += q1; 242 | CHECK(q0 == quaternion::zero()); 243 | 244 | q0 -= q1; 245 | CHECK(q0 == -q1); 246 | 247 | q0 *= 2; 248 | CHECK(q0 == q(2, 4, 6, 8)); 249 | 250 | q0 /= 8; 251 | CHECK(YamaApprox(q0) == q(0.25f, 0.5f, 0.75f, 1)); 252 | 253 | q1 *= -2; 254 | CHECK(q1 == q(2, 4, 6, 8)); 255 | q0.mul(q1); 256 | CHECK(YamaApprox(q0) == q(0.5f, 2, 4.5f, 8)); 257 | 258 | q1.div(q0); 259 | CHECK(YamaApprox(q1) == q(4, 2, 1.3333333f, 1)); 260 | 261 | CHECK(Approx(q0.length_sq()) == 88.5f); 262 | q1 = q(1, 2, 8, 10); 263 | CHECK(Approx(q1.length()) == 13); 264 | 265 | q1.normalize(); 266 | CHECK(Approx(q1.length()) == 1); 267 | CHECK(Approx(q1.w) == 0.76923076923); 268 | CHECK(q1.is_normalized()); 269 | 270 | q0.conjugate(); 271 | CHECK(YamaApprox(q0) == q(-0.5f, -2, -4.5f, 8)); 272 | 273 | q0.conjugate(); 274 | q1 = q0; 275 | q0.inverse(); 276 | CHECK(YamaApprox(q0 * q1) == quaternion::identity()); 277 | q0.inverse(); 278 | CHECK(YamaApprox(q0) == q1); 279 | } 280 | 281 | TEST_CASE("ops") 282 | { 283 | auto q0 = q(1, 2, 3, 4); 284 | auto q1 = q(3, 4, 5, 6); 285 | 286 | auto q2 = q0 + q1; 287 | CHECK(q2 == q(4, 6, 8, 10)); 288 | 289 | q1 = q2 - q0; 290 | CHECK(q1 == q(3, 4, 5, 6)); 291 | 292 | q1 = 2.f * q0; 293 | CHECK(q1 == q(2, 4, 6, 8)); 294 | 295 | q0 = q1 * 0.5f; 296 | CHECK(q0 == q(1, 2, 3, 4)); 297 | 298 | q0 = 4.f / q1; 299 | CHECK(YamaApprox(q0) == q(2, 1, 0.6666666f, 0.5f)); 300 | 301 | q1 = q0 / 2.f; 302 | CHECK(YamaApprox(q1) == q(1, 0.5f, 0.3333333f, 0.25f)); 303 | 304 | q0 = abs(q1); 305 | CHECK(YamaApprox(q1) == q(1, 0.5f, 0.3333333f, 0.25f)); 306 | 307 | q0 = abs(-q1); 308 | CHECK(YamaApprox(q1) == q(1, 0.5f, 0.3333333f, 0.25f)); 309 | 310 | q0 = q(0.25f, 0.5f, 1.5f, 1.75f); 311 | q1 = q(8, 10, 12, 16); 312 | q2 = mul(q0, q1); 313 | CHECK(q2 == q(2, 5, 18, 28)); 314 | 315 | q2 = div(q1, q(4, 2, 12, 8)); 316 | CHECK(q2 == q(2, 5, 1, 2)); 317 | 318 | auto iq0 = mod(vt(5, 6, 11, 8), vt(2, 4, 7, 4)); 319 | CHECK(iq0 == vt(1, 2, 4, 0)); 320 | 321 | CHECK(YamaApprox(mod(q(5.3f, 18.5f, 11, 8), q(2, 4.2f, 7, 4))) == q(1.3f, 1.7f, 4, 0)); 322 | 323 | q1 = sign(q2); 324 | CHECK(q1 == q(1, 1, 1, 1)); 325 | q1 = sign(-q2); 326 | CHECK(q1 == q(-1, -1, -1, -1)); 327 | q1 = sign(q(-1, 2, -4, 0)); 328 | CHECK(q1 == q(-1, 1, -1, 1)); 329 | 330 | q1 = q(-1, 10, 6, 2); 331 | q0 = max(q1, q2); 332 | CHECK(q0 == q(2, 10, 6, 2)); 333 | q0 = min(q1, q2); 334 | CHECK(q0 == q(-1, 5, 1, 2)); 335 | 336 | CHECK(dot(q(0, 1, 0, 0), q(0, 0, 0, 1)) == 0); 337 | CHECK(dot(q1, q1) == q1.length_sq()); 338 | CHECK(dot(q1, q2) == 58); 339 | 340 | const auto q3 = q(3, 4, 12, 0); 341 | q0 = normalize(q3); 342 | CHECK(YamaApprox(q0) == q(0.23076923076f, 0.30769230769f, 0.92307692307f, 0)); 343 | 344 | q0 = q(-1.723f, 5.23f, 2.522f, -2.222f); 345 | CHECK(floor(q0) == q(-2, 5, 2, -3)); 346 | CHECK(ceil(q0) == q(-1, 6, 3, -2)); 347 | CHECK(round(q0) == q(-2, 5, 3, -2)); 348 | CHECK(YamaApprox(frac(q0)) == q(0.723f, 0.23f, 0.522f, 0.222f)); 349 | 350 | CHECK(isfinite(q0)); 351 | CHECK(isfinite(q1)); 352 | CHECK(isfinite(q2)); 353 | 354 | q0.x = std::numeric_limits::quiet_NaN(); 355 | q1.y = std::numeric_limits::infinity(); 356 | q2 = q0 + q1; 357 | CHECK(!isfinite(q0)); 358 | CHECK(!isfinite(q1)); 359 | CHECK(!isfinite(q2)); 360 | 361 | q1 = q(1, 2, 3, 4); 362 | q2 = q(5, 10, 15, 18); 363 | CHECK(YamaApprox(lerp(q1, q2, 0.3f)) == q(0.189346f, 0.378692f, 0.568038f, 0.705744f)); 364 | 365 | CHECK(q1 * q2 == q(38, 76, 114, 2)); 366 | q1 *= q2; 367 | CHECK(q1 == q(38, 76, 114, 2)); 368 | CHECK(q1 / q2 == q(1, 2, 3, 4)); 369 | q1 /= q2; 370 | CHECK(q1 == q(1, 2, 3, 4)); 371 | 372 | q0 = conjugate(q2); 373 | CHECK(YamaApprox(q0) == q(-5, -10, -15, 18)); 374 | 375 | q0 = inverse(q2); 376 | CHECK(q0 == quaternion::identity() / q2); 377 | CHECK(YamaApprox(q0 * q2) == quaternion::identity()); 378 | 379 | CHECK(YamaApprox(inverse(q0)) == q2); 380 | 381 | q1 = quaternion::xyzw(0.5f, 0.7f, 0.212f, 0.8f); 382 | q2 = quaternion::xyzw(0.12f, 0.33f, 0.4f, 0.5f); 383 | 384 | q0 = slerp(q1, q2, 0.55f); 385 | 386 | CHECK(YamaApprox(q0) == 387 | quaternion::xyzw(0.30942556881258626f, 0.527364923066248f, 0.33413672218578583f, 0.6741185090656765f)); 388 | } 389 | 390 | TEST_CASE("rotate") 391 | { 392 | const auto ux = vector3::unit_x(); 393 | const auto uy = vector3::unit_y(); 394 | const auto uz = vector3::unit_z(); 395 | 396 | CHECK(YamaApprox(quaternion::rotation_axis(ux, 2.11f)) == quaternion::rotation_x(2.11f)); 397 | CHECK(YamaApprox(quaternion::rotation_axis(uy, 0.13f)) == quaternion::rotation_y(0.13f)); 398 | CHECK(YamaApprox(quaternion::rotation_axis(uz, 1.22f)) == quaternion::rotation_z(1.22f)); 399 | 400 | auto q0 = quaternion::rotation_x(constants::PI_HALF); 401 | CHECK(YamaApprox(rotate(ux, q0)) == ux); 402 | CHECK(YamaApprox(rotate(uy, q0)) == uz); 403 | CHECK(YamaApprox(rotate(uz, q0)) == -uy); 404 | 405 | vector3 axis; 406 | float angle; 407 | q0.to_axis_angle(axis, angle); 408 | CHECK(YamaApprox(axis) == ux); 409 | CHECK(Approx(angle) == constants::PI_HALF); 410 | 411 | q0 = quaternion::rotation_y(constants::PI_HALF); 412 | CHECK(YamaApprox(rotate(ux, q0)) == -uz); 413 | CHECK(YamaApprox(rotate(uy, q0)) == uy); 414 | CHECK(YamaApprox(rotate(uz, q0)) == ux); 415 | 416 | q0.to_axis_angle(axis, angle); 417 | CHECK(YamaApprox(axis) == uy); 418 | CHECK(Approx(angle) == constants::PI_HALF); 419 | 420 | q0 = quaternion::rotation_z(constants::PI_HALF); 421 | CHECK(YamaApprox(rotate(ux, q0)) == uy); 422 | CHECK(YamaApprox(rotate(uy, q0)) == -ux); 423 | CHECK(YamaApprox(rotate(uz, q0)) == uz); 424 | 425 | q0.to_axis_angle(axis, angle); 426 | CHECK(YamaApprox(axis) == uz); 427 | CHECK(Approx(angle) == constants::PI_HALF); 428 | 429 | axis = v(1, 2, 3); 430 | angle = 2.66f; 431 | q0 = quaternion::rotation_axis(axis, angle); 432 | 433 | q0.to_axis_angle(axis, angle); 434 | auto q1 = quaternion::rotation_axis(axis, angle); 435 | CHECK(YamaApprox(q1) == q0); 436 | 437 | axis = v(1.2f, 2.1f, -2.45f); 438 | auto v1 = axis; 439 | angle = 1.44f; 440 | q0 = quaternion::rotation_axis(axis, angle); 441 | CHECK(isfinite(q0)); 442 | 443 | q0.to_axis_angle(axis, angle); 444 | q1 = quaternion::rotation_axis(axis, angle); 445 | CHECK(YamaApprox(q1) == q0); 446 | 447 | q1 *= q1; 448 | q0 = quaternion::rotation_axis(v1, 2.88f); 449 | CHECK(isfinite(q0)); 450 | CHECK(YamaApprox(q1) == q0); 451 | 452 | auto v0 = normalize(v(1, 2, 3)); 453 | 454 | CHECK(quaternion::rotation_vectors(v0, v0) == quaternion::identity()); 455 | 456 | v1 = normalize(v(-3, 5, 11)); 457 | 458 | q0 = quaternion::rotation_vectors(v0, v1); 459 | CHECK(isfinite(q0)); 460 | CHECK(YamaApprox(rotate(v0, q0)) == v1); 461 | 462 | v1 = v0; 463 | v1.y += 0.01f; 464 | v1.normalize(); 465 | q0 = quaternion::rotation_vectors(v0, v1); 466 | CHECK(isfinite(q0)); 467 | CHECK(YamaApprox(rotate(v0, q0)) == v1); 468 | 469 | v1 = -v0; 470 | q0 = quaternion::rotation_vectors(v0, v1); 471 | CHECK(isfinite(q0)); 472 | CHECK(YamaApprox(rotate(v(1, 2, 3), q0)) == v(-1, -2, -3)); 473 | } 474 | -------------------------------------------------------------------------------- /test/unit/utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #define _USE_MATH_DEFINES 5 | #include "yama/yama.hpp" 6 | #include "doctest/doctest.h" 7 | 8 | using namespace yama; 9 | using doctest::Approx; 10 | 11 | TEST_SUITE_BEGIN("utils"); 12 | 13 | TEST_CASE("constants") 14 | { 15 | CHECK(Approx(constants_t::PI) == M_PI); 16 | CHECK(Approx(constants_t::PI) == constants_t::PI); 17 | CHECK(Approx(constants_t::PI_HALF) == M_PI / 2); 18 | CHECK(Approx(constants_t::PI_D4) == M_PI / 4); 19 | CHECK(Approx(constants_t::PI_DBL) == M_PI * 2); 20 | CHECK(Approx(constants_t::OVER_PI) == 1 / M_PI); 21 | CHECK(Approx(constants_t::E) == M_E); 22 | CHECK(Approx(constants_t::SQRT_2) == std::sqrt(2.0)); 23 | CHECK(constants_t::EPSILON > 0); 24 | CHECK(constants_t::EPSILON_HIGH > 0); 25 | CHECK(constants_t::EPSILON_LOW > 0); 26 | CHECK(constants_t::EPSILON_LOW < 1); 27 | CHECK(constants_t::EPSILON < constants_t::EPSILON_LOW); 28 | CHECK(constants_t::EPSILON_HIGH < constants_t::EPSILON); 29 | } 30 | 31 | TEST_CASE("functions") 32 | { 33 | CHECK(Approx(sq(5.1)) == 26.01); 34 | CHECK(sign(-123) == -1); 35 | CHECK(sign(52.11) == 1.0); 36 | CHECK(sign(-25.1f) == -1.f); 37 | CHECK(sign(unsigned(-1)) == 1); 38 | 39 | int i = -123; 40 | flip_sign(i); 41 | CHECK(i == 123); 42 | double d = 52.11; 43 | flip_sign(d); 44 | CHECK(d == -52.11); 45 | float f = -25.1f; 46 | flip_sign(f); 47 | CHECK(f == 25.1f); 48 | 49 | CHECK(Approx(lerp(4., 7., 0.3333333)) == 5.); 50 | CHECK(Approx(lerp(1.f, 5.f, 0.25f)) == 2.f); 51 | 52 | CHECK(Approx(rad_to_deg(constants_t::PI_HALF)) == 90); 53 | CHECK(Approx(rad_to_deg(constants_t::PI / 3)) == 60); 54 | 55 | CHECK(Approx(deg_to_rad(60.f)) == constants::PI / 3); 56 | CHECK(Approx(deg_to_rad(90.f)) == constants::PI_HALF); 57 | 58 | CHECK(close(1, 3, 3)); 59 | CHECK(close(1, 1)); 60 | CHECK(!close(1.0, 1.1)); 61 | CHECK(close(1.0, 1.000001)); 62 | 63 | CHECK(clamp(1.3, 1.0, 2.0) == 1.3); 64 | CHECK(clamp(1.3, 2.0, 1.0) == 1.3); 65 | CHECK(clamp(2.3, 1.0, 2.0) == 2.0); 66 | CHECK(clamp(0.3, 1.0, 2.0) == 1.0); 67 | } 68 | 69 | TEST_CASE("ordering") 70 | { 71 | strict_weak_ordering o; 72 | 73 | auto v21 = v(1, 2); 74 | auto v22 = v(2, 3); 75 | auto v23 = v(1, 3); 76 | auto v24 = v(1, 2); 77 | 78 | CHECK(o(v21, v22)); 79 | CHECK(!o(v22, v21)); 80 | CHECK(o(v21, v23)); 81 | CHECK(!o(v23, v21)); 82 | CHECK(o(v23, v22)); 83 | CHECK(!o(v22, v23)); 84 | 85 | CHECK(!o(v21, v24)); 86 | CHECK(!o(v24, v21)); 87 | 88 | auto v31 = v(1, 2, 3); 89 | auto v32 = v(2, 3, 4); 90 | auto v33 = v(1, 2, 4); 91 | auto v34 = v(1, 2, 3); 92 | 93 | CHECK(o(v31, v32)); 94 | CHECK(!o(v32, v31)); 95 | CHECK(o(v31, v33)); 96 | CHECK(!o(v33, v31)); 97 | CHECK(o(v33, v32)); 98 | CHECK(!o(v32, v33)); 99 | 100 | CHECK(!o(v31, v34)); 101 | CHECK(!o(v34, v31)); 102 | 103 | auto v41 = v(1, 2, 3, 4); 104 | auto v42 = v(2, 3, 4, 5); 105 | auto v43 = v(1, 2, 3, 5); 106 | auto v44 = v(1, 2, 3, 4); 107 | 108 | CHECK(o(v41, v42)); 109 | CHECK(!o(v42, v41)); 110 | CHECK(o(v41, v43)); 111 | CHECK(!o(v43, v41)); 112 | CHECK(o(v43, v42)); 113 | CHECK(!o(v42, v43)); 114 | 115 | CHECK(!o(v41, v44)); 116 | CHECK(!o(v44, v41)); 117 | 118 | auto q1 = quaternion::xyzw(1, 2, 3, 4); 119 | auto q2 = quaternion::xyzw(2, 3, 4, 5); 120 | auto q3 = quaternion::xyzw(1, 2, 3, 5); 121 | auto q4 = quaternion::xyzw(1, 2, 3, 4); 122 | 123 | CHECK(o(q1, q2)); 124 | CHECK(!o(q2, q1)); 125 | CHECK(o(q1, q3)); 126 | CHECK(!o(q3, q1)); 127 | CHECK(o(q3, q2)); 128 | CHECK(!o(q2, q3)); 129 | 130 | CHECK(!o(q1, q4)); 131 | CHECK(!o(q4, q1)); 132 | } 133 | -------------------------------------------------------------------------------- /test/unit/vector2.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/vector2.hpp" 5 | #include "common.hpp" 6 | #include "yama/ext/vector2_ostream.hpp" 7 | 8 | using namespace yama; 9 | using doctest::Approx; 10 | 11 | TEST_SUITE_BEGIN("vector2"); 12 | 13 | TEST_CASE("construction") 14 | { 15 | double d0[] = { 0, 0, 0, 0 }; 16 | auto v0 = vector2_t::zero(); 17 | CHECK(v0.x == 0); 18 | CHECK(v0.y == 0); 19 | CHECK(memcmp(d0, &v0, 2 * sizeof(double)) == 0); 20 | 21 | float f1[] = { 1,2,3,4 }; 22 | auto v1 = vector2::coord(1, 2); 23 | CHECK(v1.x == 1); 24 | CHECK(v1.y == 2); 25 | CHECK(memcmp(f1, &v1, 2 * sizeof(float)) == 0); 26 | 27 | auto v2 = vector2_t::uniform(3); 28 | CHECK(v2.x == 3); 29 | CHECK(v2.x == v2.y); 30 | 31 | auto v3 = v(3, 1); 32 | CHECK(v3.x == 3); 33 | CHECK(v3.y == 1); 34 | 35 | const char* c4 = "1234"; 36 | auto v4 = vt('1', '2'); 37 | CHECK(v4.x == '1'); 38 | CHECK(v4.y == '2'); 39 | CHECK(sizeof(v4) == 2); 40 | CHECK(memcmp(c4, &v4, sizeof(v4)) == 0); 41 | 42 | const float f[] = { 10,9,8,7 }; 43 | auto v5 = vector2::from_ptr(f); 44 | CHECK(memcmp(&v5, f, 2 * sizeof(float)) == 0); 45 | CHECK(v5.x == 10); 46 | CHECK(v5.y == 9); 47 | 48 | // attach 49 | auto& v6 = vector2::attach_to_ptr(f); 50 | CHECK(v6.x == 10); 51 | CHECK(v6.y == 9); 52 | CHECK(reinterpret_cast(&v6) == f); 53 | 54 | auto v7 = vector2::attach_to_array(f); 55 | CHECK(v7[0].x == 10); 56 | CHECK(v7[0].y == 9); 57 | CHECK(v7[1].x == 8); 58 | CHECK(v7[1].y == 7); 59 | CHECK(v7 == &v6); 60 | CHECK(v7[0] == v6); 61 | CHECK(reinterpret_cast(v7) == f); 62 | 63 | float ff[] = { 1, 2, 3, 4 }; 64 | auto& v8 = vector2::attach_to_ptr(ff); 65 | v8.x = 10; 66 | v8.y = 20; 67 | CHECK(ff[0] == 10); 68 | CHECK(ff[1] == 20); 69 | 70 | auto v9 = vector2::attach_to_array(ff); 71 | v9[0].y = 21; 72 | v9[1].x = 30; 73 | CHECK(ff[1] == 21); 74 | CHECK(ff[2] == 30); 75 | } 76 | 77 | TEST_CASE("compare") 78 | { 79 | auto v0 = vector2::zero(); 80 | CHECK(v0 == v(0, 0)); 81 | CHECK(v0 != v(1, 0)); 82 | CHECK(v0 != v(0, 1)); 83 | 84 | vector2 v1; 85 | v1.x = 10; 86 | v1.y = 20; 87 | CHECK(v1 == v(10, 20)); 88 | CHECK(v1 != v(1, 20)); 89 | CHECK(v1 != v(10, 1)); 90 | 91 | CHECK(v0 != v1); 92 | 93 | v0.x = 10; 94 | v0.y = 20; 95 | 96 | CHECK(v0 == v1); 97 | 98 | CHECK(close(v0, v1)); 99 | v1.x = 11; 100 | CHECK(!close(v0, v1)); 101 | v1.y = 21; 102 | CHECK(!close(v0, v1)); 103 | CHECK(close(v0, v1, 2.f)); 104 | v1 = v(10.000001f, 20.000001f); 105 | CHECK(close(v0, v1)); 106 | 107 | CHECK(vector2::unit_x() == v(1, 0)); 108 | CHECK(vector2::unit_y() == v(0, 1)); 109 | } 110 | 111 | TEST_CASE("access") 112 | { 113 | auto v0 = v(1, 2); 114 | CHECK(v0.data() == reinterpret_cast(&v0)); 115 | CHECK(v0.data()[0] == 1); 116 | CHECK(v0.data()[1] == 2); 117 | CHECK(v0[0] == 1); 118 | CHECK(v0[1] == 2); 119 | CHECK(v0.at(0) == 1); 120 | CHECK(v0.at(1) == 2); 121 | CHECK(v0.data() == v0.as_ptr()); 122 | 123 | v0[0] = 10; 124 | v0.at(1) = 20; 125 | 126 | const auto v1 = v0; 127 | CHECK(v1.data() == reinterpret_cast(&v1)); 128 | CHECK(v1.data()[0] == 10); 129 | CHECK(v1.data()[1] == 20); 130 | CHECK(v1[0] == 10); 131 | CHECK(v1[1] == 20); 132 | CHECK(v1.at(0) == 10); 133 | CHECK(v1.at(1) == 20); 134 | CHECK(v1.data() == v1.as_ptr()); 135 | 136 | v0.x = 0.3f; 137 | v0.y = 2.91f; 138 | 139 | CHECK(v0.as_vector2_t() == vt(0, 2)); 140 | } 141 | 142 | TEST_CASE("std") 143 | { 144 | auto v0 = v(11, 12); 145 | CHECK(v0.front() == 11); 146 | CHECK(v0.back() == 12); 147 | CHECK(v0.begin() == v0.data()); 148 | CHECK(v0.begin() + 2 == v0.end()); 149 | CHECK(v0.rbegin() + 2 == v0.rend()); 150 | auto i = v0.rbegin(); 151 | CHECK(*i == 12); 152 | ++i; 153 | CHECK(*i == 11); 154 | 155 | for (auto& e : v0) 156 | { 157 | e += 10; 158 | } 159 | 160 | const auto v1 = v0; 161 | 162 | CHECK(v1.front() == 21); 163 | CHECK(v1.back() == 22); 164 | CHECK(v1.begin() == v1.data()); 165 | CHECK(v1.begin() + 2 == v1.end()); 166 | CHECK(v1.rbegin() + 2 == v1.rend()); 167 | auto ci = v1.rbegin(); 168 | CHECK(*ci == 22); 169 | ++ci; 170 | CHECK(*ci == 21); 171 | } 172 | 173 | 174 | TEST_CASE("members") 175 | { 176 | auto v0 = v(1, 2); 177 | auto v1 = +v0; 178 | CHECK(v0 == v1); 179 | v1 = -v0; 180 | CHECK(v1.x == -v0.x); 181 | CHECK(v1.y == -v0.y); 182 | 183 | v0 += v1; 184 | CHECK(v0 == vector2::zero()); 185 | 186 | v0 -= v1; 187 | CHECK(v0 == -v1); 188 | 189 | v0 *= 2; 190 | CHECK(v0 == v(2, 4)); 191 | 192 | v0 /= 8; 193 | CHECK(YamaApprox(v0) == v(0.25f, 0.5f)); 194 | 195 | v1 *= -2; 196 | v0.mul(v1); 197 | CHECK(YamaApprox(v0) == v(0.5f, 2)); 198 | 199 | v1.div(v0); 200 | CHECK(YamaApprox(v1) == v(4, 2)); 201 | 202 | CHECK(Approx(v0.length_sq()) == 4.25); 203 | v1.y = 3; 204 | CHECK(Approx(v1.length()) == 5); 205 | 206 | CHECK(v1.manhattan_length() == 7); 207 | 208 | v1.normalize(); 209 | CHECK(Approx(v1.length()) == 1); 210 | CHECK(Approx(v1.x) == 0.8); 211 | CHECK(v1.is_normalized()); 212 | 213 | v0 = v(1, 2); 214 | v0.homogenous_normalize(); 215 | CHECK(YamaApprox(v0) == v(0.5f, 1)); 216 | 217 | v0 = v(1, 1); 218 | CHECK(v0.reflection(v(0, 1)) == v(1, -1)); 219 | CHECK(v0.reflection(v(0, -1)) == v(1, -1)); 220 | 221 | v0 = v(1, -2); 222 | CHECK(v0.reflection(v(0, 1)) == v(1, 2)); 223 | v0.normalize(); 224 | CHECK(YamaApprox(v0.reflection(v0)) == -v0); 225 | 226 | v0 = v(2, 3); 227 | CHECK(v0.sum() == 5); 228 | CHECK(v0.product() == 6); 229 | } 230 | 231 | TEST_CASE("ops") 232 | { 233 | auto v0 = v(1, 2); 234 | auto v1 = v(3, 4); 235 | 236 | auto v2 = v0 + v1; 237 | CHECK(v2 == v(4, 6)); 238 | 239 | v1 = v2 - v0; 240 | CHECK(v1 == v(3, 4)); 241 | 242 | v1 = 2.f * v0; 243 | CHECK(v1 == v(2, 4)); 244 | 245 | v0 = v1 * 0.5f; 246 | CHECK(v0 == v(1, 2)); 247 | 248 | v0 = 4.f / v1; 249 | CHECK(v0 == v(2, 1)); 250 | 251 | v1 = v0 / 2.f; 252 | CHECK(v1 == v(1, 0.5f)); 253 | 254 | v0 = abs(v1); 255 | CHECK(v1 == v(1, 0.5f)); 256 | 257 | v0 = abs(-v1); 258 | CHECK(v1 == v(1, 0.5f)); 259 | 260 | v0 = v(0.25f, 0.5f); 261 | v1 = v(8, 10); 262 | v2 = mul(v0, v1); 263 | CHECK(v2 == v(2, 5)); 264 | 265 | v2 = div(v1, v(4, 2)); 266 | CHECK(v2 == v(2, 5)); 267 | 268 | auto iv0 = mod(vt(5, 6), vt(2, 4)); 269 | CHECK(iv0 == vt(1, 2)); 270 | 271 | CHECK(YamaApprox(mod(v(5.3f, 18.5f), v(2, 4.2f))) == v(1.3f, 1.7f)); 272 | 273 | v1 = sign(v2); 274 | CHECK(v1 == v(1, 1)); 275 | v1 = sign(-v2); 276 | CHECK(v1 == v(-1, -1)); 277 | v1 = sign(v(-1, 2)); 278 | CHECK(v1 == v(-1, 1)); 279 | 280 | v1 = v(-1, 10); 281 | v0 = max(v1, v2); 282 | CHECK(v0 == v(2, 10)); 283 | v0 = min(v1, v2); 284 | CHECK(v0 == v(-1, 5)); 285 | 286 | CHECK(dot(v(0, 1), v(1, 0)) == 0); 287 | CHECK(dot(v1, v1) == v1.length_sq()); 288 | CHECK(dot(v1, v2) == 48); 289 | 290 | CHECK(cross_magnitude(v(0, 1), v(1, 0)) == -1); 291 | CHECK(cross_magnitude(v(2, 1), v(3, 4)) == 5); 292 | 293 | auto vo = v1.get_orthogonal(); 294 | CHECK(orthogonal(vo, v1)); 295 | 296 | vo = v2.get_orthogonal(); 297 | CHECK(orthogonal(v2, vo)); 298 | 299 | const auto v3 = v(3, 4); 300 | v0 = normalize(v3); 301 | CHECK(YamaApprox(v0) == v(0.6f, 0.8f)); 302 | 303 | CHECK(orthogonal(v(0, 1), v(1, 0))); 304 | CHECK(orthogonal(v(1, 2), v(-2, 1))); 305 | 306 | CHECK(collinear(v(1, 2), v(-1, -2))); 307 | CHECK(collinear(v(1, 0), v(5, 0))); 308 | CHECK(collinear(v(0, 2), v(0, 11))); 309 | CHECK(collinear(v(4, 2), v(1, 0.5))); 310 | 311 | v0 = v(-1.723f, 5.23f); 312 | CHECK(floor(v0) == v(-2, 5)); 313 | CHECK(ceil(v0) == v(-1, 6)); 314 | CHECK(round(v0) == v(-2, 5)); 315 | CHECK(YamaApprox(frac(v0)) == v(0.723f, 0.23f)); 316 | 317 | CHECK(isfinite(v0)); 318 | CHECK(isfinite(v1)); 319 | CHECK(isfinite(v2)); 320 | 321 | v0.x = std::numeric_limits::quiet_NaN(); 322 | v1.y = std::numeric_limits::infinity(); 323 | v2 = v0 + v1; 324 | CHECK(!isfinite(v0)); 325 | CHECK(!isfinite(v1)); 326 | CHECK(!isfinite(v2)); 327 | 328 | v1 = v(1, 2); 329 | v2 = v(5, 10); 330 | 331 | CHECK(distance_sq(v1, v2) == 80); 332 | CHECK(distance(vector2::zero(), v2) == v2.length()); 333 | CHECK(distance(v2, vector2::zero()) == v2.length()); 334 | 335 | CHECK(clamp(v(3, 1), v1, v2) == v(3, 2)); 336 | CHECK(clamp(v(8, 6), v1, v2) == v(5, 6)); 337 | CHECK(clamp(v(3, 4), v1, v2) == v(3, 4)); 338 | CHECK(clamp(v(10, 20), v1, v2) == v(5, 10)); 339 | CHECK(clamp(v(0, 1), v1, v2) == v(1, 2)); 340 | 341 | CHECK(YamaApprox(lerp(v1, v2, 0.3f)) == v(2.2f, 4.4f)); 342 | CHECK(YamaApprox(lerp(v1, v2, 0.f)) == v1); 343 | CHECK(YamaApprox(lerp(v1, v2, 1.f)) == v2); 344 | } 345 | -------------------------------------------------------------------------------- /test/unit/vector3.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/vector3.hpp" 5 | #include "common.hpp" 6 | #include "yama/ext/vector3_ostream.hpp" 7 | 8 | using namespace yama; 9 | using doctest::Approx; 10 | 11 | TEST_SUITE_BEGIN("vector3"); 12 | 13 | TEST_CASE("construction") 14 | { 15 | double d0[] = { 0, 0, 0, 0 }; 16 | auto v0 = vector3_t::zero(); 17 | CHECK(v0.x == 0); 18 | CHECK(v0.y == 0); 19 | CHECK(v0.z == 0); 20 | CHECK(memcmp(d0, &v0, 3 * sizeof(double)) == 0); 21 | 22 | float f1[] = { 1,2,3,4 }; 23 | auto v1 = vector3::coord(1, 2, 3); 24 | CHECK(v1.x == 1); 25 | CHECK(v1.y == 2); 26 | CHECK(v1.z == 3); 27 | CHECK(memcmp(f1, &v1, 3 * sizeof(float)) == 0); 28 | 29 | auto v2 = vector3_t::uniform(3); 30 | CHECK(v2.x == 3); 31 | CHECK(v2.x == v2.y); 32 | CHECK(v2.x == v2.z); 33 | 34 | auto v3 = v(3, 1, 5); 35 | CHECK(v3.x == 3); 36 | CHECK(v3.y == 1); 37 | CHECK(v3.z == 5); 38 | 39 | const char* c4 = "1234"; 40 | auto v4 = vt('1', '2', '3'); 41 | CHECK(v4.x == '1'); 42 | CHECK(v4.y == '2'); 43 | CHECK(v4.z == '3'); 44 | CHECK(sizeof(v4) == 3); 45 | CHECK(memcmp(c4, &v4, sizeof(v4)) == 0); 46 | 47 | const float f[] = { 10,9,8,7,6,5 }; 48 | auto v5 = vector3::from_ptr(f); 49 | CHECK(memcmp(&v5, f, 3 * sizeof(float)) == 0); 50 | CHECK(v5.x == 10); 51 | CHECK(v5.y == 9); 52 | CHECK(v5.z == 8); 53 | 54 | // attach 55 | auto& v6 = vector3::attach_to_ptr(f); 56 | CHECK(v6.x == 10); 57 | CHECK(v6.y == 9); 58 | CHECK(v6.z == 8); 59 | CHECK(reinterpret_cast(&v6) == f); 60 | 61 | auto v7 = vector3::attach_to_array(f); 62 | CHECK(v7[0].x == 10); 63 | CHECK(v7[0].y == 9); 64 | CHECK(v7[0].z == 8); 65 | CHECK(v7[1].x == 7); 66 | CHECK(v7[1].y == 6); 67 | CHECK(v7[1].z == 5); 68 | CHECK(v7 == &v6); 69 | CHECK(v7[0] == v6); 70 | CHECK(reinterpret_cast(v7) == f); 71 | 72 | float ff[] = { 1, 2, 3, 4, 5, 6 }; 73 | auto& v8 = vector3::attach_to_ptr(ff); 74 | v8.x = 10; 75 | v8.y = 20; 76 | CHECK(ff[0] == 10); 77 | CHECK(ff[1] == 20); 78 | 79 | auto v9 = vector3::attach_to_array(ff); 80 | v9[0].y = 21; 81 | v9[1].z = 30; 82 | CHECK(ff[1] == 21); 83 | CHECK(ff[5] == 30); 84 | } 85 | 86 | TEST_CASE("compare") 87 | { 88 | auto v0 = vector3::zero(); 89 | CHECK(v0 == v(0, 0, 0)); 90 | CHECK(v0 != v(1, 0, 0)); 91 | CHECK(v0 != v(0, 1, 0)); 92 | CHECK(v0 != v(0, 0, 1)); 93 | 94 | vector3 v1; 95 | v1.x = 10; 96 | v1.y = 20; 97 | v1.z = 30; 98 | CHECK(v1 == v(10, 20, 30)); 99 | CHECK(v1 != v(1, 20, 30)); 100 | CHECK(v1 != v(10, 1, 30)); 101 | CHECK(v1 != v(10, 20, 1)); 102 | 103 | CHECK(v0 != v1); 104 | 105 | v0.x = 10; 106 | v0.y = 20; 107 | v0.z = 30; 108 | 109 | CHECK(v0 == v1); 110 | 111 | CHECK(close(v0, v1)); 112 | v1.x = 11; 113 | CHECK(!close(v0, v1)); 114 | v1.y = 21; 115 | CHECK(!close(v0, v1)); 116 | v1.z = 31; 117 | CHECK(!close(v0, v1)); 118 | CHECK(close(v0, v1, 2.f)); 119 | v1 = v(10.000001f, 20.000001f, 30.000001f); 120 | CHECK(close(v0, v1)); 121 | 122 | CHECK(vector3::unit_x() == v(1, 0, 0)); 123 | CHECK(vector3::unit_y() == v(0, 1, 0)); 124 | CHECK(vector3::unit_z() == v(0, 0, 1)); 125 | } 126 | 127 | TEST_CASE("special_construction") 128 | { 129 | } 130 | 131 | TEST_CASE("access") 132 | { 133 | auto v0 = v(1, 2, 3); 134 | CHECK(v0.data() == reinterpret_cast(&v0)); 135 | CHECK(v0.data()[0] == 1); 136 | CHECK(v0.data()[1] == 2); 137 | CHECK(v0.data()[2] == 3); 138 | CHECK(v0[0] == 1); 139 | CHECK(v0[1] == 2); 140 | CHECK(v0[2] == 3); 141 | CHECK(v0.at(0) == 1); 142 | CHECK(v0.at(1) == 2); 143 | CHECK(v0.at(2) == 3); 144 | CHECK(v0.data() == v0.as_ptr()); 145 | 146 | v0[0] = 10; 147 | v0.at(1) = 20; 148 | v0.z = 30; 149 | 150 | const auto v1 = v0; 151 | CHECK(v1.data() == reinterpret_cast(&v1)); 152 | CHECK(v1.data()[0] == 10); 153 | CHECK(v1.data()[1] == 20); 154 | CHECK(v1.data()[2] == 30); 155 | CHECK(v1[0] == 10); 156 | CHECK(v1[1] == 20); 157 | CHECK(v1[2] == 30); 158 | CHECK(v1.at(0) == 10); 159 | CHECK(v1.at(1) == 20); 160 | CHECK(v1.at(2) == 30); 161 | CHECK(v1.data() == v1.as_ptr()); 162 | 163 | v0.x = 0.3f; 164 | v0.y = 2.91f; 165 | v0.z = -11.123f; 166 | 167 | CHECK(v0.as_vector3_t() == vt(0, 2, -11)); 168 | } 169 | 170 | TEST_CASE("std") 171 | { 172 | auto v0 = v(11, 12, 13); 173 | CHECK(v0.front() == 11); 174 | CHECK(v0.back() == 13); 175 | CHECK(v0.begin() == v0.data()); 176 | CHECK(v0.begin() + 3 == v0.end()); 177 | CHECK(v0.rbegin() + 3 == v0.rend()); 178 | auto i = v0.rbegin(); 179 | CHECK(*i == 13); 180 | ++i; 181 | CHECK(*i == 12); 182 | 183 | for (auto& e : v0) 184 | { 185 | e += 10; 186 | } 187 | 188 | const auto v1 = v0; 189 | 190 | CHECK(v1.front() == 21); 191 | CHECK(v1.back() == 23); 192 | CHECK(v1.begin() == v1.data()); 193 | CHECK(v1.begin() + 3 == v1.end()); 194 | CHECK(v1.rbegin() + 3 == v1.rend()); 195 | auto ci = v1.rbegin(); 196 | CHECK(*ci == 23); 197 | ++ci; 198 | CHECK(*ci == 22); 199 | } 200 | 201 | 202 | TEST_CASE("members") 203 | { 204 | auto v0 = v(1, 2, 3); 205 | auto v1 = +v0; 206 | CHECK(v0 == v1); 207 | v1 = -v0; 208 | CHECK(v1.x == -v0.x); 209 | CHECK(v1.y == -v0.y); 210 | CHECK(v1.z == -v0.z); 211 | 212 | v0 += v1; 213 | CHECK(v0 == vector3::zero()); 214 | 215 | v0 -= v1; 216 | CHECK(v0 == -v1); 217 | 218 | v0 *= 2; 219 | CHECK(v0 == v(2, 4, 6)); 220 | 221 | v0 /= 8; 222 | CHECK(YamaApprox(v0) == v(0.25f, 0.5f, 0.75f)); 223 | 224 | v1 *= -2; 225 | CHECK(v1 == v(2, 4, 6)); 226 | v0.mul(v1); 227 | CHECK(YamaApprox(v0) == v(0.5f, 2, 4.5f)); 228 | 229 | v1.div(v0); 230 | CHECK(YamaApprox(v1) == v(4, 2, 1.3333333f)); 231 | 232 | CHECK(Approx(v0.length_sq()) == 24.5f); 233 | v1 = v(3, 4, 12); 234 | CHECK(Approx(v1.length()) == 13); 235 | 236 | CHECK(v1.manhattan_length() == 19); 237 | 238 | v1.normalize(); 239 | CHECK(Approx(v1.length()) == 1); 240 | CHECK(Approx(v1.x) == 0.23076923076); 241 | CHECK(v1.is_normalized()); 242 | 243 | v0 = v(1, 2, 4); 244 | v0.homogenous_normalize(); 245 | CHECK(YamaApprox(v0) == v(0.25f, 0.5f, 1)); 246 | 247 | v0 = v(1, 1, 1); 248 | CHECK(v0.reflection(v(0, 1, 0)) == v(1, -1, 1)); 249 | CHECK(v0.reflection(v(0, -1, 0)) == v(1, -1, 1)); 250 | 251 | v0 = v(1, -2, 5); 252 | CHECK(v0.reflection(v(0, 1, 0)) == v(1, 2, 5)); 253 | v0.normalize(); 254 | CHECK(YamaApprox(v0.reflection(v0)) == -v0); 255 | 256 | v0 = v(2, 3, 4); 257 | CHECK(v0.sum() == 9); 258 | CHECK(v0.product() == 24); 259 | } 260 | 261 | TEST_CASE("ops") 262 | { 263 | auto v0 = v(1, 2, 3); 264 | auto v1 = v(3, 4, 5); 265 | 266 | auto v2 = v0 + v1; 267 | CHECK(v2 == v(4, 6, 8)); 268 | 269 | v1 = v2 - v0; 270 | CHECK(v1 == v(3, 4, 5)); 271 | 272 | v1 = 2.f * v0; 273 | CHECK(v1 == v(2, 4, 6)); 274 | 275 | v0 = v1 * 0.5f; 276 | CHECK(v0 == v(1, 2, 3)); 277 | 278 | v0 = 4.f / v1; 279 | CHECK(YamaApprox(v0) == v(2, 1, 0.6666666f)); 280 | 281 | v1 = v0 / 2.f; 282 | CHECK(YamaApprox(v1) == v(1, 0.5f, 0.3333333f)); 283 | 284 | v0 = abs(v1); 285 | CHECK(YamaApprox(v1) == v(1, 0.5f, 0.3333333f)); 286 | 287 | v0 = abs(-v1); 288 | CHECK(YamaApprox(v1) == v(1, 0.5f, 0.3333333f)); 289 | 290 | v0 = v(0.25f, 0.5f, 1.5f); 291 | v1 = v(8, 10, 12); 292 | v2 = mul(v0, v1); 293 | CHECK(v2 == v(2, 5, 18)); 294 | 295 | v2 = div(v1, v(4, 2, 12)); 296 | CHECK(v2 == v(2, 5, 1)); 297 | 298 | auto iv0 = mod(vt(5, 6, 11), vt(2, 4, 7)); 299 | CHECK(iv0 == vt(1, 2, 4)); 300 | 301 | CHECK(YamaApprox(mod(v(5.3f, 18.5f, 11), v(2, 4.2f, 7))) == v(1.3f, 1.7f, 4)); 302 | 303 | v1 = sign(v2); 304 | CHECK(v1 == v(1, 1, 1)); 305 | v1 = sign(-v2); 306 | CHECK(v1 == v(-1, -1, -1)); 307 | v1 = sign(v(-1, 2, -4)); 308 | CHECK(v1 == v(-1, 1, -1)); 309 | 310 | v1 = v(-1, 10, 6); 311 | v0 = max(v1, v2); 312 | CHECK(v0 == v(2, 10, 6)); 313 | v0 = min(v1, v2); 314 | CHECK(v0 == v(-1, 5, 1)); 315 | 316 | CHECK(dot(v(0, 1, 0), v(0, 0, 1)) == 0); 317 | CHECK(dot(v1, v1) == v1.length_sq()); 318 | CHECK(dot(v1, v2) == 54); 319 | 320 | CHECK(cross(v(1, 0, 0), v(0, 1, 0)) == v(0, 0, 1)); 321 | CHECK(cross(v(0, 1, 0), v(0, 0, 1)) == v(1, 0, 0)); 322 | CHECK(cross(v(1, 0, 0), v(0, 0, 1)) == v(0, -1, 0)); 323 | CHECK(cross(v1, v2) == v(-20, 13, -25)); 324 | 325 | auto vo = v1.get_orthogonal(); 326 | CHECK(orthogonal(vo, v1)); 327 | 328 | vo = v2.get_orthogonal(); 329 | CHECK(orthogonal(v2, vo)); 330 | 331 | const auto v3 = v(3, 4, 12); 332 | v0 = normalize(v3); 333 | CHECK(YamaApprox(v0) == v(0.23076923076f, 0.30769230769f, 0.92307692307f)); 334 | 335 | CHECK(orthogonal(v(0, 1, 0), v(1, 0, 0))); 336 | CHECK(orthogonal(v(0, 1, 0), v(0, 0, 1))); 337 | CHECK(orthogonal(v(1, 0, 2), v(-2, 0, 1))); 338 | CHECK(orthogonal(v(1, 2, 3), v(-2, -0.5f, 1))); 339 | 340 | CHECK(collinear(v(1, 2, 3), v(-1, -2, -3))); 341 | CHECK(collinear(v(1, 0, 0), v(5, 0, 0))); 342 | CHECK(collinear(v(0, 2, 0), v(0, 11, 0))); 343 | CHECK(collinear(v(0, 0, 6), v(0, 0, 10))); 344 | CHECK(collinear(v(1, 2, 0), v(5, 10, 0))); 345 | CHECK(collinear(v(3, 0, 6), v(5, 0, 10))); 346 | CHECK(collinear(v(0, 2, 1), v(0, -4, -2))); 347 | CHECK(collinear(v(4, 2, 10), v(1, 0.5f, 2.5f))); 348 | 349 | v0 = v(-1.723f, 5.23f, 2.522f); 350 | CHECK(floor(v0) == v(-2, 5, 2)); 351 | CHECK(ceil(v0) == v(-1, 6, 3)); 352 | CHECK(round(v0) == v(-2, 5, 3)); 353 | CHECK(YamaApprox(frac(v0)) == v(0.723f, 0.23f, 0.522f)); 354 | 355 | CHECK(isfinite(v0)); 356 | CHECK(isfinite(v1)); 357 | CHECK(isfinite(v2)); 358 | 359 | v0.x = std::numeric_limits::quiet_NaN(); 360 | v1.y = std::numeric_limits::infinity(); 361 | v2.z = -std::numeric_limits::infinity(); 362 | v2 = v0 + v1; 363 | CHECK(!isfinite(v0)); 364 | CHECK(!isfinite(v1)); 365 | CHECK(!isfinite(v2)); 366 | 367 | v1 = v(1, 2, 3); 368 | v2 = v(5, 10, 15); 369 | 370 | CHECK(distance_sq(v1, v2) == 224); 371 | CHECK(distance(vector3::zero(), v2) == v2.length()); 372 | CHECK(distance(v2, vector3::zero()) == v2.length()); 373 | 374 | CHECK(clamp(v(3, 1, 20), v1, v2) == v(3, 2, 15)); 375 | CHECK(clamp(v(8, 6, 0), v1, v2) == v(5, 6, 3)); 376 | CHECK(clamp(v(3, 4, 5), v1, v2) == v(3, 4, 5)); 377 | CHECK(clamp(v(10, 20, 30), v1, v2) == v2); 378 | CHECK(clamp(v(0, 1, 2), v1, v2) == v1); 379 | 380 | CHECK(YamaApprox(lerp(v1, v2, 0.3f)) == v(2.2f, 4.4f, 6.6f)); 381 | CHECK(YamaApprox(lerp(v1, v2, 0.f)) == v1); 382 | CHECK(YamaApprox(lerp(v1, v2, 1.f)) == v2); 383 | } 384 | -------------------------------------------------------------------------------- /test/unit/vector4.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "yama/vector4.hpp" 5 | #include "common.hpp" 6 | #include "yama/ext/vector4_ostream.hpp" 7 | 8 | using namespace yama; 9 | using doctest::Approx; 10 | 11 | TEST_SUITE_BEGIN("vector4"); 12 | 13 | TEST_CASE("construction") 14 | { 15 | double d0[] = { 0, 0, 0, 0 }; 16 | auto v0 = vector4_t::zero(); 17 | CHECK(v0.x == 0); 18 | CHECK(v0.y == 0); 19 | CHECK(v0.z == 0); 20 | CHECK(v0.w == 0); 21 | CHECK(memcmp(d0, &v0, 4 * sizeof(double)) == 0); 22 | 23 | float f1[] = { 1,2,3,4 }; 24 | auto v1 = vector4::coord(1, 2, 3, 4); 25 | CHECK(v1.x == 1); 26 | CHECK(v1.y == 2); 27 | CHECK(v1.z == 3); 28 | CHECK(v1.w == 4); 29 | CHECK(memcmp(f1, &v1, 4 * sizeof(float)) == 0); 30 | 31 | auto v2 = vector4_t::uniform(3); 32 | CHECK(v2.x == 3); 33 | CHECK(v2.x == v2.y); 34 | CHECK(v2.x == v2.z); 35 | CHECK(v2.x == v2.w); 36 | 37 | auto v3 = v(3, 1, 5, 2); 38 | CHECK(v3.x == 3); 39 | CHECK(v3.y == 1); 40 | CHECK(v3.z == 5); 41 | CHECK(v3.w == 2); 42 | 43 | const char* c4 = "1234"; 44 | auto v4 = vt('1', '2', '3', '4'); 45 | CHECK(v4.x == '1'); 46 | CHECK(v4.y == '2'); 47 | CHECK(v4.z == '3'); 48 | CHECK(v4.w == '4'); 49 | CHECK(sizeof(v4) == 4); 50 | CHECK(memcmp(c4, &v4, sizeof(v4)) == 0); 51 | 52 | const float f[] = { 10,9,8,7,6,5,4,3 }; 53 | auto v5 = vector4::from_ptr(f); 54 | CHECK(memcmp(&v5, f, 4 * sizeof(float)) == 0); 55 | CHECK(v5.x == 10); 56 | CHECK(v5.y == 9); 57 | CHECK(v5.z == 8); 58 | CHECK(v5.w == 7); 59 | 60 | // attach 61 | auto& v6 = vector4::attach_to_ptr(f); 62 | CHECK(v6.x == 10); 63 | CHECK(v6.y == 9); 64 | CHECK(v6.z == 8); 65 | CHECK(v6.w == 7); 66 | CHECK(reinterpret_cast(&v6) == f); 67 | 68 | auto v7 = vector4::attach_to_array(f); 69 | CHECK(v7[0].x == 10); 70 | CHECK(v7[0].y == 9); 71 | CHECK(v7[0].z == 8); 72 | CHECK(v7[0].w == 7); 73 | CHECK(v7[1].x == 6); 74 | CHECK(v7[1].y == 5); 75 | CHECK(v7[1].z == 4); 76 | CHECK(v7[1].w == 3); 77 | CHECK(v7 == &v6); 78 | CHECK(v7[0] == v6); 79 | CHECK(reinterpret_cast(v7) == f); 80 | 81 | float ff[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; 82 | auto& v8 = vector4::attach_to_ptr(ff); 83 | v8.x = 10; 84 | v8.w = 20; 85 | CHECK(ff[0] == 10); 86 | CHECK(ff[3] == 20); 87 | 88 | auto v9 = vector4::attach_to_array(ff); 89 | v9[0].y = 21; 90 | v9[1].z = 30; 91 | CHECK(ff[1] == 21); 92 | CHECK(ff[6] == 30); 93 | } 94 | 95 | TEST_CASE("compare") 96 | { 97 | auto v0 = vector4::zero(); 98 | CHECK(v0 == v(0, 0, 0, 0)); 99 | CHECK(v0 != v(1, 0, 0, 0)); 100 | CHECK(v0 != v(0, 1, 0, 0)); 101 | CHECK(v0 != v(0, 0, 1, 0)); 102 | CHECK(v0 != v(0, 0, 0, 1)); 103 | 104 | vector4 v1; 105 | v1.x = 10; 106 | v1.y = 20; 107 | v1.z = 30; 108 | v1.w = 40; 109 | CHECK(v1 == v(10, 20, 30, 40)); 110 | CHECK(v1 != v(1, 20, 30, 40)); 111 | CHECK(v1 != v(10, 1, 30, 40)); 112 | CHECK(v1 != v(10, 20, 1, 40)); 113 | CHECK(v1 != v(10, 20, 30, 1)); 114 | 115 | CHECK(v0 != v1); 116 | 117 | v0.x = 10; 118 | v0.y = 20; 119 | v0.z = 30; 120 | v0.w = 40; 121 | 122 | CHECK(v0 == v1); 123 | 124 | CHECK(close(v0, v1)); 125 | v1.x = 11; 126 | CHECK(!close(v0, v1)); 127 | v1.y = 21; 128 | CHECK(!close(v0, v1)); 129 | v1.z = 31; 130 | CHECK(!close(v0, v1)); 131 | v1.w = 41; 132 | CHECK(close(v0, v1, 2.f)); 133 | v1 = v(10.000001f, 20.000001f, 30.000001f, 40.000001f); 134 | CHECK(close(v0, v1)); 135 | 136 | CHECK(vector4::unit_x() == v(1, 0, 0, 0)); 137 | CHECK(vector4::unit_y() == v(0, 1, 0, 0)); 138 | CHECK(vector4::unit_z() == v(0, 0, 1, 0)); 139 | CHECK(vector4::unit_w() == v(0, 0, 0, 1)); 140 | } 141 | 142 | TEST_CASE("special_construction") 143 | { 144 | } 145 | 146 | TEST_CASE("access") 147 | { 148 | auto v0 = v(1, 2, 3, 4); 149 | CHECK(v0.data() == reinterpret_cast(&v0)); 150 | CHECK(v0.data()[0] == 1); 151 | CHECK(v0.data()[1] == 2); 152 | CHECK(v0.data()[2] == 3); 153 | CHECK(v0.data()[3] == 4); 154 | CHECK(v0[0] == 1); 155 | CHECK(v0[1] == 2); 156 | CHECK(v0[2] == 3); 157 | CHECK(v0[3] == 4); 158 | CHECK(v0.at(0) == 1); 159 | CHECK(v0.at(1) == 2); 160 | CHECK(v0.at(2) == 3); 161 | CHECK(v0.at(3) == 4); 162 | CHECK(v0.data() == v0.as_ptr()); 163 | 164 | v0[0] = 10; 165 | v0.at(1) = 20; 166 | v0.z = 30; 167 | v0.data()[3] = 40; 168 | 169 | const auto v1 = v0; 170 | CHECK(v1.data() == reinterpret_cast(&v1)); 171 | CHECK(v1.data()[0] == 10); 172 | CHECK(v1.data()[1] == 20); 173 | CHECK(v1.data()[2] == 30); 174 | CHECK(v1.data()[3] == 40); 175 | CHECK(v1[0] == 10); 176 | CHECK(v1[1] == 20); 177 | CHECK(v1[2] == 30); 178 | CHECK(v1[3] == 40); 179 | CHECK(v1.at(0) == 10); 180 | CHECK(v1.at(1) == 20); 181 | CHECK(v1.at(2) == 30); 182 | CHECK(v1.at(3) == 40); 183 | CHECK(v1.data() == v1.as_ptr()); 184 | 185 | v0.x = 0.3f; 186 | v0.y = 2.91f; 187 | v0.z = -11.123f; 188 | v0.w = -1.99f; 189 | 190 | CHECK(v0.as_vector4_t() == vt(0, 2, -11, -1)); 191 | } 192 | 193 | TEST_CASE("std") 194 | { 195 | auto v0 = v(11, 12, 13, 14); 196 | CHECK(v0.front() == 11); 197 | CHECK(v0.back() == 14); 198 | CHECK(v0.begin() == v0.data()); 199 | CHECK(v0.begin() + 4 == v0.end()); 200 | CHECK(v0.rbegin() + 4 == v0.rend()); 201 | auto i = v0.rbegin(); 202 | CHECK(*i == 14); 203 | ++i; 204 | CHECK(*i == 13); 205 | 206 | for (auto& e : v0) 207 | { 208 | e += 10; 209 | } 210 | 211 | const auto v1 = v0; 212 | 213 | CHECK(v1.front() == 21); 214 | CHECK(v1.back() == 24); 215 | CHECK(v1.begin() == v1.data()); 216 | CHECK(v1.begin() + 4 == v1.end()); 217 | CHECK(v1.rbegin() + 4 == v1.rend()); 218 | auto ci = v1.rbegin(); 219 | CHECK(*ci == 24); 220 | ++ci; 221 | CHECK(*ci == 23); 222 | } 223 | 224 | 225 | TEST_CASE("members") 226 | { 227 | auto v0 = v(1, 2, 3, 4); 228 | auto v1 = +v0; 229 | CHECK(v0 == v1); 230 | v1 = -v0; 231 | CHECK(v1.x == -v0.x); 232 | CHECK(v1.y == -v0.y); 233 | CHECK(v1.z == -v0.z); 234 | CHECK(v1.w == -v0.w); 235 | 236 | v0 += v1; 237 | CHECK(v0 == vector4::zero()); 238 | 239 | v0 -= v1; 240 | CHECK(v0 == -v1); 241 | 242 | v0 *= 2; 243 | CHECK(v0 == v(2, 4, 6, 8)); 244 | 245 | v0 /= 8; 246 | CHECK(YamaApprox(v0) == v(0.25f, 0.5f, 0.75f, 1)); 247 | 248 | v1 *= -2; 249 | CHECK(v1 == v(2, 4, 6, 8)); 250 | v0.mul(v1); 251 | CHECK(YamaApprox(v0) == v(0.5f, 2, 4.5f, 8)); 252 | 253 | v1.div(v0); 254 | CHECK(YamaApprox(v1) == v(4, 2, 1.3333333f, 1)); 255 | 256 | CHECK(Approx(v0.length_sq()) == 88.5f); 257 | v1 = v(1, 2, 8, 10); 258 | CHECK(Approx(v1.length()) == 13); 259 | 260 | CHECK(v1.manhattan_length() == 21); 261 | 262 | v1.normalize(); 263 | CHECK(Approx(v1.length()) == 1); 264 | CHECK(Approx(v1.w) == 0.76923076923); 265 | CHECK(v1.is_normalized()); 266 | 267 | v0 = v(1, 2, 4, 5); 268 | v0.homogenous_normalize(); 269 | CHECK(YamaApprox(v0) == v(0.2f, 0.4f, 0.8f, 1)); 270 | 271 | v0 = v(1, 1, 1, 1); 272 | CHECK(v0.reflection(v(0, 1, 0, 0)) == v(1, -1, 1, 1)); 273 | CHECK(v0.reflection(v(0, 0, -1, 0)) == v(1, 1, -1, 1)); 274 | 275 | v0 = v(1, -2, 5, 3); 276 | CHECK(v0.reflection(v(0, 1, 0, 0)) == v(1, 2, 5, 3)); 277 | v0.normalize(); 278 | CHECK(YamaApprox(v0.reflection(v0)) == -v0); 279 | 280 | v0 = v(2, 3, 4, 5); 281 | CHECK(v0.sum() == 14); 282 | CHECK(v0.product() == 120); 283 | } 284 | 285 | TEST_CASE("ops") 286 | { 287 | auto v0 = v(1, 2, 3, 4); 288 | auto v1 = v(3, 4, 5, 6); 289 | 290 | auto v2 = v0 + v1; 291 | CHECK(v2 == v(4, 6, 8, 10)); 292 | 293 | v1 = v2 - v0; 294 | CHECK(v1 == v(3, 4, 5, 6)); 295 | 296 | v1 = 2.f * v0; 297 | CHECK(v1 == v(2, 4, 6, 8)); 298 | 299 | v0 = v1 * 0.5f; 300 | CHECK(v0 == v(1, 2, 3, 4)); 301 | 302 | v0 = 4.f / v1; 303 | CHECK(YamaApprox(v0) == v(2, 1, 0.6666666f, 0.5f)); 304 | 305 | v1 = v0 / 2.f; 306 | CHECK(YamaApprox(v1) == v(1, 0.5f, 0.3333333f, 0.25f)); 307 | 308 | v0 = abs(v1); 309 | CHECK(YamaApprox(v1) == v(1, 0.5f, 0.3333333f, 0.25f)); 310 | 311 | v0 = abs(-v1); 312 | CHECK(YamaApprox(v1) == v(1, 0.5f, 0.3333333f, 0.25f)); 313 | 314 | v0 = v(0.25f, 0.5f, 1.5f, 1.75f); 315 | v1 = v(8, 10, 12, 16); 316 | v2 = mul(v0, v1); 317 | CHECK(v2 == v(2, 5, 18, 28)); 318 | 319 | v2 = div(v1, v(4, 2, 12, 8)); 320 | CHECK(v2 == v(2, 5, 1, 2)); 321 | 322 | auto iv0 = mod(vt(5, 6, 11, 8), vt(2, 4, 7, 4)); 323 | CHECK(iv0 == vt(1, 2, 4, 0)); 324 | 325 | CHECK(YamaApprox(mod(v(5.3f, 18.5f, 11, 8), v(2, 4.2f, 7, 4))) == v(1.3f, 1.7f, 4, 0)); 326 | 327 | v1 = sign(v2); 328 | CHECK(v1 == v(1, 1, 1, 1)); 329 | v1 = sign(-v2); 330 | CHECK(v1 == v(-1, -1, -1, -1)); 331 | v1 = sign(v(-1, 2, -4, 0)); 332 | CHECK(v1 == v(-1, 1, -1, 1)); 333 | 334 | v1 = v(-1, 10, 6, 2); 335 | v0 = max(v1, v2); 336 | CHECK(v0 == v(2, 10, 6, 2)); 337 | v0 = min(v1, v2); 338 | CHECK(v0 == v(-1, 5, 1, 2)); 339 | 340 | CHECK(dot(v(0, 1, 0, 0), v(0, 0, 0, 1)) == 0); 341 | CHECK(dot(v1, v1) == v1.length_sq()); 342 | CHECK(dot(v1, v2) == 58); 343 | 344 | const auto v3 = v(3, 4, 12, 0); 345 | v0 = normalize(v3); 346 | CHECK(YamaApprox(v0) == v(0.23076923076f, 0.30769230769f, 0.92307692307f, 0)); 347 | 348 | CHECK(orthogonal(v(0, 1, 0, 0), v(1, 0, 0, 0))); 349 | CHECK(orthogonal(v(0, 1, 0, 0), v(0, 0, 0, 1))); 350 | CHECK(orthogonal(v(1, 0, 0, 2), v(-2, 0, 0, 1))); 351 | 352 | CHECK(collinear(v(1, 2, 3, 4), v(-1, -2, -3, -4))); 353 | CHECK(collinear(v(1, 0, 0, 0), v(5, 0, 0, 0))); 354 | CHECK(collinear(v(0, 2, 0, 0), v(0, 11, 0, 0))); 355 | CHECK(collinear(v(0, 0, 6, 0), v(0, 0, 10, 0))); 356 | CHECK(collinear(v(0, 0, 0, 7), v(0, 0, 0, 3))); 357 | CHECK(collinear(v(1, 2, 0, 0), v(5, 10, 0, 0))); 358 | CHECK(collinear(v(3, 0, 6, 0), v(5, 0, 10, 0))); 359 | CHECK(collinear(v(1, 2, 0, 3), v(5, 10, 0, 15))); 360 | CHECK(collinear(v(3, 0, 6, 6), v(5, 0, 10, 10))); 361 | CHECK(collinear(v(4, 2, 10, 8), v(1, 0.5f, 2.5f, 2))); 362 | 363 | v0 = v(-1.723f, 5.23f, 2.522f, -2.222f); 364 | CHECK(floor(v0) == v(-2, 5, 2, -3)); 365 | CHECK(ceil(v0) == v(-1, 6, 3, -2)); 366 | CHECK(round(v0) == v(-2, 5, 3, -2)); 367 | CHECK(YamaApprox(frac(v0)) == v(0.723f, 0.23f, 0.522f, 0.222f)); 368 | 369 | CHECK(isfinite(v0)); 370 | CHECK(isfinite(v1)); 371 | CHECK(isfinite(v2)); 372 | 373 | v0.x = std::numeric_limits::quiet_NaN(); 374 | v1.y = std::numeric_limits::infinity(); 375 | v2 = v0 + v1; 376 | CHECK(!isfinite(v0)); 377 | CHECK(!isfinite(v1)); 378 | CHECK(!isfinite(v2)); 379 | 380 | v1 = v(1, 2, 3, 4); 381 | v2 = v(5, 10, 15, 18); 382 | 383 | CHECK(distance_sq(v1, v2) == 420); 384 | CHECK(distance(vector4::zero(), v2) == v2.length()); 385 | CHECK(distance(v2, vector4::zero()) == v2.length()); 386 | 387 | CHECK(clamp(v(3, 1, 20, 4), v1, v2) == v(3, 2, 15, 4)); 388 | CHECK(clamp(v(8, 6, 0, 18), v1, v2) == v(5, 6, 3, 18)); 389 | CHECK(clamp(v(3, 4, 5, 6), v1, v2) == v(3, 4, 5, 6)); 390 | CHECK(clamp(v(10, 20, 30, 40), v1, v2) == v2); 391 | CHECK(clamp(v(0, 1, 2, 3), v1, v2) == v1); 392 | 393 | CHECK(YamaApprox(lerp(v1, v2, 0.3f)) == v(2.2f, 4.4f, 6.6f, 8.2f)); 394 | CHECK(YamaApprox(lerp(v1, v2, 0.f)) == v1); 395 | CHECK(YamaApprox(lerp(v1, v2, 1.f)) == v2); 396 | } 397 | -------------------------------------------------------------------------------- /test/unit/vector_xyzw.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Borislav Stanimirov 2 | // SPDX-License-Identifier: MIT 3 | // 4 | #include "common.hpp" 5 | #include "yama/vector_xyzw.hpp" 6 | 7 | TEST_SUITE_BEGIN("vector_xyzw"); 8 | 9 | using namespace yama; 10 | 11 | TEST_CASE("xyzw") 12 | { 13 | auto v2 = v(1, 2); 14 | CHECK(v2.xy() == v(1, 2)); 15 | v2.xy() = v(3, 4); 16 | CHECK(v2 == v(3, 4)); 17 | CHECK(v2.xyz() == v(3, 4, 0)); 18 | CHECK(v2.xyz(11) == v(3, 4, 11)); 19 | CHECK(v2.xyzw() == v(3, 4, 0, 0)); 20 | CHECK(v2.xyzw(5, 6) == v(3, 4, 5, 6)); 21 | 22 | auto v3 = v(10, 50, 69); 23 | CHECK(v3.xyz() == v(10, 50, 69)); 24 | v3.xyz() = v(6, 7, 8); 25 | CHECK(v3 == v(6, 7, 8)); 26 | CHECK(v3.xy() == v(6, 7)); 27 | CHECK(v3.xz() == v(6, 8)); 28 | v3.xy() = v(1, 2); 29 | CHECK(v3 == v(1, 2, 8)); 30 | CHECK(v3.xyzw() == v(1, 2, 8, 0)); 31 | CHECK(v3.xyzw(3) == v(1, 2, 8, 3)); 32 | CHECK(v3.zyx() == v(8, 2, 1)); 33 | 34 | auto v4 = v(9, 8, 7, 6); 35 | CHECK(v4.xyzw() == v(9, 8, 7, 6)); 36 | CHECK(v4.zyxw() == v(7, 8, 9, 6)); 37 | CHECK(v4.wzyx() == v(6, 7, 8, 9)); 38 | v4.xyzw() = v(1, 2, 3, 4); 39 | CHECK(v4 == v(1, 2, 3, 4)); 40 | CHECK(v4.xy() == v(1, 2)); 41 | CHECK(v4.xz() == v(1, 3)); 42 | CHECK(v4.zw() == v(3, 4)); 43 | v4.xy() = v(6, 7); 44 | CHECK(v4 == v(6, 7, 3, 4)); 45 | CHECK(v4.xyz() == v(6, 7, 3)); 46 | CHECK(v4.zyx() == v(3, 7, 6)); 47 | v4.xyz() = v(1, 2, 3); 48 | CHECK(v4 == v(1, 2, 3, 4)); 49 | } 50 | --------------------------------------------------------------------------------