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