1 /** 2 Vector used for 2D Math. 3 4 Copyright: 5 Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. 6 Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md) 7 Copyright (c) 2017-2018 Godot-D contributors 8 Copyright (c) 2022-2022 Godot-DLang contributors 9 10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License) 11 12 13 */ 14 module godot.vector2; 15 16 import godot.abi.types; 17 import godot.abi.core; 18 19 import std.math; 20 21 private bool isValidSwizzle(dstring s) { 22 import std.algorithm : canFind; 23 24 if (s.length != 2 && s.length != 3) 25 return false; 26 foreach (dchar c; s) { 27 if (!"xyn".canFind(c)) 28 return false; 29 } 30 return true; 31 } 32 33 /** 34 2-element structure that can be used to represent positions in 2d-space, or any other pair of numeric values. 35 */ 36 struct Vector2 { 37 @nogc nothrow: 38 39 union { 40 struct { 41 union { 42 real_t x = 0.0; /// 43 real_t width; /// 44 } 45 46 union { 47 real_t y = 0.0; /// 48 real_t height; /// 49 } 50 } 51 52 real_t[2] coord; 53 } 54 55 import std.algorithm : count; 56 57 /++ 58 Swizzle the vector with x, y, or n. Pass floats as args for any n's; if 59 there are more n's than args, the last arg is used for the rest. If no args 60 are passed at all, 0.0 is used for each n. 61 62 The swizzle must be 2 or 3 characters, as Godot only has Vector2/3. 63 +/ 64 auto opDispatch(string swizzle, size_t nArgCount)(float[nArgCount] nArgs...) const 65 if (swizzle.isValidSwizzle && nArgCount <= swizzle.count('n')) { 66 import godot.vector3; 67 import std.algorithm : min, count; 68 69 static if (swizzle.length == 2) 70 Vector2 ret = void; 71 else 72 Vector3 ret = void; 73 /// how many n's already appeared before ci, which equals the index into nArgs for the n at ci 74 enum ni(size_t ci) = min(nArgCount - 1, swizzle[0 .. ci].count('n')); 75 static foreach (ci, c; swizzle) { 76 static if (c == 'n') { 77 static if (nArgCount == 0) 78 ret.coord[ci] = 0f; 79 else static if (ni!ci >= nArgCount) 80 ret.coord[ci] = nArgs[nArgCount - 1]; 81 else 82 ret.coord[ci] = nArgs[ni!ci]; 83 } else 84 ret.coord[ci] = mixin([c]); 85 } 86 return ret; 87 } 88 89 this(real_t x, real_t y) { 90 this.x = x; 91 this.y = y; 92 } 93 94 this(real_t[2] coord) { 95 this.coord = coord; 96 } 97 98 this(in Vector2 b) { 99 this.x = b.x; 100 this.y = b.y; 101 } 102 103 this(in Vector2i b) { 104 this.x = b.x; 105 this.y = b.y; 106 } 107 108 void opAssign(in Vector2 b) { 109 this.x = b.x; 110 this.y = b.y; 111 } 112 113 // there is cases where this happens in api.json 114 void opAssign(in Vector2i b) { 115 this.x = b.x; 116 this.y = b.y; 117 } 118 119 ref real_t opIndex(int axis) return { 120 return axis ? y : x; 121 } 122 123 const(real_t) opIndex(int axis) const { 124 return axis ? y : x; 125 } 126 127 Vector2 opBinary(string op)(in Vector2 other) const 128 if (op == "+" || op == "-" || op == "*" || op == "/") { 129 Vector2 ret; 130 ret.x = mixin("x " ~ op ~ "other.x"); 131 ret.y = mixin("y " ~ op ~ "other.y"); 132 return ret; 133 } 134 135 void opOpAssign(string op)(in Vector2 other) 136 if (op == "+" || op == "-" || op == "*" || op == "/") { 137 x = mixin("x " ~ op ~ "other.x"); 138 y = mixin("y " ~ op ~ "other.y"); 139 } 140 141 Vector2 opUnary(string op : "-")() { 142 return Vector2(-x, -y); 143 } 144 145 Vector2 opBinary(string op)(in real_t scalar) const 146 if (op == "*" || op == "/") { 147 Vector2 ret; 148 ret.x = mixin("x " ~ op ~ " scalar"); 149 ret.y = mixin("y " ~ op ~ " scalar"); 150 return ret; 151 } 152 153 Vector2 opBinaryRight(string op)(in real_t scalar) const 154 if (op == "*") { 155 Vector2 ret; 156 ret.x = mixin("x " ~ op ~ " scalar"); 157 ret.y = mixin("y " ~ op ~ " scalar"); 158 return ret; 159 } 160 161 void opOpAssign(string op)(in real_t scalar) if (op == "*" || op == "/") { 162 x = mixin("x " ~ op ~ " scalar"); 163 y = mixin("y " ~ op ~ " scalar"); 164 } 165 166 int opCmp(in Vector2 other) const { 167 import std.algorithm.comparison; 168 import std.range; 169 170 return cmp(only(x, y), only(other.x, other.y)); 171 } 172 173 real_t aspect() const { 174 return width / height; 175 } 176 177 void normalize() { 178 real_t l = x * x + y * y; 179 if (l != 0) { 180 l = sqrt(l); 181 x /= l; 182 y /= l; 183 } 184 } 185 186 Vector2 normalized() const { 187 Vector2 v = this; 188 v.normalize(); 189 return v; 190 } 191 192 real_t length() const { 193 return sqrt(x * x + y * y); 194 } 195 196 real_t lengthSquared() const { 197 return x * x + y * y; 198 } 199 200 real_t distanceTo(in Vector2 p_vector2) const { 201 return sqrt((x - p_vector2.x) * (x - p_vector2.x) + (y - p_vector2.y) * (y - p_vector2.y)); 202 } 203 204 real_t distanceSquaredTo(in Vector2 p_vector2) const { 205 return (x - p_vector2.x) * (x - p_vector2.x) + (y - p_vector2.y) * (y - p_vector2.y); 206 } 207 208 real_t angleTo(in Vector2 p_vector2) const { 209 return atan2(cross(p_vector2), dot(p_vector2)); 210 } 211 212 real_t angleToPoint(in Vector2 p_vector2) const { 213 return atan2(y - p_vector2.y, x - p_vector2.x); 214 } 215 216 real_t dot(in Vector2 p_other) const { 217 return x * p_other.x + y * p_other.y; 218 } 219 220 real_t cross(in Vector2 p_other) const { 221 return x * p_other.y - y * p_other.x; 222 } 223 224 Vector2 cross(real_t p_other) const { 225 return Vector2(p_other * y, -p_other * x); 226 } 227 228 Vector2 project(in Vector2 p_vec) const { 229 Vector2 v1 = p_vec; 230 Vector2 v2 = this; 231 return v2 * (v1.dot(v2) / v2.dot(v2)); 232 } 233 234 Vector2 planeProject(real_t p_d, in Vector2 p_vec) const { 235 return p_vec - this * (dot(p_vec) - p_d); 236 } 237 238 Vector2 clamped(real_t p_len) const { 239 real_t l = length(); 240 Vector2 v = this; 241 if (l > 0 && p_len < l) { 242 v /= l; 243 v *= p_len; 244 } 245 return v; 246 } 247 248 static Vector2 linearInterpolate(in Vector2 p_a, in Vector2 p_b, real_t p_t) { 249 Vector2 res = p_a; 250 res.x += (p_t * (p_b.x - p_a.x)); 251 res.y += (p_t * (p_b.y - p_a.y)); 252 return res; 253 } 254 255 Vector2 linearInterpolate(in Vector2 p_b, real_t p_t) const { 256 Vector2 res = this; 257 res.x += (p_t * (p_b.x - x)); 258 res.y += (p_t * (p_b.y - y)); 259 return res; 260 261 } 262 263 alias lerp = linearInterpolate; 264 265 Vector2 cubicInterpolate(in Vector2 p_b, in Vector2 p_pre_a, in Vector2 p_post_b, real_t p_t) const { 266 Vector2 p0 = p_pre_a; 267 Vector2 p1 = this; 268 Vector2 p2 = p_b; 269 Vector2 p3 = p_post_b; 270 271 real_t t = p_t; 272 real_t t2 = t * t; 273 real_t t3 = t2 * t; 274 275 Vector2 ret; 276 ret = ((p1 * 2.0) + 277 (-p0 + p2) * t + 278 (p0 * 2.0 - p1 * 5.0 + p2 * 4 - p3) * t2 + 279 (-p0 + p1 * 3.0 - p2 * 3.0 + p3) * t3) * 0.5; 280 281 return ret; 282 } 283 284 Vector2 slide(in Vector2 p_vec) const { 285 return p_vec - this * this.dot(p_vec); 286 } 287 288 Vector2 reflect(in Vector2 p_vec) const { 289 return p_vec - this * this.dot(p_vec) * 2.0; 290 } 291 292 real_t angle() const { 293 return atan2(y, x); 294 } 295 296 void setRotation(real_t p_radians) { 297 x = cos(p_radians); 298 y = sin(p_radians); 299 } 300 301 Vector2 abs() const { 302 return Vector2(fabs(x), fabs(y)); 303 } 304 305 Vector2 rotated(real_t p_by) const { 306 Vector2 v = void; 307 v.setRotation(angle() + p_by); 308 v *= length(); 309 return v; 310 } 311 312 Vector2 tangent() const { 313 return Vector2(y, -x); 314 } 315 316 Vector2 floor() const { 317 return Vector2(.floor(x), .floor(y)); 318 } 319 320 Vector2 snapped(in Vector2 p_by) const { 321 return Vector2( 322 p_by.x != 0 ? .floor(x / p_by.x + 0.5) * p_by.x : x, 323 p_by.y != 0 ? .floor(y / p_by.y + 0.5) * p_by.y : y 324 ); 325 } 326 } 327 328 // TODO: replace this stub 329 struct Vector2i { 330 @nogc nothrow: 331 332 union { 333 struct { 334 union { 335 godot_int x = 0; /// 336 godot_int width; /// 337 } 338 339 union { 340 godot_int y = 0; /// 341 godot_int height; /// 342 } 343 } 344 345 godot_int[2] coord; 346 } 347 348 this(godot_int x, godot_int y) { 349 this.x = x; 350 this.y = y; 351 } 352 353 this(int[2] coord) { 354 this.coord = cast(godot_int[]) coord; 355 } 356 357 this(in Vector2i b) { 358 this.x = b.x; 359 this.y = b.y; 360 } 361 362 this(in godot_vector2i b) { 363 this.x = b._opaque[0]; 364 this.y = b._opaque[1]; 365 } 366 367 void opAssign(in Vector2i b) { 368 this.x = b.x; 369 this.y = b.y; 370 } 371 372 void opAssign(in godot_vector2i b) { 373 this.x = b._opaque[0]; 374 this.y = b._opaque[1]; 375 } 376 377 ref godot_int opIndex(int axis) return { 378 return axis ? y : x; 379 } 380 381 const(godot_int) opIndex(int axis) const { 382 return axis ? y : x; 383 } 384 385 Vector2i opBinary(string op)(in Vector2i other) const 386 if (op == "+" || op == "-" || op == "*" || op == "/") { 387 Vector2i ret; 388 ret.x = mixin("x " ~ op ~ "other.x"); 389 ret.y = mixin("y " ~ op ~ "other.y"); 390 return ret; 391 } 392 393 void opOpAssign(string op)(in Vector2i other) 394 if (op == "+" || op == "-" || op == "*" || op == "/") { 395 x = mixin("x " ~ op ~ "other.x"); 396 y = mixin("y " ~ op ~ "other.y"); 397 } 398 399 Vector2i opUnary(string op : "-")() { 400 return Vector2i(-x, -y); 401 } 402 403 Vector2i opBinary(string op)(in godot_int scalar) const 404 if (op == "*" || op == "/") { 405 Vector2 ret; 406 ret.x = mixin("x " ~ op ~ " scalar"); 407 ret.y = mixin("y " ~ op ~ " scalar"); 408 return ret; 409 } 410 411 Vector2i opBinaryRight(string op)(in godot_int scalar) const 412 if (op == "*") { 413 Vector2i ret; 414 ret.x = mixin("x " ~ op ~ " scalar"); 415 ret.y = mixin("y " ~ op ~ " scalar"); 416 return ret; 417 } 418 419 void opOpAssign(string op)(in godot_int scalar) if (op == "*" || op == "/") { 420 x = mixin("x " ~ op ~ " scalar"); 421 y = mixin("y " ~ op ~ " scalar"); 422 } 423 424 int opCmp(in Vector2 other) const { 425 import std.algorithm.comparison; 426 import std.range; 427 428 return cmp(only(x, y), only(other.x, other.y)); 429 } 430 431 Vector2 opCast(Vector2)() const { 432 return Vector2(x, y); 433 } 434 435 real_t aspect() const { 436 return width / cast(real_t) height; 437 } 438 439 real_t length() const { 440 return cast(real_t) sqrt(cast(double) lengthSquared()); 441 } 442 443 int64_t lengthSquared() const { 444 return (x * cast(int64_t) x) + (y * cast(int64_t) y); 445 } 446 447 Vector2i abs() const { 448 import std.math : abs; 449 450 return Vector2i(.abs(x), .abs(y)); 451 } 452 453 Vector2i clamp(in Vector2i p_min, in Vector2i p_max) const { 454 import std.algorithm.comparison : _clamp = clamp; // template looses priority to local symbol 455 return Vector2i(_clamp(x, p_min.x, p_max.x), _clamp(y, p_min.y, p_max.y)); 456 } 457 }