1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  * 
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  * 
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  * 
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  * 
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  * 
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Graphic.Transformable;
25 
26 private:
27 
28 import Dgame.Math.Vector2;
29 import Dgame.Math.Matrix4x4;
30 
31 public:
32 
33 /**
34  * Transformable is an abstract class which enables his child classes to be transformable
35  * which means that they have a position, can be scaled and can be rotated.
36  * For that purpose an additional Matrix is used (besides the Vector2f and float components) which maps the transformations.
37  *
38  * See: Matrix4
39  * See: Vector2f
40  *
41  * Author: Randy Schuett (rswhite4@googlemail.com)
42  */
43 class Transformable {
44 private:
45     Vector2f _position;
46     Vector2f _rotationCenter;
47     Vector2f _origin;
48     Vector2f _scale = Vector2f(1, 1);
49 
50     float _rotationAngle = 0;
51 
52     Matrix4x4 _matrix;
53     bool _was_transformed = true;
54 
55 protected:
56     @nogc
57     final void _notifyTransform() pure nothrow {
58         _was_transformed = true;
59     }
60 
61 public:
62 final:
63     /**
64      * Returns the current matrix.
65      * If any transformations was made, the matrix is updated before
66      */
67     @nogc
68     ref const(Matrix4x4) getMatrix() pure nothrow {
69         if (_was_transformed) {
70             const Vector2f* center = _origin.isEmpty() ? &_rotationCenter : &_origin;
71             _matrix.loadIdentity()
72                    .translate(_position - _origin)
73                    .rotate(_rotationAngle, *center)
74                    .scale(_scale, *center);
75             _was_transformed = false;
76         }
77 
78         return _matrix;
79     }
80 
81     /**
82      * Sets the position
83      */
84     @nogc
85     void setPosition(float x, float y) pure nothrow {
86         _position.x = x;
87         _position.y = y;
88         _notifyTransform();
89     }
90 
91     /**
92      * Sets the position
93      */
94     @nogc
95     void setPosition(const Vector2f pos) pure nothrow {
96         _position = pos;
97         _notifyTransform();
98     }
99 
100     /**
101      * Move the position by offset (x, y)
102      */
103     @nogc
104     void move(float x, float y) pure nothrow {
105         _position.x += x;
106         _position.y += y;
107         _notifyTransform();
108     }
109 
110     /**
111      * Move the position by offset (x, y)
112      */
113     @nogc
114     void move(const Vector2f offset) pure nothrow {
115         _position += offset;
116         _notifyTransform();
117     }
118 
119     /**
120      * Returns the current position
121      */
122     @nogc
123     ref const(Vector2f) getPosition() const pure nothrow{
124         return _position;
125     }
126 
127     /**
128      * Returns the x coordinate <b>by value</b>
129      * 
130      * Note: if you want to change the coordinate, use either move or setPosition
131      */
132     @nogc
133     @property
134     float x() const pure nothrow {
135         return _position.x;
136     }
137 
138     /**
139      * Sets a new x coordinate
140      */
141     @nogc
142     @property
143     void x(float cx) pure nothrow {
144         _position.x = cx;
145         _notifyTransform();
146     }
147 
148     /**
149      * Returns the y coordinate <b>by value</b>
150      * 
151      * Note: if you want to change the coordinate, use either move or setPosition
152      */
153     @nogc
154     @property
155     float y() const pure nothrow {
156         return _position.y;
157     }
158 
159     /**
160      * Sets a new y coordinate
161      */
162     @nogc
163     @property
164     void y(float cy) pure nothrow {
165         _position.y = cy;
166         _notifyTransform();
167     }
168 
169     /**
170      * Sets the center position
171      *
172      * Note: if you use this function with setOrigin,
173      *       the origin takes the place of the rotation center.
174      */
175     @nogc
176     void setRotationCenter(float x, float y) pure nothrow {
177         _rotationCenter.x = x;
178         _rotationCenter.y = y;
179         _notifyTransform();
180     }
181 
182     /**
183      * Sets the center of the rotation
184      *
185      * Note: if you use this function with setOrigin,
186      *       the origin takes the place of the rotation center.
187      */
188     @nogc
189     void setRotationCenter(const Vector2f center) pure nothrow {
190         _rotationCenter = center;
191         _notifyTransform();
192     }
193 
194     /**
195      * Returns the current rotation center
196      */
197     @nogc
198     ref const(Vector2f) getRotationCenter() const  pure nothrow {
199         return _rotationCenter;
200     }
201 
202     /**
203      * Sets the origin. The origin is a offset which is added to the position
204      * and serves as center position for scaling and rotation.
205      *
206      * Note: If you use this with setRotationCenter
207      *       the origin will suppress the rotation center and takes it's place.
208      */
209     @nogc
210     void setOrigin(float x, float y) pure nothrow {
211         _origin.x = x;
212         _origin.y = y;
213         _notifyTransform();
214     }
215 
216     /**
217      * Sets the origin. The origin is a offset which is added to the position
218      * and serves as center position for scaling and rotation.
219      *
220      * Note: If you use this with setRotationCenter
221      *       the origin will suppress the rotation center and takes it's place.
222      */
223     @nogc
224     void setOrigin(const Vector2f origin) pure nothrow {
225         _origin = origin;
226         _notifyTransform();
227     }
228 
229     /**
230      * Returns the current origin
231      */
232     @nogc
233     ref const(Vector2f) getOrigin() const  pure nothrow {
234         return _origin;
235     }
236 
237     /**
238      * Sets the scaling (for both, x and y)
239      */
240     @nogc
241     void setScale(float x, float y) pure nothrow  {
242         _scale.x = x;
243         _scale.y = y;
244         _notifyTransform();
245     }
246 
247     /**
248      * Sets the scaling (for both, x and y)
249      */
250     @nogc
251     void setScale(const Vector2f offset) pure nothrow  {
252         _scale = offset;
253         _notifyTransform();
254     }
255 
256     /**
257      * Inc-/Decrease the scaling
258      */
259     @nogc
260     void scale(const Vector2f offset) pure nothrow {
261         _scale += offset;
262         _notifyTransform();
263     }
264 
265     /**
266      * Inc-/Decrease the scaling
267      */
268     @nogc
269     void scale(float offset) pure nothrow {
270         _scale += offset;
271         _notifyTransform();
272     }
273 
274     /**
275      * Returns the current scaling
276      */
277     @nogc
278     ref const(Vector2f) getScale() const pure nothrow {
279         return _scale;
280     }
281 
282     /**
283      * Set the rotation angle. If not in range of 0 .. 360 it will be set to 0.
284      */
285     @nogc
286     void setRotation(float rotation) pure nothrow {
287         _rotationAngle = rotation;
288         if (_rotationAngle < 0 || _rotationAngle > 360)
289             _rotationAngle = 0;
290         _notifyTransform();
291     }
292 
293     /**
294      * Inc-/Decrease the rotation.
295      * If the rotation is <b>after</b> that not in range of 0 .. 360 it will be set to 0.
296      */
297     @nogc
298     void rotate(float rotation) pure nothrow {
299         _rotationAngle += rotation;
300         if (_rotationAngle > 360 || _rotationAngle < -360)
301             _rotationAngle %= 360;
302         _notifyTransform();
303     }
304 
305     /**
306      * Returns the current rotation
307      */
308     @nogc
309     float getRotation() const pure nothrow {
310         return _rotationAngle;
311     }
312 }