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