├── .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-latest, 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 | constexpr typename std::enable_if::value, 45 | T>::type sign(const T& t) 46 | { 47 | return T((T(0) < t) - (t < T(0))); 48 | } 49 | 50 | template 51 | constexpr typename std::enable_if::value && std::is_integral::value, 52 | T>::type sign(const T& t) 53 | { 54 | return T((T(0) < t) - (t < T(0))); 55 | } 56 | 57 | template 58 | typename std::enable_if::value, 59 | T>::type sign(const T& t) 60 | { 61 | return T(t > 0); 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 | constexpr vector2_t swizzle(size_type sx, size_type sy) const { 162 | YAMA_ASSERT_CRIT(sx < value_count, "yama::vector2_t swizzle index out of range"); 163 | YAMA_ASSERT_CRIT(sy < value_count, "yama::vector2_t swizzle index out of range"); 164 | return coord(at(sx), at(sy)); 165 | } 166 | 167 | template 168 | constexpr vector2_t swizzle(const vector2_t& s) const { 169 | return swizzle(size_type(s.x), size_type(s.y)); 170 | } 171 | 172 | /////////////////////////// 173 | // std 174 | 175 | iterator begin() 176 | { 177 | return data(); 178 | } 179 | 180 | iterator end() 181 | { 182 | return data() + value_count; 183 | } 184 | 185 | const_iterator begin() const 186 | { 187 | return data(); 188 | } 189 | 190 | const_iterator end() const 191 | { 192 | return data() + value_count; 193 | } 194 | 195 | value_type& front() 196 | { 197 | return at(0); 198 | } 199 | 200 | value_type& back() 201 | { 202 | return at(value_count - 1); 203 | } 204 | 205 | constexpr const value_type& front() const 206 | { 207 | return at(0); 208 | } 209 | 210 | constexpr const value_type& back() const 211 | { 212 | return at(value_count - 1); 213 | } 214 | 215 | reverse_iterator rbegin() 216 | { 217 | return reverse_iterator(end()); 218 | } 219 | 220 | const_reverse_iterator rbegin() const 221 | { 222 | return const_reverse_iterator(end()); 223 | } 224 | 225 | reverse_iterator rend() 226 | { 227 | return reverse_iterator(begin()); 228 | } 229 | 230 | const_reverse_iterator rend() const 231 | { 232 | return const_reverse_iterator(begin()); 233 | } 234 | 235 | const_iterator cbegin() const { return begin(); } 236 | const_iterator cend() const { return end(); } 237 | const_reverse_iterator crbegin() const { return rbegin(); } 238 | const_reverse_iterator crend() const { return rend(); } 239 | 240 | /////////////////////////////////////////////////////////////////////////// 241 | // arithmetic 242 | 243 | constexpr const vector2_t& operator+() const 244 | { 245 | return *this; 246 | } 247 | 248 | constexpr vector2_t operator-() const 249 | { 250 | return coord(-x, -y); 251 | } 252 | 253 | vector2_t& operator+=(const vector2_t& b) 254 | { 255 | x += b.x; 256 | y += b.y; 257 | return *this; 258 | } 259 | 260 | vector2_t& operator-=(const vector2_t& b) 261 | { 262 | x -= b.x; 263 | y -= b.y; 264 | return *this; 265 | } 266 | 267 | vector2_t& operator*=(const value_type& s) 268 | { 269 | x *= s; 270 | y *= s; 271 | return *this; 272 | } 273 | 274 | vector2_t& operator/=(const value_type& s) 275 | { 276 | YAMA_ASSERT_WARN(s != 0, "yama::vector2_t division by zero"); 277 | x /= s; 278 | y /= s; 279 | return *this; 280 | } 281 | 282 | vector2_t& mul(const vector2_t& b) 283 | { 284 | x *= b.x; 285 | y *= b.y; 286 | return *this; 287 | } 288 | 289 | vector2_t& div(const vector2_t& b) 290 | { 291 | x /= b.x; 292 | y /= b.y; 293 | return *this; 294 | } 295 | 296 | constexpr value_type length_sq() const 297 | { 298 | return sq(x) + sq(y); 299 | } 300 | 301 | value_type length() const 302 | { 303 | return std::sqrt(length_sq()); 304 | } 305 | 306 | constexpr value_type manhattan_length() const 307 | { 308 | return std::abs(x) + std::abs(y); 309 | } 310 | 311 | value_type normalize() 312 | { 313 | auto l = length(); 314 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector2_t"); 315 | x /= l; 316 | y /= l; 317 | return l; 318 | } 319 | 320 | bool is_normalized() const 321 | { 322 | return close(length(), value_type(1)); 323 | } 324 | 325 | void homogenous_normalize() 326 | { 327 | YAMA_ASSERT_WARN(y != 0, "Homogenous normalization of yama::vector2_t with zero y"); 328 | x /= y; 329 | y = 1; 330 | } 331 | 332 | vector2_t reflection(const vector2_t& normal) const 333 | { 334 | YAMA_ASSERT_WARN(normal.is_normalized(), "Reflecting with a non-normalized normal yama::vector2_t"); 335 | auto dd = 2 * dot(*this, normal); 336 | return coord(x - dd * normal.x, y - dd * normal.y); 337 | } 338 | 339 | vector2_t get_orthogonal() const 340 | { 341 | YAMA_ASSERT_WARN(!close(*this, zero()), "finding an orthogonal of a zero vector2_t"); 342 | return coord(-y, x); 343 | } 344 | 345 | constexpr value_type product() const 346 | { 347 | return x * y; 348 | } 349 | 350 | constexpr value_type sum() const 351 | { 352 | return x + y; 353 | } 354 | }; 355 | 356 | template 357 | vector2_t operator+(const vector2_t& a, const vector2_t& b) 358 | { 359 | return vector2_t::coord(a.x + b.x, a.y + b.y); 360 | } 361 | 362 | template 363 | vector2_t operator-(const vector2_t& a, const vector2_t& b) 364 | { 365 | return vector2_t::coord(a.x - b.x, a.y - b.y); 366 | } 367 | 368 | template 369 | vector2_t operator*(const vector2_t& a, const T& s) 370 | { 371 | return vector2_t::coord(a.x * s, a.y * s); 372 | } 373 | 374 | template 375 | vector2_t operator*(const T& s, const vector2_t& b) 376 | { 377 | return vector2_t::coord(s * b.x, s * b.y); 378 | } 379 | 380 | template 381 | vector2_t operator/(const vector2_t& a, const T& s) 382 | { 383 | YAMA_ASSERT_WARN(s != 0, "yama::vector2_t division by zero"); 384 | return vector2_t::coord(a.x / s, a.y / s); 385 | } 386 | 387 | template 388 | vector2_t operator/(const T& s, const vector2_t& b) 389 | { 390 | return vector2_t::coord(s / b.x, s / b.y); 391 | } 392 | 393 | template 394 | bool operator==(const vector2_t& a, const vector2_t& b) 395 | { 396 | return a.x == b.x && a.y == b.y; 397 | } 398 | 399 | template 400 | bool operator!=(const vector2_t& a, const vector2_t& b) 401 | { 402 | return a.x != b.x || a.y != b.y; 403 | } 404 | 405 | template 406 | bool close(const vector2_t& a, const vector2_t& b, const T& epsilon = constants_t::EPSILON) 407 | { 408 | return close(a.x, b.x, epsilon) && close(a.y, b.y, epsilon); 409 | } 410 | 411 | template 412 | vector2_t abs(const vector2_t& a) 413 | { 414 | return vector2_t::coord(std::abs(a.x), std::abs(a.y)); 415 | } 416 | 417 | template 418 | vector2_t mul(const vector2_t& a, const vector2_t& b) 419 | { 420 | return vector2_t::coord(a.x * b.x, a.y * b.y); 421 | } 422 | 423 | template 424 | vector2_t div(const vector2_t& a, const vector2_t& b) 425 | { 426 | YAMA_ASSERT_WARN(b.x != 0, "yama::vector2_t division by zero"); 427 | YAMA_ASSERT_WARN(b.y != 0, "yama::vector2_t division by zero"); 428 | return vector2_t::coord(a.x / b.x, a.y / b.y); 429 | } 430 | 431 | template 432 | typename std::enable_if::value, 433 | vector2_t>::type mod(const vector2_t& n, const vector2_t& d) 434 | { 435 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector2_t division by zero"); 436 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector2_t division by zero"); 437 | return vector2_t::coord(fmod(n.x, d.x), fmod(n.y, d.y)); 438 | } 439 | 440 | template 441 | typename std::enable_if::value, 442 | vector2_t>::type mod(const vector2_t& n, const vector2_t& d) 443 | { 444 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector2_t division by zero"); 445 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector2_t division by zero"); 446 | return vector2_t::coord(n.x % d.x, n.y % d.y); 447 | } 448 | 449 | template 450 | vector2_t floor(const vector2_t& a) 451 | { 452 | return vector2_t::coord(::std::floor(a.x), ::std::floor(a.y)); 453 | } 454 | 455 | template 456 | vector2_t ceil(const vector2_t& a) 457 | { 458 | return vector2_t::coord(::std::ceil(a.x), ::std::ceil(a.y)); 459 | } 460 | 461 | template 462 | vector2_t round(const vector2_t& a) 463 | { 464 | return vector2_t::coord(::std::round(a.x), ::std::round(a.y)); 465 | } 466 | 467 | 468 | template 469 | vector2_t frac(const vector2_t& a) 470 | { 471 | auto x = ::std::abs(a.x); 472 | auto y = ::std::abs(a.y); 473 | return vector2_t::coord(x - ::std::floor(x), y - ::std::floor(y)); 474 | } 475 | 476 | template 477 | bool isfinite(const vector2_t& a) 478 | { 479 | return std::isfinite(a.x) && std::isfinite(a.y); 480 | } 481 | 482 | template 483 | vector2_t sign(const vector2_t& a) 484 | { 485 | return vector2_t::coord(sign(a.x), sign(a.y)); 486 | } 487 | 488 | template 489 | vector2_t clamp(const vector2_t& v, const vector2_t& min, const vector2_t& max) 490 | { 491 | return vector2_t::coord(clamp(v.x, min.x, max.x), clamp(v.y, min.y, max.y)); 492 | } 493 | 494 | #if !defined(min) 495 | template 496 | vector2_t min(const vector2_t& a, const vector2_t& b) 497 | { 498 | return vector2_t::coord(std::min(a.x, b.x), std::min(a.y, b.y)); 499 | } 500 | #endif 501 | 502 | #if !defined(max) 503 | template 504 | vector2_t max(const vector2_t& a, const vector2_t& b) 505 | { 506 | return vector2_t::coord(std::max(a.x, b.x), std::max(a.y, b.y)); 507 | } 508 | #endif 509 | 510 | template 511 | T dot(const vector2_t& a, const vector2_t& b) 512 | { 513 | return a.x * b.x + a.y * b.y; 514 | } 515 | 516 | template 517 | T cross_magnitude(const vector2_t& a, const vector2_t& b) 518 | { 519 | return a.x * b.y - a.y * b.x; 520 | } 521 | 522 | template 523 | T distance_sq(const vector2_t& a, const vector2_t& b) 524 | { 525 | return sq(a.x - b.x) + sq(a.y - b.y); 526 | } 527 | 528 | template 529 | T distance(const vector2_t& a, const vector2_t& b) 530 | { 531 | return std::sqrt(distance_sq(a, b)); 532 | } 533 | 534 | template 535 | vector2_t normalize(const vector2_t& a) 536 | { 537 | auto l = a.length(); 538 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector2_t"); 539 | return vector2_t::coord(a.x / l, a.y / l); 540 | } 541 | 542 | template 543 | bool orthogonal(const vector2_t& a, const vector2_t& b) 544 | { 545 | return close(dot(a, b), T(0)); 546 | } 547 | 548 | template 549 | bool collinear(const vector2_t& a, const vector2_t& b) 550 | { 551 | if (close(b.x, T(0))) 552 | { 553 | if (!close(a.x, T(0))) 554 | { 555 | return false; 556 | } 557 | 558 | if (close(b.y, T(0))) 559 | { 560 | return false; 561 | } 562 | 563 | return true; 564 | } 565 | else if (close(b.y, T(0))) 566 | { 567 | if (close(a.x, T(0))) 568 | { 569 | return false; 570 | } 571 | 572 | return close(a.y, T(0)); 573 | } 574 | else 575 | { 576 | return close(a.x / b.x, a.y / b.y); 577 | } 578 | } 579 | 580 | // type traits 581 | template 582 | struct is_yama> : public std::true_type {}; 583 | 584 | template 585 | struct is_vector> : public std::true_type {}; 586 | 587 | // casts 588 | template 589 | V2_U vector_cast(const vector2_t& v) 590 | { 591 | using U = typename V2_U::value_type; 592 | return {U(v.x), U(v.y)}; 593 | } 594 | 595 | // shorthand 596 | #if !defined(YAMA_NO_SHORTHAND) 597 | 598 | using vector2 = vector2_t; 599 | using point2 = vector2; 600 | 601 | constexpr vector2 v(preferred_type x, preferred_type y) 602 | { 603 | return vector2::coord(x, y); 604 | } 605 | 606 | template 607 | constexpr vector2_t vt(const T& x, const T& y) 608 | { 609 | return vector2_t::coord(x, y); 610 | } 611 | 612 | #endif 613 | 614 | } 615 | -------------------------------------------------------------------------------- /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 | constexpr vector3_t swizzle(size_type sx, size_type sy, size_type sz) const { 169 | YAMA_ASSERT_CRIT(sx < value_count, "yama::vector3_t swizzle index out of range"); 170 | YAMA_ASSERT_CRIT(sy < value_count, "yama::vector3_t swizzle index out of range"); 171 | YAMA_ASSERT_CRIT(sz < value_count, "yama::vector3_t swizzle index out of range"); 172 | return coord(at(sx), at(sy), at(sz)); 173 | } 174 | 175 | template 176 | constexpr vector3_t swizzle(const vector3_t& s) const { 177 | return swizzle(size_type(s.x), size_type(s.y), size_type(s.z)); 178 | } 179 | 180 | /////////////////////////// 181 | // std 182 | 183 | iterator begin() 184 | { 185 | return data(); 186 | } 187 | 188 | iterator end() 189 | { 190 | return data() + value_count; 191 | } 192 | 193 | const_iterator begin() const 194 | { 195 | return data(); 196 | } 197 | 198 | const_iterator end() const 199 | { 200 | return data() + value_count; 201 | } 202 | 203 | value_type& front() 204 | { 205 | return at(0); 206 | } 207 | 208 | value_type& back() 209 | { 210 | return at(value_count - 1); 211 | } 212 | 213 | constexpr const value_type& front() const 214 | { 215 | return at(0); 216 | } 217 | 218 | constexpr const value_type& back() const 219 | { 220 | return at(value_count - 1); 221 | } 222 | 223 | reverse_iterator rbegin() 224 | { 225 | return reverse_iterator(end()); 226 | } 227 | 228 | const_reverse_iterator rbegin() const 229 | { 230 | return const_reverse_iterator(end()); 231 | } 232 | 233 | reverse_iterator rend() 234 | { 235 | return reverse_iterator(begin()); 236 | } 237 | 238 | const_reverse_iterator rend() const 239 | { 240 | return const_reverse_iterator(begin()); 241 | } 242 | 243 | const_iterator cbegin() const { return begin(); } 244 | const_iterator cend() const { return end(); } 245 | const_reverse_iterator crbegin() const { return rbegin(); } 246 | const_reverse_iterator crend() const { return rend(); } 247 | 248 | /////////////////////////////////////////////////////////////////////////// 249 | // arithmetic 250 | 251 | constexpr const vector3_t& operator+() const 252 | { 253 | return *this; 254 | } 255 | 256 | constexpr vector3_t operator-() const 257 | { 258 | return coord(-x, -y, -z); 259 | } 260 | 261 | vector3_t& operator+=(const vector3_t& b) 262 | { 263 | x += b.x; 264 | y += b.y; 265 | z += b.z; 266 | return *this; 267 | } 268 | 269 | vector3_t& operator-=(const vector3_t& b) 270 | { 271 | x -= b.x; 272 | y -= b.y; 273 | z -= b.z; 274 | return *this; 275 | } 276 | 277 | vector3_t& operator*=(const value_type& s) 278 | { 279 | x *= s; 280 | y *= s; 281 | z *= s; 282 | return *this; 283 | } 284 | 285 | vector3_t& operator/=(const value_type& s) 286 | { 287 | YAMA_ASSERT_WARN(s != 0, "yama::vector3_t division by zero"); 288 | x /= s; 289 | y /= s; 290 | z /= s; 291 | return *this; 292 | } 293 | 294 | vector3_t& mul(const vector3_t& b) 295 | { 296 | x *= b.x; 297 | y *= b.y; 298 | z *= b.z; 299 | return *this; 300 | } 301 | 302 | vector3_t& div(const vector3_t& b) 303 | { 304 | x /= b.x; 305 | y /= b.y; 306 | z /= b.z; 307 | return *this; 308 | } 309 | 310 | constexpr value_type length_sq() const 311 | { 312 | return sq(x) + sq(y) + sq(z); 313 | } 314 | 315 | value_type length() const 316 | { 317 | return std::sqrt(length_sq()); 318 | } 319 | 320 | constexpr value_type manhattan_length() const 321 | { 322 | return std::abs(x) + std::abs(y) + std::abs(z); 323 | } 324 | 325 | value_type normalize() 326 | { 327 | auto l = length(); 328 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector3_t"); 329 | x /= l; 330 | y /= l; 331 | z /= l; 332 | return l; 333 | } 334 | 335 | bool is_normalized() const 336 | { 337 | return close(length(), value_type(1)); 338 | } 339 | 340 | void homogenous_normalize() 341 | { 342 | YAMA_ASSERT_WARN(z != 0, "Homogenous normalization of yama::vector3_t with zero y"); 343 | x /= z; 344 | y /= z; 345 | z = 1; 346 | } 347 | 348 | vector3_t reflection(const vector3_t& normal) const 349 | { 350 | YAMA_ASSERT_WARN(normal.is_normalized(), "Reflecting with a non-normalized normal yama::vector3_t"); 351 | auto dd = 2 * dot(*this, normal); 352 | return coord(x - dd * normal.x, y - dd * normal.y, z - dd * normal.z); 353 | } 354 | 355 | vector3_t get_orthogonal() const 356 | { 357 | size_t non_zeros = 0; 358 | size_t non_zero_index = 0; 359 | 360 | for (size_t i = 0; i<3; ++i) 361 | { 362 | if (std::abs(at(i)) > constants_t::EPSILON) 363 | { 364 | ++non_zeros; 365 | non_zero_index = i; 366 | } 367 | } 368 | 369 | YAMA_ASSERT_WARN(non_zeros, "finding an orthogonal of a zero vector3"); 370 | 371 | if (non_zeros >= 2) 372 | { 373 | return vector3_t::coord(y * z / 2, x * z / 2, -x * y); 374 | } 375 | else 376 | { 377 | auto ret = vector3_t::zero(); 378 | 379 | ret.at((non_zero_index + 1) % 3) = this->at(non_zero_index); 380 | return ret; 381 | } 382 | } 383 | 384 | constexpr value_type product() const 385 | { 386 | return x * y * z; 387 | } 388 | 389 | constexpr value_type sum() const 390 | { 391 | return x + y + z; 392 | } 393 | }; 394 | 395 | template 396 | vector3_t operator+(const vector3_t& a, const vector3_t& b) 397 | { 398 | return vector3_t::coord(a.x + b.x, a.y + b.y, a.z + b.z); 399 | } 400 | 401 | template 402 | vector3_t operator-(const vector3_t& a, const vector3_t& b) 403 | { 404 | return vector3_t::coord(a.x - b.x, a.y - b.y, a.z - b.z); 405 | } 406 | 407 | template 408 | vector3_t operator*(const vector3_t& a, const T& s) 409 | { 410 | return vector3_t::coord(a.x * s, a.y * s, a.z * s); 411 | } 412 | 413 | template 414 | vector3_t operator*(const T& s, const vector3_t& b) 415 | { 416 | return vector3_t::coord(s * b.x, s * b.y, s * b.z); 417 | } 418 | 419 | template 420 | vector3_t operator/(const vector3_t& a, const T& s) 421 | { 422 | YAMA_ASSERT_WARN(s != 0, "yama::vector3_t division by zero"); 423 | return vector3_t::coord(a.x / s, a.y / s, a.z / s); 424 | } 425 | 426 | template 427 | vector3_t operator/(const T& s, const vector3_t& b) 428 | { 429 | return vector3_t::coord(s / b.x, s / b.y, s / b.z); 430 | } 431 | 432 | template 433 | bool operator==(const vector3_t& a, const vector3_t& b) 434 | { 435 | return a.x == b.x && a.y == b.y && a.z == b.z; 436 | } 437 | 438 | template 439 | bool operator!=(const vector3_t& a, const vector3_t& b) 440 | { 441 | return a.x != b.x || a.y != b.y || a.z != b.z; 442 | } 443 | 444 | template 445 | bool close(const vector3_t& a, const vector3_t& b, const T& epsilon = constants_t::EPSILON) 446 | { 447 | return close(a.x, b.x, epsilon) && close(a.y, b.y, epsilon) && close(a.z, b.z, epsilon); 448 | } 449 | 450 | template 451 | vector3_t abs(const vector3_t& a) 452 | { 453 | return vector3_t::coord(std::abs(a.x), std::abs(a.y), std::abs(a.z)); 454 | } 455 | 456 | template 457 | vector3_t mul(const vector3_t& a, const vector3_t& b) 458 | { 459 | return vector3_t::coord(a.x * b.x, a.y * b.y, a.z * b.z); 460 | } 461 | 462 | template 463 | vector3_t div(const vector3_t& a, const vector3_t& b) 464 | { 465 | YAMA_ASSERT_WARN(b.x != 0, "yama::vector3_t division by zero"); 466 | YAMA_ASSERT_WARN(b.y != 0, "yama::vector3_t division by zero"); 467 | YAMA_ASSERT_WARN(b.z != 0, "yama::vector3_t division by zero"); 468 | return vector3_t::coord(a.x / b.x, a.y / b.y, a.z / b.z); 469 | } 470 | 471 | template 472 | typename std::enable_if::value, 473 | vector3_t>::type mod(const vector3_t& n, const vector3_t& d) 474 | { 475 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector3_t division by zero"); 476 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector3_t division by zero"); 477 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector3_t division by zero"); 478 | return vector3_t::coord(fmod(n.x, d.x), fmod(n.y, d.y), fmod(n.z, d.z)); 479 | } 480 | 481 | template 482 | typename std::enable_if::value, 483 | vector3_t>::type mod(const vector3_t& n, const vector3_t& d) 484 | { 485 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector3_t division by zero"); 486 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector3_t division by zero"); 487 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector3_t division by zero"); 488 | return vector3_t::coord(n.x % d.x, n.y % d.y, n.z % d.z); 489 | } 490 | 491 | template 492 | vector3_t floor(const vector3_t& a) 493 | { 494 | return vector3_t::coord(::std::floor(a.x), ::std::floor(a.y), ::std::floor(a.z)); 495 | } 496 | 497 | template 498 | vector3_t ceil(const vector3_t& a) 499 | { 500 | return vector3_t::coord(::std::ceil(a.x), ::std::ceil(a.y), ::std::ceil(a.z)); 501 | } 502 | 503 | template 504 | vector3_t round(const vector3_t& a) 505 | { 506 | return vector3_t::coord(::std::round(a.x), ::std::round(a.y), ::std::round(a.z)); 507 | } 508 | 509 | 510 | template 511 | vector3_t frac(const vector3_t& a) 512 | { 513 | auto x = ::std::abs(a.x); 514 | auto y = ::std::abs(a.y); 515 | auto z = ::std::abs(a.z); 516 | return vector3_t::coord(x - ::std::floor(x), y - ::std::floor(y), z - ::std::floor(z)); 517 | } 518 | 519 | template 520 | bool isfinite(const vector3_t& a) 521 | { 522 | return std::isfinite(a.x) && std::isfinite(a.y) && std::isfinite(a.z); 523 | } 524 | 525 | template 526 | vector3_t sign(const vector3_t& a) 527 | { 528 | return vector3_t::coord(sign(a.x), sign(a.y), sign(a.z)); 529 | } 530 | 531 | template 532 | vector3_t clamp(const vector3_t& v, const vector3_t& min, const vector3_t& max) 533 | { 534 | 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)); 535 | } 536 | 537 | #if !defined(min) 538 | template 539 | vector3_t min(const vector3_t& a, const vector3_t& b) 540 | { 541 | return vector3_t::coord(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)); 542 | } 543 | #endif 544 | 545 | #if !defined(max) 546 | template 547 | vector3_t max(const vector3_t& a, const vector3_t& b) 548 | { 549 | return vector3_t::coord(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)); 550 | } 551 | #endif 552 | 553 | template 554 | T dot(const vector3_t& a, const vector3_t& b) 555 | { 556 | return a.x * b.x + a.y * b.y + a.z * b.z; 557 | } 558 | 559 | template 560 | vector3_t cross(const vector3_t& a, const vector3_t& b) 561 | { 562 | YAMA_ASSERT_WARN(!close(a, vector3_t::zero()), "Cross product with a zero vector3_t"); 563 | YAMA_ASSERT_WARN(!close(b, vector3_t::zero()), "Cross product with a zero vector3_t"); 564 | return vector3_t::coord( 565 | a.y*b.z - a.z*b.y, 566 | a.z*b.x - a.x*b.z, 567 | a.x*b.y - a.y*b.x 568 | ); 569 | } 570 | 571 | template 572 | T distance_sq(const vector3_t& a, const vector3_t& b) 573 | { 574 | return sq(a.x - b.x) + sq(a.y - b.y) + sq(a.z - b.z); 575 | } 576 | 577 | template 578 | T distance(const vector3_t& a, const vector3_t& b) 579 | { 580 | return std::sqrt(distance_sq(a, b)); 581 | } 582 | 583 | template 584 | vector3_t normalize(const vector3_t& a) 585 | { 586 | auto l = a.length(); 587 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector3_t"); 588 | return vector3_t::coord(a.x / l, a.y / l, a.z / l); 589 | } 590 | 591 | template 592 | bool orthogonal(const vector3_t& a, const vector3_t& b) 593 | { 594 | return close(dot(a, b), T(0)); 595 | } 596 | 597 | template 598 | bool collinear(const vector3_t& a, const vector3_t& b) 599 | { 600 | T div[3]; 601 | int non_zeroes = 0; 602 | 603 | for (int i = 0; i < 3; ++i) 604 | { 605 | if (close(a[i], 0.f)) 606 | { 607 | if (close(b[i], 0.f)) 608 | { 609 | continue; 610 | } 611 | else 612 | { 613 | return false; 614 | } 615 | } 616 | 617 | div[non_zeroes++] = a[i] / b[i]; 618 | } 619 | 620 | for (int i = 1; i < non_zeroes; ++i) 621 | { 622 | if (!close(div[0], div[i])) 623 | { 624 | return false; 625 | } 626 | } 627 | 628 | return true; 629 | } 630 | 631 | // type traits 632 | template 633 | struct is_yama> : public std::true_type {}; 634 | 635 | template 636 | struct is_vector> : public std::true_type {}; 637 | 638 | // casts 639 | template 640 | V3_U vector_cast(const vector3_t& v) 641 | { 642 | using U = typename V3_U::value_type; 643 | return {U(v.x), U(v.y), U(v.z)}; 644 | } 645 | 646 | // shorthand 647 | #if !defined(YAMA_NO_SHORTHAND) 648 | 649 | using vector3 = vector3_t; 650 | using point3 = vector3; 651 | 652 | constexpr vector3 v(preferred_type x, preferred_type y, preferred_type z) 653 | { 654 | return vector3::coord(x, y, z); 655 | } 656 | 657 | template 658 | constexpr vector3_t vt(const T& x, const T& y, const T& z) 659 | { 660 | return vector3_t::coord(x, y, z); 661 | } 662 | 663 | #endif 664 | 665 | } 666 | -------------------------------------------------------------------------------- /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 | constexpr vector4_t swizzle(size_type sx, size_type sy, size_type sz, size_type sw) const { 178 | YAMA_ASSERT_CRIT(sx < value_count, "yama::vector4_t swizzle index out of range"); 179 | YAMA_ASSERT_CRIT(sy < value_count, "yama::vector4_t swizzle index out of range"); 180 | YAMA_ASSERT_CRIT(sz < value_count, "yama::vector4_t swizzle index out of range"); 181 | YAMA_ASSERT_CRIT(sw < value_count, "yama::vector4_t swizzle index out of range"); 182 | return coord(at(sx), at(sy), at(sz), at(sw)); 183 | } 184 | 185 | template 186 | constexpr vector4_t swizzle(const vector4_t& s) const { 187 | return swizzle(size_type(s.x), size_type(s.y), size_type(s.z), size_type(s.w)); 188 | } 189 | 190 | /////////////////////////// 191 | // std 192 | 193 | iterator begin() 194 | { 195 | return data(); 196 | } 197 | 198 | iterator end() 199 | { 200 | return data() + value_count; 201 | } 202 | 203 | const_iterator begin() const 204 | { 205 | return data(); 206 | } 207 | 208 | const_iterator end() const 209 | { 210 | return data() + value_count; 211 | } 212 | 213 | value_type& front() 214 | { 215 | return at(0); 216 | } 217 | 218 | value_type& back() 219 | { 220 | return at(value_count - 1); 221 | } 222 | 223 | constexpr const value_type& front() const 224 | { 225 | return at(0); 226 | } 227 | 228 | constexpr const value_type& back() const 229 | { 230 | return at(value_count - 1); 231 | } 232 | 233 | reverse_iterator rbegin() 234 | { 235 | return reverse_iterator(end()); 236 | } 237 | 238 | const_reverse_iterator rbegin() const 239 | { 240 | return const_reverse_iterator(end()); 241 | } 242 | 243 | reverse_iterator rend() 244 | { 245 | return reverse_iterator(begin()); 246 | } 247 | 248 | const_reverse_iterator rend() const 249 | { 250 | return const_reverse_iterator(begin()); 251 | } 252 | 253 | const_iterator cbegin() const { return begin(); } 254 | const_iterator cend() const { return end(); } 255 | const_reverse_iterator crbegin() const { return rbegin(); } 256 | const_reverse_iterator crend() const { return rend(); } 257 | 258 | /////////////////////////////////////////////////////////////////////////// 259 | // arithmetic 260 | 261 | constexpr const vector4_t& operator+() const 262 | { 263 | return *this; 264 | } 265 | 266 | constexpr vector4_t operator-() const 267 | { 268 | return coord(-x, -y, -z, -w); 269 | } 270 | 271 | vector4_t& operator+=(const vector4_t& b) 272 | { 273 | x += b.x; 274 | y += b.y; 275 | z += b.z; 276 | w += b.w; 277 | return *this; 278 | } 279 | 280 | vector4_t& operator-=(const vector4_t& b) 281 | { 282 | x -= b.x; 283 | y -= b.y; 284 | z -= b.z; 285 | w -= b.w; 286 | return *this; 287 | } 288 | 289 | vector4_t& operator*=(const value_type& s) 290 | { 291 | x *= s; 292 | y *= s; 293 | z *= s; 294 | w *= s; 295 | return *this; 296 | } 297 | 298 | vector4_t& operator/=(const value_type& s) 299 | { 300 | YAMA_ASSERT_WARN(s != 0, "yama::vector4_t division by zero"); 301 | x /= s; 302 | y /= s; 303 | z /= s; 304 | w /= s; 305 | return *this; 306 | } 307 | 308 | vector4_t& mul(const vector4_t& b) 309 | { 310 | x *= b.x; 311 | y *= b.y; 312 | z *= b.z; 313 | w *= b.w; 314 | return *this; 315 | } 316 | 317 | vector4_t& div(const vector4_t& b) 318 | { 319 | x /= b.x; 320 | y /= b.y; 321 | z /= b.z; 322 | w /= b.w; 323 | return *this; 324 | } 325 | 326 | constexpr value_type length_sq() const 327 | { 328 | return sq(x) + sq(y) + sq(z) + sq(w); 329 | } 330 | 331 | value_type length() const 332 | { 333 | return std::sqrt(length_sq()); 334 | } 335 | 336 | constexpr value_type manhattan_length() const 337 | { 338 | return std::abs(x) + std::abs(y) + std::abs(z) + std::abs(w); 339 | } 340 | 341 | value_type normalize() 342 | { 343 | auto l = length(); 344 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector4_t"); 345 | x /= l; 346 | y /= l; 347 | z /= l; 348 | w /= l; 349 | return l; 350 | } 351 | 352 | bool is_normalized() const 353 | { 354 | return close(length(), value_type(1)); 355 | } 356 | 357 | void homogenous_normalize() 358 | { 359 | YAMA_ASSERT_WARN(w != 0, "Homogenous normalization of yama::vector4_t with zero y"); 360 | x /= w; 361 | y /= w; 362 | z /= w; 363 | w = 1; 364 | } 365 | 366 | vector4_t reflection(const vector4_t& normal) const 367 | { 368 | YAMA_ASSERT_WARN(normal.is_normalized(), "Reflecting with a non-normalized normal yama::vector4_t"); 369 | auto dd = 2 * dot(*this, normal); 370 | return coord(x - dd * normal.x, y - dd * normal.y, z - dd * normal.z, w - dd * normal.w); 371 | } 372 | 373 | constexpr value_type product() const 374 | { 375 | return x * y * z * w; 376 | } 377 | 378 | constexpr value_type sum() const 379 | { 380 | return x + y + z + w; 381 | } 382 | }; 383 | 384 | template 385 | vector4_t operator+(const vector4_t& a, const vector4_t& b) 386 | { 387 | return vector4_t::coord(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); 388 | } 389 | 390 | template 391 | vector4_t operator-(const vector4_t& a, const vector4_t& b) 392 | { 393 | return vector4_t::coord(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); 394 | } 395 | 396 | template 397 | vector4_t operator*(const vector4_t& a, const T& s) 398 | { 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 | vector4_t operator/(const vector4_t& a, const T& s) 410 | { 411 | YAMA_ASSERT_WARN(s != 0, "yama::vector4_t division by zero"); 412 | return vector4_t::coord(a.x / s, a.y / s, a.z / s, a.w / (s)); 413 | } 414 | 415 | template 416 | vector4_t operator/(const T& s, const vector4_t& b) 417 | { 418 | return vector4_t::coord(s / b.x, s / b.y, s / b.z, s / b.w); 419 | } 420 | 421 | template 422 | bool operator==(const vector4_t& a, const vector4_t& b) 423 | { 424 | return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; 425 | } 426 | 427 | template 428 | bool operator!=(const vector4_t& a, const vector4_t& b) 429 | { 430 | return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; 431 | } 432 | 433 | template 434 | bool close(const vector4_t& a, const vector4_t& b, const T& epsilon = constants_t::EPSILON) 435 | { 436 | 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); 437 | } 438 | 439 | template 440 | vector4_t abs(const vector4_t& a) 441 | { 442 | return vector4_t::coord(std::abs(a.x), std::abs(a.y), std::abs(a.z), std::abs(a.w)); 443 | } 444 | 445 | template 446 | vector4_t mul(const vector4_t& a, const vector4_t& b) 447 | { 448 | return vector4_t::coord(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); 449 | } 450 | 451 | template 452 | vector4_t div(const vector4_t& a, const vector4_t& b) 453 | { 454 | YAMA_ASSERT_WARN(b.x != 0, "yama::vector4_t division by zero"); 455 | YAMA_ASSERT_WARN(b.y != 0, "yama::vector4_t division by zero"); 456 | YAMA_ASSERT_WARN(b.z != 0, "yama::vector4_t division by zero"); 457 | YAMA_ASSERT_WARN(b.w != 0, "yama::vector4_t division by zero"); 458 | return vector4_t::coord(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); 459 | } 460 | 461 | template 462 | typename std::enable_if::value, 463 | vector4_t>::type mod(const vector4_t& n, const vector4_t& d) 464 | { 465 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector4_t division by zero"); 466 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector4_t division by zero"); 467 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector4_t division by zero"); 468 | YAMA_ASSERT_WARN(d.w != 0, "yama::vector4_t division by zero"); 469 | return vector4_t::coord(fmod(n.x, d.x), fmod(n.y, d.y), fmod(n.z, d.z), fmod(n.w, d.w)); 470 | } 471 | 472 | template 473 | typename std::enable_if::value, 474 | vector4_t>::type mod(const vector4_t& n, const vector4_t& d) 475 | { 476 | YAMA_ASSERT_WARN(d.x != 0, "yama::vector4_t division by zero"); 477 | YAMA_ASSERT_WARN(d.y != 0, "yama::vector4_t division by zero"); 478 | YAMA_ASSERT_WARN(d.z != 0, "yama::vector4_t division by zero"); 479 | YAMA_ASSERT_WARN(d.w != 0, "yama::vector4_t division by zero"); 480 | 481 | return vector4_t::coord(n.x % d.x, n.y % d.y, n.z % d.z, n.w % d.w); 482 | } 483 | 484 | template 485 | vector4_t floor(const vector4_t& a) 486 | { 487 | return vector4_t::coord(::std::floor(a.x), ::std::floor(a.y), ::std::floor(a.z), ::std::floor(a.w)); 488 | } 489 | 490 | template 491 | vector4_t ceil(const vector4_t& a) 492 | { 493 | return vector4_t::coord(::std::ceil(a.x), ::std::ceil(a.y), ::std::ceil(a.z), ::std::ceil(a.w)); 494 | } 495 | 496 | template 497 | vector4_t round(const vector4_t& a) 498 | { 499 | return vector4_t::coord(::std::round(a.x), ::std::round(a.y), ::std::round(a.z), ::std::round(a.w)); 500 | } 501 | 502 | 503 | template 504 | vector4_t frac(const vector4_t& a) 505 | { 506 | auto x = ::std::abs(a.x); 507 | auto y = ::std::abs(a.y); 508 | auto z = ::std::abs(a.z); 509 | auto w = ::std::abs(a.w); 510 | return vector4_t::coord(x - ::std::floor(x), y - ::std::floor(y), z - ::std::floor(z), w - ::std::floor(w)); 511 | } 512 | 513 | template 514 | bool isfinite(const vector4_t& a) 515 | { 516 | return std::isfinite(a.x) && std::isfinite(a.y) && std::isfinite(a.z) && std::isfinite(a.w); 517 | } 518 | 519 | template 520 | vector4_t sign(const vector4_t& a) 521 | { 522 | return vector4_t::coord(sign(a.x), sign(a.y), sign(a.z), sign(a.w)); 523 | } 524 | 525 | template 526 | vector4_t clamp(const vector4_t& v, const vector4_t& min, const vector4_t& max) 527 | { 528 | 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)); 529 | } 530 | 531 | #if !defined(min) 532 | template 533 | vector4_t min(const vector4_t& a, const vector4_t& b) 534 | { 535 | 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)); 536 | } 537 | #endif 538 | 539 | #if !defined(max) 540 | template 541 | vector4_t max(const vector4_t& a, const vector4_t& b) 542 | { 543 | 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)); 544 | } 545 | #endif 546 | 547 | template 548 | T dot(const vector4_t& a, const vector4_t& b) 549 | { 550 | return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; 551 | } 552 | 553 | template 554 | T distance_sq(const vector4_t& a, const vector4_t& b) 555 | { 556 | return sq(a.x - b.x) + sq(a.y - b.y) + sq(a.z - b.z) + sq(a.w - b.w); 557 | } 558 | 559 | template 560 | T distance(const vector4_t& a, const vector4_t& b) 561 | { 562 | return std::sqrt(distance_sq(a, b)); 563 | } 564 | 565 | template 566 | vector4_t normalize(const vector4_t& a) 567 | { 568 | auto l = a.length(); 569 | YAMA_ASSERT_WARN(l, "Normalizing zero-length yama::vector4_t"); 570 | return vector4_t::coord(a.x / l, a.y / l, a.z / l, a.w / l); 571 | } 572 | 573 | template 574 | bool orthogonal(const vector4_t& a, const vector4_t& b) 575 | { 576 | return close(dot(a, b), T(0)); 577 | } 578 | 579 | template 580 | bool collinear(const vector4_t& a, const vector4_t& b) 581 | { 582 | T div[4]; 583 | int non_zeroes = 0; 584 | 585 | for (int i = 0; i < 4; ++i) 586 | { 587 | if (close(a[i], 0.f)) 588 | { 589 | if (close(b[i], 0.f)) 590 | { 591 | continue; 592 | } 593 | else 594 | { 595 | return false; 596 | } 597 | } 598 | 599 | div[non_zeroes++] = a[i] / b[i]; 600 | } 601 | 602 | for (int i = 1; i < non_zeroes; ++i) 603 | { 604 | if (!close(div[0], div[i])) 605 | { 606 | return false; 607 | } 608 | } 609 | 610 | return true; 611 | } 612 | 613 | // type traits 614 | template 615 | struct is_yama> : public std::true_type {}; 616 | 617 | template 618 | struct is_vector> : public std::true_type {}; 619 | 620 | // casts 621 | template 622 | V4_U vector_cast(const vector4_t& v) 623 | { 624 | using U = typename V4_U::value_type; 625 | return {U(v.x), U(v.y), U(v.z), U(v.w)}; 626 | } 627 | 628 | // shorthand 629 | #if !defined(YAMA_NO_SHORTHAND) 630 | 631 | using vector4 = vector4_t; 632 | using point4 = vector4; 633 | 634 | constexpr vector4 v(preferred_type x, preferred_type y, preferred_type z, preferred_type w) 635 | { 636 | return vector4::coord(x, y, z, w); 637 | } 638 | 639 | template 640 | constexpr vector4_t vt(const T& x, const T& y, const T& z, const T& w) 641 | { 642 | return vector4_t::coord(x, y, z, w); 643 | } 644 | 645 | #endif 646 | 647 | } 648 | -------------------------------------------------------------------------------- /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, 0)); 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, 0)); 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 | 51 | TEST_CASE("swizzle") { 52 | auto v3 = v(1, 2, 3); 53 | CHECK(v3.swizzle(0, 1, 2) == v(1, 2, 3)); 54 | CHECK(v3.swizzle(2, 1, 0) == v(3, 2, 1)); 55 | CHECK(v3.swizzle(1, 2, 2) == v(2, 3, 3)); 56 | CHECK(v3.swizzle(vector3_t{2, 1, 0}) == v(3, 2, 1)); 57 | auto v4 = v(4, 5, 6, 7); 58 | CHECK(v4.swizzle(0, 1, 2, 3) == v(4, 5, 6, 7)); 59 | CHECK(v4.swizzle(3, 2, 1, 0) == v(7, 6, 5, 4)); 60 | CHECK(v4.swizzle(1, 0, 3, 1) == v(5, 4, 7, 5)); 61 | CHECK(v4.swizzle(vector4_t{3, 2, 1, 0}) == v(7, 6, 5, 4)); 62 | 63 | } --------------------------------------------------------------------------------