1 /**
2 2D Transformation. 3x2 matrix.
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.transform2d;
15 
16 import godot.api.types;
17 import godot.vector2;
18 import godot.rect2;
19 
20 import std.math;
21 import std.algorithm.comparison;
22 import std.algorithm.mutation : swap;
23 
24 /**
25 Represents one or many transformations in 2D space such as translation, rotation, or scaling. It is similar to a 3x2 matrix.
26 */
27 struct Transform2D {
28 @nogc nothrow:
29 
30     union {
31         Vector2[3] columns = [Vector2(1, 0), Vector2(0, 1), Vector2(0, 0)];
32         struct {
33             Vector2 x_axis; /// 
34             Vector2 y_axis; /// 
35             Vector2 origin; /// 
36         }
37     }
38 
39     real_t tdotx(in Vector2 v) const {
40         return columns[0][0] * v.x + columns[1][0] * v.y;
41     }
42 
43     real_t tdoty(in Vector2 v) const {
44         return columns[0][1] * v.x + columns[1][1] * v.y;
45     }
46 
47     this(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) {
48         columns[0][0] = xx;
49         columns[0][1] = xy;
50         columns[1][0] = yx;
51         columns[1][1] = yy;
52         columns[2][0] = ox;
53         columns[2][1] = oy;
54     }
55 
56     const(Vector2) opIndex(int axis) const {
57         return columns[axis];
58     }
59 
60     ref Vector2 opIndex(int axis) return {
61         return columns[axis];
62     }
63 
64     Vector2 basisXform(in Vector2 v) const {
65         return Vector2(
66             tdotx(v),
67             tdoty(v)
68         );
69     }
70 
71     Vector2 basisXformInv(in Vector2 v) const {
72         return Vector2(
73             columns[0].dot(v),
74             columns[1].dot(v)
75         );
76     }
77 
78     Vector2 xform(in Vector2 v) const {
79         return Vector2(
80             tdotx(v),
81             tdoty(v)
82         ) + columns[2];
83     }
84 
85     Vector2 xformInv(in Vector2 p_vec) const {
86         Vector2 v = p_vec - columns[2];
87 
88         return Vector2(
89             columns[0].dot(v),
90             columns[1].dot(v)
91         );
92     }
93 
94     Rect2 xform(in Rect2 p_rect) const {
95         Vector2 x = columns[0] * p_rect.size.x;
96         Vector2 y = columns[1] * p_rect.size.y;
97         Vector2 pos = xform(p_rect.position);
98 
99         Rect2 new_rect;
100         new_rect.position = pos;
101         new_rect.expandTo(pos + x);
102         new_rect.expandTo(pos + y);
103         new_rect.expandTo(pos + x + y);
104         return new_rect;
105     }
106 
107     void setRotationAndScale(real_t p_rot, in Vector2 p_scale) {
108         columns[0][0] = cos(p_rot) * p_scale.x;
109         columns[1][1] = cos(p_rot) * p_scale.y;
110         columns[1][0] = -sin(p_rot) * p_scale.y;
111         columns[0][1] = sin(p_rot) * p_scale.x;
112 
113     }
114 
115     Rect2 xformInv(in Rect2 p_rect) const {
116         Vector2[4] ends = [
117             xformInv(p_rect.position),
118             xformInv(Vector2(p_rect.position.x, p_rect.position.y + p_rect.size.y)),
119             xformInv(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y + p_rect.size.y)),
120             xformInv(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y))
121         ];
122 
123         Rect2 new_rect;
124         new_rect.position = ends[0];
125         new_rect.expandTo(ends[1]);
126         new_rect.expandTo(ends[2]);
127         new_rect.expandTo(ends[3]);
128 
129         return new_rect;
130     }
131 
132     void invert() {
133         // FIXME: this function assumes the basis is a rotation matrix, with no scaling.
134         // affine_inverse can handle matrices with scaling, so GDScript should eventually use that.
135         swap(columns[0][1], columns[1][0]);
136         columns[2] = basisXform(-columns[2]);
137     }
138 
139     Transform2D inverse() const {
140         Transform2D inv = this;
141         inv.invert();
142         return inv;
143 
144     }
145 
146     void affineInvert() {
147         real_t det = basisDeterminant();
148         ///ERR_FAIL_COND(det==0);
149         real_t idet = 1.0 / det;
150 
151         swap(columns[0][0], columns[1][1]);
152         columns[0] *= Vector2(idet, -idet);
153         columns[1] *= Vector2(-idet, idet);
154 
155         columns[2] = basisXform(-columns[2]);
156 
157     }
158 
159     Transform2D affineInverse() const {
160         Transform2D inv = this;
161         inv.affineInvert();
162         return inv;
163     }
164 
165     void rotate(real_t p_phi) {
166         this = Transform2D(p_phi, Vector2()) * (this);
167     }
168 
169     real_t getRotation() const {
170         real_t det = basisDeterminant();
171         Transform2D m = orthonormalized();
172         if (det < 0) {
173             m.scaleBasis(Vector2(-1, -1));
174         }
175         return atan2(m[0].y, m[0].x);
176     }
177 
178     void setRotation(real_t p_rot) {
179         real_t cr = cos(p_rot);
180         real_t sr = sin(p_rot);
181         columns[0][0] = cr;
182         columns[0][1] = sr;
183         columns[1][0] = -sr;
184         columns[1][1] = cr;
185     }
186 
187     this(real_t p_rot, in Vector2 p_pos) {
188         real_t cr = cos(p_rot);
189         real_t sr = sin(p_rot);
190         columns[0][0] = cr;
191         columns[0][1] = sr;
192         columns[1][0] = -sr;
193         columns[1][1] = cr;
194         columns[2] = p_pos;
195     }
196 
197     Vector2 getScale() const {
198         real_t det_sign = basisDeterminant() > 0 ? 1 : -1;
199         return det_sign * Vector2(columns[0].length(), columns[1].length());
200     }
201 
202     void scale(in Vector2 p_scale) {
203         scaleBasis(p_scale);
204         columns[2] *= p_scale;
205     }
206 
207     void scaleBasis(in Vector2 p_scale) {
208         columns[0][0] *= p_scale.x;
209         columns[0][1] *= p_scale.y;
210         columns[1][0] *= p_scale.x;
211         columns[1][1] *= p_scale.y;
212 
213     }
214 
215     void translate(real_t p_tx, real_t p_ty) {
216         translate(Vector2(p_tx, p_ty));
217     }
218 
219     void translate(in Vector2 p_translation) {
220         columns[2] += basisXform(p_translation);
221     }
222 
223     void orthonormalize() {
224         // Gram-Schmidt Process
225 
226         Vector2 x = columns[0];
227         Vector2 y = columns[1];
228 
229         x.normalize();
230         y = (y - x * (x.dot(y)));
231         y.normalize();
232 
233         columns[0] = x;
234         columns[1] = y;
235     }
236 
237     Transform2D orthonormalized() const {
238         Transform2D on = this;
239         on.orthonormalize();
240         return on;
241 
242     }
243 
244     void opOpAssign(string op : "*")(in Transform2D p_transform) {
245         columns[2] = xform(p_transform.columns[2]);
246 
247         real_t x0, x1, y0, y1;
248 
249         x0 = tdotx(p_transform.columns[0]);
250         x1 = tdoty(p_transform.columns[0]);
251         y0 = tdotx(p_transform.columns[1]);
252         y1 = tdoty(p_transform.columns[1]);
253 
254         columns[0][0] = x0;
255         columns[0][1] = x1;
256         columns[1][0] = y0;
257         columns[1][1] = y1;
258     }
259 
260     Transform2D opBinary(string op : "*")(in Transform2D p_transform) const {
261         Transform2D t = this;
262         t *= p_transform;
263         return t;
264 
265     }
266 
267     Transform2D scaled(in Vector2 p_scale) const {
268         Transform2D copy = this;
269         copy.scale(p_scale);
270         return copy;
271 
272     }
273 
274     Transform2D basisScaled(in Vector2 p_scale) const {
275         Transform2D copy = this;
276         copy.scaleBasis(p_scale);
277         return copy;
278 
279     }
280 
281     Transform2D untranslated() const {
282         Transform2D copy = this;
283         copy.columns[2] = Vector2();
284         return copy;
285     }
286 
287     Transform2D translated(in Vector2 p_offset) const {
288         Transform2D copy = this;
289         copy.translate(p_offset);
290         return copy;
291     }
292 
293     Transform2D rotated(real_t p_phi) const {
294         Transform2D copy = this;
295         copy.rotate(p_phi);
296         return copy;
297 
298     }
299 
300     real_t basisDeterminant() const {
301         return columns[0].x * columns[1].y - columns[0].y * columns[1].x;
302     }
303 
304     Transform2D interpolateWith(in Transform2D p_transform, real_t p_c) const {
305         //extract parameters
306         Vector2 p1 = origin;
307         Vector2 p2 = p_transform.origin;
308 
309         real_t r1 = getRotation();
310         real_t r2 = p_transform.getRotation();
311 
312         Vector2 s1 = getScale();
313         Vector2 s2 = p_transform.getScale();
314 
315         //slerp rotation
316         Vector2 v1 = Vector2(cos(r1), sin(r1));
317         Vector2 v2 = Vector2(cos(r2), sin(r2));
318 
319         real_t dot = v1.dot(v2);
320 
321         dot = (dot < -1.0) ? -1.0 : ((dot > 1.0) ? 1.0 : dot); //clamp dot to [-1,1]
322 
323         Vector2 v;
324 
325         if (dot > 0.9995) {
326             v = Vector2.linearInterpolate(v1, v2, p_c).normalized(); //linearly interpolate to avoid numerical precision issues
327         } else {
328             real_t angle = p_c * acos(dot);
329             Vector2 v3 = (v2 - v1 * dot).normalized();
330             v = v1 * cos(angle) + v3 * sin(angle);
331         }
332 
333         //construct matrix
334         Transform2D res = Transform2D(atan2(v.y, v.x), Vector2.linearInterpolate(p1, p2, p_c));
335         res.scaleBasis(Vector2.linearInterpolate(s1, s2, p_c));
336         return res;
337     }
338 }