1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com)
5  *
6  * This software is provided 'as-is', without any express or implied warranty.
7  * In no event will the authors be held liable for any damages arising from the
8  * use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not claim
15  * that you wrote the original software. If you use this software in a product,
16  * an acknowledgment in the product documentation would be appreciated but is
17  * not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source distribution
23  *
24  *
25  * DSFML is based on SFML (Copyright Laurent Gomila)
26  */
27 
28 /**
29  * A $(U Transform) specifies how to translate, rotate, scale, shear, project,
30  * whatever things. In mathematical terms, it defines how to transform a
31  * coordinate system into another.
32  *
33  * For example, if you apply a rotation transform to a sprite, the result will
34  * be a rotated sprite. And anything that is transformed by this rotation
35  * transform will be rotated the same way, according to its initial position.
36  *
37  * Transforms are typically used for drawing. But they can also be used for any
38  * computation that requires to transform points between the local and global
39  * coordinate systems of an entity (like collision detection).
40  *
41  * Example:
42  * ---
43  * // define a translation transform
44  * Transform translation;
45  * translation.translate(20, 50);
46  *
47  * // define a rotation transform
48  * Transform rotation;
49  * rotation.rotate(45);
50  *
51  * // combine them
52  * Transform transform = translation * rotation;
53  *
54  * // use the result to transform stuff...
55  * Vector2f point = transform.transformPoint(Vector2f(10, 20));
56  * FloatRect rect = transform.transformRect(FloatRect(0, 0, 10, 100));
57  * ---
58  *
59  * See_Also:
60  * $(TRANSFORMABLE_LINK), $(RENDERSTATES_LINK)
61  */
62 module dsfml.graphics.transform;
63 
64 import dsfml.system.vector2;
65 import dsfml.graphics.rect;
66 
67 
68 /**
69  * Define a 3x3 transform matrix.
70  */
71 struct Transform
72 {
73 	/// 4x4 matrix defining the transformation.
74 	package float[16] m_matrix = [1.0f, 0.0f, 0.0f, 0.0f,
75 						  		  0.0f, 1.0f, 0.0f, 0.0f,
76 						  		  0.0f, 0.0f, 1.0f, 0.0f,
77 						  		  0.0f, 0.0f, 0.0f, 1.0f];
78 
79 	/**
80 	 * Construct a transform from a 3x3 matrix.
81 	 *
82 	 * Params:
83 	 * 		a00	= Element (0, 0) of the matrix
84 	 * 		a01	= Element (0, 1) of the matrix
85 	 * 		a02	= Element (0, 2) of the matrix
86 	 * 		a10	= Element (1, 0) of the matrix
87 	 * 		a11	= Element (1, 1) of the matrix
88 	 * 		a12	= Element (1, 2) of the matrix
89 	 * 		a20	= Element (2, 0) of the matrix
90 	 * 		a21	= Element (2, 1) of the matrix
91 	 * 		a22	= Element (2, 2) of the matrix
92 	 */
93 	this(float a00, float a01, float a02, float a10, float a11, float a12, float a20, float a21, float a22)
94 	{
95 		m_matrix = [ a00,  a10, 0.0f,  a20,
96     			     a01,  a11, 0.0f,  a21,
97     				0.0f, 0.0f, 1.0f, 0.0f,
98     				 a02,  a12, 0.0f,  a22];
99 	}
100 
101 	/// Construct a transform from a float array describing a 3x3 matrix.
102 	this(float[9] matrix)
103 	{
104 		m_matrix = [matrix[0], matrix[3], 0.0f, matrix[6],
105     			    matrix[1], matrix[4], 0.0f, matrix[7],
106     				     0.0f,      0.0f, 1.0f,      0.0f,
107     				matrix[2], matrix[5], 0.0f, matrix[8]];
108 	}
109 
110 	/**
111 	 * Return the inverse of the transform.
112 	 *
113 	 * If the inverse cannot be computed, an identity transform is returned.
114 	 *
115 	 * Returns: A new transform which is the inverse of self.
116 	 */
117 	Transform getInverse() const
118 	{
119 		Transform temp;
120 		sfTransform_getInverse(m_matrix.ptr,temp.m_matrix.ptr);
121 		return temp;
122 	}
123 
124 	/**
125 	 * Return the transform as a 4x4 matrix.
126 	 *
127 	 * This function returns a pointer to an array of 16 floats containing the
128 	 * transform elements as a 4x4 matrix, which is directly compatible with
129 	 * OpenGL functions.
130 	 *
131 	 * Returns: A 4x4 matrix.
132 	 */
133 	const(float)[] getMatrix() const
134 	{
135 		return m_matrix;
136 	}
137 
138 	/**
139 	 * Combine the current transform with another one.
140 	 *
141 	 * The result is a transform that is equivalent to applying this followed by
142 	 * transform. Mathematically, it is equivalent to a matrix multiplication.
143 	 *
144 	 * Params:
145 	 * 		otherTransform	= Transform to combine with this one
146 	 *
147 	 * Returns: Reference to this.
148 	 */
149 	ref Transform combine(Transform otherTransform)
150 	{
151 		sfTransform_combine(m_matrix.ptr, otherTransform.m_matrix.ptr);
152 		return this;
153 	}
154 
155 	/**
156 	 * Transform a 2D point.
157 	 *
158 	 * Params:
159 	 *		x 	= X coordinate of the point to transform
160 	 * 		y	= Y coordinate of the point to transform
161 	 *
162 	 * Returns: Transformed point.
163 	 */
164 	Vector2f transformPoint(float x, float y) const
165 	{
166 		Vector2f temp;
167 		sfTransform_transformPoint(m_matrix.ptr, x, y, &temp.x, &temp.y);
168 		return temp;
169 	}
170 
171 	/**
172 	 * Transform a 2D point.
173 	 *
174 	 * Params:
175 	 *		point 	= the point to transform
176 	 *
177 	 * Returns: Transformed point.
178 	 */
179 	Vector2f transformPoint(Vector2f point) const
180 	{
181 		Vector2f temp;
182 		sfTransform_transformPoint(m_matrix.ptr,point.x, point.y, &temp.x, &temp.y);
183 		return temp;
184 	}
185 
186 	/**
187 	 * Transform a rectangle.
188 	 *
189 	 * Since SFML doesn't provide support for oriented rectangles, the result of
190 	 * this function is always an axis-aligned rectangle. Which means that if
191 	 * the transform contains a rotation, the bounding rectangle of the
192 	 * transformed rectangle is returned.
193 	 *
194 	 * Params:
195 	 * 		rect	= Rectangle to transform
196 	 *
197 	 * Returns: Transformed rectangle.
198 	 */
199 	FloatRect transformRect(const(FloatRect) rect)const
200 	{
201 		FloatRect temp;
202 		sfTransform_transformRect(m_matrix.ptr,rect.left, rect.top, rect.width, rect.height, &temp.left, &temp.top, &temp.width, &temp.height);
203 		return temp;
204 	}
205 
206 	/**
207 	 * Combine the current transform with a translation.
208 	 *
209 	 * This function returns a reference to this, so that calls can be chained.
210 	 *
211 	 * Params:
212 	 * 		offset	= Translation offset to apply
213 	 *
214 	 * Returns: this
215 	 */
216 	ref Transform translate(Vector2f offset)
217 	{
218 		sfTransform_translate(m_matrix.ptr, offset.x, offset.y);
219 		return this;
220 	}
221 
222 	/**
223 	 * Combine the current transform with a translation.
224 	 *
225 	 * This function returns a reference to this, so that calls can be chained.
226 	 *
227 	 * Params:
228 	 * 		x	= Offset to apply on X axis
229 	 *		y	= Offset to apply on Y axis
230 	 *
231 	 * Returns: this
232 	 */
233 	ref Transform translate(float x, float y)
234 	{
235 		sfTransform_translate(m_matrix.ptr, x, y);
236 		return this;
237 	}
238 
239 	/**
240 	 * Combine the current transform with a rotation.
241 	 *
242 	 * This function returns a reference to this, so that calls can be chained.
243 	 *
244 	 * Params:
245 	 * 		angle	= Rotation angle, in degrees
246 	 *
247 	 * Returns: this
248 	 */
249 	ref Transform rotate(float angle)
250 	{
251 		sfTransform_rotate(m_matrix.ptr, angle);
252 		return this;
253 	}
254 
255 	/**
256 	 * Combine the current transform with a rotation.
257 	 *
258 	 * The center of rotation is provided for convenience as a second argument,
259 	 * so that you can build rotations around arbitrary points more easily (and
260 	 * efficiently) than the usual
261 	 * translate(-center).rotate(angle).translate(center).
262 	 *
263 	 * This function returns a reference to this, so that calls can be chained.
264 	 *
265 	 * Params:
266 	 * 		angle	= Rotation angle, in degrees
267 	 * 		centerX	= X coordinate of the center of rotation
268 	 *		centerY = Y coordinate of the center of rotation
269 	 *
270 	 * Returns: this
271 	 */
272 	ref Transform rotate(float angle, float centerX, float centerY)
273 	{
274 		sfTransform_rotateWithCenter(m_matrix.ptr, angle, centerX, centerY);
275 		return this;
276 	}
277 
278 	/**
279 	 * Combine the current transform with a rotation.
280 	 *
281 	 * The center of rotation is provided for convenience as a second argument,
282 	 * so that you can build rotations around arbitrary points more easily (and
283 	 * efficiently) than the usual
284 	 * translate(-center).rotate(angle).translate(center).
285 	 *
286 	 * This function returns a reference to this, so that calls can be chained.
287 	 *
288 	 * Params:
289 	 * 		angle	= Rotation angle, in degrees
290 	 * 		center	= Center of rotation
291 	 *
292 	 * Returns: this
293 	 */
294 	ref Transform rotate(float angle, Vector2f center)
295 	{
296 		sfTransform_rotateWithCenter(m_matrix.ptr, angle, center.x, center.y);
297 		return this;
298 	}
299 
300 	/**
301 	 * Combine the current transform with a scaling.
302 	 *
303 	 * This function returns a reference to this, so that calls can be chained.
304 	 *
305 	 * Params:
306 	 * 		scaleX	= Scaling factor on the X-axis.
307 	 * 		scaleY	= Scaling factor on the Y-axis.
308 	 *
309 	 * Returns: this
310 	 */
311 	ref Transform scale(float scaleX, float scaleY)
312 	{
313 		sfTransform_scale(m_matrix.ptr, scaleX, scaleY);
314 		return this;
315 	}
316 
317 	/**
318 	 * Combine the current transform with a scaling.
319 	 *
320 	 * This function returns a reference to this, so that calls can be chained.
321 	 *
322 	 * Params:
323 	 * 		factors	= Scaling factors
324 	 *
325 	 * Returns: this
326 	 */
327 	ref Transform scale(Vector2f factors)
328 	{
329 		sfTransform_scale(m_matrix.ptr, factors.x, factors.y);
330 		return this;
331 	}
332 
333 	/**
334 	 * Combine the current transform with a scaling.
335 	 *
336 	 * The center of scaling is provided for convenience as a second argument,
337 	 * so that you can build scaling around arbitrary points more easily
338 	 * (and efficiently) than the usual
339 	 * translate(-center).scale(factors).translate(center).
340 	 *
341 	 * This function returns a reference to this, so that calls can be chained.
342 	 *
343 	 * Params:
344 	 * 		scaleX	= Scaling factor on the X-axis
345 	 * 		scaleY	= Scaling factor on the Y-axis
346 	 * 		centerX	= X coordinate of the center of scaling
347 	 * 		centerY	= Y coordinate of the center of scaling
348 	 *
349 	 * Returns: this
350 	 */
351 	ref Transform scale(float scaleX, float scaleY, float centerX, float centerY)
352 	{
353 		sfTransform_scaleWithCenter(m_matrix.ptr, scaleX, scaleY, centerX, centerY);
354 		return this;
355 	}
356 
357 	/**
358 	 * Combine the current transform with a scaling.
359 	 *
360 	 * The center of scaling is provided for convenience as a second argument,
361 	 * so that you can build scaling around arbitrary points more easily
362 	 * (and efficiently) than the usual
363 	 * translate(-center).scale(factors).translate(center).
364 	 *
365 	 * This function returns a reference to this, so that calls can be chained.
366 	 *
367 	 * Params:
368 	 * 		factors	= Scaling factors
369 	 * 		center	= Center of scaling
370 	 *
371 	 * Returns: this
372 	 */
373 	ref Transform scale(Vector2f factors, Vector2f center)
374 	{
375 		sfTransform_scaleWithCenter(m_matrix.ptr, factors.x, factors.y, center.x, center.y);
376 		return this;
377 	}
378 
379 	string toString() const
380 	{
381 		return "";//text(InternalsfTransform.matrix);
382 	}
383 
384 	/**
385 	 * Overload of binary operator `*` to combine two transforms.
386 	 *
387 	 * This call is equivalent to:
388 	 * ---
389 	 * Transform combined = transform;
390 	 * combined.combine(rhs);
391 	 * ---
392 	 *
393 	 * Params:
394 	 * rhs = the second transform to be combined with the first
395 	 *
396 	 * Returns: New combined transform.
397 	 */
398 	Transform opBinary(string op)(Transform rhs)
399 		if(op == "*")
400 	{
401 		Transform temp = this;
402 		return temp.combine(rhs);
403 	}
404 
405 	/**
406 	 * Overload of assignment operator `*=` to combine two transforms.
407 	 *
408 	 * This call is equivalent to calling `transform.combine(rhs)`.
409 	 *
410 	 * Params:
411 	 * rhs = the second transform to be combined with the first
412 	 *
413 	 * Returns: The combined transform.
414 	 */
415 	ref Transform opOpAssign(string op)(Transform rhs)
416 		if(op == "*")
417 	{
418 		return this.combine(rhs);
419 	}
420 
421 	/**
422 	* Overload of binary operator * to transform a point
423 	*
424 	* This call is equivalent to calling `transform.transformPoint(vector)`.
425 	*
426 	* Params:
427 	* vector = the point to transform
428 	*
429 	* Returns: New transformed point.
430 	*/
431 	Vextor2f opBinary(string op)(Vector2f vector)
432 		if(op == "*")
433 	{
434 		return transformPoint(vector);
435 	}
436 
437 	/// Indentity transform (does nothing).
438 	static const(Transform) Identity;
439 }
440 
441 unittest
442 {
443 	version(DSFML_Unittest_Graphics)
444 	{
445 		import std.stdio;
446 		import std.math;
447 
448 		bool compareTransform(Transform a, Transform b)
449 		{
450 			/*
451 			 * There's a slight difference in precision between D's and C++'s
452 			 * sine and cosine functions, so we'll use approxEqual here.
453 			 */
454 			return approxEqual(a.getMatrix(), b.getMatrix());
455 		}
456 
457 		writeln("Unit Test for Transform");
458 
459 		assert(compareTransform(Transform.Identity.getInverse(), Transform.Identity));
460 
461 		Transform scaledTransform;
462 		scaledTransform.scale(2, 3);
463 
464 		Transform comparisonTransform;
465 		comparisonTransform.m_matrix =  [2.0f, 0.0f, 0.0f, 0.0f,
466 						  		  	  	 0.0f, 3.0f, 0.0f, 0.0f,
467 						  		  	  	 0.0f, 0.0f, 1.0f, 0.0f,
468 						  		  	  	 0.0f, 0.0f, 0.0f, 1.0f];
469 
470 		assert(compareTransform(scaledTransform, comparisonTransform));
471 
472 		Transform rotatedTransform;
473 		rotatedTransform.rotate(20);
474 
475 		float rad = 20 * 3.141592654f / 180.0f;
476     	float cos = cos(rad);
477     	float sin = sin(rad);
478 
479 		// combine identity with rotational matrix (what rotate() should do)
480     	comparisonTransform = Transform().combine(Transform(cos, -sin, 0,
481                        					   					sin,  cos, 0,
482                        					   					0,    0,   1));
483 
484 		assert(compareTransform(rotatedTransform, comparisonTransform));
485 
486 		writeln();
487 	}
488 }
489 
490 private extern(C):
491 
492 //Return the inverse of a transform
493 void sfTransform_getInverse(const float* transform, float* inverse);
494 
495 //Apply a transform to a 2D point
496 void sfTransform_transformPoint(const float* transform, float xIn, float yIn, float* xOut, float* yOut);
497 
498 //Apply a transform to a rectangle
499 void sfTransform_transformRect(const float* transform, float leftIn, float topIn, float widthIn, float heightIn, float* leftOut, float* topOut, float* widthOut, float* heightOut);
500 
501 //Combine two transforms
502 void sfTransform_combine(float* transform, const float* other);
503 
504 //Combine a transform with a translation
505 void sfTransform_translate(float* transform, float x, float y);
506 
507 //Combine the current transform with a rotation
508 void sfTransform_rotate(float* transform, float angle);
509 
510 //Combine the current transform with a rotation
511 void sfTransform_rotateWithCenter(float* transform, float angle, float centerX, float centerY);
512 
513 //Combine the current transform with a scaling
514 void sfTransform_scale(float* transform, float scaleX, float scaleY);
515 
516 //Combine the current transform with a scaling
517 void sfTransform_scaleWithCenter(float* transform, float scaleX, float scaleY, float centerX, float centerY);