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