1 /** 2 * Copyright: Enalye 3 * License: Zlib 4 * Authors: Enalye 5 */ 6 module atelier.core.vec2; 7 8 import std.math; 9 10 import atelier.core.vec3; 11 import atelier.core.vec4; 12 13 /// Ratio to multiply with to get a value in radians from a value in degrees. 14 enum double degToRad = std.math.PI / 180.0; 15 /// Ratio to multiply with to get a value in degrees from a value in radians. 16 enum double radToDeg = 180.0 / std.math.PI; 17 18 /// Represent a mathematical 2-dimensional vector. 19 struct Vec2(T) { 20 static assert(__traits(isArithmetic, T)); 21 22 static if (__traits(isUnsigned, T)) { 23 /// {1, 1} vector. Its length is not one ! 24 enum one = Vec2!T(1u, 1u); 25 /// Null vector. 26 enum zero = Vec2!T(0u, 0u); 27 } 28 else { 29 static if (__traits(isFloating, T)) { 30 /// {1, 1} vector. Its length is not one ! 31 enum one = Vec2!T(1f, 1f); 32 /// {0.5, 0.5} vector. Its length is not 0.5 ! 33 enum half = Vec2!T(.5f, .5f); 34 /// Null vector. 35 enum zero = Vec2!T(0f, 0f); 36 /// {-1, 0} vector. 37 enum left = Vec2!T(-1f, 0f); 38 /// {1, 0} vector. 39 enum right = Vec2!T(1f, 0f); 40 /// {0, -1} vector. 41 enum down = Vec2!T(0f, -1f); 42 /// {0, 1} vector. 43 enum up = Vec2!T(0f, 1f); 44 } 45 else { 46 /// {1, 1} vector. Its length is not one ! 47 enum one = Vec2!T(1, 1); 48 /// Null vector. 49 enum zero = Vec2!T(0, 0); 50 /// {-1, 0} vector. 51 enum left = Vec2!T(-1, 0); 52 /// {1, 0} vector. 53 enum right = Vec2!T(1, 0); 54 /// {0, -1} vector. 55 enum down = Vec2!T(0, -1); 56 /// {0, 1} vector. 57 enum up = Vec2!T(0, 1); 58 } 59 } 60 61 /// x-axis coordinate. 62 T x; 63 /// y-axis coordinate. 64 T y; 65 66 /// Changes {x, y} 67 void set(T x_, T y_) { 68 x = x_; 69 y = y_; 70 } 71 72 /// The distance between this point and the other one. 73 T distance(Vec2!T v) const { 74 static if (__traits(isUnsigned, T)) 75 alias V = int; 76 else 77 alias V = T; 78 79 V px = x - v.x, py = y - v.y; 80 81 static if (__traits(isFloating, T)) 82 return std.math.sqrt(px * px + py * py); 83 else 84 return cast(T) std.math.sqrt(cast(float)(px * px + py * py)); 85 } 86 87 /// The distance between this point and the other one squared. 88 /// Not square rooted, so no crash and more efficient. 89 T distanceSquared(Vec2!T v) const { 90 static if (__traits(isUnsigned, T)) 91 alias V = int; 92 else 93 alias V = T; 94 95 V px = x - v.x, py = y - v.y; 96 97 return px * px + py * py; 98 } 99 100 /// Dot product of the 2 vectors. 101 T dot(const Vec2!T v) const { 102 return x * v.x + y * v.y; 103 } 104 105 /// Cross product of the 2 vectors. 106 T cross(const Vec2!T v) const { 107 static if (__traits(isUnsigned, T)) 108 return cast(int)(x * v.y) - cast(int)(y * v.x); 109 else 110 return (x * v.y) - (y * v.x); 111 } 112 113 /// The normal axis of this vector. 114 Vec2!T normal() const { 115 return Vec2!T(-y, x); 116 } 117 118 /// Reflect this vector inside another one that represents the area change. 119 Vec2!T reflect(const Vec2!T v) const { 120 static if (__traits(isFloating, T)) { 121 const T dotNI2 = 2.0 * x * v.x + y * v.y; 122 return Vec2!T(cast(T)(x - dotNI2 * v.x), cast(T)(y - dotNI2 * v.y)); 123 } 124 else { 125 const T dotNI2 = 2 * x * v.x + y * v.y; 126 static if (__traits(isUnsigned, T)) 127 return Vec2!T(cast(int)(x) - cast(int)(dotNI2 * v.x), 128 cast(int)(y) - cast(int)(dotNI2 * v.y)); 129 else 130 return Vec2!T(x - dotNI2 * v.x, y - dotNI2 * v.y); 131 } 132 } 133 134 /// Refracts this vector onto another one that represents the area change. 135 Vec2!T refract(const Vec2!T v, float eta) const { 136 static if (__traits(isFloating, T)) { 137 const T dotNI = (x * v.x + y * v.y); 138 T k = 1.0 - eta * eta * (1.0 - dotNI * dotNI); 139 if (k < .0) 140 return Vec2!T(T.init, T.init); 141 else { 142 const double s = (eta * dotNI + sqrt(k)); 143 return Vec2!T(eta * x - s * v.x, eta * y - s * v.y); 144 } 145 } 146 else { 147 const float dotNI = cast(float)(x * v.x + y * v.y); 148 float k = 1.0f - eta * eta * (1.0f - dotNI * dotNI); 149 if (k < 0f) 150 return Vec2!T(T.init, T.init); 151 else { 152 const float s = (eta * dotNI + sqrt(k)); 153 return Vec2!T(cast(T)(eta * x - s * v.x), cast(T)(eta * y - s * v.y)); 154 } 155 } 156 } 157 158 /// The smaller vector possible between the two. 159 Vec2!T min(const Vec2!T v) const { 160 return Vec2!T(x < v.x ? x : v.x, y < v.y ? y : v.y); 161 } 162 163 /// The larger vector possible between the two. 164 Vec2!T max(const Vec2!T v) const { 165 return Vec2!T(x > v.x ? x : v.x, y > v.y ? y : v.y); 166 } 167 168 /// Remove negative components. 169 Vec2!T abs() const { 170 static if (__traits(isFloating, T)) 171 return Vec2!T(x < .0 ? -x : x, y < .0 ? -y : y); 172 else static if (__traits(isUnsigned, T)) 173 return Vec2!T(x < 0U ? -x : x, y < 0U ? -y : y); 174 else 175 return Vec2!T(x < 0 ? -x : x, y < 0 ? -y : y); 176 } 177 178 /// Truncate the values. 179 Vec2!T floor() const { 180 static if (__traits(isFloating, T)) 181 return Vec2!T(std.math.floor(x), std.math.floor(y)); 182 else 183 return this; 184 } 185 186 /// Round up the values. 187 Vec2!T ceil() const { 188 static if (__traits(isFloating, T)) 189 return Vec2!T(std.math.ceil(x), std.math.ceil(y)); 190 else 191 return this; 192 } 193 194 /// Round the values to the nearest integer. 195 Vec2!T round() const { 196 static if (__traits(isFloating, T)) 197 return Vec2!T(std.math.round(x), std.math.round(y)); 198 else 199 return this; 200 } 201 202 static if (__traits(isFloating, T)) { 203 /// Returns the vector actual angle. 204 T angle() const { 205 return std.math.atan2(y, x) * radToDeg; 206 } 207 208 /// Add an angle to this vector. 209 Vec2!T rotate(T angle) { 210 const T radians = angle * degToRad; 211 const T px = x, py = y; 212 const T c = std.math.cos(radians); 213 const T s = std.math.sin(radians); 214 x = px * c - py * s; 215 y = px * s + py * c; 216 return this; 217 } 218 219 /// Returns a vector with an added angle without modifying this one. 220 Vec2!T rotated(T angle) const { 221 const T radians = angle * degToRad; 222 const T c = std.math.cos(radians); 223 const T s = std.math.sin(radians); 224 return Vec2f(x * c - y * s, x * s + y * c); 225 } 226 227 /// Returns a unit vector with an angle. 228 static Vec2!T angled(T angle) { 229 const T radians = angle * degToRad; 230 return Vec2f(std.math.cos(radians), std.math.sin(radians)); 231 } 232 } 233 234 /// Adds x and y. 235 T sum() const { 236 return x + y; 237 } 238 239 /// The total length of this vector. 240 T length() const { 241 static if (__traits(isFloating, T)) 242 return std.math.sqrt(x * x + y * y); 243 else 244 return cast(T) std.math.sqrt(cast(float)(x * x + y * y)); 245 } 246 247 /// The squared length of this vector. 248 /// Can be null, and more efficiant than length. 249 T lengthSquared() const { 250 return x * x + y * y; 251 } 252 253 /// Transform this vector in a unit vector. 254 void normalize() { 255 static if (__traits(isFloating, T)) 256 const T len = std.math.sqrt(x * x + y * y); 257 else 258 const T len = cast(T) std.math.sqrt(cast(float)(x * x + y * y)); 259 260 if (len == 0) { 261 x = len; 262 y = len; 263 return; 264 } 265 x /= len; 266 y /= len; 267 } 268 269 /// Returns a unit vector from this one without modifying this one. 270 Vec2!T normalized() const { 271 static if (__traits(isFloating, T)) 272 const T len = std.math.sqrt(x * x + y * y); 273 else 274 const T len = cast(T) std.math.sqrt(cast(float)(x * x + y * y)); 275 if (len == 0) { 276 return zero; 277 } 278 return Vec2!T(x / len, y / len); 279 } 280 281 /// Bounds the vector between those two. 282 Vec2!T clamp(const Vec2!T min, const Vec2!T max) const { 283 Vec2!T v = {x, y}; 284 if (v.x < min.x) 285 v.x = min.x; 286 else if (v.x > max.x) 287 v.x = max.x; 288 if (v.y < min.y) 289 v.y = min.y; 290 else if (v.y > max.y) 291 v.y = max.y; 292 return v; 293 } 294 295 /// Bounds the vector between those boundaries. 296 Vec2!T clamp(const Vec4!T clip) const { 297 Vec2!T v = {x, y}; 298 if (v.x < clip.x) 299 v.x = clip.x; 300 else if (v.x > clip.z) 301 v.x = clip.z; 302 if (v.y < clip.y) 303 v.y = clip.y; 304 else if (v.y > clip.w) 305 v.y = clip.w; 306 return v; 307 } 308 309 /// Is this vector between those boundaries ? 310 bool isBetween(const Vec2!T min, const Vec2!T max) const { 311 if (x < min.x) 312 return false; 313 else if (x > max.x) 314 return false; 315 if (y < min.y) 316 return false; 317 else if (y > max.y) 318 return false; 319 return true; 320 } 321 322 static if (__traits(isFloating, T)) { 323 /// Returns an interpolated vector from this vector to the end vector by a factor. \ 324 /// Does not modify this vector. 325 Vec2!T lerp(Vec2!T end, float t) const { 326 return (this * (1.0 - t)) + (end * t); 327 } 328 } 329 330 /// While conserving the x/y ratio, returns the largest vector possible that fits inside the other vector. (like a size) \ 331 /// Does not modify this vector. 332 Vec2!T fit(const Vec2!T v) const { 333 if (v == Vec2!T.zero) 334 return v; 335 return (x / y) < (v.x / v.y) ? Vec2!T(x * v.y / y, v.y) : Vec2!T(v.x, y * v.x / x); 336 } 337 338 /// While conserving the x/y ratio, returns the smallest vector possible that can contain the other vector. (like a size) \ 339 /// Does not modify this vector. 340 Vec2!T contain(const Vec2!T v) const { 341 if (v == Vec2!T.zero) 342 return v; 343 return (x / y) < (v.x / v.y) ? Vec2!T(v.x, y * v.x / x) : Vec2!T(x * v.y / y, v.y); 344 } 345 346 /// Linear interpolation to approach a target 347 Vec2!T approach(const Vec2!T target, const Vec2!T step) { 348 import std.algorithm.comparison : min, max; 349 350 return Vec2!T(x > target.x ? max(x - step.x, target.x) : min(x + step.x, 351 target.x), y > target.y ? max(y - step.y, target.y) : min(y + step.y, target.y)); 352 } 353 354 /// Equality operations 355 bool opEquals(const Vec2!T v) const @safe pure nothrow { 356 return (x == v.x) && (y == v.y); 357 } 358 359 /// Ditto 360 int opCmp(const Vec2!T v) const @safe pure nothrow { 361 const T a = x - v.x; 362 const T b = y - v.y; 363 if (a && b) 364 return 1; 365 if (a < 0 && b < 0) 366 return -1; 367 return 0; 368 } 369 370 /// Unary operations 371 Vec2!T opUnary(string op)() const @safe pure nothrow { 372 return mixin("Vec2!T(" ~ op ~ " x, " ~ op ~ " y)"); 373 } 374 375 /// Binary operations 376 Vec2!T opBinary(string op)(const Vec2!T v) const @safe pure nothrow { 377 return mixin("Vec2!T(x " ~ op ~ " v.x, y " ~ op ~ " v.y)"); 378 } 379 380 /// Binary operations 381 Vec2!T opBinary(string op)(T s) const @safe pure nothrow { 382 return mixin("Vec2!T(x " ~ op ~ " s, y " ~ op ~ " s)"); 383 } 384 385 /// Binary operations 386 Vec2!T opBinaryRight(string op)(T s) const @safe pure nothrow { 387 return mixin("Vec2!T(s " ~ op ~ " x, s " ~ op ~ " y)"); 388 } 389 390 /// Assignment 391 Vec2!T opOpAssign(string op)(Vec2!T v) @safe pure nothrow { 392 mixin("x = x" ~ op ~ "v.x;y = y" ~ op ~ "v.y;"); 393 return this; 394 } 395 396 /// Assignment 397 Vec2!T opOpAssign(string op)(T s) @safe pure nothrow { 398 mixin("x = x" ~ op ~ "s;y = y" ~ op ~ "s;"); 399 return this; 400 } 401 402 /// Conversion 403 Vec2!U opCast(V : Vec2!U, U)() const @safe pure nothrow { 404 return V(cast(U) x, cast(U) y); 405 } 406 407 /// Hash value. 408 size_t toHash() const @safe pure nothrow { 409 import std.typecons : tuple; 410 411 return tuple(x, y).toHash(); 412 } 413 } 414 415 alias Vec2f = Vec2!(float); 416 alias Vec2i = Vec2!(int); 417 alias Vec2u = Vec2!(uint);