├── .gitignore ├── Doxyfile ├── README.md ├── SConstruct ├── doc ├── Notes.txt └── style_guide.md ├── geomc ├── Deque.h ├── GeomException.cpp ├── GeomException.h ├── Hash.h ├── SConscript ├── SmallStorage.h ├── Storage.h ├── Templates.h ├── Tree.h ├── function │ ├── Basis.h │ ├── Dual.h │ ├── FunctionTypes.h │ ├── PerlinNoise.h │ ├── Raster.h │ ├── SphericalHarmonics.h │ ├── Utils.h │ └── functiondetail │ │ ├── DualFunctions.h │ │ ├── DualImpl.h │ │ ├── RasterDetail.h │ │ ├── SipHash.cpp │ │ └── UtilDetail.h ├── geomc_defs.h ├── linalg │ ├── AffineTransform.h │ ├── Cholesky.h │ ├── Isometry.h │ ├── LUDecomp.h │ ├── LinalgTypes.h │ ├── Matrix.h │ ├── Orthogonal.h │ ├── Quaternion.h │ ├── Ray.h │ ├── Rotation.h │ ├── Similarity.h │ ├── Vec.h │ ├── mtxdetail │ │ ├── MatrixArithmetic.h │ │ ├── MatrixBase.h │ │ ├── MatrixCopy.h │ │ ├── MatrixDet.h │ │ ├── MatrixFunctionImpl.h │ │ ├── MatrixGlue.h │ │ ├── MatrixInv.h │ │ ├── MatrixIterator.h │ │ ├── MatrixLayout.h │ │ └── MatrixMult.h │ ├── mtxtypes │ │ ├── DiagMatrix.h │ │ ├── MatrixHandle.h │ │ ├── PermutationMatrix.h │ │ └── SimpleMatrix.h │ └── vecdetail │ │ ├── Vec2.h │ │ ├── Vec3.h │ │ ├── Vec4.h │ │ ├── VecBase.h │ │ └── VecStd.h ├── random │ ├── DenseDistribution.h │ ├── SampleGeometry.h │ └── SampleVector.h └── shape │ ├── BinLatticePartition.h │ ├── Capsule.h │ ├── CubicPath.h │ ├── CubicSpline.h │ ├── Cylinder.h │ ├── Dilated.h │ ├── Extruded.h │ ├── Frustum.h │ ├── GridIterator.h │ ├── Hollow.h │ ├── Intersect.h │ ├── KDTree.h │ ├── Plane.h │ ├── Rect.h │ ├── Shape.h │ ├── ShapeTypes.h │ ├── Similar.h │ ├── Simplex.h │ ├── Sphere.h │ ├── SphericalCap.h │ ├── Transformed.h │ └── shapedetail │ ├── GJKDebug.h │ ├── IndexHelpers.h │ ├── SeparatingAxis.h │ └── SimplexProject.h ├── license.txt ├── regression ├── SConscript ├── cholesky.cpp ├── circular_buffer.cpp ├── intersect.cpp ├── linear_solve.cpp ├── lu_debug.cpp ├── matrix_iterator.cpp ├── matrix_tests.cpp ├── perlin.cpp ├── shape.cpp ├── shape_generation.h ├── simplex.cpp ├── small_storage.cpp └── tree.cpp └── test ├── MtxDebug.cpp ├── PLU.cpp ├── RandomBattery.cpp ├── RandomBattery.h ├── Test.cpp ├── ortho.cpp └── profile.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | doc/gen 2 | 3 | .DS_Store 4 | *.log 5 | bin 6 | test 7 | build 8 | .cache 9 | bkup 10 | .sconsign.dblite 11 | /.vscode 12 | compile_commands.json 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Geomc linear algebra and geometry library 2 | 3 | Tim Babb 4 | tr.babb@gmail.com 5 | 6 | Usage 7 | ===== 8 | 9 | Includes are of the form: 10 | 11 | #include 12 | 13 | Documentation 14 | ============= 15 | 16 | [Geomc topics](http://trbabb.github.io/geomc/html/topics.html) 17 | 18 | Building 19 | ======== 20 | 21 | To build the library, run: 22 | 23 | scons install 24 | 25 | To make the documentation, run: 26 | 27 | scons docs 28 | 29 | These build options are also available: 30 | 31 | - Target webassembly: 32 | `scons --wasm` 33 | - Enable address sanitization for debugging: 34 | `scons --sanitize` 35 | - Disable optimization: 36 | `scons noopt=1` 37 | - Enable debug symbols: 38 | `scons debug=1` 39 | - Rebuild `compile_commands.json` for clang tools: 40 | `scons compile_commands` 41 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import itertools 2 | import platform 3 | 4 | import os 5 | 6 | AddOption( 7 | '--wasm', 8 | action='store_true', 9 | help='Compile to WASM', 10 | default=False) 11 | AddOption( 12 | '--sanitize', 13 | action='store_true', 14 | help='Enable address sanitization', 15 | default=False) 16 | 17 | debug = ARGUMENTS.get('debug', False) 18 | noopt = ARGUMENTS.get('noopt', False) 19 | 20 | DOC_DIR = 'doc/gen' 21 | 22 | def subst_wasm_flags(env, wasm_flags): 23 | wasm_opts = (('-s', f'{k}={v}') for k,v in wasm_flags.items()) 24 | return list(itertools.chain(*wasm_opts)) 25 | 26 | wasm_cflags = { 27 | 'NO_DISABLE_EXCEPTION_CATCHING' : 1, 28 | } 29 | 30 | wasm_linkflags = { 31 | 'ALLOW_MEMORY_GROWTH' : 1, 32 | } 33 | 34 | pkg_config_path = os.environ.get('PKG_CONFIG_PATH') if 'PKG_CONFIG_PATH' in os.environ else [] 35 | 36 | if debug: 37 | wasm_cflags['ASSERTIONS'] = 1 38 | 39 | env = Environment( 40 | CXX='clang++', 41 | CXXFLAGS=[ 42 | '-O3' if not noopt else '-O0', 43 | '-std=c++20', 44 | '-fcolor-diagnostics', 45 | '-Wall', 46 | '-Werror', 47 | '-Wno-return-type-c-linkage', 48 | '-Wno-limited-postlink-optimizations', 49 | '-Wno-unknown-warning-option', 50 | # '-v' 51 | ], 52 | LIBS=[], 53 | CPPPATH=['#'], 54 | COMPILATIONDB_USE_ABSPATH=True, 55 | ) 56 | env.AddMethod(subst_wasm_flags) 57 | env.Tool('compilation_db') 58 | 59 | if env['PLATFORM'] == 'darwin': 60 | # homebrew / pkgconfig not in the path by default 61 | env.AppendENVPath('PATH', ['/opt/homebrew/bin','/usr/local/bin']) 62 | env.Append(CPPPATH=['/opt/homebrew/include', '/usr/local/include']) 63 | env.Append(LIBPATH=['/opt/homebrew/lib', '/usr/local/lib']) 64 | 65 | if debug: 66 | env.Append(CXXFLAGS='-g') 67 | 68 | if GetOption('sanitize'): 69 | env.Append(CXXFLAGS =['-fsanitize=address', '-fno-omit-frame-pointer']) 70 | env.Append(LINKFLAGS=['-fsanitize=address', '-fno-omit-frame-pointer']) 71 | 72 | if GetOption('wasm'): 73 | arch = 'wasm' 74 | env['CXX'] = 'em++' 75 | env['AR'] = 'emar' 76 | env['RANLIB'] = 'emranlib' 77 | # todo: make this more portable. 78 | env.Append(LIBPATH=['/usr/local/wasm/lib']) 79 | env.Append(CPPPATH=['/usr/local/wasm/include']) 80 | env.Append(CXXFLAGS=env.subst_wasm_flags(wasm_cflags)) 81 | env.Append(LINKFLAGS=env.subst_wasm_flags(wasm_linkflags)) 82 | env.Append(LIBS=[]) 83 | env['LIBPATH'] = [f'#build/{arch}/geomc', '/usr/local/wasm/lib'] 84 | if debug: 85 | env.Append(LINKFLAGS=['-gsource-map']) 86 | else: 87 | arch = 'native' 88 | if env['PLATFORM'] == 'darwin': 89 | env.Append(LINKFLAGS=[ 90 | '-Wl,-rpath,@loader_path', 91 | '-Wl,-rpath,/usr/local/lib', 92 | ]) 93 | elif env['PLATFORM'] == 'linux': 94 | env.Append(LINKFLAGS=[ 95 | '-Wl,-rpath,$ORIGIN', 96 | '-Wl,-rpath,/usr/local/lib' 97 | ]) 98 | env.Append(CXXFLAGS='-march=native') 99 | env.Append(LIBPATH=['/usr/local/lib', f'#build/{arch}/geomc']) 100 | 101 | env['ARCH'] = arch 102 | 103 | docs = env.Command('docs', None, [Mkdir(DOC_DIR), 'doxygen']) 104 | env.Alias('docs', docs) 105 | 106 | Export("env") 107 | 108 | # data = SConscript('objects/SConscript', variant_dir='build/objects') 109 | lib = SConscript('geomc/SConscript', variant_dir=f'build/{arch}/geomc') 110 | test = SConscript('regression/SConscript', variant_dir=f'build/{arch}/regression', 111 | exports={'lib': lib} 112 | ) 113 | comp_db = env.CompilationDatabase(target='compile_commands.json') 114 | 115 | env.Alias('lib', lib) 116 | env.Alias('compile_commands', comp_db) 117 | 118 | Default([lib, test, comp_db]) 119 | -------------------------------------------------------------------------------- /geomc/GeomException.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * GeometryException.cpp 3 | * 4 | * Created on: Feb 22, 2009 5 | * Author: Tim Babb 6 | */ 7 | 8 | #include 9 | 10 | 11 | namespace geom { 12 | 13 | 14 | /////////////// GeomException /////////////// 15 | 16 | GeomException::GeomException(const char* msg) throw () : msg(msg) { /* do nothing */ } 17 | 18 | const char* GeomException::what() { return msg; } 19 | 20 | /////////////// DimensionMismatch /////////////// 21 | 22 | DimensionMismatchException::DimensionMismatchException(index_t a_0, index_t a_1, index_t b_0, index_t b_1) throw () : 23 | GeomException("dimension mismatch"), 24 | a_0(a_0), 25 | a_1(a_1), 26 | b_0(b_0), 27 | b_1(b_1) { /* do nothing */ } 28 | 29 | /////////////// NonsquareMatrix /////////////// 30 | 31 | NonsquareMatrixException::NonsquareMatrixException(index_t rows, index_t cols) throw () : 32 | GeomException("nonsquare matrix"), 33 | rows(rows), cols(cols) { /* do nothing */ } 34 | 35 | 36 | } // namespace geom 37 | -------------------------------------------------------------------------------- /geomc/GeomException.h: -------------------------------------------------------------------------------- 1 | /* 2 | * GeometryException.h 3 | * 4 | * Created on: Feb 22, 2009 5 | * Author: Tim Babb 6 | */ 7 | 8 | #ifndef GEOMETRYEXCEPTION_H_ 9 | #define GEOMETRYEXCEPTION_H_ 10 | 11 | #include 12 | 13 | namespace geom { 14 | 15 | /////////////////////// 16 | 17 | class GeomException { 18 | public: 19 | GeomException(const char* msg) throw (); 20 | 21 | const char* what(); 22 | 23 | const char* msg; 24 | }; 25 | 26 | /////////////////////// 27 | 28 | //todo: this should be factored to linalg, and use MatrixDim 29 | //todo: rename MatrixDim to Size2d? make a Size<#>::type? 30 | 31 | class DimensionMismatchException : public GeomException { 32 | public: 33 | index_t a_0, a_1, b_0, b_1; 34 | 35 | DimensionMismatchException(index_t a_0, index_t a_1, index_t b_0, index_t b_1) throw (); 36 | }; 37 | 38 | /////////////////////// 39 | 40 | class NonsquareMatrixException : public GeomException { 41 | public: 42 | index_t rows, cols; 43 | 44 | NonsquareMatrixException(index_t rows, index_t cols) throw (); 45 | }; 46 | 47 | } // namespace geom 48 | 49 | #endif /* GEOMETRYEXCEPTION_H_ */ 50 | -------------------------------------------------------------------------------- /geomc/SConscript: -------------------------------------------------------------------------------- 1 | import os 2 | import itertools 3 | 4 | Import("env") 5 | 6 | lib_target = 'geomc' 7 | # scons doesn't have recursive globs. this is dumb but it's clearer than a recursive function. 8 | lib_sources = [Glob('*.cpp'), Glob('*/*.cpp'), Glob('*/*/*.cpp'), Glob('*/*/*/*.cpp')] 9 | lib_headers = [ 10 | Glob('*.h'), Glob('*/*.h'), Glob('*/*/*.h'), Glob('*/*/*/*.h'), 11 | Glob('*.hpp'), Glob('*/*.hpp'), Glob('*/*/*.hpp'), Glob('*/*/*/*.hpp') 12 | ] 13 | lib_sources = list(itertools.chain.from_iterable(lib_sources)) 14 | 15 | if env['ARCH'] == 'native': 16 | install_root = '/usr/local' 17 | if env['PLATFORM'] == 'darwin': 18 | extra_flags = ' -install_name ' + f'@rpath/lib{lib_target}.dylib' 19 | elif env['PLATFORM'] == 'linux': 20 | extra_flags = f' -Wl,-soname,lib{lib_target}.so' 21 | else: 22 | extra_flags = '' 23 | # lib = env.SharedLibrary( 24 | # target=lib_target, 25 | # source=lib_sources, 26 | # LINKFLAGS='$LINKFLAGS' + extra_flags 27 | # ) 28 | lib = env.StaticLibrary(target=lib_target, source=lib_sources) 29 | else: 30 | install_root = '/usr/local/wasm' 31 | lib = env.StaticLibrary(target=lib_target, source=lib_sources) 32 | 33 | env.Alias('install', install_root) 34 | 35 | # recursively install headers 36 | for header in itertools.chain(*lib_headers): 37 | # e.g., 'linalg/Vec.h': 38 | h_path = str(header.dir) 39 | # e.g., '/usr/local/include/geomc/linalg': 40 | install_path = os.path.join(install_root, 'include/geomc', h_path) 41 | # e.g., '/usr/local/include/geomc/linalg/Vec.h': 42 | env.Install(install_path, header) 43 | 44 | # install library 45 | env.Install(os.path.join(install_root, 'lib'), lib) 46 | 47 | # I am annoyed that you return by name, not by value 48 | Return('lib') 49 | -------------------------------------------------------------------------------- /geomc/Templates.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace geom { 8 | 9 | 10 | template 11 | struct ConstType { 12 | // inaccessible 13 | }; 14 | 15 | template 16 | struct ConstType { 17 | typedef T type; 18 | typedef T* pointer_t; 19 | typedef T& reference_t; 20 | }; 21 | 22 | template 23 | struct ConstType { 24 | typedef const T type; 25 | typedef const T* pointer_t; 26 | typedef const T& reference_t; 27 | }; 28 | 29 | 30 | } // end namespace geom 31 | 32 | 33 | #if __cplusplus < 201103L 34 | 35 | namespace std { 36 | 37 | template 38 | struct conditional {}; 39 | 40 | template 41 | struct conditional { 42 | typedef T type; 43 | }; 44 | 45 | template 46 | struct conditional { 47 | typedef F type; 48 | }; 49 | 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /geomc/function/Basis.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * File: Basis.h 4 | * Author: tbabb 5 | * 6 | * Created on December 24, 2014, 3:51 PM 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | namespace geom { 16 | 17 | /** 18 | * @addtogroup function 19 | * @{ 20 | */ 21 | 22 | // helper function evaluates Legendre polynomials in batch. 23 | // makes use of recurrent definition of legendre polys to save re-execs. 24 | // evaluate a column m and -m of Legendre polynomials and stuff them 25 | // into the appropriate place in the SphericalHarmonics' coeff table 26 | template 27 | inline void legendre(SphericalHarmonics *sh, index_t m, T x) { 28 | index_t l = sh->bands(); 29 | T pmm0 = 1; 30 | if (m > 0) { 31 | // compute double factorial of 2m - 1 32 | // fold in the (-1)^m 33 | for (index_t i = 1; i <= m; i++) { 34 | pmm0 *= -(i * 2 - 1); 35 | } 36 | pmm0 *= std::pow(1 - x * x, m / 2.0); 37 | } 38 | sh->coeff(m,m) = sh->coeff(m,-m) = pmm0; 39 | if (m == l - 1) return; // there is no higher band 40 | T pmm1 = sh->coeff(m+1,m) = sh->coeff(m+1,-m) = x * (2 * m + 1) * pmm0; 41 | if (m == l - 2) return; // there is no higher band 42 | for (index_t l_i = m + 2; l_i < l; l_i++) { 43 | T pmi = (x * (2 * l_i - 1) * pmm1 - (l_i + m - 1) * pmm0) / (l_i - m); 44 | pmm0 = pmm1; 45 | pmm1 = sh->coeff(l_i,m) = sh->coeff(l_i,-m) = pmi; 46 | } 47 | } 48 | 49 | /** 50 | * Evaluate an associated Legendre polynomial. 51 | * 52 | * @code#include @endcode 53 | * @param l Band index. 54 | * @param m Sub-band. 55 | * @param x Value of `x` at which to evaluate the polynomial. 56 | */ 57 | template 58 | inline T legendre(index_t l, index_t m, T x) { 59 | T pmm0 = 1; 60 | if (m > 0) { 61 | // compute double factorial of 2m - 1 62 | // fold in the (-1)^m 63 | for (index_t i = 1; i <= m; i++) { 64 | pmm0 *= -(i * 2 - 1); 65 | } 66 | pmm0 *= std::pow(1 - x * x, m / 2.0); 67 | } 68 | if (l == m) return pmm0; 69 | T pmm1 = x * (2 * m + 1) * pmm0; 70 | if (l == m + 1) return pmm1; 71 | for (index_t i = m + 2; i <= l; i++) { 72 | T pmi = (x * (2 * i - 1) * pmm1 - (i + m - 1) * pmm0) / (i - m); 73 | pmm0 = pmm1; 74 | pmm1 = pmi; 75 | } 76 | return pmm1; 77 | } 78 | 79 | /** 80 | * Evaluate a Legendre polynomal of order `n` at `x`. Equivalent to the associated 81 | * legendre polynomial with m = 0. 82 | * 83 | * @code#include @endcode 84 | * @param n Order of polynomial. 85 | * @param x Value of `x` at which to evaluate the polynomial. 86 | */ 87 | template 88 | inline T legendre(index_t n, T x) { 89 | if (n <= 0) return 1; 90 | T p_0 = 1; // P(i - 1, x) 91 | T p_1 = x; // P(i, x) 92 | T p_i = x; // P(i + 1, x) 93 | for (index_t i = 1; i < n; i++) { 94 | p_i = ((2 * i + 1) * x * p_1 - i * p_0) / ((T)i + 1); 95 | p_0 = p_1; 96 | p_1 = p_i; 97 | } 98 | return p_i; 99 | } 100 | 101 | /** 102 | * Evaluate the integral of the Legendre polynomal of order `n` 103 | * between -1 and x. 104 | * 105 | * @code#include @endcode 106 | * @param n Order of polynomial. 107 | * @param x Value of `x` at which to evaluate the integral. 108 | */ 109 | template 110 | inline T legendre_integral(index_t n, T x) { 111 | if (n <= 0) return x + 1; 112 | T p_0 = 1; // P(i - 1, x) 113 | T p_1 = x; // P(i, x) 114 | T p_i = x; // P(i + 1, x) 115 | for (index_t i = 1; i < n; i++) { 116 | p_i = ((2 * i + 1) * x * p_1 - i * p_0) / ((T)i + 1); 117 | p_0 = p_1; 118 | p_1 = p_i; 119 | } 120 | p_i = ((2 * n + 1) * x * p_1 - n * p_0) / ((T)n + 1); 121 | return (p_i - p_0) / (2 * n + 1); 122 | } 123 | 124 | /** 125 | * Evaluate a Chebyshev polynomial of order `n` at `x`. 126 | * 127 | * @code#include @endcode 128 | * @param kind 1 or 2 for the Chebyshev polynomial of the first or second kind, respectively. 129 | * @param n Order of the polynomial. 130 | * @param x Value of `x` at which to evaluate. 131 | */ 132 | template 133 | inline T chebyshev(index_t kind, index_t n, T x) { 134 | if (n <= 0) return 1; 135 | T t_0 = 1; 136 | T t_1 = kind * x; // first kind := x; second kind := 2x 137 | T t_i = t_1; 138 | for (index_t i = 1; i < n; i++) { 139 | t_i = 2 * x * t_1 - t_0; 140 | t_0 = t_1; 141 | t_1 = t_i; 142 | } 143 | return t_i; 144 | } 145 | 146 | 147 | /// @} // group function 148 | 149 | } // end namespace geom 150 | -------------------------------------------------------------------------------- /geomc/function/Dual.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /geomc/function/FunctionTypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * FunctionTypes.h 4 | * 5 | * Created on: Mar 16, 2013 6 | * Author: tbabb 7 | */ 8 | 9 | #include 10 | 11 | namespace geom { 12 | 13 | /** 14 | * @defgroup function Function 15 | * @brief Tools for constructing and sampling continuous-valued objects. 16 | */ 17 | 18 | // classes 19 | template class Raster; 20 | template class PerlinNoise; 21 | template class Path; 22 | template class SphericalHarmonics; 23 | template class ZonalHarmonics; 24 | 25 | /** 26 | * @addtogroup function 27 | * @{ 28 | */ 29 | 30 | /// Raster edge-sampling behavior. 31 | enum EdgeBehavior { 32 | /// Clip sample coordinates to the sampleable area, thus repeating edge values beyond the boundary 33 | EDGE_CLAMP, 34 | /// Wrap around sample coordinates to the opposite edge, thus tiling the sampled data beyond the boundary 35 | EDGE_PERIODIC, 36 | /// Mirror the sample coordinates across edges 37 | EDGE_MIRROR, 38 | /// Regions outside the sampleable area have a uniform, defined value (zero by default). 39 | EDGE_CONSTANT 40 | }; 41 | 42 | /// Behavior for sampling between data points. 43 | enum Interpolation { 44 | /// Return the data nearest to the sample point. 45 | INTERP_NEAREST, 46 | /// Linearly interpolate the nearest 2n data points. 47 | INTERP_LINEAR, 48 | /// Cubically interpolate the nearest 4n data points. 49 | INTERP_CUBIC 50 | }; 51 | 52 | 53 | /// @} // addtogroup function 54 | 55 | } // namespace geom 56 | -------------------------------------------------------------------------------- /geomc/function/PerlinNoise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * PerlinNoise.h 4 | * 5 | * Created on: Apr 9, 2011 6 | * Author: tbabb 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace geom { 17 | 18 | /** 19 | * @ingroup function 20 | * @brief Real-valued smooth noise over `N` dimensions. 21 | * 22 | * Perlin noise has _O(2N)_ time cost. 23 | */ 24 | template 25 | class PerlinNoise : public Dimensional { 26 | static constexpr size_t N_GRADIENTS = 0x100; 27 | 28 | public: 29 | 30 | using gridtype = PointType; 31 | using pointtype = PointType; 32 | using grid_t = typename gridtype::point_t; 33 | using typename Dimensional::point_t; 34 | 35 | private: 36 | std::shared_ptr _gradients; 37 | 38 | public: 39 | 40 | /** 41 | * Construct a new perlin noise object with a `std::random_device` as a source of 42 | * random bits. 43 | */ 44 | PerlinNoise():PerlinNoise(std::random_device()) {} 45 | 46 | /** 47 | * Construct a new perlin noise object with the supplied random number generator. 48 | * @param rng A source of random bits. 49 | */ 50 | template 51 | PerlinNoise(Generator& rng): _gradients(new point_t[N_GRADIENTS]) { 52 | if constexpr (N == 1) { 53 | // gradients should be in the range -1,1 54 | DenseUniformDistribution dist(-1, 1); 55 | for (index_t i = 0; i < N_GRADIENTS; i++) { 56 | _gradients[i] = dist(rng); 57 | } 58 | } else { 59 | // gradients are random unit vectors 60 | for (index_t i = 0; i < N_GRADIENTS; i++) { 61 | _gradients[i] = random_unit(rng); 62 | } 63 | } 64 | } 65 | 66 | T operator()(point_t pt) const { 67 | return eval(pt); 68 | } 69 | 70 | /** 71 | * Evaluate the noise at `pt`. 72 | */ 73 | T eval(point_t pt) const { 74 | const index_t corners = 1 << N; 75 | T f_x[corners]; 76 | 77 | // we use floor() because a bare cast will round toward zero: 78 | point_t p0f = std::floor(pt); 79 | grid_t p0 = (grid_t)p0f; // grid pt 80 | point_t p_mod = pt - p0f; // pos. within grid 81 | 82 | // initialize our interpolants with (hyper-)planar values emanating from the corners. 83 | for (index_t c = 0; c < corners; c++) { 84 | grid_t cnr = corner(c); 85 | const point_t& grid_grad = get_grid_gradient(p0 + cnr); 86 | f_x[c] = pointtype::dot((p_mod - (point_t)cnr), grid_grad); 87 | } 88 | 89 | // we start by interpolating pairs of values along an arbitrary axis (axis 0). 90 | // those interpolated values will in turn be interpolated along the next axis, 91 | // as in bi-/trilinear interpolation. each interpolation reduces the number 92 | // of values by half, until we have one value. 93 | 94 | for (index_t axis = 0; axis < N; axis++) { 95 | T x = pointtype::iterator(p_mod)[axis]; 96 | T t = fade(x); 97 | for (index_t pair = 0; pair < (1 << (N - axis - 1)); pair++) { 98 | f_x[pair] = mix(t, f_x[pair * 2], f_x[pair * 2 + 1]); 99 | } 100 | } 101 | 102 | return f_x[0]; 103 | } 104 | 105 | /** 106 | * Evaluate the gradient of the noise function at `pt`. 107 | * 108 | * @param pt Location at which to sample the noise function. 109 | * @return A pair of (`noise(x)`, `gradient(noise(x))`). 110 | */ 111 | std::pair gradient(point_t pt) const { 112 | const index_t corners = 1 << N; 113 | T f[corners]; // values 114 | point_t df_dx[corners]; // gradients 115 | 116 | point_t p0f = std::floor(pt); 117 | grid_t p0 = (grid_t)p0f; 118 | point_t p_mod = pt - p0f; 119 | 120 | // useful facts: 121 | // mix(t, a, b) = (1 - t) * a + t * b 122 | // d/dt mix(t, a, b) = b - a 123 | // d/da mix(t, a, b) = 1 - t 124 | // d/db mix(t, a, b) = t 125 | 126 | // the multivariate chain rule: 127 | // df/dt f(a(t), b(t), c(t)) = 128 | // df_da(a(t), ...) * da_dt(t) + 129 | // df_db(..., b(t), ...) * db_dt(t) + ... 130 | 131 | // start with planar functions from each corner; 132 | // remember the derivatives of these functions: 133 | for (index_t c = 0; c < corners; ++c) { 134 | grid_t cnr = corner(c); 135 | df_dx[c] = get_grid_gradient(p0 + cnr); 136 | f[c] = pointtype::dot((p_mod - (point_t)cnr), df_dx[c]); 137 | } 138 | 139 | // now start interpolating these functions, updating the derivative along the way 140 | for (index_t axis = 0; axis < N; axis++) { 141 | T x = pointtype::iterator(p_mod)[axis]; 142 | T t = fade(x); 143 | T dt_dx = dfade_dt(x); // (1) note that this is zero along other axes, 144 | // as this is only a fuction of x[axis]. 145 | 146 | // as in `eval()`, we begin by interpolating pairs of points along the x axis 147 | // and reducing the number of values by half with successive interpolations. 148 | for (index_t pair = 0; pair < (1 << (N - axis - 1)); pair++) { 149 | index_t k = pair * 2; 150 | T a = f[k]; 151 | T b = f[k + 1]; 152 | point_t da_dx = df_dx[k]; 153 | point_t db_dx = df_dx[k + 1]; 154 | 155 | // the expression to be differentiated: 156 | f[pair] = mix(t, a, b); 157 | 158 | // apply the multivariate chain rule: 159 | point_t w; 160 | // dt/dx is only nonzero along the current axis: 161 | T* w_i = pointtype::iterator(w) + axis; 162 | // dmix_dt(t, ...) * dt/dx 163 | *w_i += (b - a) * dt_dx; 164 | // dmix_da(..., a, ...) * da/dx 165 | w += (1 - t) * da_dx; 166 | // dmix_db(..., ..., b) * db/dx 167 | w += t * db_dx; 168 | df_dx[pair] = w; 169 | } 170 | } 171 | 172 | return std::pair(f[0], df_dx[0]); 173 | } 174 | 175 | protected: 176 | 177 | const point_t& get_grid_gradient(const grid_t& pt) const { 178 | uint64_t idx = 0; 179 | auto m = gridtype::iterator(pt); 180 | for (index_t i = 0; i < N; ++i) { 181 | // do an iterated linear congruential scramble, using Knuth's constants: 182 | uint64_t k = static_cast(m[i]); 183 | idx = 6364136223846793005ULL * (k + idx) + 1442695040888963407ULL; 184 | } 185 | idx = static_cast(positive_mod(idx, N_GRADIENTS)); 186 | return _gradients[idx]; 187 | } 188 | 189 | static inline grid_t corner(index_t i) { 190 | grid_t cnr; 191 | for (index_t axis = 0; axis < N; axis++) { 192 | gridtype::iterator(cnr)[axis] = (i & (1 << axis)) != 0; 193 | } 194 | return cnr; 195 | } 196 | 197 | static inline T fade(T t) { 198 | return t * t * t * (t * (t * 6 - 15) + 10); 199 | } 200 | 201 | static inline T dfade_dt(T t) { 202 | T w = t - 1; 203 | return 30 * w * w * t * t; 204 | } 205 | }; 206 | 207 | } 208 | -------------------------------------------------------------------------------- /geomc/function/functiondetail/RasterDetail.h: -------------------------------------------------------------------------------- 1 | /* 2 | * RasterDetail.h 3 | * 4 | * Created on: Mar 16, 2013 5 | * Author: tbabb 6 | */ 7 | 8 | #ifndef RASTERDETAIL_H_ 9 | #define RASTERDETAIL_H_ 10 | 11 | #include 12 | 13 | namespace geom { 14 | namespace detail { 15 | 16 | /************************* 17 | * Edge behaviors * 18 | *************************/ 19 | 20 | template 21 | class _ImplEdge { 22 | // no contents. 23 | }; 24 | 25 | template <> 26 | class _ImplEdge { 27 | public: 28 | static inline index_t coord(index_t c, index_t max) { 29 | return std::max(std::min(c,max-1),(index_t)0); 30 | } 31 | }; 32 | 33 | template <> 34 | class _ImplEdge { 35 | public: 36 | static inline index_t coord(index_t c, index_t max) { 37 | return positive_mod(c,max); 38 | } 39 | }; 40 | 41 | template <> 42 | class _ImplEdge { 43 | public: 44 | static inline index_t coord(index_t c, index_t max) { 45 | c = std::abs(c); 46 | max -= 1; 47 | int cyc = c/max; 48 | int i = c%max; 49 | return ((cyc & 1) == 0)?(i):(max-i); 50 | } 51 | }; 52 | 53 | template <> 54 | class _ImplEdge { 55 | public: 56 | static inline index_t coord(index_t c, index_t max) { 57 | return c; 58 | } 59 | }; 60 | 61 | // the following is completely unreadable 62 | // because c++ is utterly moronic. 63 | // this should all be in the main class, but 64 | // member functions cannot be specialized in-place 65 | // because c++ is, again, too damn stupid to 66 | // perform the simple substitution transformtaion 67 | // applied here. therefore we make a fuckton of 68 | // opaque helper classes, one for pretty much every 69 | // single member function. 70 | 71 | /************************* 72 | * Sampling behaviors * 73 | *************************/ 74 | 75 | 76 | template 77 | class _ImplRasterSample { 78 | // pass 79 | }; 80 | 81 | template 82 | class _ImplRasterSample { 83 | public: 84 | typedef typename Raster::coord_t coord_t; 85 | typedef typename Raster::grid_t grid_t; 86 | typedef typename Raster::sample_t sample_t; 87 | 88 | static inline sample_t sample(const Raster *r, const coord_t &pt) { 89 | grid_t gridPt = grid_t(pt + coord_t(0.5)); 90 | return r->template sample_discrete(gridPt); 91 | } 92 | }; 93 | 94 | template 95 | class _ImplRasterSample { 96 | public: 97 | typedef typename Raster::coord_t coord_t; 98 | typedef typename Raster::grid_t grid_t; 99 | typedef typename Raster::sample_t sample_t; 100 | 101 | static inline sample_t sample(const Raster *r, const coord_t &pt) { 102 | sample_t buf[1<template copy(buf, Rect(gridPt, gridPt + grid_t(1))); 108 | 109 | return interp_linear(PointType::iterator(s), buf, N); 110 | } 111 | }; 112 | 113 | template 114 | class _ImplRasterSample { 115 | public: 116 | typedef typename Raster::coord_t coord_t; 117 | typedef typename Raster::grid_t grid_t; 118 | typedef typename Raster::sample_t sample_t; 119 | 120 | static inline sample_t sample(const Raster *r, const coord_t &pt) { 121 | sample_t buf[1<<(2*N)]; 122 | grid_t gridPt = (grid_t)pt; 123 | coord_t s = pt - ((coord_t)gridPt); 124 | 125 | // copy surrounding 4^N sample pts into a contiguous buffer 126 | r->template copy(buf, Rect(gridPt - grid_t(1), gridPt + grid_t(2))); 127 | 128 | return interp_cubic(PointType::iterator(s), buf, N); 129 | } 130 | }; 131 | 132 | /************************* 133 | * Indexing strategy * 134 | *************************/ 135 | 136 | template 137 | class _ImplRasterIndex { 138 | public: 139 | 140 | static inline index_t index(const grid_t &extent, const grid_t &c) { 141 | index_t dim = 1; 142 | index_t idx = 0; 143 | for (index_t i = 0; i < N; i++){ 144 | index_t x = detail::_ImplEdge::coord(c[i], extent[i]); 145 | idx += x*dim; 146 | dim *= extent[i]; 147 | } 148 | return idx; 149 | } 150 | }; 151 | 152 | template 153 | class _ImplRasterIndex { 154 | public: 155 | 156 | static inline index_t index(const grid_t &extent, const grid_t &c) { 157 | return detail::_ImplEdge::coord(c, extent); 158 | } 159 | }; 160 | 161 | /************************* 162 | * Volume calc * 163 | *************************/ 164 | 165 | template 166 | index_t array_product(const index_t *start) { 167 | index_t prod = 1; 168 | const index_t *end = start+N; 169 | for (; start < end; start++){ 170 | prod *= *start; 171 | } 172 | return prod; 173 | } 174 | 175 | template <> 176 | inline index_t array_product<1>(const index_t *start) { 177 | return *start; 178 | } 179 | 180 | 181 | }; // namespace detail 182 | }; // namespace geom 183 | 184 | #endif /* RASTERDETAIL_H_ */ 185 | -------------------------------------------------------------------------------- /geomc/function/functiondetail/SipHash.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace geom { 4 | 5 | static inline constexpr void u32to8(uint8_t p[4], uint32_t v) { 6 | p[0] = (uint8_t)(v); 7 | p[1] = (uint8_t)(v >> 8); 8 | p[2] = (uint8_t)(v >> 16); 9 | p[3] = (uint8_t)(v >> 24); 10 | } 11 | 12 | static inline constexpr void u64to8(uint8_t p[8], uint64_t v) { 13 | u32to8(p, v); 14 | u32to8(p + 4, v >> 32); 15 | } 16 | 17 | static inline constexpr uint64_t u8to64(const uint8_t p[8]) { 18 | uint64_t v = p[0]; 19 | v |= ((uint64_t) p[1] << 8) | ((uint64_t) p[2] << 16); 20 | v |= ((uint64_t) p[3] << 24) | ((uint64_t) p[4] << 32); 21 | v |= ((uint64_t) p[5] << 40) | ((uint64_t) p[6] << 48); 22 | v |= ((uint64_t) p[7] << 56); 23 | return v; 24 | } 25 | 26 | static inline void sipround(uint64_t& v0, uint64_t& v1, uint64_t& v2, uint64_t& v3) { 27 | v0 += v1; 28 | v1 = std::rotl(v1, 13); 29 | v1 ^= v0; 30 | v0 = std::rotl(v0, 32); 31 | v2 += v3; 32 | v3 = std::rotl(v3, 16); 33 | v3 ^= v2; 34 | v0 += v3; 35 | v3 = std::rotl(v3, 21); 36 | v3 ^= v0; 37 | v2 += v1; 38 | v1 = std::rotl(v1, 17); 39 | v1 ^= v2; 40 | v2 = std::rotl(v2, 32); 41 | } 42 | 43 | 44 | namespace detail { 45 | 46 | /* 47 | Computes a SipHash value 48 | *in: pointer to input data (read-only) 49 | inlen: input data length in bytes (any size_t value) 50 | *k: pointer to the key data (read-only), must be 16 bytes 51 | *out: pointer to output data (write-only), outlen bytes must be allocated 52 | outlen: length of the output in bytes, must be 8 or 16 53 | */ 54 | void siphash( 55 | const void* in, 56 | const size_t inlen, 57 | const void* k, 58 | uint8_t* out, 59 | const size_t outlen) 60 | { 61 | /* default: SipHash-2-4 */ 62 | constexpr size_t c_rounds = 2; 63 | constexpr size_t d_rounds = 4; 64 | 65 | const unsigned char *ni = (const unsigned char *)in; 66 | const unsigned char *kk = (const unsigned char *)k; 67 | 68 | uint64_t v0 = 0x736f6d6570736575; 69 | uint64_t v1 = 0x646f72616e646f6d; 70 | uint64_t v2 = 0x6c7967656e657261; 71 | uint64_t v3 = 0x7465646279746573; 72 | uint64_t k0 = u8to64(kk); 73 | uint64_t k1 = u8to64(kk + 8); 74 | uint64_t m; 75 | int i; 76 | const unsigned char *end = ni + inlen - (inlen % sizeof(uint64_t)); 77 | const int left = inlen & 7; 78 | uint64_t b = ((uint64_t)inlen) << 56; 79 | v3 ^= k1; 80 | v2 ^= k0; 81 | v1 ^= k1; 82 | v0 ^= k0; 83 | 84 | if (outlen == 16) 85 | v1 ^= 0xee; 86 | 87 | for (; ni != end; ni += 8) { 88 | m = u8to64(ni); 89 | v3 ^= m; 90 | for (i = 0; i < c_rounds; ++i) { 91 | sipround(v0, v1, v2, v3); 92 | } 93 | v0 ^= m; 94 | } 95 | 96 | switch (left) { 97 | case 7: b |= ((uint64_t)ni[6]) << 48; [[fallthrough]]; 98 | case 6: b |= ((uint64_t)ni[5]) << 40; [[fallthrough]]; 99 | case 5: b |= ((uint64_t)ni[4]) << 32; [[fallthrough]]; 100 | case 4: b |= ((uint64_t)ni[3]) << 24; [[fallthrough]]; 101 | case 3: b |= ((uint64_t)ni[2]) << 16; [[fallthrough]]; 102 | case 2: b |= ((uint64_t)ni[1]) << 8; [[fallthrough]]; 103 | case 1: b |= ((uint64_t)ni[0]); break; 104 | case 0: 105 | break; 106 | } 107 | 108 | v3 ^= b; 109 | for (i = 0; i < c_rounds; ++i) sipround(v0, v1, v2, v3); 110 | v0 ^= b; 111 | if (outlen == 16) { 112 | v2 ^= 0xee; 113 | } else { 114 | v2 ^= 0xff; 115 | } 116 | for (i = 0; i < d_rounds; ++i) sipround(v0, v1, v2, v3); 117 | b = v0 ^ v1 ^ v2 ^ v3; 118 | u64to8(out, b); 119 | if (outlen == 8) return; 120 | v1 ^= 0xdd; 121 | for (i = 0; i < d_rounds; ++i) sipround(v0, v1, v2, v3); 122 | 123 | b = v0 ^ v1 ^ v2 ^ v3; 124 | u64to8(out + 8, b); 125 | 126 | return; 127 | } 128 | 129 | } // namespace detail 130 | } // namespace geom 131 | -------------------------------------------------------------------------------- /geomc/function/functiondetail/UtilDetail.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | 10 | /////////////// 11 | // fwd decls // 12 | /////////////// 13 | 14 | namespace std { 15 | 16 | template 17 | inline geom::Dual abs(const geom::Dual& x); 18 | 19 | template 20 | inline geom::Dual sqrt(const geom::Dual& x); 21 | 22 | template 23 | inline geom::Dual min(const geom::Dual& x, const geom::Dual& y); 24 | 25 | template 26 | inline geom::Dual max(const geom::Dual& x, const geom::Dual& y); 27 | 28 | template 29 | inline geom::Dual ceil(const geom::Dual& x); 30 | 31 | template 32 | inline geom::Dual floor(const geom::Dual& x); 33 | 34 | } // namespace std 35 | 36 | 37 | namespace geom { 38 | 39 | template 40 | inline T multiply_add(T a, T b, T c); 41 | 42 | template 43 | inline T diff_of_products(T a, T b, T c, T d); 44 | 45 | template 46 | inline T sum_of_products(T a, T b, T c, T d); 47 | 48 | 49 | ///////////////////////// 50 | // FMA implementations // 51 | //////////////////////// 52 | 53 | namespace detail { 54 | 55 | template 56 | struct _ImplFMADot { 57 | static constexpr T diff_of_products(T a, T b, T c, T d) { 58 | return a * b - c * d; 59 | } 60 | 61 | static constexpr T sum_of_products(T a, T b, T c, T d) { 62 | return a * b + c * d; 63 | } 64 | 65 | static constexpr T multiply_add(T a, T b, T c) { 66 | return a * b + c; 67 | } 68 | }; 69 | 70 | template 71 | struct _ImplFMADot>> { 72 | static inline T diff_of_products(T a, T b, T c, T d) { 73 | T cd = c * d; 74 | T err = std::fma(-c, d, cd); 75 | T x = std::fma( a, b, -cd); 76 | return x + err; 77 | } 78 | 79 | static inline T sum_of_products(T a, T b, T c, T d) { 80 | T cd = c * d; 81 | T err = std::fma(c, d, -cd); 82 | T x = std::fma(a, b, cd); 83 | return x + err; 84 | } 85 | 86 | static inline T multiply_add(T a, T b, T c) { 87 | return std::fma(a, b, c); 88 | } 89 | }; 90 | 91 | template 92 | struct _ImplFMADot, void> { 93 | static inline Dual diff_of_products( 94 | Dual a, 95 | Dual b, 96 | Dual c, 97 | Dual d) 98 | { 99 | T dp = geom::sum_of_products(a.x, b.dx, a.dx, b.x); 100 | T dq = geom::sum_of_products(c.x, d.dx, c.dx, d.x); 101 | return Dual( 102 | geom::diff_of_products(a, b, c, d), 103 | dp - dq); 104 | } 105 | 106 | static inline Dual sum_of_products( 107 | Dual a, 108 | Dual b, 109 | Dual c, 110 | Dual d) 111 | { 112 | T dp = geom::sum_of_products(a.x, b.dx, a.dx, b.x); 113 | T dq = geom::sum_of_products(c.x, d.dx, c.dx, d.x); 114 | return Dual( 115 | geom::sum_of_products(a.x, b.x, c.x, d.x), 116 | dp + dq); 117 | } 118 | 119 | static inline Dual multiply_add(Dual a, Dual b, Dual c) { 120 | return Dual( 121 | geom::multiply_add(a.x, b.x, c.x), 122 | geom::sum_of_products(a.x, b.dx, a.dx, b.x) + c.dx 123 | ); 124 | } 125 | }; 126 | 127 | 128 | template 129 | struct _ImplFMADot, void> { 130 | static inline std::complex diff_of_products( 131 | std::complex a, 132 | std::complex b, 133 | std::complex c, 134 | std::complex d) 135 | { 136 | // (a.r * b.r - a.i * b.i) + (a.r * b.i + a.i * b.r)i - 137 | // (c.r * d.r - c.i * d.i) + (c.r * d.i + c.i * d.r)i 138 | auto ab_r = geom::diff_of_products(a.real, b.real, a.imag, b.imag); 139 | auto cd_r = geom::diff_of_products(c.real, d.real, c.imag, d.imag); 140 | auto ab_i = geom::sum_of_products(a.real, b.imag, a.imag, b.real); 141 | auto cd_i = geom::sum_of_products(c.real, d.imag, c.imag, d.real); 142 | return {ab_r - cd_r, ab_i - cd_i}; 143 | } 144 | 145 | static inline std::complex sum_of_products( 146 | std::complex a, 147 | std::complex b, 148 | std::complex c, 149 | std::complex d) 150 | { 151 | // (a.r * b.r - a.i * b.i) + (a.r * b.i + a.i * b.r)i + 152 | // (c.r * d.r - c.i * d.i) + (c.r * d.i + c.i * d.r)i 153 | auto ab_r = geom::diff_of_products(a.real, b.real, a.imag, b.imag); 154 | auto cd_r = geom::diff_of_products(c.real, d.real, c.imag, d.imag); 155 | auto ab_i = geom::sum_of_products(a.real, b.imag, a.imag, b.real); 156 | auto cd_i = geom::sum_of_products(c.real, d.imag, c.imag, d.real); 157 | return {ab_r + cd_r, ab_i + cd_i}; 158 | } 159 | 160 | static inline std::complex multiply_add( 161 | std::complex a, 162 | std::complex b, 163 | std::complex c) 164 | { 165 | // todo: better...? 166 | // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i 167 | // auto ab_r = diff_of_products(a.real, b.real, a.imag, b.imag); 168 | // auto ab_i = sum_of_products(a.real, b.imag, a.imag, b.real); 169 | // return {ab_r + c.real, ab_i + c.i}; 170 | 171 | // or maybe try to kahanify this yourself...? 172 | 173 | // (a + bi) * (c + di) + (k_r + k_i i) 174 | // real: (ac - bd + k_r) 175 | auto bd_k = geom::multiply_add(-a.imag, b.imag, c.real); 176 | auto real = geom::multiply_add( a.real, b.real, bd_k); 177 | // imag: (ad + bc + k_i) 178 | auto bc_k = geom::multiply_add(a.imag, b.real, c.imag); 179 | auto imag = geom::multiply_add(a.real, b.imag, bc_k); 180 | return {real, imag}; 181 | } 182 | }; 183 | 184 | } // detail 185 | } // geom 186 | -------------------------------------------------------------------------------- /geomc/geomc_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * geomc_defs.h 5 | * 6 | * Created on: Dec 24, 2010 7 | * Author: tbabb 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /** 17 | * @mainpage GEOMC Library 18 | * 19 | * Geomc is a C++ template library for geometry and basic linear algebra. It is 20 | * built to provide building blocks for 2D and 3D applications, though generalization 21 | * to `N` dimensions is a major design philosophy. 22 | * 23 | * Wherever possible, types are designed to interoperate intuitively via C++ 24 | * arithmetic operators. Performance, templatization over element types and 25 | * dimension, and minimization of dynamic memory allocations are all emphasized. 26 | * 27 | * Source code 28 | * =========== 29 | * 30 | * Download the geomc headers and source from: 31 | * http://github.com/trbabb/geomc 32 | * 33 | */ 34 | 35 | // Shall a matrix check whether index is out-of-bounds on every access? 36 | 37 | /* #define GEOMC_MTX_CHECK_BOUNDS */ 38 | 39 | /** 40 | * Shall matrices verify correct dimensions before performing inter-matrix 41 | * operations? (recommended) 42 | */ 43 | 44 | #define GEOMC_MTX_CHECK_DIMS 45 | 46 | /** 47 | * Shall matrices check and handle storage aliasing among matrices in 48 | * inter-matrix operations? 49 | */ 50 | 51 | #ifndef GEOMC_MTX_CHECK_ALIASING 52 | #define GEOMC_MTX_CHECK_ALIASING 1 53 | #endif 54 | 55 | // Shall vectors include functions for outputting to streams? 56 | 57 | #ifndef GEOMC_USE_STREAMS 58 | #define GEOMC_USE_STREAMS 1 59 | #endif 60 | 61 | // Shall the module include functions for outputting to streams? 62 | 63 | #define GEOMC_FUNCTION_USE_STREAMS 64 | 65 | 66 | #define PI (3.141592653589793238462643383) 67 | #define TAU (6.283185307179586476925286767) 68 | 69 | #define DYNAMIC_DIM (0) 70 | 71 | #define M_CLAMP(v,lo,hi) std::min(std::max((v),(lo)),(hi)) 72 | 73 | #define M_ENABLE_IF(cond) \ 74 | typename std::enable_if<(cond), int>::type DUMMY=0 75 | 76 | #define DERIVED_TYPE(base,derived) \ 77 | typename std::enable_if< std::is_base_of< (base), (derived) >, (derived)>::type 78 | 79 | #define REQUIRE_INHERIT(base,derived) \ 80 | typename std::enable_if< std::is_base_of< (base), (derived) >, int>::type dummy=0 81 | 82 | #define REQUIRE_INHERIT_T(base,derived) \ 83 | typename std::enable_if< std::is_base_of< base, derived >, int>::type 84 | 85 | typedef std::ptrdiff_t index_t; 86 | 87 | 88 | /** @brief Namespace of all `geomc` functions and classes. */ 89 | namespace geom { 90 | 91 | // storage fwd decls 92 | template struct Storage; 93 | template struct SizedStorage; 94 | template struct UnmanagedStorage; 95 | 96 | }; 97 | 98 | #ifdef PARSING_DOXYGEN 99 | 100 | /** @brief Functions to extend support of stdlib to geomc classes. */ 101 | namespace std { }; 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /geomc/linalg/Cholesky.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // todo: make a Symmetric matrix class, which this can operate on. 7 | // also optimize addition / multiplication / inversion / det / etc. 8 | // also author a square(&symm, mtx) function to compute M * M^T into a symm. mtx 9 | // (and work all of the above into Kalman filter). 10 | // todo: consider also a Triangular matrix class. 11 | // todo: there is no need to template MatrixLayout— square symmetric matrices 12 | // can be safely transposed. 13 | // todo: use data_begin() and data_end() for the matrix. 14 | 15 | namespace geom { 16 | 17 | /** @ingroup linalg 18 | * @{ 19 | */ 20 | 21 | /** 22 | * @brief Perform a Cholesky decomposition on a bare array. 23 | * 24 | * Perform an in-place Cholesky decomposition on the given square positive-definite 25 | * matrix `A`, producing a lower-triangular matrix `M` such that 26 | * `(M * M`T`) = A`. 27 | * 28 | * @tparam T Element type. 29 | * @tparam RowMajor Whether the elements in `m` are arranged in row-major (true) 30 | * or column-major (false) order. 31 | * @param m The elements of the matrix to be decomposed. 32 | * @param n The number of rows/columns in the matrix to be decomposed. 33 | * @return True if the decomposition could be completed; false if the matrix was not 34 | * positive-definite or ill-conditioned. 35 | */ 36 | template 37 | bool cholesky(T* m, index_t n) { 38 | bool ok = true; 39 | detail::MxWrap mx = {m, n, n}; 40 | for (index_t r = 0; r < n; ++r) { 41 | for (index_t c = 0; c <= r; ++c) { 42 | T s = mx.elem(r, c); 43 | for (index_t k = 0; k < c; ++k) { 44 | s -= mx.elem(r, k) * mx.elem(c, k); 45 | } 46 | if (r == c) { 47 | ok = ok and (s > 0); 48 | s = std::sqrt(s); 49 | } else { 50 | s = s / mx.elem(c, c); 51 | // upper matrix gets set to 0: 52 | mx.elem(c, r) = 0; 53 | } 54 | mx.elem(r, c) = s; 55 | } 56 | } 57 | return ok; 58 | } 59 | 60 | 61 | /** 62 | * @brief Perform a Cholesky decomposition on a Matrix. 63 | * 64 | * Perform an in-place Cholesky decomposition on the given square positive-definite 65 | * matrix `A`, producing a lower-triangular matrix `M` such that 66 | * `(M * M`T`) = A`. 67 | * 68 | * @tparam T Element type of the matrix. 69 | * @tparam M Number of rows in the matrix. Must either match N or be dynamic (0). 70 | * @tparam N Number of columns in the matrix. Must either match M or be dynamic (0). 71 | * 72 | * @param m A square positive-definite matrix. 73 | * @return False if the matrix is not square, not positive-definite, or could 74 | * not be decomposed due to ill-conditioning; `true` if the decomposition was 75 | * completed successfully. 76 | */ 77 | #ifdef PARSING_DOXYGEN 78 | template 79 | bool cholesky(SimpleMatrix* m); 80 | #else 81 | template 82 | inline typename std::enable_if::type 83 | cholesky(SimpleMatrix* m) { 84 | if (M * N == 0 and m->rows() != m->cols()) { 85 | return false; 86 | } 87 | return cholesky(m->begin(), m->rows()); 88 | } 89 | #endif 90 | 91 | 92 | /// @} // ingroup linalg 93 | 94 | } 95 | -------------------------------------------------------------------------------- /geomc/linalg/Isometry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace geom { 6 | 7 | /** 8 | * @ingroup linalg 9 | * @brief A rigid rotation and translation. 10 | * 11 | * Isometric transfroms do not have any skew or scales; they preserve 12 | * shapes, angles, and distances. 13 | * 14 | * Isometries meet the Transform concept. For transforms which include a scaling, 15 | * see Similarity. For nonuniform scaling or skew transforms, see AffineTransform. 16 | * 17 | * Isometries compose like transforms, with multiplication on the left: 18 | * 19 | * Isometry i1, i2; 20 | * Isometry i3 = i2 * i1; // isometry which applies i1, then i2 21 | * Vec v = i3 * i1 * v0; // apply i1 to v0, then i3 to the result 22 | * Sphere s = i1 * sphere; // apply i1 to a sphere 23 | * 24 | * Isometries can be inverted with the `/` operator: 25 | * 26 | * Isometry i1, i2; 27 | * Isometry i3 = i2 / i1; // isometry which takes i1 to i2 28 | * Vec v = v0 / i3; // apply the inverse of i3 to v0 29 | * 30 | * Compose with rotations and translations: 31 | * 32 | * Isometry i; 33 | * Rotation r; 34 | * Isoemtry i2 = r * i; // i2 is i with r post-applied 35 | * Isometry i3 = i + Vec(1,2,3); // i3 is i translated by (1,2,3) 36 | * 37 | */ 38 | template 39 | class Isometry : public Dimensional { 40 | public: 41 | 42 | /// Rotation component. 43 | Rotation rx; 44 | /// Translation component. 45 | Vec tx; 46 | 47 | Isometry():rx(),tx() {} 48 | Isometry(const Rotation& rx, const Vec& tx):rx(rx),tx(tx) {} 49 | Isometry(const Rotation& rx):rx(rx),tx() {} 50 | Isometry(const Vec& tx):rx(),tx(tx) {} 51 | 52 | /// Cast to an affine transform. 53 | operator AffineTransform() const { 54 | return geom::translation(tx) * rx.transform(); 55 | } 56 | 57 | /// Cast the underlying coordinate type. 58 | template 59 | explicit operator Isometry() const { 60 | return Isometry(rx, tx); 61 | } 62 | 63 | /// Extend the dimensionality of this isometry. 64 | template 65 | requires (M > N) 66 | operator Isometry() const { 67 | return Isometry(rx, tx.template resized()); 68 | } 69 | 70 | /// Extend the dimensionality of this isometry and change the coordinate type. 71 | template 72 | requires (M > N) 73 | explicit operator Isometry() const { 74 | return Isometry( 75 | static_cast>(rx), 76 | Vec(tx) 77 | ); 78 | } 79 | 80 | /// Compose two isometric transforms. 81 | Isometry operator*(const Isometry& other) const { 82 | return Isometry(rx * other.rx, tx + rx.transform(other.tx)); 83 | } 84 | 85 | /// Compose in-place. 86 | Isometry& operator*=(const Isometry& other) { 87 | tx += rx.transform(other.tx); 88 | rx *= other.rx; 89 | return *this; 90 | } 91 | 92 | /// Transform a point. 93 | Vec operator*(const Vec& p) const { 94 | return rx * p + tx; 95 | } 96 | 97 | /// Transform a direction vector. 98 | Vec apply_direction(const Vec v) const { 99 | return rx * v; 100 | } 101 | 102 | /// Inverse transform a direction vector. 103 | Vec apply_inverse_direction(const Vec v) const { 104 | return v / rx; 105 | } 106 | 107 | /// Compose with the inverse of an isometry. 108 | Isometry operator/(const Isometry& other) const { 109 | return Isometry(rx / other.rx, rx.transform(other.tx - tx)); 110 | } 111 | 112 | /// In-place apply inverse 113 | Isometry& operator/=(const Isometry& other) { 114 | tx = rx.transform(other.tx - tx); 115 | rx /= other.rx; 116 | return *this; 117 | } 118 | 119 | /// Compute the inverse of the isometry. 120 | Isometry inverse() const { 121 | Rotation r_inv = rx.inverse(); 122 | return Isometry( 123 | r_inv, 124 | r_inv * (-tx) 125 | ); 126 | } 127 | 128 | /// Apply a translation. 129 | Isometry operator+=(const Vec& v) { 130 | tx += v; 131 | return *this; 132 | } 133 | }; 134 | 135 | /** 136 | * @addtogroup linalg 137 | * @{ 138 | */ 139 | 140 | /// @brief Transform a point. 141 | /// @related Isometry 142 | template 143 | Vec operator/(const Vec& v, const Isometry& i) { 144 | // unapply the translation and rotation in reverse order 145 | return (v - i.tx) / i.rx; 146 | } 147 | 148 | /// @brief Transform a ray. 149 | /// @related Isometry 150 | /// @related Ray 151 | template 152 | Ray operator*(const Isometry& xf, const Ray& ray) { 153 | return {xf * ray.origin, xf.rx * ray.direction}; 154 | } 155 | 156 | /// @brief Inverse-transform a ray. 157 | /// @related Isometry 158 | /// @related Ray 159 | template 160 | Ray operator/(const Ray& ray, const Isometry& xf) { 161 | return {ray.origin / xf, ray.direction / xf.rx}; 162 | } 163 | 164 | /// @brief Apply a rotation to an isometry. 165 | /// @related Isometry 166 | /// @related Rotation 167 | template 168 | Isometry operator*(const Rotation& r, const Isometry& i) { 169 | return Isometry(r * i.rx, r.transform(i.tx)); 170 | } 171 | 172 | /// @brief Apply an isometry to a rotation. 173 | /// @related Isometry 174 | /// @related Rotation 175 | template 176 | Isometry operator*(const Isometry& i, const Rotation& r) { 177 | return Isometry(i.rx * r, i.tx); 178 | } 179 | 180 | /// @brief Apply a translation to an isometry. 181 | /// @related Isometry 182 | template 183 | Isometry operator+(const Isometry& i, const Vec& v) { 184 | return Isometry(i.rx, i.tx + v); 185 | } 186 | 187 | /// @brief Apply a translation to an isometry. 188 | /// @related Isometry 189 | template 190 | Isometry operator+(const Vec& v, const Isometry& i) { 191 | return Isometry(i.rx, i.tx + v); 192 | } 193 | 194 | /// @brief Scale the magnitude of an isometry. A scale of 0 produces an identity transform. 195 | /// Applying a scale of 1 to an isometry results in no change. 196 | /// @related Isometry 197 | template 198 | Isometry operator*(T s, const Isometry& xf) { 199 | return { 200 | s * xf.rx, 201 | s * xf.tx 202 | }; 203 | } 204 | 205 | /// @brief Scale the magnitude of an isometry. 206 | /// @related Isometry 207 | template 208 | Isometry operator*(const Isometry& xf, T s) { 209 | return { 210 | s * xf.rx, 211 | s * xf.tx 212 | }; 213 | } 214 | 215 | /// @brief Continuously interpolate two isometries. 216 | /// @related Isometry 217 | template 218 | Isometry mix(T s, const Isometry& a, const Isometry& b) { 219 | return { 220 | mix(s, a.rx, b.rx), 221 | mix(s, a.tx, b.tx) 222 | }; 223 | } 224 | 225 | /// @} // group linalg 226 | 227 | template 228 | struct Digest, H> { 229 | H operator()(const Isometry& xf) const { 230 | H nonce = geom::truncated_constant(0x176a7504edd40424, 0x292b420bad0c4b61); 231 | return geom::hash_many(nonce, xf.rx, xf.tx); 232 | } 233 | }; 234 | 235 | #ifdef GEOMC_USE_STREAMS 236 | 237 | template 238 | std::ostream& operator<<(std::ostream& os, const Isometry& xf) { 239 | os << "Isometry(" << xf.rx << ", " << "Tx" << xf.tx << ")"; 240 | return os; 241 | } 242 | 243 | #endif 244 | 245 | } // namespace geom 246 | -------------------------------------------------------------------------------- /geomc/linalg/Orthogonal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * File: Orthogonal.h 5 | * Author: tbabb 6 | * 7 | * Created on October 15, 2014, 12:23 AM 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /* 15 | computing one vector orthogonal to n others: 16 | M * v = (0 ... 1 ... 0) 17 | where we set the basis vectors we wish to solve for to 1. 18 | we are solving for the unit vectors in the null space of M. basically. 19 | 20 | http://en.wikibooks.org/wiki/Linear_Algebra/Null_Spaces 21 | 22 | get to row-echelon form, and solve: 23 | d e f g x 0 24 | 0 h i j * y = 0 25 | 0 0 k l z 0 26 | 1 w 1 27 | 28 | ↓ ↓ 29 | d e f # g # x 0 30 | 0 h i # j # * y = 0 31 | 0 0 k # l # z 0 32 | - 0 ←┬─ ignore/clear extra cols/vars 33 | w 1 │ 34 | - 0 ←┘ 35 | 36 | dx + ey + fz + gw = 0 37 | 0x + hy + iz + jw = 0 38 | 0x + 0y + kz + lw = 0 39 | 0x + 0y + 0z + 1w = 1 40 | -- 41 | kz = -l 42 | z = -l / k 43 | ... 44 | etc. 45 | */ 46 | 47 | 48 | namespace geom { 49 | 50 | // todo: we do not need to compute L for most of this. 51 | // amend decomp_lup() and friends to accept `bool compute_l` defaulting to `true`, 52 | // which does not operate on the lower triangular matrix if `false`. 53 | // should improve performance by slightly less than a factor of 2. 54 | 55 | // todo: orthogonal() does not respect winding. 56 | // the wedge product is maybe something you need. 57 | 58 | /** 59 | * @addtogroup linalg 60 | * @{ 61 | */ 62 | 63 | /** 64 | * @brief Return a vector orthogonal to the given `N-1` vectors. 65 | * 66 | * If any of the given basis vectors are linearly dependent, 67 | * the function returns the 0 vector. 68 | * 69 | * @param v Array of `N-1` basis vectors. 70 | * @return A vector normal to all the members of `v`. 71 | */ 72 | template 73 | Vec orthogonal(const Vec v[N-1]) { 74 | Vec o; 75 | index_t P[N]; 76 | T m[N * (N-1)]; 77 | const T* v0 = v[0].begin(); 78 | detail::MxWrap mx = {m, N-1, N}; 79 | 80 | std::copy(v0, v0 + N * (N-1), m); 81 | 82 | bool parity_swap = false; 83 | if (decomp_plu(m, N-1, N, P, &parity_swap) > 0) { 84 | // matrix is singular; nullity is > 1. return 0 vector. 85 | return Vec(); 86 | } 87 | 88 | o[N-1] = 1; 89 | for (index_t r = N-2; r >= 0; r--) { 90 | // back substitute. 91 | for (index_t c = N-1; c > r; c--) { 92 | o[r] -= o[c] * mx(r,c); 93 | } 94 | o[r] /= mx(r,r); 95 | } 96 | 97 | return o; 98 | } 99 | 100 | 101 | template 102 | inline Vec orthogonal(const Vec v[2]) { 103 | return v[0] ^ v[1]; 104 | } 105 | 106 | 107 | template 108 | inline Vec orthogonal(const Vec v[1]) { 109 | return v[0].left_perpendicular(); 110 | } 111 | 112 | 113 | // todo: a different formulation could handle degeneracy in `bases` if 114 | // `bases` is specified to have N vectors; then the null bases 115 | // could fill the unused space, however many there are. 116 | // rn we bail if there is degeneracy, because it's excessive 117 | // to demand that `null_basis` always have space for N vectors. 118 | /** 119 | * @brief Compute the null space of a vector basis. 120 | * 121 | * The computed null bases will not necessarily be orthogonal to each other. 122 | * Use `orthogonalize()` after computing `nullspace()` if an orthogonal basis 123 | * is needed. 124 | * 125 | * `bases` and `null_basis` may alias each other. 126 | * 127 | * If any of the bases are linearly dependent, `null_basis` will be 128 | * filled with `N - n` zero vectors. 129 | * 130 | * @param bases Array of `n` linearly independent basis vectors. 131 | * @param n Number of basis vectors in the array. 132 | * @param null_basis Array with space to receive `N - n` output bases, whose dot 133 | * products with the inputs will all be zero. 134 | */ 135 | template 136 | bool nullspace(const Vec bases[], index_t n, Vec null_basis[]) { 137 | if (n >= N) return true; // nothing to do 138 | if (N - n == 1) { 139 | // this may be optimized for some (lower) dimensions: 140 | null_basis[0] = orthogonal(bases); 141 | return true; 142 | } 143 | 144 | T m[N * (N-2)]; // <-- N-2 is the max # bases 145 | index_t P[N - 2]; // (N-1 and N cases handled above) 146 | T* n0 = null_basis[0].begin(); 147 | const T* b0 = bases[0].begin(); 148 | 149 | // copy the bases to temporary storage 150 | std::copy(b0, b0 + N * n, m); 151 | // zero-init the results 152 | std::fill(n0, n0 + N * (N - n), 0); 153 | 154 | // each original basis is a row of: 155 | detail::MxWrap V = {m, n, N}; 156 | // each new null basis is a column of: 157 | detail::MxWrap X = {n0, N, N - n}; 158 | 159 | // get V to row-echelon form 160 | bool parity_swap = false; 161 | if (decomp_plu(m, n, N, P, &parity_swap) > 0) return false; 162 | 163 | // foreach null space basis 164 | for (index_t b = 0; b < N - n; b++) { 165 | X(b + n, b) = 1; 166 | // back substitute. 167 | for (index_t r = n - 1; r >= 0; r--) { 168 | T& x_i = X(r, b); 169 | x_i = -V(r, n + b); 170 | for (index_t c = r + 1; c < n; c++) { 171 | x_i -= V(r, c) * X(c, b); 172 | } 173 | x_i /= V(r, r); 174 | } 175 | } 176 | 177 | return true; 178 | } 179 | 180 | 181 | /** 182 | * @brief Use the Gram-Schmidt process to orthogonalize a set of basis vectors. 183 | * 184 | * The first basis vector will not change. The remaining vectors may be 185 | * of arbitrary magnitude, but will be mutually orthogonal to each 186 | * other and to the first vector. 187 | * 188 | * @param basis Set of `n` bases vectors. 189 | * @param n Number of basis vectors, between 0 and `N` inclusive. 190 | */ 191 | template 192 | void orthogonalize(Vec basis[], index_t n) { 193 | for (index_t i = 1; i < n; i++) { 194 | for (index_t j = 0; j < i; j++) { 195 | // modified gram-schmidt uses the intermediate basis 196 | // for better numerical stability. 197 | basis[i] -= basis[i].project_on(basis[j]); 198 | } 199 | } 200 | } 201 | 202 | // simple specialization for T=2 203 | template 204 | void orthogonalize(Vec basis[2], index_t _n=2) { 205 | if (_n == 2) { 206 | basis[1] = basis[0].left_perpendicular(); 207 | } 208 | } 209 | 210 | /** 211 | * @brief Use the Gram-Schmidt process to orthonormalize a set of basis vectors. 212 | * 213 | * The first basis vector will not change direction. All vectors will 214 | * be made mutually orthogonal and unit length. 215 | * 216 | * @param basis Set of `n` bases vectors. 217 | * @param n Number of basis vectors between 0 and `N` inclusive. 218 | */ 219 | template 220 | void orthonormalize(Vec basis[], index_t n) { 221 | orthogonalize(basis, n); 222 | for (index_t i = 0; i < n; i++) { 223 | basis[i] /= basis[i].mag(); 224 | } 225 | } 226 | 227 | 228 | /** 229 | * @brief Project a vector onto an orthogonal subspace. 230 | * 231 | * @param bases Array of `n` basis vectors. 232 | * @param x Vector to project. 233 | * @param n Number of basis vectors. 234 | */ 235 | template 236 | Vec project_to_orthogonal_subspace(const Vec bases[], Vec x, index_t n) { 237 | Vec out; 238 | for (index_t i = 0; i < n; i++) { 239 | out += x.project_on(bases[i]); 240 | } 241 | return out; 242 | } 243 | 244 | /** 245 | * @brief Project a vector onto a non-orthogonal subspace. 246 | * 247 | * @param bases Array of `n` basis vectors. The contents of this array will be altered. 248 | * @param x Vector to project in-place. 249 | * @param n Number of basis vectors. 250 | */ 251 | template 252 | Vec project_to_subspace(Vec bases[], Vec x, index_t n) { 253 | orthonormalize(bases, n); 254 | return project_to_orthogonal_subspace(bases, x, n); 255 | } 256 | 257 | /// @} //addtogroup linalg 258 | 259 | } // namespace geom 260 | -------------------------------------------------------------------------------- /geomc/linalg/Ray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef GEOMC_USE_STREAMS 4 | #include 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | 11 | namespace geom { 12 | 13 | /** 14 | * @brief Ray class. 15 | * @tparam T Coordinate type. 16 | * @tparam N Dimensionality. 17 | * @ingroup linalg 18 | */ 19 | template 20 | class Ray : public Dimensional { 21 | public: 22 | using typename Dimensional::point_t; 23 | /// Origin of ray. 24 | point_t origin; 25 | /// Direction of ray. 26 | point_t direction; 27 | 28 | /************************* 29 | * Structors * 30 | *************************/ 31 | 32 | /// Construct a ray through the origin directed along the +x axis. 33 | Ray():origin((T)0),direction((T)0) { 34 | direction[0] = 1; //x axis 35 | } 36 | 37 | /// Construct a ray through the point `o` with direction `v`. 38 | Ray(point_t o, point_t v): 39 | origin(o), 40 | direction(v) {} 41 | 42 | /************************* 43 | * Operators * 44 | *************************/ 45 | 46 | /// Return a ray pointing in the opposite direction. 47 | Ray operator-() const { 48 | return {origin, -direction}; 49 | } 50 | 51 | /************************* 52 | * Public Methods * 53 | *************************/ 54 | 55 | /// Change the dimensionality of this Ray. 56 | template 57 | inline Ray resized() const { 58 | return Ray( 59 | origin. template resized(), 60 | direction.template resized()); 61 | } 62 | 63 | /// Return the point along this ray's direction, at distance `d` from the ray origin. 64 | inline point_t at_distance(T d) const { 65 | return origin + direction.unit() * d; 66 | } 67 | 68 | /// Return the point `origin + s * direction`. 69 | inline point_t at_multiple(T s) const { 70 | return origin + direction * s; 71 | } 72 | 73 | /** 74 | * @brief Find the point on the ray nearest to `p` by orhtogonally 75 | * projecting `p` onto it. 76 | */ 77 | inline point_t project(point_t p) const { 78 | return (p - origin).project_on(direction) + origin; 79 | } 80 | 81 | /// Signed distance from `p` to the nearest point on the ray. 82 | inline T sdf(point_t p) const { 83 | return std::sqrt(dist2()); 84 | } 85 | 86 | /// Compute the square of the distance from `p` to the nearest point on the ray. 87 | inline T dist2(point_t p) const { 88 | point_t b = p - origin; 89 | T b2 = b.mag2(); 90 | T d = direction.dot(b); 91 | return b2 == 0 ? 0 : b2 - (d * d / b2); 92 | } 93 | 94 | }; // class Ray 95 | 96 | 97 | /** 98 | * Ray multiple. 99 | * @return `(r.origin + s * r.direction)`. 100 | * @related Ray 101 | * @ingroup linalg 102 | */ 103 | template 104 | inline typename PointType::point_t operator*(const Ray r, T s) { 105 | return r.at_multiple(s); 106 | } 107 | 108 | 109 | /** 110 | * Ray multiple. 111 | * @return `(r.origin + s * r.direction)`. 112 | * @related Ray 113 | * @ingroup linalg 114 | */ 115 | template 116 | inline typename PointType::point_t operator*(T s, const Ray r) { 117 | return r.at_multiple(s); 118 | } 119 | 120 | #ifdef GEOMC_USE_STREAMS 121 | 122 | /** @ingroup linalg 123 | * Ray stream output, in the form: 124 | * 125 | * <(x0, x1, x2, ... ) + s * (v0, v1, v2, ...)> 126 | * where `x` is the origin and `v` is the direction. 127 | * @related Ray 128 | */ 129 | template 130 | inline std::ostream &operator<< (std::ostream &stream, const Ray &r) { 131 | stream << "<" << r.origin << " + s * " << r.direction << ">"; 132 | return stream; 133 | } 134 | 135 | #endif // GEOMC_USE_STREAMS 136 | 137 | 138 | template 139 | struct Digest, H> { 140 | H operator()(const Ray &r) const { 141 | H nonce = geom::truncated_constant(0xc5ca6b58d6ca9853, 0x8af9e7e642670825); 142 | return geom::hash_many,H>(nonce, r.origin, r.direction); 143 | } 144 | }; 145 | 146 | } // namespace geom 147 | 148 | 149 | template 150 | struct std::hash> { 151 | size_t operator()(const geom::Ray &v) const { 152 | return geom::hash, size_t>(v); 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /geomc/linalg/mtxdetail/MatrixArithmetic.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * File: MatrixArithmetic.h 5 | * Author: tbabb 6 | * 7 | * Created on July 28, 2013, 11:53 AM 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | namespace geom { 14 | namespace detail { 15 | 16 | /************************************ 17 | * Matrix add/sub * 18 | ************************************/ 19 | 20 | template 21 | struct _ImplMatrixAdd { 22 | 23 | //todo: should use decltype(Ma::elem_t + Mb::elem_t) 24 | typedef SimpleMatrix 27 | return_t; 28 | 29 | template 30 | static void add(Md *d, const Ma &a, const Mb &b) { 31 | typename Md::iterator d_i = d->begin(); 32 | typename Ma::const_iterator a_i = a.begin(); 33 | typename Mb::const_iterator b_i = b.begin(); 34 | for (; d_i != d->end(); ++d_i, ++a_i, ++b_i) { 35 | *d_i = (*a_i) + (*b_i); 36 | } 37 | } 38 | 39 | template 40 | static void sub(Md *d, const Ma &a, const Mb &b) { 41 | typename Md::iterator d_i = d->begin(); 42 | typename Ma::const_iterator a_i = a.begin(); 43 | typename Mb::const_iterator b_i = b.begin(); 44 | for (; d_i != d->end(); ++d_i, ++a_i, ++b_i) { 45 | *d_i = (*a_i) - (*b_i); 46 | } 47 | } 48 | 49 | }; 50 | 51 | //////////// Diagonal case //////////// 52 | 53 | template 55 | struct _ImplMatrixAdd < DiagMatrix, DiagMatrix > { 56 | 57 | typedef DiagMatrix Ma; 58 | typedef DiagMatrix Mb; 59 | 60 | //TODO: use decltype(T+S) 61 | typedef DiagMatrix 64 | return_t; 65 | 66 | // arbitrary destination 67 | 68 | template 69 | static void add(Md *dest, const Ma &a, const Mb &b) { 70 | dest->set_zero(); 71 | const index_t diag = std::min(a.rows(), a.cols()); 72 | 73 | const T *a_i = a.diagonal_begin(); 74 | const S *b_i = b.diagonal_begin(); 75 | for (index_t i = 0; i < diag; ++a_i, ++b_i, ++i) { 76 | dest->set(i, i, *a_i + *b_i); 77 | } 78 | } 79 | 80 | template 81 | static void sub(Md *dest, const Ma &a, const Mb &b) { 82 | dest->set_zero(); 83 | const index_t diag = std::min(a.rows(), a.cols()); 84 | 85 | const T *a_i = a.diagonal_begin(); 86 | const S *b_i = b.diagonal_begin(); 87 | for (index_t i = 0; i < diag; ++a_i, ++b_i, ++i) { 88 | dest->set(i,i, *a_i - *b_i); 89 | } 90 | } 91 | 92 | // diagonal destination 93 | // (save a set_zero, and use bare pointers). 94 | 95 | template 96 | static void add(DiagMatrix *dest, const Ma &a, const Mb &b) { 97 | const T *a_i = a.diagonal_begin(); 98 | const S *b_i = b.diagonal_begin(); 99 | U *d_i = dest->diagonal_begin(); 100 | 101 | for (; a_i != a.diagonal_end(); ++a_i, ++b_i, ++d_i) { 102 | *d_i = *a_i + *b_i; 103 | } 104 | } 105 | 106 | template 107 | static void sub(DiagMatrix *dest, const Ma &a, const Mb &b) { 108 | const T *a_i = a.diagonal_begin(); 109 | const S *b_i = b.diagonal_begin(); 110 | U *d_i = dest->diagonal_begin(); 111 | 112 | for (; a_i != a.diagonal_end(); ++a_i, ++b_i, ++d_i) { 113 | *d_i = *a_i - *b_i; 114 | } 115 | } 116 | 117 | }; 118 | 119 | 120 | // stupid stupid workaround because c++ is a stupid stupid language: 121 | template 122 | struct _ImplMatrixAddReturnType { 123 | // no return type. engage SFINAE. 124 | }; 125 | 126 | // when using enable_if::return_t> elsewhere, 127 | // compilation will fail because even if is false and "protects" 128 | // against instantiation, c++ will try to expand the argument type. if a or b 129 | // are non-matrices, then the expansion will fail. This is not SFINAE because by 130 | // the time we are evaluating what ::return_t is, we are no longer substituting, 131 | // we are type-expanding. I believe this is a flaw in the c++ language design. 132 | 133 | template 134 | struct _ImplMatrixAddReturnType ::val and 137 | detail::IsMatrix::val and 138 | detail::MatrixDimensionMatch::isStaticMatch, 139 | void 140 | >> { 141 | typedef typename _ImplMatrixAdd::return_t return_t; 142 | }; 143 | 144 | /************************************ 145 | * Matrix scalar mul * 146 | ************************************/ 147 | 148 | template 149 | struct _ImplMatrixScale { 150 | typedef Mx return_t; 151 | 152 | template 153 | static void scale(Md *d, U k, const Mx &m) { 154 | for (index_t r = 0; r < m.rows(); ++r) { 155 | for (index_t c = 0; c < m.cols(); ++c) { 156 | d->set(r, c, k * m(r,c)); 157 | } 158 | } 159 | } 160 | }; 161 | 162 | ////////// diagonal case ////////// 163 | 164 | template 165 | struct _ImplMatrixScale < DiagMatrix > { 166 | typedef DiagMatrix return_t; 167 | 168 | template 169 | static void scale(Md *d, U k, const DiagMatrix &m) { 170 | d->set_zero(); 171 | const index_t diag = std::min(m.rows(), m.cols()); 172 | const T *m_i = m.diagonal_begin(); 173 | for (index_t i = 0; i < diag; ++i, ++m_i) { 174 | d->set(i,i, k * (*m_i)); 175 | } 176 | } 177 | 178 | template 179 | static void scale(DiagMatrix *d, U k, const DiagMatrix &m) { 180 | S *d_i = d->diagonal_begin(); 181 | const T *m_i = m.diagonal_begin(); 182 | for (; m_i != m.diagonal_end(); ++d_i, ++m_i) { 183 | *d_i = k * (*m_i); 184 | } 185 | } 186 | }; 187 | 188 | 189 | }; // end namespace detail 190 | }; // end namespace geom 191 | -------------------------------------------------------------------------------- /geomc/linalg/mtxdetail/MatrixCopy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * File: MatrixCopy.h 4 | * Author: tbabb 5 | * 6 | * Created on September 14, 2013, 6:56 PM 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | 13 | namespace geom { 14 | namespace detail { 15 | 16 | template 17 | inline void _mtxcopy(Md* into, const Mx& src) { 18 | std::copy(src.begin(), src.end(), into->begin()); // also works for vectors. neat! 19 | } 20 | 21 | 22 | template 23 | inline void _mtxcopy(geom::PermutationMatrix* into, const geom::PermutationMatrix& src) { 24 | into->setRowSources(src.getRowSources()); 25 | } 26 | 27 | 28 | template 30 | void _mtxcopy(geom::DiagMatrix* into, const geom::DiagMatrix& src) { 31 | std::copy(src.diagonal_begin(), src.diagonal_end(), into->diagonal_begin()); 32 | } 33 | 34 | // SimpleMatrixes with the same layout can copy via bare pointers. 35 | template 40 | void _mtxcopy( 41 | geom::SimpleMatrix* into, 42 | const geom::SimpleMatrix& src) 43 | { 44 | std::copy(src.data_begin(), src.data_end(), into->data_begin()); 45 | } 46 | 47 | 48 | }; // namespace detail 49 | 50 | 51 | /** 52 | * @addtogroup matrix 53 | * @{ 54 | */ 55 | 56 | /********************************* 57 | * User-facing functions * 58 | *********************************/ 59 | 60 | /** 61 | * Copy the contents of `src` into `into`. This function may be optimized to 62 | * perform better than running `std::copy()` on some matrix types' iterators. It 63 | * will also succeed in certain situations where `std::copy()` would fail (for 64 | * example when copying the contents of a diagonal matrix to another, any attempt 65 | * to write off the diagonal will cause an error). 66 | * 67 | * `Matrix` and `Matrix1` must be matrix or vector types whose dimensions match. 68 | * If the dimensions can be determined to mismatch at compile-time, the program 69 | * is considered invalid and the compilation will fail. If either object has dynamic 70 | * size, the check will be deferred to runtime, throwing a `DimensionMismatchException` 71 | * if the check fails. 72 | * 73 | * @param [out] into A writeable matrix with dimensions matching `src`'s. 74 | * @param [in] src A matrix object. 75 | */ 76 | #ifdef PARSING_DOXYGEN 77 | template void mtxcopy(Matrix *into, const Matrix1 &src) {} 78 | #endif 79 | template 80 | void mtxcopy(Md* into, const Ms& src, 81 | typename std::enable_if< 82 | detail::LinalgDimensionMatch::val and 83 | (detail::_ImplVecOrient::orient != detail::ORIENT_VEC_UNKNOWN), 84 | int>::type dummy=0) { 85 | #ifdef GEOMC_MTX_CHECK_DIMS 86 | typedef detail::_ImplMtxAdaptor::orient> D; 87 | typedef detail::_ImplMtxAdaptor::orient> S; 88 | 89 | // runtime dimension match check 90 | if ((D::ROWDIM * S::ROWDIM == DYNAMIC_DIM and D::rows(*into) != S::rows(src)) or 91 | (D::COLDIM * S::COLDIM == DYNAMIC_DIM and D::cols(*into) != S::cols(src))) { 92 | throw DimensionMismatchException(D::rows(*into), D::cols(*into), S::rows(src), S::cols(src)); 93 | } 94 | #endif 95 | // todo: check mem aliasing? 96 | detail::_mtxcopy(into, src); 97 | } 98 | 99 | 100 | // unknown vector orientation case (dynamic matrix <-> vector) 101 | template 102 | void mtxcopy(Md* into, const Ms& src, 103 | typename std::enable_if< 104 | detail::_ImplVecOrient::orient == detail::ORIENT_VEC_UNKNOWN, 105 | int>::type dummy=0) { 106 | #ifdef GEOMC_MTX_CHECK_DIMS 107 | typedef detail::_ImplMtxAdaptor Dc; 108 | typedef detail::_ImplMtxAdaptor Dr; 109 | typedef detail::_ImplMtxAdaptor Sc; 110 | typedef detail::_ImplMtxAdaptor Sr; 111 | 112 | // if a dimension match can be made with either orientation, proceed with the copy 113 | if ((Dc::rows(*into) == Sc::rows(src) and Dc::cols(*into) == Sc::cols(src)) or 114 | (Dr::rows(*into) == Sr::rows(src) and Dr::cols(*into) == Sr::cols(src))) { 115 | detail::_mtxcopy(into, src); 116 | } else { 117 | throw DimensionMismatchException(Dc::rows(*into), Dc::cols(*into), Sc::rows(src), Sc::cols(src)); 118 | } 119 | #else 120 | detail::_mtxcopy(into, src); 121 | #endif 122 | } 123 | 124 | /// @} // addtogroup matrix 125 | 126 | }; // namespace geom 127 | -------------------------------------------------------------------------------- /geomc/linalg/mtxdetail/MatrixDet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // todo: all untested 7 | 8 | namespace geom { 9 | 10 | namespace detail { 11 | 12 | template 13 | struct _m2x2 { 14 | T a,b, 15 | c,d; 16 | }; 17 | 18 | 19 | template 20 | struct _m3x3 { 21 | T a,b,c, 22 | d,e,f, 23 | g,h,i; 24 | }; 25 | 26 | 27 | template 28 | struct _m4x4 { 29 | T a,b,c,d, 30 | e,f,g,h, 31 | i,j,k,l, 32 | m,n,o,p; 33 | }; 34 | 35 | template 36 | inline T cofac(T a, T b, T c, T d, T e, T f) { 37 | // compute a * b - c * d + e * f 38 | return geom::multiply_add(e, f, diff_of_products(a, b, c, d)); 39 | } 40 | 41 | } // namespace detail 42 | 43 | /** 44 | * @addtogroup matrix 45 | * @{ 46 | */ 47 | 48 | /// Compute the 2x2 matrix determinant from individual parameters. 49 | template 50 | inline T det2x2(T a, T b, T c, T d) { 51 | return diff_of_products(a, d, b, c); 52 | } 53 | 54 | template 55 | inline T det2x2(const T m[4]) { 56 | const detail::_m2x2& v = *reinterpret_cast*>(m); 57 | return diff_of_products(v.a, v.d, v.b, v.c); 58 | } 59 | 60 | /// Compute the 3x3 matrix determinant 61 | template 62 | inline T det3x3(const T m[9]) { 63 | const detail::_m3x3& v = *reinterpret_cast*>(m); 64 | return detail::cofac( 65 | v.a, det2x2(v.e, v.f, v.h, v.i), 66 | v.b, det2x2(v.d, v.f, v.g, v.i), 67 | v.c, det2x2(v.d, v.e, v.g, v.h) 68 | ); 69 | } 70 | 71 | /// Compute the 3x3 matrix determinant from individual parameters. 72 | template 73 | inline T det3x3( 74 | T a, T b, T c, 75 | T d, T e, T f, 76 | T g, T h, T i) 77 | { 78 | return detail::cofac( 79 | a, det2x2(e, f, h, i), 80 | b, det2x2(d, f, g, i), 81 | c, det2x2(d, e, g, h) 82 | ); 83 | } 84 | 85 | /// Compute the 4x4 matrix determinant. 86 | template 87 | T det4x4(const T m[16]) { 88 | const detail::_m4x4& v = *reinterpret_cast*>(m); 89 | return 90 | diff_of_products( 91 | v.a, det3x3(v.f, v.g, v.h, v.j, v.k, v.l, v.n, v.o, v.p), 92 | v.b, det3x3(v.e, v.g, v.h, v.i, v.k, v.l, v.m, v.o, v.p) 93 | ) 94 | + diff_of_products( 95 | v.c, det3x3(v.e, v.f, v.h, v.i, v.j, v.l, v.m, v.n, v.p), 96 | v.d, det3x3(v.e, v.f, v.g, v.i, v.j, v.k, v.m, v.n, v.o) 97 | ); 98 | } 99 | 100 | 101 | 102 | /** 103 | * @brief Destructively compute the determinant of a square matrix. 104 | * 105 | * The provided matrix will be used as a buffer for the computation, and no additional 106 | * memory will be allocated. The contents of `m` after the function returns are undefined. 107 | * 108 | * Note that it is generally preferable to use one of the `detNxN` methods if the 109 | * dimension is small and known at compile time. 110 | * 111 | * @param m The square matrix whose determinant is to be computed. `m` may be either row- 112 | * or column- major layout. The contents of `m` will be overwritten. 113 | * @param n The number of rows/columns in the matrix. 114 | */ 115 | template 116 | T det_destructive(T* m, index_t n) { 117 | if (n == 1) return m[0]; 118 | bool odd; 119 | if (decomp_plu(m, n, n, nullptr, &odd) != 0) return 0; 120 | T d = odd ? -1 : 1; 121 | // det(A) is the product of its diagonals, if A is triangular 122 | for (index_t i = 0; i < n; ++i) { 123 | d *= m[i * i]; 124 | } 125 | return d; 126 | } 127 | 128 | /** 129 | * @brief Compute the determinant of a square matrix. 130 | * 131 | * If `n` is larger than 8, heap memory may be allocated as buffer space for the computation. 132 | * (To avoid heap allocation, use `det_destructive()` instead). 133 | * 134 | * @param m The square matrix whose determinant is to be computed. `m` may be either row- 135 | * or column- major layout. 136 | * @param n The number of rows/columns in the matrix. 137 | */ 138 | template 139 | T detNxN(const T* m, index_t n) { 140 | bool odd; 141 | SmallStorage buf(m, n * n); 142 | if (decomp_plu(buf.get(), n, n, nullptr, &odd) != 0) return 0; 143 | T d = odd ? -1 : 1; 144 | // det(A) is the product of its diagonal elems, if A is triangular 145 | // and det(AB) = det(A) * det(B). So if M = LU, 146 | // because `L` has unit diagonal, det(M) is just the diagonal elements of `U`. 147 | for (index_t i = 0; i < n; ++i) { 148 | d *= buf[i * i]; 149 | } 150 | return d; 151 | } 152 | 153 | /** 154 | * @brief Compute the determinant of a square matrix. 155 | * 156 | * If the dimension of the matrix is larger than 8, heap memory may be allocated as buffer 157 | * space for the computation. 158 | * 159 | * If the dimension of the matrix can be determined at compile time to be nonsquare, 160 | * a static assertion is raised. Otherwise, nonsquare matrices will have determinant 0. 161 | * 162 | * @param m The square matrix whose determinant is to be computed. 163 | */ 164 | template 165 | requires (M == N or M * N == 0) 166 | inline T det(const SimpleMatrix& m) { 167 | if (M * N == 0 and m.rows() != m.cols()) return 0; 168 | return det(m.data_begin(), m.rows()); 169 | } 170 | 171 | // 2x2 matrix det 172 | template 173 | inline T det(const SimpleMatrix& m) { 174 | const T* v = m.data_begin(); 175 | // n.b.: layout is irrelevant 176 | return det2x2(v[0], v[1], v[2], v[3]); 177 | } 178 | 179 | // 3x3 matrix det 180 | template 181 | inline T det(const SimpleMatrix& m) { 182 | return det3x3(m.data_begin()); 183 | } 184 | 185 | // 4x4 matrix det 186 | template 187 | inline T det(const SimpleMatrix& m) { 188 | return det4x4(m.data_begin()); 189 | } 190 | 191 | /** 192 | * @brief Compute the determinant of a diagonal matrix. 193 | * 194 | * If the dimension of the matrix can be determined at compile time to be nonsquare, 195 | * a static assertion is raised. Otherwise, nonsquare matrices will have determinant 0. 196 | * 197 | * @param m The matrix whose determinant is to be computed. 198 | */ 199 | template 200 | T det(const DiagMatrix& m) { 201 | static_assert(N * M == 0 or M == N, "Determinant only defined for square matrices."); 202 | T d = 1; 203 | if (m.rows() != m.cols()) return 0; 204 | for (T* a = m.diagonal_begin(); a != m.diagonal_end(); ++a) { 205 | d *= *a; 206 | } 207 | return d; 208 | } 209 | 210 | /** 211 | * @brief Compute the determinant of a permutation matrix. 212 | * 213 | * If the dimension of the matrix is dynamic and larger than 32, heap memory may 214 | * be allocated as buffer space for the computation. 215 | * 216 | * If it is desired that no heap memory be allocated, consider using `permutation_sign()`, 217 | * which is equal to the permutation's determinant, and uses the permutation map 218 | * (destructively) as a buffer for the calculation. 219 | * 220 | * @param m The permutation matrix whose determinant is to be computed. 221 | */ 222 | template 223 | inline index_t det(const PermutationMatrix& m) { 224 | index_t n = m.rows(); 225 | SmallStorage 32 ? N : 32)> p(m.getRowSources(), n); 226 | return permutation_sign(p.get(), n); 227 | } 228 | 229 | /// @} // ingroup matrix 230 | 231 | } // namespace geom 232 | -------------------------------------------------------------------------------- /geomc/linalg/mtxdetail/MatrixLayout.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace geom { 7 | 8 | 9 | template 10 | struct TransposeIterator : 11 | public boost::iterator_facade 12 | < 13 | TransposeIterator, // self_t 14 | T, // pointed-to type 15 | std::random_access_iterator_tag, // implemented concepts 16 | // ↓ elem type 17 | typename std::conditional::type 18 | > 19 | { 20 | 21 | typedef typename std::conditional::type pointer_t; 22 | typedef typename std::conditional::type ref_t; 23 | 24 | const pointer_t base; 25 | const index_t major; // # elements along major (consecutive) axis 26 | const index_t minor; // # elements along minor axis 27 | index_t i; // ordinal of this iterator 28 | pointer_t p; // pointed memory location 29 | 30 | /* 31 | Numbers increase with memory address: 32 | major--> minor 33 | 0 1 2 3 4 | 34 | 5 6 7 8 9 v 35 | 10 11 12 13 14 36 | XX <-- "end" 37 | */ 38 | 39 | TransposeIterator(pointer_t base, index_t major, index_t minor, index_t i=0): 40 | base(base), 41 | major(major), 42 | minor(minor), 43 | i(i), 44 | p(offs()) {} 45 | 46 | // compute the new memory location 47 | inline pointer_t offs() const { 48 | // handle "off-end" correctly: 49 | index_t s = major * minor; 50 | index_t j = i % s; 51 | index_t k = i / s; 52 | // compute transposed offset: 53 | return base + (j % minor) * major + (j / minor) + k * s; 54 | } 55 | 56 | inline bool equal(const T* other) const { 57 | return this->p == other; 58 | } 59 | 60 | template 61 | inline bool equal(const TransposeIterator& other) const { 62 | return this->p == other.p; 63 | } 64 | 65 | inline void increment() { 66 | this->i += 1; 67 | this->p = offs(); 68 | } 69 | 70 | inline void decrement() { 71 | this->i -= 1; 72 | this->p = offs(); 73 | } 74 | 75 | inline void advance(index_t dx) { 76 | this->i += dx; 77 | this->p = offs(); 78 | } 79 | 80 | inline ref_t dereference() const { 81 | return *p; 82 | } 83 | 84 | template 85 | inline index_t distance_to(const TransposeIterator& other) const { 86 | // note the symmetry: this is the same as offs(), but major & minor are swapped. 87 | // that scans: to invert a transpose, transpose again! 88 | index_t s = major * minor; 89 | index_t q = other.p - base; 90 | index_t j = q % s; 91 | index_t k = q / s; 92 | index_t o_i = (j % major) * minor + (j / major) + k * s; 93 | 94 | return o_i - i; 95 | } 96 | 97 | }; 98 | 99 | 100 | template 101 | class FlatMatrixLayout { 102 | public: 103 | 104 | typedef const T* const_row_iterator; 105 | typedef TransposeIterator const_col_iterator; 106 | typedef T* row_iterator; 107 | typedef TransposeIterator col_iterator; 108 | 109 | static inline index_t index(index_t r, index_t c, index_t rows, index_t cols) { 110 | return r * cols + c; 111 | } 112 | 113 | template 114 | static inline typename std::conditional::type 115 | row( 116 | typename std::conditional::type base, 117 | index_t r, 118 | index_t rows, 119 | index_t cols) 120 | { 121 | return base + r * cols; 122 | } 123 | 124 | template 125 | static inline TransposeIterator col( 126 | typename std::conditional::type base, 127 | index_t c, 128 | index_t rows, 129 | index_t cols) 130 | 131 | { 132 | return TransposeIterator(base, cols, rows, c * rows); 133 | } 134 | 135 | }; 136 | 137 | 138 | template 139 | class FlatMatrixLayout { 140 | public: 141 | 142 | typedef TransposeIterator const_row_iterator; 143 | typedef const T* const_col_iterator; 144 | typedef TransposeIterator row_iterator; 145 | typedef T* col_iterator; 146 | 147 | static inline index_t index(index_t r, index_t c, index_t rows, index_t cols) { 148 | return c * rows + r; 149 | } 150 | 151 | template 152 | static inline typename std::conditional::type 153 | col( 154 | typename std::conditional::type base, 155 | index_t c, 156 | index_t rows, 157 | index_t cols) 158 | { 159 | return base + c * rows; 160 | } 161 | 162 | template 163 | static inline TransposeIterator 164 | row( 165 | typename std::conditional::type base, 166 | index_t r, 167 | index_t rows, 168 | index_t cols) 169 | { 170 | return TransposeIterator(base, rows, cols, r * cols); 171 | } 172 | 173 | }; 174 | 175 | } // namespace geom 176 | -------------------------------------------------------------------------------- /geomc/linalg/mtxtypes/DiagMatrix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * DiagMatrix.h 5 | * 6 | * Created on: Apr 28, 2013 7 | * Author: tbabb 8 | */ 9 | 10 | #include 11 | 12 | namespace geom { 13 | 14 | namespace detail { 15 | 16 | // const T because m[r][c].mutate() does not make sense. m[r][c] = X is allowed, however. 17 | template 18 | struct _ImplMtxReftype,T> { 19 | typedef detail::MtxAssignmentProxy, const T> reference; 20 | }; 21 | 22 | }; // end namespace detail 23 | 24 | /** @ingroup matrix 25 | * @brief A matrix with nonzero elements only along the main diagonal. 26 | * 27 | * @tparam T Element type. 28 | * @tparam M Row dimension. 29 | * @tparam N Column dimension. 30 | * 31 | * Storage for `DiagMatrix`es is O(n), and operations with diagonal matrices 32 | * usually benefit from the O(n) algorithmic improvement associated with their 33 | * lower dimension. 34 | */ 35 | template 36 | class DiagMatrix : public detail::MatrixBase > { 37 | 38 | index_t diag; 39 | #ifndef PARSING_DOXYGEN 40 | // this shit confuses doxygen because it am stoopid 41 | UniqueStorage data; 42 | #endif 43 | typename Dimension::storage_t n_rows; 44 | typename Dimension::storage_t n_cols; 45 | 46 | public: 47 | 48 | typedef typename detail::_ImplMtxReftype,T>::reference reference; 49 | 50 | using detail::MatrixBase >::operator(); 51 | 52 | /** 53 | * Construct a new diagonal matrix. 54 | * @param nrows Number of rows (ignored / not required if statically sized). 55 | * @param ncols Number of columns (ignored / not required if statically sized). 56 | */ 57 | #ifdef PARSING_DOXYGEN 58 | explicit DiagMatrix(index_t nrows, index_t ncols) {} 59 | #else 60 | explicit DiagMatrix(index_t nrows=detail::DefinedIf::value, 61 | index_t ncols=detail::DefinedIf::value) : 62 | diag(nrows < ncols ? nrows : ncols), 63 | data(diag) { 64 | Dimension::set(n_rows, nrows); 65 | Dimension::set(n_cols, ncols); 66 | set_identity(); 67 | } 68 | #endif 69 | 70 | /** 71 | * Construct a new `n` x `n` diagonal matrix, with diagonal elements copied 72 | * from `src`. 73 | * @param src Array containing diagonal elements. 74 | * @param n Number of elements in `src`. Ignored / not required if statically sized. 75 | */ 76 | #ifdef PARSING_DOXYGEN 77 | DiagMatrix(const T src[], index_t n) {} 78 | #else 79 | DiagMatrix(const T src[], 80 | index_t n=detail::DefinedIf::value) : 81 | diag(n), 82 | data(diag) { 83 | Dimension::set(n_rows, n); 84 | Dimension::set(n_cols, n); 85 | std::copy(src, src + n, data.get()); 86 | } 87 | #endif 88 | 89 | inline index_t rows() const { 90 | return Dimension::get(n_rows); 91 | } 92 | 93 | inline index_t cols() const { 94 | return Dimension::get(n_cols); 95 | } 96 | 97 | inline T operator()(index_t r, index_t c) const { 98 | #ifdef GEOMC_MTX_CHECK_BOUNDS 99 | if (r < 0 or r >= rows() or c < 0 or c >= cols()) { 100 | throw std::out_of_range("matrix coordinates"); 101 | } 102 | #endif 103 | return (r == c) ? data.get()[r] : (T(0)); 104 | } 105 | 106 | void set_identity() { 107 | std::fill(data.get(), data.get() + diag, 1); 108 | } 109 | 110 | inline void set_zero() { 111 | std::fill(data.get(), data.get() + diag, 0); 112 | } 113 | 114 | /** 115 | * @return An iterator over the elements of the main diagonal (those with 116 | * matching row and column coordinates), pointing at element `(0, 0)`. 117 | */ 118 | T* diagonal_begin() { 119 | return data.get(); 120 | } 121 | 122 | /** 123 | * @return A read-only iterator over the elements of the main diagonal (those with 124 | * matching row and column coordinates), pointing at element `(0, 0)`. 125 | */ 126 | const T* diagonal_begin() const { 127 | return data.get(); 128 | } 129 | 130 | /** 131 | * @return An iterator over the elements of the main diagonal (those with 132 | * matching row and column coordinates), pointing at the last diagonal element. 133 | */ 134 | T* diagonal_end() { 135 | return data.get() + diag; 136 | } 137 | 138 | /** 139 | * @return A read-only iterator over the elements of the main diagonal (those with 140 | * matching row and column coordinates), pointing at the last diagonal element. 141 | */ 142 | const T* diagonal_end() const { 143 | return data.get() + diag; 144 | } 145 | 146 | /** 147 | * Return the `i`th diagonal element. 148 | */ 149 | const T& diagonal(index_t i) const { 150 | return data.get()[i]; 151 | } 152 | 153 | /** 154 | * Return a reference to the `i`th diagonal element. 155 | */ 156 | T& diagonal(index_t i) { 157 | return data.get()[i]; 158 | } 159 | 160 | inline void get_storage_tokens(storage_token_t* buf) const { 161 | const T* p = data.get(); 162 | *buf = {p, p + rows()}; 163 | } 164 | 165 | constexpr index_t storage_token_count() const { 166 | return 1; 167 | } 168 | }; 169 | 170 | 171 | }; // end namespace geom 172 | -------------------------------------------------------------------------------- /geomc/linalg/mtxtypes/MatrixHandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * MatrixHandle.h 4 | * 5 | * Created on: Apr 28, 2013 6 | * Author: tbabb 7 | */ 8 | 9 | #include 10 | 11 | namespace geom { 12 | 13 | namespace detail { 14 | 15 | 16 | // const T because m[r][c].mutate() may not make sense. m[r][c] = X is allowed, however. 17 | template 18 | struct _ImplMtxReftype, T> { 19 | typedef detail::MtxAssignmentProxy, const T> reference; 20 | }; 21 | 22 | // storage count is dynamic 23 | template 24 | struct _ImplStorageObjCount > { 25 | const static index_t count; 26 | }; 27 | 28 | template 29 | const index_t _ImplStorageObjCount >::count = DYNAMIC_DIM; 30 | 31 | 32 | }; // end namespace detail 33 | 34 | /** @ingroup matrix 35 | * @brief A generic matrix class which can hold references to all other 36 | * matrix types. 37 | */ 38 | template 39 | class MatrixHandle : public detail::WriteableMatrixBase > { 40 | public: 41 | typedef typename detail::_ImplMtxReftype,T>::reference reference; 42 | 43 | using detail::WriteableMatrixBase >::get; 44 | 45 | MatrixHandle() {} 46 | virtual ~MatrixHandle() {} 47 | 48 | //////////// methods //////////// 49 | 50 | virtual index_t rows() const = 0; 51 | virtual index_t cols() const = 0; 52 | virtual T operator()(index_t r, index_t c) const = 0; 53 | virtual reference set(index_t r, index_t c, T val) = 0; 54 | virtual void set_identity() = 0; 55 | virtual void set_zero() = 0; 56 | 57 | virtual void get_storage_tokens(storage_token_t* buf) const = 0; 58 | virtual index_t storage_token_count() const = 0; 59 | 60 | }; 61 | 62 | template 63 | class MatrixWrapper : public MatrixHandle { 64 | public: 65 | 66 | typedef typename M::elem_t elem_t; 67 | typedef typename MatrixHandle::reference reference; 68 | 69 | using MatrixHandle::get; 70 | 71 | M *m; 72 | MatrixWrapper(M *m):m(m) {} 73 | virtual ~MatrixWrapper() {} 74 | 75 | //////////// methods //////////// 76 | 77 | virtual elem_t operator()(index_t r, index_t c) const { 78 | return (*m)(r, c); 79 | } 80 | 81 | virtual reference set(index_t r, index_t c, elem_t val) { 82 | m->set(r, c, val); 83 | return reference(this,r,c); 84 | } 85 | 86 | virtual void set_identity() { 87 | m->set_identity(); 88 | } 89 | 90 | virtual void set_zero() { 91 | m->set_zero(); 92 | } 93 | 94 | virtual index_t rows() const { 95 | return m->rows(); 96 | } 97 | 98 | virtual index_t cols() const { 99 | return m->cols(); 100 | } 101 | 102 | virtual void get_storage_tokens(storage_token_t* buf) const { 103 | m->get_storage_tokens(buf); 104 | } 105 | 106 | virtual index_t storage_token_count() const { 107 | return m->storage_token_count(); 108 | } 109 | }; 110 | 111 | } // end namespace geom 112 | -------------------------------------------------------------------------------- /geomc/linalg/vecdetail/Vec2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Vec2.h 5 | * 6 | * Created on: May 9, 2009 7 | * Author: Tim Babb 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | namespace geom { 14 | 15 | /** @ingroup linalg 16 | * @brief 2D specialization of vector class. 17 | * 18 | * `Vec`'s elements may be accessed under these equivalent naming schemes: 19 | * 20 | * v.{x,y} // conventional Euclidean coordinate names 21 | * v.{s,t} // conventional parameterization coordinate names 22 | * v.{row,col} // matrix coordinate names 23 | * 24 | * with the latter scheme intended for use as matrix coordinates. `x`, `s`, and 25 | * `row` all refer to the same element. 26 | * 27 | * Take special note that, in accordance with convention, `row` refers to the 28 | * vertical position of a matrix element, despite being the first coordinate. 29 | * This means that `row`, a vertical coordinate, aliases `x`, a traditionally 30 | * horizontal coordinate. For this reason it is **inadviseable to interchange 31 | * usage** of the "matrix coordinate" and "Euclidean" naming schemes. 32 | */ 33 | template class Vec : public detail::VecCommon< T, 2, Vec > { 34 | 35 | public: 36 | 37 | // to prevent c++20 ambiguity warnings: 38 | using detail::VecCommon>::operator==; 39 | 40 | /// @brief (1, 0) vector constant 41 | static const Vec X_AXIS; 42 | /// @brief (0, 1) vector constant 43 | static const Vec Y_AXIS; 44 | 45 | /*===========================* 46 | * Structors * 47 | *===========================*/ 48 | 49 | /// Construct vector with both elements set to 0. 50 | constexpr Vec():detail::VecCommon< T, 2, Vec >() {} 51 | 52 | /// Construct a vector with both elements set to `a`. 53 | constexpr Vec(T a):detail::VecCommon< T, 2, Vec >(a) {} 54 | 55 | /// Construct a vector with elements `(x, y)`. 56 | constexpr Vec(T x, T y) { 57 | detail::VecBase::x = x; 58 | detail::VecBase::y = y; 59 | } 60 | 61 | /// Construct a vector with elements copied from the 2-element array `v`. 62 | constexpr Vec(const T v[2]):detail::VecCommon< T, 2, Vec >(v){} 63 | 64 | /*===========================* 65 | * Convenience Arithmetic * 66 | *===========================*/ 67 | 68 | //do not hide these; we want to overload them: 69 | using detail::VecCommon< T, 2, Vec >::add; 70 | using detail::VecCommon< T, 2, Vec >::sub; 71 | using detail::VecCommon< T, 2, Vec >::scale; 72 | using detail::VecCommon< T, 2, Vec >::dot; 73 | 74 | /// Addition. Convenience function with separate args for `x` and `y`. 75 | inline Vec add(T dx, T dy) const { 76 | return Vec(detail::VecBase::x + dx, detail::VecBase::y + dy); 77 | } 78 | 79 | /// Subtraction. Convenience function with separate args for `x` and `y`. 80 | inline Vec sub(T dx, T dy) const { 81 | return Vec(detail::VecBase::x - dx, detail::VecBase::y - dy); 82 | } 83 | 84 | /// Scalar multiplication. Convenience function with separate args for `x` and `y`. 85 | inline Vec scale(T sx, T sy) const { 86 | return Vec(detail::VecBase::x*sx, detail::VecBase::y*sy); 87 | } 88 | 89 | /// Dot product with the vector `(x1, y1)`. 90 | inline T dot(T x1, T y1) const { 91 | return detail::VecBase::x*x1 + detail::VecBase::y*y1; 92 | } 93 | 94 | /// @return A new vector with the X component reflected. 95 | inline Vec reflected_x() const { 96 | return Vec(-detail::VecBase::x, detail::VecBase::y); 97 | } 98 | 99 | /// @return A new vector with the Y component reflected. 100 | inline Vec reflected_y() const { 101 | return Vec(detail::VecBase::x, -detail::VecBase::y); 102 | } 103 | 104 | /// @return `true` if no elements are an infinity or `NaN`. 105 | bool is_finite_real() const { 106 | return is_real(detail::VecBase::x) and is_real(detail::VecBase::y); 107 | } 108 | 109 | /*===========================* 110 | * 2D Geometry * 111 | *===========================*/ 112 | 113 | /** 114 | * @return A vector perpendicular to `this`, created by a rotation of 115 | * 90 degrees counterclockwise. 116 | */ 117 | 118 | Vec left_perpendicular() const { 119 | return Vec(-this->y, this->x); 120 | } 121 | 122 | /** 123 | * @return A vector perpendicular to `this`, created by a rotation of 124 | * 90 degrees clockwise. 125 | */ 126 | Vec right_perpendicular() const { 127 | return Vec(detail::VecBase::y, -detail::VecBase::x); 128 | } 129 | 130 | /** 131 | * @return A vector rotated counterclockwise by the angle `radians`. 132 | * @param radians Rotation angle in radians 133 | */ 134 | Vec rotated(T radians) const { 135 | T sint = std::sin(radians); 136 | T cost = std::cos(radians); 137 | return Vec(cost * this->x - sint * this->y, 138 | sint * this->x + cost * this->y); 139 | } 140 | 141 | /** 142 | * @return A vector rotated counterclockwise by the angle `radians` about 143 | * the point `center`. 144 | * @param center Center of rotation. 145 | * @param radians Angle of rotation. 146 | */ 147 | Vec rotate(Vec center, T radians) const { 148 | return center + (((*this) - center).rotate(radians)); 149 | } 150 | 151 | /** 152 | * @return The polar coordinates `(r, angle)` for this cartesian point, 153 | * with `angle` in radians. 154 | */ 155 | inline Vec to_polar() const { 156 | return Vec(this->mag(), angle()); 157 | } 158 | 159 | /** 160 | * @return The cartesian `(x, y)` coordinates of this point interpreted 161 | * as polar coordinates, with `this[0] = r` and `this[1] = radians`. 162 | */ 163 | inline Vec from_polar() const { 164 | Vec v; 165 | v.x = detail::VecBase::y * std::cos(detail::VecBase::x); 166 | v.y = detail::VecBase::y * std::sin(detail::VecBase::x); 167 | return v; 168 | } 169 | 170 | /** 171 | * @return The angle in radians to this vector from the x-axis, between 0 172 | * and `2 * pi`. 173 | */ 174 | inline T angle() const { 175 | T theta = (T)(std::atan2(detail::VecBase::y,detail::VecBase::x)); 176 | if (theta < 0) theta += 2 * M_PI; 177 | return theta; 178 | } 179 | 180 | }; //end Vec2 definition 181 | 182 | template const Vec Vec::X_AXIS = Vec(1,0); 183 | template const Vec Vec::Y_AXIS = Vec(0,1); 184 | 185 | } //end namespace geom 186 | -------------------------------------------------------------------------------- /geomc/linalg/vecdetail/Vec4.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Vec4.h 4 | * 5 | * Created on: Feb 22, 2009 6 | * Author: Tim Babb 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace geom { 14 | 15 | /*===========================* 16 | * Global Functions * 17 | *===========================*/ 18 | 19 | template const Vec from_argb(int aRGB) { 20 | T a = ((aRGB & 0xff000000) >> 24) / detail::_RGBChannelConversion::factor; 21 | T r = ((aRGB & 0x00ff0000) >> 16) / detail::_RGBChannelConversion::factor; 22 | T g = ((aRGB & 0x0000ff00) >> 8) / detail::_RGBChannelConversion::factor; 23 | T b = (aRGB & 0x000000ff) / detail::_RGBChannelConversion::factor; 24 | return Vec(r,g,b,a); 25 | } 26 | 27 | template const Vec from_rgba(int rgba) { 28 | T r = ((rgba & 0xff000000) >> 24) / detail::_RGBChannelConversion::factor; 29 | T g = ((rgba & 0x00ff0000) >> 16) / detail::_RGBChannelConversion::factor; 30 | T b = ((rgba & 0x0000ff00) >> 8) / detail::_RGBChannelConversion::factor; 31 | T a = (rgba & 0x000000ff) / detail::_RGBChannelConversion::factor; 32 | return Vec(r,g,b,a); 33 | } 34 | 35 | /** 36 | * @brief Composite two non-premultiplied colors over each other. 37 | */ 38 | template 39 | const Vec composite_over(Vec fg, Vec bg) { 40 | Vec c_fg = fg.template resized<3>(); 41 | Vec c_bg = bg.template resized<3>(); 42 | T a_fg = fg.w; 43 | T a_bg = bg.w; 44 | T a_out = a_fg + (1 - a_fg) * a_bg; 45 | if (a_out == 0) { 46 | return fg + bg; 47 | } 48 | return { 49 | (c_fg * a_fg + c_bg * a_bg * (1 - a_fg)) / a_out, 50 | a_out 51 | }; 52 | } 53 | 54 | /*===========================* 55 | * Vector Class * 56 | *===========================*/ 57 | 58 | /** @ingroup linalg 59 | * @brief 4D specialization of vector class. 60 | * 61 | * `Vec`'s elements may be accessed under these equivalent naming schemes: 62 | * 63 | * v.{x,y,z,w} // conventional Euclidean coordinate names 64 | * v.{s,t,u,v} // conventional parameterization coordinate names 65 | * v.{r,g,b,a} // conventional color tuple coordinate names 66 | * 67 | * with `x`, `s`, and `r` all referring to the same element. 68 | */ 69 | template class Vec : public detail::VecCommon< T, 4, Vec > { 70 | public: 71 | 72 | /// @brief (1, 0, 0, 0) constant 73 | static const Vec X_AXIS; 74 | /// @brief (0, 1, 0, 0) constant 75 | static const Vec Y_AXIS; 76 | /// @brief (0, 0, 1, 0) constant 77 | static const Vec Z_AXIS; 78 | /// @brief (0, 0, 0, 1) constant 79 | static const Vec W_AXIS; 80 | 81 | /*===========================* 82 | * Structors * 83 | *===========================*/ 84 | 85 | 86 | /// Construct vector with all elements set to 0. 87 | constexpr Vec():detail::VecCommon>() {} 88 | 89 | /// Construct a vector with elements `(x, y, z, w)`. 90 | constexpr Vec(T x, T y, T z, T w):detail::VecCommon>{x,y,z,w} {} 91 | 92 | /// Construct a vector with elements copied from the 4-element array `v`. 93 | constexpr Vec(const T v[4]):detail::VecCommon>(v) {}; 94 | 95 | /** 96 | * @brief Construct a vector with `(x, y, z)` taken from the 3D vector `xyz`, 97 | * and `w` as the final element. 98 | */ 99 | template 100 | constexpr Vec(Vec xyz, T w):detail::VecCommon>{ 101 | xyz.x, xyz.y, xyz.z, w 102 | } {} 103 | 104 | template 105 | constexpr Vec(Vec xy, T z, T w):detail::VecCommon>{ 106 | xy.x, xy.y, z, w 107 | } {} 108 | 109 | /// Construct a vector with all elements set to `a`. 110 | constexpr Vec(T a):detail::VecCommon>(a) {} 111 | 112 | /*===========================* 113 | * Arithmetic * 114 | *===========================*/ 115 | 116 | // to prevent c++20 ambiguity warnings: 117 | using detail::VecCommon>::operator==; 118 | 119 | using detail::VecCommon< T, 4, Vec >::add; 120 | using detail::VecCommon< T, 4, Vec >::sub; 121 | using detail::VecCommon< T, 4, Vec >::scale; 122 | using detail::VecCommon< T, 4, Vec >::dot; 123 | 124 | /// @return `(dx, dy, dz, dw)` added with `this` 125 | Vec add(T dx, T dy, T dz, T dw) const { 126 | return Vec(detail::VecBase::x+dx, 127 | detail::VecBase::y+dy, 128 | detail::VecBase::z+dz, 129 | detail::VecBase::w+dw); 130 | } 131 | 132 | /// @return `(dx, dy, dz, dw)` subtracted from `this` 133 | Vec sub(T dx, T dy, T dz, T dw) const { 134 | return Vec(detail::VecBase::x-dx, 135 | detail::VecBase::y-dy, 136 | detail::VecBase::z-dz, 137 | detail::VecBase::w-dw); 138 | } 139 | 140 | /// @return Element-wise scaled copy 141 | Vec scale(T a, T b, T c, T d) const { 142 | return Vec(a*detail::VecBase::x, 143 | b*detail::VecBase::y, 144 | c*detail::VecBase::z, 145 | d*detail::VecBase::w); 146 | } 147 | 148 | /// @return Dot product of `this` with the vector `(xx, yy, zz, ww)` 149 | T dot(T xx, T yy, T zz, T ww) const { 150 | return (detail::VecBase::x * xx) + 151 | (detail::VecBase::y * yy) + 152 | (detail::VecBase::z * zz) + 153 | (detail::VecBase::w * ww); 154 | } 155 | 156 | }; //end Vec4 definition 157 | 158 | template const Vec Vec::X_AXIS = Vec(1,0,0,0); 159 | template const Vec Vec::Y_AXIS = Vec(0,1,0,0); 160 | template const Vec Vec::Z_AXIS = Vec(0,0,1,0); 161 | template const Vec Vec::W_AXIS = Vec(0,0,0,1); 162 | 163 | } //end namespace geom 164 | -------------------------------------------------------------------------------- /geomc/linalg/vecdetail/VecStd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | /* 5 | * File: VecStd.h 6 | * Author: tbabb 7 | * 8 | * Created on June 24, 2014, 2:43 AM 9 | */ 10 | 11 | namespace std { 12 | 13 | 14 | // these make vectors more interchangeable with bare types: 15 | /** 16 | * Element-wise maximum. 17 | * @related geom::Vec 18 | */ 19 | template 20 | inline geom::Vec max(const geom::Vec &a, const geom::Vec &b) { 21 | return a.max(b); 22 | } 23 | 24 | /** 25 | * Element-wise minimum. 26 | * @related geom::Vec 27 | */ 28 | template 29 | inline geom::Vec min(const geom::Vec &a, const geom::Vec &b) { 30 | return a.min(b); 31 | } 32 | 33 | /** 34 | * Element-wise absolute value. 35 | * @related geom::Vec 36 | */ 37 | template 38 | inline geom::Vec abs(const geom::Vec &v) { 39 | return v.abs(); 40 | } 41 | 42 | /** 43 | * Element-wise floor. 44 | * @related geom::Vec 45 | */ 46 | template 47 | inline geom::Vec floor(const geom::Vec &v) { 48 | return v.floor(); 49 | } 50 | 51 | /** 52 | * Element-wise ceiling. 53 | * @related geom::Vec 54 | */ 55 | template 56 | inline geom::Vec ceil(const geom::Vec &v) { 57 | return v.ceil(); 58 | } 59 | 60 | /** 61 | * Element-wise square root. 62 | * @related geom::Vec 63 | */ 64 | template 65 | inline geom::Vec sqrt(const geom::Vec &v) { 66 | geom::Vec o; 67 | for (index_t i = 0; i < N; i++) { 68 | o[i] = std::sqrt(v[i]); 69 | } 70 | return o; 71 | } 72 | 73 | /** 74 | * Element-wise sine. 75 | * @related geom::Vec 76 | */ 77 | template 78 | inline geom::Vec sin(const geom::Vec &v) { 79 | geom::Vec o; 80 | for (index_t i = 0; i < N; i++) { 81 | o[i] = std::sin(v[i]); 82 | } 83 | return o; 84 | } 85 | 86 | /** 87 | * Element-wise cosine. 88 | * @related geom::Vec 89 | */ 90 | template 91 | inline geom::Vec cos(const geom::Vec &v) { 92 | geom::Vec o; 93 | for (index_t i = 0; i < N; i++) { 94 | o[i] = std::cos(v[i]); 95 | } 96 | return o; 97 | } 98 | 99 | /** 100 | * Element-wise tangent. 101 | * @related geom::Vec 102 | */ 103 | template 104 | inline geom::Vec tan(const geom::Vec &v) { 105 | geom::Vec o; 106 | for (index_t i = 0; i < N; i++) { 107 | o[i] = std::tan(v[i]); 108 | } 109 | return o; 110 | } 111 | 112 | /** 113 | * Element-wise exponentiation (evi). 114 | * @related geom::Vec 115 | */ 116 | template 117 | inline geom::Vec exp(const geom::Vec &v) { 118 | geom::Vec o; 119 | for (index_t i = 0; i < N; i++) { 120 | o[i] = std::exp(v[i]); 121 | } 122 | return o; 123 | } 124 | 125 | /** 126 | * Element-wise natural log. 127 | * @related geom::Vec 128 | */ 129 | template 130 | inline geom::Vec log(const geom::Vec &v) { 131 | geom::Vec o; 132 | for (index_t i = 0; i < N; i++) { 133 | o[i] = std::log(v[i]); 134 | } 135 | return o; 136 | } 137 | 138 | template 139 | inline geom::Vec pow(const geom::Vec &v, const geom::Vec &e) { 140 | geom::Vec o; 141 | for (index_t i = 0; i < N; i++) { 142 | o[i] = std::pow(v[i], e[i]); 143 | } 144 | return o; 145 | } 146 | 147 | template 148 | inline geom::Vec pow(const geom::Vec &v, T e) { 149 | geom::Vec o; 150 | for (index_t i = 0; i < N; i++) { 151 | o[i] = std::pow(v[i], e); 152 | } 153 | return o; 154 | } 155 | 156 | /// @} //ingroup linalg 157 | 158 | } // namespace std 159 | -------------------------------------------------------------------------------- /geomc/random/DenseDistribution.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // todo: we should use high order bits (higher quality) for the exponent 8 | // and low order bits for the mantissa. exponent is more important for 9 | // the distribution of the result. 10 | 11 | namespace geom { 12 | 13 | /** 14 | * @addtogroup random 15 | * @{ 16 | */ 17 | 18 | /** 19 | * @brief A random number generator that produces uniformly-distributed values. 20 | * 21 | * Many uniform random number generators only generate a small subset of the 22 | * representable floating point values. This generator produces all possible 23 | * values in the range [0,1] with equal probability by directly constructing 24 | * the mantissa and exponent of a floating point number. 25 | * 26 | * Pharr discusses the method used here: 27 | * https://pharr.org/matt/blog/2022/03/05/sampling-fp-unit-interval 28 | * 29 | * Non-float types fall back to std::uniform_real_distribution. 30 | */ 31 | template 32 | struct DenseUniformDistribution : public std::uniform_real_distribution {}; 33 | 34 | /// @brief Dense uniform distribution specialization for float. 35 | template <> 36 | struct DenseUniformDistribution { 37 | private: 38 | Rect _range; 39 | 40 | public: 41 | using param_type = Rect; 42 | using result_type = float; 43 | 44 | constexpr DenseUniformDistribution(): 45 | _range(0.f, 1.f) {} 46 | constexpr DenseUniformDistribution(Rect range): 47 | _range(range) {} 48 | constexpr DenseUniformDistribution(float a, float b): 49 | _range(Rect::from_corners(a, b)) {} 50 | 51 | template 52 | float operator()(Generator& g) const { 53 | union FPBox { 54 | float f; 55 | uint32_t i; 56 | }; 57 | 58 | // without IEEE754, our bit twiddling will not work. 59 | // generally this will only fail for "exotic" systems: 60 | static_assert(std::numeric_limits::is_iec559, "floating point must be ieee754"); 61 | 62 | auto randbits = std::uniform_int_distribution(); 63 | constexpr size_t mantissa_bits = 23; 64 | size_t scale_bits = 64 - mantissa_bits; 65 | uint64_t r = randbits(g); 66 | int32_t exp = 126; 67 | uint32_t mant = r >> scale_bits; 68 | uint32_t z; 69 | while (true) { 70 | z = std::countr_zero(r); 71 | exp = std::max(exp - z, 0); 72 | if (z < scale_bits or exp == 0) [[likely]] break; 73 | // we will get here only with probability 2^-40 74 | scale_bits = 64; 75 | r = randbits(g); 76 | } 77 | if (mant == 0) [[unlikely]] { 78 | // avoid biasing the result toward zero. above, it is not 79 | // possible to generate 1.0. with probability 1/2, bump the exponent. 80 | exp += ((r >> (scale_bits - 1)) & 1); 81 | } 82 | FPBox ans; 83 | ans.i = (exp << mantissa_bits) | mant; // combine exp and mantissa 84 | return _range.remap(ans.f); 85 | } 86 | 87 | void reset() {} 88 | Rect param() const { return _range; } 89 | void param(Rect range) { _range = range; } 90 | 91 | float min() const { return _range.lo; } 92 | float max() const { return _range.hi; } 93 | 94 | float a() const { return _range.lo; } 95 | float b() const { return _range.hi; } 96 | 97 | bool operator==(const DenseUniformDistribution& other) const { 98 | return _range == other._range; 99 | } 100 | }; 101 | 102 | /// @brief Dense uniform distribution specialization for double. 103 | template <> 104 | struct DenseUniformDistribution { 105 | private: 106 | Rect _range; 107 | 108 | public: 109 | using param_type = Rect; 110 | using result_type = double; 111 | 112 | constexpr DenseUniformDistribution(): 113 | _range(0., 1.) {} 114 | constexpr DenseUniformDistribution(Rect range): 115 | _range(range) {} 116 | constexpr DenseUniformDistribution(double a, double b): 117 | _range(Rect::from_corners(a, b)) {} 118 | 119 | template 120 | double operator()(Generator& g) const { 121 | union FPBox { 122 | double d; 123 | uint64_t l; 124 | }; 125 | 126 | // without IEEE754, our bit twiddling will not work. 127 | // generally this will only fail for "exotic" systems: 128 | static_assert(std::numeric_limits::is_iec559, "floating point must be ieee754"); 129 | 130 | auto randbits = std::uniform_int_distribution(); 131 | constexpr size_t mantissa_bits = 52; 132 | size_t scale_bits = 64 - mantissa_bits; 133 | uint64_t r = randbits(g); 134 | int32_t exp = 1022; 135 | uint64_t mant = r >> scale_bits; 136 | uint32_t z; 137 | while (true) { 138 | z = std::countr_zero(r); 139 | exp = std::max(exp - z, 0); 140 | if (z < scale_bits or exp == 0) [[likely]] break; 141 | // we get here with probablity 2^-11 142 | scale_bits = 64; 143 | r = randbits(g); 144 | } 145 | if (mant == 0) [[unlikely]] { 146 | // avoid biasing the result toward zero. above, it is not 147 | // possible to generate 1.0. with probability 1/2, bump the exponent. 148 | exp += ((r >> (scale_bits - 1)) & 1); 149 | } 150 | FPBox ans; 151 | ans.l = (((uint64_t)exp) << mantissa_bits) | mant; // combine exp and mantissa 152 | return _range.remap(ans.d); 153 | } 154 | 155 | void reset() {} 156 | Rect param() const { return _range; } 157 | void param(Rect range) { _range = range; } 158 | 159 | double min() const { return _range.lo; } 160 | double max() const { return _range.hi; } 161 | 162 | double a() const { return _range.lo; } 163 | double b() const { return _range.hi; } 164 | 165 | bool operator==(const DenseUniformDistribution& other) const { 166 | return _range == other._range; 167 | } 168 | }; 169 | 170 | 171 | /// @brief Dense uniform distribution specialization for Duals. 172 | template 173 | struct DenseUniformDistribution> { 174 | private: 175 | DenseUniformDistribution _d; 176 | Rect,1> _range; 177 | public: 178 | using dual_t = Dual; 179 | using param_type = Rect; 180 | using result_type = dual_t; 181 | 182 | constexpr DenseUniformDistribution(): 183 | _range(0., 1.) {} 184 | constexpr DenseUniformDistribution(param_type range): 185 | _range(range) {} 186 | constexpr DenseUniformDistribution(T a, T b): 187 | _range(Rect::from_corners(a, b)) {} 188 | 189 | template 190 | Dual operator()(Generator& g) const { 191 | dual_t d = _d(g); 192 | return _range.remap(d); 193 | } 194 | 195 | void reset() { _d.reset(); } 196 | param_type param() const { return _range; } 197 | void param(param_type range) { _range = range; } 198 | 199 | dual_t min() const { return _range.lo; } 200 | dual_t max() const { return _range.hi; } 201 | 202 | T a() const { return _range.lo.real(); } 203 | T b() const { return _range.hi.real(); } 204 | 205 | bool operator==(const DenseUniformDistribution& other) const { 206 | return _range == other._range; 207 | } 208 | }; 209 | 210 | /// @} 211 | 212 | } // namespace geom 213 | -------------------------------------------------------------------------------- /geomc/random/SampleVector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace geom { 7 | 8 | /** 9 | * @addtogroup random 10 | * @{ 11 | */ 12 | 13 | /// A default random number generator type. 14 | using DefaultLCG = std::linear_congruential_engine< 15 | // Knuth's MMIX LCG parameters 16 | uint64_t, 17 | 6364136223846793005ULL, 18 | 1442695040888963407ULL, 19 | 0 20 | >; 21 | 22 | /// Create a new random number generator with a nondeterministic seed. 23 | inline DefaultLCG create_rng() { 24 | using T = DefaultLCG::result_type; 25 | using Seeder = std::uniform_int_distribution; 26 | std::random_device rd; 27 | return DefaultLCG(Seeder{}(rd)); 28 | } 29 | 30 | /** 31 | * @brief Generate a random vector drawn from a multivariate gaussian distribution 32 | * with mean 0 and variance 1. 33 | */ 34 | template 35 | inline VecType random_gaussian(Generator& rng) { 36 | std::normal_distribution gauss(0, 1); 37 | if constexpr (N == 1) { 38 | return gauss(rng); 39 | } else { 40 | Vec v; 41 | for (index_t i = 0; i < N; ++i) { 42 | v[i] = gauss(rng); 43 | } 44 | return v; 45 | } 46 | } 47 | 48 | /** 49 | * @brief Generate a random vector with unit length. 50 | */ 51 | template 52 | inline VecType random_unit(Generator& rng) { 53 | if constexpr (N == 1) { 54 | // sample from {-1, 1} 55 | std::uniform_int_distribution coin(0, 1); 56 | return coin(rng) ? 1 : -1; 57 | } else if constexpr (N <= 3) { 58 | // rejection sampling 59 | DenseUniformDistribution unif {-1,1}; 60 | Vec p; 61 | do { 62 | // generate a point in the signed unit box 63 | for (index_t i = 0; i < N; ++i) { 64 | p[i] = unif(rng); 65 | } 66 | // if the point is outside the unit sphere, reject it and try again 67 | } while (p.mag2() > 1); 68 | return p.unit(); 69 | } else { 70 | // draw a multivariate gaussian, then project it onto the sphere 71 | std::normal_distribution gauss(0, 1); 72 | Vec p; 73 | for (index_t i = 0; i < N; ++i) { 74 | p[i] = gauss(rng); 75 | } 76 | return p.unit(); 77 | } 78 | } 79 | 80 | /// @} 81 | 82 | } // namespace geom 83 | -------------------------------------------------------------------------------- /geomc/shape/Hollow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace geom { 8 | 9 | /** 10 | * @ingroup shape 11 | * @brief Selects the boundary of a shape. 12 | * 13 | * Hollow shapes are infinitely thin. They are the boundary of a shape, but do not 14 | * contain any points. Hollow shapes can report the distance to their surface, 15 | * the normal vector at a test point, and can orthogonally project points onto 16 | * their surface. 17 | * 18 | * Hollow shapes may be a useful foundation for thick-shelled shapes using 19 | * the `Dilated` shape wrapper. 20 | */ 21 | template 22 | class Hollow: public Dimensional { 23 | public: 24 | // - hollow shapes are in general not convex 25 | // - hollow shapes do not have one ray-intersection interval 26 | using typename Dimensional::elem_t; 27 | using Dimensional::N; 28 | using T = elem_t; 29 | 30 | /// Solid shape. 31 | Shape shape; 32 | 33 | /// Wrap a default-constructed shape with the identity transform. 34 | Hollow() {} 35 | 36 | /// Wrap a shape with a similarity transform. 37 | Hollow(const Shape& shape): 38 | shape(shape) {} 39 | 40 | /// Shape equality test. 41 | bool operator==(const Hollow& other) const { 42 | return shape == other.shape; 43 | } 44 | 45 | static constexpr bool admits_cusps() { return Shape::admits_cusps(); } 46 | 47 | /** 48 | * @brief Shape-point intersection test. Always returns `false`, because 49 | * hollow shapes are infinitely thin. 50 | */ 51 | bool contains(typename Shape::point_t p) const requires RegionObject { 52 | return false; // hollow shapes are infinitely thin 53 | } 54 | 55 | /// Project a point to the surface of this shape. 56 | Vec project(Vec p) const requires ProjectableObject { 57 | return shape.project(p); 58 | } 59 | 60 | /** 61 | * @brief Nearest point on the interior of the shape to `p`; the same as 62 | * `project(p)`, since hollow shapes do not have an interior. 63 | */ 64 | Vec clip(Vec p) const requires ProjectableObject { 65 | return shape.project(p); 66 | } 67 | 68 | /// Normal vector at point `p`. 69 | Vec normal(Vec p) const requires ProjectableObject { 70 | T sign = shape.contains(p) ? -1 : 1; 71 | return sign * shape.normal(p); 72 | } 73 | 74 | /// Signed distance function. 75 | T sdf(Vec p) const requires SdfObject { 76 | return std::abs(shape.sdf(p)); 77 | } 78 | 79 | /// Compute the axis-aligned bounding box of the shape. 80 | Rect bounds() const requires BoundedObject { 81 | return shape.bounds(); 82 | } 83 | 84 | /** 85 | * @brief Measure the boundary (surface area) of the shape. 86 | * 87 | * This is the same as the boundary of the inner shape. Hollow shapes 88 | * do not have any volume, so this is their only meaningful measure. 89 | */ 90 | T measure_boundary() const requires BoundaryMeasurableObject { 91 | return shape.measure_boundary(); 92 | } 93 | 94 | }; 95 | 96 | /// @ingroup shape 97 | /// @{ 98 | 99 | /** 100 | * @brief Transform a hollow shape. 101 | * @related Hollow 102 | */ 103 | template Xf> 104 | requires Transformable 105 | inline Hollow operator*(const Xf& xf, const Hollow& s) { 106 | return Hollow(xf * s.shape); 107 | } 108 | 109 | /** 110 | * @brief Inverse transform a hollow shape. 111 | * @related Hollow 112 | */ 113 | template Xf> 114 | requires Transformable 115 | inline Hollow operator/(const Hollow& s, const Xf& xf) { 116 | return Hollow(xf / s.shape); 117 | } 118 | 119 | /** 120 | * @brief In-place transform a hollow shape. 121 | * @related Hollow 122 | */ 123 | template Xf> 124 | requires Transformable 125 | inline Hollow& operator*=(Hollow& s, const Xf& xf) { 126 | s.shape *= xf; 127 | return s; 128 | } 129 | 130 | /** 131 | * @brief In-place inverse transform a hollow shape. 132 | * @related Hollow 133 | */ 134 | template Xf> 135 | requires Transformable 136 | inline Hollow& operator/=(Hollow& s, const Xf& xf) { 137 | s.shape /= xf; 138 | return s; 139 | } 140 | 141 | /// @} 142 | 143 | template 144 | struct Digest, H> { 145 | H operator()(const geom::Hollow &s) const { 146 | H nonce = geom::truncated_constant(0xecd1792decf07dcb, 0xdd9a7673d74739b2); 147 | return geom::hash_many(nonce, s.shape); 148 | } 149 | }; 150 | 151 | #ifdef GEOMC_USE_STREAMS 152 | 153 | template 154 | std::ostream& operator<<(std::ostream& os, const Hollow& h) { 155 | os << "Hollow(" << h.shape << ")"; 156 | return os; 157 | } 158 | 159 | #endif 160 | 161 | } // namespace geom 162 | 163 | template 164 | struct std::hash> { 165 | size_t operator()(const geom::Hollow &s) const { 166 | return geom::hash, size_t>(s); 167 | } 168 | }; 169 | -------------------------------------------------------------------------------- /geomc/shape/Intersect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if DEBUG_INTERSECTION 9 | #include 10 | #endif 11 | 12 | #include 13 | 14 | 15 | namespace geom { 16 | 17 | template 18 | struct Intersector { 19 | // afaict this is never reached in 2D or 3D 20 | // (2D, 3D, 4D, ... = 10, 20, 40, ...) 21 | index_t max_iterations = 10 * (1 << (N - 2)); 22 | /// axis separating the shapes in the previous intersection test, if they overlapped. 23 | Vec separation_axis = Vec::unit_x; 24 | /// number of GJK iterations taken to produce the last intersection result. 25 | index_t iterations = 0; 26 | /// set to `true` iff the last intersection test resulted in a degenerate simplex 27 | bool was_degenerate = false; 28 | 29 | #if DEBUG_INTERSECTION 30 | FILE* debug_file = nullptr; 31 | #endif 32 | 33 | protected: 34 | // two "buffers": 35 | Simplex simplex_a; 36 | Simplex simplex_b; 37 | // front and back buffer: 38 | Simplex* cur_simplex = &simplex_a; 39 | Simplex* next_simplex = &simplex_b; 40 | 41 | public: 42 | 43 | const Simplex& simplex() const { return *cur_simplex; } 44 | 45 | bool intersects( 46 | const AnyConvex& shape_a, 47 | const AnyConvex& shape_b) 48 | { 49 | // `a` is a point on the minkowski difference 50 | Vec a = shape_a.convex_support( separation_axis) - 51 | shape_b.convex_support(-separation_axis); 52 | // `d` is the previous search direction 53 | Vec d = -a; 54 | // initialize the simplex with a single point: 55 | cur_simplex->n = 0; 56 | cur_simplex->insert(a); 57 | 58 | iterations = 0; 59 | while (true) { 60 | iterations += 1; 61 | a = shape_a.convex_support( d) - 62 | shape_b.convex_support(-d); 63 | T k = a.dot(d); 64 | if (k < 0 or a == cur_simplex->pts[cur_simplex->n - 1]) { 65 | // we tried to search as far as we could in direction `d`, 66 | // but got no closer to the origin. 67 | separation_axis = -a.project_on(d); 68 | // return whether the origin is inside the minkowski difference 69 | return k >= 0; 70 | } 71 | cur_simplex->insert(a); 72 | // project the origin onto the simplex, 73 | // putting the projection face into `next_simplex` 74 | detail::SimplexProjection proj { 75 | *cur_simplex, 76 | {}, // origin 77 | detail::ProjectionOp::CLIP, 78 | detail::SimplexFaces::SKIP_BACKFACE 79 | }; 80 | d = proj.normal_direction(); 81 | *next_simplex = proj.projected_face(); 82 | was_degenerate = proj.result.is_degenerate; 83 | 84 | #if DEBUG_INTERSECTION 85 | if (debug_file) { 86 | emit_splex_stage(debug_file, *cur_simplex, *next_simplex, a, -d); 87 | } 88 | #endif 89 | std::swap(cur_simplex, next_simplex); 90 | if (cur_simplex->n == N + 1 or d.is_zero()) { 91 | // the simplex is full, and the origin is inside it. 92 | separation_axis = d; 93 | return true; 94 | } 95 | if (iterations > max_iterations or d.mag2() == 0) { return true; } 96 | } 97 | } 98 | 99 | }; // struct Intersector 100 | 101 | 102 | template 103 | bool intersects( 104 | const AnyConvex& shape_a, 105 | const AnyConvex& shape_b) 106 | { 107 | Intersector intersector; 108 | return intersector.intersects(shape_a, shape_b); 109 | } 110 | 111 | } // namespace geom 112 | -------------------------------------------------------------------------------- /geomc/shape/Sphere.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // todo: r should be "radius" 10 | 11 | namespace geom { 12 | 13 | /** 14 | * @brief Formula for the volume of a `d` dimensional ball with radius `r`. 15 | */ 16 | template 17 | constexpr T measure_ball_interior(index_t d, T r) { 18 | if (d == 0) return 1; 19 | if (d == 1) return 2 * r; 20 | return 2 * std::numbers::pi_v * r * r * measure_ball_interior(d - 2, r) / (T) d; 21 | } 22 | 23 | /** 24 | * @brief Formula for the surface area of a `d` dimensional ball with radius `r`. 25 | */ 26 | template 27 | constexpr T measure_sphere_boundary(index_t d, T r) { 28 | constexpr T k = 2 * std::numbers::pi_v; 29 | if (d == 0) return 1; 30 | if (d == 1) return 2; 31 | if (d == 2) return k * r; 32 | return k * r * r * measure_sphere_boundary(d - 2, r) / (T) (d - 2); 33 | } 34 | 35 | /** 36 | * @ingroup shape 37 | * @brief An N-dimensional circle, sphere, or hypersphere with a filled interior. 38 | * 39 | * `Circle` is a template alias for `Sphere`. 40 | */ 41 | template 42 | class Sphere: public Dimensional { 43 | using ptype = PointType; 44 | public: 45 | using typename Dimensional::point_t; 46 | /// Center of the sphere. 47 | point_t center; 48 | /// Radius of the sphere. 49 | T radius; 50 | 51 | /** 52 | * Construct a sphere at the origin with radius 1. 53 | */ 54 | constexpr Sphere(): 55 | center((T)0), 56 | radius(1) {} 57 | 58 | /** 59 | * Construct a sphere with center at the origin, having radius `r`. 60 | * @param r Radius of spehre. 61 | */ 62 | constexpr Sphere(T r): 63 | center((T)0), 64 | radius(r) {} 65 | 66 | /** 67 | * Construct a sphere with center at the point `c`, having radius `r`. 68 | * @param c Center of sphere. 69 | * @param r Radius of spehre. 70 | */ 71 | constexpr Sphere(const point_t& c, T r): 72 | center(c), 73 | radius(r) {} 74 | 75 | static constexpr bool admits_cusps() { return false; } 76 | 77 | bool operator==(const Sphere& other) const { 78 | return center == other.center && radius == other.radius; 79 | } 80 | 81 | Rect bounds() const { 82 | point_t rvec(radius); 83 | return Rect(center - rvec, center + rvec); 84 | } 85 | 86 | /// Shape-point intersection test. 87 | inline bool contains(point_t p) const { 88 | return ptype::mag2(center - p) <= radius * radius; 89 | } 90 | 91 | /** 92 | * Sphere-sphere intersection test. 93 | * @param s Another sphere. 94 | * @return `true` if `s` overlaps with this sphere's volume, false otherwise. 95 | */ 96 | bool intersects(Sphere s) const { 97 | return ptype::mag2(center - s.center) <= radius * radius; 98 | } 99 | 100 | bool intersects(const Rect& rect) const { 101 | return rect.dist2(radius) <= radius * radius; 102 | } 103 | 104 | template 105 | requires (Shape::N == N) and std::same_as 106 | bool intersects(const Shape& other) const { 107 | return geom::intersects( 108 | as_any_convex(*this), 109 | as_any_convex(other) 110 | ); 111 | } 112 | 113 | point_t convex_support(point_t d) const { 114 | return center + ptype::unit(d) * radius; 115 | } 116 | 117 | /// Signed distance function. 118 | inline T sdf(point_t p) const { 119 | return ptype::mag(p - center) - radius; 120 | } 121 | 122 | /** 123 | * Return the point `p` orthogonally projected onto the surface of the shape. 124 | */ 125 | point_t project(point_t p) const { 126 | return ptype::unit(p - center) * radius + center; 127 | } 128 | 129 | /// Outward-facing direction. 130 | point_t normal(point_t p) const { 131 | return ptype::unit(p - center); 132 | } 133 | 134 | point_t clip(point_t p) const { 135 | point_t dx = p - center; 136 | T d2 = ptype::mag2(dx); 137 | if (d2 > radius * radius) { 138 | return center + radius * dx / std::sqrt(d2); 139 | } else { 140 | return p; 141 | } 142 | } 143 | 144 | /// Shape-ray intersection test. 145 | Rect intersect(const Ray& ray) const { 146 | T r2 = radius * radius; 147 | Vec dir = ray.direction; 148 | Vec x0 = center - ray.origin; 149 | // solve for s such that ||s * ray - ctr|| == radius 150 | T a = dir.mag2(); 151 | T b = -2 * dir.dot(x0); 152 | T c = x0.mag2() - r2; 153 | T roots[2]; 154 | if (quadratic_solve(roots, a, b, c)) { 155 | return Rect::from_corners(roots[0], roots[1]); 156 | } else { 157 | // empty interval 158 | return Rect(); 159 | } 160 | } 161 | 162 | /** 163 | * @brief Measure the interior (volume) of the shape. 164 | * 165 | * If the sphere is 2D (a disk), this is the area of the disk. 166 | * If the sphere is 3D (a ball), this is the volume of the ball. 167 | * In higher dimensions, this is the hypervolume. 168 | */ 169 | T measure_interior() const { 170 | return measure_ball_interior(N, radius); 171 | } 172 | 173 | /** 174 | * @brief Measure the boundary of the shape. 175 | * 176 | * If the sphere is 2D (a circle), this is the circumference of the circle. 177 | * If the sphere is 3D (a sphere), this is the surface area of the sphere. 178 | * In higher dimensions, this is the volume or hypervolume of the boundary. 179 | */ 180 | T measure_boundary() const { 181 | return measure_sphere_boundary(N, radius); 182 | } 183 | 184 | }; /* Sphere */ 185 | 186 | /// @addtogroup shape 187 | /// @{ 188 | 189 | /// @brief Transform a sphere by a similarity transform. 190 | /// @related Sphere 191 | /// @related Similarity 192 | template 193 | Sphere operator*(const Similarity& xf, const Sphere& s) { 194 | return Sphere(xf * s.center, s.radius * xf.sx); 195 | } 196 | 197 | /// @brief Inverse-transform a sphere by a similarity transform. 198 | /// @related Sphere 199 | /// @related Similarity 200 | template 201 | Sphere operator/(const Sphere& s, const Similarity& xf) { 202 | return Sphere(xf / s.center, s.radius / xf.sx); 203 | } 204 | 205 | /// @brief Transform a sphere by an isometry. 206 | /// @related Sphere 207 | /// @related Isometry 208 | template 209 | Sphere operator*(const Isometry& xf, const Sphere& s) { 210 | return Sphere(xf * s.center, s.radius); 211 | } 212 | 213 | /// @brief Inverse-transform a sphere by an isometry. 214 | /// @related Sphere 215 | /// @related Isometry 216 | template 217 | Sphere operator/(const Sphere& s, const Isometry& xf) { 218 | return Sphere(xf / s.center, s.radius); 219 | } 220 | 221 | /// @} // addtogroup shape 222 | 223 | template 224 | struct Digest, H> { 225 | H operator()(const Sphere& s) const { 226 | H nonce = geom::truncated_constant(0x948904c693a7ddeb, 0xd121a1f8ce15ac6c); 227 | return geom::hash_many(nonce, s.center, s.radius); 228 | } 229 | }; 230 | 231 | #ifdef GEOMC_USE_STREAMS 232 | 233 | template 234 | std::ostream& operator<<(std::ostream& os, const Sphere& s) { 235 | os << "Sphere(" << s.center << "," << s.radius << ")"; 236 | return os; 237 | } 238 | 239 | #endif 240 | 241 | } /* namespace geom */ 242 | 243 | 244 | template 245 | struct std::hash> { 246 | size_t operator()(const geom::Sphere &s) const { 247 | return geom::hash, size_t>(s); 248 | } 249 | }; 250 | -------------------------------------------------------------------------------- /geomc/shape/shapedetail/GJKDebug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace geom { 6 | 7 | /* debug file format: 8 | * 9 | * convex hull points of shape A 10 | * convex hull points of shape B 11 | * [ 12 | * simplex before projection 13 | * simplex after projection 14 | * pt on minkowski difference 15 | * search direction from simplex toward origin 16 | * --- 17 | * ] x number of steps in the search 18 | * ======== 19 | */ 20 | 21 | 22 | template 23 | void emit_simplex(FILE* f, const Simplex& s) { 24 | fprintf(f, "simplex %ldD %ld | ", N, s.n); 25 | for (index_t i = 0; i < s.n; ++i) { 26 | for (int j = 0; j < N; ++j) { 27 | fprintf(f, " %f", s.pts[i][j]); 28 | } 29 | fprintf(f, ", "); 30 | } 31 | fprintf(f, "\n"); 32 | } 33 | 34 | template 35 | void emit_vec(FILE* f, const char* label, const Vec& v) { 36 | fprintf(f, "%s %ldD | ", label, N); 37 | for (int j = 0; j < N; ++j) { 38 | fprintf(f, " %f", v[j]); 39 | } 40 | fprintf(f, "\n"); 41 | } 42 | 43 | template 44 | void emit_splex_stage( 45 | FILE* f, 46 | const Simplex& cur, 47 | const Simplex& next, 48 | const Vec& a, 49 | const Vec& d) 50 | { 51 | emit_simplex(f, cur); 52 | emit_simplex(f, next); 53 | emit_vec(f, "A", a); // we don't actually need to emit this, because it'll be the last vtx in `cur` 54 | emit_vec(f, "p", d); 55 | fprintf(f, "---\n"); 56 | } 57 | 58 | template 59 | void emit_hull(FILE* f, const Vec* pts, index_t n) { 60 | fprintf(f, "hull %ldD %ld | ", N, n); 61 | for (index_t i = 0; i < n; ++i) { 62 | for (int j = 0; j < N; ++j) { 63 | fprintf(f, " %f", pts[i][j]); 64 | } 65 | fprintf(f, ", "); 66 | } 67 | fprintf(f, "\n"); 68 | } 69 | 70 | 71 | } // namespace geom 72 | -------------------------------------------------------------------------------- /geomc/shape/shapedetail/IndexHelpers.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace geom { 6 | 7 | namespace detail { 8 | 9 | /***************************************** 10 | * KDTree helpers * 11 | *****************************************/ 12 | 13 | 14 | // basic shape 15 | // (works on rects) 16 | template 17 | struct ShapeIndexHelper { 18 | 19 | // can be anything unionable with a Rect 20 | // (i.e. either a rect or a vec) 21 | typedef Rect bound_t; 22 | 23 | static inline Vec getPoint(const D& obj) { 24 | return obj.center(); 25 | } 26 | 27 | static inline T dist2(const D& obj, const Vec& p) { 28 | return obj.dist2(p); 29 | } 30 | 31 | static inline bound_t bounds(const D& obj) { 32 | return obj.bounds(); 33 | } 34 | 35 | }; 36 | 37 | 38 | // pointers to indexable shapes 39 | template 40 | struct ShapeIndexHelper< T, N, D* > { 41 | 42 | typedef typename ShapeIndexHelper::bound_t bound_t; 43 | 44 | static inline Vec getPoint(const D* obj) { 45 | return ShapeIndexHelper::getPoint(*obj); 46 | } 47 | 48 | static inline T dist2(const D* obj, const Vec& p) { 49 | return ShapeIndexHelper::getDist2(*obj, p); 50 | } 51 | 52 | static inline bound_t bounds(const D* obj) { 53 | return ShapeIndexHelper::bounds(*obj); 54 | } 55 | 56 | }; 57 | 58 | 59 | // key / value pairs 60 | // (delegate to the shape helper of the key) 61 | template 62 | struct ShapeIndexHelper< T, N, std::pair > { 63 | 64 | typedef typename ShapeIndexHelper::bound_t bound_t; 65 | 66 | static inline Vec getPoint(const std::pair& obj) { 67 | return ShapeIndexHelper::getPoint(obj.first); 68 | } 69 | 70 | static inline T dist2(const std::pair& obj, const Vec& p) { 71 | return ShapeIndexHelper::getDist2(obj.first); 72 | } 73 | 74 | static inline bound_t bounds(const std::pair& obj) { 75 | return ShapeIndexHelper::bounds(obj.first); 76 | } 77 | 78 | }; 79 | 80 | 81 | // bare vector 82 | template 83 | struct ShapeIndexHelper< T, N, Vec > { 84 | 85 | typedef Vec bound_t; 86 | 87 | static inline Vec getPoint(const Vec& v) { 88 | return v; 89 | } 90 | 91 | static inline T dist2(const Vec& v, const Vec& p) { 92 | return v.dist2(p); 93 | } 94 | 95 | static inline Vec bounds(const Vec& v) { 96 | return v; 97 | } 98 | 99 | }; 100 | 101 | 102 | // bounded shapes 103 | template 104 | struct ShapeIndexHelper< T, N, Bounded > { 105 | 106 | typedef Rect bound_t; 107 | 108 | inline Vec getPoint(const Bounded& r) { 109 | return r.bounds().center(); 110 | } 111 | 112 | static inline T dist2(const Bounded& r, const Vec& p) { 113 | return r.bounds().clip(p).dist2(p); 114 | } 115 | 116 | static inline bound_t bounds(const Bounded& r) { 117 | return r.bounds(); 118 | } 119 | 120 | }; 121 | 122 | 123 | // returns an upper bound on the squared distance to the nearest object 124 | // to `p` in mimimum bounding box `r`. 125 | template 126 | T worst_case_nearest2(const Vec& p, const Rect& r) { 127 | T result = std::numeric_limits::max(); 128 | 129 | // each face of the box must have an object touching it, if it is a minimal box. 130 | // each face has a farthest corner where such an object could be hiding, 131 | // and we want the nearest of these. 132 | 133 | Vec far_extremes; 134 | Vec near_extremes; 135 | 136 | for (index_t axis = 0; axis < N; axis++) { 137 | T lodist = std::abs(p[axis] - r.lo[axis]); 138 | T hidist = std::abs(p[axis] - r.hi[axis]); 139 | 140 | far_extremes[axis] = lodist > hidist ? r.lo[axis] : r.hi[axis]; 141 | near_extremes[axis] = lodist > hidist ? r.hi[axis] : r.lo[axis]; 142 | } 143 | 144 | for (index_t axis = 0; axis < N; axis++) { 145 | Vec face_extreme = far_extremes; 146 | // along this axis, we only need the nearer face: 147 | face_extreme[axis] = near_extremes[axis]; 148 | result = std::min(result, (face_extreme - p).mag2()); 149 | } 150 | return result; 151 | } 152 | 153 | } // namespace detail 154 | 155 | } // namespace geom -------------------------------------------------------------------------------- /geomc/shape/shapedetail/SeparatingAxis.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * File: SeparatingAxis.h 5 | * Author: tbabb 6 | * 7 | * Created on May 26, 2014, 11:27 PM 8 | */ 9 | 10 | #include 11 | 12 | 13 | /************************************************************ 14 | * For small dimensions, we use the separating axis theorem * 15 | * to perform box-box intersection tests. The number of * 16 | * axes we must test and the strategy for choosing them * 17 | * changes based on the dimension; these classes are * 18 | * specialized for those dimensions. The cases get far more * 19 | * complicated as N increases; only N=2 and N=3 are * 20 | * implemented. * 21 | ************************************************************/ 22 | 23 | 24 | namespace geom { 25 | 26 | namespace detail { 27 | 28 | /*************************************** 29 | * Iterator base class * 30 | ***************************************/ 31 | 32 | template 33 | class RectAxisHelperBase { 34 | 35 | public: 36 | 37 | const AffineTransform& xf; 38 | index_t i; 39 | 40 | RectAxisHelperBase(const AffineTransform& xf) : xf(xf), i(0) {} 41 | 42 | inline void next() { 43 | i += 1; 44 | } 45 | 46 | protected: 47 | 48 | Vec _getXfAxis(index_t j) { 49 | Vec a; 50 | for (index_t k = 0; k < N; k++) { 51 | a[k] = xf.mat[k][j]; 52 | } 53 | return a; 54 | } 55 | 56 | 57 | Vec _getNormal(index_t j) { 58 | Vec n; 59 | n[j] = 1; 60 | return xf.apply_normal(n); 61 | } 62 | 63 | }; 64 | 65 | 66 | /*************************************** 67 | * General N-dimensional axis tester. * 68 | * Not supported for N != {2,3}. * 69 | ***************************************/ 70 | 71 | template 72 | class RectAxisHelper : public RectAxisHelperBase { 73 | public: 74 | static const bool SAT_SUPPORTED = false; 75 | 76 | // add support for arbitrary dimensions here. 77 | // (GJK may be best for the general case, however) 78 | 79 | }; 80 | 81 | 82 | /********** N=3 Axis Iterator **********/ 83 | 84 | template 85 | class RectAxisHelper : public RectAxisHelperBase { 86 | private: 87 | 88 | typedef RectAxisHelperBase base_t; 89 | 90 | public: 91 | 92 | static const bool SAT_SUPPORTED = true; 93 | 94 | RectAxisHelper(const AffineTransform& xf) : base_t(xf) {} 95 | 96 | // we don't include the canonical axes, because we handle 97 | // them separately for speed. A more user-facing implementation 98 | // would iterate over all 15 axes. 99 | 100 | Vec axis() { 101 | index_t i = base_t::i; 102 | if (i < 3) { 103 | // test each of the principal axes in the oriented box 104 | return this->_getNormal(i); 105 | } else { // we are assuming `not done()` 106 | // test all combinations of principal axes between 107 | // the two boxes. We assume one is axis-aligned. 108 | Vec b0_axis; 109 | Vec b1_axis = this->_getXfAxis(i % 3); 110 | b0_axis[i / 3 - 1] = 1; 111 | return b0_axis ^ b1_axis; 112 | } 113 | } 114 | 115 | inline bool done() { 116 | return base_t::i >= 12; // 3 Xf axes, plus 3 * 3 axis combinations. 117 | } 118 | }; 119 | 120 | 121 | /********** N=2 Axis Iterator **********/ 122 | 123 | template 124 | class RectAxisHelper : public RectAxisHelperBase { 125 | private: 126 | typedef RectAxisHelperBase base_t; 127 | public: 128 | 129 | static const bool SAT_SUPPORTED = true; 130 | 131 | RectAxisHelper(const AffineTransform& xf) : base_t(xf) {} 132 | 133 | // canonical axes already handled. 134 | 135 | Vec axis() { 136 | return this->_getNormal(base_t::i); 137 | } 138 | 139 | inline bool done() { 140 | return base_t::i >= 2; 141 | } 142 | }; 143 | 144 | 145 | /*********************************************************** 146 | * Intersector class. Use separating axis theorem if * 147 | * supported; else gjk. * 148 | ***********************************************************/ 149 | 150 | template 151 | class RectIntersector { 152 | 153 | static inline bool intersect(const Transformed >& b0, const Rect& b1) { 154 | return gjk_intersect(b0, b1); 155 | } 156 | }; 157 | 158 | template 159 | class RectIntersector::SAT_SUPPORTED>::type> 161 | { 162 | public: 163 | 164 | static bool intersect(const Transformed >& b0, const Rect& b1) { 165 | // here we apply the separating axis theorem. 166 | // all edge axes, face normals, and edge-edge normals must be tested. 167 | // the first two cases are one and the same; they are the principal 168 | // axes of each rect; amounting to 2N tests. The third case is 169 | // 9 axes for N=3 and 0 axes for N=2. Other dimensions may be 170 | // supported by extending detail::RectAxisHelper to higher N, 171 | // though this is unlikely to be simpler than simply falling back to GJK. 172 | constexpr index_t n_corners = 1 << N; 173 | Vec b0_pts[n_corners]; 174 | Vec b1_pts[n_corners]; 175 | Vec b0_body_extreme[2] = { b0.shape.lo, b0.shape.hi }; 176 | Vec b1_body_extreme[2] = { b1.lo, b1.hi }; 177 | 178 | // compute world-space points 179 | for (index_t c = 0; c < n_corners; c++) { 180 | for (index_t i = 0; i < N; i++) { 181 | int min_or_max = (c & (1 << i)) != 0; 182 | b0_pts[c][i] = b0_body_extreme[min_or_max][i]; 183 | b1_pts[c][i] = b1_body_extreme[min_or_max][i]; 184 | } 185 | b0_pts[c] = b0.xf * b0_pts[c]; 186 | } 187 | 188 | // compare along world (i.e. b1's) axes. 189 | // special case because no projection needed. 190 | // this is basically testing if the two AABBs overlap. 191 | for (index_t i = 0; i < N; i++) { 192 | T lo = std::numeric_limits::max(); 193 | T hi = std::numeric_limits::lowest(); 194 | for (index_t j = 0; j < n_corners; j++) { 195 | lo = std::min(lo, b0_pts[j][i]); 196 | hi = std::max(hi, b0_pts[j][i]); 197 | } 198 | // if no overlap on this axis, we've found a separating axis. 199 | if (lo > b1.hi[i] or hi < b1.lo[i]) return false; 200 | } 201 | 202 | // now test all the remaining axes. For N=2, this is simply 203 | // the two principal axes of the oriented box. For N=3, we must also 204 | // test each axis which is the cross product of two principal axes, 205 | // for up to 12 additional axis tests (we have covered N already). 206 | for (detail::RectAxisHelper helper(b0.xf); not helper.done(); helper.next()) { 207 | Vec axis = helper.axis(); 208 | if (axis == Vec::zeros) continue; // redundant axis 209 | Rect b0_range, b1_range; 210 | b0_range = b1_range = Rect(std::numeric_limits::max(), 211 | std::numeric_limits::lowest()); 212 | for (index_t corner = 0; corner < n_corners; corner++) { 213 | T b0_proj = axis.dot(b0_pts[corner]); 214 | T b1_proj = axis.dot(b1_pts[corner]); 215 | b0_range |= b0_proj; 216 | b1_range |= b1_proj; 217 | } 218 | // no overlap on this axis; separating axis found. 219 | if (not b0_range.intersects(b1_range)) return false; 220 | } 221 | 222 | // overlap on all candidate axes. 223 | return true; 224 | } 225 | 226 | }; 227 | 228 | 229 | } // namespace detail 230 | } // namespace geom 231 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Tim Babb 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /regression/SConscript: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import itertools 3 | 4 | def runUnitTest(env, target, source): 5 | import subprocess 6 | app = str(source[0].abspath) 7 | if not subprocess.call(app): 8 | with open(str(target[0]),'w') as f: 9 | f.write("PASSED\n") 10 | 11 | Import("env") 12 | Import("lib") 13 | 14 | arch = env['ARCH'] 15 | 16 | test_pkgs = ['gtest', 'gtest_main'] 17 | 18 | test_env = env.Clone() 19 | test_env.Append(LIBS=[lib]) 20 | test_env.Append(CPPPATH=['.']) 21 | 22 | test_sources = Glob('*.cpp') 23 | 24 | wasm_flags = { 25 | 'ALLOW_MEMORY_GROWTH' : 1, 26 | } 27 | 28 | for pkg in test_pkgs: 29 | test_env.ParseConfig(f"pkg-config --cflags --libs {pkg}") 30 | 31 | if arch == 'wasm': 32 | wasm_opts = (('-s', f'{k}={v}') for k,v in wasm_flags.items()) 33 | test_env.Append(LINKFLAGS=list(itertools.chain(*wasm_opts))) 34 | if ARGUMENTS.get('debug', False): 35 | test_env.Append(LINKFLAGS=['--source-map-base', 'http://localhost:8000/']) 36 | 37 | test_objs = [] 38 | 39 | for p in test_sources: 40 | name = Path(p.path).stem 41 | if arch == 'wasm': 42 | target = f'#/html/regression/{name}.html' 43 | else: 44 | target = f'#/bin/{arch}/regression/{name}' 45 | prog = test_env.Program(target, 46 | source=p, 47 | depends=['geomc']) 48 | if arch == 'wasm': 49 | # only create the test html file if we're building for wasm. 50 | # we can't run it directly. 51 | test_objs.append(prog) 52 | else: 53 | runtest = test_env.Command(f"{name}.passed", target, runUnitTest) 54 | # actually run the test if we're building natively 55 | test_objs.append(runtest) 56 | 57 | out_target = test_env.Alias('test', test_objs) 58 | 59 | serve_path = env.GetBuildPath('#/html/regression') 60 | serve = env.Command('serve', test_objs, f'python3 -m http.server 8000 -d {serve_path}') 61 | env.Alias('serve', serve) 62 | 63 | Return('out_target') 64 | -------------------------------------------------------------------------------- /regression/cholesky.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME Cholesky 2 | 3 | // #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace geom; 10 | using namespace std; 11 | 12 | typedef std::mt19937_64 rng_t; 13 | 14 | 15 | template 16 | SimpleMatrix random_matrix(index_t sz, rng_t* rng) { 17 | typedef std::normal_distribution d_normal_t; 18 | d_normal_t N = d_normal_t(0., 1); // (ctr, variance) 19 | SimpleMatrix mx(sz,sz); 20 | 21 | for (index_t r = 0; r < sz; ++r) { 22 | for (index_t c = 0; c < sz; ++c) { 23 | mx(r,c) = N(*rng); 24 | } 25 | } 26 | return mx; 27 | } 28 | 29 | 30 | template 31 | T matrix_diff(const SimpleMatrix& a, const SimpleMatrix& b) { 32 | EXPECT_EQ(a.rows(), b.rows()); 33 | EXPECT_EQ(a.cols(), b.cols()); 34 | 35 | T residual = 0; 36 | for (index_t r = 0; r < a.rows(); ++r) { 37 | for (index_t c = 0; c < a.cols(); ++c) { 38 | T z = a[r][c] - b[r][c]; 39 | residual += z * z; 40 | } 41 | } 42 | return std::sqrt(residual); 43 | } 44 | 45 | template 46 | void run_cholesky(index_t sz, rng_t* rng) { 47 | SimpleMatrix mx = random_matrix(sz, rng); 48 | SimpleMatrix mxT(sz, sz); 49 | SimpleMatrix A(sz, sz); 50 | SimpleMatrix C(sz, sz); 51 | 52 | // make mx a positive definite matrix `A` by taking mx * mx^T: 53 | transpose(&mxT, mx); 54 | mul(&A, mx, mxT); 55 | 56 | // cholesky decompose `A` into `mx`. 57 | mtxcopy(&mx, A); 58 | EXPECT_TRUE(cholesky(&mx)); 59 | 60 | // confirm `mx^T * mx = A` 61 | transpose(&mxT, mx); 62 | mul(&C, mx, mxT); 63 | T rms = matrix_diff(C, A); 64 | EXPECT_NEAR(rms, 0, (T)1e-5); 65 | } 66 | 67 | 68 | TEST(TEST_MODULE_NAME, verify_cholesky) { 69 | rng_t rng(11937294775LL); 70 | std::uniform_int_distribution<> rnd_int(2, 16); 71 | const index_t n = 50000; 72 | for (index_t i = 0; i < n; ++i) { 73 | index_t k = rnd_int(rng); 74 | run_cholesky(k, &rng); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /regression/circular_buffer.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME CircularBuffer 2 | 3 | // #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace geom; 9 | using namespace std; 10 | 11 | 12 | // todo: validate item objects with copy/move/resource destruction semantics 13 | 14 | 15 | template 16 | void validate_stepfill(Deque& buf, index_t ct) { 17 | EXPECT_EQ(buf.size(), ct); 18 | EXPECT_EQ(buf.front(), 10); 19 | EXPECT_EQ(buf.back(), 10 * ct); 20 | 21 | for (index_t i = 0; i < ct; ++i) { 22 | // did the insert work? 23 | EXPECT_EQ(buf[i], (i + 1) * 10); 24 | // does "wraparound" work? 25 | EXPECT_EQ(buf[i + buf.size()], buf[i]); 26 | // does "negative indexing" work? 27 | EXPECT_EQ(buf[i - buf.size()], buf[i]); 28 | } 29 | } 30 | 31 | 32 | template 33 | void exercise(Deque& buf, index_t ct) { 34 | // forwards insertion: 35 | for (index_t i = 0; i < ct; ++i) { 36 | buf.push_back((i + 1) * 10); 37 | } 38 | 39 | validate_stepfill(buf, ct); 40 | 41 | buf.clear(); 42 | EXPECT_EQ(buf.size(), 0); 43 | 44 | // try backwards insertion: 45 | for (index_t i = 0; i < ct; ++i) { 46 | buf.push_front((ct - i) * 10); 47 | } 48 | 49 | validate_stepfill(buf, ct); 50 | 51 | // clear the buffer by popping 52 | index_t removed; 53 | for (removed = 0; buf.size() > 0; ++removed) { 54 | std::optional w = buf.pop_front(); 55 | EXPECT_TRUE(w.has_value()); 56 | EXPECT_TRUE(w == (removed + 1) * 10); 57 | } 58 | EXPECT_EQ(buf.size(), 0); 59 | EXPECT_EQ(removed, ct); 60 | } 61 | 62 | 63 | template 64 | void exercise_copy_and_move(index_t ct) { 65 | Deque buf0; 66 | Deque buf1; 67 | 68 | // stepfill 69 | for (index_t i = 0; i < ct; ++i) { 70 | buf0.push_back((i + 1) * 10); 71 | } 72 | validate_stepfill(buf0, ct); 73 | 74 | // manually empty it 75 | while (buf0.size() > 0) { buf0.pop_front(); } 76 | // stepfill again, but with the head somewhere in the middle 77 | for (index_t i = 0; i < ct; ++i) { 78 | buf0.push_back((i + 1) * 10); 79 | } 80 | validate_stepfill(buf0, ct); 81 | 82 | // copy-assign 83 | buf1 = buf0; 84 | validate_stepfill(buf1, ct); 85 | 86 | // copy construct 87 | Deque buf2(buf0); 88 | validate_stepfill(buf2, ct); 89 | buf2.clear(); 90 | 91 | // move assign 92 | buf2 = std::move(buf1); 93 | validate_stepfill(buf2, ct); 94 | EXPECT_EQ(buf1.size(), 0); 95 | exercise(buf1, N - 1); 96 | exercise(buf1, N * 2); 97 | buf2.clear(); 98 | exercise(buf2, N - 1); 99 | exercise(buf2, N * 2); 100 | 101 | // move construct 102 | Deque buf3(std::move(buf0)); 103 | validate_stepfill(buf3, ct); 104 | EXPECT_EQ(buf0.size(), 0); 105 | exercise(buf0, N - 1); 106 | exercise(buf0, N * 2); 107 | buf3.clear(); 108 | exercise(buf3, N - 1); 109 | exercise(buf3, N * 2); 110 | } 111 | 112 | TEST(TEST_MODULE_NAME, create_circular_buffer) { 113 | Deque buf; 114 | 115 | EXPECT_EQ(buf.size(), 0); 116 | EXPECT_EQ(buf.capacity(), 8); 117 | 118 | std::optional z = buf.pop_front(); 119 | EXPECT_TRUE(not z.has_value()); 120 | z = buf.pop_back(); 121 | EXPECT_TRUE(not z.has_value()); 122 | 123 | buf.push_back(111); 124 | EXPECT_EQ(buf.size(), 1); 125 | EXPECT_EQ(buf.front(), 111); 126 | EXPECT_EQ(buf.back(), 111); 127 | 128 | z = buf.pop_front(); 129 | EXPECT_TRUE(z.has_value()); 130 | EXPECT_EQ(*z, 111); 131 | EXPECT_EQ(buf.size(), 0); 132 | 133 | buf.push_front(222); 134 | EXPECT_EQ(buf.size(), 1); 135 | EXPECT_EQ(buf.front(), 222); 136 | EXPECT_EQ(buf.back(), 222); 137 | 138 | z = buf.pop_back(); 139 | EXPECT_TRUE(z.has_value()); 140 | EXPECT_EQ(*z, 222); 141 | EXPECT_EQ(buf.size(), 0); 142 | } 143 | 144 | 145 | TEST(TEST_MODULE_NAME, exercise_small_buffer) { 146 | Deque buf; 147 | // try the buffer with a small number of items. 148 | exercise(buf, 6); 149 | // try the buffer again, but with the head at the middle of the buffer: 150 | exercise(buf, 6); 151 | } 152 | 153 | 154 | TEST(TEST_MODULE_NAME, exercise_large_buffer) { 155 | Deque buf; 156 | // try the buffer with a large number of items. 157 | exercise(buf, 19); 158 | // try again, but with the head in the middle of the buffer: 159 | exercise(buf, 19); 160 | } 161 | 162 | 163 | TEST(TEST_MODULE_NAME, exercise_small_copy_and_move) { 164 | exercise_copy_and_move(7); 165 | } 166 | 167 | TEST(TEST_MODULE_NAME, exercise_large_copy_and_move) { 168 | exercise_copy_and_move(15); 169 | } 170 | -------------------------------------------------------------------------------- /regression/intersect.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME Intersect 2 | 3 | // set this to 1 to get diagnostic text file dumps of failure cases 4 | #define DEBUG_INTERSECTION 0 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shape_generation.h" 14 | 15 | #if DEBUG_INTERSECTION 16 | constexpr bool DO_DEBUG = true; 17 | #else 18 | constexpr bool DO_DEBUG = false; 19 | #endif 20 | 21 | template 22 | struct PointHull : public Convex> { 23 | const Vec* pts; 24 | index_t n; 25 | 26 | PointHull(const Vec *pts, index_t n):pts(pts),n(n) {} 27 | 28 | Vec convex_support(Vec d) const { 29 | T largest_dot = std::numeric_limits::lowest(); 30 | Vec pt; 31 | for (index_t i = 0; i < n; i++) { 32 | T a = d.dot(pts[i]); 33 | if (a > largest_dot) { 34 | largest_dot = a; 35 | pt = pts[i]; 36 | } 37 | } 38 | return pt; 39 | } 40 | }; 41 | 42 | template 43 | void random_box(AffineBox* r, rng_t* rng) { 44 | std::uniform_real_distribution range{2,8}; 45 | 46 | rnd(rng, &r->xf); 47 | r->xf *= scale(Vec(range(*rng))); 48 | r->xf *= translation(rnd(rng).unit()); 49 | ShapeSampler> box{{-1,1}}; 50 | r->shape = Rect::from_corners(box(rng), box(rng)); 51 | } 52 | 53 | template 54 | void get_box_corners(const AffineBox& r, Vec p[1 << N]) { 55 | Vec extreme[2] = { r.shape.lo, r.shape.hi }; 56 | 57 | for (index_t i = 0; i < (1 << N); ++i) { 58 | Vec v; 59 | for (index_t axis = 0; axis < N; ++axis) { 60 | v[axis] = extreme[((i >> axis) & 1)][axis]; 61 | } 62 | p[i] = r.xf * v; 63 | } 64 | } 65 | 66 | 67 | // this is tautological for N > 3. OBBs use GJK directly! 68 | template 69 | void test_box_gjk_matches_sat(rng_t* rng, index_t iters) { 70 | const index_t n = (index_t)std::ceil(std::sqrt(iters)); 71 | AffineBox* boxes = new AffineBox[n]; 72 | for (index_t i = 0; i < n; i++) { 73 | random_box(boxes + i, rng); 74 | } 75 | constexpr index_t n_corners = 1 << N; 76 | 77 | std::vector iter_hist; 78 | 79 | char fname[] = "gjk_D_.txt"; 80 | fname[3] = '0' + N; 81 | fname[5] = std::is_same::value ? 'f' : 'd'; 82 | FILE* f = DO_DEBUG ? fopen(fname, "w") : nullptr; 83 | 84 | index_t i0 = 0; 85 | index_t i1 = 0; 86 | index_t failures = 0; 87 | index_t positive = 0; 88 | index_t negative = 0; 89 | index_t pos_fails = 0; 90 | index_t neg_fails = 0; 91 | index_t pos_results = 0; 92 | index_t neg_results = 0; 93 | index_t ident_failures = 0; 94 | index_t degen_failures = 0; 95 | index_t max_iters = 0; 96 | for (index_t j = 0; j < iters; j++) { 97 | i0 = (i0 + 1) % n; 98 | if (i0 == 0) i1 = (i1 + 1) % n; 99 | Vec b0[n_corners]; 100 | Vec b1[n_corners]; 101 | get_box_corners(boxes[i0], b0); 102 | get_box_corners(boxes[i1], b1); 103 | Intersector intersector; 104 | bool gjk = intersector.intersects( 105 | as_any_convex(PointHull(b0, n_corners)), 106 | as_any_convex(PointHull(b1, n_corners)) 107 | ); 108 | bool SAT = boxes[i0].intersects(boxes[i1]); 109 | index_t iters = intersector.iterations; 110 | if (iter_hist.size() < iters + 1) iter_hist.resize(iters + 1); 111 | iter_hist[iters] += 1; 112 | max_iters = std::max(max_iters, iters); 113 | if (gjk != SAT) { 114 | failures++; 115 | if (SAT) pos_fails++; 116 | else neg_fails++; 117 | if (i0 == i1) ident_failures++; 118 | if (intersector.was_degenerate) degen_failures++; 119 | if constexpr (DO_DEBUG) { 120 | if (failures < 30) { 121 | // emit a limited number of failure cases 122 | emit_hull(f, b0, n_corners); 123 | emit_hull(f, b1, n_corners); 124 | intersector = {}; 125 | intersector.debug_file = f; 126 | intersector.intersects( 127 | as_any_convex(PointHull(b0, n_corners)), 128 | as_any_convex(PointHull(b1, n_corners)) 129 | ); 130 | fprintf(f, "========\n"); 131 | } 132 | } 133 | } 134 | if (gjk) pos_results++; 135 | else neg_results++; 136 | 137 | if (SAT) positive++; 138 | else negative++; 139 | } 140 | 141 | if (f) fclose(f); 142 | 143 | delete [] boxes; 144 | std::cout << "tested " << iters << " " << N << "D" << typeid(T).name() << " shapes. "; 145 | std::cout << positive << " intersecting, " << negative << " disjoint" << std::endl; 146 | if (failures != 0) { 147 | std::cout << " actually-intersecting failures: " << pos_fails << std::endl; 148 | std::cout << " actually-disjoint failures: " << neg_fails << std::endl; 149 | std::cout << " identical-object failures: " << ident_failures << std::endl; 150 | std::cout << " positive results: " << pos_results << std::endl; 151 | std::cout << " negative results: " << neg_results << std::endl; 152 | std::cout << " degenerate failures: " << degen_failures << std::endl; 153 | std::cout << std::endl; 154 | } 155 | std::cout << " iteration count histogram:\n"; 156 | std::cout << " "; 157 | std::cout << "(max " << max_iters << ") "; 158 | for (index_t i = 0; i < iter_hist.size(); ++i) { 159 | std::cout << iter_hist[i] << " "; 160 | } 161 | std::cout << "\n"; 162 | EXPECT_EQ(failures, 0); 163 | } 164 | 165 | 166 | constexpr index_t N_TESTS = 1'000'000; 167 | 168 | TEST(TEST_MODULE_NAME, test_2f_gjk_sat_agree) { 169 | test_box_gjk_matches_sat(&rng, N_TESTS); 170 | } 171 | 172 | TEST(TEST_MODULE_NAME, test_2d_gjk_sat_agree) { 173 | test_box_gjk_matches_sat(&rng, N_TESTS); 174 | } 175 | 176 | TEST(TEST_MODULE_NAME, test_3f_gjk_sat_agree) { 177 | test_box_gjk_matches_sat(&rng, N_TESTS); 178 | } 179 | 180 | TEST(TEST_MODULE_NAME, test_3d_gjk_sat_agree) { 181 | test_box_gjk_matches_sat(&rng, N_TESTS); 182 | } 183 | -------------------------------------------------------------------------------- /regression/linear_solve.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME LinearSolve 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // increase for good coverage. 11 | // set to 1 for debugging. 12 | #define N_TESTS 256 13 | 14 | using namespace geom; 15 | using namespace std; 16 | 17 | typedef std::mt19937_64 rng_t; 18 | 19 | // test: check that PLU = M, actually 20 | // (is P what it says it is?) 21 | 22 | 23 | template 24 | void randomize(T* v, index_t n, rng_t* rng) { 25 | typedef std::normal_distribution d_normal_t; 26 | d_normal_t gauss = d_normal_t(0., 1); // (ctr, variance) 27 | 28 | for (index_t i = 0; i < n; ++i) { 29 | v[i] = gauss(*rng); 30 | } 31 | } 32 | 33 | 34 | template 35 | void solve_vectors(rng_t* rng) { 36 | Vec bases[N]; // destroyed by the factorization 37 | Vec vi[N]; 38 | Vec x; 39 | randomize(bases[0].begin(), N * N, rng); 40 | randomize(x.begin(), N, rng); 41 | std::copy(bases, bases + N, vi); // copy the bases, so we can check them 42 | Vec b = x; // likewise here 43 | 44 | // we should be able to write b as x[i] * bases[i]. 45 | if (linear_solve(bases, 1, &x, 0)) { 46 | Vec b0; 47 | for (index_t i = 0; i < N; ++i) { 48 | b0 += x[i] * vi[i]; 49 | } 50 | // check that the components of b0 and b are close. 51 | for (index_t i = 0; i < N; ++i) { 52 | EXPECT_NEAR(b[i], b0[i], 1e-5); 53 | } 54 | } else { 55 | // ↓ very unlikely 56 | std::cerr << "Singular test matrix\n"; 57 | } 58 | } 59 | 60 | 61 | template 62 | void exercise_solve_vectors(rng_t* rng, index_t trials) { 63 | for (index_t i = 0; i < trials; ++i) { 64 | solve_vectors(rng); 65 | } 66 | } 67 | 68 | 69 | template 70 | void exercise_plu(rng_t* rng, index_t n, index_t trials) { 71 | SimpleMatrix mx(n, n); 72 | SimpleMatrix L(n, n); 73 | SimpleMatrix U(n, n); 74 | SimpleMatrix LU(n, n); 75 | PermutationMatrix<0> P(n); 76 | index_t* Pi = new index_t[n]; 77 | bool _parity; 78 | 79 | for (index_t i = 0; i < trials; ++i) { 80 | randomize(mx.begin(), n * n, rng); 81 | std::copy(mx.begin(), mx.end(), LU.begin()); 82 | decomp_plu(LU.begin(), n, n, Pi, &_parity); 83 | P.setRowSources(Pi); 84 | // copy L and U 85 | for (index_t i = 0; i < n; ++i) { 86 | for (index_t j = 0; j < n; ++j) { 87 | L(i,j) = i >= j ? (i == j ? 1 : LU(i,j)) : 0; 88 | U(i,j) = i <= j ? LU(i,j) : 0; 89 | } 90 | } 91 | mul(&LU, L, U); // LU <- L * U 92 | mul(&L, P, mx); // L <- P * M 93 | // check that LU = PM 94 | for (index_t j = 0; j < n * n; ++j) { 95 | EXPECT_NEAR(LU.begin()[j], L.begin()[j], 1e-5); 96 | } 97 | } 98 | 99 | delete [] Pi; 100 | } 101 | 102 | 103 | template 104 | void exercise_orthogonalize(rng_t* rng, index_t trials) { 105 | Vec vs[N]; 106 | for (index_t i = 0; i < trials; ++i) { 107 | randomize(vs[0].begin(), N * N, rng); 108 | orthogonalize(vs, N); 109 | // check that each of the orthogonalized bases have ~= 0 dot 110 | // product with all the others: 111 | for (index_t j = 0; j < N - 1; ++j) { 112 | for (index_t k = j + 1; k < N; ++k) { 113 | EXPECT_NEAR(vs[j].dot(vs[k]), 0, 1e-5); 114 | } 115 | } 116 | } 117 | } 118 | 119 | 120 | template 121 | void exercise_nullspace(rng_t* rng, index_t trials) { 122 | auto urnd = std::uniform_int_distribution(1, N - 1); 123 | Vec bases[N]; 124 | for (index_t i = 0; i < trials; ++i) { 125 | index_t n = urnd(*rng); 126 | randomize(bases[0].begin(), n * N, rng); 127 | nullspace(bases, n, bases + n); 128 | 129 | // verify that each of the null bases have 130 | // zero dot product with the source bases. 131 | // (they are not guaranteed to be orthogonal to each other) 132 | for (index_t j = 0; j < n; ++j) { 133 | for (index_t k = n; k < N; ++k) { 134 | EXPECT_NEAR(bases[j].dot(bases[k]), 0, 1e-5); 135 | } 136 | } 137 | } 138 | } 139 | 140 | 141 | 142 | TEST(TEST_MODULE_NAME, verify_nullspace) { 143 | rng_t rng(16512485420001724907ULL); 144 | exercise_nullspace(&rng, N_TESTS); 145 | exercise_nullspace(&rng, N_TESTS); 146 | exercise_nullspace(&rng, N_TESTS); 147 | exercise_nullspace(&rng, N_TESTS); 148 | exercise_nullspace(&rng, N_TESTS); 149 | } 150 | 151 | 152 | TEST(TEST_MODULE_NAME, verify_orthogonal) { 153 | rng_t rng(15794404771588593305ULL); 154 | exercise_orthogonalize(&rng, N_TESTS); 155 | exercise_orthogonalize(&rng, N_TESTS); 156 | exercise_orthogonalize(&rng, N_TESTS); 157 | exercise_orthogonalize(&rng, N_TESTS); 158 | exercise_orthogonalize(&rng, N_TESTS); 159 | } 160 | 161 | 162 | TEST(TEST_MODULE_NAME, verify_PLU) { 163 | rng_t rng(1013126187766094264ULL); 164 | exercise_plu(&rng, 2, N_TESTS); 165 | exercise_plu(&rng, 3, N_TESTS); 166 | exercise_plu(&rng, 4, N_TESTS); 167 | exercise_plu(&rng, 5, N_TESTS); 168 | exercise_plu(&rng, 7, N_TESTS); 169 | // long and slow: 170 | exercise_plu(&rng, 10, std::max(1, N_TESTS/2)); 171 | } 172 | 173 | 174 | TEST(TEST_MODULE_NAME, linear_solve_tests) { 175 | rng_t rng(7301667549950575693ULL); 176 | exercise_solve_vectors(&rng, N_TESTS); 177 | exercise_solve_vectors(&rng, N_TESTS); 178 | exercise_solve_vectors(&rng, N_TESTS); 179 | exercise_solve_vectors(&rng, N_TESTS); 180 | exercise_solve_vectors(&rng, N_TESTS); 181 | exercise_solve_vectors(&rng, N_TESTS); 182 | } 183 | -------------------------------------------------------------------------------- /regression/lu_debug.cpp: -------------------------------------------------------------------------------- 1 | // void destructive_apply_permutation(index_t* p, T* A, index_t n, index_t m); 2 | 3 | #define TEST_MODULE_NAME LuDebug 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | using namespace geom; 12 | using namespace std; 13 | 14 | 15 | TEST(TEST_MODULE_NAME, verify_permute) { 16 | pcg64 rng {0xa3da2a8b16106f19}; 17 | std::uniform_real_distribution unit_dist(0, 1); 18 | constexpr index_t N = 5; 19 | index_t P[N]; 20 | SimpleMatrix m; 21 | SimpleMatrix m0; 22 | SimpleMatrix m1; 23 | PermutationMatrix Px; 24 | for (index_t i = 0; i < N; ++i) { 25 | P[i] = i; 26 | m(i,0) = unit_dist(rng); 27 | m(i,1) = unit_dist(rng); 28 | m(i,2) = unit_dist(rng); 29 | } 30 | std::shuffle(P, P + N, rng); 31 | Px.setRowSources(P); 32 | mul(&m0, Px, m); 33 | 34 | mtxcopy(&m1, m); 35 | detail::destructive_apply_permutation(P, m1.data_begin(), N, 3); 36 | EXPECT_EQ(m0, m1); 37 | } 38 | -------------------------------------------------------------------------------- /regression/matrix_iterator.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME MatrixIterator 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace geom; 8 | using namespace std; 9 | 10 | namespace std { 11 | 12 | template 13 | std::ostream& operator<<(std::ostream& o, const geom::TransposeIterator& i) { 14 | o << "<" << i.base << "[" << (i.p - i.base) << "] = " << (*i) << ">"; 15 | return o; 16 | } 17 | } 18 | 19 | #define TEST_ROWS 13 20 | #define TEST_COLS 7 21 | 22 | 23 | // xxx todo: test negative offsets 24 | 25 | 26 | typedef FlatMatrixLayout rowmaj; 27 | typedef FlatMatrixLayout colmaj; 28 | 29 | 30 | struct MatrixFixture : public ::testing::Test { 31 | const index_t rows; 32 | const index_t cols; 33 | index_t rowmtx[TEST_ROWS * TEST_COLS]; 34 | index_t colmtx[TEST_ROWS * TEST_COLS]; 35 | 36 | MatrixFixture(): 37 | rows(TEST_ROWS), 38 | cols(TEST_COLS) { 39 | index_t i = 0; 40 | for (index_t r = 0; r < rows; ++r) { 41 | for (index_t c = 0; c < cols; ++c, ++i) { 42 | rowmtx[r * cols + c] = i; 43 | colmtx[c * rows + r] = i; 44 | } 45 | } 46 | } 47 | 48 | virtual ~MatrixFixture() {} 49 | }; 50 | 51 | 52 | TEST_F(MatrixFixture, verify_mtx_index) { 53 | // verify that row and column layouts are mutual transposes 54 | for (index_t r = 0; r < rows; ++r) { 55 | for (index_t c = 0; c < cols; ++c) { 56 | EXPECT_EQ( 57 | rowmaj::index(r,c, rows, cols), 58 | colmaj::index(c,r, cols, rows)); 59 | } 60 | } 61 | } 62 | 63 | 64 | TEST_F(MatrixFixture, verify_row_order) { 65 | // verify that row and column layouts agree about the ordering of/within individual rows 66 | index_t i = 0; 67 | for (index_t r = 0; r < rows; ++r) { 68 | auto r_i = rowmaj::row(rowmtx, r, rows, cols); 69 | auto c_i = colmaj::row(colmtx, r, rows, cols); 70 | for (index_t c = 0; c < cols; ++c, ++r_i, ++c_i, ++i) { 71 | EXPECT_EQ(*r_i, *c_i); // agreement? 72 | EXPECT_EQ(*r_i, i); // not just wrong in the same way? 73 | } 74 | } 75 | } 76 | 77 | 78 | TEST_F(MatrixFixture, verify_col_order) { 79 | // verify that row and column layouts agree about the ordering of/within individual columns 80 | for (index_t c = 0; c < cols; ++c) { 81 | auto r_i = rowmaj::col(rowmtx, c, rows, cols); 82 | auto c_i = colmaj::col(colmtx, c, rows, cols); 83 | for (index_t r = 0; r < rows; ++r, ++r_i, ++c_i) { 84 | EXPECT_EQ(*r_i, *c_i); 85 | } 86 | } 87 | } 88 | 89 | 90 | TEST_F(MatrixFixture, verify_row_iteration) { 91 | // verify that row and column layouts row-traverse the entire array in the same order 92 | // (may fail, e.g., at row or column boundaries) 93 | auto r_ir = rowmaj::row(rowmtx, 0, rows, cols); 94 | auto c_ir = colmaj::row(colmtx, 0, rows, cols); 95 | for (index_t i = 0; i < rows * cols; ++i, ++r_ir, ++c_ir) { 96 | EXPECT_EQ(*r_ir, *c_ir); 97 | } 98 | } 99 | 100 | 101 | TEST_F(MatrixFixture, verify_col_iteration) { 102 | // verify that row and column layouts column-traverse the entire array in the same order 103 | // (may fail, e.g., at row or column boundaries) 104 | auto r_ic = rowmaj::col(rowmtx, 0, rows, cols); 105 | auto c_ic = colmaj::col(colmtx, 0, rows, cols); 106 | for (index_t i = 0; i < rows * cols; ++i, ++r_ic, ++c_ic) { 107 | EXPECT_EQ(*r_ic, *c_ic); 108 | } 109 | } 110 | 111 | 112 | TEST_F(MatrixFixture, verify_offset_increment_row) { 113 | // verify that "offsetting by `n`" and "incrementing `n` times" are the same for row iterators. 114 | for (index_t r = 0; r < rows; ++r) { 115 | auto r_i = rowmaj::row(rowmtx, r, rows, cols); 116 | auto c_i = colmaj::row(colmtx, r, rows, cols); 117 | for (index_t c = 0; c < cols; ++c, ++r_i, ++c_i) { 118 | auto r_j = rowmaj::row(rowmtx, r, rows, cols) + c; 119 | auto c_j = colmaj::row(colmtx, r, rows, cols) + c; 120 | // iters consider themselves equal? 121 | EXPECT_EQ(r_i, r_j); 122 | EXPECT_EQ(c_i, c_j); 123 | // iters point to same location? 124 | EXPECT_EQ(*r_i, *r_j); 125 | EXPECT_EQ(*c_i, *c_j); 126 | } 127 | } 128 | } 129 | 130 | 131 | TEST_F(MatrixFixture, verify_offset_increment_col) { 132 | // verify that "offsetting by `n`" and "incrementing `n` times" are the same for column iterators. 133 | for (index_t c = 0; c < cols; ++c) { 134 | auto r_i = rowmaj::col(rowmtx, c, rows, cols); 135 | auto c_i = colmaj::col(colmtx, c, rows, cols); 136 | for (index_t r = 0; r < rows; ++r, ++r_i, ++c_i) { 137 | auto r_j = rowmaj::col(rowmtx, c, rows, cols) + r; 138 | auto c_j = colmaj::col(colmtx, c, rows, cols) + r; 139 | // iters consider themselves equal? 140 | EXPECT_EQ(r_i, r_j); 141 | EXPECT_EQ(c_i, c_j); 142 | // iters point to same location? 143 | EXPECT_EQ(*r_i, *r_j); 144 | EXPECT_EQ(*c_i, *c_j); 145 | } 146 | } 147 | } 148 | 149 | 150 | TEST_F(MatrixFixture, verify_distance) { 151 | // verify that distance() and advance() agree 152 | for (index_t r_src = 0; r_src < rows; ++r_src) { 153 | for (index_t c_src = 0; c_src < cols; ++c_src) { 154 | auto src_i_r = rowmaj::row(rowmtx, r_src, rows, cols) + c_src; 155 | auto src_i_c = colmaj::row(colmtx, r_src, rows, cols) + c_src; 156 | for (index_t r_dst = 0; r_dst < rows; ++r_dst) { 157 | for (index_t c_dst = 0; c_dst < cols; ++c_dst) { 158 | auto dst_i_r = rowmaj::row(rowmtx, r_dst, rows, cols) + c_dst; 159 | auto dst_i_c = colmaj::row(colmtx, r_dst, rows, cols) + c_dst; 160 | auto r_dx = dst_i_r - src_i_r; 161 | auto c_dx = dst_i_c - src_i_c; 162 | // iterators consider themselves the same? 163 | EXPECT_EQ(src_i_r + r_dx, dst_i_r); 164 | EXPECT_EQ(src_i_c + c_dx, dst_i_c); 165 | // iters point to same location? 166 | EXPECT_EQ(*(src_i_r + r_dx), *(dst_i_r)); 167 | EXPECT_EQ(*(src_i_c + c_dx), *(dst_i_c)); 168 | } 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /regression/perlin.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME Perlin 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace geom; 10 | using namespace std; 11 | 12 | using rng_t = pcg64; 13 | 14 | 15 | // todo: should figure out a way to get a copy of a PerlinNoise in Dual form, 16 | // and verify the gradient that way. 17 | 18 | 19 | // return rms(error), max(error) 20 | template 21 | std::pair perlin_gradient(rng_t& rng, index_t n_trials) { 22 | PerlinNoise pn(rng); 23 | const T eps = 0.00001; 24 | 25 | T err_sq = (T)0; 26 | T err_max = (T)0; 27 | 28 | SampleShape> smp_sphere {}; 29 | 30 | for (index_t i = 0; i < n_trials; ++i) { 31 | Vec x = 512 * smp_sphere(rng); // :G 32 | auto x_dx = pn.gradient(x); 33 | Vec g; 34 | 35 | // finite difference the gradient 36 | for (index_t axis = 0; axis < N; axis++) { 37 | Vec dx; 38 | dx[axis] = eps; 39 | g[axis] = (pn.eval(x + dx) - pn.eval(x - dx)) / (2 * eps); 40 | } 41 | 42 | // gradient()'s opinion on f(x) should be the same as eval()'s. 43 | EXPECT_NEAR(x_dx.first, pn.eval(x), eps); 44 | 45 | g -= x_dx.second; 46 | T e = g.dot(g); 47 | err_sq += e; 48 | err_max = std::max(e, err_max); 49 | } 50 | 51 | return std::pair( 52 | std::sqrt(err_sq) / n_trials, 53 | std::sqrt(err_max)); 54 | } 55 | 56 | 57 | TEST(TEST_MODULE_NAME, test_perlin_gradient) { 58 | rng_t rng {1017381749271967481LL}; 59 | std::pair k; 60 | k = perlin_gradient(rng, 10000); 61 | EXPECT_NEAR(k.first, 0, 5e-11); 62 | EXPECT_NEAR(k.second, 0, 1e-8); 63 | k = perlin_gradient(rng, 10000); 64 | EXPECT_NEAR(k.first, 0, 5e-11); 65 | EXPECT_NEAR(k.second, 0, 1e-8); 66 | k = perlin_gradient(rng, 10000); 67 | EXPECT_NEAR(k.first, 0, 5e-11); 68 | EXPECT_NEAR(k.second, 0, 1e-8); 69 | } 70 | -------------------------------------------------------------------------------- /regression/simplex.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME Simplex 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "shape_generation.h" 13 | 14 | // increase for good coverage. 15 | // set to 1 for debugging. 16 | #define N_TESTS 100'000 17 | 18 | using namespace geom; 19 | using namespace std; 20 | 21 | typedef pcg64 rng_t; 22 | 23 | template 24 | void randomize(T* v, index_t n, rng_t* rng) { 25 | typedef std::normal_distribution d_normal_t; 26 | d_normal_t gauss = d_normal_t(0., 1); // (ctr, variance) 27 | 28 | for (index_t i = 0; i < n; ++i) { 29 | v[i] = gauss(*rng); 30 | } 31 | } 32 | 33 | template 34 | bool validate_simplex_measure(const Simplex& s) { 35 | index_t n = s.n - 1; 36 | T vol = s.measure(); 37 | int k = 1; 38 | Vec bases[N]; 39 | for (index_t i = 0; i < n; ++i) { 40 | bases[i] = s[i + 1] - s[0]; 41 | k *= (i + 1); 42 | } 43 | // volume is sqrt of the determinant of the gram matrix 44 | // divided by n factorial. 45 | T b[N * N]; 46 | WrapperMatrix m {b, n, n}; 47 | WrapperMatrix m0 {bases[0].begin(), n}; 48 | WrapperMatrix m1 {bases[0].begin(), n}; 49 | // (n x n) = (n x N) * (N x n) 50 | mul(&m, m0, m1); 51 | T vol2 = std::sqrt(std::abs(det_destructive(b, n))) / k; 52 | 53 | // std::cout << "error: " << std::abs(vol2 / vol - 1) << "\n"; 54 | 55 | // BOOST_CHECK_SMALL(vol2 / vol - 1, 1e-4); 56 | // if (std::abs(vol2 / vol - 1) > 1e-5) { 57 | // // std::cout << "incorrect | N: " << N << " n: " << n << "\n"; 58 | // // std::cerr << " vol: " << vol << " vol2: " << vol2 << "\n"; 59 | // // std::cerr << " N: " << N << " n: " << n << "\n"; 60 | // } else { 61 | // std::cout << "correct | N: " << N << " n: " << n << "\n"; 62 | // } 63 | return std::abs(vol2 / vol - 1) < 1e-5; 64 | } 65 | 66 | 67 | template 68 | void exercise_simplex_measure(rng_t* rng, index_t trials) { 69 | auto urnd = std::uniform_int_distribution(1, N + 1); 70 | Simplex s; 71 | int ok[N + 1] = {0}; 72 | for (index_t i = 0; i < trials; ++i) { 73 | index_t n = urnd(*rng); 74 | randomize(s.pts[0].begin(), n * N, rng); 75 | s.n = n; 76 | ok[n] += validate_simplex_measure(s); 77 | } 78 | std::cout << "--------------------- " << N << "D ---------------------\n"; 79 | for (index_t i = 1; i <= N; ++i) { 80 | std::cout << "n = " << i << " | " << ok[i] / (float) trials << " success\n"; 81 | } 82 | // std::cout << "---------> N = " << N << " | " << ok / (float) trials << " success\n"; 83 | } 84 | 85 | 86 | template 87 | void test_simplex_projection(rng_t* rng, const Simplex& s, index_t trials) { 88 | auto bb = s.bounds(); 89 | auto dims = bb.dimensions(); 90 | auto ctr = bb.center(); 91 | // no degenerate boxes pls 92 | for (index_t i = 0; i < N; ++i) [[unlikely]] { 93 | if (dims[i] == 0) dims[i] = 1; 94 | } 95 | for (index_t i = 0; i < trials; ++i) { 96 | Simplex ss_proj; 97 | Simplex ss_clip; 98 | Vec p = rnd(rng) * dims + ctr; 99 | Vec pp = s.project(p, &ss_proj); 100 | Vec pc = s.clip(p, &ss_clip); 101 | bool inside = s.contains(p); 102 | if (inside) { 103 | // `p` is inside a full-volume simplex. 104 | // no points should have been excluded; 105 | // the "projected-to" simplex should be the same as the original 106 | EXPECT_TRUE(ss_clip == s); 107 | // the target simplex is the same as the original 108 | EXPECT_EQ(ss_clip.n, s.n); 109 | // the clipped point should be precisely the original point 110 | EXPECT_EQ(p, pc); 111 | // the projected point should be an n-1 dimensional face 112 | EXPECT_EQ(ss_proj.n, N); 113 | } else { 114 | // if the point is not inside the simplex, then projected point 115 | // and the clipped point are the same 116 | EXPECT_EQ(pp, pc); 117 | // the direction from the surface pt to the original point 118 | // should be orthogonal to the simplex face 119 | Vec v = p - pp; 120 | for (index_t j = 1; j < ss_clip.n; ++j) { 121 | Vec b = ss_clip.pts[j] - ss_clip.pts[0]; 122 | EXPECT_NEAR(b.dot(v), 0, 1e-5); 123 | } 124 | bool facing = ss_clip.is_facing(p); 125 | EXPECT_TRUE(facing); 126 | } 127 | } 128 | } 129 | 130 | 131 | template 132 | void exercise_simplex_projection(rng_t* rng, index_t trials) { 133 | for (index_t i = 0; i < trials; ++i) { 134 | Simplex s = RandomShape>::rnd_shape(rng); 135 | std::uniform_int_distribution<> d(1,N+1); 136 | s.n = d(*rng); // make the simplex have a random number of verts 137 | test_simplex_projection(rng, s, 10); 138 | } 139 | } 140 | 141 | 142 | /* 143 | TEST(TEST_MODULE_NAME, verify_simplex_measure) { 144 | rng_t rng(16512485420001724907ULL); 145 | exercise_simplex_measure(&rng, N_TESTS); 146 | exercise_simplex_measure(&rng, N_TESTS); 147 | exercise_simplex_measure(&rng, N_TESTS); 148 | exercise_simplex_measure(&rng, N_TESTS); 149 | exercise_simplex_measure(&rng, N_TESTS); 150 | } 151 | */ 152 | 153 | 154 | TEST(TEST_MODULE_NAME, simplex_projection) { 155 | rng_t rng(2532266933125789701ULL); 156 | std::cout << "computing 2D..." << std::endl; 157 | exercise_simplex_projection(&rng, N_TESTS); 158 | std::cout << "computing 3D..." << std::endl; 159 | exercise_simplex_projection(&rng, N_TESTS); 160 | std::cout << "computing 4D..." << std::endl; 161 | exercise_simplex_projection(&rng, N_TESTS); 162 | std::cout << "computing 5D..." << std::endl; 163 | exercise_simplex_projection(&rng, std::max(N_TESTS/10, 1)); 164 | std::cout << "computing 7D..." << std::endl; 165 | exercise_simplex_projection(&rng, std::max(N_TESTS/100, 1)); 166 | } 167 | -------------------------------------------------------------------------------- /regression/small_storage.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_MODULE_NAME SmallStorage 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // todo: moar tests 9 | // - we don't actually verify that element ctors/dtors are run properly 10 | 11 | using namespace geom; 12 | using namespace std; 13 | 14 | typedef pcg64 rng_t; 15 | 16 | using SStor = SmallStorage; 17 | 18 | 19 | void check_okay(const SStor& s, int start, int increment, size_t sz) { 20 | EXPECT_EQ(s.size(), sz); 21 | EXPECT_GE(s.capacity(), sz); 22 | for (size_t i = 0; i < sz; ++i) { 23 | EXPECT_EQ(s[i], start + i * increment); 24 | } 25 | } 26 | 27 | 28 | TEST(TEST_MODULE_NAME, verify_default_ctor) { 29 | SStor s{}; 30 | EXPECT_EQ(s.size(), 0); 31 | EXPECT_EQ(s.static_capacity(), s.capacity()); 32 | EXPECT_EQ(s.begin(), s.end()); 33 | 34 | } 35 | 36 | TEST(TEST_MODULE_NAME, verify_initializer_list) { 37 | SStor ss {{1,2,3,4}}; 38 | EXPECT_EQ(ss[0], 1); 39 | EXPECT_EQ(ss[1], 2); 40 | EXPECT_EQ(ss[2], 3); 41 | EXPECT_EQ(ss[3], 4); 42 | EXPECT_EQ(ss.size(), 4); 43 | EXPECT_EQ(ss.capacity(), 4); 44 | 45 | SStor s1 {{1,2,3,4,5,6,7,8}}; 46 | for (int i = 0; i < 8; ++i) { 47 | EXPECT_EQ(s1[i], i + 1); 48 | } 49 | EXPECT_EQ(s1.size(), 8); 50 | EXPECT_GE(s1.capacity(), 8); 51 | } 52 | 53 | TEST(TEST_MODULE_NAME, verify_copy_assign) { 54 | SStor ss {{3,2,1}}; 55 | { 56 | SStor s0 {{0,1,2,3,4,5}}; 57 | ss = s0; 58 | } 59 | EXPECT_EQ(ss.size(), 6); 60 | EXPECT_GE(ss.capacity(), 6); 61 | for (int i = 0; i < 6; ++i) { 62 | EXPECT_EQ(ss[i],i); 63 | } 64 | 65 | { 66 | SStor s0 {{5,4,3}}; 67 | ss = s0; 68 | } 69 | EXPECT_EQ(ss.size(), 3); 70 | EXPECT_GE(ss.capacity(), 3); 71 | EXPECT_EQ(ss[0], 5); 72 | EXPECT_EQ(ss[1], 4); 73 | EXPECT_EQ(ss[2], 3); 74 | } 75 | 76 | TEST(TEST_MODULE_NAME, verify_copy_ctor) { 77 | SStor ssmol {{5,4,3}}; 78 | SStor sbig {{0,1,2,3,4,5,6,7}}; 79 | { 80 | SStor s {ssmol}; 81 | EXPECT_EQ(s.size(), ssmol.size()); 82 | EXPECT_EQ(s.capacity(), ssmol.capacity()); 83 | EXPECT_EQ(s[0], 5); 84 | EXPECT_EQ(s[1], 4); 85 | EXPECT_EQ(s[2], 3); 86 | } 87 | 88 | { 89 | SStor s {sbig}; 90 | EXPECT_EQ(s.size(), sbig.size()); 91 | EXPECT_GE(s.capacity(), s.size()); 92 | for (int i = 0; i < s.size(); ++i) { 93 | EXPECT_EQ(s[i], i); 94 | } 95 | } 96 | } 97 | 98 | TEST(TEST_MODULE_NAME, verify_move_assign) { 99 | SStor s; 100 | { 101 | SStor s0 {{2,4,6,8,10,12}}; 102 | s = std::move(s0); 103 | EXPECT_NE(s0.begin(), s.begin()); 104 | } 105 | check_okay(s, 2, 2, 6); 106 | { 107 | SStor s0 {{5,4,3,2}}; 108 | s = std::move(s0); 109 | EXPECT_NE(s0.begin(), s.begin()); 110 | } 111 | check_okay(s, 5, -1, 4); 112 | } 113 | 114 | TEST(TEST_MODULE_NAME, verify_move_ctor) { 115 | SStor ssmol {{0,1,2,3}}; 116 | SStor sbig {{2,4,6,8,10, 12,14,16,18,20}}; 117 | { 118 | SStor s {std::move(ssmol)}; 119 | EXPECT_NE(s.begin(), ssmol.begin()); 120 | check_okay(s, 0, 1, 4); 121 | } 122 | { 123 | SStor s {std::move(sbig)}; 124 | EXPECT_NE(s.begin(), sbig.begin()); 125 | check_okay(s, 2, 2, 10); 126 | } 127 | } 128 | 129 | TEST(TEST_MODULE_NAME, verify_reserve) { 130 | SStor s{{0,1,2,3}}; 131 | s.reserve(20); 132 | check_okay(s, 0, 1, 4); 133 | EXPECT_GE(s.capacity(), 20); 134 | } 135 | 136 | TEST(TEST_MODULE_NAME, verify_push_back) { 137 | SStor s; 138 | for (size_t i = 0; i < 33; ++i) { 139 | s.push_back(i * 2); 140 | check_okay(s, 0, 2, i + 1); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /test/MtxDebug.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using namespace geom; 8 | 9 | int main_mtxdebug(int argc, char **argv) { 10 | typedef SimpleMatrix Ma; 11 | typedef SimpleMatrix Mb; 12 | typedef SimpleMatrix Mc; 13 | typedef SimpleMatrix Md; 14 | typedef SimpleMatrix SqM; 15 | typedef DiagMatrix diagM; 16 | typedef PermutationMatrix<3> perM; 17 | 18 | cout << IS_MATRIX(Ma) << endl; 19 | cout << MATRIX_DIM_AGREE(Ma, Ma) << endl; 20 | cout << MATRIX_MUL_DIM_AGREE(Ma,Mb) << endl; 21 | cout << MATRIX_MUL_DIM_AGREE(Ma,Ma) << endl; 22 | cout << MATRIX_MUL_DIM_AGREE(Ma,Mc) << endl; 23 | cout << typeid(detail::_ImplMtxMul::return_t).name() << endl; 24 | 25 | Ma ma; 26 | Mb mb; 27 | Md md; 28 | Mc mc(3,3); 29 | diagM diag; 30 | perM per; 31 | SqM sq; 32 | 33 | cout << (ma * mb) << endl; 34 | 35 | typedef Vec V3; 36 | typedef Vec V4; 37 | V4 v4(1,2,3,4); 38 | V3 v3; 39 | 40 | cout << MATRIX_MUL_DIM_AGREE(Ma,V4) << endl; 41 | cout << MATRIX_MUL_DIM_AGREE(Mc,V4) << endl; 42 | cout << MATRIX_MUL_DIM_AGREE(Ma,V3) << endl; 43 | 44 | // static mtx mul dest 45 | mul(&md, ma, mb); 46 | cout << md << endl; 47 | 48 | // mtx * vec mul 49 | cout << mul(ma, v4) << endl; 50 | 51 | // mtx * vec mul, dest 52 | mul(&v3, ma, v4); 53 | cout << v3 << endl; 54 | 55 | cout << MATRIX_MUL_DIM_AGREE(diagM, Ma) << endl; 56 | cout << detail::_ImplMtxAdaptor::COLDIM << endl; 57 | 58 | cout << (diag * ma) << endl; 59 | 60 | cout << (diag * v3) << endl; 61 | 62 | cout << mul(&v3, diag, v3) << endl; 63 | 64 | cout << (per * v3) << endl; 65 | 66 | cout << (mc * v3) << endl; 67 | 68 | cout << (mc == sq) << endl; 69 | 70 | cout << mul(&v3, mc, v3) << endl; 71 | 72 | return 0; 73 | } 74 | 75 | /* need: vec/diag 76 | 77 | class _ImplMtxMul { 78 | class _ImplMtxMul, REQUIRE_MATRIX_T(Mat)> { 79 | class _ImplMtxMul, Mat, REQUIRE_MATRIX_T(Mat)> { 80 | class _ImplMtxMul, DiagMatrix, void> { 81 | class _ImplMtxMul, REQUIRE_MATRIX_T(Mat)> { 82 | class _ImplMtxMul, Mat, REQUIRE_MATRIX_T(Mat)> { //xx 83 | class _ImplMtxMul, REQUIRE_MATRIX_T(Mat)> { 84 | class _ImplMtxMul, Mat, REQUIRE_MATRIX_T(Mat)> { 85 | class _ImplMtxMul, Vec, void> { //xx 86 | class _ImplMtxMul, PermutationMatrix, void> { 87 | class _ImplMtxMul, PermutationMatrix, void> { 88 | 89 | unit test: 90 | - mul every matrix with every other 91 | - match / mismatch 92 | - dynamic match / mismatch 93 | - compare results to SimpleMatrix mul 94 | - profile speed 95 | - inv of each type 96 | - alloc vs. into 97 | - size 2->6 98 | - stability / accuracy: 99 | - divergence of iterated inv 100 | - inv * orig == ident 101 | - inv(inv(M)) == orig 102 | 103 | - speed tests of other ops 104 | 105 | */ 106 | -------------------------------------------------------------------------------- /test/PLU.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using namespace geom; 8 | 9 | //int poo(int argc, char **argv) { 10 | int main(int argc, char **argv) { 11 | const static index_t sz = 5; 12 | SimpleMatrix m; 13 | 14 | Random *rng = new MTRand(20928234); 15 | 16 | for (index_t r = 0; r < m.rows(); r++) { 17 | for (index_t c = 0; c < m.cols(); c++) { 18 | m.set(r,c,rng->rand()); 19 | } 20 | } 21 | 22 | PLUDecomposition plu(m); 23 | 24 | cout << "singular? " << plu.isSingular() << endl; 25 | 26 | SimpleMatrix L; 27 | SimpleMatrix U; 28 | 29 | plu.getL(&L); 30 | plu.getU(&U); 31 | 32 | cout << m << endl; 33 | cout << "P = " << endl << plu.getP() << endl; 34 | cout << "L = " << endl << L << endl; 35 | cout << "U = " << endl << U << endl; 36 | 37 | cout << (L * U) << endl; 38 | cout << (plu.getP() * m) << endl; 39 | 40 | Vec b; 41 | Vec x; 42 | for (index_t i = 0; i < sz; i++) { 43 | b[i] = rng->rand(); 44 | } 45 | 46 | plu.linearSolve(x.begin(), b.begin()); 47 | 48 | cout << "b = " << b << endl; 49 | cout << "x = " << x << endl; 50 | cout << "Mx = " << (m * x) << endl; 51 | cout << "residual: " << (m * x) - b << endl; 52 | 53 | SimpleMatrix m_inv; 54 | plu.inverse(&m_inv); 55 | 56 | bool ok = true; 57 | 58 | cout << "inv: " << endl; 59 | cout << m_inv << endl; 60 | cout << "M * M^-1:" << endl << (m * m_inv) << endl; 61 | cout << "inv(M): " << endl << m * inv(m, &ok) << endl; 62 | 63 | return 0; 64 | } 65 | 66 | /* 67 | PA = LU 68 | Pb = LUx 69 | Pb = Ly 70 | y = Ux 71 | 72 | */ 73 | -------------------------------------------------------------------------------- /test/RandomBattery.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * RandomBattery.cpp 3 | * 4 | * A suite of tests to catch the most obvious mistakes in a PRNG implementation. 5 | * 6 | * Created on: Nov 11, 2012 7 | * Author: tbabb 8 | */ 9 | 10 | #include "RandomBattery.h" 11 | #include 12 | #include 13 | #include 14 | 15 | namespace geom { 16 | 17 | void stdev(result_t &result, double *nums, int n_nums){ 18 | double mean = 0; 19 | for (int i = 0; i < n_nums; i++){ 20 | mean += nums[i]; 21 | } 22 | mean /= n_nums; 23 | 24 | double stdev_sum = 0; 25 | for (int i = 0; i < n_nums; i++){ 26 | double delta = nums[i] - mean; 27 | stdev_sum += delta * delta; 28 | } 29 | 30 | result.stdev = sqrt(stdev_sum / n_nums); 31 | result.mean = mean; 32 | 33 | } 34 | 35 | RandomBattery::RandomBattery(Random *rng):rng(rng) {} 36 | 37 | RandomBattery::~RandomBattery() {} 38 | 39 | result_t RandomBattery::distribute(int trials){ 40 | const int n_bins = 32; 41 | double bins[n_bins]; 42 | 43 | for (int i = 0; i < n_bins; i++){ 44 | bins[i] = 0; 45 | } 46 | 47 | for (int i = 0; i < trials; i++){ 48 | int n = rng->rand(n_bins); 49 | bins[n]++; 50 | } 51 | 52 | plot(bins, n_bins, (float)trials/n_bins); 53 | result_t result; 54 | stdev(result, bins, n_bins); 55 | return result; 56 | } 57 | 58 | result_t RandomBattery::bitHistogram(int trials){ 59 | double places[32]; 60 | for (int i = 0; i < 32; i++){ 61 | places[i] = 0; 62 | } 63 | for (int i = 0; i < trials; i++){ 64 | uint32_t x = rng->rand32(); 65 | for (int b = 0; b < 32; b++){ 66 | places[b] += x & 1; 67 | x = x >> 1; 68 | } 69 | } 70 | 71 | plot(places, 32, trials/2); 72 | result_t result; 73 | stdev(result, places, 32); 74 | return result; 75 | } 76 | 77 | result_t RandomBattery::byteHistogram(int trials){ 78 | double bytes[256]; 79 | for (int i = 0; i < 256; i++){ 80 | bytes[i] = 0; 81 | } 82 | 83 | for (int i = 0; i < trials; i++){ 84 | uint32_t x = rng->rand32(); 85 | bytes[x & 0x000000ff ]++; 86 | bytes[x & 0x0000ff00 >> 8]++; 87 | bytes[x & 0x00ff0000 >> 16]++; 88 | bytes[x & 0xff000000 >> 24]++; 89 | } 90 | 91 | plot(bytes, 256, 4*trials/256); 92 | result_t result; 93 | stdev(result, bytes, 256); 94 | return result; 95 | } 96 | 97 | double RandomBattery::speed(int trials){ 98 | uint32_t intermediate = 0; 99 | clock_t begin = clock(); 100 | for (int i = 0; i < trials; i++){ 101 | intermediate = intermediate ^ rng->rand32(); 102 | } 103 | clock_t end = clock(); 104 | return (end-begin) / (double)CLOCKS_PER_SEC; 105 | } 106 | 107 | void RandomBattery::runAll(int trials){ 108 | result_t rslt; 109 | 110 | std::cout << "distribution over N buckets:" << std::endl; 111 | rslt = distribute(trials); 112 | std::cout << "stdev: " << rslt.stdev << " mean: " << rslt.mean << " spread: " << (rslt.stdev / rslt.mean) << std::endl; 113 | 114 | std::cout << "distribution of bits:" << std::endl; 115 | rslt = bitHistogram(trials); 116 | std::cout << "stdev: " << rslt.stdev << " mean: " << rslt.mean << " spread: " << (rslt.stdev / rslt.mean) << std::endl; 117 | 118 | std::cout << "distribution of bytes:" << std::endl; 119 | rslt = byteHistogram(trials); 120 | std::cout << "stdev: " << rslt.stdev << " mean: " << rslt.mean << " spread: " << (rslt.stdev / rslt.mean) << std::endl; 121 | 122 | double t = speed(trials); 123 | std::cout << "time: " << t << " (" << (trials/t) << " words/sec)" << std::endl; 124 | } 125 | 126 | void RandomBattery::plot(double *bins, int n_bins, double max_value){ 127 | int char_width = 80; 128 | for (int bin = 0; bin < n_bins; bin++){ 129 | int stars = round(char_width * (bins[bin] / max_value)); 130 | for (int s = 0; s < stars; s++){ 131 | std::cout << "*"; 132 | } 133 | std::cout << std::endl; 134 | } 135 | } 136 | 137 | 138 | 139 | } /* namespace geom */ 140 | -------------------------------------------------------------------------------- /test/RandomBattery.h: -------------------------------------------------------------------------------- 1 | /* 2 | * RandomBattery.h 3 | * 4 | * Created on: Nov 11, 2012 5 | * Author: tbabb 6 | */ 7 | 8 | #ifndef RANDOMBATTERY_H_ 9 | #define RANDOMBATTERY_H_ 10 | 11 | #include 12 | 13 | namespace geom { 14 | 15 | struct result_t { 16 | double stdev; 17 | double mean; 18 | }; 19 | 20 | class RandomBattery { 21 | public: 22 | RandomBattery(Random *rng); 23 | virtual ~RandomBattery(); 24 | 25 | result_t distribute(int trials); 26 | result_t bitHistogram(int trials); 27 | result_t byteHistogram(int trials); 28 | double speed(int trials); 29 | 30 | void runAll(int trials); 31 | 32 | void plot(double *bins, int n_bins, double max_value); 33 | 34 | protected: 35 | Random *rng; 36 | }; 37 | 38 | } /* namespace geom */ 39 | #endif /* RANDOMBATTERY_H_ */ 40 | -------------------------------------------------------------------------------- /test/Test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Test.cpp 3 | * 4 | * Created on: Nov 21, 2010 5 | * Author: tbabb 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | 12 | using namespace std; 13 | using namespace geom; 14 | 15 | typedef Raster Image2d3f; 16 | 17 | int main_test(int arc, char** argv){ 18 | //int main(int arc, char** argv){ 19 | Vec d = Vec(512,256); 20 | Image2d3f img = Image2d3f(d); 21 | 22 | img.set(Vec2i(10,10), Vec3f(1,0,0)); 23 | 24 | 25 | for (int i = 0; i < 40; i++){ 26 | double s = 8 + i/10.0; 27 | cout << s << " " << img.sample(Vec2d(10.62314,s)) << endl; 28 | } 29 | 30 | double b[4][4] = { 31 | {0.10, 0.18, 0.31, 0.20}, 32 | {0.16, 0.23, 0.52, 0.35}, 33 | {0.30, 0.50, 0.57, 0.21}, 34 | {0.12, 0.36, 0.42, 0.14} 35 | }; 36 | 37 | cout << endl << "---------" << endl << endl; 38 | 39 | for (int i = 0; i < 10; i++){ 40 | double s[2] = {i / 10.0, 0.5}; 41 | cout << s[0] << " " << interp_cubic(s, b[0], 2) << std::endl; 42 | } 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /test/ortho.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define N 6 6 | 7 | using namespace std; 8 | using namespace geom; 9 | 10 | void ortho() { 11 | Sampler smp; 12 | Vec basis[N-1]; 13 | for (int i = 0; i < N-1; i++) { 14 | basis[i] = smp.unit(); 15 | cout << basis[i] << "\n"; 16 | } 17 | cout << "\n"; 18 | Vec o = orthogonal(basis); 19 | cout << "N: " << o << "\n"; 20 | for (int i = 0; i < N-1; i++) { 21 | cout << "dot: " << basis[i].dot(o) << "\n"; 22 | } 23 | } 24 | 25 | 26 | void nullsp() { 27 | Sampler smp; 28 | Vec basis[N]; 29 | index_t n = getRandom()->rand(N-2) + 1; 30 | cout << "given basis:\n"; 31 | for (int i = 0; i < n; i++) { 32 | basis[i] = smp.unit(); 33 | cout << basis[i] << "\n"; 34 | } 35 | 36 | nullspace(basis, n, basis + n); 37 | //orthonormalize(basis+n, N-n); 38 | 39 | cout << "\nnullspace basis:\n"; 40 | for(int i = 0; i < N-n; i++) { 41 | cout << basis[n+i] << "\n"; 42 | } 43 | cout << "\ndots with original basis:\n"; 44 | for (int b = 0; b < N-n; b++) { 45 | for (int q = 0; q < n; q++) { 46 | cout << "dot: " << basis[n+b].dot(basis[q]) << "\n"; 47 | } 48 | } 49 | cout << "\ndots with selves:\n"; 50 | for (int b = 0; b < N-n; b++) { 51 | for (int b2 = 0; b2 < N-n; b2++) { 52 | if (b2 == b) continue; 53 | cout << "dot: " << basis[n+b].dot(basis[n+b2]) << "\n"; 54 | } 55 | } 56 | cout << "\nlengths:\n"; 57 | for(int b = 0; b < N-n; b++) { 58 | cout << basis[n+b].mag() << "\n"; 59 | } 60 | } 61 | 62 | 63 | void orthonormal() { 64 | Sampler smp; 65 | Vec basis[N]; 66 | cout << "given basis:\n"; 67 | for (int i = 0; i < N; i++) { 68 | basis[i] = smp.unit(); 69 | cout << basis[i] << "\n"; 70 | } 71 | orthonormalize(basis,N); 72 | for (int i = 0; i < N; i++) { 73 | cout << "basis " << i << "\n"; 74 | for (int j = 0; j < N; j++) { 75 | if (j == i) continue; 76 | cout << " " << basis[i].dot(basis[j]) << "\n"; 77 | } 78 | } 79 | 80 | } 81 | 82 | 83 | int main(int argc, char **argv) { 84 | cout << "\n====== orthogonal ======\n"; 85 | ortho(); 86 | cout << "\n\n====== nullspace ======\n"; 87 | nullsp(); 88 | cout << "\n\n====== orthonormal =======\n"; 89 | orthonormal(); 90 | return 0; 91 | } 92 | --------------------------------------------------------------------------------