1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2017 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 /**
26  *
27  * The interface and template are provided for convenience, on top of
28  * $(TRANSFORM_LINK).
29  *
30  * $(TRANSFORM_LINK), as a low-level class, offers a great level of flexibility
31  * but it is not always convenient to manage. Indeed, one can easily combine any
32  * kind of operation, such as a translation followed by a rotation followed by a
33  * scaling, but once the result transform is built, there's no way to go
34  * backward and, let's say, change only the rotation without modifying the
35  * translation and scaling.
36  *
37  * The entire transform must be recomputed, which means that you need to
38  * retrieve the initial translation and scale factors as well, and combine them
39  * the same way you did before updating the rotation. This is a tedious
40  * operation, and it requires to store all the individual components of the
41  * final transform.
42  *
43  * That's exactly what $(U Transformable) and $(U NormalTransformable) were
44  * written for: they hides these variables and the composed transform behind an
45  * easy to use interface. You can set or get any of the individual components
46  * without worrying about the others. It also provides the composed transform
47  * (as a $(TRANSFORM_LINK)), and keeps it up-to-date.
48  *
49  * In addition to the position, rotation and scale, $(U Transformable) provides
50  * an "origin" component, which represents the local origin of the three other
51  * components. Let's take an example with a 10x10 pixels sprite. By default, the
52  * sprite is positioned/rotated/scaled relatively to its top-left corner,
53  * because it is the local point (0, 0). But if we change the origin to be
54  * (5, 5), the sprite will be positioned/rotated/scaled around its center
55  * instead. And if we set the origin to (10, 10), it will be transformed around
56  * its bottom-right corner.
57  *
58  * To keep $(U Transformable) and $(U NormalTransformable) simple, there's only
59  * one origin for all the components. You cannot position the sprite relatively
60  * to its top-left corner while rotating it around its center, for example. To
61  * do such things, use $(TRANSFORM_LINK) directly.
62  *
63  * $(U Transformable) is meant to be used as a base for other classes. It is
64  * often combined with $(DRAWABLE_LINK) -- that's what DSFML's sprites, texts
65  * and shapes do.
66  * ---
67  * class MyEntity : Transformable, Drawable
68  * {
69  *     //generates the boilerplate code for Transformable
70  *     mixin NormalTransformable;
71  *
72  *     void draw(RenderTarget target, RenderStates states) const
73  *     {
74  *         states.transform *= getTransform();
75  *         target.draw(..., states);
76  *     }
77  * }
78  *
79  * auto entity = new MyEntity();
80  * entity.position = Vector2f(10, 20);
81  * entity.rotation = 45;
82  * window.draw(entity);
83  * ---
84  *
85  * $(PARA If you don't want to use the API directly (because you don't need all
86  * the functions, or you have different naming conventions for example), you can
87  * have a $(U TransformableMember) as a member variable.)
88  * ---
89  * class MyEntity
90  * {
91  *     this()
92  *     {
93  *         myTransform = new TransformableMember();
94  *     }
95  *
96  *     void setPosition(MyVector v)
97  *     {
98  *         myTransform.setPosition(v.x, v.y);
99  *     }
100  *
101  *     void draw(RenderTarget target, RenderStates states) const
102  *     {
103  *         states.transform *= myTransform.getTransform();
104  *         target.draw(..., states);
105  *     }
106  *
107  * private TransformableMember myTransform;
108  * }
109  * ---
110  *
111  * $(PARA A note on coordinates and undistorted rendering:
112  * By default, DSFML (or more exactly, OpenGL) may interpolate drawable objects
113  * such as sprites or texts when rendering. While this allows transitions like
114  * slow movements or rotations to appear smoothly, it can lead to unwanted
115  * results in some cases, for example blurred or distorted objects. In order to
116  * render a $(DRAWABLE_LINK) object pixel-perfectly, make sure the involved
117  * coordinates allow a 1:1 mapping of pixels in the window to texels (pixels in
118  * the texture). More specifically, this means:)
119  * $(UL
120  * $(LI The object's position, origin and scale have no fractional part)
121  * $(LI The object's and the view's rotation are a multiple of 90 degrees)
122  * $(LI The view's center and size have no fractional part))
123  *
124  * See_Also:
125  * $(TRANSFORM_LINK)
126  */
127 module dsfml.graphics.transformable;
128 
129 import dsfml.system.vector2;
130 
131 //public import so that people don't have to worry about
132 //importing transform when they import transformable
133 public import dsfml.graphics.transform;
134 
135 /**
136  * Decomposed transform defined by a position, a rotation, and a scale.
137  */
138 interface Transformable
139 {
140     @property
141     {
142         /**
143          * The local origin of the object.
144          *
145          * The origin of an object defines the center point for all
146          * transformations (position, scale, ratation).
147          *
148          * The coordinates of this point must be relative to the top-left corner
149          * of the object, and ignore all transformations (position, scale,
150          * rotation).
151          *
152          * The default origin of a transformable object is (0, 0).
153          */
154         Vector2f origin(Vector2f newOrigin);
155         /// ditto
156         Vector2f origin() const;
157     }
158 
159     @property
160     {
161         /// The position of the object. The default is (0, 0).
162         Vector2f position(Vector2f newPosition);
163         /// ditto
164         Vector2f position() const;
165     }
166 
167     @property
168     {
169         /// The orientation of the object, in degrees. The default is 0 degrees.
170         float rotation(float newRotation);
171         /// ditto
172         float rotation() const;
173     }
174 
175     @property
176     {
177         /// The scale factors of the object. The default is (1, 1).
178         Vector2f scale(Vector2f newScale);
179         /// ditto
180         Vector2f scale() const;
181     }
182 
183     /**
184      * Get the inverse of the combined transform of the object.
185      *
186      * Returns: Inverse of the combined transformations applied to the object.
187      */
188     const(Transform) getTransform();
189 
190     /**
191      * Get the combined transform of the object.
192      *
193      * Returns: Transform combining the position/rotation/scale/origin of the
194      * object.
195      */
196     const(Transform) getInverseTransform();
197 
198     /**
199      * Move the object by a given offset.
200      *
201      * This function adds to the current position of the object, unlike the
202      * position property which overwrites it.
203      *
204      * Params:
205      * 		offset	= The offset
206      */
207     void move(Vector2f offset);
208 
209 }
210 
211 /**
212  * Mixin template that generates the boilerplate code for the $(U Transformable)
213  * interface.
214  *
215  * This template is provided for convenience, so that you don't have to add the
216  * properties and functions manually.
217  */
218 mixin template NormalTransformable()
219 {
220     private
221     {
222         // Origin of translation/rotation/scaling of the object
223         Vector2f m_origin = Vector2f(0,0);
224         // Position of the object in the 2D world
225         Vector2f m_position = Vector2f(0,0);
226         // Orientation of the object, in degrees
227         float m_rotation = 0;
228         // Scale of the object
229         Vector2f m_scale = Vector2f(1,1);
230         // Combined transformation of the object
231         Transform m_transform;
232         // Does the transform need to be recomputed?
233         bool m_transformNeedUpdate;
234         // Combined transformation of the object
235         Transform m_inverseTransform;
236         // Does the transform need to be recomputed?
237         bool m_inverseTransformNeedUpdate;
238     }
239 
240     @property
241     {
242         Vector2f origin(Vector2f newOrigin)
243         {
244             m_origin = newOrigin;
245             m_transformNeedUpdate = true;
246             m_inverseTransformNeedUpdate = true;
247             return newOrigin;
248         }
249 
250         Vector2f origin() const
251         {
252             return m_origin;
253         }
254     }
255 
256     @property
257     {
258         Vector2f position(Vector2f newPosition)
259         {
260             m_position = newPosition;
261             m_transformNeedUpdate = true;
262             m_inverseTransformNeedUpdate = true;
263             return newPosition;
264         }
265 
266         Vector2f position() const
267         {
268             return m_position;
269         }
270     }
271 
272     @property
273     {
274         float rotation(float newRotation)
275         {
276             m_rotation = cast(float)fmod(newRotation, 360);
277             if(m_rotation < 0)
278             {
279                 m_rotation += 360;
280             }
281             m_transformNeedUpdate = true;
282             m_inverseTransformNeedUpdate = true;
283             return newRotation;
284         }
285 
286         float rotation() const
287         {
288             return m_rotation;
289         }
290     }
291 
292     @property
293     {
294         Vector2f scale(Vector2f newScale)
295         {
296             m_scale = newScale;
297             m_transformNeedUpdate = true;
298             m_inverseTransformNeedUpdate = true;
299             return newScale;
300         }
301 
302         Vector2f scale() const
303         {
304             return m_scale;
305         }
306     }
307 
308     const(Transform) getInverseTransform()
309     {
310         if (m_inverseTransformNeedUpdate)
311         {
312             m_inverseTransform = getTransform().getInverse();
313             m_inverseTransformNeedUpdate = false;
314         }
315 
316         return m_inverseTransform;
317     }
318 
319     const(Transform) getTransform()
320     {
321         if (m_transformNeedUpdate)
322         {
323             float angle = -m_rotation * 3.141592654f / 180f;
324             float cosine = cast(float)(cos(angle));
325             float sine = cast(float)(sin(angle));
326             float sxc = m_scale.x * cosine;
327             float syc = m_scale.y * cosine;
328             float sxs = m_scale.x * sine;
329             float sys = m_scale.y * sine;
330             float tx = -m_origin.x * sxc - m_origin.y * sys + m_position.x;
331             float ty = m_origin.x * sxs - m_origin.y * syc + m_position.y;
332 
333             m_transform = Transform( sxc, sys, tx,
334                                     -sxs, syc, ty,
335                                     0f, 0f, 1f);
336             m_transformNeedUpdate = false;
337         }
338 
339         return m_transform;
340     }
341 
342     void move(Vector2f offset)
343     {
344         position = position + offset;
345     }
346 }
347 
348 /**
349  * Concrete class that implements the $(U Transformable) interface.
350  *
351  * This class is provided for convenience, so that a $(U Transformable) object
352  * can be used as a member of a class instead of inheriting from
353  * $(U Transformable).
354  */
355 class TransformableMember: Transformable
356 {
357     mixin NormalTransformable;
358 }