1 /++ 2 A module that describes how to work with a vector, as well as functions for 3 processing a vector for other needs. 4 5 Please note that the vector works only with two-dimensional space, therefore, 6 the vector is calculated only along two axes. 7 8 Macros: 9 LREF = <a href="#$1">$1</a> 10 HREF = <a href="$1">$2</a> 11 12 Authors: $(HREF https://github.com/TodNaz, TodNaz) 13 Copyright: Copyright (c) 2020 - 2021, TodNaz. 14 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE, MIT) 15 +/ 16 module tida.vector; 17 18 import std.traits; 19 import std.math; 20 21 /++ 22 Vector structure. May include any numeric data type available for vector arithmetic. 23 24 Example: 25 --- 26 assert(vec!float(32, 32) + vecf(32, 32) == Vector!float[64, 64]); 27 --- 28 +/ 29 struct Vector(T) 30 if (isNumeric!T && isSigned!T) 31 { 32 import core.exception; 33 34 public: 35 alias Type = T; 36 37 T x, /// X-axis position. 38 y; /// Y-axis position. 39 40 @safe nothrow pure: 41 /++ 42 Any numeric type that can be freely converted to a template vector type 43 can be included in the vector's constructor. 44 +/ 45 this(R)(R x, R y) 46 if (isImplicitlyConvertible!(R, T)) 47 { 48 this.x = cast(T) x; 49 this.y = cast(T) y; 50 } 51 52 /++ 53 Any numeric type that can be freely converted to a template vector type 54 can be included in the vector's constructor. 55 +/ 56 this(R)(R[2] arrvec) 57 if (isImplicitlyConvertible!(R, T)) 58 { 59 this.x = cast(T) arrvec[0]; 60 this.y = cast(T) arrvec[1]; 61 } 62 63 void opIndexAssign(R)(R value, size_t index) 64 if (isImplicitlyConvertible!(R, T)) 65 { 66 if (index == 0) 67 { 68 this.x = cast(R) value; 69 } else 70 if (index == 1) 71 { 72 this.y = cast(R) value; 73 } else 74 throw new RangeError(); 75 } 76 77 void opOpAssign(string op)(Vector rhs) 78 { 79 static if (op == "+") 80 { 81 this.x += rhs.x; 82 this.y += rhs.y; 83 }else 84 static if (op == "-") 85 { 86 this.x = x - rhs.x; 87 this.y = y - rhs.y; 88 }else 89 static if (op == "*") 90 { 91 this.x = x * rhs.x; 92 this.y = y * rhs.y; 93 }else 94 static if (op == "/") 95 { 96 this.x = x / rhs.x; 97 this.y = y / rhs.y; 98 }else 99 static assert(null, "The `" ~ op ~ "` operator is not implemented."); 100 } 101 102 void opOpAssign(string op)(T num) 103 { 104 static if (op == "+") 105 { 106 this.x = this.x + num; 107 this.y = this.y + num; 108 }else 109 static if (op == "-") 110 { 111 this.x = this.x - num; 112 this.y = this.y - num; 113 }else 114 static if (op == "*") 115 { 116 this.x = this.x * num; 117 this.y = this.y * num; 118 }else 119 static if (op == "/") 120 { 121 this.x = this.x / num; 122 this.y = this.y / num; 123 }else 124 static assert(null, "The `" ~ op ~ "` operator is not implemented."); 125 } 126 127 /++ 128 Normalizes the vector. 129 +/ 130 void normalize() 131 { 132 immutable d = 1 / length; 133 134 this.x = x * d; 135 this.y = y * d; 136 } 137 138 inout: 139 T opIndex(size_t index) 140 { 141 if (index == 0) 142 { 143 return this.x; 144 } else 145 if (index == 1) 146 { 147 return this.y; 148 } else 149 throw new RangeError(); 150 } 151 152 /++ 153 Converts a vector to an array. 154 +/ 155 T[] array() 156 { 157 return [x, y]; 158 } 159 160 bool opEquals(Vector a, Vector b) 161 { 162 return a is b || (a.x == b.x && a.y == b.y); 163 } 164 165 bool opEquals(Vector other) 166 { 167 return this is other || (this.x == other.x && this.y == other.y); 168 } 169 170 int opCmp(Vector rhs) 171 { 172 return (x > rhs.x && y > rhs.y) ? 1 : -1; 173 } 174 175 Vector!T opBinary(string op)(Vector rhs) 176 { 177 static if (op == "+") 178 { 179 return Vector!T(this.x + rhs.x, this.y + rhs.y); 180 } 181 else 182 static if (op == "-") 183 { 184 return Vector!T(this.x - rhs.x, this.y - rhs.y); 185 } 186 else 187 static if (op == "*") 188 { 189 return Vector!T(this.x * rhs.x, this.y * rhs.y); 190 }else 191 static if (op == "/") 192 { 193 return Vector!T(this.x / rhs.x, this.y / rhs.y); 194 }else 195 static if (op == "%") 196 { 197 return Vector!T(this.x % rhs.x, this.y % rhs.y); 198 }else 199 static assert(null, "The `" ~ op ~ "` operator is not implemented."); 200 } 201 202 Vector!T opBinary(string op, R)(R num) 203 if (isImplicitlyConvertible!(R, T)) 204 { 205 static if (op == "+") 206 { 207 return Vector!T(this.x + num, this.y + num); 208 }else 209 static if (op == "-") 210 { 211 return Vector!T(this.x - num, this.y - num); 212 }else 213 static if (op == "*") 214 { 215 return Vector!T(this.x * num, this.y * num); 216 } 217 else 218 static if (op == "/") 219 { 220 return Vector!T(this.x / num, this.y / num); 221 }else 222 static if (op == "%") 223 { 224 return Vector!T(this.x % num, this.y % num); 225 }else 226 static if (op == "^^") 227 { 228 return Vector!T(this.x ^^ num, this.y ^^ num); 229 }else 230 static assert(null, "The `" ~ op ~ "` operator is not implemented."); 231 } 232 233 /++ 234 Vector length. 235 +/ 236 T length() 237 { 238 static if(isIntegral!T) 239 { 240 return cast(T) (sqrt(cast(float) ((this.x * this.x) + (this.y * this.y)))); 241 }else 242 { 243 return sqrt((this.x * this.x) + (this.y * this.y)); 244 } 245 } 246 247 /++ 248 Returns a normalized vector. 249 +/ 250 Vector!T normalized() 251 { 252 immutable d = 1 / length; 253 254 return Vector!T(this.x * d, this.y * d); 255 } 256 257 Vector!T negatived() 258 { 259 return Vector!T(-this.x, -this.y); 260 } 261 262 Vector!T positived() 263 { 264 return Vector!T(+this.x, +this.y); 265 } 266 267 template opUnary(string op) 268 if (op == "-" || op == "+") 269 { 270 static if (op == "+") 271 alias opUnary = positived; 272 else 273 static if (op == "-") 274 alias opUnary = negatived; 275 } 276 } 277 278 unittest 279 { 280 assert(vec!real([32, 64]) == (vec!real(32.0, 64.0))); 281 assert(-vec!real(32, 32) == vec!real(-32, -32)); 282 } 283 284 /++ 285 Checks if this type is a vector. 286 +/ 287 template isVector(T) 288 { 289 enum isVector = __traits(hasMember, T, "x") && __traits(hasMember, T, "y"); 290 } 291 292 /++ 293 Checks if the vector is integers. 294 +/ 295 template isVectorIntegral(T) 296 if (isVector!T) 297 { 298 enum isVectorIntegral = isIntegral!(T.Type); 299 } 300 301 /++ 302 Checks if the vector is float. 303 +/ 304 template isVectorFloatingPoint(T) 305 if (isVector!T) 306 { 307 enum isVectorFloatingPoint = isFloatingPoint!(T.Type); 308 } 309 310 unittest 311 { 312 static assert(isVector!(Vector!int)); 313 static assert(!isVector!int); 314 315 static assert(isVectorIntegral!(Vector!int)); 316 static assert(!isVectorIntegral!(Vector!float)); 317 318 static assert(isVectorFloatingPoint!(Vector!double)); 319 static assert(!isVectorFloatingPoint!(Vector!long)); 320 } 321 322 alias Vecf = Vector!float; /// Vector float. 323 alias vec(T) = Vector!T; /// Vector. 324 alias vecf = vec!float; /// Vector float. 325 326 /++ 327 Not a numeric vector. 328 +/ 329 template vecNaN(T) 330 if (isVectorFloatingPoint!(Vector!T)) 331 { 332 enum vecNaN = Vector!T(T.nan, T.nan); 333 } 334 335 /// ditto 336 enum vecfNaN = vecNaN!float; 337 338 /// Zero vector 339 enum vecZero(T) = vec!T(0, 0); 340 341 /// ditto 342 enum vecfZero = vecZero!float; 343 344 /++ 345 Checks if the vector is non-numeric. 346 +/ 347 bool isVectorNaN(T)(Vector!T vector) 348 if (isVectorFloatingPoint!(Vector!T)) 349 { 350 import std.math : isNaN; 351 352 return vector.x.isNaN && vector.y.isNaN; 353 } 354 355 /// ditto 356 alias isVecfNaN = isVectorNaN!float; 357 358 unittest 359 { 360 assert(vecfNaN.isVecfNaN); 361 assert(!vecf(0.0f, 0.0f).isVecfNaN); 362 } 363 364 /++ 365 Generates a buffer from vectors. 366 367 Params: 368 vectors = Array vector. 369 +/ 370 T[] generateArray(T)(Vector!T[] vectors) @safe nothrow pure 371 { 372 import std.algorithm : map, joiner; 373 import std.range : array; 374 375 return vectors 376 .map!(e => e.array) 377 .joiner 378 .array; 379 } 380 381 unittest 382 { 383 assert([vec!int(16, 16), vec!int(32, 48), vec!int(48, 8)].generateArray == ([16, 16, 32, 48, 48, 8])); 384 } 385 386 inout(T) sqr(T)(inout(T) value) @safe nothrow pure 387 { 388 return value * value; 389 } 390 391 /++ 392 Construct the vector modulo. 393 394 Params: 395 vec = Vector. 396 +/ 397 inout(Vector!T) abs(T)(inout(Vector!T) vec) @safe nothrow pure 398 { 399 import std.math : abs; 400 401 return Vector!T(abs(vec.x), abs(vec.y)); 402 } 403 404 unittest 405 { 406 assert(abs(vec!long(-64, -32)) == (vec!long(64, 32))); 407 assert(abs(vec!float(-128.5f, 19.0f)) == (vec!float(128.5f, 19.0f))); 408 } 409 410 /++ 411 Distance between two points. 412 413 Params: 414 a = First point. 415 b = Second point. 416 +/ 417 inout(T) distance(T)(inout(Vector!T) a, inout(Vector!T) b) @safe nothrow pure 418 { 419 import std.math : sqrt; 420 421 return sqrt(sqr(b.x - a.x) + sqr(b.y - a.y)); 422 } 423 424 /++ 425 Average distance between vectors. 426 +/ 427 inout(Vector!T) averateVectors(T)(inout(Vector!T) a, inout(Vector!T) b) @safe nothrow pure 428 { 429 return ((b - a) / 2) + ((a > b) ? b : a); 430 } 431 432 unittest 433 { 434 assert(vec!long(32, 32).averateVectors(vec!long(64, 64)) == (vec!long(48, 48))); 435 436 assert(vec!real(48.0, 48.0).averateVectors(vec!real(128.0, 128.0)) == (vec!real(88.0, 88.0))); 437 } 438 439 /++ 440 Creates a random vector. 441 442 Params: 443 begin = Begin. 444 end = End. 445 446 Example: 447 --- 448 Vecf rnd = uniform(vecf(32, 16), vecf(64, 48)); 449 // vec.x = random: 32 .. 64 450 // vec.y = random: 16 .. 48 451 --- 452 +/ 453 inout(Vector!T) uniform(T)(inout(Vector!T) begin, inout(Vector!T) end) @safe 454 { 455 import std.random : uniform; 456 457 return Vector!T(uniform(begin.x, end.x), uniform(begin.y, end.y)); 458 } 459 460 /++ 461 Rounds the vector up. 462 463 Params: 464 vec = Rounded vector. 465 466 Example: 467 --- 468 assert(Vecf(32.5,32.5) == Vecf(33, 33)); 469 --- 470 +/ 471 inout(Vector!T) round(T)(inout(Vector!T) vec) @safe nothrow pure 472 if (isVectorFloatingPoint!(Vector!T)) 473 { 474 import core.stdc.math : roundl; 475 476 return Vector!T(roundl(vec.x), roundl(vec.y)); 477 } 478 479 unittest 480 { 481 assert(vec!real(31.4, 33.51).round == (vec!real(31.0, 34.0))); 482 } 483 484 /++ 485 Floors the vector down. 486 487 Params: 488 vec = Floored vector. 489 490 Example: 491 --- 492 assert(vecf(32.5, 32.5) == vecf(32, 32)); 493 --- 494 +/ 495 inout(Vector!T) floor(T)(inout(Vector!T) vec) @safe nothrow pure 496 if (isVectorFloatingPoint!(Vector!T)) 497 { 498 import std.math : floor; 499 500 return Vector!T(floor(vec.x), floor(vec.y)); 501 } 502 503 unittest 504 { 505 assert(vec!double(31.4, 33.51).floor == (vec!double(31.0, 33.0))); 506 }