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 26 alias Type = T; 27 28 public: 29 T x, /// X-axis position. 30 y; /// Y-axis position. 31 32 @safe nothrow pure: 33 /++ 34 Any numeric type that can be freely converted to a template vector type 35 can be included in the vector's constructor. 36 +/ 37 this(R)(R x, R y) 38 { 39 this.x = cast(T) x; 40 this.y = cast(T) y; 41 } 42 43 /++ 44 Any numeric type that can be freely converted to a template vector type 45 can be included in the vector's constructor. 46 +/ 47 this(R)(R[2] arrvec) 48 if (isImplicitlyConvertible!(R, T)) 49 { 50 this.x = cast(T) arrvec[0]; 51 this.y = cast(T) arrvec[1]; 52 } 53 54 void opOpAssign(string op)(Vector rhs) 55 { 56 static if (op == "+") 57 { 58 this.x += rhs.x; 59 this.y += rhs.y; 60 }else 61 static if (op == "-") 62 { 63 this.x = x - rhs.x; 64 this.y = y - rhs.y; 65 }else 66 static if (op == "*") 67 { 68 this.x = x * rhs.x; 69 this.y = y * rhs.y; 70 }else 71 static if (op == "/") 72 { 73 this.x = x / rhs.x; 74 this.y = y / rhs.y; 75 }else 76 static assert(null, "The `" ~ op ~ "` operator is not implemented."); 77 } 78 79 void opOpAssign(string op)(T num) 80 { 81 static if (op == "+") 82 { 83 this.x = this.x + num; 84 this.y = this.y + num; 85 }else 86 static if (op == "-") 87 { 88 this.x = this.x - num; 89 this.y = this.y - num; 90 }else 91 static if (op == "*") 92 { 93 this.x = this.x * num; 94 this.y = this.y * num; 95 }else 96 static if (op == "/") 97 { 98 this.x = this.x / num; 99 this.y = this.y / num; 100 }else 101 static assert(null, "The `" ~ op ~ "` operator is not implemented."); 102 } 103 104 unittest 105 { 106 Vector!double a = [3.0, 3.0]; 107 a += Vector!double(3.0, 3.0); 108 assert(a == Vector!double(6.0, 6.0)); 109 110 a /= 2; 111 assert(a == Vector!double(3.0, 3.0)); 112 } 113 114 /++ 115 Normalizes the vector. 116 +/ 117 void normalize() 118 { 119 immutable d = 1 / length; 120 121 this.x = x * d; 122 this.y = y * d; 123 } 124 inout: 125 /++ 126 Converts a vector to an array. 127 +/ 128 T[] array() 129 { 130 return [x, y]; 131 } 132 133 bool opEquals(Vector a, Vector b) 134 { 135 if (a is b) 136 return true; 137 138 return a.x == b.x && a.y == b.y; 139 } 140 141 bool opEquals(Vector other) 142 { 143 if (this is other) 144 return true; 145 146 return this.x == other.x && this.y == other.y; 147 } 148 149 unittest 150 { 151 Vector!real a = [3.0, 3.0]; 152 assert(a == Vector!real(3.0, 3.0)); 153 } 154 155 int opCmp(Vector rhs) 156 { 157 return (x > rhs.x && y > rhs.y) ? 1 : -1; 158 } 159 160 unittest 161 { 162 Vector!real a = [3.0, 3.0]; 163 assert(a > Vector!real(2.5, 1.1)); 164 assert(a < Vector!real(4.1, 3.1)); 165 } 166 167 Vector!T opBinary(string op)(Vector rhs) 168 { 169 static if (op == "+") 170 { 171 return Vector!T(this.x + rhs.x, this.y + rhs.y); 172 } 173 else 174 static if (op == "-") 175 { 176 return Vector!T(this.x - rhs.x, this.y - rhs.y); 177 } 178 else 179 static if (op == "*") 180 { 181 return Vector!T(this.x * rhs.x, this.y * rhs.y); 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 assert(null, "The `" ~ op ~ "` operator is not implemented."); 192 } 193 194 Vector!T opBinary(string op)(T num) 195 { 196 static if (op == "+") 197 { 198 return Vector!T(this.x + num, this.y + num); 199 }else 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 } 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 }else 217 static if (op == "^^") 218 { 219 return Vector!T(this.x ^^ num, this.y ^^ num); 220 }else 221 static assert(null, "The `" ~ op ~ "` operator is not implemented."); 222 } 223 224 unittest 225 { 226 Vector!int a = [3, 3]; 227 228 assert(a + Vector!int(3, 3) == Vector!int(6, 6)); 229 assert(a ^^ 2 == Vector!int(9, 9)); 230 } 231 232 /++ 233 Vector length. 234 +/ 235 T length() 236 { 237 static if(isIntegral!T) 238 { 239 return cast(T) (sqrt(cast(float) ((this.x * this.x) + (this.y * this.y)))); 240 }else 241 { 242 return sqrt((this.x * this.x) + (this.y * this.y)); 243 } 244 } 245 246 /++ 247 Returns a normalized vector. 248 +/ 249 Vector!T normalized() 250 { 251 immutable d = 1 / length; 252 253 return Vector!T(this.x * d, this.y * d); 254 } 255 } 256 257 /++ 258 Checks if this type is a vector. 259 +/ 260 template isVector(T) 261 { 262 enum isVector = __traits(hasMember, T, "x") && __traits(hasMember, T, "y"); 263 } 264 265 /++ 266 Checks if the vector is integers. 267 +/ 268 template isVectorIntegral(T) 269 if (isVector!T) 270 { 271 enum isVectorIntegral = isIntegral!(T.Type); 272 } 273 274 /++ 275 Checks if the vector is float. 276 +/ 277 template isVectorFloatingPoint(T) 278 if (isVector!T) 279 { 280 enum isVectorFloatingPoint = isFloatingPoint!(T.Type); 281 } 282 283 unittest 284 { 285 static assert(isVector!(Vector!int)); 286 static assert(!isVector!int); 287 288 static assert(isVectorIntegral!(Vector!int)); 289 static assert(!isVectorIntegral!(Vector!float)); 290 291 static assert(isVectorFloatingPoint!(Vector!double)); 292 static assert(!isVectorFloatingPoint!(Vector!long)); 293 } 294 295 alias Vecf = Vector!float; /// Vector float. 296 alias vec(T) = Vector!T; /// Vector. 297 alias vecf = vec!float; /// Vector float. 298 299 /++ 300 Not a numeric vector. 301 +/ 302 template vecNaN(T) 303 if (isVectorFloatingPoint!(Vector!T)) 304 { 305 enum vecNaN = Vector!T(T.nan, T.nan); 306 } 307 308 /// ditto 309 enum vecfNaN = vecNaN!float; 310 311 /++ 312 Checks if the vector is non-numeric. 313 +/ 314 bool isVectorNaN(T)(Vector!T vector) 315 if (isVectorFloatingPoint!(Vector!T)) 316 { 317 import std.math : isNaN; 318 319 return vector.x.isNaN && vector.y.isNaN; 320 } 321 322 /// ditto 323 alias isVecfNaN = isVectorNaN!float; 324 325 /++ 326 Generates a buffer from vectors. 327 328 Params: 329 vectors = Array vector. 330 +/ 331 T[] generateArray(T)(Vector!T[] vectors) @safe nothrow pure 332 { 333 T[] result; 334 foreach (e; vectors) 335 result ~= e.array; 336 337 return result; 338 } 339 340 unittest 341 { 342 assert(vecfNaN.isVecfNaN); 343 assert(vecNaN!double.isVectorNaN); 344 assert(vecNaN!real.isVectorNaN); 345 } 346 347 inout(T) sqr(T)(inout(T) value) @safe nothrow pure 348 { 349 return value * value; 350 } 351 352 /++ 353 Construct the vector modulo. 354 355 Params: 356 vec = Vector. 357 +/ 358 inout(Vector!T) abs(T)(inout(Vector!T) vec) @safe nothrow pure 359 { 360 import std.math : abs; 361 362 return Vector!T(abs(vector.x), abs(vector.y)); 363 } 364 365 /++ 366 Distance between two points. 367 368 Params: 369 a = First point. 370 b = Second point. 371 +/ 372 inout(T) distance(T)(inout(Vector!T) a, inout(Vector!T) b) @safe nothrow pure 373 { 374 import std.math : sqrt; 375 376 return sqrt(sqr(b.x - a.x) + sqr(b.y - a.y)); 377 } 378 379 /++ 380 Average distance between vectors. 381 +/ 382 inout(Vector!T) averateVectors(T)(inout(Vector!T) a, inout(Vector!T) b) @safe nothrow pure 383 { 384 return ((b - a) / 2) + ((a > b) ? b : a); 385 } 386 387 /++ 388 Creates a random vector. 389 390 Params: 391 begin = Begin. 392 end = End. 393 394 Example: 395 --- 396 Vecf rnd = uniform(vecf(32, 16), vecf(64, 48)); 397 // vec.x = random: 32 .. 64 398 // vec.y = random: 16 .. 48 399 --- 400 +/ 401 inout(Vector!T) uniform(T)(inout(Vector!T) begin, inout(Vector!T) end) @safe 402 { 403 import std.random : uniform; 404 405 return Vector!T(uniform(begin.x, end.x), uniform(begin.y, end.y)); 406 } 407 408 /++ 409 Rounds the vector up. 410 411 Params: 412 vec = Rounded vector. 413 414 Example: 415 --- 416 assert(Vecf(32.5,32.5) == Vecf(33, 33)); 417 --- 418 +/ 419 inout(Vector!T) round(T)(inout(Vector!T) vec) @safe nothrow 420 if (isVectorFloatingPoint!(Vector!T)) 421 { 422 import std.math : round; 423 424 return Vector!T(round(vec.x), round(vec.y)); 425 } 426 427 /++ 428 Floors the vector down. 429 430 Params: 431 vec = Floored vector. 432 433 Example: 434 --- 435 assert(vecf(32.5, 32.5) == vecf(32, 32)); 436 --- 437 +/ 438 inout(Vector!T) floor(T)(inout(Vector!T) vec) @safe nothrow 439 if (isVectorFloatingPoint!(Vector!T)) 440 { 441 import std.math : floor; 442 443 return Vector!T(floor(vec.x), floor(vec.y)); 444 }