1 /* 2 * Copyright (c) 2017-2018 SEL 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 * See the GNU Lesser General Public License for more details. 13 * 14 */ 15 /** 16 * Copyright: Copyright (c) 2017-2020 sel-project 17 * License: MIT 18 * Authors: Kripth 19 * Source: $(HTTP github.com/sel-project/sel-math/sel/math/vector.d, sel/math/vector.d) 20 */ 21 module sel.math.vector; 22 23 import std.algorithm : reverse, canFind; 24 import std.array : join, split; 25 import std.conv : to, ConvException; 26 static import std.math; 27 import std.meta : staticIndexOf; 28 import std.range.primitives : ElementType; 29 import std.string : replace; 30 import std.traits : IntegralTypeOf, isNumeric, isArray, CommonType, isFloatingPointTrait = isFloatingPoint, isImplicitlyConvertible; 31 import std.typecons : isTuple; 32 import std.typetuple : TypeTuple; 33 34 static import std.typecons; 35 36 /** 37 * Vector for coordinates storing and operations. 38 */ 39 struct Vector(T, char[] c) if(c.length > 1 && areValidCoordinates(c)) { 40 41 public alias Type = T; 42 public alias coordinates = c; 43 44 mixin("alias Tuple = std.typecons.Tuple!(T, \"" ~ join(coordinates.idup.split(""), "\", T, \"") ~ "\");"); 45 46 mixin("public enum coords = TypeTuple!('" ~ join(coordinates.idup.split(""), "','") ~ "');"); 47 48 enum bool isFloatingPoint = isFloatingPointTrait!T; 49 50 //private Tuple value; 51 52 union { 53 54 Tuple value; 55 struct { mixin("T " ~ join(coordinates.idup.split(""), ";T ") ~ ";"); } 56 T[c.length] array; 57 58 } 59 60 alias tuple = value; 61 62 public pure nothrow @safe @nogc this(in Tuple value) { 63 this.value = value; 64 } 65 66 public pure nothrow @safe @nogc this(in T value) { 67 this.array = value; 68 } 69 70 public @safe this(E...)(in E args) if(E.length == coordinates.length) { 71 foreach(i, value; args) { 72 this.array[i] = value; 73 } 74 } 75 76 public @safe @nogc this(in T[coords.length] array) { 77 this.array = array; 78 } 79 80 public @safe @nogc this(in T[] array) { 81 this.array = array; 82 } 83 84 /** 85 * Compares the vector with another vector of the same length or with 86 * a single number. 87 * Returns: true if all the values are equals, false otherwise 88 * Example: 89 * --- 90 * assert(Vector2!int(0, 10) == Vector2!int(0, 10)); 91 * assert(Vector3!ubyte(1, 1, 255) == Vector3!real(1, 1, 255)); 92 * assert(vector(0, 0, 0, 0) == 0); 93 * assert(vector(1, 2) == [1, 2]); 94 * assert(vector(float.nan, float.nan) != vector(float.nan, float.nan)); 95 * --- 96 */ 97 public bool opEquals(F)(in F value) inout { 98 static if(isVector!F && coords == F.coords) return this.opEqualsImpl!"this.{c}==value.{c}"(value); 99 else static if(isArray!F) return value.length == coords.length && this.opEqualsImpl!"this.{c}==value[{i}]"(value); 100 else static if(__traits(compiles, T.init == F.init)) return this.opEqualsImpl!"this.{c}==value"(value); 101 else return false; 102 } 103 104 private bool opEqualsImpl(string op, F)(F value) inout { 105 mixin((){ 106 string[] ret; 107 foreach(i, immutable c; coords) { 108 ret ~= op.replace("{c}", to!string(c)).replace("{i}", to!string(i)); 109 } 110 return "return " ~ ret.join("&&") ~ ";"; 111 }()); 112 } 113 114 // for associative arrays key 115 /*public bool opEquals(ref const Vector) const { 116 return false; 117 }*/ 118 119 /** 120 * Performs an unary operation on the vector. 121 * Returns: the new vector 122 * Example: 123 * --- 124 * auto v = vector(-1, 0, 1); 125 * assert(-v == vector(1, 0, -1)); 126 * assert(++v == vector(0, 1, 2)); // this will change the original vector's values! 127 * assert(v-- == vector(0, 1, 2) && v == vector(-1, 0, 1)); 128 * --- 129 */ 130 public typeof(this) opUnary(string op)() if(__traits(compiles, { mixin("T t;t=" ~ op ~ "t;"); })) { 131 typeof(this) ret; 132 foreach(immutable c ; coords) { 133 mixin("ret.value." ~ c ~ "=" ~ op ~ "this.value." ~ c ~ ";"); 134 } 135 return ret; 136 } 137 138 /** 139 * Performs a binary operation on the vector. 140 * Params: 141 * value = a number, a vector or an array with the same size 142 * Returns: the new vector 143 * Example: 144 * --- 145 * assert(vector(1, 1) - 1 == vector(0, 0)); 146 * assert(vector(10, 10) * vector(0, 9) == vector(0, 90)); 147 * assert(vector(16, 15) & [15, 3] == vector(0, 3)); 148 * assert(1 - vector(100, 0, -100) == vector(-99, 1, 101)); 149 * --- 150 */ 151 public typeof(this) opBinary(string op, F)(F value) inout if(op != "in") { 152 return this.dup.opOpAssign!op(value); 153 } 154 155 public typeof(this) opBinaryRight(string op, F)(F value) inout if(op != "in" && __traits(compiles, typeof(this)(value))) { 156 return typeof(this)(value).opBinary!op(this); 157 } 158 159 /** 160 * Performs an assign operation on the vector, modifying it. 161 * Params: 162 * value = a number, a vector or an array with the same size 163 * Returns: 164 * Example: 165 * --- 166 * auto v = vector(1, 2); 167 * v += 4; 168 * v *= [0, 2]; 169 * assert(v == vector(0, 12)); 170 * --- 171 */ 172 public typeof(this) opOpAssign(string op, F)(F value) if(isVector!F && coordinates == F.coordinates) { 173 return this.opAssignImpl!("this.value.{c}" ~ op ~ "=value.{c}")(value); 174 } 175 176 /// ditto 177 public typeof(this) opOpAssign(string op, F)(F value) if(isArray!F) { 178 return this.opAssignImpl!("this.value.{c}" ~ op ~ "=value[{i}]")(value); 179 } 180 181 /// ditto 182 public typeof(this) opOpAssign(string op, F)(F value) if(isImplicitlyConvertible!(F, T)) { 183 return this.opAssignImpl!("this.value.{c}" ~ op ~ "=value")(value); 184 } 185 186 private typeof(this) opAssignImpl(string query, F)(F value) { 187 foreach(i, immutable c; coords) { 188 mixin(query.replace("{c}", to!string(c)).replace("{i}", to!string(i)) ~ ";"); 189 } 190 return this; 191 } 192 193 /** 194 * Converts the vector to the given one, mantaining the variables's 195 * value when possible. 196 * Example: 197 * --- 198 * assert(cast(Vector2!int)vector(.1, .1, 14) == vector(0, 14)); 199 * assert(cast(Vector4!real)vector(.5, 100) == vector(.5, 0, 100, 0)); 200 * // this will only return the vector 201 * assert(cast(Vector2!int 202 * --- 203 */ 204 public @safe auto opCast(F)() inout if(isVector!F) { 205 static if(is(T == F) && coordinates == F.coordinates) { 206 return this; 207 } else { 208 F ret; 209 foreach(immutable c; F.coords) { 210 static if(coordinates.canFind(c)) { 211 mixin("ret.value." ~ c) = to!(F.Type)(mixin("this." ~ c)); 212 } 213 } 214 return ret; 215 } 216 } 217 218 /** 219 * Converts the vector into an array of the same size. 220 * Example: 221 * --- 222 * assert(cast(int[])vector(1, 2) == [1, 2]); 223 * assert(cast(long[])vector(.1, 1.5, -.1) == [0L, 1L, 0L]); 224 * --- 225 */ 226 public @safe auto opCast(F)() inout if(isArray!F) { 227 F array = new typeof(F.init[0])[coords.length]; 228 foreach(i, coord; coords) { 229 array[i] = to!(typeof(F.init[0]))(mixin("this." ~ coord)); 230 } 231 return array; 232 } 233 234 /** 235 * Changes the vector's type. 236 */ 237 public auto type(F)() inout if(isImplicitlyConvertible!(F, T)) { 238 Vector!(F, coordinates) ret; 239 foreach(immutable c ; coords) { 240 mixin("ret.value." ~ c) = mixin("this." ~ c); 241 } 242 return ret; 243 } 244 245 /** 246 * Duplicates the vector, mantaing the type, variables' 247 * names and their value. 248 * Example: 249 * --- 250 * assert(vector(1, 1).dup == vector(1, 1)); 251 * --- 252 */ 253 alias dup = type!T; 254 255 /** 256 * Gets the vector's length. 257 */ 258 public @property double length() inout { 259 double length = 0; 260 foreach(immutable c ; coords) { 261 length += mixin("this." ~ c) ^^ 2; 262 //mixin("length += this.value." ~ c ~ " * this.value." ~ c ~ ";"); 263 } 264 return std.math.sqrt(length); 265 } 266 267 /** 268 * Sets the vector's length. 269 */ 270 public @property double length(double length) { 271 double mult = length / this.length; 272 foreach(immutable c ; coords) { 273 static if(is(T == double)) { 274 mixin("this.value." ~ c) *= mult; 275 } else { 276 mixin("this.value." ~ c) = cast(T)(mixin("this." ~ c) * mult); 277 } 278 } 279 return length; 280 } 281 282 /** 283 * Converts the vector into a string for logging and debugging purposes. 284 */ 285 public string toString() inout { 286 string[] cs; 287 foreach(i, coord; coords) { 288 cs ~= to!string(mixin("this." ~ coord)); 289 } 290 return "Vector!(" ~ T.stringof ~ ", \"" ~ coordinates.idup ~ "\")(" ~ cs.join(", ") ~ ")"; 291 } 292 293 } 294 295 /// ditto 296 alias Vector(T, string coords) = Vector!(T, coords.dup); 297 298 /// ditto 299 alias Vector2(T) = Vector!(T, "xz"); 300 301 /// ditto 302 alias Vector3(T) = Vector!(T, "xyz"); 303 304 /// ditto 305 alias Vector4(T) = Vector!(T, "xyzw"); 306 307 private bool areValidCoordinates(char[] coords) { 308 foreach(i, char c; coords[0..$-1]) { 309 if(coords[i+1..$].canFind(c)) return false; 310 } 311 return true; 312 } 313 314 /** 315 * Automatically creates a vector if the number of the 316 * given arguments matches one of the default vectors. 317 * Example: 318 * --- 319 * assert(is(typeof(vector(1, 1)) == Vector2!int)); 320 * assert(is(typeof(vector(2Lu, 4)) == Vector2!ulong)); 321 * assert(is(typeof(vector(5, 5, 19.0)) == Vector3!double)); 322 * assert(is(typeof(vector(0, real.nan, double.nan, float.nan)) == Vector4!real)); 323 * --- 324 */ 325 public auto vector(E...)(E args) if(E.length > 1 && E.length <= 4 && !is(CommonType!E == void)) { 326 return mixin("Vector" ~ to!string(E.length) ~ "!(CommonType!E)")(args); 327 } 328 329 /// Checks if the given type is a vector 330 enum bool isVector(T) = __traits(compiles, Vector!(T.Type, T.coordinates)(T.Type.init)); 331 332 public nothrow @safe T mathFunction(alias func, T)(T vector) if(isVector!T) { 333 T.Type[] values; 334 foreach(immutable c ; T.coords) { 335 values ~= cast(T.Type)func(mixin("vector." ~ c)); 336 } 337 return T(values); 338 } 339 340 /** 341 * Rounds a vector to the nearest integer. 342 * Example: 343 * --- 344 * assert(round(vector(.25, .5, .75)) == vector(0, 1, 1)); 345 * --- 346 */ 347 public nothrow @safe T round(T)(T vector) if(isVector!T) { 348 return mathFunction!(std.math.round)(vector); 349 } 350 351 /** 352 * Floors a vector to the nearest integer. 353 * Example: 354 * --- 355 * assert(floor(vector(.25, .5, .75)) == vector(0, 0, 0)); 356 * --- 357 */ 358 public nothrow @safe T floor(T)(T vector) if(isVector!T) { 359 return mathFunction!(std.math.floor)(vector); 360 } 361 362 /** 363 * Ceils a vector to the nearest integer. 364 * Example: 365 * --- 366 * assert(ceil(vector(.25, .5, .75)) == vector(1, 1, 1)); 367 * --- 368 */ 369 public nothrow @safe T ceil(T)(T vector) if(isVector!T) { 370 return mathFunction!(std.math.ceil)(vector); 371 } 372 373 /** 374 * Calculate the absolute value of the array. 375 * Example: 376 * --- 377 * assert(abs(vector(-1, 0, 90)) == vector(1, 0, 90)); 378 * --- 379 */ 380 public nothrow @safe T abs(T)(T vector) if(isVector!T) { 381 return mathFunction!(std.math.abs)(vector); 382 } 383 384 /** 385 * Checks whether or not every member of the vector is finite 386 * (not infite, -inifite, nan). 387 * Example: 388 * --- 389 * assert(isFinite(vector(1, 2))); 390 * assert(isFinite(vector(float.min, float.max))); 391 * assert(!isFinite(vector(1, float.nan))); 392 * assert(!isFinite(vector(-float.infinity, 1f/0f))); 393 * --- 394 */ 395 public pure nothrow @safe @nogc bool isFinite(T)(T vector) if(isVector!T && T.isFloatingPoint) { 396 foreach(immutable c ; T.coords) { 397 if(!std.math.isFinite(mixin("vector." ~ c))) return false; 398 } 399 return true; 400 } 401 402 /** 403 * Checks whether or not at least one member of the vector 404 * is not a number (nan). 405 * Example: 406 * --- 407 * assert(!isNaN(vector(0, 2.1))); 408 * assert(isNaN(vector(float.init, -double.init))); 409 * assert(isNaN(vector(0, float.nan))); 410 * --- 411 */ 412 public pure nothrow @safe @nogc bool isNaN(T)(T vector) if(isVector!T && T.isFloatingPoint) { 413 foreach(immutable c ; T.coords) { 414 if(std.math.isNaN(mixin("vector." ~ c))) return true; 415 } 416 return false; 417 } 418 419 public @safe double distanceSquared(F, G)(F vector1, G vector2) if(isVector!F && isVector!G && F.coordinates == G.coordinates) { 420 double sum = 0; 421 foreach(immutable c ; F.coords) { 422 sum += std.math.pow(mixin("vector1." ~ c) - mixin("vector2." ~ c), 2); 423 } 424 return sum; 425 } 426 427 /** 428 * Calculates the distance between to vectors of the 429 * same length. 430 * Params: 431 * vector1 = the first vector 432 * vector2 = the second vector 433 * Returns: the distance between the two vectors (always higher or equals than 0) 434 * Example: 435 * --- 436 * assert(distance(vector(0, 0), vector(1, 0)) == 1); 437 * assert(distance(vector(0, 0, 0) == vector(1, 1, 1)) == 3 ^^ .5); // 3 ^^ .5 is the squared root of 3 438 * --- 439 */ 440 public @safe double distance(T, char[] coords, E)(Vector!(T, coords) vector1, Vector!(E, coords) vector2) { 441 return std.math.sqrt(distanceSquared(vector1, vector2)); 442 } 443 444 public pure nothrow @safe double dot(T, char[] coords, E)(Vector!(T, coords) vector1, Vector!(E, coords) vector2) { 445 double dot = 0; 446 foreach(immutable c ; Vector!(T, coords).coords) { 447 dot += mixin("vector1." ~ c) * mixin("vector2." ~ c); 448 } 449 return dot; 450 } 451 452 public pure nothrow @safe Vector!(CommonType!(A, B), coords) cross(A, B, char[] coords)(Vector!(A, coords) a, Vector!(B, coords) b) { 453 foreach(immutable exc ; Vector!(T, coords).coords) { 454 455 } 456 } 457 458 unittest { 459 460 Vector3!int v3 = Vector3!int(-1, 0, 12); 461 462 // storage 463 assert(v3.x == -1); 464 assert(v3.y == 0); 465 assert(v3.z == 12); 466 assert(v3.tuple == Vector3!int.Tuple(-1, 0, 12)); 467 assert(v3.array == [-1, 0, 12]); 468 469 // comparing 470 assert(v3.x == -1); 471 assert(v3.y == 0); 472 assert(v3.z == 12); 473 assert(v3 == Vector3!int(-1, 0, 12)); 474 assert(v3 == Vector3!float(-1, 0, 12)); 475 assert(v3 != Vector3!double(-1, 0, 12.00000001)); 476 477 // unary 478 assert(-v3 == Vector3!int(1, 0, -12)); 479 assert(++v3 == Vector3!int(0, 1, 13) && v3 == Vector3!int(0, 1, 13)); 480 assert(v3-- == Vector3!int(0, 1, 13) && v3 == Vector3!int(-1, 0, 12)); 481 482 // binary operator 483 assert(v3 + 3 == Vector3!int(2, 3, 15)); 484 assert(v3 * 100 == Vector3!int(-100, 0, 1200)); 485 assert(v3 - v3 == Vector3!int(0, 0, 0)); 486 assert(Vector3!double(.5, 0, 0) + v3 == Vector3!double(-.5, 0, 12)); 487 assert(v3 * [1, 2, 3] == Vector3!int(-1, 0, 36)); 488 assert((v3 & 1) == Vector3!int(1, 0, 0)); 489 assert(1 - v3 == Vector3!int(2, 1, -11)); 490 491 // assign operator 492 assert((v3 *= 3) == Vector3!int(-3, 0, 36)); 493 assert(v3 == Vector3!int(-3, 0, 36)); 494 v3 >>= 1; 495 assert(v3 == Vector3!int(-2, 0, 18)); 496 497 // cast 498 Vector3!float v3f = cast(Vector3!float)v3; 499 Vector2!int reduced = cast(Vector2!int)v3; 500 Vector4!long bigger = cast(Vector4!long)v3; 501 assert(v3f == Vector3!float(-2, 0, 18)); 502 assert(reduced == Vector2!float(-2, 18)); 503 assert(bigger == Vector4!long(-2, 0, 18, 0)); 504 505 // vector function 506 assert(vector(8, 19).Type.stringof == "int"); 507 assert(vector(1.0, 2, 99.9).Type.stringof == "double"); 508 assert(vector(1f, .01, 12L).Type.stringof == "double"); 509 510 // math functions 511 assert(round(vector(.2, .7)) == vector(0, 1)); 512 assert(floor(vector(.2, .7)) == vector(0, 0)); 513 assert(ceil(vector(.2, .7)) == vector(1, 1)); 514 assert(abs(vector(-.2, .7)) == vector(.2, .7)); 515 516 // distance 517 assert(distance(vector(0, 0), vector(0, 1)) == 1); 518 assert(distance(vector(0, 0, 0), vector(1, 1, 1)) == 3 ^^ .5); 519 520 // as associative array key 521 uint[Vector2!int] aa; 522 aa[Vector2!int(1, 2)] = 4; 523 assert(aa[Vector2!int(1, 2)] == 4); 524 525 }