├── .clang-format ├── .gitignore ├── .sonarcloud.properties ├── .travis.yml ├── CMakeLists.txt ├── CONTRIBUTING.md ├── Doxyfile ├── LICENSE ├── README.md ├── appveyor.yml ├── c_src ├── CMakeLists.txt ├── klein.h └── klein_c.cpp ├── cmake └── klein-config.cmake ├── docs ├── api │ ├── dir.md │ ├── dot.md │ ├── dual.md │ ├── dualn.md │ ├── exp_log.md │ ├── ext.md │ ├── gp.md │ ├── lines.md │ ├── motor.md │ ├── plane.md │ ├── point.md │ ├── proj.md │ ├── reg.md │ ├── rotor.md │ └── translator.md ├── case_studies │ └── ga_skeletal_animation.md ├── css │ └── extra.css ├── discord.md ├── geometry-potpourri.md ├── gpu.md ├── img │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── site.webmanifest │ └── tpose.png ├── index.md ├── js │ ├── extra.js │ └── tutorial.js ├── overview.md ├── perf.md ├── quickstart.md ├── references.md ├── shell.md └── tutorial │ ├── exterior_algebra.md │ └── intro.md ├── glsl └── klein.glsl ├── mkdocs.yml ├── perf ├── CMakeLists.txt ├── glm_perf.cpp ├── klein_perf.cpp └── rtm_perf.cpp ├── public └── klein │ ├── detail │ ├── exp_log.hpp │ ├── exterior_product.hpp │ ├── geometric_product.hpp │ ├── inner_product.hpp │ ├── matrix.hpp │ ├── sandwich.hpp │ ├── sse.hpp │ └── x86 │ │ ├── x86_exp_log.hpp │ │ ├── x86_exterior_product.hpp │ │ ├── x86_geometric_product.hpp │ │ ├── x86_inner_product.hpp │ │ ├── x86_matrix.hpp │ │ ├── x86_matrix_cxx11.inl │ │ ├── x86_sandwich.hpp │ │ ├── x86_sandwich_cxx11.inl │ │ └── x86_sse.hpp │ ├── direction.hpp │ ├── dual.hpp │ ├── exp_log.hpp │ ├── geometric_product.hpp │ ├── inner_product.hpp │ ├── join.hpp │ ├── klein.hpp │ ├── line.hpp │ ├── mat3x4.hpp │ ├── mat4x4.hpp │ ├── meet.hpp │ ├── motor.hpp │ ├── plane.hpp │ ├── point.hpp │ ├── projection.hpp │ ├── rotor.hpp │ ├── translator.hpp │ └── util.hpp ├── scripts ├── demo.klein ├── test.klein └── validation.klein ├── sym ├── CMakeLists.txt ├── ga.cpp ├── ga.hpp ├── main.cpp ├── parser.cpp ├── parser.hpp ├── poly.cpp ├── poly.hpp ├── repl.cpp ├── repl.hpp └── test.cpp ├── test ├── CMakeLists.txt ├── glsl_shim.hpp ├── main.cpp ├── test_ep.cpp ├── test_exp_log.cpp ├── test_glsl.cpp ├── test_gp.cpp ├── test_ip.cpp ├── test_metric.cpp ├── test_rp.cpp ├── test_sse.cpp └── test_sw.cpp └── theme └── partials └── footer.html /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -4 2 | AlignAfterOpenBracket: true 3 | AlignConsecutiveAssignments: true 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlinesLeft: true 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: false 8 | AllowShortBlocksOnASingleLine: false 9 | AllowShortCaseLabelsOnASingleLine: false 10 | AllowShortFunctionsOnASingleLine: false 11 | AllowShortIfStatementsOnASingleLine: false 12 | AllowShortLoopsOnASingleLine: false 13 | AlwaysBreakAfterReturnType: None 14 | AlwaysBreakBeforeMultilineStrings: true 15 | AlwaysBreakTemplateDeclarations: true 16 | BinPackArguments: false 17 | BinPackParameters: false 18 | BreakBeforeBraces: Custom 19 | BraceWrapping: 20 | AfterClass: true 21 | AfterControlStatement: true 22 | AfterEnum: true 23 | AfterExternBlock: false 24 | AfterFunction: true 25 | AfterNamespace: true 26 | AfterObjCDeclaration: true 27 | AfterStruct: true 28 | AfterUnion: true 29 | AfterExternBlock: true 30 | BeforeCatch: true 31 | BeforeElse: true 32 | IndentBraces: false 33 | SplitEmptyFunction: false 34 | SplitEmptyRecord: false 35 | SplitEmptyNamespace: false 36 | BreakBeforeBinaryOperators: All 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializers: BeforeComma 39 | BreakStringLiterals: true 40 | ColumnLimit: 80 41 | CommentPragmas: '' 42 | CompactNamespaces: false 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 44 | ConstructorInitializerIndentWidth: 4 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: true 47 | DerivePointerBinding: false 48 | FixNamespaceComments: true 49 | IndentCaseLabels: false 50 | IndentPPDirectives: AfterHash 51 | IndentWidth: 4 52 | IndentWrappedFunctionNames: false 53 | KeepEmptyLinesAtTheStartOfBlocks: false 54 | Language: Cpp 55 | MaxEmptyLinesToKeep: 1 56 | NamespaceIndentation: Inner 57 | PenaltyBreakBeforeFirstCallParameter: 0 58 | PenaltyBreakComment: 0 59 | PenaltyBreakFirstLessLess: 0 60 | PenaltyBreakString: 1 61 | PenaltyExcessCharacter: 10 62 | PenaltyReturnTypeOnItsOwnLine: 20 63 | PointerAlignment: Left 64 | SortIncludes: true 65 | SortUsingDeclarations: true 66 | SpaceAfterTemplateKeyword: true 67 | SpaceBeforeAssignmentOperators: true 68 | SpaceBeforeParens: ControlStatements 69 | SpaceInEmptyParentheses: false 70 | SpacesBeforeTrailingComments: 1 71 | SpacesInAngles: false 72 | SpacesInCStyleCastParentheses: false 73 | SpacesInContainerLiterals: false 74 | SpacesInParentheses: false 75 | SpacesInSquareBrackets: false 76 | Standard: C++11 77 | TabWidth: 4 78 | UseTab: Never 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | 3 | # CMake files 4 | build* 5 | CMakeLists.txt.user 6 | CMakeCache.txt 7 | CMakeFiles 8 | CMakeScripts 9 | Testing 10 | Makefile 11 | cmake_install.cmake 12 | install_manifest.txt 13 | compile_commands.json 14 | CTestTestfile.cmake 15 | _deps 16 | 17 | # Windows files 18 | Thumbs.db 19 | 20 | # MacOS files 21 | .DS_Store 22 | 23 | # Dump file 24 | *.stackdump 25 | 26 | # IDE/editor files 27 | .vscode 28 | *.bak 29 | *.tmp 30 | ipch/ 31 | 32 | # Statically generated documentation 33 | site/ 34 | doxygen/ 35 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | sonar.sources=public 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: false 3 | 4 | # Default to Ubuntu Bionic 5 | os: linux 6 | dist: bionic 7 | 8 | branches: 9 | only: 10 | - master 11 | - coverity_scan 12 | 13 | env: 14 | global: 15 | # COVERITY_SCAN_TOKEN 16 | - secure: 'R+LQViHn4EdJCzE3RU40ynnaSQVE+fTp4ipN8YzSBsIP7oUfTh64nJGyt7PmbAHYzF4dUYy9+KcDDdrF252grrzcLGo3HMrMONxmQ1RJUJnj1brLaGNRw2iwEA1qBm2pznDmOMrbXm7vjIfzVbx7Y7Orv658d85osT9rfWODgkpK9AYDm6SgMZrwpN9RR2Mzie6/hnBkTpmYvLtnwLBi20nkocxnMwMt5SMJ6voGciKqnO5yh77gODO0K7otcwJVruHRZ8ldCxFy3fb3+bGHuf/lYxR0IXlEz+zvCfmLrKQcVK8FGaUr53JJ9G2M+fGmB1UyDQHIktDxnYYQATphHEkLo6LsBc7APczb+l3C3CMAGipq4k7zMwBSCbviPrOOTfCDTNzFd6mF71PIdCu+9T29v8yECYUInRVzZMCj/2HEdlMdZvwWmXndCiP8iWIteTkAOerav2cGFhasB6yivKSgk8Z1l5kpFinnYgCFQkk/qLqD2SIzXA6mKhPPu9cvgi7PFqlSt6GE/pGh+iX4q/huYnDkxZZEwm4M2DgU6RDVzX78e+mQ3rby8varWlaTNjoZ7QKXcsvRPHEeH1EjgvJODgJlyCoU/oEK+VR3OtN4QaxIW3z70+rOlnJ3WHtokhElEotcXE1LSFL0DNPoMC+XIcUsGS1rl2b+jOdNrSA=' 17 | 18 | addons: 19 | coverity_scan: 20 | project: 21 | name: 'jeremyong/Klein' 22 | description: 'Build submitted via Travis CI' 23 | notification_email: jeremycong@gmail.com 24 | build_command_prepend: 'cmake -DENABLE_COVERAGE:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug .' 25 | build_command: 'cmake --build .' 26 | branch_pattern: coverity_scan 27 | 28 | jobs: 29 | include: 30 | # MacOS Clang Debug 31 | - env: 32 | - CXX=clang++ 33 | - CC=clang 34 | - BUILD_TYPE=Debug 35 | os: osx 36 | osx_image: xcode11.3 37 | 38 | # MacOS Clang Release 39 | - env: 40 | - CXX=clang++ 41 | - CC=clang 42 | - BUILD_TYPE=Release 43 | os: osx 44 | osx_image: xcode11.3 45 | 46 | # Bionic Clang-9 Debug 47 | - env: 48 | - CXX=clang++-9 49 | - CC=clang 50 | - BUILD_TYPE=Debug 51 | addons: 52 | apt: 53 | sources: 54 | - sourceline: 'ppa:ubuntu-toolchain-r/test' 55 | - sourceline: 'deb https://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main' 56 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 57 | packages: 58 | - clang-9 59 | - ninja-build 60 | 61 | # Bionic Clang-9 Release 62 | - env: 63 | - CXX=clang++-9 64 | - CC=clang 65 | - BUILD_TYPE=Release 66 | addons: 67 | apt: 68 | sources: 69 | - sourceline: 'ppa:ubuntu-toolchain-r/test' 70 | - sourceline: 'deb https://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main' 71 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 72 | packages: 73 | - clang-9 74 | - ninja-build 75 | 76 | # Bionic GCC-9 Debug 77 | - env: 78 | - CXX=g++-9 79 | - CC=gcc 80 | - BUILD_TYPE=Debug 81 | - ENABLE_GCOV=1 82 | addons: 83 | apt: 84 | sources: 85 | - sourceline: 'ppa:ubuntu-toolchain-r/test' 86 | packages: 87 | - g++-9 88 | - ninja-build 89 | 90 | # Bionic GCC-9 Release 91 | - env: 92 | - CXX=g++-9 93 | - CC=gcc 94 | - BUILD_TYPE=Release 95 | addons: 96 | apt: 97 | sources: 98 | - sourceline: 'ppa:ubuntu-toolchain-r/test' 99 | packages: 100 | - g++-9 101 | - ninja-build 102 | 103 | install: 104 | - | 105 | if [ "${TRAVIS_OS_NAME}" = "osx" ]; then 106 | brew update 107 | brew upgrade cmake 108 | brew install ninja 109 | export CMAKE=cmake 110 | else 111 | mkdir -p ${TRAVIS_BUILD_DIR}/tools 112 | travis_retry wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.16.4/cmake-3.16.4-Linux-x86_64.tar.gz 113 | tar -xf cmake-3.16.4-Linux-x86_64.tar.gz 114 | mv cmake-3.16.4-Linux-x86_64 ${TRAVIS_BUILD_DIR}/tools/cmake 115 | export PATH=${TRAVIS_BUILD_DIR}/tools/cmake/bin:$PATH 116 | export CMAKE=${TRAVIS_BUILD_DIR}/tools/cmake/bin/cmake 117 | fi 118 | - ${CMAKE} --version 119 | - ninja --version 120 | 121 | before_script: 122 | - ${CMAKE} -G Ninja -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DKLEIN_ENABLE_PERF=OFF 123 | - ${CMAKE} --build . 124 | 125 | script: 126 | - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./klein_test && ./klein_test_sse42 && ./klein_test_glsl && ./klein_test_cxx11; fi 127 | - if [ "${ENABLE_GCOV}" = 1 ]; then bash <(curl -s https://codecov.io/bash) -x gcov-9 -a "-s `pwd`"; fi 128 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # USAGE 2 | # Include this project transitively in your cmake project using add_subdirectory 3 | # Alternatively, copy the contents of the public folder somewhere in your include 4 | # path and be sure to compile with the correct architecture flags for your compiler 5 | # Search for "target_compile_options" in this file to see what flags are needed. 6 | 7 | cmake_minimum_required(VERSION 3.15) 8 | 9 | project(klein VERSION 2.3.0 LANGUAGES C CXX) 10 | 11 | # Detect if we are a standalone projection or if we're included transitively 12 | # and set the various option defaults accordingly. By default, tests and utilities 13 | # are not built unless Klein is built as a standalone project 14 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 15 | set(KLEIN_STANDALONE ON) 16 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 17 | option(KLEIN_ENABLE_PERF "Enable downloading external libs for perf analysis" ON) 18 | option(KLEIN_ENABLE_TESTS "Enable compilation of Klein tests" ON) 19 | option(KLEIN_VALIDATE "Enable runtime validations" ON) 20 | option(KLEIN_INSTALL "Create CMake config-file package during install step" ON) 21 | else() 22 | set(KLEIN_STANDALONE OFF) 23 | option(KLEIN_ENABLE_PERF "Enable downloading external libs for perf analysis" OFF) 24 | option(KLEIN_ENABLE_TESTS "Enable compilation of Klein tests" OFF) 25 | option(KLEIN_VALIDATE "Enable runtime validations" OFF) 26 | option(KLEIN_INSTALL "Create CMake config-file package during install step" ON) 27 | endif() 28 | option(KLEIN_ENABLE_SIMDE "Enable simde shim to support non-x86 platforms" OFF) 29 | 30 | option(KLEIN_BUILD_SYM "Enable compilation of symbolic Klein utility" ON) 31 | option(KLEIN_BUILD_C_BINDINGS "Enable compilation of the Klein C bindings" ON) 32 | 33 | include(FetchContent) 34 | FetchContent_Declare( 35 | simde 36 | GIT_REPOSITORY https://github.com/simd-everywhere/simde 37 | GIT_TAG v0.7.2 38 | GIT_SHALLOW ON 39 | ) 40 | FetchContent_GetProperties(simde) 41 | if(NOT simde_POPULATED) 42 | FetchContent_Populate(simde) 43 | endif() 44 | 45 | # The default platform and instruction set is x86 SSE3 46 | add_library(klein INTERFACE) 47 | add_library(klein::klein ALIAS klein) 48 | target_include_directories(klein INTERFACE 49 | $ 50 | $ 51 | $ 52 | ) 53 | if(NOT MSVC) 54 | target_compile_options(klein INTERFACE -msse3) 55 | endif() 56 | if(KLEIN_ENABLE_SIMDE) 57 | target_compile_definitions(klein INTERFACE KLEIN_USE_SIMDE) 58 | endif() 59 | 60 | add_library(klein_cxx11 INTERFACE) 61 | add_library(klein::klein_cxx11 ALIAS klein_cxx11) 62 | target_include_directories(klein_cxx11 INTERFACE 63 | $ 64 | $ 65 | $ 66 | ) 67 | target_compile_features(klein_cxx11 INTERFACE cxx_std_11) 68 | if(NOT MSVC) 69 | target_compile_options(klein_cxx11 INTERFACE -msse3) 70 | endif() 71 | if(KLEIN_ENABLE_SIMDE) 72 | target_compile_definitions(klein_cxx11 INTERFACE KLEIN_USE_SIMDE) 73 | endif() 74 | 75 | add_library(klein_sse42 INTERFACE) 76 | add_library(klein::klein_sse42 ALIAS klein_sse42) 77 | target_include_directories(klein_sse42 INTERFACE 78 | $ 79 | $ 80 | $ 81 | ) 82 | target_compile_features(klein_sse42 INTERFACE cxx_std_17) 83 | # SSE4.1 has > 97% market penetration according to the Steam hardware survey 84 | # queried as of December 2019 while AVX2 is around 70%. Thus, we can assume 85 | # FMA support is at least 70%, but perhaps not much more beyond that. 86 | # TODO: Optionally support FMA 87 | if(MSVC) 88 | # On MSVC, SSE2 enables code generation of SSE2 and later (does not include 89 | # AVX extensions). This is on by default. 90 | else() 91 | # Unlike MSVC, FMA instructions are enabled with a separate feature flag 92 | target_compile_options(klein_sse42 INTERFACE -msse4.1) 93 | target_compile_definitions(klein_sse42 INTERFACE KLEIN_SSE_4_1) 94 | endif() 95 | if(KLEIN_ENABLE_SIMDE) 96 | target_compile_definitions(klein_sse42 INTERFACE KLEIN_USE_SIMDE) 97 | endif() 98 | 99 | if(KLEIN_ENABLE_PERF) 100 | add_subdirectory(perf) 101 | endif() 102 | 103 | if(KLEIN_ENABLE_TESTS) 104 | enable_testing() 105 | 106 | add_subdirectory(test) 107 | endif() 108 | 109 | if(KLEIN_BUILD_SYM) 110 | add_subdirectory(sym) 111 | endif() 112 | 113 | if(KLEIN_BUILD_C_BINDINGS) 114 | # Support dropped for the time being 115 | # add_subdirectory(c_src) 116 | endif() 117 | 118 | if(KLEIN_INSTALL) 119 | 120 | install(TARGETS klein klein_cxx11 klein_sse42 EXPORT klein_targets) 121 | 122 | install(DIRECTORY public/klein TYPE INCLUDE) 123 | 124 | include(CMakePackageConfigHelpers) 125 | write_basic_package_version_file( 126 | "${CMAKE_CURRENT_BINARY_DIR}/klein/klein-config-version.cmake" 127 | VERSION ${CMAKE_PROJECT_VERSION} 128 | COMPATIBILITY AnyNewerVersion 129 | ) 130 | 131 | export(EXPORT klein_targets 132 | FILE "${CMAKE_CURRENT_BINARY_DIR}/klein/klein-targets.cmake" 133 | NAMESPACE klein:: 134 | ) 135 | 136 | configure_file(cmake/klein-config.cmake 137 | "${CMAKE_CURRENT_BINARY_DIR}/klein/klein-config.cmake" 138 | COPYONLY 139 | ) 140 | 141 | set(ConfigPackageLocation share/klein) 142 | install(EXPORT klein_targets 143 | FILE 144 | klein-targets.cmake 145 | NAMESPACE 146 | klein:: 147 | DESTINATION 148 | ${ConfigPackageLocation} 149 | ) 150 | install( 151 | FILES 152 | cmake/klein-config.cmake 153 | "${CMAKE_CURRENT_BINARY_DIR}/klein/klein-config-version.cmake" 154 | DESTINATION 155 | ${ConfigPackageLocation} 156 | ) 157 | 158 | endif() 159 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Klein 2 | 3 | Contributions welcome! Before endeavoring to provide a much appreciated change to 4 | Klein, please read this document to make the process as smooth and as pleasant as 5 | possible. 6 | 7 | ## Guidelines 8 | 9 | 1. Before embarking on a large-scale change or architectural improvement, please 10 | [open an issue](https://github.com/jeremyong/klein/issues) and explain your 11 | motivation, solution, and plan of attack. 12 | 2. If you have questions about how something works or whether something is a 13 | good idea, you're welcome to join our [discord](https://discordapp.com/invite/mdbAgr) 14 | to discuss things and get some quick feedback. 15 | 3. Please adhere to the `git` and `C++` guidelines below, and don't hesitate to 16 | ask questions if the process as outlined here is unclear. 17 | 4. Understand that all contributions made fall under the project's MIT license. 18 | 19 | ## Git Workflow 20 | 21 | 1. A [rebase](https://help.github.com/en/github/using-git/about-git-rebase) workflow 22 | is the preferred method of keeping your fork/branch up to date with changes. This 23 | maintains a clean commit history with less bookkeeping needed to isolate problems 24 | introduced in the context of a linear timeline. 25 | 2. Git messages should be as detailed as necessary to describe the change, with an 26 | imperatively voiced single-line commit title. Refer to 27 | [this article](https://chris.beams.io/posts/git-commit/) for additional exposition. 28 | 3. While a change is under review, don't squash your commits just yet until the review 29 | is finalized. That way, changes that are committed in response to feedback can be 30 | indepedently audited. 31 | 32 | ## C++ Guidelines 33 | 34 | 1. All names are `snake_case` with the exception of macros which are `UPPER_SNAKE_CASE`. 35 | 2. All symbols must reside in the `kln` or `kln::detail` namespaces. 36 | 3. Implicit allocations are forbidden 37 | 4. New functionality should generally be accompanied with suitable test coverage. 38 | 5. If a tradeoff between speed and precision can be made, the fast version is 39 | preferred and a `_precise` fallback can be provided in addition. 40 | 6. Code should abide by the "don't pay for what you don't use" mantra. 41 | 7. A recent `clang-format` must be installed and working on your system so that file 42 | formatting is canonicalized. 43 | 8. For functions that return a value, generally `[[nodiscard]]` should be used. 44 | 9. All methods and functions should be marked `noexcept` as no code in Klein should need 45 | to ever `throw`. 46 | 10. All variables that are potentially unused should be marked with the `[[maybe_unused]]` 47 | attribute. 48 | 11. Instructions leveraging `AVX` or a more recent instruction set must be behind a 49 | preprocessor flag named `KLN_ENABLE_ISE_*` ("enable instruction set extension ..."). 50 | 12. All functions that accept arguments consisting of SIMD registers must be decorated 51 | with `KLN_VEC_CALL` to enable the vector register calling convention. 52 | 13. API documentation is periodically generated from embedded source code comments that 53 | follow a triple-slash (`///`) so please update documentation accordingly. 54 | 14. Be mindful of techniques, idioms, or dependencies that may have an adverse affect on 55 | compile times. While Klein leverages templates to a certain degree, maintaining lean 56 | compilation time is a persistent effort. 57 | 58 | ## Thanks and happy coding! 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeremy Ong 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 | # [Klein](https://jeremyong.com/klein) 2 | 3 | _edit: Project has been temporarily archived as I don't have time to maintain it at the moment. Stay tuned for an updated version of the project in the future._ 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://opensource.org/licenses/MIT) 6 | [![DOI](https://zenodo.org/badge/236777729.svg)](https://zenodo.org/badge/latestdoi/236777729) 7 | 8 | [![Build Status](https://travis-ci.org/jeremyong/klein.svg?branch=master)](https://travis-ci.org/jeremyong/klein) 9 | [![Build Status](https://ci.appveyor.com/api/projects/status/w3ug2ad08jyved8o?svg=true)](https://ci.appveyor.com/project/jeremyong/klein) 10 | [![Coverity Status](https://img.shields.io/coverity/scan/20402.svg)](https://scan.coverity.com/projects/jeremyong-klein) 11 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/5908bd446f3d4bb0bb1fd2e0808cb8a1)](https://www.codacy.com/manual/jeremyong/klein?utm_source=github.com&utm_medium=referral&utm_content=jeremyong/klein&utm_campaign=Badge_Grade) 12 | 13 | 👉👉 [Project Site](https://jeremyong.com/klein) 👈👈 14 | 15 | ## Description 16 | 17 | Do you need to do any of the following? Quickly? _Really_ quickly even? 18 | 19 | - Projecting points onto lines, lines to planes, points to planes? 20 | - Measuring distances and angles between points, lines, and planes? 21 | - Rotate or translate points, lines, and planes? 22 | - Perform smooth rigid body transforms? Interpolate them smoothly? 23 | - Construct lines from points? Planes from points? Planes from a line and a point? 24 | - Intersect planes to form lines? Intersect a planes and lines to form points? 25 | 26 | If so, then Klein is the library for you! 27 | 28 | Klein is an implementation of `P(R*_{3, 0, 1})`, aka 3D Projective Geometric Algebra. 29 | It is designed for applications that demand high-throughput (animation libraries, 30 | kinematic solvers, etc). In contrast to other GA libraries, Klein does not attempt to 31 | generalize the metric or dimensionality of the space. In exchange for this loss of generality, 32 | Klein implements the algebraic operations using the full weight of SSE (Streaming 33 | SIMD Extensions) for maximum throughput. 34 | 35 | ## Requirements 36 | 37 | - Machine with a processor that supports SSE3 or later (Steam hardware survey reports 100% market penetration) 38 | - C++11/14/17 compliant compiler (tested with GCC 9.2.1, Clang 9.0.1, and Visual Studio 2019) 39 | - Optional SSE4.1 support 40 | 41 | ## Usage 42 | 43 | You have two options to use Klein in your codebase. First, you can simply copy the contents of the 44 | `public` folder somewhere in your include path. Alternatively, you can include this entire project 45 | in your source tree, and using cmake, `add_subdirectory(Klein)` and link the `klein::klein` interface 46 | target. 47 | 48 | In your code, there is a single header to include via `#include `, at which point 49 | you can create planes, points, lines, ideal lines, bivectors, motors, directions, and use their 50 | operations. Please refer to the [project site](https://jeremyong.com/klein) for the most up-to-date 51 | documentation. 52 | 53 | ## Motivation 54 | 55 | PGA fully streamlines traditionally used quaternions, and dual-quaternions in a single algebra. 56 | Normally, the onus is on the user to perform appropriate casts and ensure signs and memory layout 57 | are accounted for. Here, all types are unified within the geometric algebra, 58 | and operations such as applying quaternion or dual-quaternions (rotor/motor) to planes, points, 59 | and lines make sense. There is a surprising amount of uniformity in the algebra, which enables 60 | efficient implementation, a simple API, and reduced code size. 61 | 62 | ## Performance Considerations 63 | 64 | It is known that a "better" way to vectorize computation in general is to arrange the data in an SoA 65 | layout to avoid unnecessary cross-lane arithmetic or unnecessary shuffling. PGA is unique in that 66 | a given PGA multivector has a natural decomposition into 4 blocks of 4 floating-point quantities. 67 | For the even sub-algebra (isomorphic to the space of dual-quaternions) also known as the _motor 68 | algebra_, the geometric product can be densely packed and implemented efficiently using SSE. 69 | 70 | ## References 71 | 72 | Klein is deeply indebted to several members of the GA community and their work. Beyond the works 73 | cited here, the author stands of the shoulders of giants (Felix _Klein_, Sophus Lie, Arthur Cayley, 74 | William Rowan Hamilton, Julius Plücker, and William Kingdon Clifford, among others). 75 | 76 | [1] 77 | Gunn, Charles G. (2019). 78 | Course notes Geometric Algebra for Computer Graphics, SIGGRAPH 2019. 79 | [arXiv link](https://arxiv.org/abs/2002.04509) 80 | 81 | [2] 82 | Steven De Keninck and Charles Gunn. (2019). 83 | SIGGRAPH 2019 Geometric Algebra Course. 84 | [youtube link](https://www.youtube.com/watch?v=tX4H_ctggYo) 85 | 86 | [3] 87 | Leo Dorst, Daniel Fontijne, Stephen Mann. (2007) 88 | Geometric Algebra for Computer Science. 89 | Burlington, MA: Morgan Kaufmann Publishers Inc. 90 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | configuration: 6 | - Debug 7 | 8 | clone_depth: 1 9 | 10 | skip_commits: 11 | files: 12 | - scripts/* 13 | - docs/* 14 | - glsl/* 15 | - theme/* 16 | - bench/* 17 | - '*.md' 18 | 19 | init: 20 | - set PATH=C:\cygwin\bin;%PATH% 21 | 22 | image: 23 | - Visual Studio 2019 24 | 25 | platform: 26 | - x64 27 | 28 | build_script: 29 | - cmake --version 30 | - cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=%configuration% -DKLEIN_ENABLE_PERF=OFF 31 | - cmake --build . 32 | - dir 33 | - C:\projects\klein\%configuration%\klein_test.exe 34 | - C:\projects\klein\%configuration%\klein_test_sse42.exe 35 | - C:\projects\klein\%configuration%\klein_test_cxx11.exe 36 | -------------------------------------------------------------------------------- /c_src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Klein C-bindings 2 | 3 | add_library(klein_c klein_c.cpp) 4 | target_link_libraries(klein_c PRIVATE klein) 5 | target_compile_features(klein_c PRIVATE cxx_std_11) -------------------------------------------------------------------------------- /c_src/klein.h: -------------------------------------------------------------------------------- 1 | // File: klein.h 2 | // Purpose: Klein C interface, suitable for binding to other languages 3 | 4 | #pragma once 5 | 6 | #if __cplusplus 7 | extern "C" 8 | { 9 | #endif 10 | 11 | #include 12 | 13 | typedef struct 14 | { 15 | /// LSB (e0, e1, e2, e3) MSB 16 | __m128 p0; 17 | } kln_plane; 18 | 19 | /// For lines, the scalar component and pseudoscalar components should be 20 | /// exactly zero. 21 | typedef struct 22 | { 23 | /// LSB (1, e12, e31, e23) MSB 24 | __m128 p1; 25 | 26 | /// LSB (e0123, e01, e02, e03) MSB 27 | __m128 p2; 28 | } kln_line; 29 | 30 | /// For directions, the e123 coordinate should be exactly zero. Directions 31 | /// are modeled as points at infinity (i.e. ideal points) 32 | typedef struct 33 | { 34 | /// LSB (e123, e021, e013, e032) MSB 35 | __m128 p3; 36 | } kln_direction; 37 | 38 | /// The point at cartesian coordinate $(x, y, z)$ corresponds to the 39 | /// multivector: 40 | /// $$\mathbf{e}_{123} + x\mathbf{e}_{032} + y\mathbf{e}_{013} +\ 41 | /// z\mathbf{e}_{021}$$ 42 | typedef struct 43 | { 44 | /// LSB (e123, e021, e013, e032) MSB 45 | __m128 p3; 46 | } kln_point; 47 | 48 | typedef struct 49 | { 50 | /// LSB (1, e12, e31, e23) MSB 51 | __m128 p1; 52 | } kln_rotor; 53 | 54 | typedef struct 55 | { 56 | /// LSB (e0123, e01, e02, e03) MSB 57 | __m128 p2; 58 | } kln_translator; 59 | 60 | typedef struct 61 | { 62 | /// LSB (1, e12, e31, e23) MSB 63 | __m128 p1; 64 | 65 | /// LSB (e0123, e01, e02, e03) MSB 66 | __m128 p2; 67 | } kln_motor; 68 | 69 | // INITIALIZATION ROUTINES 70 | 71 | /// Initialize a given plane to the quantity $a\mathbf{e}_1 + b\mathbf{e}_2 +\ 72 | /// c\mathbf{e}_3 + d\mathbf{e}_0$. 73 | void kln_plane_init(kln_plane* plane, float a, float b, float c, float d); 74 | 75 | /// A line is specifed by 6 coordinates which correspond to the line's 76 | /// [Plücker 77 | /// coordinates](https://en.wikipedia.org/wiki/Pl%C3%BCcker_coordinates). 78 | /// The coordinates specified in this way correspond to the following 79 | /// multivector: 80 | /// 81 | /// $$a\mathbf{e}_{01} + b\mathbf{e}_{02} + c\mathbf{e}_{03} +\ 82 | /// d\mathbf{e}_{12} + e\mathbf{e}_{31} + f\mathbf{e}_{23}$$ 83 | void kln_line_init(kln_line* line, 84 | float a, 85 | float b, 86 | float c, 87 | float d, 88 | float e, 89 | float f); 90 | 91 | /// Initialize a given point to the quantity $\mathbf{e}_{123} + x\mathbf{e}_{023} +\ 92 | /// y\mathbf{e}_{031} + z\mathbf{e}_{012}$. 93 | void kln_point_init(kln_point* point, float x, float y, float z); 94 | 95 | // VARIOUS GROUP ACTIONS 96 | 97 | /// Reflect point through plane 98 | kln_point kln_reflect_point(kln_plane const* plane, kln_point const* point); 99 | 100 | /// Reflect line through plane 101 | kln_line kln_reflect_line(kln_plane const* plane, kln_line const* line); 102 | 103 | /// Reflect plane2 through plane1 104 | kln_plane kln_reflect_plane(kln_plane const* plane1, kln_plane const* plane2); 105 | 106 | /// Apply rotor to point 107 | kln_point kln_rotate_point(kln_rotor const* rotor, kln_point const* point); 108 | 109 | /// Apply rotor to line 110 | kln_line kln_rotate_line(kln_rotor const* rotor, kln_line const* line); 111 | 112 | /// Apply rotor to plane 113 | kln_plane kln_rotate_plane(kln_rotor const* rotor, kln_plane const* line); 114 | 115 | /// Apply a translator to a point 116 | kln_point kln_translate_point(kln_translator const* translator, 117 | kln_point const* point); 118 | 119 | /// Apply a translator to a line 120 | kln_line 121 | kln_translate_line(kln_translator const* translator, kln_line const* line); 122 | 123 | /// Apply a translator to a plane 124 | kln_plane kln_translate_plane(kln_translator const* translator, 125 | kln_plane const* plane); 126 | 127 | /// Apply motor to point 128 | kln_point kln_motor_point(kln_motor const* motor, kln_point const* point); 129 | 130 | /// Apply motor to line 131 | kln_line kln_motor_line(kln_motor const* motor, kln_line const* line); 132 | 133 | /// Apply motor to plane 134 | kln_plane kln_motor_plane(kln_motor const* motor, kln_plane const* plane); 135 | 136 | // GROUP ACTION COMPOSITION 137 | 138 | /// Compose two rotors (rotor2 * rotor1) 139 | kln_rotor 140 | kln_compose_rotors(kln_rotor const* rotor1, kln_rotor const* rotor2); 141 | 142 | /// Compose two translators (translator2 * translator1) 143 | kln_translator kln_compose_translators(kln_translator const* translator1, 144 | kln_translator const* translator2); 145 | 146 | /// Compose a rotor and a translator to create a motor (translator * rotor) 147 | kln_motor kln_compose_rotor_translator(kln_rotor const* rotor, 148 | kln_translator const* translator); 149 | 150 | /// Compose a translator and a rotor to create a motor (translator * rotor) 151 | kln_motor kln_compose_translator_rotor(kln_translator const* translator, 152 | kln_rotor const* rotor); 153 | 154 | /// Compose two motors (motor2 * motor1) 155 | kln_motor 156 | kln_compose_motors(kln_motor const* motor1, kln_motor const* motor2); 157 | 158 | // MISCELLANEOUS 159 | 160 | /// Motor logarithm 161 | kln_line motor_log(kln_motor const* motor); 162 | 163 | /// Bivector exponential 164 | kln_motor line_exp(kln_line const* line); 165 | 166 | #if __cplusplus 167 | } 168 | #endif -------------------------------------------------------------------------------- /c_src/klein_c.cpp: -------------------------------------------------------------------------------- 1 | #include "klein.h" 2 | 3 | #include 4 | 5 | kln::plane const& convert(kln_plane const& plane) 6 | { 7 | return reinterpret_cast(plane); 8 | } 9 | 10 | kln_plane const& convert(kln::plane const& plane) 11 | { 12 | return reinterpret_cast(plane); 13 | } 14 | 15 | kln::line const& convert(kln_line const& line) 16 | { 17 | return reinterpret_cast(line); 18 | } 19 | 20 | kln_line const& convert(kln::line const& line) 21 | { 22 | return reinterpret_cast(line); 23 | } 24 | 25 | kln::point const& convert(kln_point const& point) 26 | { 27 | return reinterpret_cast(point); 28 | } 29 | 30 | kln_point const& convert(kln::point const& point) 31 | { 32 | return reinterpret_cast(point); 33 | } 34 | 35 | kln::rotor const& convert(kln_rotor const& rotor) 36 | { 37 | return reinterpret_cast(rotor); 38 | } 39 | 40 | kln_rotor const& convert(kln::rotor const& rotor) 41 | { 42 | return reinterpret_cast(rotor); 43 | } 44 | 45 | kln::translator const& convert(kln_translator const& translator) 46 | { 47 | return reinterpret_cast(translator); 48 | } 49 | 50 | kln_translator const& convert(kln::translator const& translator) 51 | { 52 | return reinterpret_cast(translator); 53 | } 54 | 55 | kln::motor const& convert(kln_motor const& motor) 56 | { 57 | return reinterpret_cast(motor); 58 | } 59 | 60 | kln_motor const& convert(kln::motor const& motor) 61 | { 62 | return reinterpret_cast(motor); 63 | } 64 | 65 | void kln_plane_init(kln_plane* plane, float a, float b, float c, float d) 66 | { 67 | plane->p0 = kln::plane{a, b, c, d}.p0_; 68 | } 69 | 70 | void kln_line_init(kln_line* line, float a, float b, float c, float d, float e, float f) 71 | { 72 | kln::line tmp{a, b, c, d, e, f}; 73 | line->p1 = tmp.p1_; 74 | line->p2 = tmp.p2_; 75 | } 76 | 77 | void kln_point_init(kln_point* point, float x, float y, float z) 78 | { 79 | point->p3 = kln::point{x, y, z}.p3_; 80 | } 81 | 82 | kln_point kln_reflect_point(kln_plane const* plane, kln_point const* point) 83 | { 84 | return convert(convert(*plane)(convert(*point))); 85 | } 86 | 87 | kln_line kln_reflect_line(kln_plane const* plane, kln_line const* line) 88 | { 89 | return convert(convert(*plane)(convert(*line))); 90 | } 91 | 92 | kln_plane kln_reflect_plane(kln_plane const* plane1, kln_plane const* plane2) 93 | { 94 | return convert(convert(*plane1)(convert(*plane2))); 95 | } 96 | 97 | kln_point kln_rotate_point(kln_rotor const* rotor, kln_point const* point) 98 | { 99 | return convert(convert(*rotor)(convert(*point))); 100 | } 101 | 102 | kln_line kln_rotate_line(kln_rotor const* rotor, kln_line const* line) 103 | { 104 | return convert(convert(*rotor)(convert(*line))); 105 | } 106 | 107 | kln_plane kln_rotate_plane(kln_rotor const* rotor, kln_plane const* plane) 108 | { 109 | return convert(convert(*rotor)(convert(*plane))); 110 | } 111 | 112 | kln_point 113 | kln_translate_point(kln_translator const* translator, kln_point const* point) 114 | { 115 | return convert(convert(*translator)(convert(*point))); 116 | } 117 | 118 | kln_line kln_translate_line(kln_translator const* translator, kln_line const* line) 119 | { 120 | return convert(convert(*translator)(convert(*line))); 121 | } 122 | 123 | kln_plane 124 | kln_translate_plane(kln_translator const* translator, kln_plane const* plane) 125 | { 126 | return convert(convert(*translator)(convert(*plane))); 127 | } 128 | 129 | kln_point kln_motor_point(kln_motor const* motor, kln_point const* point) 130 | { 131 | return convert(convert(*motor)(convert(*point))); 132 | } 133 | 134 | kln_line kln_motor_line(kln_motor const* motor, kln_line const* line) 135 | { 136 | return convert(convert(*motor)(convert(*line))); 137 | } 138 | 139 | kln_plane kln_motor_plane(kln_motor const* motor, kln_plane const* plane) 140 | { 141 | return convert(convert(*motor)(convert(*plane))); 142 | } 143 | 144 | kln_rotor kln_compose_rotors(kln_rotor const* rotor1, kln_rotor const* rotor2) 145 | { 146 | return convert(kln::rotor{convert(*rotor2) * convert(*rotor1)}); 147 | } 148 | 149 | kln_translator kln_compose_translators(kln_translator const* translator1, 150 | kln_translator const* translator2) 151 | { 152 | return convert(kln::translator{convert(*translator2) * convert(*translator1)}); 153 | } 154 | 155 | kln_motor kln_compose_rotor_translator(kln_rotor const* rotor, 156 | kln_translator const* translator) 157 | { 158 | return convert(kln::motor{convert(*translator) * convert(*rotor)}); 159 | } 160 | 161 | kln_motor kln_compose_translator_rotor(kln_translator const* translator, 162 | kln_rotor const* rotor) 163 | { 164 | return convert(kln::motor{convert(*rotor) * convert(*translator)}); 165 | } 166 | 167 | kln_motor kln_compose_motors(kln_motor const* motor1, kln_motor const* motor2) 168 | { 169 | return convert(kln::motor{convert(*motor2) * convert(*motor1)}); 170 | } 171 | 172 | kln_line motor_log(kln_motor const* motor) 173 | { 174 | return convert(kln::line{log(convert(*motor))}); 175 | } 176 | 177 | kln_motor line_exp(kln_line const* line) 178 | { 179 | return convert(kln::motor{exp(convert(*line))}); 180 | } -------------------------------------------------------------------------------- /cmake/klein-config.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/klein-targets.cmake") 2 | 3 | find_path( 4 | simde_headers 5 | "x86/sse4.2.h" 6 | PATH_SUFFIXES "simde" 7 | ) 8 | 9 | target_include_directories(klein::klein INTERFACE ${simde_headers}) 10 | target_include_directories(klein::klein_cxx11 INTERFACE ${simde_headers}) 11 | target_include_directories(klein::klein_sse42 INTERFACE ${simde_headers}) 12 | -------------------------------------------------------------------------------- /docs/api/dot.md: -------------------------------------------------------------------------------- 1 | ## group `dot` {#group__dot} 2 | 3 | The symmetric inner product takes two arguments and contracts the lower graded element to the greater graded element. If lower graded element spans an index that is not contained in the higher graded element, the result is annihilated. Otherwise, the result is the part of the higher graded element "most unlike" the lower graded element. Thus, the symmetric inner product can be thought of as a bidirectional contraction operator. 4 | 5 | There is some merit in providing both a left and right contraction operator for explicitness. However, when using Klein, it's generally clear what the interpretation of the symmetric inner product is with respect to the projection on various entities. 6 | 7 | !!! example "Angle between planes" 8 | ```cpp 9 | kln::plane a{x1, y1, z1, d1}; 10 | kln::plane b{x2, y2, z2, d2}; 11 | 12 | // Compute the cos of the angle between two planes 13 | float cos_ang = a | b; 14 | ``` 15 | 16 | 17 | !!! example "Line to plane through point" 18 | ```cpp 19 | kln::point a{x1, y1, z1}; 20 | kln::plane b{x2, y2, z2, d2}; 21 | 22 | // The line l contains a and the shortest path from a to plane b. 23 | line l = a | b; 24 | ``` 25 | 26 | ### Summary 27 | 28 | Members | Descriptions 29 | --------------------------------|--------------------------------------------- 30 | `public float ` [`operator\|`](#group__dot_1gad318da9ce7f0d17c6be4a28606f7055d)`(plane a,plane b) noexcept` | 31 | `public plane ` [`operator\|`](#group__dot_1gac5eb4e0be057f05d06fdb47af9c67884)`(plane a,line b) noexcept` | 32 | `public plane ` [`operator\|`](#group__dot_1ga6e653adaf2281eb98ea2fb859adebf91)`(line b,plane a) noexcept` | 33 | `public plane ` [`operator\|`](#group__dot_1ga95c5016a18aac7ec611fbb69ade553eb)`(plane a,ideal_line b) noexcept` | 34 | `public plane ` [`operator\|`](#group__dot_1ga3672748f41be5a0c523e0434bb48da7d)`(ideal_line b,plane a) noexcept` | 35 | `public line ` [`operator\|`](#group__dot_1gaf165d820d2709047750f9f9c4e627dc2)`(plane a,point b) noexcept` | 36 | `public line ` [`operator\|`](#group__dot_1ga21190874d649c1828316b8133c8c7ec0)`(point a,plane b) noexcept` | 37 | `public float ` [`operator\|`](#group__dot_1gab9b2443f7c67a96f08b03ce749565305)`(line a,line b) noexcept` | 38 | `public plane ` [`operator\|`](#group__dot_1ga4319b71d30d700e2579c26e181fd16fd)`(point a,line b) noexcept` | 39 | `public plane ` [`operator\|`](#group__dot_1ga36111b62f962203bb1a964cd6417b371)`(line b,point a) noexcept` | 40 | `public float ` [`operator\|`](#group__dot_1ga4ad7845d5581052aca22d5547ef7d5b1)`(point a,point b) noexcept` | 41 | 42 | ### Members 43 | 44 | #### float [operator|](#group__dot_1gad318da9ce7f0d17c6be4a28606f7055d)(plane a,plane b) noexcept {#group__dot_1gad318da9ce7f0d17c6be4a28606f7055d} 45 | 46 | #### plane [operator|](#group__dot_1gac5eb4e0be057f05d06fdb47af9c67884)(plane a,line b) noexcept {#group__dot_1gac5eb4e0be057f05d06fdb47af9c67884} 47 | 48 | #### plane [operator|](#group__dot_1ga6e653adaf2281eb98ea2fb859adebf91)(line b,plane a) noexcept {#group__dot_1ga6e653adaf2281eb98ea2fb859adebf91} 49 | 50 | #### plane [operator|](#group__dot_1ga95c5016a18aac7ec611fbb69ade553eb)(plane a,ideal_line b) noexcept {#group__dot_1ga95c5016a18aac7ec611fbb69ade553eb} 51 | 52 | #### plane [operator|](#group__dot_1ga3672748f41be5a0c523e0434bb48da7d)(ideal_line b,plane a) noexcept {#group__dot_1ga3672748f41be5a0c523e0434bb48da7d} 53 | 54 | #### line [operator|](#group__dot_1gaf165d820d2709047750f9f9c4e627dc2)(plane a,point b) noexcept {#group__dot_1gaf165d820d2709047750f9f9c4e627dc2} 55 | 56 | #### line [operator|](#group__dot_1ga21190874d649c1828316b8133c8c7ec0)(point a,plane b) noexcept {#group__dot_1ga21190874d649c1828316b8133c8c7ec0} 57 | 58 | #### float [operator|](#group__dot_1gab9b2443f7c67a96f08b03ce749565305)(line a,line b) noexcept {#group__dot_1gab9b2443f7c67a96f08b03ce749565305} 59 | 60 | #### plane [operator|](#group__dot_1ga4319b71d30d700e2579c26e181fd16fd)(point a,line b) noexcept {#group__dot_1ga4319b71d30d700e2579c26e181fd16fd} 61 | 62 | #### plane [operator|](#group__dot_1ga36111b62f962203bb1a964cd6417b371)(line b,point a) noexcept {#group__dot_1ga36111b62f962203bb1a964cd6417b371} 63 | 64 | #### float [operator|](#group__dot_1ga4ad7845d5581052aca22d5547ef7d5b1)(point a,point b) noexcept {#group__dot_1ga4ad7845d5581052aca22d5547ef7d5b1} 65 | 66 | -------------------------------------------------------------------------------- /docs/api/dual.md: -------------------------------------------------------------------------------- 1 | ## group `dual` {#group__dual} 2 | 3 | The Poincaré Dual of an element is the "subspace complement" of the argument with respect to the pseudoscalar in the exterior algebra. In practice, it is a relabeling of the coordinates to their dual-coordinates and is used most often to implement a "join" operation in terms of the exterior product of the duals of each operand. 4 | 5 | Ex: The dual of the point $\mathbf{e}_{123} + 3\mathbf{e}_{013} - 2\mathbf{e}_{021}$ (the point at $(0, 3, -2)$) is the plane $\mathbf{e}_0 + 3\mathbf{e}_2 - 2\mathbf{e}_3$. 6 | 7 | ### Summary 8 | 9 | Members | Descriptions 10 | --------------------------------|--------------------------------------------- 11 | `public KLN_INLINE point KLN_VEC_CALL ` [`operator!`](#group__dual_1ga3a687310cf04e42ff485b03d557ed5a0)`(plane in) noexcept` | 12 | `public KLN_INLINE plane KLN_VEC_CALL ` [`operator!`](#group__dual_1ga105ff06095b7eb1fc4ab4b345d7aefb1)`(point in) noexcept` | 13 | `public KLN_INLINE line KLN_VEC_CALL ` [`operator!`](#group__dual_1gaa54dbe6355262d69f4302296708cc3fd)`(line in) noexcept` | 14 | `public KLN_INLINE branch KLN_VEC_CALL ` [`operator!`](#group__dual_1gac57689e4bf002977c93dbb5e162f7a73)`(ideal_line in) noexcept` | 15 | `public KLN_INLINE ideal_line KLN_VEC_CALL ` [`operator!`](#group__dual_1gaba4e70ba96775d4bda3676593256a6be)`(branch in) noexcept` | 16 | `public KLN_INLINE dual KLN_VEC_CALL ` [`operator!`](#group__dual_1ga322859aabdd00a64e316ef1d4923a266)`(dual in) noexcept` | 17 | 18 | ### Members 19 | 20 | #### KLN_INLINE point KLN_VEC_CALL [operator!](#group__dual_1ga3a687310cf04e42ff485b03d557ed5a0)(plane in) noexcept {#group__dual_1ga3a687310cf04e42ff485b03d557ed5a0} 21 | 22 | #### KLN_INLINE plane KLN_VEC_CALL [operator!](#group__dual_1ga105ff06095b7eb1fc4ab4b345d7aefb1)(point in) noexcept {#group__dual_1ga105ff06095b7eb1fc4ab4b345d7aefb1} 23 | 24 | #### KLN_INLINE line KLN_VEC_CALL [operator!](#group__dual_1gaa54dbe6355262d69f4302296708cc3fd)(line in) noexcept {#group__dual_1gaa54dbe6355262d69f4302296708cc3fd} 25 | 26 | #### KLN_INLINE branch KLN_VEC_CALL [operator!](#group__dual_1gac57689e4bf002977c93dbb5e162f7a73)(ideal_line in) noexcept {#group__dual_1gac57689e4bf002977c93dbb5e162f7a73} 27 | 28 | #### KLN_INLINE ideal_line KLN_VEC_CALL [operator!](#group__dual_1gaba4e70ba96775d4bda3676593256a6be)(branch in) noexcept {#group__dual_1gaba4e70ba96775d4bda3676593256a6be} 29 | 30 | #### KLN_INLINE dual KLN_VEC_CALL [operator!](#group__dual_1ga322859aabdd00a64e316ef1d4923a266)(dual in) noexcept {#group__dual_1ga322859aabdd00a64e316ef1d4923a266} 31 | 32 | -------------------------------------------------------------------------------- /docs/api/dualn.md: -------------------------------------------------------------------------------- 1 | ## group `dualn` {#group__dualn} 2 | 3 | A dual number is a multivector of the form $p + q\mathbf{e}_{0123}$. 4 | 5 | ### Summary 6 | 7 | Members | Descriptions 8 | --------------------------------|--------------------------------------------- 9 | `public dual KLN_VEC_CALL ` [`operator+`](#group__dualn_1gad54e557339dd7c8929fb70177ce99431)`(dual a,dual b) noexcept` | 10 | `public dual KLN_VEC_CALL ` [`operator-`](#group__dualn_1gac3c6b82d268dbc0ce92a43479952f30a)`(dual a,dual b) noexcept` | 11 | `public dual KLN_VEC_CALL ` [`operator*`](#group__dualn_1gaaad721d43b803f0bf57bc8d1454dd824)`(dual a,float s) noexcept` | 12 | `public dual KLN_VEC_CALL ` [`operator*`](#group__dualn_1ga061a15005779a7687ab40e54a3257fca)`(float s,dual a) noexcept` | 13 | `public dual KLN_VEC_CALL ` [`operator/`](#group__dualn_1ga2c6bedacc94979d14173bd340aefd6d5)`(dual a,float s) noexcept` | 14 | 15 | ### Members 16 | 17 | #### dual KLN_VEC_CALL [operator+](#group__dualn_1gad54e557339dd7c8929fb70177ce99431)(dual a,dual b) noexcept {#group__dualn_1gad54e557339dd7c8929fb70177ce99431} 18 | 19 | #### dual KLN_VEC_CALL [operator-](#group__dualn_1gac3c6b82d268dbc0ce92a43479952f30a)(dual a,dual b) noexcept {#group__dualn_1gac3c6b82d268dbc0ce92a43479952f30a} 20 | 21 | #### dual KLN_VEC_CALL [operator*](#group__dualn_1gaaad721d43b803f0bf57bc8d1454dd824)(dual a,float s) noexcept {#group__dualn_1gaaad721d43b803f0bf57bc8d1454dd824} 22 | 23 | #### dual KLN_VEC_CALL [operator*](#group__dualn_1ga061a15005779a7687ab40e54a3257fca)(float s,dual a) noexcept {#group__dualn_1ga061a15005779a7687ab40e54a3257fca} 24 | 25 | #### dual KLN_VEC_CALL [operator/](#group__dualn_1ga2c6bedacc94979d14173bd340aefd6d5)(dual a,float s) noexcept {#group__dualn_1ga2c6bedacc94979d14173bd340aefd6d5} 26 | 27 | -------------------------------------------------------------------------------- /docs/api/ext.md: -------------------------------------------------------------------------------- 1 | ## group `ext` {#group__ext} 2 | 3 | ([meet.hpp](../../api/undefined#meet_8hpp)) 4 | 5 | The exterior product between two basis elements extinguishes if the two operands share any common index. Otherwise, the element produced is equivalent to the union of the subspaces. A sign flip is introduced if the concatenation of the element indices is an odd permutation of the cyclic basis representation. The exterior product extends to general multivectors by linearity. 6 | 7 | !!! example "Meeting two planes" 8 | ```cpp 9 | kln::plane p1{x1, y1, z1, d1}; 10 | kln::plane p2{x2, y2, z2, d2}; 11 | 12 | // l lies at the intersection of p1 and p2. 13 | kln::line l = p1 ^ p2; 14 | ``` 15 | 16 | 17 | !!! example "Meeting a line and a plane" 18 | ```cpp 19 | kln::plane p1{x, y, z, d}; 20 | kln::line l2{mx, my, mz, dx, dy, dz}; 21 | 22 | // p2 lies at the intersection of p1 and l2. 23 | kln::point p2 = p1 ^ l2; 24 | ``` 25 | 26 | ### Summary 27 | 28 | Members | Descriptions 29 | --------------------------------|--------------------------------------------- 30 | `public line KLN_VEC_CALL ` [`operator^`](#group__ext_1gaf6b69634e1bd50ac311c3c3809e0d3f0)`(plane a,plane b) noexcept` | 31 | `public point KLN_VEC_CALL ` [`operator^`](#group__ext_1gaddd37930213efde0f90e8abb0f60c2bc)`(plane a,branch b) noexcept` | 32 | `public point KLN_VEC_CALL ` [`operator^`](#group__ext_1ga17077e02ba6e4391e6fdf056ae06da76)`(branch b,plane a) noexcept` | 33 | `public point KLN_VEC_CALL ` [`operator^`](#group__ext_1ga7c2f4e2deca4fc77fe75ff0fc608ed23)`(plane a,ideal_line b) noexcept` | 34 | `public point KLN_VEC_CALL ` [`operator^`](#group__ext_1ga0aa1144df65f9d813a10f258d06277ae)`(ideal_line b,plane a) noexcept` | 35 | `public point KLN_VEC_CALL ` [`operator^`](#group__ext_1gaa1676d98117be736ee2bba5f56e309b3)`(plane a,line b) noexcept` | 36 | `public point KLN_VEC_CALL ` [`operator^`](#group__ext_1gaf6b24c4d8ecfa0b241e6549f6ce55531)`(line b,plane a) noexcept` | 37 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1ga4586341ae4d37e91856b56a6890ce5f8)`(plane a,point b) noexcept` | 38 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1ga0ca0c0ab8d43f634ca56afd825f9d44e)`(point b,plane a) noexcept` | 39 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1gad6a59a5e1fce6d154880a2f703d57904)`(branch a,ideal_line b) noexcept` | 40 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1gab85e4f6b524bf5ced8e69bed740a5c36)`(ideal_line b,branch a) noexcept` | 41 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1ga2e4a907b6183e39f7d1654b2d2a5fbf7)`(line a,line b) noexcept` | 42 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1ga870be4918722a28ad026286942172a07)`(line a,ideal_line b) noexcept` | 43 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1ga4d9bce873ef833b7845ac0fdc5eb8b0f)`(ideal_line b,line a) noexcept` | 44 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1gad6dec9eb7586335e38b38485ea08cf9c)`(line a,branch b) noexcept` | 45 | `public dual KLN_VEC_CALL ` [`operator^`](#group__ext_1gaf52ece148d4d7adfb0645b7c96b75cfa)`(branch b,line a) noexcept` | 46 | 47 | ### Members 48 | 49 | #### line KLN_VEC_CALL [operator^](#group__ext_1gaf6b69634e1bd50ac311c3c3809e0d3f0)(plane a,plane b) noexcept {#group__ext_1gaf6b69634e1bd50ac311c3c3809e0d3f0} 50 | 51 | #### point KLN_VEC_CALL [operator^](#group__ext_1gaddd37930213efde0f90e8abb0f60c2bc)(plane a,branch b) noexcept {#group__ext_1gaddd37930213efde0f90e8abb0f60c2bc} 52 | 53 | #### point KLN_VEC_CALL [operator^](#group__ext_1ga17077e02ba6e4391e6fdf056ae06da76)(branch b,plane a) noexcept {#group__ext_1ga17077e02ba6e4391e6fdf056ae06da76} 54 | 55 | #### point KLN_VEC_CALL [operator^](#group__ext_1ga7c2f4e2deca4fc77fe75ff0fc608ed23)(plane a,ideal_line b) noexcept {#group__ext_1ga7c2f4e2deca4fc77fe75ff0fc608ed23} 56 | 57 | #### point KLN_VEC_CALL [operator^](#group__ext_1ga0aa1144df65f9d813a10f258d06277ae)(ideal_line b,plane a) noexcept {#group__ext_1ga0aa1144df65f9d813a10f258d06277ae} 58 | 59 | #### point KLN_VEC_CALL [operator^](#group__ext_1gaa1676d98117be736ee2bba5f56e309b3)(plane a,line b) noexcept {#group__ext_1gaa1676d98117be736ee2bba5f56e309b3} 60 | 61 | #### point KLN_VEC_CALL [operator^](#group__ext_1gaf6b24c4d8ecfa0b241e6549f6ce55531)(line b,plane a) noexcept {#group__ext_1gaf6b24c4d8ecfa0b241e6549f6ce55531} 62 | 63 | #### dual KLN_VEC_CALL [operator^](#group__ext_1ga4586341ae4d37e91856b56a6890ce5f8)(plane a,point b) noexcept {#group__ext_1ga4586341ae4d37e91856b56a6890ce5f8} 64 | 65 | #### dual KLN_VEC_CALL [operator^](#group__ext_1ga0ca0c0ab8d43f634ca56afd825f9d44e)(point b,plane a) noexcept {#group__ext_1ga0ca0c0ab8d43f634ca56afd825f9d44e} 66 | 67 | #### dual KLN_VEC_CALL [operator^](#group__ext_1gad6a59a5e1fce6d154880a2f703d57904)(branch a,ideal_line b) noexcept {#group__ext_1gad6a59a5e1fce6d154880a2f703d57904} 68 | 69 | #### dual KLN_VEC_CALL [operator^](#group__ext_1gab85e4f6b524bf5ced8e69bed740a5c36)(ideal_line b,branch a) noexcept {#group__ext_1gab85e4f6b524bf5ced8e69bed740a5c36} 70 | 71 | #### dual KLN_VEC_CALL [operator^](#group__ext_1ga2e4a907b6183e39f7d1654b2d2a5fbf7)(line a,line b) noexcept {#group__ext_1ga2e4a907b6183e39f7d1654b2d2a5fbf7} 72 | 73 | #### dual KLN_VEC_CALL [operator^](#group__ext_1ga870be4918722a28ad026286942172a07)(line a,ideal_line b) noexcept {#group__ext_1ga870be4918722a28ad026286942172a07} 74 | 75 | #### dual KLN_VEC_CALL [operator^](#group__ext_1ga4d9bce873ef833b7845ac0fdc5eb8b0f)(ideal_line b,line a) noexcept {#group__ext_1ga4d9bce873ef833b7845ac0fdc5eb8b0f} 76 | 77 | #### dual KLN_VEC_CALL [operator^](#group__ext_1gad6dec9eb7586335e38b38485ea08cf9c)(line a,branch b) noexcept {#group__ext_1gad6dec9eb7586335e38b38485ea08cf9c} 78 | 79 | #### dual KLN_VEC_CALL [operator^](#group__ext_1gaf52ece148d4d7adfb0645b7c96b75cfa)(branch b,line a) noexcept {#group__ext_1gaf52ece148d4d7adfb0645b7c96b75cfa} 80 | 81 | -------------------------------------------------------------------------------- /docs/api/proj.md: -------------------------------------------------------------------------------- 1 | ## group `proj` {#group__proj} 2 | 3 | Projections in Geometric Algebra take on a particularly simple form. For two geometric entities $a$ and $b$, there are two cases to consider. First, if the grade of $a$ is greater than the grade of $b$, the projection of $a$ on $b$ is given by: 4 | 5 | $$ \textit{proj}_b a = (a \cdot b) \wedge b $$ 6 | 7 | The inner product can be thought of as the part of $b$ *least like* $a$. Using the meet operator on this part produces the part of $b$ *most like* $a$. A simple sanity check is to consider the grades of the result. If the grade of $b$ is less than the grade of $a$, we end up with an entity with grade $a - b + b = a$ as expected. 8 | 9 | In the second case (the grade of $a$ is less than the grade of $b$), the projection of $a$ on $b$ is given by: 10 | 11 | $$ \textit{proj}_b a = (a \cdot b) \cdot b $$ 12 | 13 | It can be verified that as in the first case, the grade of the result is the same as the grade of $a$. As this projection occurs in the opposite sense from what one may have seen before, additional clarification is provided below. 14 | 15 | ### Summary 16 | 17 | Members | Descriptions 18 | --------------------------------|--------------------------------------------- 19 | `public point KLN_VEC_CALL ` [`project`](#group__proj_1gad3cddd86655de814988bb594b8df5ff7)`(point a,line b) noexcept` | Project a point onto a line. 20 | `public point KLN_VEC_CALL ` [`project`](#group__proj_1ga6732017602bbd6cbb4326946720927f2)`(point a,plane b) noexcept` | Project a point onto a plane. 21 | `public line KLN_VEC_CALL ` [`project`](#group__proj_1gad0ac38479bfb78b1ef9d0c06cb37a033)`(line a,plane b) noexcept` | Project a line onto a plane. 22 | `public plane KLN_VEC_CALL ` [`project`](#group__proj_1ga9ba81079e93afed456b56ae7273af189)`(plane a,point b) noexcept` | Project a plane onto a point. Given a plane $p$ and point $P$, produces the plane through $P$ that is parallel to $p$. 23 | `public line KLN_VEC_CALL ` [`project`](#group__proj_1ga1d875a5cc0de38ff17c3e4f298a116bd)`(line a,point b) noexcept` | Project a line onto a point. Given a line $\ell$ and point $P$, produces the line through $P$ that is parallel to $\ell$. 24 | `public plane KLN_VEC_CALL ` [`project`](#group__proj_1ga29fd7694ba97a90fc9ae4525ab609314)`(plane a,line b) noexcept` | Project a plane onto a line. Given a plane $p$ and line $\ell$, produces the plane through $\ell$ that is parallel to $p$ if $p \parallel \ell$. 25 | 26 | ### Members 27 | 28 | #### point KLN_VEC_CALL [project](#group__proj_1gad3cddd86655de814988bb594b8df5ff7)(point a,line b) noexcept {#group__proj_1gad3cddd86655de814988bb594b8df5ff7} 29 | 30 | Project a point onto a line. 31 | 32 | #### point KLN_VEC_CALL [project](#group__proj_1ga6732017602bbd6cbb4326946720927f2)(point a,plane b) noexcept {#group__proj_1ga6732017602bbd6cbb4326946720927f2} 33 | 34 | Project a point onto a plane. 35 | 36 | #### line KLN_VEC_CALL [project](#group__proj_1gad0ac38479bfb78b1ef9d0c06cb37a033)(line a,plane b) noexcept {#group__proj_1gad0ac38479bfb78b1ef9d0c06cb37a033} 37 | 38 | Project a line onto a plane. 39 | 40 | #### plane KLN_VEC_CALL [project](#group__proj_1ga9ba81079e93afed456b56ae7273af189)(plane a,point b) noexcept {#group__proj_1ga9ba81079e93afed456b56ae7273af189} 41 | 42 | Project a plane onto a point. Given a plane $p$ and point $P$, produces the plane through $P$ that is parallel to $p$. 43 | 44 | Intuitively, the point is represented dually in terms of a *pencil of planes* that converge on the point itself. When we compute $p | P$, this selects the line perpendicular to $p$ through $P$. Subsequently, taking the inner product with $P$ again selects the plane from the plane pencil of $P$ *least like* that line. 45 | 46 | #### line KLN_VEC_CALL [project](#group__proj_1ga1d875a5cc0de38ff17c3e4f298a116bd)(line a,point b) noexcept {#group__proj_1ga1d875a5cc0de38ff17c3e4f298a116bd} 47 | 48 | Project a line onto a point. Given a line $\ell$ and point $P$, produces the line through $P$ that is parallel to $\ell$. 49 | 50 | #### plane KLN_VEC_CALL [project](#group__proj_1ga29fd7694ba97a90fc9ae4525ab609314)(plane a,line b) noexcept {#group__proj_1ga29fd7694ba97a90fc9ae4525ab609314} 51 | 52 | Project a plane onto a line. Given a plane $p$ and line $\ell$, produces the plane through $\ell$ that is parallel to $p$ if $p \parallel \ell$. 53 | 54 | If $p \nparallel \ell$, the result will be the plane $p'$ containing $\ell$ that maximizes $p \cdot p'$ (that is, $p'$ is as parallel to $p$ as possible). 55 | 56 | -------------------------------------------------------------------------------- /docs/api/reg.md: -------------------------------------------------------------------------------- 1 | ## group `reg` {#group__reg} 2 | 3 | The regressive product is implemented in terms of the exterior product. Given multivectors $\mathbf{a}$ and $\mathbf{b}$, the regressive product $\mathbf{a}\vee\mathbf{b}$ is equivalent to $J(J(\mathbf{a})\wedge J(\mathbf{b}))$. Thus, both meets and joins reside in the same algebraic structure. 4 | 5 | !!! example "Joining two points" 6 | ```cpp 7 | kln::point p1{x1, y1, z1}; 8 | kln::point p2{x2, y2, z2}; 9 | 10 | // l contains both p1 and p2. 11 | kln::line l = p1 & p2; 12 | ``` 13 | 14 | 15 | !!! example "Joining a line and a point" 16 | ```cpp 17 | kln::point p1{x, y, z}; 18 | kln::line l2{mx, my, mz, dx, dy, dz}; 19 | 20 | // p2 contains both p1 and l2. 21 | kln::plane p2 = p1 & l2; 22 | ``` 23 | 24 | ### Summary 25 | 26 | Members | Descriptions 27 | --------------------------------|--------------------------------------------- 28 | `public line KLN_VEC_CALL ` [`operator&`](#group__reg_1ga63b0997f8119571d904e3c02673d8bbb)`(point a,point b) noexcept` | 29 | `public plane KLN_VEC_CALL ` [`operator&`](#group__reg_1gad37eea7e6c630474cae21c6a6d76dfd1)`(point a,line b) noexcept` | 30 | `public plane KLN_VEC_CALL ` [`operator&`](#group__reg_1ga6be92308d719cd634211b5fc0909d00c)`(line b,point a) noexcept` | 31 | `public plane KLN_VEC_CALL ` [`operator&`](#group__reg_1ga9f3f5c16881afe59f2c77e2977653094)`(point a,branch b) noexcept` | 32 | `public plane KLN_VEC_CALL ` [`operator&`](#group__reg_1ga7b9ddcd88aa6a10b980b69de28aebeb3)`(branch b,point a) noexcept` | 33 | `public plane KLN_VEC_CALL ` [`operator&`](#group__reg_1ga05885edf19c3ef66e942390073922bd0)`(point a,ideal_line b) noexcept` | 34 | `public plane KLN_VEC_CALL ` [`operator&`](#group__reg_1ga2da1a859d4b8c745a77855eabc380980)`(ideal_line b,point a) noexcept` | 35 | `public dual KLN_VEC_CALL ` [`operator&`](#group__reg_1gaff23302987cbc2a0a33bae1420992e01)`(plane a,point b) noexcept` | 36 | `public dual KLN_VEC_CALL ` [`operator&`](#group__reg_1ga66d87e84a1c2618381cb696800164ecc)`(point a,plane b) noexcept` | 37 | 38 | ### Members 39 | 40 | #### line KLN_VEC_CALL [operator&](#group__reg_1ga63b0997f8119571d904e3c02673d8bbb)(point a,point b) noexcept {#group__reg_1ga63b0997f8119571d904e3c02673d8bbb} 41 | 42 | #### plane KLN_VEC_CALL [operator&](#group__reg_1gad37eea7e6c630474cae21c6a6d76dfd1)(point a,line b) noexcept {#group__reg_1gad37eea7e6c630474cae21c6a6d76dfd1} 43 | 44 | #### plane KLN_VEC_CALL [operator&](#group__reg_1ga6be92308d719cd634211b5fc0909d00c)(line b,point a) noexcept {#group__reg_1ga6be92308d719cd634211b5fc0909d00c} 45 | 46 | #### plane KLN_VEC_CALL [operator&](#group__reg_1ga9f3f5c16881afe59f2c77e2977653094)(point a,branch b) noexcept {#group__reg_1ga9f3f5c16881afe59f2c77e2977653094} 47 | 48 | #### plane KLN_VEC_CALL [operator&](#group__reg_1ga7b9ddcd88aa6a10b980b69de28aebeb3)(branch b,point a) noexcept {#group__reg_1ga7b9ddcd88aa6a10b980b69de28aebeb3} 49 | 50 | #### plane KLN_VEC_CALL [operator&](#group__reg_1ga05885edf19c3ef66e942390073922bd0)(point a,ideal_line b) noexcept {#group__reg_1ga05885edf19c3ef66e942390073922bd0} 51 | 52 | #### plane KLN_VEC_CALL [operator&](#group__reg_1ga2da1a859d4b8c745a77855eabc380980)(ideal_line b,point a) noexcept {#group__reg_1ga2da1a859d4b8c745a77855eabc380980} 53 | 54 | #### dual KLN_VEC_CALL [operator&](#group__reg_1gaff23302987cbc2a0a33bae1420992e01)(plane a,point b) noexcept {#group__reg_1gaff23302987cbc2a0a33bae1420992e01} 55 | 56 | #### dual KLN_VEC_CALL [operator&](#group__reg_1ga66d87e84a1c2618381cb696800164ecc)(point a,plane b) noexcept {#group__reg_1ga66d87e84a1c2618381cb696800164ecc} 57 | 58 | -------------------------------------------------------------------------------- /docs/css/extra.css: -------------------------------------------------------------------------------- 1 | .md-footer-copyright { 2 | padding: 0.9rem; 3 | } 4 | -------------------------------------------------------------------------------- /docs/discord.md: -------------------------------------------------------------------------------- 1 | The Klein discord is very new but anyone is welcome to drop in and offer feedback, ask for help, or 2 | discuss topics ranging from graphics programming, math (geometric algebra or otherwise), physics, 3 | and more. 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/gpu.md: -------------------------------------------------------------------------------- 1 | GPU support is currently provided by a single 2 | [glsl file](https://github.com/jeremyong/klein/blob/master/glsl/klein.glsl). 3 | This file can be pasted at the start of a shader to provide limited functionality supported by the 4 | full C++ Klein library. The `kln_plane`, `kln_line`, `kln_point`, `kln_rotor`, and `kln_motor` entity structs defined 5 | in this shader header are byte-for-byte identical to their C++ counterparts. 6 | 7 | Currently, the following functions are supported: 8 | 9 | | Function | Description | 10 | | ------------------------------------------------------------------ | ------------------------------------------------- | 11 | | `kln_rotor kln_mul(in kln_rotor a, in kln_rotor b)` | Multiplies two rotors and returns the result | 12 | | `kln_translator kln_mul(in kln_translator a, in kln_translator b)` | Multiplies two translators and returns the result | 13 | | `kln_motor kln_mul(in kln_motor a, in kln_motor b)` | Multiplies two motors and returns the result | 14 | | `kln_plane kln_apply(in kln_rotor r, in kln_plane p)` | Applies a rotor to a plane | 15 | | `kln_plane kln_apply(in kln_motor m, in kln_plane p)` | Applies a motor to a plane | 16 | | `kln_point kln_apply(in kln_rotor r, in kln_point p)` | Applies a rotor to a point | 17 | | `kln_point kln_apply(in kln_motor m, in kln_point p)` | Applies a motor to a point | 18 | | `kln_point kln_apply(in kln_motor m)` | Applies a motor to the origin | 19 | 20 | GPU support is verified with a C++ test suite powered by a 21 | [shim](https://github.com/jeremyong/klein/blob/master/test/glsl_shim.hpp) 22 | to handle vector swizzle operations and provide implementations for GLSL built-in functions. GPU 23 | support is currently preliminary and achieving parity with seamless interoperability with the Klein 24 | C++ headers is an ongoing objective. 25 | -------------------------------------------------------------------------------- /docs/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyong/klein/21823fc90d191c68a4f31e0b233c58ea28ca3d5a/docs/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyong/klein/21823fc90d191c68a4f31e0b233c58ea28ca3d5a/docs/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyong/klein/21823fc90d191c68a4f31e0b233c58ea28ca3d5a/docs/img/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyong/klein/21823fc90d191c68a4f31e0b233c58ea28ca3d5a/docs/img/favicon-16x16.png -------------------------------------------------------------------------------- /docs/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyong/klein/21823fc90d191c68a4f31e0b233c58ea28ca3d5a/docs/img/favicon-32x32.png -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyong/klein/21823fc90d191c68a4f31e0b233c58ea28ca3d5a/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /docs/img/tpose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyong/klein/21823fc90d191c68a4f31e0b233c58ea28ca3d5a/docs/img/tpose.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ![Klein](./img/android-chrome-192x192.png) 2 | 3 | # Klein 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://opensource.org/licenses/MIT) 6 | [![DOI](https://zenodo.org/badge/236777729.svg)](https://zenodo.org/badge/latestdoi/236777729) 7 | [![Build Status](https://travis-ci.org/jeremyong/Klein.svg?branch=master)](https://travis-ci.org/jeremyong/Klein) 8 | [![Build Status](https://ci.appveyor.com/api/projects/status/w3ug2ad08jyved8o?svg=true)](https://ci.appveyor.com/project/jeremyong/klein) 9 | [![Coverity Status](https://img.shields.io/coverity/scan/20402.svg)](https://scan.coverity.com/projects/jeremyong-klein) 10 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/5908bd446f3d4bb0bb1fd2e0808cb8a1)](https://www.codacy.com/manual/jeremyong/klein?utm_source=github.com&utm_medium=referral&utm_content=jeremyong/klein&utm_campaign=Badge_Grade) 11 | 12 | Do you need to do any of the following? Quickly? _Really_ quickly even? 13 | 14 | - Projecting points onto lines, lines to planes, points to planes? 15 | - Measuring distances and angles between points, lines, and planes? 16 | - Rotate or translate points, lines, and planes? 17 | - Perform smooth rigid body transforms? Interpolate them smoothly? 18 | - Construct lines from points? Planes from points? Planes from a line and a point? 19 | - Intersect planes to form lines? Intersect a planes and lines to form points? 20 | 21 | If so, then Klein is the library for you! 22 | 23 | ## Feature Summary 24 | 25 | [Klein](https://github.com/jeremyong/Klein) is an implementation of $\mathbf{P}(\mathbb{R}^*_{3, 0, 1})$, aka 3D Projective Geometric Algebra. 26 | It is designed for applications that demand high-throughput (animation libraries, 27 | kinematic solvers, etc). In contrast to other GA libraries, Klein does not attempt to 28 | generalize the metric or dimensionality of the space. In exchange for this loss of generality, 29 | Klein implements the algebraic operations using the full weight of SSE (Streaming 30 | SIMD Extensions) for maximum throughput. Klein's performance is fully competitive with state of the 31 | art kinematic and math libraries built with traditional vector and quaternion formulations. 32 | 33 | !!! tip 34 | 35 | **Knowledge of Geometric Algebra is NOT required to benefit from the library**, but 36 | familiarity can ease the learning curve of the API somewhat, as the operators in 37 | Geometric Algebra map cleanly to geometric operations. 38 | 39 | - Geometric computing library suitable for use with realtime graphics and animation applications 40 | - Header-only core libary with an optional lightweight symbolic computer algebra system 41 | - SSE3 or SSE4.1-optimized implementations 42 | - Tested on Linux, MacOS, and Windows 43 | - Requires no third-party dependencies 44 | - Permissively licensed 45 | 46 | ## Frequently tested compilers 47 | 48 | - GCC 9.2.1 on Linux 49 | - Clang 9.0.1 on Linux 50 | - Visual Studio 2019 on Windows 10 51 | - Xcode 11.3 on MacOS 52 | 53 | ## Supported entities 54 | 55 | - Points 56 | - Directions 57 | - Lines 58 | - Ideal lines 59 | - Planes 60 | - Rotors 61 | - Translators 62 | - Motors 63 | 64 | ## Supported operations 65 | 66 | - Geometric product (used to compose group actions) 67 | - Exterior product (used to meet entities) 68 | - Regressive product (used to join entities) 69 | - Conjugation (aka "sandwich") operators (defined via the call operator on planes, rotors, translators, and motors) 70 | - Inner product (used for metric measurements and to project entities) 71 | - Standard arithmetic operations 72 | - Motor logarithm to compute the motor axis 73 | - Line exponentiation to generate motors 74 | - Rotor logarithm to compute the rotor axis (also referred to as a `branch`) 75 | - Branch exponentiation to generate rotors 76 | 77 | The spherical interpolation operation "slerp" can be implemented easily in terms of the rotor or 78 | motor logarithm, depending on what is needed. 79 | -------------------------------------------------------------------------------- /docs/js/extra.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var macros = { 5 | '\\ee': '\\mathbf{e}', 6 | '\\JJ': '\\mathbf{J}', 7 | '\\PGA': '\\mathbf{P}(\\mathbb{R}^*_{3, 0, 1})', 8 | }; 9 | 10 | var katexMath = (function () { 11 | var maths = document.querySelectorAll('.arithmatex'), 12 | tex; 13 | 14 | for (var i = 0; i < maths.length; i++) { 15 | tex = maths[i].textContent || maths[i].innerText; 16 | if (tex.startsWith('\\(') && tex.endsWith('\\)')) { 17 | katex.render(tex.slice(2, -2), maths[i], { 'displayMode': false, macros }); 18 | } else if (tex.startsWith('\\[') && tex.endsWith('\\]')) { 19 | katex.render(tex.slice(2, -2), maths[i], { 'displayMode': true, macros }); 20 | } 21 | } 22 | }); 23 | 24 | (function () { 25 | var onReady = function onReady(fn) { 26 | if (document.addEventListener) { 27 | document.addEventListener("DOMContentLoaded", fn); 28 | } else { 29 | document.attachEvent("onreadystatechange", function () { 30 | if (document.readyState === "interactive") { 31 | fn(); 32 | } 33 | }); 34 | } 35 | }; 36 | 37 | onReady(function () { 38 | if (typeof katex !== "undefined") { 39 | katexMath(); 40 | } 41 | 42 | if (typeof mermaid !== 'undefined') { 43 | mermaid.initialize({ 44 | theme: 'neutral' 45 | }); 46 | } 47 | }); 48 | })(); 49 | }()); -------------------------------------------------------------------------------- /docs/js/tutorial.js: -------------------------------------------------------------------------------- 1 | const vs_src = ` 2 | attribute vec4 a_pos; 3 | uniform mat4 u_mvp; 4 | uniform mat4 u_proj; 5 | 6 | void main() { 7 | gl_Position = u_proj * u_mvp * a_pos; 8 | } 9 | `; 10 | 11 | const fs_src = ` 12 | void main() { 13 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); 14 | } 15 | ` 16 | 17 | function init_canvas(cv) { 18 | const gl = cv.getContext('webgl'); 19 | 20 | gl.clearColor(0, 0, 0, 1.0); 21 | gl.clear(gl.COLOR_BUFFER_BIT); 22 | } 23 | 24 | function load_default(gl) { 25 | const prog = link_program(gl, vs_src, fs_src); 26 | if (!prog) { 27 | return null; 28 | } 29 | 30 | return { 31 | prog, 32 | a_pos: gl.getAttribLocation(prog, 'a_pos'), 33 | u_mvp: gl.getAttribLocation(prog, 'u_mvp'), 34 | u_proj: gl.getAttribLocation(prog, 'u_proj'), 35 | } 36 | } 37 | 38 | function load_shader(gl, type, source) { 39 | const shader = gl.createShader(type); 40 | gl.shaderSource(shader, source); 41 | gl.compileShader(shader); 42 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 43 | console.error(gl.getShaderInfoLog(shader)); 44 | gl.deleteShader(shader); 45 | return null; 46 | } 47 | return shader; 48 | } 49 | 50 | function link_program(gl, vs, fs) { 51 | const vert = load_shader(gl, gl.VERTEX_SHADER, vs); 52 | const frag = load_shader(gl, gl.VERTEX_SHADER, fs); 53 | 54 | const prog = gl.createProgram(); 55 | gl.attachShader(prog, vert); 56 | gl.attachShader(prog, frag); 57 | gl.linkProgram(prog); 58 | 59 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 60 | console.error(gl.getProgramInfoLog(prog)); 61 | gl.deleteShader(vert); 62 | gl.deleteShader(frag); 63 | return null; 64 | } 65 | 66 | return prog; 67 | } -------------------------------------------------------------------------------- /docs/overview.md: -------------------------------------------------------------------------------- 1 | # API Overview 2 | 3 | 8 | 9 | Working with Klein is designed to be as simple as it is efficient. To "grok" the API, it suffices to 10 | understand the API in terms of the primary operations supported through the primary geometric entities. 11 | 12 | The entities provided are Euclidean objects ([`points`](../api/point), [`lines`](../api/lines), [`planes`](../api/plane)), objects that arise in 13 | Projective space ([`directions`](../api/dir), [`ideal lines`](../api/lines)), 14 | and geometric actions ([`rotors`](../api/rotor), [`translators`](../api/translator), 15 | [`motors`](../api/motor), and [`planes`](../api/plane)). 16 | 17 | | Class | Description | 18 | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 19 | | `plane` | A plane is the manifestation of a reflection of $\mathbf{E}^3$ (consider the set of fixed points of a reflection). | 20 | | `line` | A line is the manifestation of a rotation of $\mathbf{E}^3$ (consider the set of fixed points of a rotation). | 21 | | `branch` | A branch is a line through the origin | 22 | | `ideal_line` | An ideal line is a line at infinity | 23 | | `point` | A point is the manifestation of a roto-reflection of $\mathbf{E}^3$ (which has a single fixed point) | 24 | | `direction` | A direction is modeled as a point at infinity (homogeneous weight $0$) | 25 | | `rotor` | A rotor is the product of two intersecting planes (generating a rotation) | 26 | | `translator` | A translator is the product of two parallel planes (generating a translation) | 27 | | `motor` | A motor is a screw combining a rotation and translation along a screw axis | 28 | | `dual` | A dual is the sum of a scalar and pseudoscalar quantity. Dual numbers show up in a number of contexts, including the factorization of a motor axis, and as the result of several meet and join operations. | 29 | 30 | !!! note 31 | 32 | The call operator on a plane performs a _reflection_ of the passed entity through the plane. The call operator on a rotor performs a _rotation_ of the entity. The translator 33 | translates, and the motor performs a combination of a rotation and a translation. 34 | 35 | The multivector operations such as the geometric product, exterior product, regressive product, etc. 36 | are supported for all the listed entities above via the following operator table: 37 | 38 | | Operator | Description | 39 | | -------- | --------------------------------------------- | 40 | | `+` | Addition | 41 | | `-` | Subtraction | 42 | | `* s` | Uniform scaling by a float or int `s` | 43 | | `/ s` | Uniform inverse scaling by a float or int `s` | 44 | | `*` | [Geometric Product](../api/gp) | 45 | | `^` | [Exterior Product](../api/ext) | 46 | | `&` | [Regressive Product](../api/reg) | 47 | | `|` | [Symmetric Inner Product](../api/dot) | 48 | | `!` | [Poincaré Dual](../api/dual) | 49 | 50 | !!! note 51 | 52 | Addition and subtraction is only supported between arguments of the same type. For example, two planes can be added together, but not a `plane` and a `rotor`. 53 | 54 | !!! tip 55 | 56 | For the geometric actions (implemented via the conjugation operator $xP\widetilde{x}$), the call 57 | operator should be used on the action itself as this will invoke a more optimized routine than 58 | invoking `x * P * ~x` manually. For example, given a rotor `r` and a point `p`, applying the rotor 59 | to `p` as `r(p)` will be faster with equivalent results to `r * p * ~r`. 60 | 61 | Similarly, while the regressive product between two entities `a` and `b` can be computed as 62 | `!(!a ^ !b)`, the explicit `a & b` should be preferred for efficiency. 63 | 64 | There are a few additional freestanding functions to perform various tasks. 65 | 66 | | Function | Description | 67 | | --------- | ---------------------------------------------------------------------------- | 68 | | `project` | Projects the first argument onto the second argument and returns the result. | 69 | | `log` | Takes the logarithm of the argument and returns the result. | 70 | | `exp` | Computes the exponential of the argument and returns the result. | 71 | | `sqrt` | Computes the square root of a rotor or motor and returns the result. | 72 | 73 | !!! note 74 | 75 | Throughout the API, you may see functions and methods marked with `KLN_VEC_CALL`. This macro expands to `__vectorcall` to ensure that register passing is used on MSVC. 76 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | Klein is header only, so you may use it by adding the contents of the `public` 2 | directory to your include path. If you use [cmake](https://cmake.org/), you 3 | may opt to link the `klein` target interface. If you have CMake 3.15 or later, 4 | you can use the following snippet to easily fetch Klein into your build tree: 5 | 6 | ```cmake 7 | include(FetchContent) 8 | 9 | # This tracks the latest commit on the master branch of the Klein 10 | # repository. Instead of `origin/master`, you can specify a specific 11 | # commit hash or tag. 12 | FetchContent_Declare( 13 | klein 14 | GIT_REPOSITORY https://github.com/jeremyong/Klein.git 15 | GIT_TAG origin/master 16 | ) 17 | FetchContent_MakeAvailable(klein) 18 | 19 | # Now, you can use target_link_libraries(your_lib PUBLIC klein::klein) 20 | # If you can target SSE4.1 (~97% market penetration), you can link against 21 | # the target klein::klein_sse42 instead. 22 | ``` 23 | 24 | The primary "catch-all" header provided can be included using `#include `. 25 | The `klein.hpp` header includes the following: 26 | 27 | | File | Purpose | 28 | | ----------------------- | ----------------------------------------------------------------- | 29 | | `direction.hpp` | Defines the `direction` class. | 30 | | `dual.hpp` | Defines the `dual` class. | 31 | | `plane.hpp` | Defines the `plane` class. | 32 | | `point.hpp` | Defines the `point` class. | 33 | | `line.hpp` | Defines the `line`, `branch`, and `ideal_line` classes. | 34 | | `rotor.hpp` | Defines the `rotor` class. | 35 | | `translator.hpp` | Defines the `translator` class. | 36 | | `motor.hpp` | Defines the `motor` class. | 37 | | `geometric_product.hpp` | Defines the geometric product between all supported entities. | 38 | | `meet.hpp` | Defines the exterior product between all supported entities. | 39 | | `join.hpp` | Defines the regressive product between all supported entities. | 40 | | `inner_product.hpp` | Defines the inner product between all supported entities. | 41 | | `project.hpp` | Defines the `project` function to project between entities. | 42 | | `exp_log.hpp` | Defines the `exp` and `log` functions between supported entities. | 43 | | `util.hpp` | Defines various mathematical constants and helper routines. | 44 | 45 | Here's a simple snippet to get you started: 46 | 47 | ```c++ 48 | #include 49 | 50 | // Create a rotor representing a pi/2 rotation about the z-axis 51 | // Normalization is done automatically 52 | rotor r{kln::pi * 0.5f, 0.f, 0.f, 1.f}; 53 | 54 | // Create a translator that represents a translation of 1 unit 55 | // in the yz-direction. Normalization is done automatically. 56 | translator t{1.f, 0.f, 1.f, 1.f}; 57 | 58 | // Create a motor that combines the action of the rotation and 59 | // translation above. 60 | motor m = r * t; 61 | 62 | // Construct a point at position (1, 0, 0) 63 | point p1{1, 0, 0}; 64 | 65 | // Apply the motor to the point. This is equivalent to the conjugation 66 | // operator m * p1 * ~m where * is the geometric product and ~ is the 67 | // reverse operation. 68 | point p2 = m(p1); 69 | 70 | // We could have also written p2 = m * p1 * ~m but this will be slower 71 | // because the call operator eliminates some redundant or cancelled 72 | // computation. 73 | // point p2 = m * p1 * ~m; 74 | 75 | // We can access the coordinates of p2 with p2.x(), p2.y(), p2.z(), 76 | // and p2.w(), where p.2w() is the homogeneous coordinate (initialized 77 | // to one). It is recommended to localize coordinate access in this way 78 | // as it requires unpacking storage that may occupy an SSE register. 79 | 80 | // Rotors and motors can produce 4x4 transformation matrices suitable 81 | // for upload to a shader or for interoperability with code expecting 82 | // matrices as part of its interface. The matrix returned in this way 83 | // is a column-major matrix 84 | mat4x4 m_matrix = m.as_mat4x4(); 85 | ``` 86 | 87 | The spherical interpolation (aka slerp) employed to produce smooth incremental 88 | rotations/transformations in the quaternion algebra is available in Klein 89 | using the exp and log functions as in the snippet below. 90 | 91 | ```c++ 92 | // Blend between two motors with a parameter t in the range [0, 1] 93 | kln::motor blend_motors(kln::motor const& a, kln::motor const& b, float t) 94 | { 95 | // Starting from a, the motor needed to get to b is b * ~a. 96 | // To perform this motion continuously, we can take the principal 97 | // branch of the logarithm of b * ~a, and subdivide it before 98 | // re-exponentiating it to produce a motor again. 99 | 100 | // In practice, this should be cached whenever possible. 101 | line motor_step = log(b * ~a); 102 | 103 | // exp(log(m)) = exp(t*log(m) + (1 - t)*log(m)) 104 | // = exp(t*(log(m))) * exp((1 - t)*log(m)) 105 | motor_step *= t; 106 | 107 | // The exponential of the step here can be cached if the blend occurs 108 | // with fixed steps toward the final motor. Compose the interpolated 109 | // result with the start motor to produce the intermediate blended 110 | // motor. 111 | return exp(motor_step) * a; 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /docs/references.md: -------------------------------------------------------------------------------- 1 | Klein is deeply indebted to several members of the GA community and their work. Beyond the works 2 | cited here, the author stands of the shoulders of giants (Felix _Klein_, Sophus Lie, Arthur Cayley, 3 | William Rowan Hamilton, Julius Plücker, and William Kingdon Clifford, among others). 4 | 5 | [1] 6 | Gunn, Charles G. (2019). 7 | Course notes Geometric Algebra for Computer Graphics, SIGGRAPH 2019. 8 | [arXiv link](https://arxiv.org/abs/2002.04509) 9 | 10 | [2] 11 | Steven De Keninck and Charles Gunn. (2019). 12 | SIGGRAPH 2019 Geometric Algebra Course. 13 | [youtube link](https://www.youtube.com/watch?v=tX4H_ctggYo) 14 | 15 | [3] 16 | Leo Dorst, Daniel Fontijne, Stephen Mann. (2007) 17 | Geometric Algebra for Computer Science. 18 | Burlington, MA: Morgan Kaufmann Publishers Inc. 19 | -------------------------------------------------------------------------------- /docs/shell.md: -------------------------------------------------------------------------------- 1 | In the [`sym`](https://github.com/jeremyong/Klein/tree/master/sym) directory resides 2 | code for a lightweight computer algebra system that can evaluate geometric algebra 3 | expressions symbolically. This tool was used to validate the implementation of 4 | Klein and verify various optimizations in its SSE routines (nearly every operation 5 | is fully vectorized). 6 | 7 | The tool is compiled by default as the executable `klein_shell` when Klein is built 8 | as a standalone project. If you wish to enable it if transitively including the 9 | project, you'll need to set the CMake option `KLEIN_BUILD_SYM` to `ON`. 10 | 11 | To use the shell, you can either run it interactively from a terminal, or you can 12 | pipe text files to it. For example, suppose we had a file that looked like: 13 | 14 | ``` 15 | # test.klein 16 | # My klein test 17 | 18 | (a0 e123 + a1 e021 + a2 e013 + a3 e032) * (b0 + b1 e12 + b2 e31 + b3 e23) 19 | ``` 20 | 21 | Running the command `cat test.klein | ./klein_shell` will produce the following 22 | output: 23 | 24 | ``` 25 | # test.klein 26 | # My klein test 27 | 28 | a0 b0 e0 + -a0 b3 e012 + a0 b2 e013 + -a0 b1 e023 29 | ``` 30 | 31 | Whitespace and lines starting with a `#` are echoed back in the output and the 32 | expressions provided are evaluated and simplified symbolically. Numerical constants 33 | are permitted. 34 | 35 | The grammar is relatively simple and the Klein shell is a work in progress. 36 | Several examples are provided in the repository to examine to understand proper 37 | usage. They are stored in the [`scripts`](https://github.com/jeremyong/Klein/tree/master/scripts) 38 | folder and are used to both demonstrate GA concepts and validate existing code 39 | and test cases. 40 | -------------------------------------------------------------------------------- /docs/tutorial/exterior_algebra.md: -------------------------------------------------------------------------------- 1 | !!! danger 2 | 3 | You are currently reading a DRAFT that is available publicly to facilitate collaboration 4 | 5 | In the [introduction](../intro), we considered a set of three basis vectors $\ee_1$, 6 | $\ee_2$, and $\ee_3$. In addition, we pontificated a bit on why restricting ourselves to 7 | vectors can cause issues, and argued for the need for a richer structure to match the 8 | richness of the geometry. But how should we go about doing this? 9 | 10 | In 3-dimensions, it seems a bit unfair that only "arrows" 11 | can be represented. After all, our world is filled with objects that have area and 12 | volume too. Suppose we wanted to represent a unit area in the x-y plane. Let's give it 13 | a name, say $\ee_{12}$. It seems reasonable that the areas $\ee_{13}$ and $\ee_{23}$. 14 | But wait! Our choice of of index order seems a bit arbitrary. What about $\ee_{21}$, 15 | $\ee_{31}$ and $\ee_{32}$. If we follow our nose a bit, it seems reasonable that the 16 | area represented by $\ee_{12}$ should equal $-\ee_{21}$. After all, they possess opposite 17 | orientations from one another. 18 | 19 |
20 | 59 | 60 | What about elements with two repeated indices? Like $\ee_{11}$ 61 | or $\ee_{33}$? Well, such elements can't reasonably span any area, so let's get back to 62 | how we should handle those in a moment. Like the unit vectors, it seems sensible to allow 63 | us to scale these area-elements with a weight (like $3\ee_{12}$) and add them together 64 | to create areas of arbitrary weight and orientation. So, for example, let's try to add 65 | $\ee_{12} + 2\ee_{23}$. What should its orientation be? 66 | 67 | TODO! 68 | 69 | For volumes, we have one choice that spans a non-zero volume which is $\ee_{123}$. We 70 | could have also chosen a different basis ordering, which again we'll get back to later. 71 | Let's call our single-index elements _vectors_ as before, the two-index elements _bivectors_, 72 | and the three-index elements _trivectors_. 73 | 74 | TODO! 75 | -------------------------------------------------------------------------------- /docs/tutorial/intro.md: -------------------------------------------------------------------------------- 1 | !!! danger 2 | 3 | You are currently reading a DRAFT that is available publicly to facilitate collaboration 4 | 5 | 6 | 7 | This guide is meant to be a gentle intro to Projective Geometric Algebra, 8 | also referred to as $\mathbf{P}(\mathbb{R}^*_{3, 0, 1})$. It assumes no 9 | knowledge of quaternions and dual-quaternions, but if you have some familiarity 10 | with either, you may find yourself armed with new insights and appreciation. Also not assumed 11 | is any knowledge of abstract algebra, or any specific algebra closely related to Geometric 12 | Algebra (e.g. exterior algebra, Clifford Algebra, etc.). 13 | 14 | The emphasis first is on just getting familiar with the notation, the various 15 | operations and what they do. The [references](../../references) page on the left contains 16 | some excellent material if you prefer a bottom up approach. Here though, the goal will 17 | be to build your intuition primarily through examples, and then introduce the formalism 18 | afterwards. 19 | 20 | !!! tip 21 | 22 | Grab a pen and paper! You are expected to work out a number of expressions by hand 23 | and _see_ for yourself that the formulae behave as advertised. Further, drawing pictures 24 | is _very_ important to maintain the linkage between the algebra and the geometry. 25 | 26 | We're going to go straight to 3D, so hang on tight. Let's start with three perfectly 27 | ordinary basis vectors, $\ee_1$, $\ee_2$ and $\ee_3$. Now, normally when we think about 28 | vectors, we imagine that they have some length and direction. In this case, let's have 29 | $\ee_1$ point in the x-direction, $\ee_2$ point in the y-direction, $\ee_3$ point in the 30 | z-direction, and give all of them unit length. Each one of these basis vectors can be 31 | scaled by a weight, and we can take linear combinations of them to create any vector 32 | in our 3D space. So far, everything behaves just like your good ol' 3D vector space. 33 | 34 |
35 | 72 | 73 | Now, let's pause and consider for a moment what our vector space might be lacking. With 74 | vectors alone, we can certainly come up with ways to represent all sorts of things. 75 | Sometimes, vectors are arrows from the origin. Other times, we use vectors to mean the 76 | point terminated at by that arrow. Still other times, a vector is used to represent a 77 | plane through the origin by encoding the normal to the plane. In a way, vectors are 78 | somewhat encumbered due to the need to represent _all_ the various entities in geometry 79 | one way or another. But even if we try, we'll find that there are still aspects of geometry 80 | that we can't reasonably or easily represent with vectors. For example, what if the 81 | plane didn't go through the origin? I suppose we could use two vectors, one for the normal, 82 | and one to describe a point in the plane. What about a rotation? Maybe we use a vector 83 | for the axis, and the length includes the angle. Translations? Maybe the vector points in the 84 | direction of the translation and the length encodes the displacement. Do you see 85 | an issue with the way things are going with this thought exercise? All of these 86 | interpretations of what a "vector" is are not mutually compatible with one another! We 87 | certainly can't add a vector intended to be used as a rotation axis and a vector intended 88 | to be used as a plane normal and expect to have a consistent interpretation of the result. 89 | All of them need to be treated distinctly and live "in their own space" as it were, with 90 | very delicate code to keep the invisible boundaries between them uncrossed. 91 | 92 | Needless to say, mathematically, the situation described above leaves much to be desired. 93 | What we'd like is an algebra (aka the Geometric Algebra) that could describe all the entities 94 | we need (points, lines, planes, rotations, translations, to name a few) in a _unifying_ 95 | framework glued by an operation which has a sensible meaning when its operands are any 96 | of the listed entities (aka the geometric product). To make this a reality though, we're 97 | going to need to move past our vector space and limited set of operations to an algebra 98 | that is much richer. This algebra (the Geometric Algebra) will have more operations than 99 | you're used to, and more "things" than you're used to, but that's to be expected. After all, 100 | geometry is far richer than just arrows emanating from the origin. Embracing the additional 101 | structure is in some sense, akin to embracing the reality that is geometry itself, and 102 | acknowledge that the algebra is going to need to play some catch up to describe the 103 | geometry more aptly. So, then, aside from vectors, what else do we need? In the next 104 | section, we'll describe the exterior algebra as a stepping stone to geometric algebra, 105 | so see you there! 106 | -------------------------------------------------------------------------------- /glsl/klein.glsl: -------------------------------------------------------------------------------- 1 | #ifndef KLEIN_GUARD 2 | #define KLEIN_GUARD 3 | 4 | // p0 -> (e0, e1, e2, e3) 5 | // p1 -> (1, e23, e31, e12) 6 | // p2 -> (e0123, e01, e02, e03) 7 | // p3 -> (e123, e032, e013, e021) 8 | 9 | struct kln_plane 10 | { 11 | vec4 p0; 12 | }; 13 | 14 | struct kln_line 15 | { 16 | vec4 p1; 17 | vec4 p2; 18 | }; 19 | 20 | // If integrating this library with other code, remember that the point layout 21 | // here has the homogeneous component in p3[0] and not p3[3]. The swizzle to 22 | // get the vec3 Cartesian representation is p3.yzw 23 | struct kln_point 24 | { 25 | vec4 p3; 26 | }; 27 | 28 | struct kln_rotor 29 | { 30 | vec4 p1; 31 | }; 32 | 33 | struct kln_translator 34 | { 35 | vec4 p2; 36 | }; 37 | 38 | struct kln_motor 39 | { 40 | vec4 p1; 41 | vec4 p2; 42 | }; 43 | 44 | kln_rotor kln_mul(in kln_rotor a, in kln_rotor b) 45 | { 46 | kln_rotor c; 47 | c.p1 = a.p1.x * b.p1; 48 | c.p1 -= a.p1.yzwy * b.p1.ywyz; 49 | 50 | vec4 t = a.p1.zyzw * b.p1.zxxx; 51 | t += a.p1.wwyz * b.p1.wzwy; 52 | t.x = -t.x; 53 | 54 | c.p1 += t; 55 | return c; 56 | } 57 | 58 | kln_translator kln_mul(in kln_translator a, in kln_translator b) 59 | { 60 | // (1 + a.p2) * (1 + b.p2) = 1 + a.p2 + b.p2 61 | kln_translator c; 62 | c.p2 = a.p2 + b.p2; 63 | return c; 64 | } 65 | 66 | kln_motor kln_mul(in kln_motor a, in kln_motor b) 67 | { 68 | kln_motor c; 69 | vec4 a_zyzw = a.p1.zyzw; 70 | vec4 a_ywyz = a.p1.ywyz; 71 | vec4 a_wzwy = a.p1.wzwy; 72 | vec4 c_wwyz = b.p1.wwyz; 73 | vec4 c_yzwy = b.p1.yzwy; 74 | 75 | c.p1 = a.p1.x * b.p1; 76 | vec4 t = a_ywyz * c_yzwy; 77 | t += a_zyzw * b.p1.zxxx; 78 | t.x = -t.x; 79 | c.p1 += t; 80 | c.p1 -= a_wzwy * c_wwyz; 81 | 82 | c.p2 = a.p1.x * b.p2; 83 | c.p2 += a.p2 * b.p1.x; 84 | c.p2 += a_ywyz * b.p2.yzwy; 85 | c.p2 += a.p2.ywyz * c_yzwy; 86 | t = a_zyzw * b.p2.zxxx; 87 | t += a_wzwy * b.p2.wwyz; 88 | t += a.p2.zxxx * b.p1.zyzw; 89 | t += a.p2.wzwy * c_wwyz; 90 | t.x = -t.x; 91 | c.p2 -= t; 92 | return c; 93 | } 94 | 95 | kln_plane kln_apply(in kln_rotor r, in kln_plane p) 96 | { 97 | vec4 dc_scale = vec4(1.0, 2.0, 2.0, 2.0); 98 | vec4 neg_low = vec4(-1.0, 1.0, 1.0, 1.0); 99 | 100 | vec4 t1 = r.p1.zxxx * r.p1.zwyz; 101 | t1 += r.p1.yzwy * r.p1.yyzw; 102 | t1 *= dc_scale; 103 | 104 | vec4 t2 = r.p1 * r.p1.xwyz; 105 | t2 -= (r.p1.wxxx * r.p1.wzwy) * neg_low; 106 | t2 *= dc_scale; 107 | 108 | vec4 t3 = r.p1 * r.p1; 109 | t3 -= r.p1.xwyz * r.p1.xwyz; 110 | t3 += r.p1.xxxx * r.p1.xxxx; 111 | t3 -= r.p1.xzwy * r.p1.xzwy; 112 | 113 | // TODO: provide variadic rotor-plane application 114 | kln_plane q; 115 | q.p0 = t1 * p.p0.xzwy; 116 | q.p0 += t2 * p.p0.xwyz; 117 | q.p0 += t3 * p.p0; 118 | return q; 119 | } 120 | 121 | kln_plane kln_apply(in kln_motor m, in kln_plane p) 122 | { 123 | vec4 dc_scale = vec4(1.0, 2.0, 2.0, 2.0); 124 | vec4 neg_low = vec4(-1.0, 1.0, 1.0, 1.0); 125 | 126 | vec4 t1 = m.p1.zxxx * m.p1.zwyz; 127 | t1 += m.p1.yzwy * m.p1.yyzw; 128 | t1 *= dc_scale; 129 | 130 | vec4 t2 = m.p1 * m.p1.xwyz; 131 | t2 -= (m.p1.wxxx * m.p1.wzwy) * neg_low; 132 | t2 *= dc_scale; 133 | 134 | vec4 t3 = m.p1 * m.p1; 135 | t3 -= m.p1.xwyz * m.p1.xwyz; 136 | t3 += m.p1.xxxx * m.p1.xxxx; 137 | t3 -= m.p1.xzwy * m.p1.xzwy; 138 | 139 | vec4 t4 = m.p1.x * m.p2; 140 | t4 += m.p1.xzwy * m.p2.xwyz; 141 | t4 += m.p1 * m.p2.x; 142 | t4 -= m.p1.xwyz * m.p2.xzwy; 143 | t4 *= vec4(0.0, 2.0, 2.0, 2.0); 144 | 145 | // TODO: provide variadic motor-plane application 146 | kln_plane q; 147 | q.p0 = t1 * p.p0.xzwy; 148 | q.p0 += t2 * p.p0.xwyz; 149 | q.p0 += t3 * p.p0; 150 | q.p0 += vec4(dot(t4, p.p0), 0.0, 0.0, 0.0); 151 | return q; 152 | } 153 | 154 | kln_point kln_apply(in kln_rotor r, in kln_point p) 155 | { 156 | vec4 scale = vec4(0, 2, 2, 2); 157 | 158 | vec4 t1 = r.p1 * r.p1.xwyz; 159 | t1 -= r.p1.x * r.p1.xzwy; 160 | t1 *= scale; 161 | 162 | vec4 t2 = r.p1.x * r.p1.xwyz; 163 | t2 += r.p1.xzwy * r.p1; 164 | t2 *= scale; 165 | 166 | vec4 t3 = r.p1 * r.p1; 167 | t3 += r.p1.yxxx * r.p1.yxxx; 168 | vec4 t4 = r.p1.zwyz * r.p1.zwyz; 169 | t4 += r.p1.wzwy * r.p1.wzwy; 170 | t3 -= t4 * vec4(-1.0, 1.0, 1.0, 1.0); 171 | 172 | // TODO: provide variadic rotor-point application 173 | kln_point q; 174 | q.p3 = t1 * p.p3.xwyz; 175 | q.p3 += t2 * p.p3.xzwy; 176 | q.p3 += t3 * p.p3; 177 | return q; 178 | } 179 | 180 | kln_point kln_apply(in kln_motor m, in kln_point p) 181 | { 182 | vec4 scale = vec4(0, 2, 2, 2); 183 | 184 | vec4 t1 = m.p1 * m.p1.xwyz; 185 | t1 -= m.p1.x * m.p1.xzwy; 186 | t1 *= scale; 187 | 188 | vec4 t2 = m.p1.x * m.p1.xwyz; 189 | t2 += m.p1.xzwy * m.p1; 190 | t2 *= scale; 191 | 192 | vec4 t3 = m.p1 * m.p1; 193 | t3 += m.p1.yxxx * m.p1.yxxx; 194 | vec4 t4 = m.p1.zwyz * m.p1.zwyz; 195 | t4 += m.p1.wzwy * m.p1.wzwy; 196 | t3 -= t4 * vec4(-1.0, 1.0, 1.0, 1.0); 197 | 198 | t4 = m.p1.xzwy * m.p2.xwyz; 199 | t4 -= m.p1.x * m.p2; 200 | t4 -= m.p1.xwyz * m.p2.xzwy; 201 | t4 -= m.p1 * m.p2.x; 202 | t4 *= scale; 203 | 204 | // TODO: provide variadic motor-point application 205 | kln_point q; 206 | q.p3 = t1 * p.p3.xwyz; 207 | q.p3 += t2 * p.p3.xzwy; 208 | q.p3 += t3 * p.p3; 209 | q.p3 += t4 * p.p3.x; 210 | return q; 211 | } 212 | 213 | // If no entity is provided as the second argument, the motor is 214 | // applied to the origin. 215 | // NOTE: The motor MUST be normalized for the result of this operation to be 216 | // well defined. 217 | kln_point kln_apply(in kln_motor m) 218 | { 219 | kln_point p; 220 | p.p3 = m.p1 * m.p2.x; 221 | p.p3 += m.p1.x * m.p2; 222 | p.p3 += m.p1.xwyz * m.p2.xzwy; 223 | p.p3 = m.p1.xzwy * m.p2.xwyz - p.p3; 224 | p.p3 *= vec4(0.0, 2.0, 2.0, 2.0); 225 | p.p3.x = 1.0; 226 | return p; 227 | } 228 | 229 | #endif // KLEIN_GUARD -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: klein 2 | theme: 3 | name: material 4 | custom_dir: 'theme' 5 | palette: 6 | primary: 'teal' 7 | accent: 'deep orange' 8 | favicon: 'img/favicon.ico' 9 | feature: 10 | tabs: false 11 | logo: 12 | icon: 'flip' 13 | site_url: https://jeremyong.com/klein/ 14 | repo_url: https://github.com/jeremyong/klein/ 15 | edit_uri: '' 16 | repo_name: 'jeremyong/klein' 17 | site_description: Klein - Realtime Projective Geometric Algebra 18 | site_author: Jeremy Ong 19 | copyright: 'Klein Copyright © 2020 Jeremy Ong' 20 | google_analytics: ['UA-10576233-1', 'auto'] 21 | 22 | plugins: 23 | - search 24 | 25 | nav: 26 | - 'Home': 'index.md' 27 | - 'Quick Start': 'quickstart.md' 28 | - 'API': 29 | - 'Overview': 'overview.md' 30 | - 'GPU Support': 'gpu.md' 31 | - 'Plane': 'api/plane.md' 32 | - 'Lines': 'api/lines.md' 33 | - 'Point': 'api/point.md' 34 | - 'Direction': 'api/dir.md' 35 | - 'Rotor': 'api/rotor.md' 36 | - 'Translator': 'api/translator.md' 37 | - 'Motor': 'api/motor.md' 38 | - 'Geometric Product': 'api/gp.md' 39 | - 'Exterior Product': 'api/ext.md' 40 | - 'Poincare Dual': 'api/dual.md' 41 | - 'Regressive Product': 'api/reg.md' 42 | - 'Inner Product': 'api/dot.md' 43 | - 'Exponential/Logarithm': 'api/exp_log.md' 44 | - 'Projections': 'api/proj.md' 45 | - 'Dual Numbers': 'api/dualn.md' 46 | - 'Geometry Potpourri': 'geometry-potpourri.md' 47 | - 'Tutorial': 48 | - 'Introduction': 'tutorial/intro.md' 49 | - 'Exterior Algebra': 'tutorial/exterior_algebra.md' 50 | - 'Case Studies': 51 | - 'Basic Skeletal Animation': 'case_studies/ga_skeletal_animation.md' 52 | - 'Klein Shell': 'shell.md' 53 | - 'Performance': 'perf.md' 54 | - 'Discord': 'discord.md' 55 | - 'References': 'references.md' 56 | 57 | extra_css: 58 | - 'css/extra.css' 59 | - 'https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css' 60 | 61 | extra_javascript: 62 | - 'js/extra.js' 63 | - 'https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js' 64 | - 'https://cdn.jsdelivr.net/npm/ganja.js@1.0.169' 65 | 66 | markdown_extensions: 67 | - abbr 68 | - admonition 69 | - attr_list 70 | - codehilite 71 | - def_list 72 | - footnotes 73 | - pymdownx.arithmatex: 74 | generic: True 75 | - pymdownx.details 76 | - pymdownx.superfences 77 | - pymdownx.mark 78 | - pymdownx.extrarawhtml 79 | - toc 80 | - sane_lists 81 | 82 | extra: 83 | social: 84 | - type: 'github' 85 | link: 'https://github.com/jeremyong' 86 | - type: 'twitter' 87 | link: 'https://twitter.com/m_ninepoints' 88 | - type: 'linkedin' 89 | link: 'https://www.linkedin.com/in/jeremycong' 90 | -------------------------------------------------------------------------------- /perf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | if(NOT WIN32) 4 | find_package(glm) 5 | endif() 6 | 7 | if(NOT glm_FOUND) 8 | FetchContent_Declare( 9 | glm 10 | GIT_REPOSITORY https://github.com/g-truc/glm.git 11 | GIT_TAG 0.9.9.7 12 | ) 13 | FetchContent_MakeAvailable(glm) 14 | endif() 15 | 16 | FetchContent_Declare( 17 | rtm 18 | GIT_REPOSITORY git@github.com:nfrechette/rtm.git 19 | GIT_TAG origin/master 20 | ) 21 | if(NOT rtm_POPULATED) 22 | FetchContent_Populate(rtm) 23 | # RTM doesn't provide an option to avoid building tests and doesn't actually have 24 | # a nice cmake interface target to link against, so don't add it as a subdirectory 25 | endif() 26 | 27 | FetchContent_Declare( 28 | mc_ruler 29 | GIT_REPOSITORY https://github.com/jeremyong/mc_ruler.git 30 | GIT_TAG origin/master 31 | ) 32 | FetchContent_MakeAvailable(mc_ruler) 33 | 34 | include(MCRuler) 35 | 36 | if(CMAKE_BUILD_TYPE MATCHES "Release") 37 | add_library(klein_perf klein_perf.cpp) 38 | target_link_libraries(klein_perf PRIVATE mc_ruler::mc_ruler klein) 39 | mc_ruler(klein_perf SOURCES klein_perf.cpp) 40 | 41 | add_library(glm_perf glm_perf.cpp) 42 | target_link_libraries(glm_perf PRIVATE mc_ruler::mc_ruler glm) 43 | mc_ruler(glm_perf SOURCES glm_perf.cpp) 44 | 45 | add_library(rtm_perf rtm_perf.cpp) 46 | target_link_libraries(rtm_perf PRIVATE mc_ruler::mc_ruler) 47 | target_include_directories(rtm_perf PRIVATE ${rtm_SOURCE_DIR}/includes) 48 | mc_ruler(rtm_perf SOURCES rtm_perf.cpp) 49 | endif() -------------------------------------------------------------------------------- /perf/glm_perf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | glm::quat rotor_composition(glm::quat const& a, glm::quat const& b) 8 | { 9 | MC_MEASURE_BEGIN(rotor_composition); 10 | auto out = a * b; 11 | MC_MEASURE_END(); 12 | return out; 13 | } 14 | 15 | glm::vec4 motor_application(glm::dualquat const& a, glm::vec4 const& b) 16 | { 17 | MC_MEASURE_BEGIN(motor_application); 18 | auto out = glm::mat3x4_cast(a) * b; 19 | MC_MEASURE_END(); 20 | return out; 21 | } 22 | 23 | glm::mat3x4 motor_to_mat(glm::dualquat const& a) 24 | { 25 | MC_MEASURE_BEGIN(motor_to_mat); 26 | auto out = glm::mat3x4_cast(a); 27 | MC_MEASURE_END(); 28 | return out; 29 | } -------------------------------------------------------------------------------- /perf/klein_perf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | kln::rotor rotor_add(kln::rotor const& a, kln::rotor const& b) 5 | { 6 | MC_MEASURE_BEGIN(rotor_add); 7 | auto out = a + b; 8 | MC_MEASURE_END(); 9 | return out; 10 | } 11 | 12 | kln::rotor rotor_composition(kln::rotor const& a, kln::rotor const& b) 13 | { 14 | MC_MEASURE_BEGIN(rotor_composition); 15 | auto out = a * b; 16 | MC_MEASURE_END(); 17 | return out; 18 | } 19 | 20 | kln::motor motor_composition(kln::motor const& a, kln::motor const& b) 21 | { 22 | MC_MEASURE_BEGIN(motor_composition); 23 | auto out = a * b; 24 | MC_MEASURE_END(); 25 | return out; 26 | } 27 | 28 | kln::point motor_application(kln::motor const& m, kln::point const& p) 29 | { 30 | MC_MEASURE_BEGIN(motor_application); 31 | auto out = m(p); 32 | MC_MEASURE_END(); 33 | return out; 34 | } 35 | 36 | kln::mat3x4 motor_to_mat3x4(kln::motor const& m) 37 | { 38 | MC_MEASURE_BEGIN(motor_to_mat3x4); 39 | auto out = m.as_mat3x4(); 40 | MC_MEASURE_END(); 41 | return out; 42 | } 43 | 44 | kln::mat4x4 motor_to_mat4x4(kln::motor const& m) 45 | { 46 | MC_MEASURE_BEGIN(motor_to_mat4x4); 47 | auto out = m.as_mat4x4(); 48 | MC_MEASURE_END(); 49 | return out; 50 | } -------------------------------------------------------------------------------- /perf/rtm_perf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | rtm::quatf rotor_composition(rtm::quatf const& a, rtm::quatf const& b) 6 | { 7 | MC_MEASURE_BEGIN(rotor_composition); 8 | auto out = rtm::quat_mul(a, b); 9 | MC_MEASURE_END(); 10 | return out; 11 | } 12 | 13 | rtm::vector4f qvvf_application(rtm::qvvf const& a, rtm::vector4f const& b) 14 | { 15 | MC_MEASURE_BEGIN(qvvf_application); 16 | auto out = rtm::qvv_mul_point3_no_scale(b, a); 17 | MC_MEASURE_END(); 18 | return out; 19 | } -------------------------------------------------------------------------------- /public/klein/detail/exp_log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86/x86_exp_log.hpp" -------------------------------------------------------------------------------- /public/klein/detail/exterior_product.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86/x86_exterior_product.hpp" -------------------------------------------------------------------------------- /public/klein/detail/geometric_product.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86/x86_geometric_product.hpp" -------------------------------------------------------------------------------- /public/klein/detail/inner_product.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86/x86_inner_product.hpp" -------------------------------------------------------------------------------- /public/klein/detail/matrix.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86/x86_matrix.hpp" -------------------------------------------------------------------------------- /public/klein/detail/sandwich.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86/x86_sandwich.hpp" -------------------------------------------------------------------------------- /public/klein/detail/sse.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86/x86_sse.hpp" -------------------------------------------------------------------------------- /public/klein/detail/x86/x86_exterior_product.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86_sse.hpp" 4 | 5 | namespace kln 6 | { 7 | namespace detail 8 | { 9 | // Partition memory layouts 10 | // LSB --> MSB 11 | // p0: (e0, e1, e2, e3) 12 | // p1: (1, e23, e31, e12) 13 | // p2: (e0123, e01, e02, e03) 14 | // p3: (e123, e032, e013, e021) 15 | 16 | KLN_INLINE void KLN_VEC_CALL ext00(__m128 a, 17 | __m128 b, 18 | __m128& KLN_RESTRICT p1_out, 19 | __m128& KLN_RESTRICT p2_out) noexcept 20 | { 21 | // (a1 b2 - a2 b1) e12 + 22 | // (a2 b3 - a3 b2) e23 + 23 | // (a3 b1 - a1 b3) e31 + 24 | 25 | // (a0 b1 - a1 b0) e01 + 26 | // (a0 b2 - a2 b0) e02 + 27 | // (a0 b3 - a3 b0) e03 28 | 29 | p1_out = _mm_mul_ps(a, KLN_SWIZZLE(b, 1, 3, 2, 0)); 30 | p1_out = KLN_SWIZZLE( 31 | _mm_sub_ps(p1_out, _mm_mul_ps(KLN_SWIZZLE(a, 1, 3, 2, 0), b)), 32 | 1, 33 | 3, 34 | 2, 35 | 0); 36 | 37 | p2_out = _mm_mul_ps(KLN_SWIZZLE(a, 0, 0, 0, 0), b); 38 | p2_out = _mm_sub_ps(p2_out, _mm_mul_ps(a, KLN_SWIZZLE(b, 0, 0, 0, 0))); 39 | 40 | // For both outputs above, we don't zero the lowest component because 41 | // we've arranged a cancelation 42 | } 43 | 44 | // Plane ^ Branch (branch is a line through the origin) 45 | KLN_INLINE void KLN_VEC_CALL extPB(__m128 a, __m128 b, __m128& p3_out) noexcept 46 | { 47 | // (a1 b1 + a2 b2 + a3 b3) e123 + 48 | // (-a0 b1) e032 + 49 | // (-a0 b2) e013 + 50 | // (-a0 b3) e021 51 | p3_out = _mm_mul_ps(_mm_mul_ps(KLN_SWIZZLE(a, 0, 0, 0, 1), b), 52 | _mm_set_ps(-1.f, -1.f, -1.f, 0.f)); 53 | 54 | p3_out = _mm_add_ss(p3_out, hi_dp(a, b)); 55 | } 56 | 57 | // p0 ^ p2 = p2 ^ p0 58 | KLN_INLINE void KLN_VEC_CALL ext02(__m128 a, __m128 b, __m128& p3_out) noexcept 59 | { 60 | // (a1 b2 - a2 b1) e021 61 | // (a2 b3 - a3 b2) e032 + 62 | // (a3 b1 - a1 b3) e013 + 63 | 64 | p3_out = _mm_mul_ps(a, KLN_SWIZZLE(b, 1, 3, 2, 0)); 65 | p3_out = KLN_SWIZZLE( 66 | _mm_sub_ps(p3_out, _mm_mul_ps(KLN_SWIZZLE(a, 1, 3, 2, 0), b)), 67 | 1, 68 | 3, 69 | 2, 70 | 0); 71 | } 72 | 73 | template 74 | KLN_INLINE void KLN_VEC_CALL ext03(__m128 a, __m128 b, __m128& p2) noexcept 75 | { 76 | // (a0 b0 + a1 b1 + a2 b2 + a3 b3) e0123 77 | p2 = dp(a, b); 78 | } 79 | 80 | // p0 ^ p3 = -p3 ^ p0 81 | template <> 82 | KLN_INLINE void KLN_VEC_CALL ext03(__m128 a, __m128 b, __m128& p2) noexcept 83 | { 84 | ext03(a, b, p2); 85 | p2 = _mm_xor_ps(p2, _mm_set_ss(-0.f)); 86 | } 87 | // The exterior products p2 ^ p2, p2 ^ p3, p3 ^ p2, and p3 ^ p3 all vanish 88 | } // namespace detail 89 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/detail/x86/x86_inner_product.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "x86_sse.hpp" 4 | 5 | namespace kln 6 | { 7 | namespace detail 8 | { 9 | // Partition memory layouts 10 | // LSB --> MSB 11 | // p0: (e0, e1, e2, e3) 12 | // p1: (1, e23, e31, e12) 13 | // p2: (e0123, e01, e02, e03) 14 | // p3: (e123, e032, e013, e021) 15 | 16 | KLN_INLINE void KLN_VEC_CALL dot00(__m128 a, __m128 b, __m128& p1_out) noexcept 17 | { 18 | // a1 b1 + a2 b2 + a3 b3 19 | p1_out = hi_dp(a, b); 20 | } 21 | 22 | // The symmetric inner product on these two partitions commutes 23 | KLN_INLINE void KLN_VEC_CALL dot03(__m128 a, 24 | __m128 b, 25 | __m128& KLN_RESTRICT p1_out, 26 | __m128& KLN_RESTRICT p2_out) noexcept 27 | { 28 | // (a2 b1 - a1 b2) e03 + 29 | // (a3 b2 - a2 b3) e01 + 30 | // (a1 b3 - a3 b1) e02 + 31 | // a1 b0 e23 + 32 | // a2 b0 e31 + 33 | // a3 b0 e12 34 | 35 | p1_out = _mm_mul_ps(a, KLN_SWIZZLE(b, 0, 0, 0, 0)); 36 | #ifdef KLEIN_SSE_4_1 37 | p1_out = _mm_blend_ps(p1_out, _mm_setzero_ps(), 1); 38 | #else 39 | p1_out 40 | = _mm_and_ps(p1_out, _mm_castsi128_ps(_mm_set_epi32(-1, -1, -1, 0))); 41 | #endif 42 | 43 | p2_out 44 | = KLN_SWIZZLE(_mm_sub_ps(_mm_mul_ps(KLN_SWIZZLE(a, 1, 3, 2, 0), b), 45 | _mm_mul_ps(a, KLN_SWIZZLE(b, 1, 3, 2, 0))), 46 | 1, 47 | 3, 48 | 2, 49 | 0); 50 | } 51 | 52 | KLN_INLINE void KLN_VEC_CALL dot11(__m128 a, __m128 b, __m128& p1_out) noexcept 53 | { 54 | p1_out = _mm_xor_ps(_mm_set_ss(-0.f), hi_dp_ss(a, b)); 55 | } 56 | 57 | KLN_INLINE void KLN_VEC_CALL dot33(__m128 a, __m128 b, __m128& p1_out) noexcept 58 | { 59 | // -a0 b0 60 | p1_out = _mm_mul_ps(_mm_set_ss(-1.f), _mm_mul_ss(a, b)); 61 | } 62 | 63 | // Point | Line 64 | KLN_INLINE void KLN_VEC_CALL dotPTL(__m128 a, __m128 b, __m128& p0) noexcept 65 | { 66 | // (a1 b1 + a2 b2 + a3 b3) e0 + 67 | // -a0 b1 e1 + 68 | // -a0 b2 e2 + 69 | // -a0 b3 e3 70 | 71 | __m128 dp = hi_dp_ss(a, b); 72 | p0 = _mm_mul_ps(KLN_SWIZZLE(a, 0, 0, 0, 0), b); 73 | p0 = _mm_xor_ps(p0, _mm_set_ps(-0.f, -0.f, -0.f, 0.f)); 74 | #ifdef KLEIN_SSE_4_1 75 | p0 = _mm_blend_ps(p0, dp, 1); 76 | #else 77 | p0 = _mm_add_ss(p0, dp); 78 | #endif 79 | } 80 | 81 | // Plane | Ideal Line 82 | template 83 | KLN_INLINE void KLN_VEC_CALL dotPIL(__m128 a, __m128 c, __m128& p0) noexcept 84 | { 85 | p0 = hi_dp(a, c); 86 | } 87 | 88 | template <> 89 | KLN_INLINE void KLN_VEC_CALL dotPIL(__m128 a, __m128 c, __m128& p0) noexcept 90 | { 91 | dotPIL(a, c, p0); 92 | p0 = _mm_xor_ps(p0, _mm_set_ss(-0.f)); 93 | } 94 | 95 | // Plane | Line 96 | template 97 | KLN_INLINE void KLN_VEC_CALL dotPL(__m128 a, __m128 b, __m128 c, __m128& p0) noexcept 98 | { 99 | // -(a1 c1 + a2 c2 + a3 c3) e0 + 100 | // (a2 b1 - a1 b2) e3 101 | // (a3 b2 - a2 b3) e1 + 102 | // (a1 b3 - a3 b1) e2 + 103 | 104 | p0 = _mm_mul_ps(KLN_SWIZZLE(a, 1, 3, 2, 0), b); 105 | p0 = _mm_sub_ps(p0, _mm_mul_ps(a, KLN_SWIZZLE(b, 1, 3, 2, 0))); 106 | p0 = _mm_sub_ss(KLN_SWIZZLE(p0, 1, 3, 2, 0), hi_dp_ss(a, c)); 107 | } 108 | 109 | template <> 110 | KLN_INLINE void KLN_VEC_CALL 111 | dotPL(__m128 a, __m128 b, __m128 c, __m128& p0) noexcept 112 | { 113 | // (a1 c1 + a2 c2 + a3 c3) e0 + 114 | // (a1 b2 - a2 b1) e3 115 | // (a2 b3 - a3 b2) e1 + 116 | // (a3 b1 - a1 b3) e2 + 117 | 118 | p0 = _mm_mul_ps(a, KLN_SWIZZLE(b, 1, 3, 2, 0)); 119 | p0 = _mm_sub_ps(p0, _mm_mul_ps(KLN_SWIZZLE(a, 1, 3, 2, 0), b)); 120 | p0 = _mm_add_ss(KLN_SWIZZLE(p0, 1, 3, 2, 0), hi_dp_ss(a, c)); 121 | } 122 | } // namespace detail 123 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/detail/x86/x86_matrix.hpp: -------------------------------------------------------------------------------- 1 | // File: x86_matrix.hpp 2 | // Purpose: Provide conversion routines from rotors, motors, and translators to 3 | // matrices 4 | // 5 | // Notes: 6 | // The preferred layout is a column-major layout as mat-mat and mat-vec 7 | // multiplication is more naturally implemented when defined this way. 8 | 9 | #pragma once 10 | 11 | #include "x86_sse.hpp" 12 | 13 | namespace kln 14 | { 15 | // Partition memory layouts 16 | // LSB --> MSB 17 | // p0: (e0, e1, e2, e3) 18 | // p1: (1, e23, e31, e12) 19 | // p2: (e0123, e01, e02, e03) 20 | // p3: (e123, e032, e013, e021) 21 | 22 | #if __cplusplus >= 201703L 23 | // Convert a motor to a column-major 4x4 24 | template 25 | KLN_INLINE void KLN_VEC_CALL mat4x4_12(__m128 b, 26 | [[maybe_unused]] __m128 const* c, 27 | __m128* out) noexcept 28 | { 29 | // The derivation of this conversion follows directly from the general 30 | // expansion of conjugating a point with a motor. See sw312 in 31 | // klein_sw.hpp for details. 32 | // 33 | // LSB 34 | // (2a0(b2 c3 - b0 c1 - b3 c2 - b1 c0) + 35 | // 2a3(b1 b3 - b0 b2) + 36 | // 2a2(b0 b3 + b2 b1) + 37 | // a1(b0^2 + b1^2 - b3^2 - b2^2)) e032 // x-coordinate 38 | // 39 | // (2a0(b3 c1 - b0 c2 - b1 c3 - b2 c0) + 40 | // 2a1(b2 b1 - b0 b3) + 41 | // 2a3(b0 b1 + b3 b2) + 42 | // a2(b0^2 + b2^2 - b1^2 - b3^2)) e013 + // y-coordinate 43 | // 44 | // (2a0(b1 c2 - b0 c3 - b2 c1 - b3 c0) + 45 | // 2a2(b3 b2 - b0 b1) + 46 | // 2a1(b0 b2 + b1 b3) + 47 | // a3(b0^2 + b3^2 - b2^2 - b1^2)) e021 + // z-coordinate 48 | // 49 | // a0(b0^2 + b1^2 + b2^2 + b3^2) e123 // w-coordinate 50 | // MSB 51 | 52 | // Store a number of scalar temporaries needed later 53 | float buf[4]; 54 | _mm_storeu_ps(buf, _mm_mul_ps(b, b)); 55 | float b0_2 = buf[0]; 56 | float b1_2 = buf[1]; 57 | float b2_2 = buf[2]; 58 | float b3_2 = buf[3]; 59 | 60 | // The first column of the matrix we need to produce contains the scale 61 | // factors of the x-coordinate (a1). This can be read off as: 62 | // 63 | // b0^2 + b1^2 - b3^2 - b2^2 64 | // 2(b1 b2 - b3 b0) 65 | // 2(b2 b0 + b1 b3) 66 | // 0 67 | __m128& c0 = out[0]; 68 | c0 = _mm_mul_ps(b, KLN_SWIZZLE(b, 0, 0, 2, 0)); 69 | __m128 tmp 70 | = _mm_mul_ps(KLN_SWIZZLE(b, 0, 1, 3, 1), KLN_SWIZZLE(b, 0, 3, 0, 1)); 71 | tmp = _mm_xor_ps(_mm_set_ps(0.f, 0.f, -0.f, 0.f), tmp); 72 | c0 = _mm_mul_ps(_mm_set_ps(0.f, 2.f, 2.f, 1.f), _mm_add_ps(c0, tmp)); 73 | c0 = _mm_sub_ps(c0, _mm_set_ss(b3_2 + b2_2)); 74 | 75 | // We can perform the same exercise for y (a2) (the second column): 76 | // 77 | // 2(b0 b3 + b2 b1) 78 | // (-b1^2 - b3^2 + b0^2 + b2^2) 79 | // 2(b2 b3 - b0 b1) 80 | // 0 81 | __m128& c1 = out[1]; 82 | c1 = _mm_mul_ps(b, KLN_SWIZZLE(b, 0, 3, 1, 3)); 83 | tmp = _mm_mul_ps(KLN_SWIZZLE(b, 0, 0, 3, 2), KLN_SWIZZLE(b, 0, 1, 3, 1)); 84 | tmp = _mm_xor_ps(_mm_set_ps(0.f, -0.f, 0.f, 0.f), tmp); 85 | c1 = _mm_mul_ps(_mm_set_ps(0.f, 2.f, -1.f, 2.f), _mm_add_ps(c1, tmp)); 86 | c1 = _mm_add_ps(c1, _mm_set_ps(0.f, 0.f, b0_2 + b2_2, 0.f)); 87 | 88 | // z (a3) 89 | // 90 | // 2(-b0 b2 + b1 b3) 91 | // 2(b1 b0 + b2 b3) 92 | // (-b2^2 + b0^2 + b3^2 - b1^2) 93 | // 0 94 | __m128& c2 = out[2]; 95 | c2 = _mm_xor_ps(_mm_set_ps(0.f, -0.f, 0.f, -0.f), 96 | _mm_mul_ps(b, KLN_SWIZZLE(b, 0, 2, 0, 2))); 97 | c2 = _mm_add_ps( 98 | c2, _mm_mul_ps(KLN_SWIZZLE(b, 0, 0, 2, 1), KLN_SWIZZLE(b, 0, 0, 3, 3))); 99 | c2 = _mm_mul_ps(c2, _mm_set_ps(0.f, 1.f, 2.f, 2.f)); 100 | c2 = _mm_add_ps(c2, _mm_set_ps(0.f, b3_2 - b1_2, 0.f, 0.f)); 101 | 102 | // And finally w (a0) 103 | // 104 | // 2(b2 c3 - b0 c1 - b3 c2 - b1 c0) 105 | // 2(b3 c1 - b1 c3 - b0 c2 - b2 c0) 106 | // 2(b1 c2 - b2 c1 - b0 c3 - b3 c0) 107 | // b0^2 + b1^2 + b2^2 + b3^2 108 | __m128& c3 = out[3]; 109 | if constexpr (Translate) 110 | { 111 | c3 = _mm_mul_ps(b, KLN_SWIZZLE(*c, 0, 1, 3, 1)); 112 | c3 = _mm_add_ps( 113 | c3, 114 | _mm_mul_ps(KLN_SWIZZLE(b, 0, 0, 0, 3), KLN_SWIZZLE(*c, 0, 3, 2, 2))); 115 | c3 = _mm_add_ps( 116 | c3, 117 | _mm_mul_ps(KLN_SWIZZLE(b, 0, 3, 2, 1), KLN_SWIZZLE(*c, 0, 0, 0, 0))); 118 | tmp = _mm_mul_ps(KLN_SWIZZLE(b, 0, 1, 3, 2), KLN_SWIZZLE(*c, 0, 2, 1, 3)); 119 | c3 = _mm_mul_ps(_mm_set_ps(0.f, 2.f, 2.f, 2.f), _mm_sub_ps(tmp, c3)); 120 | } 121 | if constexpr (Normalized) 122 | { 123 | # ifdef KLEIN_SSE_4_1 124 | c3 = _mm_blend_ps(c3, _mm_set_ps(1.f, 0.f, 0.f, 0.f), 0b1000); 125 | # else 126 | c3 = _mm_add_ps(c3, _mm_set_ps(1.f, 0.f, 0.f, 0.f)); 127 | # endif 128 | } 129 | else 130 | { 131 | # ifdef KLEIN_SSE_4_1 132 | c3 = _mm_blend_ps( 133 | c3, _mm_set_ps(b0_2 + b1_2 + b2_2 + b3_2, 0.f, 0.f, 0.f), 0b1000); 134 | # else 135 | c3 = _mm_add_ps(c3, _mm_set_ps(b0_2 + b1_2 + b2_2 + b3_2, 0.f, 0.f, 0.f)); 136 | # endif 137 | } 138 | } 139 | #else 140 | # include "x86_matrix_cxx11.inl" 141 | #endif 142 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/detail/x86/x86_sse.hpp: -------------------------------------------------------------------------------- 1 | // File: x86_sse.hpp 2 | // Purpose: Provide convenience macros and utilities for invoking x86 SSE 3 | // intrinsics 4 | #pragma once 5 | 6 | #ifdef KLEIN_SSE_4_1 7 | # ifdef KLEIN_USE_SIMDE 8 | # include 9 | # else 10 | # include 11 | # endif 12 | #else 13 | # ifdef KLEIN_USE_SIMDE 14 | # include 15 | # else 16 | # include 17 | # endif 18 | #endif 19 | 20 | // Little-endian XMM register swizzle 21 | // 22 | // KLN_SWIZZLE(reg, 3, 2, 1, 0) is the identity. 23 | // 24 | // This is undef-ed at the bottom of klein.hpp so as not to 25 | // pollute the macro namespace 26 | #ifndef KLN_SWIZZLE 27 | # define KLN_SWIZZLE(reg, x, y, z, w) \ 28 | _mm_shuffle_ps((reg), (reg), _MM_SHUFFLE(x, y, z, w)) 29 | #endif 30 | 31 | #ifndef KLN_RESTRICT 32 | # define KLN_RESTRICT __restrict 33 | #endif 34 | 35 | #ifndef KLN_VEC_CALL 36 | # ifdef _MSC_VER 37 | // Microsoft Compiler 38 | # define KLN_VEC_CALL __vectorcall 39 | # else 40 | // GCC or Clang compiler (sse register passing is on by default) 41 | # define KLN_VEC_CALL 42 | # endif 43 | #endif 44 | 45 | #ifndef KLN_INLINE 46 | # ifdef _MSC_VER 47 | # define KLN_INLINE __forceinline 48 | # else 49 | # define KLN_INLINE inline __attribute__((always_inline)) 50 | # endif 51 | #endif 52 | 53 | namespace kln 54 | { 55 | namespace detail 56 | { 57 | // DP high components and caller ignores returned high components 58 | KLN_INLINE __m128 KLN_VEC_CALL hi_dp_ss(__m128 a, __m128 b) noexcept 59 | { 60 | // 0 1 2 3 -> 1 + 2 + 3, 0, 0, 0 61 | 62 | __m128 out = _mm_mul_ps(a, b); 63 | 64 | // 0 1 2 3 -> 1 1 3 3 65 | __m128 hi = _mm_movehdup_ps(out); 66 | 67 | // 0 1 2 3 + 1 1 3 3 -> (0 + 1, 1 + 1, 2 + 3, 3 + 3) 68 | __m128 sum = _mm_add_ps(hi, out); 69 | 70 | // unpacklo: 0 0 1 1 71 | out = _mm_add_ps(sum, _mm_unpacklo_ps(out, out)); 72 | 73 | // (1 + 2 + 3, _, _, _) 74 | return _mm_movehl_ps(out, out); 75 | } 76 | 77 | // Reciprocal with an additional single Newton-Raphson refinement 78 | KLN_INLINE __m128 KLN_VEC_CALL rcp_nr1(__m128 a) noexcept 79 | { 80 | // f(x) = 1/x - a 81 | // f'(x) = -1/x^2 82 | // x_{n+1} = x_n - f(x)/f'(x) 83 | // = 2x_n - a x_n^2 = x_n (2 - a x_n) 84 | 85 | // ~2.7x baseline with ~22 bits of accuracy 86 | __m128 xn = _mm_rcp_ps(a); 87 | __m128 axn = _mm_mul_ps(a, xn); 88 | return _mm_mul_ps(xn, _mm_sub_ps(_mm_set1_ps(2.f), axn)); 89 | } 90 | 91 | // Reciprocal sqrt with an additional single Newton-Raphson refinement. 92 | KLN_INLINE __m128 KLN_VEC_CALL rsqrt_nr1(__m128 a) noexcept 93 | { 94 | // f(x) = 1/x^2 - a 95 | // f'(x) = -1/(2x^(3/2)) 96 | // Let x_n be the estimate, and x_{n+1} be the refinement 97 | // x_{n+1} = x_n - f(x)/f'(x) 98 | // = 0.5 * x_n * (3 - a x_n^2) 99 | 100 | // From Intel optimization manual: expected performance is ~5.2x 101 | // baseline (sqrtps + divps) with ~22 bits of accuracy 102 | 103 | __m128 xn = _mm_rsqrt_ps(a); 104 | __m128 axn2 = _mm_mul_ps(xn, xn); 105 | axn2 = _mm_mul_ps(a, axn2); 106 | __m128 xn3 = _mm_sub_ps(_mm_set1_ps(3.f), axn2); 107 | return _mm_mul_ps(_mm_mul_ps(_mm_set1_ps(0.5f), xn), xn3); 108 | } 109 | 110 | // Sqrt Newton-Raphson is evaluated in terms of rsqrt_nr1 111 | KLN_INLINE __m128 KLN_VEC_CALL sqrt_nr1(__m128 a) noexcept 112 | { 113 | return _mm_mul_ps(a, rsqrt_nr1(a)); 114 | } 115 | 116 | #ifdef KLEIN_SSE_4_1 117 | KLN_INLINE __m128 KLN_VEC_CALL hi_dp(__m128 a, __m128 b) noexcept 118 | { 119 | return _mm_dp_ps(a, b, 0b11100001); 120 | } 121 | 122 | KLN_INLINE __m128 KLN_VEC_CALL hi_dp_bc(__m128 a, __m128 b) noexcept 123 | { 124 | return _mm_dp_ps(a, b, 0b11101111); 125 | } 126 | 127 | KLN_INLINE __m128 KLN_VEC_CALL dp(__m128 a, __m128 b) noexcept 128 | { 129 | return _mm_dp_ps(a, b, 0b11110001); 130 | } 131 | 132 | KLN_INLINE __m128 KLN_VEC_CALL dp_bc(__m128 a, __m128 b) noexcept 133 | { 134 | return _mm_dp_ps(a, b, 0xff); 135 | } 136 | #else 137 | // Equivalent to _mm_dp_ps(a, b, 0b11100001); 138 | KLN_INLINE __m128 KLN_VEC_CALL hi_dp(__m128 a, __m128 b) noexcept 139 | { 140 | // 0 1 2 3 -> 1 + 2 + 3, 0, 0, 0 141 | 142 | __m128 out = _mm_mul_ps(a, b); 143 | 144 | // 0 1 2 3 -> 1 1 3 3 145 | __m128 hi = _mm_movehdup_ps(out); 146 | 147 | // 0 1 2 3 + 1 1 3 3 -> (0 + 1, 1 + 1, 2 + 3, 3 + 3) 148 | __m128 sum = _mm_add_ps(hi, out); 149 | 150 | // unpacklo: 0 0 1 1 151 | out = _mm_add_ps(sum, _mm_unpacklo_ps(out, out)); 152 | 153 | // (1 + 2 + 3, _, _, _) 154 | out = _mm_movehl_ps(out, out); 155 | 156 | return _mm_and_ps(out, _mm_castsi128_ps(_mm_set_epi32(0, 0, 0, -1))); 157 | } 158 | 159 | KLN_INLINE __m128 KLN_VEC_CALL hi_dp_bc(__m128 a, __m128 b) noexcept 160 | { 161 | // Multiply across and mask low component 162 | __m128 out = _mm_mul_ps(a, b); 163 | 164 | // 0 1 2 3 -> 1 1 3 3 165 | __m128 hi = _mm_movehdup_ps(out); 166 | 167 | // 0 1 2 3 + 1 1 3 3 -> (0 + 1, 1 + 1, 2 + 3, 3 + 3) 168 | __m128 sum = _mm_add_ps(hi, out); 169 | 170 | // unpacklo: 0 0 1 1 171 | out = _mm_add_ps(sum, _mm_unpacklo_ps(out, out)); 172 | 173 | return KLN_SWIZZLE(out, 2, 2, 2, 2); 174 | } 175 | 176 | KLN_INLINE __m128 KLN_VEC_CALL dp(__m128 a, __m128 b) noexcept 177 | { 178 | // Multiply across and shift right (shifting in zeros) 179 | __m128 out = _mm_mul_ps(a, b); 180 | __m128 hi = _mm_movehdup_ps(out); 181 | // (a1 b1, a2 b2, a3 b3, 0) + (a2 b2, a2 b2, 0, 0) 182 | // = (a1 b1 + a2 b2, _, a3 b3, 0) 183 | out = _mm_add_ps(hi, out); 184 | out = _mm_add_ss(out, _mm_movehl_ps(hi, out)); 185 | return _mm_and_ps(out, _mm_castsi128_ps(_mm_set_epi32(0, 0, 0, -1))); 186 | } 187 | 188 | KLN_INLINE __m128 KLN_VEC_CALL dp_bc(__m128 a, __m128 b) noexcept 189 | { 190 | // Multiply across and shift right (shifting in zeros) 191 | __m128 out = _mm_mul_ps(a, b); 192 | __m128 hi = _mm_movehdup_ps(out); 193 | // (a1 b1, a2 b2, a3 b3, 0) + (a2 b2, a2 b2, 0, 0) 194 | // = (a1 b1 + a2 b2, _, a3 b3, 0) 195 | out = _mm_add_ps(hi, out); 196 | out = _mm_add_ss(out, _mm_movehl_ps(hi, out)); 197 | return KLN_SWIZZLE(out, 0, 0, 0, 0); 198 | } 199 | #endif 200 | } // namespace detail 201 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/direction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/sse.hpp" 4 | 5 | #ifdef KLEIN_VALIDATE 6 | # include 7 | #endif 8 | 9 | namespace kln 10 | { 11 | /// \defgroup dir Directions 12 | /// Directions in $\mathbf{P}(\mathbb{R}^3_{3, 0, 1})$ are represented using 13 | /// points at infinity (homogeneous coordinate 0). Having a homogeneous 14 | /// coordinate of zero ensures that directions are translation-invariant. 15 | 16 | /// \addtogroup dir 17 | /// @{ 18 | class direction final 19 | { 20 | public: 21 | direction() noexcept = default; 22 | 23 | /// Create a normalized direction 24 | direction(float x, float y, float z) noexcept 25 | : p3_{_mm_set_ps(z, y, x, 0.f)} 26 | { 27 | normalize(); 28 | } 29 | 30 | direction(__m128 p3) noexcept 31 | : p3_{p3} 32 | {} 33 | 34 | /// Data should point to four floats with memory layout `(0.f, x, y, z)` 35 | /// where the zero occupies the lowest address in memory. 36 | explicit direction(float* data) noexcept 37 | { 38 | #ifdef KLEIN_VALIDATE 39 | assert(data[0] == 0.f 40 | && "Homogeneous coordinate of point data used to initialize a" 41 | "direction must be exactly zero"); 42 | #endif 43 | p3_ = _mm_loadu_ps(data); 44 | } 45 | 46 | [[nodiscard]] float x() const noexcept 47 | { 48 | float out[4]; 49 | _mm_store_ps(out, p3_); 50 | return out[1]; 51 | } 52 | 53 | [[nodiscard]] float y() const noexcept 54 | { 55 | float out[4]; 56 | _mm_store_ps(out, p3_); 57 | return out[2]; 58 | } 59 | 60 | [[nodiscard]] float z() const noexcept 61 | { 62 | float out[4]; 63 | _mm_store_ps(out, p3_); 64 | return out[3]; 65 | } 66 | 67 | /// Normalize this direction by dividing all components by the 68 | /// magnitude (by default, `rsqrtps` is used with a single Newton-Raphson 69 | /// refinement iteration) 70 | void normalize() noexcept 71 | { 72 | __m128 tmp = detail::rsqrt_nr1(detail::hi_dp_bc(p3_, p3_)); 73 | p3_ = _mm_mul_ps(p3_, tmp); 74 | } 75 | 76 | /// Return a normalized copy of this direction 77 | direction normalized() const noexcept 78 | { 79 | direction out = *this; 80 | out.normalize(); 81 | return out; 82 | } 83 | 84 | /// Direction addition 85 | direction& KLN_VEC_CALL operator+=(direction b) noexcept 86 | { 87 | p3_ = _mm_add_ps(p3_, b.p3_); 88 | return *this; 89 | } 90 | 91 | /// Direction subtraction 92 | direction& KLN_VEC_CALL operator-=(direction b) noexcept 93 | { 94 | p3_ = _mm_sub_ps(p3_, b.p3_); 95 | return *this; 96 | } 97 | 98 | /// Direction uniform scale 99 | direction& operator*=(float s) noexcept 100 | { 101 | p3_ = _mm_mul_ps(p3_, _mm_set1_ps(s)); 102 | return *this; 103 | } 104 | 105 | /// Direction uniform scale 106 | direction& operator*=(int s) noexcept 107 | { 108 | p3_ = _mm_mul_ps(p3_, _mm_set1_ps(static_cast(s))); 109 | return *this; 110 | } 111 | 112 | /// Direction uniform inverse scale 113 | direction& operator/=(float s) noexcept 114 | { 115 | p3_ = _mm_mul_ps(p3_, detail::rcp_nr1(_mm_set1_ps(s))); 116 | return *this; 117 | } 118 | 119 | /// Direction uniform inverse scale 120 | direction& operator/=(int s) noexcept 121 | { 122 | p3_ = _mm_mul_ps( 123 | p3_, detail::rcp_nr1(_mm_set1_ps(static_cast(s)))); 124 | return *this; 125 | } 126 | 127 | __m128 p3_; 128 | }; 129 | 130 | /// Direction addition 131 | [[nodiscard]] inline direction KLN_VEC_CALL operator+(direction a, 132 | direction b) noexcept 133 | { 134 | direction c; 135 | c.p3_ = _mm_add_ps(a.p3_, b.p3_); 136 | return c; 137 | } 138 | 139 | /// Direction subtraction 140 | [[nodiscard]] inline direction KLN_VEC_CALL operator-(direction a, 141 | direction b) noexcept 142 | { 143 | direction c; 144 | c.p3_ = _mm_sub_ps(a.p3_, b.p3_); 145 | return c; 146 | } 147 | 148 | /// Direction uniform scale 149 | [[nodiscard]] inline direction KLN_VEC_CALL operator*(direction d, float s) noexcept 150 | { 151 | direction c; 152 | c.p3_ = _mm_mul_ps(d.p3_, _mm_set1_ps(s)); 153 | return c; 154 | } 155 | 156 | /// Direction uniform scale 157 | [[nodiscard]] inline direction KLN_VEC_CALL operator*(float s, direction d) noexcept 158 | { 159 | return d * s; 160 | } 161 | 162 | /// Direction uniform scale 163 | [[nodiscard]] inline direction KLN_VEC_CALL operator*(direction d, int s) noexcept 164 | { 165 | return d * static_cast(s); 166 | } 167 | 168 | /// Direction uniform scale 169 | [[nodiscard]] inline direction KLN_VEC_CALL operator*(int s, direction d) noexcept 170 | { 171 | return d * static_cast(s); 172 | } 173 | 174 | /// Direction uniform inverse scale 175 | [[nodiscard]] inline direction KLN_VEC_CALL operator/(direction d, float s) noexcept 176 | { 177 | direction c; 178 | c.p3_ = _mm_mul_ps(d.p3_, detail::rcp_nr1(_mm_set1_ps(s))); 179 | return c; 180 | } 181 | 182 | /// Direction uniform inverse scale 183 | [[nodiscard]] inline direction KLN_VEC_CALL operator/(direction d, int s) noexcept 184 | { 185 | return d / static_cast(s); 186 | } 187 | 188 | /// Unary minus 189 | [[nodiscard]] inline direction operator-(direction d) noexcept 190 | { 191 | return {_mm_xor_ps(d.p3_, _mm_set1_ps(-0.f))}; 192 | } 193 | } // namespace kln 194 | /// @} -------------------------------------------------------------------------------- /public/klein/dual.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/sse.hpp" 4 | 5 | namespace kln 6 | { 7 | /// \defgroup dualn Dual Numbers 8 | /// A dual number is a multivector of the form $p + q\mathbf{e}_{0123}$. 9 | 10 | /// \addtogroup dualn 11 | /// @{ 12 | class dual 13 | { 14 | public: 15 | float scalar() const noexcept 16 | { 17 | return p; 18 | } 19 | 20 | float e0123() const noexcept 21 | { 22 | return q; 23 | } 24 | 25 | dual& operator+=(dual b) noexcept 26 | { 27 | p += b.p; 28 | q += b.q; 29 | return *this; 30 | } 31 | 32 | dual& operator-=(dual b) noexcept 33 | { 34 | p -= b.p; 35 | q -= b.q; 36 | return *this; 37 | } 38 | 39 | dual& operator*=(float s) noexcept 40 | { 41 | p *= s; 42 | q *= s; 43 | return *this; 44 | } 45 | 46 | dual& operator/=(float s) noexcept 47 | { 48 | p /= s; 49 | q /= s; 50 | return *this; 51 | } 52 | 53 | float p; 54 | float q; 55 | }; 56 | 57 | [[nodiscard]] inline dual KLN_VEC_CALL operator+(dual a, dual b) noexcept 58 | { 59 | return {a.p + b.p, a.q + b.q}; 60 | } 61 | 62 | [[nodiscard]] inline dual KLN_VEC_CALL operator-(dual a, dual b) noexcept 63 | { 64 | return {a.p - b.p, a.q - b.q}; 65 | } 66 | 67 | [[nodiscard]] inline dual KLN_VEC_CALL operator*(dual a, float s) noexcept 68 | { 69 | return {a.p * s, a.q * s}; 70 | } 71 | 72 | [[nodiscard]] inline dual KLN_VEC_CALL operator*(float s, dual a) noexcept 73 | { 74 | return {a.p * s, a.q * s}; 75 | } 76 | 77 | [[nodiscard]] inline dual KLN_VEC_CALL operator/(dual a, float s) noexcept 78 | { 79 | return {a.p / s, a.q / s}; 80 | } 81 | 82 | /// @} 83 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/exp_log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "line.hpp" 4 | #include "motor.hpp" 5 | #include "rotor.hpp" 6 | #include "translator.hpp" 7 | 8 | #include "detail/exp_log.hpp" 9 | 10 | namespace kln 11 | { 12 | /// \defgroup exp_log Exponential and Logarithm 13 | /// @{ 14 | /// 15 | /// The group of rotations, translations, and screws (combined rotatation and 16 | /// translation) is _nonlinear_. This means, given say, a rotor $\mathbf{r}$, 17 | /// the rotor 18 | /// $\frac{\mathbf{r}}{2}$ _does not_ correspond to half the rotation. 19 | /// Similarly, for a motor $\mathbf{m}$, the motor $n \mathbf{m}$ is not $n$ 20 | /// applications of the motor $\mathbf{m}$. One way we could achieve this is 21 | /// through exponentiation; for example, the motor $\mathbf{m}^3$ will perform 22 | /// the screw action of $\mathbf{m}$ three times. However, repeated 23 | /// multiplication in this fashion lacks both efficiency and numerical 24 | /// stability. 25 | /// 26 | /// The solution is to take the logarithm of the action which maps the action to 27 | /// a linear space. Using `log(A)` where `A` is one of `rotor`, 28 | /// `translator`, or `motor`, we can apply linear scaling to `log(A)`, 29 | /// and then re-exponentiate the result. Using this technique, `exp(n * log(A))` 30 | /// is equivalent to $\mathbf{A}^n$. 31 | 32 | /// Takes the principal branch of the logarithm of the motor, returning a 33 | /// bivector. Exponentiation of that bivector without any changes produces 34 | /// this motor again. Scaling that bivector by $\frac{1}{n}$, 35 | /// re-exponentiating, and taking the result to the $n$th power will also 36 | /// produce this motor again. The logarithm presumes that the motor is 37 | /// normalized. 38 | [[nodiscard]] inline line KLN_VEC_CALL log(motor m) noexcept 39 | { 40 | line out; 41 | detail::log(m.p1_, m.p2_, out.p1_, out.p2_); 42 | return out; 43 | } 44 | 45 | /// Exponentiate a line to produce a motor that posesses this line 46 | /// as its axis. This routine will be used most often when this line is 47 | /// produced as the logarithm of an existing rotor, then scaled to subdivide 48 | /// or accelerate the motor's action. The line need not be a _simple bivector_ 49 | /// for the operation to be well-defined. 50 | [[nodiscard]] inline motor KLN_VEC_CALL exp(line l) noexcept 51 | { 52 | motor out; 53 | detail::exp(l.p1_, l.p2_, out.p1_, out.p2_); 54 | return out; 55 | } 56 | 57 | /// Compute the logarithm of the translator, producing an ideal line axis. 58 | /// In practice, the logarithm of a translator is simply the ideal partition 59 | /// (without the scalar $1$). 60 | [[nodiscard]] inline ideal_line KLN_VEC_CALL log(translator t) noexcept 61 | { 62 | ideal_line out; 63 | out.p2_ = t.p2_; 64 | return out; 65 | } 66 | 67 | /// Exponentiate an ideal line to produce a translation. 68 | /// 69 | /// The exponential of an ideal line 70 | /// $a \mathbf{e}_{01} + b\mathbf{e}_{02} + c\mathbf{e}_{03}$ is given as: 71 | /// 72 | /// $$\exp{\left[a\ee_{01} + b\ee_{02} + c\ee_{03}\right]} = 1 +\ 73 | /// a\ee_{01} + b\ee_{02} + c\ee_{03}$$ 74 | [[nodiscard]] inline translator KLN_VEC_CALL exp(ideal_line il) noexcept 75 | { 76 | translator out; 77 | out.p2_ = il.p2_; 78 | return out; 79 | } 80 | 81 | /// Returns the principal branch of this rotor's logarithm. Invoking 82 | /// `exp` on the returned `kln::branch` maps back to this rotor. 83 | /// 84 | /// Given a rotor $\cos\alpha + \sin\alpha\left[a\ee_{23} + b\ee_{31} +\ 85 | /// c\ee_{23}\right]$, the log is computed as simply 86 | /// $\alpha\left[a\ee_{23} + b\ee_{31} + c\ee_{23}\right]$. 87 | /// This map is only well-defined if the 88 | /// rotor is normalized such that $a^2 + b^2 + c^2 = 1$. 89 | [[nodiscard]] inline branch KLN_VEC_CALL log(rotor r) noexcept 90 | { 91 | float cos_ang; 92 | _mm_store_ss(&cos_ang, r.p1_); 93 | float ang = std::acos(cos_ang); 94 | float sin_ang = std::sin(ang); 95 | 96 | branch out; 97 | out.p1_ = _mm_mul_ps(r.p1_, detail::rcp_nr1(_mm_set1_ps(sin_ang))); 98 | out.p1_ = _mm_mul_ps(out.p1_, _mm_set1_ps(ang)); 99 | #ifdef KLEIN_SSE_4_1 100 | out.p1_ = _mm_blend_ps(out.p1_, _mm_setzero_ps(), 1); 101 | #else 102 | out.p1_ = _mm_and_ps(out.p1_, _mm_castsi128_ps(_mm_set_epi32(-1, -1, -1, 0))); 103 | #endif 104 | return out; 105 | } 106 | 107 | /// Exponentiate a branch to produce a rotor. 108 | [[nodiscard]] inline rotor KLN_VEC_CALL exp(branch b) noexcept 109 | { 110 | // Compute the rotor angle 111 | float ang; 112 | _mm_store_ss(&ang, detail::sqrt_nr1(detail::hi_dp(b.p1_, b.p1_))); 113 | float cos_ang = std::cos(ang); 114 | float sin_ang = std::sin(ang) / ang; 115 | 116 | rotor out; 117 | out.p1_ = _mm_mul_ps(_mm_set1_ps(sin_ang), b.p1_); 118 | out.p1_ = _mm_add_ps(out.p1_, _mm_set_ps(0.f, 0.f, 0.f, cos_ang)); 119 | return out; 120 | } 121 | 122 | /// Compute the square root of the provided rotor $r$. 123 | [[nodiscard]] inline rotor KLN_VEC_CALL sqrt(rotor r) noexcept 124 | { 125 | r.p1_ = _mm_add_ss(r.p1_, _mm_set_ss(1.f)); 126 | r.normalize(); 127 | return r; 128 | } 129 | 130 | [[nodiscard]] inline rotor KLN_VEC_CALL sqrt(branch b) noexcept 131 | { 132 | rotor r; 133 | r.p1_ = _mm_add_ss(b.p1_, _mm_set_ss(1.f)); 134 | r.normalize(); 135 | return r; 136 | } 137 | 138 | /// Compute the square root of the provided translator $t$. 139 | [[nodiscard]] inline translator KLN_VEC_CALL sqrt(translator t) noexcept 140 | { 141 | t *= 0.5f; 142 | return t; 143 | } 144 | 145 | /// Compute the square root of the provided motor $m$. 146 | [[nodiscard]] inline motor KLN_VEC_CALL sqrt(motor m) noexcept 147 | { 148 | m.p1_ = _mm_add_ss(m.p1_, _mm_set_ss(1.f)); 149 | m.normalize(); 150 | return m; 151 | } 152 | /// @} 153 | } // namespace kln 154 | -------------------------------------------------------------------------------- /public/klein/inner_product.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/inner_product.hpp" 4 | 5 | #include "line.hpp" 6 | #include "motor.hpp" 7 | #include "plane.hpp" 8 | #include "point.hpp" 9 | #include "rotor.hpp" 10 | 11 | namespace kln 12 | { 13 | /// \defgroup dot Symmetric Inner Product 14 | /// 15 | /// The symmetric inner product takes two arguments and contracts the lower 16 | /// graded element to the greater graded element. If lower graded element 17 | /// spans an index that is not contained in the higher graded element, the 18 | /// result is annihilated. Otherwise, the result is the part of the higher 19 | /// graded element "most unlike" the lower graded element. Thus, the 20 | /// symmetric inner product can be thought of as a bidirectional contraction 21 | /// operator. 22 | /// 23 | /// There is some merit in providing both a left and right contraction 24 | /// operator for explicitness. However, when using Klein, it's generally 25 | /// clear what the interpretation of the symmetric inner product is with 26 | /// respect to the projection on various entities. 27 | /// 28 | /// !!! example "Angle between planes" 29 | /// 30 | /// ```cpp 31 | /// kln::plane a{x1, y1, z1, d1}; 32 | /// kln::plane b{x2, y2, z2, d2}; 33 | /// 34 | /// // Compute the cos of the angle between two planes 35 | /// float cos_ang = a | b; 36 | /// ``` 37 | /// 38 | /// !!! example "Line to plane through point" 39 | /// 40 | /// ```cpp 41 | /// kln::point a{x1, y1, z1}; 42 | /// kln::plane b{x2, y2, z2, d2}; 43 | /// 44 | /// // The line l contains a and the shortest path from a to plane b. 45 | /// line l = a | b; 46 | /// ``` 47 | 48 | /// \addtogroup dot 49 | /// @{ 50 | 51 | [[nodiscard]] inline float operator|(plane a, plane b) noexcept 52 | { 53 | float out; 54 | __m128 s; 55 | detail::dot00(a.p0_, b.p0_, s); 56 | _mm_store_ss(&out, s); 57 | return out; 58 | } 59 | 60 | [[nodiscard]] inline plane operator|(plane a, line b) noexcept 61 | { 62 | plane out; 63 | detail::dotPL(a.p0_, b.p1_, b.p2_, out.p0_); 64 | return out; 65 | } 66 | 67 | [[nodiscard]] inline plane operator|(line b, plane a) noexcept 68 | { 69 | plane out; 70 | detail::dotPL(a.p0_, b.p1_, b.p2_, out.p0_); 71 | return out; 72 | } 73 | 74 | [[nodiscard]] inline plane operator|(plane a, ideal_line b) noexcept 75 | { 76 | plane out; 77 | detail::dotPIL(a.p0_, b.p2_, out.p0_); 78 | return out; 79 | } 80 | 81 | [[nodiscard]] inline plane operator|(ideal_line b, plane a) noexcept 82 | { 83 | plane out; 84 | detail::dotPIL(a.p0_, b.p2_, out.p0_); 85 | return out; 86 | } 87 | 88 | [[nodiscard]] inline line operator|(plane a, point b) noexcept 89 | { 90 | line out; 91 | detail::dot03(a.p0_, b.p3_, out.p1_, out.p2_); 92 | return out; 93 | } 94 | 95 | [[nodiscard]] inline line operator|(point a, plane b) noexcept 96 | { 97 | return b | a; 98 | } 99 | 100 | [[nodiscard]] inline float operator|(line a, line b) noexcept 101 | { 102 | __m128 s; 103 | detail::dot11(a.p1_, b.p1_, s); 104 | float out; 105 | _mm_store_ss(&out, s); 106 | return out; 107 | } 108 | 109 | [[nodiscard]] inline plane operator|(point a, line b) noexcept 110 | { 111 | plane out; 112 | detail::dotPTL(a.p3_, b.p1_, out.p0_); 113 | return out; 114 | } 115 | 116 | [[nodiscard]] inline plane operator|(line b, point a) noexcept 117 | { 118 | return a | b; 119 | } 120 | 121 | [[nodiscard]] inline float operator|(point a, point b) noexcept 122 | { 123 | __m128 s; 124 | detail::dot33(a.p3_, b.p3_, s); 125 | float out; 126 | _mm_store_ss(&out, s); 127 | return out; 128 | } 129 | /// @} 130 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/join.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dual.hpp" 4 | #include "line.hpp" 5 | #include "plane.hpp" 6 | #include "point.hpp" 7 | 8 | #include "meet.hpp" 9 | 10 | namespace kln 11 | { 12 | /// \defgroup dual Poincaré Dual 13 | /// 14 | /// The Poincaré Dual of an element is the "subspace complement" of the 15 | /// argument with respect to the pseudoscalar in the exterior algebra. In 16 | /// practice, it is a relabeling of the coordinates to their 17 | /// dual-coordinates and is used most often to implement a "join" operation 18 | /// in terms of the exterior product of the duals of each operand. 19 | /// 20 | /// Ex: The dual of the point $\mathbf{e}_{123} + 3\mathbf{e}_{013} - 21 | /// 2\mathbf{e}_{021}$ (the point at 22 | /// $(0, 3, -2)$) is the plane 23 | /// $\mathbf{e}_0 + 3\mathbf{e}_2 - 2\mathbf{e}_3$. 24 | 25 | /// \addtogroup dual 26 | /// @{ 27 | 28 | KLN_INLINE point KLN_VEC_CALL operator!(plane in) noexcept 29 | { 30 | return {in.p0_}; 31 | } 32 | 33 | KLN_INLINE plane KLN_VEC_CALL operator!(point in) noexcept 34 | { 35 | return {in.p3_}; 36 | } 37 | 38 | KLN_INLINE line KLN_VEC_CALL operator!(line in) noexcept 39 | { 40 | return {in.p2_, in.p1_}; 41 | } 42 | 43 | KLN_INLINE branch KLN_VEC_CALL operator!(ideal_line in) noexcept 44 | { 45 | return {in.p2_}; 46 | } 47 | 48 | KLN_INLINE ideal_line KLN_VEC_CALL operator!(branch in) noexcept 49 | { 50 | return {in.p1_}; 51 | } 52 | 53 | KLN_INLINE dual KLN_VEC_CALL operator!(dual in) noexcept 54 | { 55 | return {in.q, in.p}; 56 | } 57 | /// @} 58 | 59 | /// \defgroup reg Regressive Product 60 | /// 61 | /// The regressive product is implemented in terms of the exterior product. 62 | /// Given multivectors $\mathbf{a}$ and $\mathbf{b}$, the regressive product 63 | /// $\mathbf{a}\vee\mathbf{b}$ is equivalent to 64 | /// $J(J(\mathbf{a})\wedge J(\mathbf{b}))$. Thus, both meets and joins 65 | /// reside in the same algebraic structure. 66 | /// 67 | /// !!! example "Joining two points" 68 | /// 69 | /// ```cpp 70 | /// kln::point p1{x1, y1, z1}; 71 | /// kln::point p2{x2, y2, z2}; 72 | /// 73 | /// // l contains both p1 and p2. 74 | /// kln::line l = p1 & p2; 75 | /// ``` 76 | /// 77 | /// !!! example "Joining a line and a point" 78 | /// 79 | /// ```cpp 80 | /// kln::point p1{x, y, z}; 81 | /// kln::line l2{mx, my, mz, dx, dy, dz}; 82 | /// 83 | /// // p2 contains both p1 and l2. 84 | /// kln::plane p2 = p1 & l2; 85 | /// ``` 86 | 87 | /// \addtogroup reg 88 | /// @{ 89 | [[nodiscard]] inline line KLN_VEC_CALL operator&(point a, point b) noexcept 90 | { 91 | return !(!a ^ !b); 92 | } 93 | 94 | [[nodiscard]] inline plane KLN_VEC_CALL operator&(point a, line b) noexcept 95 | { 96 | return !(!a ^ !b); 97 | } 98 | 99 | [[nodiscard]] inline plane KLN_VEC_CALL operator&(line b, point a) noexcept 100 | { 101 | return a & b; 102 | } 103 | 104 | [[nodiscard]] inline plane KLN_VEC_CALL operator&(point a, branch b) noexcept 105 | { 106 | return !(!a ^ !b); 107 | } 108 | 109 | [[nodiscard]] inline plane KLN_VEC_CALL operator&(branch b, point a) noexcept 110 | { 111 | return a & b; 112 | } 113 | 114 | [[nodiscard]] inline plane KLN_VEC_CALL operator&(point a, ideal_line b) noexcept 115 | { 116 | return !(!a ^ !b); 117 | } 118 | 119 | [[nodiscard]] inline plane KLN_VEC_CALL operator&(ideal_line b, point a) noexcept 120 | { 121 | return a & b; 122 | } 123 | 124 | [[nodiscard]] inline dual KLN_VEC_CALL operator&(plane a, point b) noexcept 125 | { 126 | return !(!a ^ !b); 127 | } 128 | 129 | [[nodiscard]] inline dual KLN_VEC_CALL operator&(point a, plane b) noexcept 130 | { 131 | return !(!a ^ !b); 132 | } 133 | /// @} 134 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/klein.hpp: -------------------------------------------------------------------------------- 1 | // File: klein.hpp 2 | // Include this header to gain access to all the primary library facilities: 3 | // 1. Representations of points, lines, planes, directions, rotors, translators, 4 | // and motors as multivectors 5 | // 2. SSE-optimized operations between all the above 6 | 7 | #pragma once 8 | 9 | #include "exp_log.hpp" 10 | #include "geometric_product.hpp" 11 | #include "inner_product.hpp" 12 | #include "join.hpp" 13 | #include "meet.hpp" 14 | #include "projection.hpp" 15 | #include "util.hpp" -------------------------------------------------------------------------------- /public/klein/mat3x4.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/sse.hpp" 4 | 5 | namespace kln 6 | { 7 | /// 3x4 column-major matrix (used for converting rotors/motors to matrix form to 8 | /// upload to shaders). Note that the storage requirement is identical to a 9 | /// column major mat4x4 due to the SIMD representation. 10 | struct mat3x4 11 | { 12 | union 13 | { 14 | __m128 cols[4]; 15 | float data[16]; 16 | }; 17 | 18 | /// Apply the linear transformation represented by this matrix to a point 19 | /// packed with the layout (x, y, z, 1.f) 20 | __m128 KLN_VEC_CALL operator()(__m128 const& xyzw) const noexcept 21 | { 22 | __m128 out = _mm_mul_ps(cols[0], KLN_SWIZZLE(xyzw, 0, 0, 0, 0)); 23 | out = _mm_add_ps(out, _mm_mul_ps(cols[1], KLN_SWIZZLE(xyzw, 1, 1, 1, 1))); 24 | out = _mm_add_ps(out, _mm_mul_ps(cols[2], KLN_SWIZZLE(xyzw, 2, 2, 2, 2))); 25 | out = _mm_add_ps(out, _mm_mul_ps(cols[3], KLN_SWIZZLE(xyzw, 3, 3, 3, 3))); 26 | return out; 27 | } 28 | 29 | // TODO: provide a transpose function 30 | }; 31 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/mat4x4.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/sse.hpp" 4 | 5 | namespace kln 6 | { 7 | /// 4x4 column-major matrix (used for converting rotors/motors to matrix form to 8 | /// upload to shaders). 9 | struct mat4x4 10 | { 11 | union 12 | { 13 | __m128 cols[4]; 14 | float data[16]; 15 | }; 16 | 17 | /// Apply the linear transformation represented by this matrix to a point 18 | /// packed with the layout (x, y, z, 1.f) 19 | __m128 KLN_VEC_CALL operator()(__m128 const& xyzw) const noexcept 20 | { 21 | __m128 out = _mm_mul_ps(cols[0], KLN_SWIZZLE(xyzw, 0, 0, 0, 0)); 22 | out = _mm_add_ps(out, _mm_mul_ps(cols[1], KLN_SWIZZLE(xyzw, 1, 1, 1, 1))); 23 | out = _mm_add_ps(out, _mm_mul_ps(cols[2], KLN_SWIZZLE(xyzw, 2, 2, 2, 2))); 24 | out = _mm_add_ps(out, _mm_mul_ps(cols[3], KLN_SWIZZLE(xyzw, 3, 3, 3, 3))); 25 | return out; 26 | } 27 | 28 | // TODO: provide a transpose function 29 | }; 30 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/meet.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "detail/exterior_product.hpp" 4 | 5 | #include "dual.hpp" 6 | #include "line.hpp" 7 | #include "plane.hpp" 8 | #include "point.hpp" 9 | 10 | namespace kln 11 | { 12 | /// \defgroup ext Exterior Product 13 | /// 14 | /// (meet.hpp) 15 | /// 16 | /// The exterior product between two basis elements extinguishes if the two 17 | /// operands share any common index. Otherwise, the element produced is 18 | /// equivalent to the union of the subspaces. A sign flip is introduced if 19 | /// the concatenation of the element indices is an odd permutation of the 20 | /// cyclic basis representation. The exterior product extends to general 21 | /// multivectors by linearity. 22 | /// 23 | /// !!! example "Meeting two planes" 24 | /// 25 | /// ```cpp 26 | /// kln::plane p1{x1, y1, z1, d1}; 27 | /// kln::plane p2{x2, y2, z2, d2}; 28 | /// 29 | /// // l lies at the intersection of p1 and p2. 30 | /// kln::line l = p1 ^ p2; 31 | /// ``` 32 | /// 33 | /// !!! example "Meeting a line and a plane" 34 | /// 35 | /// ```cpp 36 | /// kln::plane p1{x, y, z, d}; 37 | /// kln::line l2{mx, my, mz, dx, dy, dz}; 38 | /// 39 | /// // p2 lies at the intersection of p1 and l2. 40 | /// kln::point p2 = p1 ^ l2; 41 | /// ``` 42 | 43 | /// \addtogroup ext 44 | /// @{ 45 | 46 | [[nodiscard]] inline line KLN_VEC_CALL operator^(plane a, plane b) noexcept 47 | { 48 | line out; 49 | detail::ext00(a.p0_, b.p0_, out.p1_, out.p2_); 50 | return out; 51 | } 52 | 53 | [[nodiscard]] inline point KLN_VEC_CALL operator^(plane a, branch b) noexcept 54 | { 55 | point out; 56 | detail::extPB(a.p0_, b.p1_, out.p3_); 57 | return out; 58 | } 59 | 60 | [[nodiscard]] inline point KLN_VEC_CALL operator^(branch b, plane a) noexcept 61 | { 62 | return a ^ b; 63 | } 64 | 65 | [[nodiscard]] inline point KLN_VEC_CALL operator^(plane a, ideal_line b) noexcept 66 | { 67 | point out; 68 | detail::ext02(a.p0_, b.p2_, out.p3_); 69 | return out; 70 | } 71 | 72 | [[nodiscard]] inline point KLN_VEC_CALL operator^(ideal_line b, plane a) noexcept 73 | { 74 | return a ^ b; 75 | } 76 | 77 | [[nodiscard]] inline point KLN_VEC_CALL operator^(plane a, line b) noexcept 78 | { 79 | point out; 80 | detail::extPB(a.p0_, b.p1_, out.p3_); 81 | __m128 tmp; 82 | detail::ext02(a.p0_, b.p2_, tmp); 83 | out.p3_ = _mm_add_ps(tmp, out.p3_); 84 | return out; 85 | } 86 | 87 | [[nodiscard]] inline point KLN_VEC_CALL operator^(line b, plane a) noexcept 88 | { 89 | return a ^ b; 90 | } 91 | 92 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(plane a, point b) noexcept 93 | { 94 | __m128 tmp; 95 | detail::ext03(a.p0_, b.p3_, tmp); 96 | dual out{0.f}; 97 | _mm_store_ss(&out.q, tmp); 98 | return out; 99 | } 100 | 101 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(point b, plane a) noexcept 102 | { 103 | __m128 tmp; 104 | detail::ext03(a.p0_, b.p3_, tmp); 105 | dual out{0.f}; 106 | _mm_store_ss(&out.q, tmp); 107 | return out; 108 | } 109 | 110 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(branch a, ideal_line b) noexcept 111 | { 112 | __m128 dp = detail::hi_dp_ss(a.p1_, b.p2_); 113 | float out; 114 | _mm_store_ss(&out, dp); 115 | return {0.f, out}; 116 | } 117 | 118 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(ideal_line b, branch a) noexcept 119 | { 120 | return a ^ b; 121 | } 122 | 123 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(line a, line b) noexcept 124 | { 125 | __m128 dp = detail::hi_dp_ss(a.p1_, b.p2_); 126 | float out[2]; 127 | _mm_store_ss(out, dp); 128 | dp = detail::hi_dp_ss(b.p1_, a.p2_); 129 | _mm_store_ss(out + 1, dp); 130 | return {0.f, out[0] + out[1]}; 131 | } 132 | 133 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(line a, ideal_line b) noexcept 134 | { 135 | return branch{a.p1_} ^ b; 136 | } 137 | 138 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(ideal_line b, line a) noexcept 139 | { 140 | return a ^ b; 141 | } 142 | 143 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(line a, branch b) noexcept 144 | { 145 | return ideal_line{a.p2_} ^ b; 146 | } 147 | 148 | [[nodiscard]] inline dual KLN_VEC_CALL operator^(branch b, line a) noexcept 149 | { 150 | return a ^ b; 151 | } 152 | /// @} 153 | } // namespace kln -------------------------------------------------------------------------------- /public/klein/projection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "inner_product.hpp" 4 | #include "line.hpp" 5 | #include "meet.hpp" 6 | #include "plane.hpp" 7 | #include "point.hpp" 8 | 9 | namespace kln 10 | { 11 | /// \defgroup proj Projections 12 | /// 13 | /// Projections in Geometric Algebra take on a particularly simple form. 14 | /// For two geometric entities $a$ and $b$, there are two cases to consider. 15 | /// First, if the grade of $a$ is greater than the grade of $b$, the projection 16 | /// of $a$ on $b$ is given by: 17 | /// 18 | /// $$ \textit{proj}_b a = (a \cdot b) \wedge b $$ 19 | /// 20 | /// The inner product can be thought of as the part of $b$ _least like_ $a$. 21 | /// Using the meet operator on this part produces the part of $b$ _most like_ 22 | /// $a$. A simple sanity check is to consider the grades of the result. If the 23 | /// grade of $b$ is less than the grade of $a$, we end up with an entity with 24 | /// grade $a - b + b = a$ as expected. 25 | /// 26 | /// In the second case (the grade of $a$ is less than the grade of $b$), the 27 | /// projection of $a$ on $b$ is given by: 28 | /// 29 | /// $$ \textit{proj}_b a = (a \cdot b) \cdot b $$ 30 | /// 31 | /// It can be verified that as in the first case, the grade of the result is the 32 | /// same as the grade of $a$. As this projection occurs in the opposite sense 33 | /// from what one may have seen before, additional clarification is provided 34 | /// below. 35 | 36 | /// \addtogroup proj 37 | /// @{ 38 | 39 | /// Project a point onto a line 40 | inline point KLN_VEC_CALL project(point a, line b) noexcept 41 | { 42 | return {(a | b) ^ b}; 43 | } 44 | 45 | /// Project a point onto a plane 46 | inline point KLN_VEC_CALL project(point a, plane b) noexcept 47 | { 48 | return {(a | b) ^ b}; 49 | } 50 | 51 | /// Project a line onto a plane 52 | inline line KLN_VEC_CALL project(line a, plane b) noexcept 53 | { 54 | return {(a | b) ^ b}; 55 | } 56 | 57 | /// Project a plane onto a point. Given a plane $p$ and point $P$, produces the 58 | /// plane through $P$ that is parallel to $p$. 59 | /// 60 | /// Intuitively, the point is represented dually in terms of a _pencil of 61 | /// planes_ that converge on the point itself. When we compute $p | P$, this 62 | /// selects the line perpendicular to $p$ through $P$. Subsequently, taking the 63 | /// inner product with $P$ again selects the plane from the plane pencil of $P$ 64 | /// _least like_ that line. 65 | inline plane KLN_VEC_CALL project(plane a, point b) noexcept 66 | { 67 | return {(a | b) | b}; 68 | } 69 | 70 | /// Project a line onto a point. Given a line $\ell$ and point $P$, produces the 71 | /// line through $P$ that is parallel to $\ell$. 72 | inline line KLN_VEC_CALL project(line a, point b) noexcept 73 | { 74 | return {(a | b) | b}; 75 | } 76 | 77 | /// Project a plane onto a line. Given a plane $p$ and line $\ell$, produces the 78 | /// plane through $\ell$ that is parallel to $p$ if $p \parallel \ell$. 79 | /// 80 | /// If $p \nparallel \ell$, the result will be the plane $p'$ containing $\ell$ 81 | /// that maximizes $p \cdot p'$ (that is, $p'$ is as parallel to $p$ as 82 | /// possible). 83 | inline plane KLN_VEC_CALL project(plane a, line b) noexcept 84 | { 85 | return {(a | b) | b}; 86 | } 87 | /// @} 88 | } // namespace kln 89 | -------------------------------------------------------------------------------- /public/klein/util.hpp: -------------------------------------------------------------------------------- 1 | /// File: util.hpp 2 | /// Description: Collection of various helper routines and constants 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "detail/sse.hpp" 10 | #include "projection.hpp" 11 | 12 | namespace kln 13 | { 14 | template 15 | constexpr T pi_v = 3.141592653589793238462643383279502884L; 16 | 17 | constexpr float pi = pi_v; 18 | 19 | template 20 | constexpr T pi_2_v = 1.570796326794896619231321691639751442L; 21 | 22 | constexpr float pi_2 = pi_2_v; 23 | 24 | template 25 | constexpr T pi_4_v = 0.785398163397448309615660845819875721L; 26 | 27 | constexpr float pi_4 = pi_4_v; 28 | 29 | /// tau = 2 * pi 30 | template 31 | constexpr T tau_v = 6.28318530717958647692528676655900577L; 32 | 33 | constexpr float tau = tau_v; 34 | 35 | template 36 | constexpr T e_v = 2.718281828459045235360287471352662498L; 37 | 38 | constexpr float e = e_v; 39 | 40 | template 41 | constexpr T sqrt2_v = 1.414213562373095048801688724209698079L; 42 | 43 | constexpr float sqrt2 = sqrt2_v; 44 | 45 | /// sqrt(2) / 2 = 1 / sqrt(2) 46 | template 47 | constexpr T sqrt2_2_v = 0.707106781186547524400844362104849039L; 48 | 49 | constexpr float sqrt2_2 = sqrt2_2_v; 50 | } // namespace kln -------------------------------------------------------------------------------- /scripts/demo.klein: -------------------------------------------------------------------------------- 1 | # Translate a point 2 | (1 + b1 e01 + b2 e02 + b3 e03) * (a0 e123 + a1 e032 + a2 e013 + a3 e021) * (1 - b1 e01 - b2 e02 - b3 e03) 3 | 4 | # Translate a plane 5 | (b0 + b1 e01 + b2 e02 + b3 e03) * (a0 e0 + a1 e1 + a2 e2 + a3 e3) * (b0 - b1 e01 - b2 e02 - b3 e03) 6 | 7 | # Rotate a point 8 | (b0 + b1 e23 + b2 e31 + b3 e12) * (a0 e123 + a1 e021 + a2 e013 + a3 e032) * (b0 - b1 e23 - b2 e31 - b3 e12) 9 | 10 | # Rotate a plane 11 | (b0 + b1 e23 + b2 e31 + b3 e12) * (a3 e0 + a2 e1 + a1 e2 + a0 e3) * (b0 - b1 e23 - b2 e31 - b3 e12) 12 | 13 | # Conjugate point with a motor 14 | (b0 + b1 e23 + b2 e31 + b3 e12 + c1 e01 + c2 e02 + c3 e03 + c0 e0123) * (a0 e123 + a1 e032 + a2 e013 + a3 e021) * (b0 - b1 e23 - b2 e31 - b3 e12 - c1 e01 - c2 e02 - c3 e03 + c0 e0123) 15 | 16 | # Example (motor * point * ~motor) 17 | (1 + 2 e23 + 3 e31 + 4 e12 + 5 e01 + 6 e02 + 7 e03 + 8 e0123) * (1e123 + 2e021 + e013 - e032)* (1 - 2 e23 - 3 e31 - 4 e12 - 5 e01 - 6 e02 - 7 e03 + 8 e0123) 18 | 19 | # Conjugate plane with a motor 20 | (b0 + b1 e23 + b2 e31 + b3 e12 + c1 e01 + c2 e02 + c3 e03 + c0 e0123) * (a0 e0 + a1 e1 + a2 e2 + a3 e3) * (b0 - b1 e23 - b2 e31 - b3 e12 - c1 e01 - c2 e02 - c3 e03 + c0 e0123) 21 | 22 | # Example (motor * plane * ~motor) 23 | (1 + 2 e23 + 3 e31 + 4 e12 + 5 e01 + 6 e02 + 7 e03 + 8 e0123) * (3e1 + 2e2 + e3 - e0)* (1 - 2 e23 - 3 e31 - 4 e12 - 5 e01 - 6 e02 - 7 e03 + 8 e0123) 24 | 25 | # Rotor * Translator 26 | (a0 + a1 e23 + a2 e31 + a3 e12) * (1 + b0 e01 + b1 e02 + b2 e03) 27 | 28 | # Conjugate a line with a motor 29 | (b0 + b1 e23 + b2 e31 + b3 e12 + c1 e01 + c2 e02 + c3 e03 + c0 e0123) * (a1 e23 + a2 e31 + a3 e12) * (b0 - b1 e23 - b2 e31 - b3 e12 - c1 e01 - c2 e02 - c3 e03 + c0 e0123) 30 | 31 | # Example (motor * euclidean line * ~motor) 32 | (1 + 2 e23 + 3 e31 + 4 e12 + 5 e01 + 6 e02 + 7 e03 + 8 e0123) * (3e23 + 2e31 + e12 + 1e01 - 2e02)* (1 - 2 e23 - 3 e31 - 4 e12 - 5 e01 - 6 e02 - 7 e03 + 8 e0123) 33 | 34 | # Conjugate origin with motor 35 | (b0 + b1 e23 + b2 e31 + b3 e12 + c1 e01 + c2 e02 + c3 e03 + c0 e0123) * e123 * (b0 - b1 e23 - b2 e31 - b3 e12 - c1 e01 - c2 e02 - c3 e03 + c0 e0123) 36 | 37 | # motor * ~motor 38 | (b0 + b1 e23 + b2 e31 + b3 e12 + c1 e01 + c2 e02 + c3 e03 + c0 e0123) * (b0 - b1 e23 - b2 e31 - b3 e12 - c1 e01 - c2 e02 - c3 e03 + c0 e0123) 39 | 40 | # Inverse norm * motor 41 | (s + t e0123) * (b0 + b1 e23 + b2 e31 + b3 e12 + c1 e01 + c2 e02 + c3 e03 + c0 e0123) 42 | 43 | # Bivector squared norm 44 | (a1 e23 + a2 e31 + a3 e12 + b1 e01 + b2 e02 + b3 e03) * (a1 e23 + a2 e31 + a3 e12 + b1 e01 + b2 e02 + b3 e03) 45 | 46 | # Bivector times its ideal axis and vice versa (note that it commutes) 47 | (a1 e23 + a2 e31 + a3 e12 + b1 e01 + b2 e02 + b3 e03) * (a1 e23 + a2 e31 + a3 e12 + b1 e01 + b2 e02 + b3 e03) * e0123 48 | 49 | (a1 e23 + a2 e31 + a3 e12 + b1 e01 + b2 e02 + b3 e03) * e0123 * (a1 e23 + a2 e31 + a3 e12 + b1 e01 + b2 e02 + b3 e03) 50 | 51 | # rotor * ~rotor 52 | (b0 + b1 e23 + b2 e31 + b3 e12) * (b0 - b1 e23 - b2 e31 - b3 e12) 53 | 54 | # Regressive product 55 | (4e1 - 2e32) & (3e012 + e10) 56 | 57 | # Plane-plane intersection 58 | (3e0 + e1 + e3) ^ (e0 + e3) 59 | 60 | # Plane-line intersection 61 | (-e01 + 2e03 - e31) ^ (e1 + e3) 62 | (e1 + e3) ^ (-e01 + 2e03 - e31) -------------------------------------------------------------------------------- /scripts/test.klein: -------------------------------------------------------------------------------- 1 | # Verification of computations used in various tests 2 | 3 | # Line * ideal line 4 | (e02 + e12 + 2 e31 + 3 e23) * (-2 e01 + e02 + 4 e03) 5 | 6 | # Line * point 7 | (e03 + e12 + 2 e31 + 3 e23) * (-2 e032 + e013 + 4 e021 + e123) 8 | 9 | # Point * line 10 | (-2 e032 + e013 + 4 e021 + e123) * (e03 + e12 + 2 e31 + 3 e23) 11 | 12 | # Dense * dense 13 | (1 + e0 + e1 + e2 + e3 + e12 + e31 + e23 + e01 + e02 + e03 + e123 + e021 + e013 + e032 + e0123) * (1 + e0 + e1 + e2 + e3 + e12 + e31 + e23 + e01 + e02 + e03 + e123 + e021 + e013 + e032 + e0123) 14 | 15 | # Plane ^ plane 16 | (e1 + 2e2 + 3e3 + 4e0) ^ (2e1 + 3e2 - e3 - 2e0) 17 | 18 | # Plane ^ line 19 | (e1 + 2e2 + 3e3 + 4e0) ^ (e03 - 2e12 + e31 + 4e23) 20 | 21 | # Plane ^ ideal line 22 | (e1 + 2e2 + 3e3 + 4e0) ^ (-2e01 + e02 + 4e03) 23 | 24 | # Plane ^ point 25 | (e1 + 2e2 + 3e3 + 4e0) ^ (e123 + -2e032 + e013 + 4e021) 26 | 27 | # Line ^ plane 28 | (e03 - 2e12 + e31 + 4e23) ^ (e1 + 2e2 + 3e3 + 4e0) 29 | 30 | # Line ^ line 31 | (e01 + e12 + 2e31 + 3e23) ^ (e02 - 2e12 + e31 + 4e23) 32 | 33 | # Line ^ ideal line 34 | (e03 - 2e12 + e31 + 4e23) ^ (-2e01 + e02 + 4e03) 35 | 36 | # Line ^ point 37 | (e01 + e12 + 2e31 + 3e23) ^ (e123 - 2e032 + e013 + 4e021) 38 | 39 | # Ideal line ^ plane 40 | (-2e01 + e02 + 4e03) ^ (e1 + 2e2 + 3e3 + 4e0) 41 | 42 | # Ideal line ^ line 43 | (-2e01 + e02 + 4e03) ^ (e03 - 2e12 + e31 + 4e23) 44 | 45 | # Point ^ plane 46 | (e123 + -2e032 + e013 + 4e021) ^ (e1 + 2e2 + 3e3 + 4e0) 47 | 48 | # Point ^ line 49 | (e123 - 2e032 + e013 + 4e021) ^ (e01 + e12 + 2e31 + 3e23) 50 | 51 | # Point ^ ideal line 52 | (e01 + 2e02 + 3e03) ^ (e123 - 2e032 + e013 + 4e021) 53 | 54 | # Point ^ point 55 | (e123 + e032 + 2e013 + 3e021) ^ (e123 - 2e032 + e013 + 4e021) 56 | 57 | # Plane | plane 58 | (e1 + 2e2 + 3e3 + 4e0) | (2e1 + 3e2 -e3 -2e0) 59 | 60 | # Plane | line 61 | (e1 + 2e2 + 3e3 + 4e0) | (e03 - 2e12 + e31 + 4e23) 62 | 63 | # Plane | ideal line 64 | (e1 + 2e2 + 3e3 + 4e0) | (-2e01 + e02 + 4e03) 65 | 66 | # Plane | point 67 | (e1 + 2e2 + 3e3 + 4e0) | (e123 -2e032 + e013 + 4e021) 68 | 69 | # Line | plane 70 | (e03 - 2e12 + e31 + 4e23) | (e1 + 2e2 + 3e3 + 4e0) 71 | 72 | # Line | line 73 | (e01 + e12 + 2e31 + 3e23) | (e02 - 2e12 + e31 + 4e23) 74 | 75 | # Line | ideal line 76 | (e03 - 2e12 + e31 + 4e23) | (-2e01 + e02 + 4e03) 77 | 78 | # Line | point 79 | (e03 + e12 + 2e31 + 3e23) | (e123 -2e032 + e013 + 4e021) 80 | 81 | # Ideal line | plane 82 | (-2e01 + e02 + 4e03) | (e1 + 2e2 + 3e3 + 4e0) 83 | 84 | # Ideal line | plane 85 | (-2e01 + e02 + 4e03) | (e03 + e12 + 2e31 + 3e23) 86 | 87 | # Ideal line | point 88 | (-2e01 + e02 + 4e03) | (e123 - 2e032 + 1e013 + 4e021) 89 | 90 | # Point | plane 91 | (e123 - 2e032 + e013 + 4e021) | (e1 + 2e2 + 3e3 + 4e0) 92 | 93 | # Point | line 94 | (e123 - 2e032 + e013 + 4e021) | (e03 + e12 + 2e31 + 3e23) 95 | 96 | # Point | line 97 | (e123 - 2e032 + e013 + 4e021) | (e01 + 2e02 + 3e03) 98 | 99 | # Point | point 100 | (e123 + e032 + 2e013 + 3e021) | (e123 - 2e032 + e013 + 4e021) 101 | 102 | # Dense | dense 103 | (1 + e0 + e1 + e2 + e3 + e12 + e31 + e23 + e01 + e02 + e03 + e123 + e021 + e013 + e032 + e0123) | (1 + e0 + e1 + e2 + e3 + e12 + e31 + e23 + e01 + e02 + e03 + e123 + e021 + e013 + e032 + e0123) 104 | 105 | # Reflect plane 106 | (3e1 + 2e2 + e3 - e0) * (e1 + 2e2 - e3 - 3e0) *(3e1 + 2e2 + e3 - e0) 107 | 108 | # Reflect line 109 | (3e1 + 2e2 + e3 - e0) * (e01 - 2e02 + 3e03 - 4e12 + 5e31 + 6e23) *(3e1 + 2e2 + e3 - e0) 110 | 111 | # Reflect point 112 | (3e1 + 2e2 + e3 - e0) * (e123 + 4e032 - 2e013 - e021) *(3e1 + 2e2 + e3 - e0) 113 | 114 | # Rotate plane 115 | (1 + 2e12 - 3e31 + 4e23) * (2e0 + 3e1 + 4e2 + 5e3) * (1 - 2e12 + 3e31 - 4e23) 116 | 117 | # Rotate point 118 | (1 + 2e12 - 3e31 + 4e23) * (2e123 + 3e032 + 4e013 + 5e021) * (1 - 2e12 + 3e31 - 4e23) 119 | 120 | # Rotate line 121 | (1 + 2e12 - 3e31 + 4e23) * (-e01 + 2e02 - 3e03 + 4e12 + 5e31 - 6e23) * (1 - 2e12 + 3e31 - 4e23) 122 | 123 | # Translate line 124 | (1 - 5e01 - 2e02 + 2e03) * (-e01 + 2e02 - 3e03 + 4e12 + 5e31 - 6e23) * (1 + 5e01 + 2e02 - 2e03) 125 | 126 | # Motorize line 127 | (2 - e12 + 3e31 + 4e23 - 5e01 - 2e02 + 2e03 - 3e0123) * (-e01 + 2e02 - 3e03 + 4e12 + 5e31 - 6e23) * (2 + e12 - 3e31 - 4e23 + 5e01 + 2e02 - 2e03 - 3e0123) 128 | 129 | # Motor plane 130 | (1 + 2e12 - 3e31 + 4e23 + 5e01 + 6e02 + 7e03 + 8e0123) * (2e0 + 3e1 + 4e2 + 5e3) * (1 - 2e12 + 3e31 - 4e23 - 5e01 - 6e02 - 7e03 + 8e0123) 131 | 132 | # Motor point 133 | (1 + 2e12 - 3e31 + 4e23 + 5e01 + 6e02 + 7e03 + 8e0123) * (2e123 + 3e032 + 4e013 + 5e021) * (1 - 2e12 + 3e31 - 4e23 - 5e01 - 6e02 - 7e03 + 8e0123) 134 | 135 | # Motor origin 136 | (1 + 2e12 - 3e31 + 4e23 + 5e01 + 6e02 + 7e03 + 8e0123) * e123 * (1 - 2e12 + 3e31 - 4e23 - 5e01 - 6e02 - 7e03 + 8e0123) 137 | 138 | # Rotor * rotor 139 | (2 + 3e23 + 4e31 + 5e12) * (6 + 7e23 + 8e31 + 9e12) 140 | 141 | # Motor * motor 142 | (2 + 3e23 + 4e31 + 5e12 + 6e01 + 7e02 + 8e03 + 9e0123) * (6 + 7e23 + 8e31 + 9e12 + 10e01 + 11e02 + 12e03 + 13e0123) 143 | 144 | # Branch * Branch 145 | (e23 - 2e31 - 3e12) * (2e23 + e31 + 3e12) -------------------------------------------------------------------------------- /sym/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(symlib ga.cpp repl.cpp parser.cpp poly.cpp) 2 | target_compile_features(symlib PUBLIC cxx_std_17) 3 | 4 | if(NOT MSVC) 5 | # Needed for popcnt intrinsic 6 | target_compile_options(symlib PUBLIC -msse4.2) 7 | endif() 8 | 9 | add_executable(klein_shell main.cpp) 10 | target_link_libraries(klein_shell PRIVATE symlib) 11 | set_target_properties(klein_shell 12 | PROPERTIES 13 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 14 | ) 15 | 16 | if(KLEIN_ENABLE_TESTS) 17 | add_executable(sym_test test.cpp) 18 | target_link_libraries(sym_test PRIVATE symlib doctest) 19 | target_compile_definitions(sym_test PRIVATE 20 | DOCTEST_CONFIG_SUPER_FAST_ASSERTS # uses a function call for asserts to speed up compilation 21 | DOCTEST_CONFIG_USE_STD_HEADERS # prevent non-standard overloading of std declarations 22 | DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS # enable doctest::Approx() to take any argument explicitly convertible to a double 23 | DOCTEST_CONFIG_NO_POSIX_SIGNALS 24 | DOCTEST_CONFIG_NO_EXCEPTIONS 25 | ) 26 | set_target_properties(sym_test 27 | PROPERTIES 28 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 29 | ) 30 | endif() -------------------------------------------------------------------------------- /sym/ga.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "poly.hpp" 4 | 5 | #include 6 | #include 7 | 8 | uint32_t popcnt(uint32_t i); 9 | 10 | class algebra 11 | { 12 | public: 13 | // Positive dimensions, negative dimensions, degenerate dimensions 14 | // Note that in terms of basis labels, degenerate dimensions come first so 15 | // that the indexing doesn't vary with spatial dimension 16 | algebra(uint32_t p, uint32_t q, uint32_t r); 17 | 18 | // The sign of the result indicates if a sign flip is needed 19 | // Elements are encoded 1 + the bitfield to disambiguate 0. 20 | int32_t mul(uint32_t lhs, uint32_t rhs) const noexcept; 21 | 22 | // Exterior product 23 | // Results are encoded in the same way as mul. 24 | int32_t ext(uint32_t lhs, uint32_t rhs) const noexcept; 25 | 26 | // Regressive product (J(J(lhs) ^ J(rhs))) 27 | int32_t reg(uint32_t lhs, uint32_t rhs) const noexcept; 28 | 29 | // Symmetric inner product 30 | int32_t dot(uint32_t lhs, uint32_t rhs) const noexcept; 31 | 32 | // Applies the dual coordinate map J 33 | int32_t dual(uint32_t in) const noexcept; 34 | 35 | // Return the parity of a reversion operator on a given element 36 | bool rev(uint32_t in) const noexcept; 37 | 38 | bool operator==(algebra const&) const noexcept; 39 | 40 | // Pseudoscalar 41 | uint32_t pss() const noexcept 42 | { 43 | return (1 << dim_) - 1; 44 | } 45 | 46 | private: 47 | uint32_t p_; 48 | uint32_t q_; 49 | uint32_t r_; 50 | uint32_t dim_; 51 | }; 52 | 53 | struct elem_comp 54 | { 55 | bool operator()(uint32_t lhs, uint32_t rhs) const noexcept 56 | { 57 | uint32_t lhs_pop = popcnt(lhs); 58 | uint32_t rhs_pop = popcnt(rhs); 59 | 60 | if (lhs_pop < rhs_pop) 61 | { 62 | return true; 63 | } 64 | else if (lhs_pop > rhs_pop) 65 | { 66 | return false; 67 | } 68 | else 69 | { 70 | return lhs < rhs; 71 | } 72 | } 73 | }; 74 | 75 | class mv 76 | { 77 | public: 78 | mv() = default; 79 | mv(algebra const& a) noexcept; 80 | 81 | mv operator-() const& noexcept; 82 | mv& operator-() && noexcept; 83 | mv operator~() const& noexcept; 84 | mv& operator~() && noexcept; 85 | mv& operator+=(mv const& other) noexcept; 86 | mv& operator-=(mv const& other) noexcept; 87 | mv& operator*=(mv const& other) noexcept; 88 | mv& operator^=(mv const& other) noexcept; 89 | mv& operator&=(mv const& other) noexcept; 90 | mv& operator|=(mv const& other) noexcept; 91 | 92 | mv& push(uint32_t e, poly const& p) noexcept; 93 | 94 | // Map from basis blade to polynomial 95 | std::map terms; 96 | 97 | private: 98 | // Remove terms that are exactly zero 99 | mv& prune(); 100 | 101 | friend mv operator+(mv const& lhs, mv const& rhs) noexcept; 102 | friend mv operator*(mv const& lhs, mv const& rhs) noexcept; 103 | friend mv operator^(mv const& lhs, mv const& rhs) noexcept; 104 | friend mv operator&(mv const& lhs, mv const& rhs) noexcept; 105 | friend mv operator|(mv const& lhs, mv const& rhs) noexcept; 106 | algebra const* algebra_ = nullptr; 107 | }; 108 | 109 | mv operator+(mv const& lhs, mv const& rhs) noexcept; 110 | mv operator*(mv const& lhs, mv const& rhs) noexcept; 111 | mv operator^(mv const& lhs, mv const& rhs) noexcept; 112 | mv operator&(mv const& lhs, mv const& rhs) noexcept; 113 | mv operator|(mv const& lhs, mv const& rhs) noexcept; 114 | std::ostream& operator<<(std::ostream& os, mv const& m) noexcept; -------------------------------------------------------------------------------- /sym/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ga.hpp" 2 | #include "parser.hpp" 3 | #include "repl.hpp" 4 | 5 | int main(int argc, char** argv) 6 | { 7 | // std::string test = "4e0 + (b + c) * e102"; 8 | // std::string test = "(3e0 + 3e012) + (3e0 - 3e012)"; 9 | // algebra pga{3, 0, 1}; 10 | // std::cout << tokenize(test, a); 11 | // mv mv1 = parse("(e23 + 2e012) * (2 - 3e01)", pga); 12 | // std::cout << mv1 << std::endl; 13 | repl r; 14 | r.run(); 15 | return 0; 16 | } -------------------------------------------------------------------------------- /sym/parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ga.hpp" 4 | 5 | #include 6 | #include 7 | 8 | enum class token_type 9 | { 10 | element, 11 | number, 12 | identifier, 13 | delimiter, 14 | op, 15 | eof 16 | }; 17 | 18 | struct identifier 19 | { 20 | char const* data; 21 | size_t size; 22 | }; 23 | 24 | struct element 25 | { 26 | uint8_t value; 27 | bool negative; 28 | }; 29 | 30 | struct token 31 | { 32 | union 33 | { 34 | element e; 35 | float num; 36 | identifier id; 37 | char delimiter; 38 | char op; 39 | }; 40 | token_type type; 41 | }; 42 | 43 | // iostream operators for convenient debugging 44 | std::ostream& operator<<(std::ostream& os, identifier const& id); 45 | std::ostream& operator<<(std::ostream& os, element const& el); 46 | std::ostream& operator<<(std::ostream& os, token const& t); 47 | std::ostream& operator<<(std::ostream& os, std::vector const& tokens); 48 | 49 | std::vector tokenize(std::string const& input, algebra const& algebra_); 50 | 51 | mv parse(std::string const& input, algebra const& a); 52 | -------------------------------------------------------------------------------- /sym/poly.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class mon 7 | { 8 | public: 9 | // Map from variable to degree 10 | // A map is used here so that traversal order is deterministic 11 | std::map factors; 12 | 13 | mon& operator*=(mon const& other) noexcept; 14 | 15 | // Multiply the monomial by a named variable with degree 16 | mon& push(std::string var, int deg = 1) noexcept; 17 | 18 | int degree() const noexcept; 19 | }; 20 | 21 | mon operator*(mon const& lhs, mon const& rhs) noexcept; 22 | 23 | namespace std 24 | { 25 | template <> 26 | struct hash 27 | { 28 | size_t operator()(mon const& in) const noexcept 29 | { 30 | size_t out = 0; 31 | size_t k; 32 | if constexpr (sizeof(void*) == 8) 33 | { 34 | k = 0x517cc1b727220a95; 35 | } 36 | else 37 | { 38 | k = 0x9e3779b9; 39 | } 40 | 41 | for (auto&& [var, deg] : in.factors) 42 | { 43 | out ^= std::hash{}(var) + k + (out >> 2) + (out << 6); 44 | out ^= std::hash{}(deg) + k + (out >> 2) + (out << 6); 45 | } 46 | return out; 47 | } 48 | }; 49 | } // namespace std 50 | 51 | bool operator==(mon const& lhs, mon const& rhs) noexcept; 52 | bool operator<(mon const& lhs, mon const& rhs) noexcept; 53 | 54 | class poly 55 | { 56 | public: 57 | // Map from monomial to scalar coefficient 58 | std::map terms; 59 | 60 | poly& push(mon const& m, float f = 1.f) noexcept; 61 | poly& operator+=(poly const& other) noexcept; 62 | poly& operator*=(poly const& other) noexcept; 63 | poly operator-() const& noexcept; 64 | poly& operator-() && noexcept; 65 | }; 66 | 67 | poly operator+(poly const& lhs, poly const& rhs) noexcept; 68 | poly operator*(poly const& lhs, poly const& rhs) noexcept; 69 | std::ostream& operator<<(std::ostream& os, poly const& p) noexcept; -------------------------------------------------------------------------------- /sym/repl.cpp: -------------------------------------------------------------------------------- 1 | #include "repl.hpp" 2 | 3 | #include "parser.hpp" 4 | 5 | #include 6 | #include 7 | 8 | enum codes 9 | { 10 | key_up = 72, 11 | key_down = 80, 12 | }; 13 | 14 | void repl::run() 15 | { 16 | // Allow specification of algebra 17 | algebra a{3, 0, 1}; 18 | for (std::string line; std::getline(std::cin, line);) 19 | { 20 | if (line.empty()) 21 | { 22 | std::cout << std::endl; 23 | continue; 24 | } 25 | 26 | // Skip lines that only contain whitespace or commas 27 | bool should_parse = false; 28 | bool issue_command = false; 29 | for (auto c : line) 30 | { 31 | if (std::isspace(c)) 32 | { 33 | continue; 34 | } 35 | else if (c == '#') 36 | { 37 | // Lines that begin with a # are comments 38 | // Echo them in the output 39 | std::cout << line; 40 | break; 41 | } 42 | else if (c == '.') 43 | { 44 | // Lines that start with a . are commands 45 | issue_command = true; 46 | break; 47 | } 48 | else 49 | { 50 | should_parse = true; 51 | } 52 | } 53 | 54 | if (issue_command) 55 | { 56 | // TODO replace with an actual command parser 57 | if (line == ".break") 58 | { 59 | break_lines = !break_lines; 60 | } 61 | continue; 62 | } 63 | else if (!should_parse) 64 | { 65 | std::cout << std::endl; 66 | continue; 67 | } 68 | 69 | try 70 | { 71 | mv result = parse(line, a); 72 | std::cout << result << std::endl; 73 | } 74 | catch (const std::runtime_error& e) 75 | { 76 | std::cerr << e.what() << '\n'; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /sym/repl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class repl 4 | { 5 | public: 6 | void run(); 7 | 8 | private: 9 | bool break_lines = false; 10 | }; -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | doctest 5 | GIT_REPOSITORY https://github.com/onqtam/doctest.git 6 | GIT_TAG 2.3.6 7 | ) 8 | FetchContent_MakeAvailable(doctest) 9 | 10 | list(APPEND CMAKE_MODULE_PATH ${doctest_SOURCE_DIR}/scripts/cmake) 11 | 12 | add_executable(klein_test 13 | main.cpp 14 | test_ep.cpp 15 | test_exp_log.cpp 16 | test_ip.cpp 17 | test_gp.cpp 18 | test_metric.cpp 19 | test_rp.cpp 20 | test_sse.cpp 21 | test_sw.cpp 22 | ) 23 | target_link_libraries(klein_test PRIVATE klein::klein doctest) 24 | target_compile_features(klein_test PRIVATE cxx_std_17) 25 | target_compile_definitions(klein_test PRIVATE 26 | DOCTEST_CONFIG_SUPER_FAST_ASSERTS # uses a function call for asserts to speed up compilation 27 | DOCTEST_CONFIG_USE_STD_HEADERS # prevent non-standard overloading of std declarations 28 | DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS # enable doctest::Approx() to take any argument explicitly convertible to a double 29 | DOCTEST_CONFIG_NO_POSIX_SIGNALS 30 | DOCTEST_CONFIG_NO_EXCEPTIONS 31 | ) 32 | if (NOT MSVC) 33 | target_compile_options(klein_test 34 | PRIVATE 35 | -fno-omit-frame-pointer 36 | -Wall 37 | -Wno-comment # Needed for doxygen 38 | ) 39 | endif() 40 | # Place the test executable at the project binary directory instead of in the nested subfolder 41 | set_target_properties(klein_test 42 | PROPERTIES 43 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 44 | ) 45 | 46 | add_executable(klein_test_sse42 47 | main.cpp 48 | test_ep.cpp 49 | test_exp_log.cpp 50 | test_ip.cpp 51 | test_gp.cpp 52 | test_metric.cpp 53 | test_rp.cpp 54 | test_sse.cpp 55 | test_sw.cpp 56 | ) 57 | target_link_libraries(klein_test_sse42 PRIVATE klein::klein_sse42 doctest) 58 | target_compile_features(klein_test_sse42 PRIVATE cxx_std_17) 59 | target_compile_definitions(klein_test_sse42 PRIVATE 60 | DOCTEST_CONFIG_SUPER_FAST_ASSERTS # uses a function call for asserts to speed up compilation 61 | DOCTEST_CONFIG_USE_STD_HEADERS # prevent non-standard overloading of std declarations 62 | DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS # enable doctest::Approx() to take any argument explicitly convertible to a double 63 | DOCTEST_CONFIG_NO_POSIX_SIGNALS 64 | DOCTEST_CONFIG_NO_EXCEPTIONS 65 | ) 66 | if (NOT MSVC) 67 | target_compile_options(klein_test_sse42 68 | PRIVATE 69 | -fno-omit-frame-pointer 70 | -fsanitize=address 71 | -Wall 72 | -Wno-comment # Needed for doxygen 73 | ) 74 | target_link_options(klein_test_sse42 PRIVATE -fno-omit-frame-pointer -fsanitize=address) 75 | endif() 76 | # Place the test executable at the project binary directory instead of in the nested subfolder 77 | set_target_properties(klein_test_sse42 78 | PROPERTIES 79 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 80 | ) 81 | 82 | add_executable(klein_test_cxx11 83 | main.cpp 84 | test_ep.cpp 85 | test_exp_log.cpp 86 | test_ip.cpp 87 | test_gp.cpp 88 | test_metric.cpp 89 | test_rp.cpp 90 | test_sse.cpp 91 | test_sw.cpp 92 | ) 93 | target_link_libraries(klein_test_cxx11 PRIVATE klein::klein_cxx11 doctest) 94 | target_compile_features(klein_test_cxx11 PRIVATE cxx_std_11) 95 | target_compile_definitions(klein_test_cxx11 PRIVATE 96 | DOCTEST_CONFIG_SUPER_FAST_ASSERTS # uses a function call for asserts to speed up compilation 97 | DOCTEST_CONFIG_USE_STD_HEADERS # prevent non-standard overloading of std declarations 98 | DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS # enable doctest::Approx() to take any argument explicitly convertible to a double 99 | DOCTEST_CONFIG_NO_POSIX_SIGNALS 100 | DOCTEST_CONFIG_NO_EXCEPTIONS 101 | ) 102 | if (NOT MSVC) 103 | target_compile_options(klein_test_cxx11 104 | PRIVATE 105 | -fno-omit-frame-pointer 106 | -fsanitize=address 107 | -Wall 108 | -Wno-comment # Needed for doxygen 109 | ) 110 | target_link_options(klein_test_cxx11 PRIVATE -fno-omit-frame-pointer -fsanitize=address) 111 | endif() 112 | # Place the test executable at the project binary directory instead of in the nested subfolder 113 | set_target_properties(klein_test_cxx11 114 | PROPERTIES 115 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 116 | ) 117 | 118 | add_executable(klein_test_glsl test_glsl.cpp) 119 | target_include_directories(klein_test_glsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../glsl) 120 | target_link_libraries(klein_test_glsl PRIVATE doctest) 121 | target_compile_features(klein_test_glsl PRIVATE cxx_std_17) 122 | target_compile_definitions(klein_test_glsl PRIVATE 123 | DOCTEST_CONFIG_SUPER_FAST_ASSERTS # uses a function call for asserts to speed up compilation 124 | DOCTEST_CONFIG_USE_STD_HEADERS # prevent non-standard overloading of std declarations 125 | DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS # enable doctest::Approx() to take any argument explicitly convertible to a double 126 | DOCTEST_CONFIG_NO_POSIX_SIGNALS 127 | DOCTEST_CONFIG_NO_EXCEPTIONS 128 | ) 129 | set_target_properties(klein_test_glsl 130 | PROPERTIES 131 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 132 | ) -------------------------------------------------------------------------------- /test/glsl_shim.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | constexpr uint8_t swizzle_index(char const& c) 7 | { 8 | switch (c) 9 | { 10 | case 'x': 11 | return 0; 12 | case 'y': 13 | return 1; 14 | case 'z': 15 | return 2; 16 | case 'w': 17 | return 3; 18 | } 19 | return 0; 20 | } 21 | 22 | #define SWIZZLE(a, b, c, d) \ 23 | swizzle \ 27 | a##b##c##d 28 | 29 | #define SWIZZLE_3(a, b, c) \ 30 | SWIZZLE(a, b, c, x); \ 31 | SWIZZLE(a, b, c, y); \ 32 | SWIZZLE(a, b, c, z); \ 33 | SWIZZLE(a, b, c, w); 34 | 35 | #define SWIZZLE_2(a, b) \ 36 | SWIZZLE_3(a, b, x); \ 37 | SWIZZLE_3(a, b, y); \ 38 | SWIZZLE_3(a, b, z); \ 39 | SWIZZLE_3(a, b, w); 40 | 41 | #define SWIZZLE_1(a) \ 42 | SWIZZLE_2(a, x); \ 43 | SWIZZLE_2(a, y); \ 44 | SWIZZLE_2(a, z); \ 45 | SWIZZLE_2(a, w); 46 | 47 | #define SWIZZLES \ 48 | SWIZZLE_1(x); \ 49 | SWIZZLE_1(y); \ 50 | SWIZZLE_1(z); \ 51 | SWIZZLE_1(w) 52 | 53 | // Redefine various glsl types and keywords 54 | 55 | #define in 56 | #define out 57 | 58 | struct vec4 59 | { 60 | template 61 | struct swizzle 62 | { 63 | constexpr operator vec4() const noexcept 64 | { 65 | float const* data = reinterpret_cast(this); 66 | return {data[a], data[b], data[c], data[d]}; 67 | } 68 | 69 | template 70 | vec4 operator*(swizzle const& other) const noexcept 71 | { 72 | return static_cast(*this) * static_cast(other); 73 | } 74 | 75 | vec4 operator*(vec4 const& other) const noexcept 76 | { 77 | return static_cast(*this) * other; 78 | } 79 | }; 80 | 81 | template 82 | struct component 83 | { 84 | operator float() const noexcept 85 | { 86 | return reinterpret_cast(this)[i]; 87 | } 88 | 89 | vec4 operator*(vec4 const& other) const noexcept 90 | { 91 | return other * static_cast(*this); 92 | } 93 | 94 | float operator-() const noexcept 95 | { 96 | return -reinterpret_cast(this)[i]; 97 | } 98 | 99 | component& operator=(float other) noexcept 100 | { 101 | reinterpret_cast(this)[i] = other; 102 | return *this; 103 | } 104 | }; 105 | 106 | union 107 | { 108 | float data[4]; 109 | component<0> x; 110 | component<1> y; 111 | component<2> z; 112 | component<3> w; 113 | SWIZZLES; 114 | }; 115 | 116 | vec4() = default; 117 | 118 | vec4(float a, float b, float c, float d) noexcept 119 | : data{a, b, c, d} 120 | {} 121 | 122 | vec4 operator*(float other) const noexcept 123 | { 124 | vec4 result; 125 | for (size_t i = 0; i != 4; ++i) 126 | { 127 | result.data[i] = data[i] * other; 128 | } 129 | return result; 130 | } 131 | 132 | vec4& operator*=(float other) noexcept 133 | { 134 | for (size_t i = 0; i != 4; ++i) 135 | { 136 | data[i] = data[i] * other; 137 | } 138 | return *this; 139 | } 140 | 141 | template 142 | vec4 operator*(component const& other) const noexcept 143 | { 144 | vec4 result; 145 | for (size_t i = 0; i != 4; ++i) 146 | { 147 | result.data[i] = data[i] * static_cast(other); 148 | } 149 | return result; 150 | } 151 | 152 | template 153 | vec4& operator*=(component const& other) noexcept 154 | { 155 | for (size_t i = 0; i != 4; ++i) 156 | { 157 | data[i] = data[i] * static_cast(other); 158 | } 159 | return *this; 160 | } 161 | 162 | vec4 operator+(const vec4& other) const noexcept 163 | { 164 | vec4 result; 165 | for (size_t i = 0; i != 4; ++i) 166 | { 167 | result.data[i] = data[i] + other.data[i]; 168 | } 169 | return result; 170 | } 171 | 172 | vec4 operator*(const vec4& other) const noexcept 173 | { 174 | vec4 result; 175 | for (size_t i = 0; i != 4; ++i) 176 | { 177 | result.data[i] = data[i] * other.data[i]; 178 | } 179 | return result; 180 | } 181 | 182 | vec4 operator-(const vec4& other) const noexcept 183 | { 184 | vec4 result; 185 | for (size_t i = 0; i != 4; ++i) 186 | { 187 | result.data[i] = data[i] - other.data[i]; 188 | } 189 | return result; 190 | } 191 | 192 | vec4& operator+=(const vec4& other) noexcept 193 | { 194 | for (size_t i = 0; i != 4; ++i) 195 | { 196 | data[i] += other.data[i]; 197 | } 198 | return *this; 199 | } 200 | 201 | vec4& operator*=(const vec4& other) noexcept 202 | { 203 | for (size_t i = 0; i != 4; ++i) 204 | { 205 | data[i] *= other.data[i]; 206 | } 207 | return *this; 208 | } 209 | 210 | vec4& operator-=(const vec4& other) noexcept 211 | { 212 | for (size_t i = 0; i != 4; ++i) 213 | { 214 | data[i] -= other.data[i]; 215 | } 216 | return *this; 217 | } 218 | }; 219 | 220 | float dot(vec4 const& a, vec4 const& b) 221 | { 222 | float result = 0; 223 | for (size_t i = 0; i != 4; ++i) 224 | { 225 | result += a.data[i] * b.data[i]; 226 | } 227 | return result; 228 | } -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include 3 | 4 | #include 5 | 6 | // Cheat sheet 7 | // TEST_SUITE_BEGIN defines the start of a scope of tests grouped under a suite 8 | // TEST_SUITE_END ends the test suite scope 9 | // TEST_CASE defines a test case and takes a string name 10 | // After the test name, the * operator can be used to add decorators 11 | // - skip(bool = true) 12 | // - may_fail(bool = true) 13 | // - should_fail(bool = true) 14 | // - expected_failures(int) 15 | // - timeout(double) 16 | // - description("text") 17 | // TEST_CASE_TEMPLATE takes a comma separated list of types after the test name 18 | // and type parameter name SUBCASE defines a sub-test case within the scope of a 19 | // test case 20 | 21 | // CHECK checks a condition 22 | // REQUIRE checks a condition and stops the test if the condition fails 23 | // REQUIRE_FALSE ensures that a condition fails 24 | // For most asserts though, use 25 | // [REQUIRE|CHECK|WARN]_[EQ|NE|GT|LT|GE|LE|UNARY|UNARY_FALSE] as these compile 26 | // faster. 27 | 28 | // For floating point comparison, there is the handy helper `doctest::Approx(2, 29 | // 1)` which when compared to another float, checks the comparison to within a 30 | // percentage-based tolerance. You can specify the tolerance using 31 | // `doctest::Approx::epsilon(float percentage)`. 32 | 33 | // Command-line 34 | // -ltc list-test-cases 35 | // -lts list-test-suites 36 | // -tc (comma separated test cases) 37 | // -tce negated -tc 38 | // -ts (comma separated test suites) 39 | // -tse negated -ts 40 | // -rs randomizes test order 41 | // -d time each test and print duration 42 | 43 | using namespace kln; 44 | 45 | TEST_CASE("multivector-sum") 46 | { 47 | SUBCASE("points") 48 | { 49 | point p1{1.f, 2.f, 3.f}; 50 | point p2{2.f, 3.f, -1.f}; 51 | point p3 = p1 + p2; 52 | CHECK_EQ(p3.x(), 1.f + 2.f); 53 | CHECK_EQ(p3.y(), 2.f + 3.f); 54 | CHECK_EQ(p3.z(), 3.f + -1.f); 55 | 56 | point p4 = p1 - p2; 57 | CHECK_EQ(p4.x(), 1.f - 2.f); 58 | CHECK_EQ(p4.y(), 2.f - 3.f); 59 | CHECK_EQ(p4.z(), 3.f - -1.f); 60 | 61 | // Adding rvalue to lvalue 62 | point p5 = point{1.f, 2.f, 3.f} + p2; 63 | CHECK_EQ(p5.x(), 1.f + 2.f); 64 | CHECK_EQ(p5.y(), 2.f + 3.f); 65 | CHECK_EQ(p5.z(), 3.f + -1.f); 66 | 67 | // Adding rvalue to rvalue 68 | point p6 = point{1.f, 2.f, 3.f} + point{2.f, 3.f, -1.f}; 69 | CHECK_EQ(p6.x(), 1.f + 2.f); 70 | CHECK_EQ(p6.y(), 2.f + 3.f); 71 | CHECK_EQ(p6.z(), 3.f + -1.f); 72 | } 73 | 74 | SUBCASE("planes") 75 | { 76 | plane p{1.f, 3.f, 4.f, -5.f}; 77 | float p_norm = p | p; 78 | CHECK_NE(p_norm, 1.f); 79 | p.normalize(); 80 | p_norm = p | p; 81 | CHECK_EQ(p_norm, doctest::Approx(1.f)); 82 | } 83 | } 84 | 85 | TEST_CASE("rotor-constrain") 86 | { 87 | rotor r1{1, 2, 3, 4}; 88 | rotor r2 = r1.constrained(); 89 | CHECK_EQ(r1, r2); 90 | 91 | r1 = -r1; 92 | r2 = r1.constrained(); 93 | CHECK_EQ(r1, -r2); 94 | } 95 | 96 | TEST_CASE("motor-constrain") 97 | { 98 | motor m1{1, 2, 3, 4, 5, 6, 7, 8}; 99 | motor m2 = m1.constrained(); 100 | CHECK_EQ(m1, m2); 101 | 102 | m1 = -m1; 103 | m2 = m1.constrained(); 104 | CHECK_EQ(m1, -m2); 105 | } -------------------------------------------------------------------------------- /test/test_ep.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace kln; 6 | 7 | TEST_CASE("multivector-ep") 8 | { 9 | SUBCASE("plane^plane") 10 | { 11 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 12 | plane p1{1.f, 2.f, 3.f, 4.f}; 13 | plane p2{2.f, 3.f, -1.f, -2.f}; 14 | line p12 = p1 ^ p2; 15 | CHECK_EQ(p12.e01(), 10.f); 16 | CHECK_EQ(p12.e02(), 16.f); 17 | CHECK_EQ(p12.e03(), 2.f); 18 | CHECK_EQ(p12.e12(), -1.f); 19 | CHECK_EQ(p12.e31(), 7.f); 20 | CHECK_EQ(p12.e23(), -11.f); 21 | } 22 | 23 | SUBCASE("plane^line") 24 | { 25 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 26 | plane p1{1.f, 2.f, 3.f, 4.f}; 27 | 28 | // a*e01 + b*e02 + c*e03 + d*e23 + e*e31 + f*e12 29 | line l1{0.f, 0.f, 1.f, 4.f, 1.f, -2.f}; 30 | 31 | point p1l1 = p1 ^ l1; 32 | CHECK_EQ(p1l1.e021(), 8.f); 33 | CHECK_EQ(p1l1.e013(), -5.f); 34 | CHECK_EQ(p1l1.e032(), -14.f); 35 | CHECK_EQ(p1l1.e123(), 0.f); 36 | } 37 | 38 | SUBCASE("plane^ideal-line") 39 | { 40 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 41 | plane p1{1.f, 2.f, 3.f, 4.f}; 42 | 43 | // a*e01 + b*e02 + c*e03 44 | ideal_line l1{-2.f, 1.f, 4.f}; 45 | 46 | point p1l1 = p1 ^ l1; 47 | CHECK_EQ(p1l1.e021(), 5.f); 48 | CHECK_EQ(p1l1.e013(), -10.f); 49 | CHECK_EQ(p1l1.e032(), 5.f); 50 | CHECK_EQ(p1l1.e123(), 0.f); 51 | } 52 | 53 | SUBCASE("plane^point") 54 | { 55 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 56 | plane p1{1.f, 2.f, 3.f, 4.f}; 57 | // x*e_032 + y*e_013 + z*e_021 + e_123 58 | point p2{-2.f, 1.f, 4.f}; 59 | 60 | dual p1p2 = p1 ^ p2; 61 | CHECK_EQ(p1p2.scalar(), 0.f); 62 | CHECK_EQ(p1p2.e0123(), 16.f); 63 | } 64 | 65 | SUBCASE("line^plane") 66 | { 67 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 68 | plane p1{1.f, 2.f, 3.f, 4.f}; 69 | 70 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 71 | line l1{0.f, 0.f, 1.f, 4.f, 1.f, -2.f}; 72 | 73 | point p1l1 = l1 ^ p1; 74 | CHECK_EQ(p1l1.e021(), 8.f); 75 | CHECK_EQ(p1l1.e013(), -5.f); 76 | CHECK_EQ(p1l1.e032(), -14.f); 77 | CHECK_EQ(p1l1.e123(), 0.f); 78 | } 79 | 80 | SUBCASE("line^line") 81 | { 82 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 83 | line l1{1.f, 0.f, 0.f, 3.f, 2.f, 1.f}; 84 | line l2{0.f, 1.f, 0.f, 4.f, 1.f, -2.f}; 85 | 86 | dual l1l2 = l1 ^ l2; 87 | CHECK_EQ(l1l2.e0123(), 6.f); 88 | } 89 | 90 | SUBCASE("line^ideal-line") 91 | { 92 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 93 | line l1{0.f, 0.f, 1.f, 3.f, 2.f, 1.f}; 94 | // a*e01 + b*e02 + c*e03 95 | ideal_line l2{-2.f, 1.f, 4.f}; 96 | 97 | dual l1l2 = l1 ^ l2; 98 | CHECK_EQ(l1l2.e0123(), 0.f); 99 | CHECK_EQ(l1l2.scalar(), 0.f); 100 | } 101 | 102 | SUBCASE("ideal-line^plane") 103 | { 104 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 105 | plane p1{1.f, 2.f, 3.f, 4.f}; 106 | 107 | // a*e01 + b*e02 + c*e03 108 | ideal_line l1{-2.f, 1.f, 4.f}; 109 | 110 | point p1l1 = l1 ^ p1; 111 | CHECK_EQ(p1l1.e021(), 5.f); 112 | CHECK_EQ(p1l1.e013(), -10.f); 113 | CHECK_EQ(p1l1.e032(), 5.f); 114 | CHECK_EQ(p1l1.e123(), 0.f); 115 | } 116 | 117 | SUBCASE("point^plane") 118 | { 119 | // x*e_032 + y*e_013 + z*e_021 + e_123 120 | point p1{-2.f, 1.f, 4.f}; 121 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 122 | plane p2{1.f, 2.f, 3.f, 4.f}; 123 | 124 | dual p1p2 = p1 ^ p2; 125 | CHECK_EQ(p1p2.e0123(), -16.f); 126 | } 127 | } -------------------------------------------------------------------------------- /test/test_exp_log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace kln; 6 | 7 | TEST_CASE("rotor-exp-log") 8 | { 9 | rotor r{kln::pi * 0.5f, 0.3f, -3.f, 1.f}; 10 | branch b = log(r); 11 | rotor r2 = exp(b); 12 | 13 | CHECK_EQ(r2.scalar(), doctest::Approx(r.scalar())); 14 | CHECK_EQ(r2.e12(), doctest::Approx(r.e12())); 15 | CHECK_EQ(r2.e31(), doctest::Approx(r.e31())); 16 | CHECK_EQ(r2.e23(), doctest::Approx(r.e23())); 17 | } 18 | 19 | TEST_CASE("rotor-sqrt") 20 | { 21 | rotor r1{kln::pi * 0.5f, 0.3f, -3.f, 1.f}; 22 | rotor r2 = sqrt(r1); 23 | rotor r3 = r2 * r2; 24 | CHECK_EQ(r1.scalar(), doctest::Approx(r3.scalar())); 25 | CHECK_EQ(r1.e12(), doctest::Approx(r3.e12())); 26 | CHECK_EQ(r1.e31(), doctest::Approx(r3.e31())); 27 | CHECK_EQ(r1.e23(), doctest::Approx(r3.e23())); 28 | } 29 | 30 | TEST_CASE("motor-exp-log-sqrt") 31 | { 32 | // Construct a motor from a translator and rotor 33 | rotor r{kln::pi * 0.5f, 0.3f, -3.f, 1.f}; 34 | translator t{12.f, -2.f, 0.4f, 1.f}; 35 | motor m1 = r * t; 36 | line l = log(m1); 37 | motor m2 = exp(l); 38 | 39 | CHECK_EQ(m1.scalar(), doctest::Approx(m2.scalar())); 40 | CHECK_EQ(m1.e12(), doctest::Approx(m2.e12())); 41 | CHECK_EQ(m1.e31(), doctest::Approx(m2.e31())); 42 | CHECK_EQ(m1.e23(), doctest::Approx(m2.e23())); 43 | CHECK_EQ(m1.e01(), doctest::Approx(m2.e01())); 44 | CHECK_EQ(m1.e02(), doctest::Approx(m2.e02())); 45 | CHECK_EQ(m1.e03(), doctest::Approx(m2.e03())); 46 | CHECK_EQ(m1.e0123(), doctest::Approx(m2.e0123())); 47 | 48 | motor m3 = sqrt(m1) * sqrt(m1); 49 | CHECK_EQ(m1.scalar(), doctest::Approx(m3.scalar())); 50 | CHECK_EQ(m1.e12(), doctest::Approx(m3.e12())); 51 | CHECK_EQ(m1.e31(), doctest::Approx(m3.e31())); 52 | CHECK_EQ(m1.e23(), doctest::Approx(m3.e23())); 53 | CHECK_EQ(m1.e01(), doctest::Approx(m3.e01())); 54 | CHECK_EQ(m1.e02(), doctest::Approx(m3.e02())); 55 | CHECK_EQ(m1.e03(), doctest::Approx(m3.e03())); 56 | CHECK_EQ(m1.e0123(), doctest::Approx(m3.e0123())); 57 | } 58 | 59 | TEST_CASE("motor-slerp") 60 | { 61 | // Construct a motor from a translator and rotor 62 | rotor r{kln::pi * 0.5f, 0.3f, -3.f, 1.f}; 63 | translator t{12.f, -2.f, 0.4f, 1.f}; 64 | motor m1 = r * t; 65 | line l = log(m1); 66 | // Divide the motor action into three equal steps 67 | line step = l / 3; 68 | motor m_step = exp(step); 69 | motor m2 = m_step * m_step * m_step; 70 | CHECK_EQ(m1.scalar(), doctest::Approx(m2.scalar())); 71 | CHECK_EQ(m1.e12(), doctest::Approx(m2.e12())); 72 | CHECK_EQ(m1.e31(), doctest::Approx(m2.e31())); 73 | CHECK_EQ(m1.e23(), doctest::Approx(m2.e23())); 74 | CHECK_EQ(m1.e01(), doctest::Approx(m2.e01())); 75 | CHECK_EQ(m1.e02(), doctest::Approx(m2.e02())); 76 | CHECK_EQ(m1.e03(), doctest::Approx(m2.e03())); 77 | CHECK_EQ(m1.e0123(), doctest::Approx(m2.e0123())); 78 | } 79 | 80 | TEST_CASE("motor-blend") 81 | { 82 | rotor r1{kln::pi * 0.5f, 0, 0, 1.f}; 83 | translator t1{1.f, 0.f, 0.f, 1.f}; 84 | motor m1 = r1 * t1; 85 | 86 | rotor r2{kln::pi * 0.5f, 0.3f, -3.f, 1.f}; 87 | translator t2{12.f, -2.f, 0.4f, 1.f}; 88 | motor m2 = r2 * t2; 89 | 90 | motor motion = m2 * ~m1; 91 | line step = log(motion) / 4.f; 92 | motor motor_step = exp(step); 93 | 94 | // Applying motor_step 0 times to m1 is m1. 95 | // Applying motor_step 4 times to m1 is m2 * ~m1; 96 | motor result = motor_step * motor_step * motor_step * motor_step * m1; 97 | CHECK_EQ(result.scalar(), doctest::Approx(m2.scalar())); 98 | CHECK_EQ(result.e12(), doctest::Approx(m2.e12())); 99 | CHECK_EQ(result.e31(), doctest::Approx(m2.e31())); 100 | CHECK_EQ(result.e23(), doctest::Approx(m2.e23())); 101 | CHECK_EQ(result.e01(), doctest::Approx(m2.e01())); 102 | CHECK_EQ(result.e02(), doctest::Approx(m2.e02())); 103 | CHECK_EQ(result.e03(), doctest::Approx(m2.e03())); 104 | CHECK_EQ(result.e0123(), doctest::Approx(m2.e0123())); 105 | } 106 | 107 | TEST_CASE("translator-motor-log") 108 | { 109 | translator t{1.f, 1.f, 2.f, 3.f}; 110 | motor m{t}; 111 | line l = log(m); 112 | CHECK_EQ(l.e01(), m.e01()); 113 | CHECK_EQ(l.e02(), m.e02()); 114 | CHECK_EQ(l.e03(), m.e03()); 115 | } 116 | 117 | TEST_CASE("ideal-motor-step") 118 | { 119 | rotor r1{0, 0, 0, 1.f}; 120 | translator t1{1.f, 0.f, 0.f, 1.f}; 121 | motor m1 = r1 * t1; 122 | 123 | line step = log(m1) / 4.f; 124 | motor motor_step = exp(step); 125 | 126 | // Applying motor_step 4 times should recover the translator t1 127 | // (embedded) in m1 128 | motor result = motor_step * motor_step * motor_step * motor_step; 129 | CHECK_EQ(result.scalar(), doctest::Approx(m1.scalar())); 130 | CHECK_EQ(result.e12(), doctest::Approx(m1.e12())); 131 | CHECK_EQ(result.e31(), doctest::Approx(m1.e31())); 132 | CHECK_EQ(result.e23(), doctest::Approx(m1.e23())); 133 | CHECK_EQ(result.e01(), doctest::Approx(m1.e01())); 134 | CHECK_EQ(result.e02(), doctest::Approx(m1.e02())); 135 | CHECK_EQ(result.e03(), doctest::Approx(m1.e03())); 136 | CHECK_EQ(result.e0123(), doctest::Approx(m1.e0123())); 137 | } 138 | -------------------------------------------------------------------------------- /test/test_glsl.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 3 | #include 4 | 5 | #include "glsl_shim.hpp" 6 | 7 | #include 8 | 9 | TEST_CASE("rr_mul") 10 | { 11 | kln_rotor r1; 12 | r1.p1 = vec4(2, 3, 4, 5); 13 | 14 | kln_rotor r2; 15 | r2.p1 = vec4(6, 7, 8, 9); 16 | 17 | kln_rotor r3 = kln_mul(r1, r2); 18 | CHECK_EQ(r3.p1.x, -86.f); 19 | CHECK_EQ(r3.p1.y, 36.f); 20 | CHECK_EQ(r3.p1.z, 32.f); 21 | CHECK_EQ(r3.p1.w, 52.f); 22 | } 23 | 24 | TEST_CASE("mm_mul") 25 | { 26 | kln_motor m1; 27 | m1.p1 = vec4(2, 3, 4, 5); 28 | m1.p2 = vec4(9, 6, 7, 8); 29 | 30 | kln_motor m2; 31 | m2.p1 = vec4(6, 7, 8, 9); 32 | m2.p2 = vec4(13, 10, 11, 12); 33 | 34 | kln_motor m3 = kln_mul(m1, m2); 35 | CHECK_EQ(m3.p1.x, -86.f); 36 | CHECK_EQ(m3.p1.y, 36.f); 37 | CHECK_EQ(m3.p1.z, 32.f); 38 | CHECK_EQ(m3.p1.w, 52.f); 39 | CHECK_EQ(m3.p2.x, 384.f); 40 | CHECK_EQ(m3.p2.y, -38.f); 41 | CHECK_EQ(m3.p2.z, -76.f); 42 | CHECK_EQ(m3.p2.w, -66.f); 43 | } 44 | 45 | TEST_CASE("rotor_plane") 46 | { 47 | kln_rotor r; 48 | r.p1 = vec4(1, 4, -3, 2); 49 | kln_plane p1; 50 | p1.p0 = vec4(2, 3, 4, 5); 51 | kln_plane p2 = kln_apply(r, p1); 52 | CHECK_EQ(p2.p0.x, 60); 53 | CHECK_EQ(p2.p0.y, 42); 54 | CHECK_EQ(p2.p0.z, -144); 55 | CHECK_EQ(p2.p0.w, -150); 56 | } 57 | 58 | TEST_CASE("motor_plane") 59 | { 60 | kln_motor m; 61 | m.p1 = vec4(1, 4, -3, 2); 62 | m.p2 = vec4(8, 5, 6, 7); 63 | kln_plane p1; 64 | p1.p0 = vec4(2, 3, 4, 5); 65 | kln_plane p2 = kln_apply(m, p1); 66 | CHECK_EQ(p2.p0.x, 416); 67 | CHECK_EQ(p2.p0.y, 42); 68 | CHECK_EQ(p2.p0.z, -144); 69 | CHECK_EQ(p2.p0.w, -150); 70 | } 71 | 72 | TEST_CASE("rotor_point") 73 | { 74 | kln_rotor r; 75 | r.p1 = vec4(1, 4, -3, 2); 76 | kln_point p1; 77 | p1.p3 = vec4(2, 3, 4, 5); 78 | kln_point p2 = kln_apply(r, p1); 79 | CHECK_EQ(p2.p3.x, 60); 80 | CHECK_EQ(p2.p3.y, 42); 81 | CHECK_EQ(p2.p3.z, -144); 82 | CHECK_EQ(p2.p3.w, -150); 83 | } 84 | 85 | TEST_CASE("motor_point") 86 | { 87 | kln_motor m; 88 | m.p1 = vec4(1, 4, -3, 2); 89 | m.p2 = vec4(8, 5, 6, 7); 90 | kln_point p1; 91 | p1.p3 = vec4(2, 3, 4, 5); 92 | kln_point p2 = kln_apply(m, p1); 93 | CHECK_EQ(p2.p3.x, 60); 94 | CHECK_EQ(p2.p3.y, -238); 95 | CHECK_EQ(p2.p3.z, -144); 96 | CHECK_EQ(p2.p3.w, -86); 97 | } 98 | 99 | TEST_CASE("motor_origin") 100 | { 101 | kln_motor m; 102 | m.p1 = vec4(1, 4, -3, 2); 103 | m.p2 = vec4(8, 5, 6, 7); 104 | kln_point p = kln_apply(m); 105 | CHECK_EQ(p.p3.x, 1); 106 | CHECK_EQ(p.p3.y, -140); 107 | CHECK_EQ(p.p3.z, 0); 108 | CHECK_EQ(p.p3.w, 32); 109 | } -------------------------------------------------------------------------------- /test/test_ip.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace kln; 6 | 7 | TEST_CASE("multivector-ip") 8 | { 9 | SUBCASE("plane|plane") 10 | { 11 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 12 | plane p1{1.f, 2.f, 3.f, 4.f}; 13 | plane p2{2.f, 3.f, -1.f, -2.f}; 14 | float p12 = p1 | p2; 15 | CHECK_EQ(p12, 5.f); 16 | } 17 | 18 | SUBCASE("plane|line") 19 | { 20 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 21 | plane p1{1.f, 2.f, 3.f, 4.f}; 22 | 23 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 24 | line l1{0.f, 0.f, 1.f, 4.f, 1.f, -2.f}; 25 | 26 | plane p1l1 = p1 | l1; 27 | CHECK_EQ(p1l1.e0(), -3.f); 28 | CHECK_EQ(p1l1.e1(), 7.f); 29 | CHECK_EQ(p1l1.e2(), -14.f); 30 | CHECK_EQ(p1l1.e3(), 7.f); 31 | } 32 | 33 | SUBCASE("plane|ideal-line") 34 | { 35 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 36 | plane p1{1.f, 2.f, 3.f, 4.f}; 37 | 38 | // a*e01 + b*e02 + c*e03 39 | ideal_line l1{-2.f, 1.f, 4.f}; 40 | 41 | plane p1l1 = p1 | l1; 42 | CHECK_EQ(p1l1.e0(), -12.f); 43 | } 44 | 45 | SUBCASE("plane|point") 46 | { 47 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 48 | plane p1{1.f, 2.f, 3.f, 4.f}; 49 | // x*e_032 + y*e_013 + z*e_021 + e_123 50 | point p2{-2.f, 1.f, 4.f}; 51 | 52 | line p1p2 = p1 | p2; 53 | CHECK_EQ(p1p2.e01(), -5.f); 54 | CHECK_EQ(p1p2.e02(), 10.f); 55 | CHECK_EQ(p1p2.e03(), -5.f); 56 | CHECK_EQ(p1p2.e12(), 3.f); 57 | CHECK_EQ(p1p2.e31(), 2.f); 58 | CHECK_EQ(p1p2.e23(), 1.f); 59 | } 60 | 61 | SUBCASE("line|plane") 62 | { 63 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 64 | plane p1{1.f, 2.f, 3.f, 4.f}; 65 | 66 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 67 | line l1{0.f, 0.f, 1.f, 4.f, 1.f, -2.f}; 68 | 69 | plane p1l1 = l1 | p1; 70 | CHECK_EQ(p1l1.e0(), 3.f); 71 | CHECK_EQ(p1l1.e1(), -7.f); 72 | CHECK_EQ(p1l1.e2(), 14.f); 73 | CHECK_EQ(p1l1.e3(), -7.f); 74 | } 75 | 76 | SUBCASE("line|line") 77 | { 78 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 79 | line l1{1.f, 0.f, 0.f, 3.f, 2.f, 1.f}; 80 | line l2{0.f, 1.f, 0.f, 4.f, 1.f, -2.f}; 81 | 82 | float l1l2 = l1 | l2; 83 | CHECK_EQ(l1l2, -12); 84 | } 85 | 86 | SUBCASE("line|point") 87 | { 88 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 89 | line l1{0.f, 0.f, 1.f, 3.f, 2.f, 1.f}; 90 | // x*e_032 + y*e_013 + z*e_021 + e_123 91 | point p2{-2.f, 1.f, 4.f}; 92 | 93 | plane l1p2 = l1 | p2; 94 | CHECK_EQ(l1p2.e0(), 0.f); 95 | CHECK_EQ(l1p2.e1(), -3.f); 96 | CHECK_EQ(l1p2.e2(), -2.f); 97 | CHECK_EQ(l1p2.e3(), -1.f); 98 | } 99 | 100 | SUBCASE("point|plane") 101 | { 102 | // x*e_032 + y*e_013 + z*e_021 + e_123 103 | point p1{-2.f, 1.f, 4.f}; 104 | // d*e_0 + a*e_1 + b*e_2 + c*e_3 105 | plane p2{1.f, 2.f, 3.f, 4.f}; 106 | 107 | line p1p2 = p1 | p2; 108 | CHECK_EQ(p1p2.e01(), -5.f); 109 | CHECK_EQ(p1p2.e02(), 10.f); 110 | CHECK_EQ(p1p2.e03(), -5.f); 111 | CHECK_EQ(p1p2.e12(), 3.f); 112 | CHECK_EQ(p1p2.e31(), 2.f); 113 | CHECK_EQ(p1p2.e23(), 1.f); 114 | } 115 | 116 | SUBCASE("point|line") 117 | { 118 | // a*e01 + b*e01 + c*e02 + d*e23 + e*e31 + f*e12 119 | line l1{0.f, 0.f, 1.f, 3.f, 2.f, 1.f}; 120 | // x*e_032 + y*e_013 + z*e_021 + e_123 121 | point p2{-2.f, 1.f, 4.f}; 122 | 123 | plane l1p2 = p2 | l1; 124 | CHECK_EQ(l1p2.e0(), 0.f); 125 | CHECK_EQ(l1p2.e1(), -3.f); 126 | CHECK_EQ(l1p2.e2(), -2.f); 127 | CHECK_EQ(l1p2.e3(), -1.f); 128 | } 129 | 130 | SUBCASE("point|point") 131 | { 132 | // x*e_032 + y*e_013 + z*e_021 + e_123 133 | point p1{1.f, 2.f, 3.f}; 134 | point p2{-2.f, 1.f, 4.f}; 135 | 136 | float p1p2 = p1 | p2; 137 | CHECK_EQ(p1p2, -1.f); 138 | } 139 | 140 | SUBCASE("project point to line") 141 | { 142 | point p1{2.f, 2.f, 0.f}; 143 | point p2{0.f, 0.f, 0.f}; 144 | point p3{1.f, 0.f, 0.f}; 145 | line l = p2 & p3; 146 | point p4{(l | p1) ^ l}; 147 | p4.normalize(); 148 | 149 | CHECK_EQ(p4.e123(), doctest::Approx(1.f)); 150 | CHECK_EQ(p4.x(), doctest::Approx(2.f)); 151 | CHECK_EQ(p4.y(), doctest::Approx(0.f)); 152 | CHECK_EQ(p4.z(), doctest::Approx(0.f)); 153 | } 154 | } -------------------------------------------------------------------------------- /test/test_metric.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace kln; 6 | 7 | TEST_CASE("measure-point-to-point") 8 | { 9 | point p1{1.f, 0, 0}; 10 | point p2{0.f, 1.f, 0}; 11 | line l = p1 & p2; 12 | // Produce the squared distance between p1 and p2 13 | CHECK_EQ(l.squared_norm(), 2.f); 14 | } 15 | 16 | TEST_CASE("measure-point-to-plane") 17 | { 18 | // Plane p2 19 | // / 20 | // / \ line perpendicular to 21 | // / \ p2 through p1 22 | // 0------x---------> 23 | // p1 24 | 25 | // (2, 0, 0) 26 | point p1{2.f, 0.f, 0.f}; 27 | // Plane x - y = 0 28 | plane p2{1.f, -1.f, 0.f, 0.f}; 29 | p2.normalize(); 30 | // Distance from point p1 to plane p2 31 | auto root_two = doctest::Approx(std::sqrt(2.f)); 32 | CHECK_EQ(std::abs((p1 & p2).scalar()), root_two); 33 | CHECK_EQ(std::abs((p1 ^ p2).e0123()), root_two); 34 | } 35 | 36 | TEST_CASE("measure-point-to-line") 37 | { 38 | line l{0, 1, 0, 1, 0, 0}; 39 | point p{0, 1, 2}; 40 | float distance = plane{l & p}.norm(); 41 | CHECK_EQ(distance, doctest::Approx(std::sqrt(2.f))); 42 | } 43 | 44 | TEST_CASE("euler-angles") 45 | { 46 | // Make 3 rotors about the x, y, and z-axes. 47 | rotor rx{1.f, 1.f, 0.f, 0.f}; 48 | rotor ry{1.f, 0.f, 1.f, 0.f}; 49 | rotor rz{1.f, 0.f, 0.f, 1.f}; 50 | 51 | rotor r = rx * ry * rz; 52 | auto ea = r.as_euler_angles(); 53 | CHECK_EQ(ea.roll, doctest::Approx(1.f)); 54 | CHECK_EQ(ea.pitch, doctest::Approx(1.f)); 55 | CHECK_EQ(ea.yaw, doctest::Approx(1.f)); 56 | 57 | rotor r2{ea}; 58 | 59 | float buf[8]; 60 | r.store(buf); 61 | r2.store(buf + 4); 62 | for (size_t i = 0; i != 4; ++i) 63 | { 64 | CHECK_EQ(buf[i], doctest::Approx(buf[i + 4])); 65 | } 66 | } 67 | 68 | TEST_CASE("euler-angles-precision") 69 | { 70 | euler_angles ea1{pi * 0.2f, pi * 0.2f, 0.f}; 71 | rotor r1{ea1}; 72 | euler_angles ea2 = r1.as_euler_angles(); 73 | 74 | CHECK_EQ(ea1.roll, doctest::Approx(ea2.roll)); 75 | CHECK_EQ(ea1.pitch, doctest::Approx(ea2.pitch)); 76 | CHECK_EQ(ea1.yaw, doctest::Approx(ea2.yaw)); 77 | } -------------------------------------------------------------------------------- /test/test_rp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace kln; 6 | 7 | TEST_CASE("multivector-rp") 8 | { 9 | SUBCASE("+z line") 10 | { 11 | point p1{0.f, 0.f, 0.f}; 12 | point p2{0.f, 0.f, 1.f}; 13 | line p12 = p1 & p2; 14 | CHECK_EQ(p12.e12(), 1.f); 15 | } 16 | 17 | SUBCASE("+y line") 18 | { 19 | point p1{0.f, -1.f, 0.f}; 20 | point p2{0.f, 0.f, 0.f}; 21 | line p12 = p1 & p2; 22 | CHECK_EQ(p12.e31(), 1.f); 23 | } 24 | 25 | SUBCASE("+x line") 26 | { 27 | point p1{-2.f, 0.f, 0.f}; 28 | point p2{-1.f, 0.f, 0.f}; 29 | line p12 = p1 & p2; 30 | CHECK_EQ(p12.e23(), 1.f); 31 | } 32 | 33 | SUBCASE("plane-construction") 34 | { 35 | point p1{1.f, 3.f, 2.f}; 36 | point p2{-1.f, 5.f, 2.f}; 37 | point p3{2.f, -1.f, -4.f}; 38 | 39 | plane p123{p1 & p2 & p3}; 40 | 41 | // Check that all 3 points lie on the plane 42 | CHECK_EQ(p123.e1() + p123.e2() * 3.f + p123.e3() * 2.f + p123.e0(), 0.f); 43 | CHECK_EQ(-p123.e1() + p123.e2() * 5.f + p123.e3() * 2.f + p123.e0(), 0.f); 44 | CHECK_EQ(p123.e1() * 2.f - p123.e2() - p123.e3() * 4.f + p123.e0(), 0.f); 45 | } 46 | } -------------------------------------------------------------------------------- /test/test_sse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace kln; 6 | 7 | TEST_CASE("rcp_nr1") 8 | { 9 | __m128 a = _mm_set_ps(4.f, 3.f, 2.f, 1.f); 10 | 11 | __m128 b = detail::rcp_nr1(a); 12 | float buf[4]; 13 | _mm_store_ps(buf, b); 14 | 15 | CHECK_EQ(buf[0], doctest::Approx(1.f)); 16 | CHECK_EQ(buf[1], doctest::Approx(0.5f)); 17 | CHECK_EQ(buf[2], doctest::Approx(1.f / 3.f)); 18 | CHECK_EQ(buf[3], doctest::Approx(0.25f)); 19 | } -------------------------------------------------------------------------------- /theme/partials/footer.html: -------------------------------------------------------------------------------- 1 | 2 | {% import "partials/language.html" as lang with context %} 3 | 4 | 5 | --------------------------------------------------------------------------------