1 /** 2 Flexible vector template with some math utils 3 */ 4 module sily.vector; 5 6 import std.math; 7 import std.numeric; 8 import std.conv; 9 import std.traits; 10 import std.typetuple; 11 import std.algorithm; 12 import std.stdio; 13 import std.string; 14 import std.format; 15 16 import sily.meta.swizzle; 17 import sily.math; 18 import sily.array; 19 20 /// Alias to vector with set size 21 alias Vector2(T) = Vector!(T, 2); 22 /// Ditto 23 alias Vector3(T) = Vector!(T, 3); 24 /// Ditto 25 alias Vector4(T) = Vector!(T, 4); 26 27 /// Alias to vector with set size and type 28 alias Vector2f = Vector2!float; 29 /// Ditto 30 alias Vector2d = Vector2!double; 31 /// Ditto 32 alias Vector2i = Vector2!int; 33 /// Ditto 34 alias Vector2u = Vector2!uint; 35 36 /// Ditto 37 alias Vector3f = Vector3!float; 38 /// Ditto 39 alias Vector3d = Vector3!double; 40 /// Ditto 41 alias Vector3i = Vector3!int; 42 /// Ditto 43 alias Vector3u = Vector3!uint; 44 45 /// Ditto 46 alias Vector4f = Vector4!float; 47 /// Ditto 48 alias Vector4d = Vector4!double; 49 /// Ditto 50 alias Vector4i = Vector4!int; 51 /// Ditto 52 alias Vector4u = Vector4!uint; 53 54 /// GLSL style alias 55 alias vec2 = Vector2f; 56 /// Ditto 57 alias vec3 = Vector3f; 58 /// Ditto 59 alias vec4 = Vector4f; 60 61 /// Ditto 62 alias dvec2 = Vector2d; 63 /// Ditto 64 alias dvec3 = Vector3d; 65 /// Ditto 66 alias dvec4 = Vector4d; 67 68 /// Ditto 69 alias ivec2 = Vector2i; 70 /// Ditto 71 alias ivec3 = Vector3i; 72 /// Ditto 73 alias ivec4 = Vector4i; 74 75 /// Ditto 76 alias uvec2 = Vector2u; 77 /// Ditto 78 alias uvec3 = Vector3u; 79 /// Ditto 80 alias uvec4 = Vector4u; 81 82 /// Vector structure with data accesible with `[N]` or swizzling 83 struct Vector(T, size_t N) if (isNumeric!T && N > 0) { 84 /// Vector data 85 public T[N] data = [ 0 ]; 86 87 /// Alias to allow easy `data` access 88 alias data this; 89 /// Alias to data type (e.g. float, int) 90 alias dataType = T; 91 /** 92 Alias to vector type. Can be used to contruct vectors 93 of same type 94 --- 95 auto rvec7 = Vector!(real, 7)(10); 96 auto rvec7s = rvec7.VecType(20); 97 --- 98 */ 99 alias VecType = Vector!(T, N); 100 /// Alias to vector size 101 enum size_t size = N; 102 103 /** 104 Constructs Vector from components. If no components present 105 vector will be filled with 0 106 Example: 107 --- 108 // Vector can be constructed manually or with aliases 109 auto v1 = Vector!(int, 2)(10, 20); 110 auto v2 = ivec2(10, 20); 111 auto v3 = Vector2i(10, 20); 112 auto v4 = Vector2!int(10, 20); 113 // Also vector can be given only one value, 114 // in that case it'll be filled with that value 115 auto v5 = ivec4(13); 116 auto v6 = vec4(0.3f); 117 // Vector values can be accessed with array slicing, 118 // by using color symbols or swizzling 119 float v6x = v6.x; 120 float v6z = v6.z; 121 float[] v6yzx = v6.yzx; 122 float v6y = v6[1]; 123 // Valid vector accessors are: 124 // Vector2 - [x, y], [w, h], [u, v] 125 // Vector3 - [x, y, z], [w, h, d], [u, v, t], [r, g, b] 126 // Vector4 - [x, y, z, w], [r, g, b, a] 127 // Other sizes must be accessed with index 128 --- 129 */ 130 this(in T val) { 131 foreach (i; 0 .. size) { data[i] = val; } 132 } 133 /// Ditto 134 this(in T[N] vals...) { 135 data = vals; 136 } 137 138 /* -------------------------------------------------------------------------- */ 139 /* UNARY OPERATIONS OVERRIDES */ 140 /* -------------------------------------------------------------------------- */ 141 142 /// opBinary x [+, -, *, /, %] y 143 auto opBinary(string op, R)(in Vector!(R, N) b) const if ( isNumeric!R ) { 144 // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 145 VecType ret = VecType(); 146 foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b.data[i];" ); } 147 return ret; 148 } 149 150 /// Ditto 151 auto opBinaryRight(string op, R)(in Vector!(R, N) b) const if ( isNumeric!R ) { 152 // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 153 VecType ret = VecType(); 154 foreach (i; 0 .. size) { mixin( "ret[i] = b.data[i] " ~ op ~ " data[i];" ); } 155 return ret; 156 } 157 158 /// Ditto 159 auto opBinary(string op, R)(in R b) const if ( isNumeric!R ) { 160 // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 161 VecType ret = VecType(); 162 foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b;" ); } 163 return ret; 164 } 165 166 /// Ditto 167 auto opBinaryRight(string op, R)(in R b) const if ( isNumeric!R ) { 168 // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 169 VecType ret = VecType(); 170 foreach (i; 0 .. size) { mixin( "ret[i] = b " ~ op ~ " data[i];" ); } 171 return ret; 172 } 173 174 /// opEquals x == y 175 bool opEquals(R)(in Vector!(R, size) b) const if ( isNumeric!R ) { 176 // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 177 bool eq = true; 178 foreach (i; 0 .. size) { eq = eq && data[i] == b.data[i]; } 179 return eq; 180 } 181 182 /// opCmp x [< > <= >=] y 183 int opCmp(R)(in Vector!(R, N) b) const if ( isNumeric!R ) { 184 // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 185 T al = length; 186 T bl = b.length; 187 if (al == bl) return 0; 188 if (al < bl) return -1; 189 return 1; 190 } 191 192 /// opUnary [-, +, --, ++] x 193 auto opUnary(string op)() if(op == "-"){ 194 // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 195 VecType ret = VecType(); 196 if (op == "-") 197 foreach (i; 0 .. size) { data[i] = -data[i]; } 198 return ret; 199 } 200 201 /// opOpAssign x [+, -, *, /, %]= y 202 auto opOpAssign(string op, R)( in Vector!(R, N) b ) if ( isNumeric!R ) { 203 // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 204 foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b.data[i];" ); } 205 return this; 206 } 207 208 /// Ditto 209 auto opOpAssign(string op, R)( in R b ) if ( isNumeric!R ) { 210 // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ "."); 211 foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b;" ); } 212 return this; 213 } 214 215 /// Returns hash 216 size_t toHash() const @safe nothrow { 217 return typeid(data).getHash(&data); 218 } 219 220 static if (N == 2 || N == 3 || N == 4) { 221 static if (N == 2) private enum AS = "x y|w h|u v"; 222 else 223 static if (N == 3) private enum AS = "x y z|w h d|u v t|r g b"; 224 else 225 static if (N == 4) private enum AS = "x y z w|r g b a"; 226 /// Mixes in swizzle 227 mixin accessByString!(T, N, "data", AS); 228 } 229 230 /// Returns copy of vector 231 public VecType copyof() { 232 return VecType(data); 233 } 234 235 /// Returns string representation of vector: `[1.00, 1.00,... , 1.00]` 236 public string toString() const { 237 import std.conv : to; 238 string s; 239 s ~= "["; 240 foreach (i; 0 .. size) { 241 s ~= isFloatingPoint!T ? format("%.2f", data[i]) : format("%d", data[i]); 242 if (i != size - 1) s ~= ", "; 243 } 244 s ~= "]"; 245 return s; 246 } 247 248 /// Returns pointer to data 249 T* ptr() return { 250 return data.ptr; 251 } 252 253 /* -------------------------------------------------------------------------- */ 254 /* STATIC GETTERS AND SETTERS */ 255 /* -------------------------------------------------------------------------- */ 256 257 /// Constructs predefined vector 258 static alias zero = () => VecType(0); 259 /// Ditto 260 static alias one = () => VecType(1); 261 262 static if(isFloatingPoint!T) { 263 /// Ditto 264 static alias inf = () => VecType(float.infinity); 265 } 266 267 static if(N == 2) { 268 /// Ditto 269 static alias left = () => VecType(-1, 0); 270 /// Ditto 271 static alias right = () => VecType(1, 0); 272 /// Ditto 273 static alias up = () => VecType(0, -1); 274 /// Ditto 275 static alias down = () => VecType(0, 1); 276 } 277 278 static if(N == 3) { 279 static alias forward = () => VecType(0, 0, -1); 280 /// Ditto 281 static alias back = () => VecType(0, 0, 1); 282 /// Ditto 283 static alias left = () => VecType(-1, 0, 0); 284 /// Ditto 285 static alias right = () => VecType(1, 0, 0); 286 /// Ditto 287 static alias up = () => VecType(0, 1, 0); 288 /// Ditto 289 static alias down = () => VecType(0, -1, 0); 290 } 291 292 /* -------------------------------------------------------------------------- */ 293 /* MATH */ 294 /* -------------------------------------------------------------------------- */ 295 296 /// Returns squared vector length 297 public T lengthSquared() { 298 T l = 0; 299 foreach (i; 0 .. size) { l += data[i] * data[i]; } 300 return l; 301 } 302 303 /** 304 Returns squared distance from vector to `b` 305 Params: 306 b = Vector to calculate distance to 307 Returns: Distance 308 */ 309 public T distanceSquaredTo(VecType b) { 310 T dist = 0; 311 foreach (i; 0 .. size) { dist += (data[i] - b.data[i]) * (data[i] - b.data[i]); } 312 return dist; 313 } 314 315 /* -------------------------------------------------------------------------- */ 316 /* FLOATING POINT MATH */ 317 /* -------------------------------------------------------------------------- */ 318 // Int math is still might be accessible 319 // if I'll need it. All I'd need to do 320 // Is to add another `static if` 321 322 // FLOAT VECTORS 323 static if(isFloatingPoint!T) { 324 /** 325 Is vector approximately close to `v` 326 Params: 327 v = Vector to compare 328 Returns: 329 */ 330 public bool isClose(VecType v) { 331 bool eq = true; 332 foreach (i; 0 .. size) { eq = eq && data[i].isClose(v[i], float.epsilon); } 333 return eq; 334 } 335 336 /// Returns vector length 337 public T length() { 338 return sqrt(lengthSquared); 339 } 340 341 /// Normalises vector 342 public void normalize() { 343 T l = lengthSquared; 344 if (l != 0) { 345 l = sqrt(lengthSquared); 346 foreach (i; 0 .. size) { data[i] /= l; } 347 } 348 } 349 /// Ditto 350 alias normalise = normalize; 351 352 /// Returns true if vector is normalised 353 public bool isNormalized() { 354 return lengthSquared.isClose(1, float.epsilon); 355 } 356 /// Ditto 357 alias isNormalised = isNormalized; 358 359 /** 360 Calculates distance to vector `b` 361 Params: 362 b = Vector 363 Returns: Distance 364 */ 365 public T distanceTo(VecType b) { 366 return sqrt(distanceSquaredTo(b)); 367 } 368 369 /** 370 Performs dot product 371 Params: 372 b = Vector 373 Returns: dot product 374 */ 375 public float dot(VecType b) { 376 T d = 0; 377 foreach (i; 0 .. size) { d += data[i] * b.data[i]; } 378 return d; 379 } 380 381 /// Signs current vector 382 public void sign() { 383 foreach (i; 0 .. size) { data[i] = data[i].sgn(); } 384 } 385 386 /// Floors vector values 387 public void floor() { 388 foreach (i; 0 .. size) { data[i] = data[i].floor(); } 389 } 390 391 /// Ceils vector values 392 public void ceil() { 393 foreach (i; 0 .. size) { data[i] = data[i].ceil(); } 394 } 395 396 /// Rounds vector values 397 public void round() { 398 foreach (i; 0 .. size) { data[i] = data[i].round(); } 399 } 400 401 /// Abs vector values 402 public void abs() { 403 foreach (i; 0 .. size) { data[i] = data[i].abs(); } 404 } 405 406 /** 407 Clamps vector values to min 408 Params: 409 b = Minimal Vector 410 */ 411 public void min(VecType b) { 412 foreach (i; 0 .. size) { data[i] = data[i].min(b.data[i]); } 413 } 414 415 /** 416 Clamps vector values to max 417 Params: 418 b = Maximal Vector 419 */ 420 public void max(VecType b) { 421 foreach (i; 0 .. size) { data[i] = data[i].max(b.data[i]); } 422 } 423 424 /** 425 Clamps vector values 426 Params: 427 b = Minimal Vector 428 b = Maximal Vector 429 */ 430 public void clamp(VecType p_min, VecType p_max) { 431 foreach (i; 0 .. size) { data[i] = data[i].clamp(p_min.data[i], p_max.data[i]); } 432 } 433 434 /** 435 Snaps vector values 436 Params: 437 p_step = Vector to snap to 438 */ 439 public void snap(VecType p_step) { 440 foreach (i; 0 .. size) { 441 data[i] = data[i].snap(p_step[i]); 442 } 443 } 444 445 /** 446 Limits vector length 447 Params: 448 p_len = Max length 449 */ 450 public void limitLength(T p_len) { 451 T l = length(); 452 if (l > 0 && p_len < l) { 453 for (int i = 0; i < size; ++i) { 454 data[i] /= l; 455 data[i] *= p_len; 456 } 457 } 458 } 459 460 /** 461 Linear interpolates vector 462 Params: 463 to = Vector to interpolate to 464 weight = Interpolation weight in range [0.0, 1.0] 465 */ 466 public void lerp(VecType to, T weight) { 467 foreach (i; 0 .. size) { data[i] = (weight * (to.data[i] - data[i])); } 468 } 469 470 // FIXME 471 // TODO 472 // it's in math 473 // public Vector2!T cubicInterpolate(Vector2!T b, Vector2!T prea, Vector2!T postb, float weight) { 474 // Vector2 res = *this; 475 // res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight); 476 // res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight); 477 // return res; 478 // } 479 } 480 481 /* -------------------------------------------------------------------------- */ 482 /* VECTOR2F */ 483 /* -------------------------------------------------------------------------- */ 484 static if(isFloatingPoint!T && N == 2) { 485 // public static Vector2!T fromAngle(float p_angle) { 486 // return Vector2!T(cos(p_angle), sin(p_angle)); 487 // } 488 489 // public float cross(VecType b) { 490 // return this.x * b.y - this.y * b.x; 491 // } 492 493 // public float angle() { 494 // return atan2(this.y, this.x); 495 // } 496 497 // public float angleTo(Vector2!T b) { 498 // return atan2(cross(b), dot(b)); 499 // } 500 501 // public float angleToPoint(Vector2!T b) { 502 // return (b - data).angle(); 503 // } 504 505 // public float aspect() { 506 // return this.x / this.y; 507 // } 508 509 // public Vector2!T project(Vector2!T b) { 510 // return b * (dot(b) / b.lengthSquared()); 511 // } 512 513 // public Vector2!T moveToward(Vector2!T p_to, const T p_delta) { 514 // Vector2!T v = copyof(); 515 // Vector2!T vd = p_to - v; 516 // T len = vd.length; 517 // return len <= p_delta || len < float.epsilon ? p_to : v + vd / len * p_delta; 518 // } 519 520 // public Vector2!T slide(Vector2!T p_normal) { 521 // if (!p_normal.isNormalized) { 522 // writeln("Normal vector must be normalized"); 523 // // throw new Error("MATH::ERROR::VECTOR2"); 524 // return copyof(); 525 // } 526 // return copyof() - p_normal * dot(p_normal); 527 // } 528 529 // public Vector2!T bounce(Vector2!T p_normal) { 530 // return -reflect(p_normal); 531 // } 532 533 // public Vector2!T reflect(Vector2!T p_normal) { 534 // if (!p_normal.isNormalized) { 535 // writeln("Normal vector must be normalized"); 536 // // throw new Error("MATH::ERROR::VECTOR2"); 537 // return copyof(); 538 // } 539 // return to!T(2) * p_normal * dot(p_normal) - copyof(); 540 // } 541 542 // public Vector2!T orthogonal() { 543 // return Vector2!T(this.y, -this.x); 544 // } 545 546 // public Vector2!T rotated(float phi) { 547 // T sine = sin(phi); 548 // T cosi = cos(phi); 549 // return Vector2!T( 550 // this.x * cosi - this.y * sine, 551 // this.x * sine + this.y * cosi); 552 // } 553 554 // public VecType slerp(VecType to, T weight) { 555 // T stLensq = lengthSquared; 556 // T enLensq = to.lengthSquared; 557 // if (stLensq == 0.0f || enLensq == 0.0f) { 558 // // Zero length vectors have no angle, so the best we can do is either lerp or throw an error. 559 // return lerp(to, weight); 560 // } 561 // T stLen = sqrt(stLensq); 562 // T rsLen = stLen.lerp(sqrt(enLensq), weight); 563 // T angle = angleTo(to); 564 // return rotated(angle * weight) * (rsLen / stLen); 565 // } 566 567 // LINK https://glmatrix.net/docs/vec2.js 568 // LINK https://github.com/godotengine/godot/blob/master/core/math/vector2.cpp 569 } 570 571 /* -------------------------------------------------------------------------- */ 572 /* VECTOR3F */ 573 /* -------------------------------------------------------------------------- */ 574 static if(isFloatingPoint!T && N == 3) { 575 // TODO 576 // VecType cross(VecType b) { 577 // VecType cr = VecType(); 578 // T ax = data[0], 579 // ay = data[1], 580 // az = data[2]; 581 // T bx = b.data[0], 582 // by = b.data[1], 583 // bz = b.data[2]; 584 // cr.data[0] = ay * bz - az * by; 585 // cr.data[1] = az * bx - ax * bz; 586 // cr.data[2] = ax * by - ay * bx; 587 // return cr; 588 // } 589 590 // TODO 591 592 // LINK https://glmatrix.net/docs/vec3.js.html 593 // LINK https://github.com/godotengine/godot/blob/master/core/math/vector3.cpp 594 } 595 596 /* -------------------------------------------------------------------------- */ 597 /* VECTOR4F */ 598 /* -------------------------------------------------------------------------- */ 599 // here probably gonna be almost nothing 600 // here be dragons? 601 static if(isFloatingPoint!T && N == 4) { 602 // TODO ? 603 } 604 605 // Vector2 Vector2::posmod(const real_t p_mod) const { 606 // return Vector2(Math::fposmod(x, p_mod), Math::fposmod(y, p_mod)); 607 // } 608 609 // Vector2 Vector2::posmodv(const Vector2 &p_modv) const { 610 // return Vector2(Math::fposmod(x, p_modv.x), Math::fposmod(y, p_modv.y)); 611 // } 612 }