1 module canvas.math; 2 import std.traits; 3 4 // Vectors: 5 6 alias Vec2 = AbstractVec2!double; 7 alias IVec2 = AbstractVec2!int; 8 alias FVec2 = AbstractVec2!float; 9 alias RVec2 = AbstractVec2!real; 10 11 alias Vec3 = AbstractVec3!double; 12 alias IVec3 = AbstractVec3!int; 13 alias FVec3 = AbstractVec3!float; 14 alias RVec3 = AbstractVec3!real; 15 16 alias Vec4 = AbstractVec4!double; 17 alias IVec4 = AbstractVec4!int; 18 alias FVec4 = AbstractVec4!float; 19 alias RVec4 = AbstractVec4!real; 20 21 struct AbstractVec2(T) { 22 T x = 0; 23 T y = 0; 24 25 this(V)(AbstractVec2!V base) { 26 x = cast(T) base.x; 27 y = cast(T) base.y; 28 } 29 30 this(T x, T y = 0) { 31 this.x = x; 32 this.y = y; 33 } 34 35 T magnitudeSq() @property const { 36 return x * x + y * y; 37 } 38 39 static if (isFloatingPoint!T) { 40 T magnitude() @property const { 41 import std.math : sqrt; 42 43 return sqrt(x * x + y * y); 44 } 45 46 AbstractVec2!T normalize() const { 47 if (magnitude == 0) { 48 return this; 49 } 50 51 return this / magnitude; 52 } 53 54 AbstractVec2!T round() const { 55 import std.math : round; 56 57 return AbstractVec2!T(round(x), round(y)); 58 } 59 } 60 61 auto opBinary(string op, R)(const(AbstractVec2!R) rhs) const { 62 alias ResT = typeof(mixin("cast(T) 0" ~ op ~ "cast(R) 0")); 63 AbstractVec2!ResT result; 64 result.x = mixin("x" ~ op ~ "rhs.x"); 65 result.y = mixin("y" ~ op ~ "rhs.y"); 66 return result; 67 } 68 69 auto opBinary(string op, R)(const(R) rhs) const if (isNumeric!R) { 70 alias ResT = typeof(mixin("cast(T) 0" ~ op ~ "cast(R) 0")); 71 AbstractVec2!ResT result; 72 result.x = mixin("x" ~ op ~ "rhs"); 73 result.y = mixin("y" ~ op ~ "rhs"); 74 return result; 75 } 76 77 auto opOpAssign(string op, T)(T value) { 78 auto res = mixin("this" ~ op ~ "value"); 79 x = res.x; 80 y = res.y; 81 return this; 82 } 83 84 auto opUnary(string op)() const if (op == "-") { 85 return AbstractVec2!T(-x, -y); 86 } 87 88 AbstractVec2!T opDispatch(string member)() @property const 89 if (member.length == 2) { 90 import std.algorithm : all; 91 92 static assert(member.all!(c => c >= 'x' && c <= 'y')); 93 94 AbstractVec2!T result; 95 static foreach (i, char c; member) { 96 result.tupleof[i] = mixin("this." ~ c); 97 } 98 return result; 99 } 100 101 AbstractVec3!T opDispatch(string member)() @property const 102 if (member.length == 3) { 103 import std.algorithm : all; 104 105 static assert(member.all!(c => c >= 'x' && c <= 'y')); 106 107 AbstractVec3!T result; 108 static foreach (i, char c; member) { 109 result.tupleof[i] = mixin("this." ~ c); 110 } 111 return result; 112 } 113 114 AbstractVec4!T opDispatch(string member)() @property const 115 if (member.length == 4) { 116 import std.algorithm : all; 117 118 static assert(member.all!(c => c >= 'x' && c <= 'y')); 119 120 AbstractVec4!T result; 121 static foreach (i, char c; member) { 122 result.tupleof[i] = mixin("this." ~ c); 123 } 124 return result; 125 } 126 127 bool eq(AbstractVec2!T other, T eps) const { 128 return (this - other).magnitudeSq <= eps * eps; 129 } 130 131 string toString() const @safe { 132 import std.conv : text; 133 134 return text("(", x, ", ", y, ")"); 135 } 136 } 137 138 struct AbstractVec3(T) { 139 T x = 0; 140 T y = 0; 141 T z = 0; 142 143 this(V)(AbstractVec3!V base) { 144 x = cast(T) base.x; 145 y = cast(T) base.y; 146 z = cast(T) base.z; 147 } 148 149 this(T x, T y = 0, T z = 0) { 150 this.x = x; 151 this.y = y; 152 this.z = z; 153 } 154 155 T magnitudeSq() @property const { 156 return x * x + y * y + z * z; 157 } 158 159 static if (isFloatingPoint!T) { 160 T magnitude() @property const { 161 import std.math : sqrt; 162 163 return sqrt(x * x + y * y + z * z); 164 } 165 166 AbstractVec3!T normalize() const { 167 if (magnitude == 0) { 168 return this; 169 } 170 171 return this / magnitude; 172 } 173 } 174 175 auto opBinary(string op, R)(const(AbstractVec3!R) rhs) const { 176 alias ResT = typeof(mixin("cast(T) 0" ~ op ~ "cast(R) 0")); 177 AbstractVec3!ResT result; 178 result.x = mixin("x" ~ op ~ "rhs.x"); 179 result.y = mixin("y" ~ op ~ "rhs.y"); 180 result.z = mixin("z" ~ op ~ "rhs.z"); 181 return result; 182 } 183 184 auto opBinary(string op, R)(const(R) rhs) const if (isNumeric!R) { 185 alias ResT = typeof(mixin("cast(T) 0" ~ op ~ "cast(R) 0")); 186 AbstractVec3!ResT result; 187 result.x = mixin("x" ~ op ~ "rhs"); 188 result.y = mixin("y" ~ op ~ "rhs"); 189 result.z = mixin("z" ~ op ~ "rhs"); 190 return result; 191 } 192 193 auto opOpAssign(string op, T)(T value) { 194 auto res = mixin("this" ~ op ~ "value"); 195 x = res.x; 196 y = res.y; 197 z = res.z; 198 return this; 199 } 200 201 auto opUnary(string op)() const if (op == "-") { 202 return AbstractVec3!T(-x, -y, -z); 203 } 204 205 AbstractVec2!T opDispatch(string member)() @property const 206 if (member.length == 2) { 207 import std.algorithm : all; 208 209 static assert(member.all!(c => c >= 'x' && c <= 'z')); 210 211 AbstractVec2!T result; 212 static foreach (i, char c; member) { 213 result.tupleof[i] = mixin("this." ~ c); 214 } 215 return result; 216 } 217 218 AbstractVec3!T opDispatch(string member)() @property const 219 if (member.length == 3) { 220 import std.algorithm : all; 221 222 static assert(member.all!(c => c >= 'x' && c <= 'z')); 223 224 AbstractVec3!T result; 225 static foreach (i, char c; member) { 226 result.tupleof[i] = mixin("this." ~ c); 227 } 228 return result; 229 } 230 231 AbstractVec4!T opDispatch(string member)() @property const 232 if (member.length == 4) { 233 import std.algorithm : all; 234 235 static assert(member.all!(c => c >= 'x' && c <= 'z')); 236 237 AbstractVec4!T result; 238 static foreach (i, char c; member) { 239 result.tupleof[i] = mixin("this." ~ c); 240 } 241 return result; 242 } 243 244 bool eq(AbstractVec3!T other, T eps) const { 245 return (this - other).magnitudeSq <= eps * eps; 246 } 247 248 string toString() const @safe { 249 import std.conv : text; 250 251 return text("(", x, ", ", y, ", ", z, ")"); 252 } 253 } 254 255 struct AbstractVec4(T) { 256 T x = 0; 257 T y = 0; 258 T z = 0; 259 T w = 0; 260 261 ref inout(T) r() inout @property { return x; } 262 ref inout(T) g() inout @property { return y; } 263 ref inout(T) b() inout @property { return z; } 264 ref inout(T) a() inout @property { return w; } 265 266 this(V)(AbstractVec4!V base) { 267 x = cast(T) base.x; 268 y = cast(T) base.y; 269 z = cast(T) base.z; 270 w = cast(T) base.w; 271 } 272 273 this(T x, T y = 0, T z = 0, T w = 0) { 274 this.x = x; 275 this.y = y; 276 this.z = z; 277 this.w = w; 278 } 279 280 T magnitudeSq() @property const { 281 return x * x + y * y + z * z + w * w; 282 } 283 284 static if (isFloatingPoint!T) { 285 T magnitude() @property const { 286 import std.math : sqrt; 287 288 return sqrt(x * x + y * y + z * z + w * w); 289 } 290 291 AbstractVec4!T normalize() const { 292 if (magnitude == 0) { 293 return this; 294 } 295 296 return this / magnitude; 297 } 298 } 299 300 auto opBinary(string op, R)(const(AbstractVec4!R) rhs) const { 301 alias ResT = typeof(mixin("cast(T) 0" ~ op ~ "cast(R) 0")); 302 AbstractVec4!ResT result; 303 result.x = mixin("x" ~ op ~ "rhs.x"); 304 result.y = mixin("y" ~ op ~ "rhs.y"); 305 result.z = mixin("z" ~ op ~ "rhs.z"); 306 result.w = mixin("w" ~ op ~ "rhs.w"); 307 return result; 308 } 309 310 auto opBinary(string op, R)(const(R) rhs) const if (isNumeric!R) { 311 alias ResT = typeof(mixin("cast(T) 0" ~ op ~ "cast(R) 0")); 312 AbstractVec4!ResT result; 313 result.x = mixin("x" ~ op ~ "rhs"); 314 result.y = mixin("y" ~ op ~ "rhs"); 315 result.z = mixin("z" ~ op ~ "rhs"); 316 result.w = mixin("w" ~ op ~ "rhs"); 317 return result; 318 } 319 320 auto opOpAssign(string op, T)(T value) { 321 auto res = mixin("this" ~ op ~ "value"); 322 x = res.x; 323 y = res.y; 324 z = res.z; 325 w = res.w; 326 return this; 327 } 328 329 auto opUnary(string op)() const if (op == "-") { 330 return AbstractVec4!T(-x, -y, -z, -w); 331 } 332 333 AbstractVec2!T opDispatch(string member)() @property const 334 if (member.length == 2) { 335 import std.algorithm : all; 336 337 static assert(member.all!(c => c >= 'w' && c <= 'z')); 338 339 AbstractVec2!T result; 340 static foreach (i, char c; member) { 341 result.tupleof[i] = mixin("this." ~ c); 342 } 343 return result; 344 } 345 346 AbstractVec3!T opDispatch(string member)() @property const 347 if (member.length == 3) { 348 import std.algorithm : all; 349 350 static assert(member.all!(c => c >= 'w' && c <= 'z')); 351 352 AbstractVec3!T result; 353 static foreach (i, char c; member) { 354 result.tupleof[i] = mixin("this." ~ c); 355 } 356 return result; 357 } 358 359 AbstractVec4!T opDispatch(string member)() @property const 360 if (member.length == 4) { 361 import std.algorithm : all; 362 363 static assert(member.all!(c => c >= 'w' && c <= 'z')); 364 365 AbstractVec4!T result; 366 static foreach (i, char c; member) { 367 result.tupleof[i] = mixin("this." ~ c); 368 } 369 return result; 370 } 371 372 bool eq(AbstractVec4!T other, T eps) const { 373 return (this - other).magnitudeSq <= eps * eps; 374 } 375 376 string toString() const @safe { 377 import std.conv : text; 378 379 return text("(", x, ", ", y, ", ", z, ", ", w, ")"); 380 } 381 } 382 383 // Matrices: 384 385 alias Mat3 = AbstractMat3!double; 386 alias IMat3 = AbstractMat3!int; 387 alias FMat3 = AbstractMat3!float; 388 alias RMat3 = AbstractMat3!real; 389 390 // alias Mat4 = AbstractMat4!double; 391 // alias IMat4 = AbstractMat4!int; 392 // alias FMat4 = AbstractMat4!float; 393 // alias RMat4 = AbstractMat4!real; 394 395 struct AbstractMat3(T) { 396 private T[3][3] m = [ 397 [1, 0, 0], 398 [0, 1, 0], 399 [0, 0, 1], 400 ]; 401 402 this(T x, T y = 0) { 403 m = [ 404 [1, 0, x], 405 [0, 1, y], 406 [0, 0, cast(T) 1], 407 ]; 408 } 409 410 this(AbstractVec2!T vec) { 411 m = [ 412 [1, 0, vec.x], 413 [0, 1, vec.y], 414 [0, 0, cast(T) 1], 415 ]; 416 } 417 418 this(T[3][3] values) { 419 m = values; 420 } 421 422 this(V)(AbstractMat3!V value) { 423 foreach (i; 0 .. 3) { 424 foreach (j; 0 .. 3) { 425 m[i][j] = cast(T) value.m[i][j]; 426 } 427 } 428 } 429 430 T opIndex(size_t r, size_t c) const { return m[r][c]; } 431 432 T x() const @property { return m[0][2]; } 433 T y() const @property { return m[1][2]; } 434 435 AbstractVec2!T translation() const { 436 return AbstractVec2!T(x, y); 437 } 438 439 AbstractMat3!T withTranslation(AbstractVec2!T value) const { 440 AbstractMat3!T res = this; 441 res.m[0][2] = value.x; 442 res.m[1][2] = value.y; 443 return res; 444 } 445 446 AbstractMat3!T withoutTranslation() const { 447 return withTranslation(AbstractVec2!T.init); 448 } 449 450 AbstractVec2!T scale() const { 451 return AbstractVec2!T(m[0][0], m[1][1]); 452 } 453 454 static AbstractMat3!T scale(AbstractVec2!T value) { 455 return AbstractMat3!T().withScale(value); 456 } 457 458 static AbstractMat3!T scale(T value) { 459 return AbstractMat3!T().withScale(value); 460 } 461 462 AbstractMat3!T withScale(AbstractVec2!T value) const { 463 AbstractMat3!T res = this; 464 res.m[0][0] = value.x; 465 res.m[1][1] = value.y; 466 return res; 467 } 468 469 AbstractMat3!T withScale(T value) const { 470 AbstractMat3!T res = this; 471 res.m[0][0] = value; 472 res.m[1][1] = value; 473 return res; 474 } 475 476 AbstractMat3!T withoutScale() const { 477 return withScale(1); 478 } 479 480 static if (isFloatingPoint!T) { 481 static AbstractMat3!T rotation(double theta) { 482 import std.math : cos, sin; 483 484 return AbstractMat3!T([ 485 [cast(T) cos(theta), cast(T) -sin(theta), 0], 486 [cast(T) sin(theta), cast(T) cos(theta), 0], 487 [0, 0, cast(T) 1], 488 ]); 489 } 490 } 491 492 AbstractVec2!T opBinary(string op)(AbstractVec2!T other) const if (op == "*") { 493 return (this * AbstractMat3!T(other)).translation; 494 } 495 496 AbstractMat3!T opBinary(string op)(AbstractMat3!T other) const if (op == "*") { 497 AbstractMat3!T res; 498 499 res.m[0][0] = other.m[0][0] * m[0][0] + other.m[1][0] * m[0][1] + other.m[2][0] * m[0][2]; 500 res.m[1][0] = other.m[0][0] * m[1][0] + other.m[1][0] * m[1][1] + other.m[2][0] * m[1][2]; 501 res.m[2][0] = other.m[0][0] * m[2][0] + other.m[1][0] * m[2][1] + other.m[2][0] * m[2][2]; 502 res.m[0][1] = other.m[0][1] * m[0][0] + other.m[1][1] * m[0][1] + other.m[2][1] * m[0][2]; 503 res.m[1][1] = other.m[0][1] * m[1][0] + other.m[1][1] * m[1][1] + other.m[2][1] * m[1][2]; 504 res.m[2][1] = other.m[0][1] * m[2][0] + other.m[1][1] * m[2][1] + other.m[2][1] * m[2][2]; 505 res.m[0][2] = other.m[0][2] * m[0][0] + other.m[1][2] * m[0][1] + other.m[2][2] * m[0][2]; 506 res.m[1][2] = other.m[0][2] * m[1][0] + other.m[1][2] * m[1][1] + other.m[2][2] * m[1][2]; 507 res.m[2][2] = other.m[0][2] * m[2][0] + other.m[1][2] * m[2][1] + other.m[2][2] * m[2][2]; 508 509 return res; 510 } 511 }