├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── UNLICENSE ├── dub.json ├── meson.build └── source └── bettercmath ├── box.d ├── cmath.d ├── easings.d ├── hexagrid2d.d ├── matrix.d ├── misc.d ├── package.d ├── transform.d ├── valuerange.d └── vector.d /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = spaces 5 | indent_size = 4 6 | 7 | [*.{build,json}] 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: gilzoide # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: gilzoide # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.buymeacoffee.com/gilzoide'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /bettercmath 6 | bettercmath.so 7 | bettercmath.dylib 8 | bettercmath.dll 9 | bettercmath.a 10 | bettercmath.lib 11 | bettercmath-test-* 12 | *.exe 13 | *.o 14 | *.obj 15 | *.lst 16 | libbettercmath.* 17 | 18 | # adrdox stuff 19 | skeleton.html 20 | generated-docs/ 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | ### Added 3 | - Setter functions for `BoundingBox.center` property 4 | - `Vector.opBinaryRight` with static arrays 5 | - `Vector` constructor from a Vector-like struct, that is, one 6 | with all fields of same type 7 | - `Vector.opCast` for a Vector-like struct type, that is, one 8 | with all fields of same type 9 | 10 | ### Changed 11 | - Renamed `BoundingBox.start` to `BoundingBox.origin` 12 | - Added support for static arrays as arguments in `BoundingBox` 13 | 14 | ### Fixed 15 | - Variadic `Vector` constructor to be called only if more 16 | than one argument is passed 17 | 18 | 19 | ## [0.3.1] - 2021-01-25 20 | ### Added 21 | - `Vector.opBinary` and `Vector.opBinaryRight` versions that accepts non-T arguments 22 | 23 | ### Fixed 24 | - Import valuerange in package.d 25 | - Avoid overflow/underflow in `lerp` 26 | - Make `Vector._get` and `Vector._slice` public 27 | 28 | ## [0.3.0] - 2021-01-15 29 | ### Added 30 | - **box** submodule, with type and dimension generic axis-aligned bounding box template 31 | - `width`, `height` and `depth` aliases for `Vector` elements 32 | - `vector` overload that receive elements and infers element type using `std.traits.CommonType` 33 | 34 | ### Changed 35 | - Changed target type to source library in `dub.json` and `meson.build`, 36 | as all package functionality is within templates 37 | 38 | ### Fixed 39 | - Purity of Vector constructor with Range argument 40 | 41 | ### Removed 42 | - `vector.map` function, use `std.range.map` instead 43 | 44 | 45 | ## [0.2.0] - 2020-12-28 46 | ### Added 47 | - `Matrix.copyInto` function that copies values between matrices of any dimensions 48 | - `Matrix.opCast` function to cast between matrices of different dimensions 49 | - `TransformOptions` enum for specifying template options, for now if Transform 50 | should use a compact matrix type 51 | - `Transform.copyInto` function that copies a Transform into another 52 | - `Transform.copyInto` function that copies a Transform into a Transform-like 53 | Matrix type (NxN or NxN-1) 54 | - `Transform.opCast` function to cast between Transform types 55 | - `EasingFunction` alias to easing functions' type 56 | - This CHANGELOG file 57 | 58 | ### Changed 59 | - Added `auto ref` to several Matrix and Vector functions' arguments 60 | - Changed `Vector.opCast` to accept `T2[N]` instead of `Vector!(T2, N)` 61 | - Forced easing functions return types to `T` 62 | 63 | ### Fixed 64 | - Shearing transformations when passing a single value 65 | 66 | ### Removed 67 | - Transform template compact parameter, in favor of `TransformOptions` 68 | - Transfrom's `full`, `compact`, `fullInto` and `compactInto` functions, 69 | in favor of `copyInto` and `opCast` 70 | - Shearing transformations for 1D Transforms 71 | 72 | 73 | ## [0.1.0] - 2020-12-23 74 | ### Added 75 | - DUB package manifest 76 | - Meson project file 77 | - README file with an overview about the package and it's submodules 78 | - **cmath** submodule, with type generic standard math function wrappers 79 | - **easings** submodule, with type generic easing functions 80 | - **hexagrid2d** submodule, with 2D hexagon grid math functionality 81 | - **matrix** submodule, with type generic matrices 82 | - **misc** submodule, with miscelaneous functions 83 | - **transform** submodule, with type and dimension generic affine 84 | transformation matrices 85 | - **valuerange** submodule, with type generic scalar value ranges for 86 | value interpolation and remapping 87 | - **vector** submodule, with type and dimension generic vector math 88 | 89 | [Unreleased]: https://github.com/gilzoide/bettercmath/compare/v0.3.1...HEAD 90 | [0.3.1]: https://github.com/gilzoide/bettercmath/compare/v0.3.0...v0.3.1 91 | [0.3.0]: https://github.com/gilzoide/bettercmath/compare/v0.2.0...v0.3.0 92 | [0.2.0]: https://github.com/gilzoide/bettercmath/compare/v0.1.0...v0.2.0 93 | [0.1.0]: https://github.com/gilzoide/bettercmath/releases/tag/v0.1.0 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bettercmath 2 | A `-betterC` compatible 3D math library for [D](https://dlang.org/). 3 | 4 | It is available as a [DUB package](https://code.dlang.org/packages/bettercmath), 5 | has [online documentation](https://bettercmath.dpldocs.info/) 6 | and may be used directly as a [Meson subproject](https://mesonbuild.com/Subprojects.html) 7 | or [wrap](https://mesonbuild.com/Wrap-dependency-system-manual.html). 8 | 9 | 10 | ## Submodules 11 | 12 | - **box**: Type and dimension generic Axis-Aligned Bounding Boxes 13 | - **cmath**: Standard math type generic functions and constants, using D runtime ([std.math](https://dlang.org/phobos/std_math.html)) 14 | on [CTFE](https://tour.dlang.org/tour/en/gems/compile-time-function-evaluation-ctfe) 15 | and C runtime ([core.stdc.math](https://dlang.org/phobos/core_stdc_math.html)) otherwise 16 | - **easings**: Type generic easing functions based on 17 | - **hexagrid2d**: 2D Hexagon grid math based on 18 | - **matrix**: Type and dimension generic Matrix type for use in linear algebra 19 | - **misc**: Miscelaneous math functions (angle measure transformation, type generic linear interpolation) 20 | - **transform**: Type and dimension generic Affine Transformations backed by possibly compacted Matrices 21 | - **valuerange**: Inclusive scalar value ranges for interpolating and remapping between ranges 22 | - **vector**: Type and dimension generic Vector type for use in linear algebra 23 | 24 | 25 | ## TODO 26 | - Support row-major matrices 27 | - Color submodule 28 | - Gradient submodule 29 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bettercmath", 3 | "authors": [ 4 | "gilzoide" 5 | ], 6 | "description": "3D math library compatible with betterC", 7 | "license": "public license", 8 | "configurations": [ 9 | { 10 | "name": "library" 11 | }, 12 | { 13 | "name": "betterC", 14 | "buildOptions": [ 15 | "betterC" 16 | ] 17 | } 18 | ], 19 | "targetType": "sourceLibrary" 20 | } 21 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('bettercmath', 'd') 2 | 3 | sources = files( 4 | 'source/bettercmath/box.d', 5 | 'source/bettercmath/cmath.d', 6 | 'source/bettercmath/easings.d', 7 | 'source/bettercmath/hexagrid2d.d', 8 | 'source/bettercmath/package.d', 9 | 'source/bettercmath/matrix.d', 10 | 'source/bettercmath/misc.d', 11 | 'source/bettercmath/transform.d', 12 | 'source/bettercmath/valuerange.d', 13 | 'source/bettercmath/vector.d', 14 | ) 15 | incdir = include_directories('source') 16 | 17 | bettercmath_dep = declare_dependency( 18 | sources: sources, 19 | include_directories: incdir, 20 | ) 21 | -------------------------------------------------------------------------------- /source/bettercmath/box.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Type and dimension generic Axis-Aligned Bounding Box (AABB). 3 | */ 4 | module bettercmath.box; 5 | 6 | @safe @nogc nothrow pure: 7 | 8 | version (unittest) 9 | { 10 | alias Rectangle = BoundingBox!(float, 2, BoundingBoxOptions.storeSize); 11 | } 12 | 13 | /// Options for the BoundingBox template. 14 | enum BoundingBoxOptions 15 | { 16 | /// Default options: store `end` corner information and derive `size`. 17 | none = 0, 18 | /// Store `size` information and derive `end` corner. 19 | storeSize = 1, 20 | } 21 | 22 | /** 23 | * Generic Axis-Aligned Bounding Box. 24 | * 25 | * May be stored as the starting and ending corners, 26 | * or as starting point and size. 27 | * 28 | * Params: 29 | * T = Element type 30 | * N = Box dimension, must be positive 31 | * options = Additional options, like storage meaning 32 | */ 33 | struct BoundingBox(T, uint Dim, BoundingBoxOptions options = BoundingBoxOptions.none) 34 | if (Dim > 0) 35 | { 36 | import bettercmath.vector : Vector; 37 | 38 | alias ElementType = T; 39 | /// Bounding Box dimension. 40 | enum dimension = Dim; 41 | /// Point type, a Vector with the same type and dimension. 42 | alias Point = Vector!(T, Dim); 43 | private alias PointArg = T[Dim]; 44 | /// Size type, a Vector with the same type and dimension. 45 | alias Size = Vector!(T, Dim); 46 | private alias SizeArg = T[Dim]; 47 | 48 | private enum storeSize = options & BoundingBoxOptions.storeSize; 49 | 50 | /// Starting BoundingBox corner. 51 | Point origin = 0; 52 | 53 | static if (storeSize) 54 | { 55 | /// Size of a BoundingBox, may be negative. 56 | Size size = 1; 57 | 58 | /// Get the `end` corner of a BoundingBox. 59 | @property Point end() const 60 | { 61 | return origin + size; 62 | } 63 | /// Set the `end` corner of a BoundingBox. 64 | @property void end(const PointArg value) 65 | { 66 | size = value - origin; 67 | } 68 | 69 | /// Cast BoundingBox to another storage type. 70 | auto opCast(U : BoundingBox!(T, Dim, options ^ BoundingBoxOptions.storeSize))() const 71 | { 72 | typeof(return) box = { 73 | origin = this.origin, 74 | end = this.end, 75 | }; 76 | return box; 77 | } 78 | } 79 | else 80 | { 81 | /// Ending BoundingBox corner. 82 | Point end = 1; 83 | 84 | /// Get the size of a BoundingBox, may be negative. 85 | @property Size size() const 86 | { 87 | return end - origin; 88 | } 89 | /// Set the size of a BoundingBox, using `origin` as the pivot. 90 | @property void size(const SizeArg value) 91 | { 92 | end = origin + value; 93 | } 94 | 95 | /// Cast BoundingBox to another storage type. 96 | auto opCast(U : BoundingBox!(T, Dim, options ^ BoundingBoxOptions.storeSize))() const 97 | { 98 | typeof(return) box = { 99 | origin = this.origin, 100 | size = this.size, 101 | }; 102 | return box; 103 | } 104 | } 105 | 106 | /// Get the width of a BoundingBox, may be negative. 107 | @property T width() const 108 | { 109 | return size.width; 110 | } 111 | /// Set the width of a BoundingBox, using `origin` as the pivot. 112 | @property void width(const T value) 113 | { 114 | auto s = size; 115 | s.width = value; 116 | size = s; 117 | } 118 | 119 | static if (Dim >= 2) 120 | { 121 | /// Get the height of a BoundingBox, may be negative. 122 | @property T height() const 123 | { 124 | return size.height; 125 | } 126 | /// Set the height of a BoundingBox, using `origin` as the pivot. 127 | @property void height(const T value) 128 | { 129 | auto s = size; 130 | s.height = value; 131 | size = s; 132 | } 133 | } 134 | static if (Dim >= 3) 135 | { 136 | /// Get the depth of a BoundingBox, may be negative. 137 | @property T depth() const 138 | { 139 | return size.depth; 140 | } 141 | /// Set the depth of a BoundingBox, using `origin` as the pivot. 142 | @property void depth(const T value) 143 | { 144 | auto s = size; 145 | s.depth = value; 146 | size = s; 147 | } 148 | } 149 | 150 | /// Get the central point of BoundingBox. 151 | @property Point center() const 152 | { 153 | return (origin + end) / 2; 154 | } 155 | /// Set the central point of BoundingBox. 156 | @property void center(const PointArg value) 157 | { 158 | immutable delta = value - center; 159 | origin += delta; 160 | static if (!storeSize) 161 | { 162 | end += delta; 163 | } 164 | } 165 | /// Ditto 166 | @property void center(const T value) 167 | { 168 | center(Point(value)); 169 | } 170 | /// 171 | unittest 172 | { 173 | Rectangle rect; 174 | rect.center = 2; 175 | assert(rect.size == Rectangle.init.size); 176 | rect.center = [1, 2]; 177 | assert(rect.size == Rectangle.init.size); 178 | } 179 | 180 | /// Returns whether BoundingBox have any non-positive size values. 181 | @property bool empty() const 182 | { 183 | import std.algorithm : any; 184 | return size[].any!"a <= 0"; 185 | } 186 | 187 | /// Returns a copy of BoundingBox with sorted corners, so that `size` only presents non-negative values. 188 | BoundingBox abs() const 189 | { 190 | import std.algorithm : swap; 191 | typeof(return) result = this; 192 | foreach (i; 0 .. result.dimension) 193 | { 194 | if (result.origin[i] < result.end[i]) 195 | { 196 | swap(result.origin[i], result.end[i]); 197 | } 198 | } 199 | return result; 200 | } 201 | 202 | /// Get the volume of the BoundingBox. 203 | @property T volume() const 204 | { 205 | import std.algorithm : fold; 206 | return size.fold!"a * b"; 207 | } 208 | 209 | static if (Dim == 2) 210 | { 211 | /// 2D area is the same as generic box volume. 212 | alias area = volume; 213 | } 214 | 215 | static if (Dim == 3) 216 | { 217 | /// Get the surface area of a 3D BoundingBox. 218 | @property T surfaceArea() const 219 | { 220 | auto s = size; 221 | return 2 * (s.x * s.y + s.y * s.z + s.x * s.z); 222 | } 223 | } 224 | 225 | /// Returns a new BoundingBox by insetting this one by `delta`. 226 | BoundingBox inset(const SizeArg delta) 227 | { 228 | immutable halfDelta = Size(delta) / 2; 229 | typeof(return) box; 230 | box.origin = this.origin + halfDelta; 231 | box.size = this.size - halfDelta; 232 | return box; 233 | } 234 | /// Ditto 235 | BoundingBox inset(const T delta) 236 | { 237 | return inset(Size(delta)); 238 | } 239 | 240 | /// Returns true if Point is contained within BoundingBox. 241 | bool contains(T, uint N)(const auto ref T[N] point) const 242 | { 243 | import std.algorithm : all, map, min; 244 | import std.range : iota; 245 | enum minDimension = min(this.dimension, N); 246 | return iota(minDimension).map!(i => point[i] >= origin[i] && point[i] <= end[i]).all; 247 | } 248 | 249 | /// Returns true if `box` is completely contained within `this` BoundingBox. 250 | bool contains(Args...)(const auto ref BoundingBox!(T, Args) box) const 251 | { 252 | return contains(box.origin) && contains(box.end); 253 | } 254 | 255 | /// Returns the intersection between two BoundingBoxes. 256 | auto intersection(Args...)(const auto ref BoundingBox!(T, Args) box) const 257 | { 258 | import std.algorithm : map, min, max; 259 | import std.range : iota, zip; 260 | enum minDimension = min(this.dimension, box.dimension); 261 | BoundingBox!(T, minDimension, options) result; 262 | result.origin = zip(this.origin[0 .. minDimension], box.origin[0 .. minDimension]).map!(max); 263 | result.end = zip(this.end[0 .. minDimension], box.end[0 .. minDimension]).map!(min); 264 | return result; 265 | } 266 | 267 | /// Returns true if `box` intersects `this`. 268 | auto intersects(Args...)(const auto ref BoundingBox!(T, Args) box) const 269 | { 270 | return !intersection(box).empty; 271 | } 272 | } 273 | 274 | /// Common alias for Bounding Boxes. 275 | alias AABB = BoundingBox; 276 | -------------------------------------------------------------------------------- /source/bettercmath/cmath.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Type generic standard math functions and constants. 3 | */ 4 | module bettercmath.cmath; 5 | 6 | import cmath = core.stdc.math; 7 | import dmath = std.math; 8 | import std.meta : AliasSeq; 9 | 10 | import bettercmath.misc : FloatType; 11 | 12 | private enum functions = AliasSeq!( 13 | "acos", 14 | "asin", 15 | "atan", 16 | "atan2", 17 | "cos", 18 | "sin", 19 | "tan", 20 | "acosh", 21 | "asinh", 22 | "atanh", 23 | "cosh", 24 | "sinh", 25 | "tanh", 26 | "exp", 27 | "exp2", 28 | "expm1", 29 | "frexp", 30 | "ilogb", 31 | "ldexp", 32 | "log", 33 | "log10", 34 | "log1p", 35 | "log2", 36 | "logb", 37 | "modf", 38 | "scalbn", 39 | "scalbln", 40 | "cbrt", 41 | "fabs", 42 | "hypot", 43 | "pow", 44 | "sqrt", 45 | "erf", 46 | "erfc", 47 | "lgamma", 48 | "tgamma", 49 | "ceil", 50 | "floor", 51 | "nearbyint", 52 | "rint", 53 | "lrint", 54 | "llrint", 55 | "round", 56 | "lround", 57 | "llround", 58 | "trunc", 59 | "fmod", 60 | "remainder", 61 | "remquo", 62 | "copysign", 63 | "nan", 64 | "nextafter", 65 | "nexttoward", 66 | "fdim", 67 | "fmax", 68 | "fmin", 69 | "fma", 70 | ); 71 | 72 | static foreach (f; functions) 73 | { 74 | mixin(q{alias } ~ f ~ q{ = MathFunc!} ~ "\"" ~ f ~ "\".opCall;"); 75 | } 76 | 77 | private enum constants = AliasSeq!( 78 | "E", 79 | "PI", 80 | "PI_2", 81 | "PI_4", 82 | "M_1_PI", 83 | "M_2_PI", 84 | "M_2_SQRTPI", 85 | "LN10", 86 | "LN2", 87 | "LOG2", 88 | "LOG2E", 89 | "LOG2T", 90 | "LOG10E", 91 | "SQRT2", 92 | "SQRT1_2", 93 | ); 94 | 95 | static foreach (c; constants) 96 | { 97 | mixin(q{alias } ~ c ~ q{ = MathConst!} ~ "\"" ~ c ~ "\";"); 98 | } 99 | 100 | // Private helpers for templated math function calls 101 | private string cfuncname(T : double, string f)() 102 | { 103 | return f; 104 | } 105 | private string cfuncname(T : real, string f)() 106 | { 107 | return f ~ "l"; 108 | } 109 | private string cfuncname(T : float, string f)() 110 | { 111 | return f ~ "f"; 112 | } 113 | private string cfuncname(T : long, string f)() 114 | { 115 | return f ~ "f"; 116 | } 117 | 118 | /** 119 | * Template wrapper for standard library math functions. 120 | * 121 | * On CTFE, calls the D runtime math (std.math) functions. 122 | * On runtime, calls the right variant of the C runtime math (core.stdc.math) functions. 123 | */ 124 | template MathFunc(string f) 125 | { 126 | template opCall(T, Args...) 127 | { 128 | import std.traits : ReturnType; 129 | private alias dfunc = __traits(getMember, dmath, f); 130 | private alias cfunc = __traits(getMember, cmath, cfuncname!(T, f)()); 131 | 132 | nothrow @nogc ReturnType!cfunc opCall(T arg1, Args args) 133 | { 134 | if (__ctfe) 135 | { 136 | // Use D functions on CTFE 137 | return dfunc(cast(FloatType!T) arg1, args); 138 | } 139 | else 140 | { 141 | // Use the appropriate C function on runtime 142 | return cfunc(arg1, args); 143 | } 144 | } 145 | } 146 | } 147 | 148 | /// Template wrapper for typed versions of the standard library math constants. 149 | private template MathConst(string c) 150 | { 151 | private alias dconst = __traits(getMember, dmath, c); 152 | enum MathConst(T = real) = cast(FloatType!T) dconst; 153 | } 154 | -------------------------------------------------------------------------------- /source/bettercmath/easings.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Type generic easing functions. 3 | */ 4 | module bettercmath.easings; 5 | 6 | import bettercmath.cmath; 7 | 8 | // Easings based on https://easings.net/ 9 | // and this implementation: https://github.com/warrenm/AHEasing/ 10 | 11 | private T squared(T)(const T p) 12 | { 13 | return p * p; 14 | } 15 | 16 | // Modeled after the line y = x 17 | T linear(T)(const T p) 18 | { 19 | return p; 20 | } 21 | 22 | // Modeled after the parabola y = x^2 23 | T easeInQuadratic(T)(const T p) 24 | { 25 | return p * p; 26 | } 27 | alias easeInQuad = easeInQuadratic; 28 | 29 | // Modeled after the parabola y = -x^2 + 2x 30 | T easeOutQuadratic(T)(const T p) 31 | { 32 | return -(p * (p - 2)); 33 | } 34 | alias easeOutQuad = easeOutQuadratic; 35 | 36 | // Modeled after the piecewise quadratic 37 | // y = (1/2)((2x)^2) ; [0, 0.5) 38 | // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1] 39 | T easeInOutQuadratic(T)(const T p) 40 | { 41 | if(p < 0.5) 42 | { 43 | return 2 * p * p; 44 | } 45 | else 46 | { 47 | return (-2 * p * p) + (4 * p) - 1; 48 | } 49 | } 50 | alias easeInOutQuad = easeInOutQuadratic; 51 | 52 | // Modeled after the cubic y = x^3 53 | T easeInCubic(T)(const T p) 54 | { 55 | return p * p * p; 56 | } 57 | 58 | // Modeled after the cubic y = (x - 1)^3 + 1 59 | T easeOutCubic(T)(const T p) 60 | { 61 | auto f = (p - 1); 62 | return f * f * f + 1; 63 | } 64 | 65 | // Modeled after the piecewise cubic 66 | // y = (1/2)((2x)^3) ; [0, 0.5) 67 | // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1] 68 | T easeInOutCubic(T)(const T p) 69 | { 70 | if(p < 0.5) 71 | { 72 | return 4 * p * p * p; 73 | } 74 | else 75 | { 76 | auto f = ((2 * p) - 2); 77 | return 0.5 * f * f * f + 1; 78 | } 79 | } 80 | 81 | // Modeled after the quartic x^4 82 | T easeInQuartic(T)(const T p) 83 | { 84 | return p * p * p * p; 85 | } 86 | alias easeInQuart = easeInQuartic; 87 | 88 | // Modeled after the quartic y = 1 - (x - 1)^4 89 | T easeOutQuartic(T)(const T p) 90 | { 91 | auto f = (p - 1); 92 | return f * f * f * (1 - p) + 1; 93 | } 94 | alias easeOutQuart = easeOutQuartic; 95 | 96 | // Modeled after the piecewise quartic 97 | // y = (1/2)((2x)^4) ; [0, 0.5) 98 | // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1] 99 | T easeInOutQuartic(T)(const T p) 100 | { 101 | if(p < 0.5) 102 | { 103 | return 8 * p * p * p * p; 104 | } 105 | else 106 | { 107 | auto f = (p - 1); 108 | return -8 * f * f * f * f + 1; 109 | } 110 | } 111 | alias easeInOutQuart = easeInOutQuartic; 112 | 113 | // Modeled after the quintic y = x^5 114 | T easeInQuintic(T)(const T p) 115 | { 116 | return p * p * p * p * p; 117 | } 118 | alias easeInQuint = easeInQuintic; 119 | 120 | // Modeled after the quintic y = (x - 1)^5 + 1 121 | T easeOutQuintic(T)(const T p) 122 | { 123 | auto f = (p - 1); 124 | return f * f * f * f * f + 1; 125 | } 126 | alias easeOutQuint = easeOutQuintic; 127 | 128 | // Modeled after the piecewise quintic 129 | // y = (1/2)((2x)^5) ; [0, 0.5) 130 | // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1] 131 | T easeInOutQuintic(T)(const T p) 132 | { 133 | if(p < 0.5) 134 | { 135 | return 16 * p * p * p * p * p; 136 | } 137 | else 138 | { 139 | auto f = ((2 * p) - 2); 140 | return 0.5 * f * f * f * f * f + 1; 141 | } 142 | } 143 | alias easeInOutQuint = easeInOutQuintic; 144 | 145 | // Modeled after quarter-cycle of sine wave 146 | T easeInSine(T)(const T p) 147 | { 148 | return sin((p - 1) * PI_2!T) + 1; 149 | } 150 | 151 | // Modeled after quarter-cycle of sine wave (different phase) 152 | T easeOutSine(T)(const T p) 153 | { 154 | return sin(p * PI_2!T); 155 | } 156 | 157 | // Modeled after half sine wave 158 | T easeInOutSine(T)(const T p) 159 | { 160 | return 0.5 * (1 - cos(p * PI!T)); 161 | } 162 | 163 | // Modeled after shifted quadrant IV of unit circle 164 | T easeInCircular(T)(const T p) 165 | { 166 | return 1 - sqrt(1 - (p * p)); 167 | } 168 | alias easeInCirc = easeInCircular; 169 | 170 | // Modeled after shifted quadrant II of unit circle 171 | T easeOutCircular(T)(const T p) 172 | { 173 | return sqrt((2 - p) * p); 174 | } 175 | alias easeOutCirc = easeOutCircular; 176 | 177 | // Modeled after the piecewise circular function 178 | // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5) 179 | // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] 180 | T easeInOutCircular(T)(const T p) 181 | { 182 | if(p < 0.5) 183 | { 184 | return 0.5 * (1 - sqrt(1 - 4 * (p * p))); 185 | } 186 | else 187 | { 188 | return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1); 189 | } 190 | } 191 | alias easeInOutCirc = easeInOutCircular; 192 | 193 | // Modeled after the exponential function y = 2^(10(x - 1)) 194 | T easeInExponential(T)(const T p) 195 | { 196 | return (p == 0.0) ? p : squared(10 * (p - 1)); 197 | } 198 | alias easeInExpo = easeInExponential; 199 | 200 | // Modeled after the exponential function y = -2^(-10x) + 1 201 | T easeOutExponential(T)(const T p) 202 | { 203 | return (p == 1.0) ? p : 1 - squared(-10 * p); 204 | } 205 | alias easeOutExpo = easeOutExponential; 206 | 207 | // Modeled after the piecewise exponential 208 | // y = (1/2)2^(10(2x - 1)) ; [0,0.5) 209 | // y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1] 210 | T easeInOutExponential(T)(const T p) 211 | { 212 | if(p == 0.0 || p == 1.0) return p; 213 | 214 | if(p < 0.5) 215 | { 216 | return 0.5 * squared((20 * p) - 10); 217 | } 218 | else 219 | { 220 | return -0.5 * squared((-20 * p) + 10) + 1; 221 | } 222 | } 223 | alias easeInOutExpo = easeInOutExponential; 224 | 225 | // Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1)) 226 | T easeInElastic(T)(const T p) 227 | { 228 | return sin(13 * PI_2!T * p) * squared(10 * (p - 1)); 229 | } 230 | 231 | // Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 232 | T easeOutElastic(T)(const T p) 233 | { 234 | return sin(-13 * PI_2!T * (p + 1)) * squared(-10 * p) + 1; 235 | } 236 | 237 | // Modeled after the piecewise exponentially-damped sine wave: 238 | // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5) 239 | // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1] 240 | T easeInOutElastic(T)(const T p) 241 | { 242 | if(p < 0.5) 243 | { 244 | return 0.5 * sin(13 * PI_2!T * (2 * p)) * squared(10 * ((2 * p) - 1)); 245 | } 246 | else 247 | { 248 | return 0.5 * (sin(-13 * PI_2!T * ((2 * p - 1) + 1)) * squared(-10 * (2 * p - 1)) + 2); 249 | } 250 | } 251 | 252 | // Modeled after the overshooting cubic y = x^3-x*sin(x*pi) 253 | T easeInBack(T)(const T p) 254 | { 255 | return p * p * p - p * sin(p * PI!T); 256 | } 257 | 258 | // Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi)) 259 | T easeOutBack(T)(const T p) 260 | { 261 | auto f = (1 - p); 262 | return 1 - (f * f * f - f * sin(f * PI!T)); 263 | } 264 | 265 | // Modeled after the piecewise overshooting cubic function: 266 | // y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5) 267 | // y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1] 268 | T easeInOutBack(T)(const T p) 269 | { 270 | if(p < 0.5) 271 | { 272 | auto f = 2 * p; 273 | return 0.5 * (f * f * f - f * sin(f * PI!T)); 274 | } 275 | else 276 | { 277 | auto f = (1 - (2*p - 1)); 278 | return 0.5 * (1 - (f * f * f - f * sin(f * PI!T))) + 0.5; 279 | } 280 | } 281 | 282 | T easeInBounce(T)(const T p) 283 | { 284 | return 1 - easeOutBounce(1 - p); 285 | } 286 | 287 | T easeOutBounce(T)(const T p) 288 | { 289 | if(p < 4/11.0) 290 | { 291 | return (121 * p * p)/16.0; 292 | } 293 | else if(p < 8/11.0) 294 | { 295 | return (363/40.0 * p * p) - (99/10.0 * p) + 17/5.0; 296 | } 297 | else if(p < 9/10.0) 298 | { 299 | return (4356/361.0 * p * p) - (35442/1805.0 * p) + 16061/1805.0; 300 | } 301 | else 302 | { 303 | return (54/5.0 * p * p) - (513/25.0 * p) + 268/25.0; 304 | } 305 | } 306 | 307 | T easeInOutBounce(T)(const T p) 308 | { 309 | if(p < 0.5) 310 | { 311 | return 0.5 * easeInBounce(p*2); 312 | } 313 | else 314 | { 315 | return 0.5 * easeOutBounce(p * 2 - 1) + 0.5; 316 | } 317 | } 318 | 319 | /// Alias for the easing functions' type. 320 | alias EasingFunction(T) = T function(const T); 321 | 322 | /** 323 | * Collection of all easings instanciated with the same type. 324 | */ 325 | template Easing(T) 326 | { 327 | alias linear = .linear!T; 328 | 329 | alias easeInQuadratic = .easeInQuadratic!T; 330 | alias easeInQuad = easeInQuadratic; 331 | alias easeOutQuadratic = .easeOutQuadratic!T; 332 | alias easeOutQuad = easeOutQuadratic; 333 | alias easeInOutQuadratic = .easeInOutQuadratic!T; 334 | alias easeInOutQuad = easeInOutQuadratic; 335 | 336 | alias easeInCubic = .easeInCubic!T; 337 | alias easeOutCubic = .easeOutCubic!T; 338 | alias easeInOutCubic = .easeInOutCubic!T; 339 | 340 | alias easeInQuartic = .easeInQuartic!T; 341 | alias easeInQuart = easeInQuartic; 342 | alias easeOutQuartic = .easeOutQuartic!T; 343 | alias easeOutQuart = easeOutQuartic; 344 | alias easeInOutQuartic = .easeInOutQuartic!T; 345 | alias easeInOutQuart = easeInOutQuartic; 346 | 347 | alias easeInQuintic = .easeInQuintic!T; 348 | alias easeInQuint= easeInQuintic; 349 | alias easeOutQuintic = .easeOutQuintic!T; 350 | alias easeOutQuint= easeOutQuintic; 351 | alias easeInOutQuintic = .easeInOutQuintic!T; 352 | alias easeInOutQuint= easeInOutQuintic; 353 | 354 | alias easeInSine = .easeInSine!T; 355 | alias easeOutSine = .easeOutSine!T; 356 | alias easeInOutSine = .easeInOutSine!T; 357 | 358 | alias easeInCircular = .easeInCircular!T; 359 | alias easeInCirc = easeInCircular; 360 | alias easeOutCircular = .easeOutCircular!T; 361 | alias easeOutCirc = easeOutCircular; 362 | alias easeInOutCircular = .easeInOutCircular!T; 363 | alias easeInOutCirc = easeInOutCircular; 364 | 365 | alias easeInExponential = .easeInExponential!T; 366 | alias easeInExpo = easeInExponential; 367 | alias easeOutExponential = .easeOutExponential!T; 368 | alias easeOutExpo = easeOutExponential; 369 | alias easeInOutExponential = .easeInOutExponential!T; 370 | alias easeInOutExpo = easeInOutExponential; 371 | 372 | alias easeInElastic = .easeInElastic!T; 373 | alias easeOutElastic = .easeOutElastic!T; 374 | alias easeInOutElastic = .easeInOutElastic!T; 375 | 376 | alias easeInBack = .easeInBack!T; 377 | alias easeOutBack = .easeOutBack!T; 378 | alias easeInOutBack = .easeInOutBack!T; 379 | 380 | alias easeInBounce = .easeInBounce!T; 381 | alias easeOutBounce = .easeOutBounce!T; 382 | alias easeInOutBounce = .easeInOutBounce!T; 383 | 384 | /// Get an easing function by name. 385 | /// This is useful for templates where the easing function 386 | /// will be passed by name, for example for configuring an 387 | /// animation in compile time. 388 | EasingFunction!T named(string name)() 389 | { 390 | mixin("return &" ~ name ~ ";"); 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /source/bettercmath/hexagrid2d.d: -------------------------------------------------------------------------------- 1 | /** 2 | * 2D hexagon grid math. 3 | * 4 | * See_Also: https://www.redblobgames.com/grids/hexagons/ 5 | */ 6 | module bettercmath.hexagrid2d; 7 | 8 | import std.algorithm : among; 9 | import std.traits : isFloatingPoint; 10 | 11 | import bettercmath.cmath; 12 | import bettercmath.vector; 13 | import bettercmath.matrix; 14 | import bettercmath.misc; 15 | 16 | @safe @nogc nothrow: 17 | 18 | private enum sqrt3 = sqrt(3); 19 | 20 | version (unittest) 21 | { 22 | private alias Hexi = Hex!(int); 23 | private alias Hexf = Hex!(float); 24 | } 25 | 26 | enum Orientation 27 | { 28 | pointy, 29 | flat, 30 | } 31 | 32 | struct Layout(Orientation orientation, FT = float) 33 | if (isFloatingPoint!FT) 34 | { 35 | pure: 36 | private alias Mat2 = Matrix!(FT, 2); 37 | private alias Vec2 = Vector!(FT, 2); 38 | private alias Vec2i = Vector!(int, 2); 39 | 40 | alias Hexagon = Hex!(int); 41 | alias FractionalHexagon = Hex!(FT); 42 | 43 | Vec2 origin; 44 | Vec2 size; 45 | 46 | static if (orientation == Orientation.pointy) 47 | { 48 | enum Directions 49 | { 50 | East = Hexagon(1, 0), 51 | E = East, 52 | NorthEast = Hexagon(1, -1), 53 | NE = NorthEast, 54 | NorthWest = Hexagon(0, -1), 55 | NW = NorthWest, 56 | West = Hexagon(-1, 0), 57 | W = West, 58 | SouthWest = Hexagon(-1, 1), 59 | SW = SouthWest, 60 | SouthEast = Hexagon(0, 1), 61 | SE = SouthEast, 62 | } 63 | private enum toPixelMatrix = Mat2.fromRows( 64 | sqrt3, sqrt3 / 2.0, 65 | 0, 3.0 / 2.0 66 | ); 67 | private enum fromPixelMatrix = Mat2.fromRows( 68 | sqrt3 / 3.0, -1.0 / 3.0, 69 | 0, 2.0 / 3.0 70 | ); 71 | private enum FT[6] angles = [30, 90, 150, 210, 270, 330]; 72 | } 73 | else 74 | { 75 | enum Directions 76 | { 77 | SouthEast = Hexagon(1, 0), 78 | SE = SouthEast, 79 | NorthEast = Hexagon(1, -1), 80 | NE = NorthEast, 81 | North = Hexagon(0, -1), 82 | N = North, 83 | NorthWest = Hexagon(-1, 0), 84 | NW = NorthWest, 85 | SouthWest = Hexagon(-1, 1), 86 | SW = SouthWest, 87 | South = Hexagon(0, 1), 88 | S = South, 89 | } 90 | private enum toPixelMatrix = Mat2.fromRows( 91 | 3.0 / 2.0, 0, 92 | sqrt3 / 2.0, sqrt3 93 | ); 94 | private enum fromPixelMatrix = Mat2.fromRows( 95 | 2.0 / 3.0, 0, 96 | -1.0 / 3.0, sqrt3 / 3.0 97 | ); 98 | private enum FT[6] angles = [0, 60, 120, 180, 240, 300]; 99 | } 100 | 101 | Vec2 toPixel(const Hexagon hex) const 102 | { 103 | typeof(return) result = toPixelMatrix * cast(Vec2) hex.coordinates; 104 | return result * size + origin; 105 | } 106 | 107 | FractionalHexagon fromPixel(const Vec2 originalPoint) const 108 | { 109 | const Vec2 point = (originalPoint - origin) / size; 110 | return typeof(return)(fromPixelMatrix * point); 111 | } 112 | 113 | Vec2[6] corners() const 114 | { 115 | typeof(return) result = void; 116 | foreach (i; 0 .. 6) 117 | { 118 | FT angle = deg2rad(angles[i]); 119 | result[i] = [size.x * cos(angle), size.y * sin(angle)]; 120 | } 121 | return result; 122 | } 123 | } 124 | 125 | struct Hex(T = int) 126 | { 127 | pure: 128 | alias ElementType = T; 129 | /// Axial coordinates, see https://www.redblobgames.com/grids/hexagons/implementation.html 130 | private Vector!(T, 2) _coordinates; 131 | @property const(typeof(_coordinates)) coordinates() const 132 | { 133 | return _coordinates; 134 | } 135 | 136 | @property T q() const 137 | { 138 | return coordinates[0]; 139 | } 140 | @property T r() const 141 | { 142 | return coordinates[1]; 143 | } 144 | @property T s() const 145 | { 146 | return -q -r; 147 | } 148 | 149 | this(T q, T r) 150 | { 151 | _coordinates = [q, r]; 152 | } 153 | this(T[2] coordinates) 154 | { 155 | _coordinates = coordinates; 156 | } 157 | 158 | // Operations 159 | Hex opBinary(string op)(const Hex other) const 160 | if (op.among("+", "-")) 161 | { 162 | return Hex(this.coordinates.opBinary!op(other.coordinates)); 163 | } 164 | 165 | Hex opBinary(string op : "*")(const int scale) const 166 | { 167 | Hex result; 168 | result.coordinates = coordinates * scale; 169 | return result; 170 | } 171 | 172 | T magnitude() const 173 | { 174 | import std.algorithm : sum; 175 | return cast(T)((fabs(q) + fabs(r) + fabs(s)) / 2); 176 | } 177 | 178 | T distanceTo(const Hex other) const 179 | { 180 | Hex vector = this - other; 181 | return vector.magnitude(); 182 | } 183 | } 184 | 185 | Hex!(int) rounded(FT)(const Hex!(FT) hex) 186 | if (isFloatingPoint!FT) 187 | { 188 | import std.algorithm : map; 189 | alias Vec3 = Vector!(FT, 3); 190 | Vec3 cubic_hex = hex.coordinates ~ hex.s; 191 | Vec3 roundedVec = cubic_hex[].map!(round); 192 | Vec3 diff = roundedVec - cubic_hex; 193 | 194 | if (diff[0] > diff[1] && diff[0] > diff[2]) 195 | { 196 | roundedVec[0] = -roundedVec[1] - roundedVec[2]; 197 | } 198 | else if (diff[1] > diff[2]) 199 | { 200 | roundedVec[1] = -roundedVec[0] - roundedVec[2]; 201 | } 202 | return typeof(return)(cast(int) roundedVec[0], cast(int) roundedVec[1]); 203 | } 204 | unittest 205 | { 206 | Hexf a = Hexf(2.1, 3.5); // -5.6 207 | assert(a.rounded() == Hexi(2, 4)); 208 | } 209 | 210 | struct RectangleHexagrid(Orientation orientation, T, uint columns, uint rows) 211 | { 212 | Layout!(orientation) layout; 213 | Hex!(int) hexagons; 214 | T[columns][rows] values; 215 | } 216 | -------------------------------------------------------------------------------- /source/bettercmath/matrix.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Type and dimension generic Matrix type. 3 | */ 4 | module bettercmath.matrix; 5 | 6 | @safe @nogc pure nothrow: 7 | 8 | version (unittest) 9 | { 10 | import bettercmath.vector; 11 | private alias Vec2 = Vector!(float, 2); 12 | private alias Vec3 = Vector!(float, 3); 13 | private alias Mat2 = Matrix!(float, 2); 14 | private alias Mat23 = Matrix!(float, 2, 3); 15 | private alias Mat32 = Matrix!(float, 3, 2); 16 | private alias Mat3 = Matrix!(float, 3); 17 | private alias Mat34 = Matrix!(float, 3, 4); 18 | private alias Mat43 = Matrix!(float, 4, 3); 19 | private alias Mat4 = Matrix!(float, 4); 20 | } 21 | 22 | /** 23 | * Column-major 2D matrix type. 24 | */ 25 | struct Matrix(T, uint numColumns, uint numRows = numColumns) 26 | if (numColumns > 0 && numRows > 0) 27 | { 28 | import std.algorithm : min; 29 | /// Alias for Matrix element type. 30 | alias ElementType = T; 31 | /// Number of elements in each row, same as the number of columns. 32 | enum rowSize = numColumns; 33 | /// Number of elements in each column, same as the number of rows. 34 | enum columnSize = numRows; 35 | /// Minimum dimension between number of rows and number of columns. 36 | enum minDimension = min(rowSize, columnSize); 37 | /// Total number of elements. 38 | enum numElements = rowSize * columnSize; 39 | /// Whether matrix is square or not. 40 | enum isSquare = rowSize == columnSize; 41 | 42 | /// Matrix underlying elements. 43 | T[numElements] elements = 0; 44 | 45 | /// Constructs a Matrix specifying all elements. 46 | this()(const auto ref T[numElements] values) 47 | { 48 | this.elements = values; 49 | } 50 | /// Constructs a Matrix specifying the diagonal value. 51 | this(const T diag) 52 | { 53 | foreach (i; 0 .. minDimension) 54 | { 55 | this[i, i] = diag; 56 | } 57 | } 58 | 59 | /// Copy Matrix values into `target` Matrix of any dimensions. 60 | /// If dimensions are not the same, the values at non-overlapping indices 61 | /// are ignored. 62 | auto ref copyInto(uint C, uint R)(ref return Matrix!(T, C, R) target) const 63 | { 64 | // If matrices have the same column size, underlying array may be copied at once 65 | static if (this.columnSize == target.columnSize) 66 | { 67 | enum copySize = min(this.numElements, target.numElements); 68 | target.elements[0 .. copySize] = this.elements[0 .. copySize]; 69 | } 70 | else 71 | { 72 | enum columnCopySize = min(this.columnSize, target.columnSize); 73 | enum rowCopySize = min(this.rowSize, target.rowSize); 74 | foreach (i; 0 .. rowCopySize) 75 | { 76 | target[i][0 .. columnCopySize] = this[i][0 .. columnCopySize]; 77 | } 78 | } 79 | return target; 80 | } 81 | 82 | /// Returns a copy of Matrix, adjusting dimensions as necessary. 83 | /// Non-overlapping indices will stay initialized to 0. 84 | U opCast(U : Matrix!(T, C, R), uint C, uint R)() const 85 | { 86 | typeof(return) result; 87 | return copyInto(result); 88 | } 89 | 90 | /// Returns a Range of all columns. 91 | auto columns() 92 | { 93 | import std.range : chunks; 94 | return elements[].chunks(columnSize); 95 | } 96 | /// Returns a Range of all columns. 97 | auto columns() const 98 | { 99 | import std.range : chunks; 100 | return elements[].chunks(columnSize); 101 | } 102 | /// Returns a Range of all rows. 103 | auto rows() 104 | { 105 | import std.range : lockstep, StoppingPolicy; 106 | return columns.lockstep(StoppingPolicy.requireSameLength); 107 | } 108 | /// Returns a Range of all rows. 109 | auto rows() const 110 | { 111 | import std.range : lockstep, StoppingPolicy; 112 | return columns.lockstep(StoppingPolicy.requireSameLength); 113 | } 114 | 115 | /// Index a column. 116 | inout(T)[] opIndex(size_t i) inout 117 | in { assert(i < rowSize, "Index out of bounds"); } 118 | do 119 | { 120 | auto initialIndex = i * columnSize; 121 | return elements[initialIndex .. initialIndex + columnSize]; 122 | } 123 | /// Index an element directly. 124 | /// Params: 125 | /// i = column index 126 | /// j = row index 127 | ref inout(T) opIndex(size_t i, size_t j) inout 128 | in { assert(i < rowSize && j < columnSize, "Index out of bounds"); } 129 | do 130 | { 131 | return elements[i*columnSize + j]; 132 | } 133 | 134 | /// Row size 135 | enum opDollar(size_t pos : 0) = rowSize; 136 | /// Column size 137 | enum opDollar(size_t pos : 1) = columnSize; 138 | 139 | /// Constructs a Matrix from all elements in column-major format. 140 | static Matrix fromColumns(Args...)(const auto ref Args args) 141 | if (args.length == numElements) 142 | { 143 | return Matrix([args]); 144 | } 145 | /// Constructs a Matrix from an array of all elements in column-major format. 146 | static Matrix fromColumns()(const auto ref T[numElements] elements) 147 | { 148 | return Matrix(elements); 149 | } 150 | /// Constructs a Matrix from a 2D array of columns. 151 | static Matrix fromColumns()(const auto ref T[rowSize][columnSize] columns) 152 | { 153 | return Matrix(cast(T[numElements]) columns); 154 | } 155 | 156 | /// Constructs a Matrix from row-major format 157 | static Matrix fromRows(Args...)(const auto ref Args args) 158 | { 159 | return Matrix!(T, columnSize, rowSize).fromColumns(args).transposed; 160 | } 161 | 162 | /// Constructs a Matrix with all diagonal values equal to `diag` and all others equal to 0. 163 | static Matrix fromDiagonal(const T diag) 164 | { 165 | return Matrix(diag); 166 | } 167 | /// Constructs a Matrix with diagonal values from `diag` and all others equal to 0. 168 | static Matrix fromDiagonal(uint N)(const auto ref T[N] diag) 169 | if (N <= minDimension) 170 | { 171 | Matrix mat; 172 | foreach (i; 0 .. N) 173 | { 174 | mat[i, i] = diag[i]; 175 | } 176 | return mat; 177 | } 178 | 179 | /// Returns the result of multiplying `vec` by Matrix. 180 | /// If matrix is not square, the resulting array dimension will be different from input. 181 | T[columnSize] opBinary(string op : "*")(const auto ref T[rowSize] vec) const 182 | { 183 | typeof(return) result; 184 | foreach (i; 0 .. columnSize) 185 | { 186 | T sum = 0; 187 | foreach (j; 0 .. rowSize) 188 | { 189 | sum += this[j, i] * vec[j]; 190 | } 191 | result[i] = sum; 192 | } 193 | return result; 194 | } 195 | /// 196 | unittest 197 | { 198 | auto m1 = Mat23.fromRows(1, 2, 199 | 3, 4, 200 | 5, 6); 201 | auto v1 = Vec2(1, 2); 202 | assert(m1 * v1 == Vec3(1*1 + 2*2, 203 | 1*3 + 2*4, 204 | 1*5 + 2*6)); 205 | } 206 | 207 | /// Returns the result of Matrix multiplication. 208 | Matrix!(T, OtherColumns, columnSize) opBinary(string op : "*", uint OtherColumns)( 209 | const auto ref Matrix!(T, OtherColumns, rowSize) other 210 | ) const 211 | { 212 | typeof(return) result = void; 213 | foreach (i; 0 .. columnSize) 214 | { 215 | foreach (j; 0 .. OtherColumns) 216 | { 217 | T sum = 0; 218 | foreach (k; 0 .. rowSize) 219 | { 220 | sum += this[k, i] * other[j, k]; 221 | } 222 | result[j, i] = sum; 223 | } 224 | } 225 | return result; 226 | } 227 | /// 228 | unittest 229 | { 230 | alias Mat23 = Matrix!(int, 2, 3); 231 | alias Mat12 = Matrix!(int, 1, 2); 232 | 233 | Mat23 m1 = Mat23.fromRows(1, 1, 234 | 2, 2, 235 | 3, 3); 236 | Mat12 m2 = Mat12.fromRows(4, 237 | 5); 238 | auto result = m1 * m2; 239 | assert(result.elements == [ 240 | 1*4 + 1*5, 241 | 2*4 + 2*5, 242 | 3*4 + 3*5, 243 | ]); 244 | } 245 | 246 | static if (isSquare) 247 | { 248 | /// Constant Identity matrix (diagonal values 1). 249 | enum identity = fromDiagonal(1); 250 | 251 | /// Inplace matrix multiplication with "*=" operator, only available for square matrices. 252 | ref Matrix opOpAssign(string op : "*")(const auto ref Matrix other) return 253 | { 254 | foreach (i; 0 .. columnSize) 255 | { 256 | foreach (j; 0 .. rowSize) 257 | { 258 | T sum = 0; 259 | foreach (k; 0 .. rowSize) 260 | { 261 | sum += this[k, i] * other[j, k]; 262 | } 263 | this[j, i] = sum; 264 | } 265 | } 266 | return this; 267 | } 268 | 269 | // TODO: determinant, inverse matrix, at least for 2x2, 3x3 and 4x4 270 | } 271 | 272 | 273 | // Matrix 4x4 methods 274 | static if (rowSize == 4 && columnSize == 4) 275 | { 276 | /// Returns an orthographic projection matrix. 277 | /// See_Also: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml 278 | static Matrix orthographic(T left, T right, T bottom, T top, T near = -1, T far = 1) 279 | { 280 | Matrix result; 281 | 282 | result[0, 0] = 2.0 / (right - left); 283 | result[1, 1] = 2.0 / (top - bottom); 284 | result[2, 2] = 2.0 / (near - far); 285 | result[3, 3] = 1.0; 286 | 287 | result[3, 0] = (left + right) / (left - right); 288 | result[3, 1] = (bottom + top) / (bottom - top); 289 | result[3, 2] = (far + near) / (near - far); 290 | 291 | return result; 292 | } 293 | alias ortho = orthographic; 294 | 295 | /// Calls `perspective` converting angle from degrees to radians. 296 | /// See_Also: perspective 297 | static auto perspectiveDegrees(T fovDegrees, T aspectRatio, T near, T far) 298 | { 299 | import bettercmath.misc : degreesToRadians; 300 | return perspective(degreesToRadians(fovDegrees), aspectRatio, near, far); 301 | } 302 | /// Returns a perspective projection matrix. 303 | /// See_Also: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml 304 | static Matrix perspective(T fov, T aspectRatio, T near, T far) 305 | in { assert(near > 0, "Near clipping pane should be positive"); assert(far > 0, "Far clipping pane should be positive"); } 306 | do 307 | { 308 | Matrix result; 309 | 310 | import bettercmath.cmath : tan; 311 | T cotangent = 1.0 / tan(fov * 0.5); 312 | 313 | result[0, 0] = cotangent / aspectRatio; 314 | result[1, 1] = cotangent; 315 | result[2, 3] = -1.0; 316 | result[2, 2] = (near + far) / (near - far); 317 | result[3, 2] = (2.0 * near * far) / (near - far); 318 | 319 | return result; 320 | } 321 | } 322 | } 323 | 324 | /// True if `T` is some kind of Matrix 325 | enum isMatrix(T) = is(T : Matrix!U, U...); 326 | 327 | /// Transpose a square matrix inplace. 328 | ref Matrix!(T, C, C) transpose(T, uint C)(ref return Matrix!(T, C, C) mat) 329 | { 330 | import std.algorithm : swap; 331 | foreach (i; 0 .. C) 332 | { 333 | foreach (j; i+1 .. C) 334 | { 335 | swap(mat[j, i], mat[i, j]); 336 | } 337 | } 338 | return mat; 339 | } 340 | /// 341 | unittest 342 | { 343 | auto m1 = Mat2.fromRows(1, 2, 344 | 3, 4); 345 | transpose(m1); 346 | assert(m1 == Mat2.fromRows(1, 3, 347 | 2, 4)); 348 | } 349 | 350 | /// Returns a transposed copy of `mat`. 351 | Matrix!(T, R, C) transposed(T, uint C, uint R)(const auto ref Matrix!(T, C, R) mat) 352 | { 353 | typeof(return) newMat = void; 354 | foreach (i; 0 .. R) 355 | { 356 | foreach (j; 0 .. C) 357 | { 358 | newMat[i, j] = mat[j, i]; 359 | } 360 | } 361 | return newMat; 362 | } 363 | /// 364 | unittest 365 | { 366 | float[6] elements = [1, 2, 3, 4, 5, 6]; 367 | float[6] transposedElements = [1, 4, 2, 5, 3, 6]; 368 | auto m1 = Mat23.fromColumns(elements); 369 | auto m2 = transposed(m1); 370 | assert(m2.elements == transposedElements); 371 | assert(transposed(m1.transposed) == m1); 372 | } 373 | 374 | -------------------------------------------------------------------------------- /source/bettercmath/misc.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Miscelaneous math functions and definitions. 3 | */ 4 | module bettercmath.misc; 5 | 6 | import std.math : PI; 7 | import std.traits : isFloatingPoint, isNumeric; 8 | 9 | /// Templated alias for a floating point type correspondent with `T`. 10 | template FloatType(T) 11 | if (isNumeric!T) 12 | { 13 | static if (isFloatingPoint!T) 14 | { 15 | alias FloatType = T; 16 | } 17 | else 18 | { 19 | alias FloatType = float; 20 | } 21 | } 22 | 23 | /// Convert angle from degrees to radians. 24 | FloatType!T degreesToRadians(T)(const T degrees) 25 | { 26 | return degrees * (PI / 180.0); 27 | } 28 | alias deg2rad = degreesToRadians; /// ditto 29 | 30 | /// Convert angle from radias to degrees. 31 | FloatType!T radiansToDegrees(T)(const T radians) 32 | { 33 | return radians * (180.0 / PI); 34 | } 35 | alias rad2deg = radiansToDegrees; /// ditto 36 | 37 | /// Linearly interpolates values `from` and `to` by `amount`. 38 | T lerp(T, U)(const T from, const T to, const U amount) 39 | { 40 | enum U one = 1; 41 | return cast(T) (amount * to + (one - amount) * from); 42 | } 43 | /// Linearly interpolates the values from `fromTo` by `amount`. 44 | T lerp(T, U)(const T[2] fromTo, const U amount) 45 | { 46 | return lerp(fromTo[0], fromTo[1], amount); 47 | } 48 | -------------------------------------------------------------------------------- /source/bettercmath/package.d: -------------------------------------------------------------------------------- 1 | module bettercmath; 2 | 3 | public import bettercmath.box; 4 | public import bettercmath.cmath; 5 | public import bettercmath.easings; 6 | public import bettercmath.hexagrid2d; 7 | public import bettercmath.matrix; 8 | public import bettercmath.misc; 9 | public import bettercmath.transform; 10 | public import bettercmath.vector; 11 | public import bettercmath.valuerange; 12 | -------------------------------------------------------------------------------- /source/bettercmath/transform.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Type and dimension generic Affine Transformations backed by possibly compacted Matrices. 3 | */ 4 | module bettercmath.transform; 5 | 6 | /// Options for the Transform template. 7 | enum TransformOptions 8 | { 9 | /// Default options. 10 | none = 0, 11 | /// Use a compact Matrix type, as Affine Transformation matrices always 12 | /// have the last row [0 ... 0 1] 13 | compact = 1 << 0, 14 | } 15 | 16 | @nogc @safe pure nothrow: 17 | 18 | /** 19 | * Affine Transformation matrix. 20 | * 21 | * Params: 22 | * T = Value types, should be numeric 23 | * Dim = Space dimensions, pass 2 for 2D, 3 for 3D, etc... 24 | * options = Additional options 25 | */ 26 | struct Transform(T, uint Dim, TransformOptions options = TransformOptions.none) 27 | if (Dim > 0) 28 | { 29 | import std.algorithm : min; 30 | 31 | import bettercmath.cmath : cos, sin; 32 | import bettercmath.matrix : Matrix; 33 | import bettercmath.misc : FloatType, degreesToRadians; 34 | 35 | alias ElementType = T; 36 | /// Transform dimension. 37 | enum dimension = Dim; 38 | /// Whether Transform is compact. 39 | enum bool isCompact = options & TransformOptions.compact; 40 | static if (!isCompact) 41 | { 42 | /// The underlying matrix type 43 | alias MatrixType = Matrix!(T, Dim + 1, Dim + 1); 44 | private alias CompactTransform = Transform!(T, Dim, TransformOptions.compact); 45 | 46 | private static bool isAffineTransformMatrix(const MatrixType matrix) 47 | { 48 | import std.algorithm : equal; 49 | import std.range : chain, only, repeat; 50 | return matrix.rows[Dim].equal(repeat(Dim, 0).chain(only(1))); 51 | } 52 | } 53 | else 54 | { 55 | /// The underlying matrix type 56 | alias MatrixType = Matrix!(T, Dim + 1, Dim); 57 | private alias CompactTransform = typeof(this); 58 | 59 | private static bool isAffineTransformMatrix(const MatrixType _) 60 | { 61 | return true; 62 | } 63 | } 64 | private alias FT = FloatType!T; 65 | 66 | /// Cast between Transform types of any dimension. 67 | U opCast(U : Transform!(T, Args), Args...)() const 68 | { 69 | typeof(return) result; 70 | return copyInto(result); 71 | } 72 | 73 | /// Copy Transform contents into `target` transform of any dimension and options. 74 | auto ref copyInto(Args...)(ref return Transform!(T, Args) target) const 75 | { 76 | copyInto(target.matrix); 77 | return target; 78 | } 79 | 80 | /// Copy Transform contents into a matrix of Transform-like dimensions. 81 | auto ref copyInto(uint C, uint R)(ref return Matrix!(T, C, R) target) const 82 | if (C == R || C == R + 1) 83 | { 84 | static if (target.rowSize == this.matrix.rowSize) 85 | { 86 | matrix.copyInto(target); 87 | } 88 | else 89 | { 90 | enum minDimension = min(C - 1, this.dimension); 91 | foreach (i; 0 .. minDimension) 92 | { 93 | target[i][0 .. minDimension] = this.matrix[i][0 .. minDimension]; 94 | } 95 | // translations must be in the last column, so copy them separately 96 | target[$-1][0 .. minDimension] = this.matrix[$-1][0 .. minDimension]; 97 | } 98 | return target; 99 | } 100 | 101 | 102 | /// The underlying matrix. 103 | MatrixType matrix = MatrixType.fromDiagonal(1); 104 | alias matrix this; 105 | 106 | /// The identity Transform. 107 | enum identity = Transform.init; 108 | 109 | /// Construct a Transform from matrix. 110 | this()(const auto ref MatrixType mat) 111 | in { assert(isAffineTransformMatrix(mat), "Matrix is not suitable for affine transformations"); } 112 | do 113 | { 114 | this.matrix = mat; 115 | } 116 | 117 | /// Reset a Transform to identity. 118 | ref Transform setIdentity() return 119 | { 120 | this = identity; 121 | return this; 122 | } 123 | 124 | /// Transform an array of values of any dimension. 125 | T[N] transform(uint N)(const auto ref T[N] values) const 126 | { 127 | enum minDimension = min(N, Dim + 1); 128 | typeof(return) result; 129 | foreach (i; 0 .. Dim) 130 | { 131 | T sum = 0; 132 | foreach (j; 0 .. minDimension) 133 | { 134 | sum += matrix[j, i] * values[j]; 135 | } 136 | static if (N < Dim + 1) 137 | { 138 | sum += matrix[$-1, i]; 139 | } 140 | result[i] = sum; 141 | } 142 | return result; 143 | } 144 | /// Transform an array of values of any dimension. 145 | auto opBinary(string op : "*", uint N)(const auto ref T[N] values) const 146 | { 147 | return transform(values); 148 | } 149 | 150 | /// Constructs a new Transform representing a translation. 151 | static Transform fromTranslation(uint N)(const auto ref T[N] values) 152 | { 153 | enum minDimension = min(N, Dim); 154 | Transform t; 155 | t[$-1][0 .. minDimension] = values[0 .. minDimension]; 156 | return t; 157 | } 158 | /// Apply translation in-place. 159 | /// Returns: this 160 | ref Transform translate(uint N)(const auto ref T[N] values) return 161 | { 162 | enum minDimension = min(N, Dim); 163 | this[$-1][0 .. minDimension] += values[0 .. minDimension]; 164 | return this; 165 | } 166 | /// Returns a translated copy of Transform. 167 | Transform translated(uint N)(const auto ref T[N] values) const 168 | { 169 | Transform t = this; 170 | return t.translate(values); 171 | } 172 | 173 | /// Constructs a new Transform representing a scaling. 174 | static Transform fromScaling(uint N)(const auto ref T[N] values) 175 | { 176 | enum minDimension = min(N, Dim); 177 | Transform t; 178 | foreach (i; 0 .. minDimension) 179 | { 180 | t[i, i] = values[i]; 181 | } 182 | return t; 183 | } 184 | /// Apply scaling in-place. 185 | /// Returns: this 186 | ref Transform scale(uint N)(const auto ref T[N] values) return 187 | { 188 | return this.combine(CompactTransform.fromScaling(values)); 189 | } 190 | /// Returns a scaled copy of Transform. 191 | Transform scaled(uint N)(const auto ref T[N] values) const 192 | { 193 | Transform t = this; 194 | return t.scale(values); 195 | } 196 | 197 | // 2D transforms 198 | static if (Dim >= 2) 199 | { 200 | /// Constructs a new Transform representing a shearing. 201 | static Transform fromShearing(uint N)(const auto ref T[N] values) 202 | { 203 | enum minDimension = min(N, Dim); 204 | Transform t; 205 | foreach (i; 0 .. minDimension) 206 | { 207 | foreach (j; 0 .. Dim) 208 | { 209 | if (j != i) 210 | { 211 | t[j, i] = values[i]; 212 | } 213 | } 214 | } 215 | return t; 216 | } 217 | /// Apply shearing in-place. 218 | /// Returns: this 219 | ref Transform shear(uint N)(const auto ref T[N] values) return 220 | { 221 | return this.combine(CompactTransform.fromShearing(values)); 222 | } 223 | /// Returns a sheared copy of Transform. 224 | Transform sheared(uint N)(const auto ref T[N] values) const 225 | { 226 | Transform t = this; 227 | return t.shear(values); 228 | } 229 | 230 | /// Constructs a new Transform representing a 2D rotation. 231 | /// Params: 232 | /// angle = Rotation angle in radians 233 | static Transform fromRotation(const FT angle) 234 | { 235 | Transform t; 236 | immutable auto c = cos(angle), s = sin(angle); 237 | t[0, 0] = c; t[0, 1] = -s; 238 | t[1, 0] = s; t[1, 1] = c; 239 | return t; 240 | } 241 | /// Constructs a new Transform representing a 2D rotation. 242 | /// Params: 243 | /// angle = Rotation angle in degrees 244 | static auto fromRotationDegrees(const FT degrees) 245 | { 246 | return fromRotation(degreesToRadians(degrees)); 247 | } 248 | /// Apply 2D rotation in-place. 249 | /// Params: 250 | /// angle = Rotation angle in radians 251 | /// Returns: this 252 | ref Transform rotate(const FT angle) return 253 | { 254 | return this.combine(CompactTransform.fromRotation(angle)); 255 | } 256 | /// Apply 2D rotation in-place. 257 | /// Params: 258 | /// angle = Rotation angle in degrees 259 | auto rotateDegrees(const FT degrees) 260 | { 261 | return rotate(degreesToRadians(degrees)); 262 | } 263 | /// Returns a rotated copy of Transform. 264 | /// Params: 265 | /// angle = Rotation angle in radians 266 | Transform rotated(const FT angle) const 267 | { 268 | Transform t = this; 269 | return t.rotate(angle); 270 | } 271 | /// Returns a rotated copy of Transform. 272 | /// Params: 273 | /// angle = Rotation angle in degrees 274 | auto rotatedDegrees(const FT degrees) const 275 | { 276 | return rotated(degreesToRadians(degrees)); 277 | } 278 | } 279 | // 3D transforms 280 | static if (Dim >= 3) 281 | { 282 | /// Constructs a new Transform representing a 3D rotation aroud the X axis. 283 | /// Params: 284 | /// angle = Rotation angle in radians 285 | static Transform fromXRotation(const FT angle) 286 | { 287 | Transform t; 288 | immutable auto c = cos(angle), s = sin(angle); 289 | t[1, 1] = c; t[2, 1] = -s; 290 | t[1, 2] = s; t[2, 2] = c; 291 | return t; 292 | } 293 | /// Constructs a new Transform representing a 3D rotation aroud the X axis. 294 | /// Params: 295 | /// angle = Rotation angle in degrees 296 | static auto fromXRotationDegrees(const FT degrees) 297 | { 298 | return fromXRotation(degreesToRadians(degrees)); 299 | } 300 | /// Apply 3D rotation around the X axis in-place. 301 | /// Params: 302 | /// angle = Rotation angle in radians 303 | ref Transform rotateX(const FT angle) return 304 | { 305 | return this.combine(CompactTransform.fromXRotation(angle)); 306 | } 307 | /// Apply 3D rotation around the X axis in-place. 308 | /// Params: 309 | /// angle = Rotation angle in degrees 310 | auto rotateXDegrees(const FT degrees) 311 | { 312 | return rotateX(degreesToRadians(degrees)); 313 | } 314 | /// Returns a copy of Transform rotated around the X axis. 315 | /// Params: 316 | /// angle = Rotation angle in radians 317 | Transform rotatedX(const FT angle) const 318 | { 319 | Transform t = this; 320 | return t.rotateX(angle); 321 | } 322 | /// Returns a copy of Transform rotated around the X axis. 323 | /// Params: 324 | /// angle = Rotation angle in degrees 325 | auto rotatedXDegrees(const FT degrees) 326 | { 327 | return rotatedX(degreesToRadians(degrees)); 328 | } 329 | 330 | 331 | /// Constructs a new Transform representing a 3D rotation aroud the Y axis. 332 | /// Params: 333 | /// angle = Rotation angle in radians 334 | static Transform fromYRotation(const FT angle) 335 | { 336 | Transform t; 337 | immutable auto c = cos(angle), s = sin(angle); 338 | t[0, 0] = c; t[2, 0] = s; 339 | t[0, 2] = -s; t[2, 2] = c; 340 | return t; 341 | } 342 | /// Constructs a new Transform representing a 3D rotation aroud the Y axis. 343 | /// Params: 344 | /// angle = Rotation angle in degrees 345 | static auto fromYRotationDegrees(const FT degrees) 346 | { 347 | return fromYRotation(degreesToRadians(degrees)); 348 | } 349 | /// Apply 3D rotation around the Y axis in-place. 350 | /// Params: 351 | /// angle = Rotation angle in radians 352 | ref Transform rotateY(const FT angle) return 353 | { 354 | return this.combine(CompactTransform.fromYRotation(angle)); 355 | } 356 | /// Apply 3D rotation around the Y axis in-place. 357 | /// Params: 358 | /// angle = Rotation angle in degrees 359 | auto rotateYDegrees(const FT degrees) 360 | { 361 | return rotateY(degreesToRadians(degrees)); 362 | } 363 | /// Returns a copy of Transform rotated around the Y axis. 364 | /// Params: 365 | /// angle = Rotation angle in radians 366 | Transform rotatedY(const FT angle) const 367 | { 368 | Transform t = this; 369 | return t.rotateY(angle); 370 | } 371 | /// Returns a copy of Transform rotated around the Y axis. 372 | /// Params: 373 | /// angle = Rotation angle in degrees 374 | auto rotatedYDegrees(const FT degrees) 375 | { 376 | return rotatedY(degreesToRadians(degrees)); 377 | } 378 | 379 | // Rotating in Z is the same as rotating in 2D 380 | alias fromZRotation = fromRotation; 381 | alias fromZRotationDegrees = fromRotationDegrees; 382 | alias rotateZ = rotate; 383 | alias rotateZDegrees = rotateDegrees; 384 | alias rotatedZ = rotated; 385 | alias rotatedZDegrees = rotatedDegrees; 386 | } 387 | } 388 | 389 | /// Pre-multiply `transformation` into `target`, returning a reference to `target` 390 | auto ref combine(T, uint Dim, TransformOptions O1, TransformOptions O2)( 391 | ref return Transform!(T, Dim, O1) target, 392 | const auto ref Transform!(T, Dim, O2) transformation 393 | ) 394 | { 395 | target = target.combined(transformation); 396 | return target; 397 | } 398 | /// Returns the result of pre-multiplying `transformation` and `target` 399 | Transform!(T, Dim, O1) combined(T, uint Dim, TransformOptions O1, TransformOptions O2)( 400 | const auto ref Transform!(T, Dim, O1) target, 401 | const auto ref Transform!(T, Dim, O2) transformation 402 | ) 403 | { 404 | // Just about matrix multiplication, but assuming last row is [0...0 1] 405 | typeof(return) result; 406 | foreach (i; 0 .. Dim) 407 | { 408 | foreach (j; 0 .. Dim + 1) 409 | { 410 | T sum = 0; 411 | foreach (k; 0 .. Dim) 412 | { 413 | sum += transformation[k, i] * target[j, k]; 414 | } 415 | result[j, i] = sum; 416 | } 417 | // Last column has to take input's last row's 1 418 | result[Dim, i] += transformation[Dim, i]; 419 | } 420 | return result; 421 | } 422 | 423 | unittest 424 | { 425 | alias Transform2D = Transform!(float, 2); 426 | alias Transform2DCompact = Transform!(float, 2, TransformOptions.compact); 427 | alias Transform3D = Transform!(float, 3); 428 | alias Transform3DCompact = Transform!(float, 3, TransformOptions.compact); 429 | } 430 | -------------------------------------------------------------------------------- /source/bettercmath/valuerange.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Inclusive scalar value ranges for interpolating and remapping values. 3 | */ 4 | module bettercmath.valuerange; 5 | 6 | /// Remap `value` from the range [inputStart, inputEnd] to [outputStart, outputEnd]. 7 | T remap(T)(const T value, const T inputStart, const T inputEnd, const T outputStart, const T outputEnd) 8 | { 9 | return (value - inputStart) / (inputEnd - inputStart) * (outputEnd - outputStart) + outputStart; 10 | } 11 | 12 | /// Remap `value` from the `input` range to `output` range. 13 | T remap(T)(const T value, const ValueRange!T input, const ValueRange!T output) 14 | { 15 | return remap(value, input.from, input.to, output.from, output.to); 16 | } 17 | 18 | /** 19 | * Range of scalar values, for more easily interpolating and remapping them. 20 | */ 21 | struct ValueRange(T) 22 | { 23 | /// Alias for ValueRange element type. 24 | alias ElementType = T; 25 | /// Value that starts the range. 26 | T from = 0; 27 | /// Value that ends the range. 28 | T to = 1; 29 | 30 | /// Construct from both values. 31 | this(const T from, const T to) 32 | { 33 | this.from = from; 34 | this.to = to; 35 | } 36 | /// Construct from array of values. 37 | this(const T[2] values) 38 | { 39 | from = values[0]; 40 | to = values[1]; 41 | } 42 | 43 | /// Invert ValueRange inplace. 44 | ref ValueRange invert() return 45 | { 46 | import std.algorithm : swap; 47 | swap(from, to); 48 | return this; 49 | } 50 | 51 | /// Returns an inverted copy of ValueRange, the range [to, from]. 52 | ValueRange inverted() const 53 | { 54 | typeof(return) r = this; 55 | return r.invert(); 56 | } 57 | 58 | /// Linearly interpolates range by `amount`. 59 | T lerp(U)(const U amount) const 60 | { 61 | import bettercmath.misc : lerp; 62 | return lerp(from, to, amount); 63 | } 64 | 65 | /// Remap `value` from this range to `newRange`. 66 | T remap(const T value, const ValueRange newRange) const 67 | { 68 | return .remap(value, this, newRange); 69 | } 70 | 71 | /// Return `value` normalized by this range, so that 0 represents the start of the range and 1 represents the end of it. 72 | T normalize(const T value) const 73 | { 74 | return (value - from) / (to - from); 75 | } 76 | 77 | /// Returns the distance between the range start and end. 78 | T distance() const 79 | { 80 | return to - from; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /source/bettercmath/vector.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Type and dimension generic Vector backed by a static array. 3 | */ 4 | module bettercmath.vector; 5 | 6 | import std.algorithm : among, copy, max, min, sum; 7 | import std.meta; 8 | import std.range; 9 | import std.traits; 10 | 11 | import bettercmath.cmath; 12 | import bettercmath.misc : FloatType; 13 | /// `lerp` function for UFCS 14 | public import bettercmath.misc : lerp; 15 | 16 | @safe @nogc nothrow: 17 | 18 | version (unittest) 19 | { 20 | private alias Vec1 = Vector!(float, 1); 21 | private alias Vec2 = Vector!(float, 2); 22 | private alias Vec2i = Vector!(int, 2); 23 | private alias Vec2ub = Vector!(ubyte, 2); 24 | private alias Vec3 = Vector!(float, 3); 25 | private alias Vec4 = Vector!(float, 4); 26 | private alias Vec4bool = Vector!(bool, 4); 27 | private struct OtherVec2 28 | { 29 | float x; 30 | float y; 31 | } 32 | private struct NotAVec2 33 | { 34 | float i; 35 | string t; 36 | } 37 | } 38 | 39 | private template VectorStruct(U) 40 | if (is(U == struct)) 41 | { 42 | private alias FieldTypes = Fields!U; 43 | alias ElementType = FieldTypes[0]; 44 | private enum isElementType(T) = is(T == ElementType); 45 | enum isVectorStruct = allSatisfy!(isElementType, FieldTypes); 46 | enum fieldNames = FieldNameTuple!U; 47 | enum length = fieldNames.length; 48 | } 49 | 50 | 51 | /** 52 | * Generic Vector backed by a static array. 53 | * 54 | * Params: 55 | * T = Element type 56 | * N = Vector dimension, must be positive 57 | */ 58 | struct Vector(T, uint N) 59 | if (N > 0) 60 | { 61 | /// Alias for Vector element type. 62 | alias ElementType = T; 63 | /// Vector dimension. 64 | enum dimension = N; 65 | /// Element array. 66 | T[N] elements = 0; 67 | alias elements this; 68 | 69 | ref inout(T) _get(size_t i)() inout pure 70 | in { assert(i < N, "Index out of bounds"); } 71 | do 72 | { 73 | return elements[i]; 74 | } 75 | ref inout(T[to - from]) _slice(size_t from, size_t to)() inout pure 76 | in { assert(from <= N - 1 && to <= N, "Index out of bounds"); } 77 | do 78 | { 79 | return elements[from .. to]; 80 | } 81 | 82 | /// Get a reference to first element. 83 | alias x = _get!(0); 84 | /// Ditto 85 | alias r = x; 86 | /// Ditto 87 | alias u = x; 88 | /// Ditto 89 | alias s = x; 90 | /// Ditto 91 | alias width = x; 92 | 93 | static if (N >= 2) 94 | { 95 | /// Get a reference to second element. 96 | alias y = _get!(1); 97 | /// Ditto 98 | alias g = y; 99 | /// Ditto 100 | alias v = y; 101 | /// Ditto 102 | alias t = y; 103 | /// Ditto 104 | alias height = y; 105 | 106 | /// Get a reference to the first and second elements. 107 | alias xy = _slice!(0, 2); 108 | /// Ditto 109 | alias rg = xy; 110 | /// Ditto 111 | alias uv = xy; 112 | /// Ditto 113 | alias st = xy; 114 | } 115 | static if (N >= 3) 116 | { 117 | /// Get a reference to third element. 118 | alias z = _get!(2); 119 | /// Ditto 120 | alias b = z; 121 | /// Ditto 122 | alias p = z; 123 | /// Ditto 124 | alias depth = z; 125 | 126 | /// Get a reference to the second and third elements. 127 | alias yz = _slice!(1, 3); 128 | /// Ditto 129 | alias gb = yz; 130 | /// Ditto 131 | alias tp = yz; 132 | 133 | /// Get a reference to the first, second and third elements. 134 | alias xyz = _slice!(0, 3); 135 | /// Ditto 136 | alias rgb = xyz; 137 | /// Ditto 138 | alias stp = xyz; 139 | } 140 | static if (N >= 4) 141 | { 142 | /// Get a reference to fourth element. 143 | alias w = _get!(3); 144 | /// Ditto 145 | alias a = w; 146 | /// Ditto 147 | alias q = w; 148 | 149 | /// Get a reference to the third and fourth elements. 150 | alias zw = _slice!(2, 4); 151 | /// Ditto 152 | alias ba = zw; 153 | /// Ditto 154 | alias pq = zw; 155 | 156 | /// Get a reference to the second, third and fourth elements. 157 | alias yzw = _slice!(1, 4); 158 | /// Ditto 159 | alias gba = yzw; 160 | /// Ditto 161 | alias tpq = yzw; 162 | 163 | /// Get a reference to the first, second, third and fourth elements. 164 | alias xyzw = _slice!(0, 4); 165 | /// Ditto 166 | alias rgba = xyzw; 167 | /// Ditto 168 | alias stpq = xyzw; 169 | } 170 | 171 | unittest 172 | { 173 | Vec2 v = [1, 2]; 174 | assert(v.x == 1); 175 | assert(v.x == v[0]); 176 | assert(v.y == 2); 177 | assert(v.y == v[1]); 178 | v.x = 2; 179 | assert(v.r == 2); 180 | 181 | Vec4 v2 = [1, 2, 3, 4]; 182 | assert(v2.xy == [1, 2]); 183 | assert(v2.yz == [2, 3]); 184 | assert(v2.zw == [3, 4]); 185 | v2.xyz = 0; 186 | assert(v2 == [0, 0, 0, 4]); 187 | } 188 | 189 | /// Constructs a Vector with all elements equal to `scalar` 190 | this(const T scalar) pure 191 | { 192 | elements[] = scalar; 193 | } 194 | /// 195 | unittest 196 | { 197 | alias Vec2 = Vector!(float, 2); 198 | Vec2 v; 199 | v = Vec2(1); 200 | assert(v == [1, 1]); 201 | v = Vec2(2.0); 202 | assert(v == [2, 2]); 203 | v = Vec2(3.0f); 204 | assert(v == [3, 3]); 205 | } 206 | /// Constructs a Vector from static array. 207 | this()(const auto ref T[N] values) pure 208 | { 209 | elements = values; 210 | } 211 | /// 212 | unittest 213 | { 214 | alias Vec2 = Vector!(float, 2); 215 | Vec2 v; 216 | v = Vec2([1, 2]); 217 | assert(v == [1f, 2f]); 218 | v = Vec2([2.0, 3.0]); 219 | assert(v == [2f, 3f]); 220 | v = Vec2([3.0f, 4.0f]); 221 | assert(v == [3f, 4f]); 222 | } 223 | /// Constructs a Vector with elements from an Input Range. 224 | /// Values not provided by `range` are initialized to 0, 225 | /// while additional values are ignored. 226 | this(R)(auto ref R range) 227 | if (isInputRange!R) 228 | { 229 | auto remainder = range.take(N).copy(elements[]); 230 | remainder[] = 0; 231 | } 232 | /// 233 | unittest 234 | { 235 | float[] values = [1, 2]; 236 | assert(Vec4(values) == [1, 2, 0, 0]); 237 | 238 | import std.range : iota, stride; 239 | assert(Vec4(iota(4)) == [0, 1, 2, 3]); 240 | assert(Vec4(iota(8).stride(2)) == [0, 2, 4, 6]); 241 | assert(Vec4(iota(6)) == [0, 1, 2, 3]); 242 | 243 | import std.algorithm : map; 244 | auto isEven = iota(1024).map!(x => x % 2 == 0); 245 | assert(Vec4bool(isEven) == [true, false, true, false]); 246 | } 247 | /// Constructs a Vector from a Vector-like struct. 248 | this(U)(const auto ref U value) 249 | if (is(U == struct) && VectorStruct!U.isVectorStruct) 250 | { 251 | alias vectorStruct = VectorStruct!U; 252 | static foreach (i; 0 .. min(vectorStruct.length, N)) 253 | { 254 | elements[i] = cast(T) mixin("value." ~ vectorStruct.fieldNames[i]); 255 | } 256 | static if (vectorStruct.length < N) 257 | { 258 | elements[vectorStruct.length .. N] = 0; 259 | } 260 | } 261 | /// 262 | unittest 263 | { 264 | Vec2 v = OtherVec2(2, 3); 265 | assert(v == Vec2(2, 3)); 266 | Vec4 v4 = OtherVec2(4, 5); 267 | assert(v4 == Vec4(4, 5, 0, 0)); 268 | assert(!__traits(compiles, { Vec2 v = NotAVec2(); })); 269 | } 270 | /// Constructs a Vector with all elements initialized separately 271 | this(Args...)(const auto ref Args args) 272 | if (args.length > 1 && args.length <= N) 273 | { 274 | foreach (i, value; args) 275 | { 276 | elements[i] = cast(T) value; 277 | } 278 | static if (Args.length < N) 279 | { 280 | elements[Args.length .. N] = 0; 281 | } 282 | } 283 | /// 284 | unittest 285 | { 286 | Vec3 v; 287 | v = Vec3(1, 2); 288 | assert(v == [1, 2, 0]); 289 | v = Vec3(1, 2, 3); 290 | assert(v == [1, 2, 3]); 291 | } 292 | 293 | pure: 294 | /// Vector with all zeros 295 | enum Vector zeros = 0; 296 | alias zeroes = zeros; 297 | /// Vector with all ones 298 | enum Vector ones = 1; 299 | 300 | /// Returns a new vector with unary operator applied to all elements 301 | Vector opUnary(string op)() const 302 | if (op.among("-", "+", "~")) 303 | { 304 | Vector result; 305 | mixin(q{result =} ~ op ~ q{elements[];}); 306 | return result; 307 | } 308 | /// 309 | unittest 310 | { 311 | assert(-Vec2(1, -2) == [-1, 2]); 312 | } 313 | 314 | /// Returns a new vector with binary operator applied to all elements and `scalar` 315 | Vector opBinary(string op)(const T scalar) const 316 | if (!op.among("~", "<<", ">>", ">>>")) 317 | { 318 | Vector result; 319 | mixin(q{result = elements[]} ~ op ~ q{scalar;}); 320 | return result; 321 | } 322 | /// Ditto 323 | auto opBinary(string op, U)(const U scalar) const 324 | if (!is(U : T) && !is(CommonType!(U, T) == void) && !op.among("~", "<<", ">>", ">>>")) 325 | { 326 | mixin(q{alias resultType = typeof(cast(T)(1) } ~ op ~ q{ cast(U)(1));}); 327 | Vector!(CommonType!(T, U), N) result; 328 | foreach (i; 0 .. N) 329 | { 330 | result[i] = mixin(q{elements[i] } ~ op ~ q{ scalar}); 331 | } 332 | return result; 333 | } 334 | /// 335 | unittest 336 | { 337 | Vec2 a = [1, 2]; 338 | assert(a + 1 == [1f + 1f, 2f + 1f]); 339 | assert(a - 1 == [1f - 1f, 2f - 1f]); 340 | assert(a * 2 == [1f * 2f, 2f * 2f]); 341 | assert(a / 2 == [1f / 2f, 2f / 2f]); 342 | assert(a % 2 == [1f % 2f, 2f % 2f]); 343 | assert(a ^^ 2 == [1f ^^ 2f, 2f ^^ 2f]); 344 | 345 | Vec2i b = [1, 2]; 346 | assert((b & 1) == [1 & 1, 2 & 1]); 347 | assert((b | 1) == [1 | 1, 2 | 1]); 348 | assert((b ^ 1) == [1 ^ 1, 2 ^ 1]); 349 | } 350 | // TODO: shift operations 351 | 352 | /// Returns a new vector with binary operator applied to all elements and `scalar` 353 | Vector opBinaryRight(string op)(const T scalar) const 354 | if (!op.among("~", "<<", ">>", ">>>")) 355 | { 356 | Vector result; 357 | mixin(q{result = scalar} ~ op ~ q{elements[];}); 358 | return result; 359 | } 360 | /// Ditto 361 | auto opBinaryRight(string op, U)(const U scalar) const 362 | if (!is(U : T) && !is(CommonType!(U, T) == void) && !op.among("~", "<<", ">>", ">>>")) 363 | { 364 | mixin(q{alias resultType = typeof(cast(U)(1) } ~ op ~ q{ cast(T)(1));}); 365 | Vector!(CommonType!(T, U), N) result; 366 | foreach (i; 0 .. N) 367 | { 368 | result[i] = mixin(q{scalar } ~ op ~ q{ elements[i]}); 369 | } 370 | return result; 371 | } 372 | /// 373 | unittest 374 | { 375 | Vec2 a = [1, 2]; 376 | assert(1 + a == [1f + 1f, 1f + 2f]); 377 | assert(1 - a == [1f - 1f, 1f - 2f]); 378 | assert(2 * a == [2f * 1f, 2f * 2f]); 379 | assert(2 / a == [2f / 1f, 2f / 2f]); 380 | assert(2 % a == [2f % 1f, 2f % 2f]); 381 | assert(2 ^^ a == [2f ^^ 1f, 2f ^^ 2f]); 382 | 383 | Vec2i b = [1, 2]; 384 | assert((1 & b) == [1 & 1, 1 & 2]); 385 | assert((1 | b) == [1 | 1, 1 | 2]); 386 | assert((1 ^ b) == [1 ^ 1, 1 ^ 2]); 387 | } 388 | 389 | /// Returns a new vector with the results of applying operator against elements of `other`. 390 | /// If operands dimensions are unequal, copies the values from greater dimension vector. 391 | Vector!(T, max(N, M)) opBinary(string op, uint M)(const auto ref T[M] other) const 392 | if (op != "~") 393 | { 394 | enum minDimension = min(N, M); 395 | typeof(return) result; 396 | mixin(q{result[0 .. minDimension] = elements[0 .. minDimension]} ~ op ~ q{other[0 .. minDimension];}); 397 | static if (M < N) 398 | { 399 | result[minDimension .. N] = elements[minDimension .. N]; 400 | } 401 | else static if (N < M) 402 | { 403 | result[minDimension .. M] = other[minDimension .. M]; 404 | } 405 | return result; 406 | } 407 | /// Ditto 408 | Vector!(CommonType!(U, T), max(N, M)) opBinary(string op, U, uint M)(const auto ref U[M] other) const 409 | if (!is(CommonType!(U, T) == void) && op != "~") 410 | { 411 | enum minDimension = min(N, M); 412 | typeof(return) result; 413 | foreach (i; 0 .. minDimension) 414 | { 415 | mixin(q{result[i] = elements[i] } ~ op ~ q{ other[i];}); 416 | } 417 | static if (M < N) 418 | { 419 | result[minDimension .. N] = elements[minDimension .. N]; 420 | } 421 | else static if (N < M) 422 | { 423 | result[minDimension .. M] = other[minDimension .. M]; 424 | } 425 | return result; 426 | } 427 | /// 428 | unittest 429 | { 430 | assert(Vec2(1, 2) + Vec2(3, 4) == [1f+3f, 2f+4f]); 431 | assert(Vec2(1, 2) - Vec2(3, 4) == [1f-3f, 2f-4f]); 432 | assert(Vec2(1, 2) * Vec2(3, 4) == [1f*3f, 2f*4f]); 433 | assert(Vec2(1, 2) / Vec2(3, 4) == [1f/3f, 2f/4f]); 434 | assert(__traits(compiles, Vec2(1, 2) + [3, 4])); 435 | 436 | assert(Vec2(1, 2) + Vec1(3) == [1f+3f, 2f]); 437 | assert(Vec2(1, 2) - Vec1(3) == [1f-3f, 2f]); 438 | assert(Vec2(1, 2) * Vec1(3) == [1f*3f, 2f]); 439 | assert(Vec2(1, 2) / Vec1(3) == [1f/3f, 2f]); 440 | 441 | assert(Vec2(1, 2) + Vec3(3, 4, 5) == [1f+3f, 2f+4f, 5f]); 442 | assert(Vec2(1, 2) - Vec3(3, 4, 5) == [1f-3f, 2f-4f, 5f]); 443 | assert(Vec2(1, 2) * Vec3(3, 4, 5) == [1f*3f, 2f*4f, 5f]); 444 | assert(Vec2(1, 2) / Vec3(3, 4, 5) == [1f/3f, 2f/4f, 5f]); 445 | 446 | assert(Vec2i(1, 2) + Vec2(3, 4) == [1+3f, 2+4f]); 447 | } 448 | 449 | /// Returns a new vector with the results of applying operator against elements of `other`. 450 | /// If operands dimensions are unequal, copies the values from greater dimension vector. 451 | Vector!(CommonType!(U, T), max(N, M)) opBinaryRight(string op, U, uint M)(const auto ref U[M] other) const 452 | if (!is(CommonType!(U, T) == void) && op != "~") 453 | { 454 | enum minDimension = min(N, M); 455 | typeof(return) result; 456 | foreach (i; 0 .. minDimension) 457 | { 458 | mixin(q{result[i] = other[i] } ~ op ~ q{ elements[i];}); 459 | } 460 | static if (M < N) 461 | { 462 | result[minDimension .. N] = elements[minDimension .. N]; 463 | } 464 | else static if (N < M) 465 | { 466 | result[minDimension .. M] = other[minDimension .. M]; 467 | } 468 | return result; 469 | } 470 | 471 | /// Returns a new vector of greater dimension by copying elements and appending `scalar`. 472 | Vector!(T, N + 1) opBinary(string op : "~")(const T scalar) const 473 | { 474 | typeof(return) result; 475 | result[0 .. N] = elements[]; 476 | result[N] = scalar; 477 | return result; 478 | } 479 | /// 480 | unittest 481 | { 482 | Vec2 v = [1, 2]; 483 | assert(v ~ 3 == Vec3(1, 2, 3)); 484 | } 485 | /// Returns a new vector of greater dimension by copying elements and prepending `scalar`. 486 | Vector!(T, N + 1) opBinaryRight(string op : "~")(const T scalar) const 487 | { 488 | typeof(return) result; 489 | result[0] = scalar; 490 | result[1 .. N + 1] = elements[]; 491 | return result; 492 | } 493 | /// 494 | unittest 495 | { 496 | Vec2 v = [1, 2]; 497 | Vec3 v2 = 0f ~ v; 498 | assert(0 ~ v == Vec3(0, 1, 2)); 499 | } 500 | 501 | /// Returns a new vector of greater dimension by copying elements and appending values from `other`. 502 | Vector!(T, N + M) opBinary(string op : "~", uint M)(const auto ref T[M] other) const 503 | { 504 | typeof(return) result = elements ~ other; 505 | return result; 506 | } 507 | /// 508 | unittest 509 | { 510 | Vec2 v1 = [1, 2]; 511 | assert(v1 ~ [3f, 4f] == Vec4(1, 2, 3, 4)); 512 | assert(v1 ~ Vec2(3f, 4f) == Vec4(1, 2, 3, 4)); 513 | } 514 | /// Returns a new vector of greater dimension by copying elements and prepending values from `other`. 515 | Vector!(T, N + M) opBinaryRight(string op : "~", uint M)(const auto ref T[M] other) const 516 | { 517 | typeof(return) result = other ~ elements; 518 | return result; 519 | } 520 | /// 521 | unittest 522 | { 523 | Vec2 v1 = [1, 2]; 524 | assert([3f, 4f] ~ v1 == Vec4(3, 4, 1, 2)); 525 | assert(Vec2(3f, 4f) ~ v1 == Vec4(3, 4, 1, 2)); 526 | } 527 | 528 | /// Cast to a static array of same dimension, but different element type. 529 | U opCast(U : T2[N], T2)() const 530 | { 531 | typeof(return) result; 532 | foreach (i; 0 .. N) 533 | { 534 | result[i] = cast(T2) elements[i]; 535 | } 536 | return result; 537 | } 538 | /// 539 | unittest 540 | { 541 | Vec2i intVec = [1, 2]; 542 | auto floatVec = cast(Vec2) intVec; 543 | assert(floatVec == Vec2(1f, 2f)); 544 | assert(floatVec == intVec); 545 | 546 | auto floatArray = cast(float[2]) intVec; 547 | assert(floatArray == [1f, 2f]); 548 | assert(floatArray == intVec); 549 | } 550 | 551 | /// Cast to a Vector-like struct. 552 | U opCast(U)() const 553 | if (is(U == struct) && VectorStruct!U.isVectorStruct) 554 | { 555 | alias vectorStruct = VectorStruct!U; 556 | typeof(return) result; 557 | static foreach (i; 0 .. min(vectorStruct.length, N)) 558 | { 559 | mixin("result." ~ vectorStruct.fieldNames[i]) = cast(vectorStruct.ElementType) elements[i]; 560 | } 561 | return result; 562 | } 563 | /// 564 | unittest 565 | { 566 | immutable Vec2 five = 5; 567 | OtherVec2 otherFive = cast(OtherVec2) five; 568 | assert(otherFive == OtherVec2(5, 5)); 569 | assert(!__traits(compiles, { auto v = cast(NotAVec2) five; })); 570 | } 571 | 572 | /// Assign result of applying operator with `scalar` to elements. 573 | ref Vector opOpAssign(string op)(const T scalar) return 574 | if (!op.among("~", "<<", ">>", ">>>")) 575 | { 576 | mixin(q{elements[] } ~ op ~ q{= scalar;}); 577 | return this; 578 | } 579 | /// 580 | unittest 581 | { 582 | Vec2i v = [1, 2]; 583 | v += 5; 584 | assert(v == [6, 7]); 585 | v -= 4; 586 | assert(v == [2, 3]); 587 | v *= -1; 588 | assert(v == [-2, -3]); 589 | v %= 3; 590 | assert(v == [-2, 0]); 591 | v |= 1; 592 | assert(v == [-1, 1]); 593 | v /= 2; 594 | assert(v == [0, 0]); // integer division 595 | } 596 | /// Assign result of applying operator with `other` to elements. 597 | ref Vector opOpAssign(string op, uint M)(const auto ref T[M] other) return 598 | if (!op.among("~", "<<", ">>", ">>>")) 599 | { 600 | enum minDimension = min(N, M); 601 | mixin(q{elements[0 .. minDimension] } ~ op ~ q{= other[0 .. minDimension];}); 602 | return this; 603 | } 604 | /// 605 | unittest 606 | { 607 | Vec3 v = [1, 2, 3]; 608 | v += Vec2(1, 2); 609 | assert(v == [2, 4, 3]); 610 | 611 | v += Vec4(1, 2, 3, 4); 612 | assert(v == [3, 6, 6]); 613 | } 614 | 615 | unittest 616 | { 617 | assert(Vec2.sizeof == Vec2.elements.sizeof); 618 | assert(Vec3.sizeof == Vec3.elements.sizeof); 619 | assert(Vec4.sizeof == Vec4.elements.sizeof); 620 | 621 | alias Vec2_100 = Vec2[100]; 622 | assert(Vec2_100.sizeof == 100 * Vec2.elements.sizeof); 623 | 624 | auto v = Vec2(5); 625 | v += 3; 626 | assert(v == Vec2(8)); 627 | } 628 | } 629 | 630 | /// True if `T` is some kind of Vector 631 | enum isVector(T) = is(T : Vector!U, U...); 632 | 633 | /// Construct Vector directly from static array, inferring element type. 634 | Vector!(T, N) vector(T, uint N)(const auto ref T[N] elements) 635 | { 636 | return typeof(return)(elements); 637 | } 638 | /// 639 | unittest 640 | { 641 | auto v = [1, 2, 3].vector; 642 | assert(v.elements == [1, 2, 3]); 643 | assert(is(typeof(v) == Vector!(int, 3))); 644 | } 645 | 646 | /// Construct Vector directly from elements, inferring element type. 647 | Vector!(CommonType!Args, Args.length) vector(Args...)(const auto ref Args args) 648 | if (!is(CommonType!Args == void)) 649 | { 650 | return typeof(return)(args); 651 | } 652 | /// 653 | unittest 654 | { 655 | auto v = vector(1f, 2, 3); 656 | assert(is(typeof(v) == Vector!(float, 3))); 657 | assert(v == [1f, 2f, 3f]); 658 | } 659 | 660 | /// Returns the dot product between two Vectors. 661 | T dot(T, uint N)(const auto ref Vector!(T, N) a, const auto ref Vector!(T, N) b) pure 662 | { 663 | auto multiplied = a * b; 664 | return multiplied[].sum; 665 | } 666 | 667 | /// Returns the cross product between two 3D Vectors. 668 | Vector!(T, 3) cross(T)(const auto ref Vector!(T, 3) a, const auto ref Vector!(T, 3) b) pure 669 | { 670 | typeof(return) result = [ 671 | a.y * b.z - a.z * b.y, 672 | a.z * b.x - a.x * b.z, 673 | a.x * b.y - a.y * b.x, 674 | ]; 675 | return result; 676 | } 677 | 678 | /// Returns a Vector that is the reflection of `vec` against `normal`. 679 | Vector!(T, N) reflect(T, uint N)(const auto ref Vector!(T, N) vec, const auto ref Vector!(T, N) normal) pure 680 | { 681 | return vec - (2 * normal * dot(vec, normal)); 682 | } 683 | 684 | /// Returns the squared magnitude (Euclidean length) of a Vector. 685 | T magnitudeSquared(T, uint N)(const auto ref Vector!(T, N) vec) pure 686 | out (r) { assert(r >= 0, "Vector squared magnitude should be non-negative!"); } 687 | do 688 | { 689 | return dot(vec, vec); 690 | } 691 | /// 692 | unittest 693 | { 694 | assert(Vec2(0, 0).magnitudeSquared() == 0); 695 | assert(Vec2(1, 0).magnitudeSquared() == 1); 696 | assert(Vec2(0, 1).magnitudeSquared() == 1); 697 | assert(Vec2(1, 1).magnitudeSquared() == 2); 698 | assert(Vec2(2, 0).magnitudeSquared() == 4); 699 | assert(Vec2(1, 2).magnitudeSquared() == 5); 700 | } 701 | 702 | /// Returns the magnitude (Euclidean length) of a Vector. 703 | auto magnitude(T, uint N)(const auto ref Vector!(T, N) vec) 704 | out (r) { assert(r >= 0, "Vector magnitude should be non-negative!"); } 705 | do 706 | { 707 | return sqrt(vec.magnitudeSquared()); 708 | } 709 | /// 710 | unittest 711 | { 712 | assert(Vec2(0, 0).magnitude() == 0); 713 | assert(Vec2(1, 0).magnitude() == 1); 714 | assert(Vec2(0, 1).magnitude() == 1); 715 | assert(Vec2(1, 1).magnitude() == sqrt(2f)); 716 | assert(Vec2(2, 0).magnitude() == 2); 717 | } 718 | 719 | /// Normalize a Vector inplace. 720 | ref Vector!(T, N) normalize(T, uint N)(ref return Vector!(T, N) vec) 721 | { 722 | auto sqMag = vec.magnitudeSquared(); 723 | if (sqMag != 0) 724 | { 725 | enum FloatType!T one = 1; 726 | const auto inverseMag = one / sqrt(sqMag); 727 | vec *= inverseMag; 728 | } 729 | return vec; 730 | } 731 | /// 732 | unittest 733 | { 734 | Vec2 v = [5, 0]; 735 | v.normalize(); 736 | assert(v == Vec2(1, 0)); 737 | } 738 | 739 | /// Returns a normalized copy of Vector. 740 | Vector!(T, N) normalized(T, uint N)(const auto ref Vector!(T, N) vec) 741 | { 742 | typeof(return) copy = vec; 743 | return copy.normalize(); 744 | } 745 | /// 746 | unittest 747 | { 748 | Vec2 v = [200, 0]; 749 | assert(v.normalized() == Vec2(1, 0)); 750 | assert(v == Vec2(200, 0)); 751 | } 752 | 753 | unittest 754 | { 755 | Vec2 a = [1, 1]; 756 | Vec2 b = [2, 3]; 757 | assert(lerp(a, b, 0) == a); 758 | assert(lerp(a, b, 0.5) == Vec2(1.5, 2)); 759 | assert(lerp(a, b, 1) == b); 760 | 761 | Vec2ub c1 = [0, 0]; 762 | Vec2ub c2 = [255, 255]; 763 | assert(lerp(c1, c2, 0) == c1); 764 | assert(lerp(c1, c2, 1) == c2); 765 | assert(lerp(c1, c2, 0.5) == Vec2ub(127, 127)); 766 | } 767 | --------------------------------------------------------------------------------