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