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 }