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