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  * $(U View) defines a camera in the 2D scene. This is a very powerful concept:
27  * you can scroll, rotate or zoom the entire scene without altering the way that
28  * your drawable objects are drawn.
29  *
30  * A view is composed of a source rectangle, which defines what part of the 2D
31  * scene is shown, and a target viewport, which defines where the contents of
32  * the source rectangle will be displayed on the render target (window or
33  * texture).
34  *
35  * The viewport allows to map the scene to a custom part of the render target,
36  * and can be used for split-screen or for displaying a minimap, for example.
37  * If the source rectangle has not the same size as the viewport, its contents
38  * will be stretched to fit in.
39  *
40  * To apply a view, you have to assign it to the render target. Then, every
41  * objects drawn in this render target will be affected by the view until you
42  * use another view.
43  *
44  * Example:
45  * ---
46  * auto window = RenderWindow();
47  * auto view = View();
48  *
49  * // Initialize the view to a rectangle located at (100, 100) and with a size of 400x200
50  * view.reset(FloatRect(100, 100, 400, 200));
51  *
52  * // Rotate it by 45 degrees
53  * view.rotate(45);
54  *
55  * // Set its target viewport to be half of the window
56  * view.setViewport(FloatRect(0.f, 0.f, 0.5f, 1.f));
57  *
58  * // Apply it
59  * window.view = view;
60  *
61  * // Render stuff
62  * window.draw(someSprite);
63  *
64  * // Set the default view back
65  * window.view = window.getDefaultView();
66  *
67  * // Render stuff not affected by the view
68  * window.draw(someText);
69  * ---
70  *
71  * $(PARA See also the note on coordinates and undistorted rendering in
72  * $(TRANSFORMABLE_LINK).)
73  *
74  * See_Also:
75  * $(RENDERWINDOW_LINK), $(RENDERTEXTURE_LINK)
76  */
77 module dsfml.graphics.view;
78 
79 import dsfml.graphics.rect;
80 import dsfml.system.vector2;
81 import dsfml.graphics.transform;
82 
83 /**
84  * 2D camera that defines what region is shown on screen.
85  */
86 struct View
87 {
88     package
89     {
90         Vector2f m_center = Vector2f(500, 500);
91         Vector2f m_size = Vector2f(1000, 1000);
92         float m_rotation = 0;
93         FloatRect m_viewport = FloatRect(0, 0, 1, 1);
94     }
95     private
96     {
97         bool m_transformUpdated;
98         bool m_invTransformUpdated;
99         Transform m_transform;
100         Transform m_inverseTransform;
101     }
102 
103     /**
104      * Construct the view from a rectangle
105      *
106      * Params:
107      *	rectangle = Rectangle defining the zone to display
108      */
109     this (FloatRect rectangle)
110     {
111         reset (rectangle);
112     }
113 
114     /**
115      * Construct the view from its center and size
116      *
117      * Params:
118      * center = Center of the zone to display
119      * size   = Size of zone to display
120      */
121     this (Vector2f center, Vector2f size)
122     {
123         m_center = center;
124         m_size = size;
125     }
126 
127     @property
128     {
129         /// The center of the view.
130         Vector2f center(Vector2f newCenter)
131         {
132             m_center = newCenter;
133 
134             m_transformUpdated = false;
135             m_invTransformUpdated = false;
136 
137             return newCenter;
138         }
139         /// ditto
140         Vector2f center() const
141         {
142             return m_center;
143         }
144     }
145 
146     @property
147     {
148         /**
149          * The orientation of the view, in degrees. The default rotation is 0
150          * degrees.
151          */
152         float rotation(float newRotation)
153         {
154             m_rotation = newRotation % 360.0;
155             if (m_rotation <0)
156                 m_rotation += 360.0;
157 
158             m_transformUpdated = false;
159             m_invTransformUpdated = false;
160 
161             return newRotation;
162         }
163         /// ditto
164         float rotation() const
165         {
166             return m_rotation;
167 
168         }
169     }
170 
171     @property
172     {
173         /// The size of the view. The default size is (1, 1).
174         Vector2f size(Vector2f newSize)
175         {
176             m_size = newSize;
177             m_transformUpdated = false;
178             m_invTransformUpdated = false;
179             return newSize;
180         }
181         /// ditto
182         Vector2f size() const
183         {
184             return m_size;
185         }
186     }
187 
188     @property
189     {
190         /**
191           * The target viewpoirt.
192           *
193           * The viewport is the rectangle into which the contents of the view are
194          * displayed, expressed as a factor (between 0 and 1) of the size of the
195          * RenderTarget to which the view is applied. For example, a view which
196          * takes the left side of the target would be defined with
197          * `View.setViewport(FloatRect(0, 0, 0.5, 1))`. By default, a view has a
198          * viewport which covers the entire target.
199           */
200         FloatRect viewport(FloatRect newTarget)
201         {
202             m_viewport = newTarget;
203 
204             return newTarget;
205         }
206         /// ditto
207         FloatRect viewport() const
208         {
209             return m_viewport;
210         }
211     }
212 
213     /**
214      * Move the view relatively to its current position.
215      *
216      * Params:
217      * 		offset	= Move offset
218      */
219     void move(Vector2f offset)
220     {
221         center = m_center + offset;
222     }
223 
224     /**
225      * Reset the view to the given rectangle.
226      *
227      * Note that this function resets the rotation angle to 0.
228      *
229      * Params:
230      * 		rectangle	= Rectangle defining the zone to display.
231      */
232     void reset(FloatRect rectangle)
233     {
234         m_center.x = rectangle.left + rectangle.width / 2.0;
235         m_center.y = rectangle.top + rectangle.height / 2.0;
236         m_size.x = rectangle.width;
237         m_size.y = rectangle.height;
238         m_rotation = 0;
239 
240         m_transformUpdated = false;
241         m_invTransformUpdated = false;
242     }
243 
244     /**
245      * Resize the view rectangle relatively to its current size.
246      *
247      * Resizing the view simulates a zoom, as the zone displayed on screen grows
248      * or shrinks. factor is a multiplier:
249      * $(UL
250      * $(LI `1` keeps the size unchanged.)
251      * $(LI `> 1` makes the view bigger (objects appear smaller).)
252      * $(LI `< 1` makes the view smaller (objects appear bigger).))
253      *
254      * Params:
255      * 		factor	= Zoom factor to apply
256      */
257     void zoom(float factor)
258     {
259         size = m_size * factor;
260     }
261 
262     /**
263      * Get the projection transform of the view.
264      *
265      * This function is meant for internal use only.
266      *
267      * Returns: Projection transform defining the view.
268      *
269      */
270     //We have both const and mutable overloads so when the object is mutable, we can lazily cache the transform.
271     Transform getTransform()
272     {
273         import std.math;
274         // Recompute the matrix if needed
275         if (!m_transformUpdated)
276         {
277             // Rotation components
278             float angle  = m_rotation * 3.141592654f / 180.0;
279             float cosine = cos(angle);
280             float sine   = sin(angle);
281             float tx     = -m_center.x * cosine - m_center.y * sine + m_center.x;
282             float ty     =  m_center.x * sine - m_center.y * cosine + m_center.y;
283 
284             // Projection components
285             float a =  2.0 / m_size.x;
286             float b = -2.0 / m_size.y;
287             float c = -a * m_center.x;
288             float d = -b * m_center.y;
289 
290             // Rebuild the projection matrix
291             m_transform = Transform( a * cosine, a * sine,   a * tx + c,
292                                     -b * sine,   b * cosine, b * ty + d,
293                                      0.0,        0.0,        1.0);
294             m_transformUpdated = true;
295         }
296 
297         return m_transform;
298     }
299 
300     /// ditto
301     Transform getTransform() const
302     {
303         import std.math;
304         // Recompute the matrix
305         Transform currentTransform;
306 
307         // Rotation components
308         float angle  = m_rotation * 3.141592654f / 180.0;
309         float cosine = cos(angle);
310         float sine   = sin(angle);
311         float tx     = -m_center.x * cosine - m_center.y * sine + m_center.x;
312         float ty     =  m_center.x * sine - m_center.y * cosine + m_center.y;
313 
314         // Projection components
315         float a =  2.0 / m_size.x;
316         float b = -2.0 / m_size.y;
317         float c = -a * m_center.x;
318         float d = -b * m_center.y;
319 
320         // Rebuild the projection matrix
321         currentTransform = Transform( a * cosine, a * sine,   a * tx + c,
322                                 -b * sine,   b * cosine, b * ty + d,
323                                  0.0,        0.0,        1.0);
324 
325         return currentTransform;
326     }
327 
328     /**
329      * Get the inverse projection transform of the view.
330      *
331      * This function is meant for internal use only.
332      *
333      * Returns: Inverse of the projection transform defining the view.
334      *
335      */
336     Transform getInverseTransform()
337     {
338         // Recompute the matrix if needed
339         if (!m_invTransformUpdated)
340         {
341             m_inverseTransform = getTransform().getInverse();
342             m_invTransformUpdated = true;
343         }
344 
345         return m_inverseTransform;
346     }
347     /// ditto
348     Transform getInverseTransform() const
349     {
350         // Recompute the matrix if needed
351         return getTransform().getInverse();
352     }
353 }
354 
355 unittest
356 {
357     version(DSFML_Unittest_Graphics)
358     {
359         import std.stdio;
360 
361         import dsfml.graphics.rendertexture;
362 
363         writeln("Unit test for View");
364 
365         //the portion of the screen the view is displaying is at position (0,0) with a width of 100 and a height of 100
366         auto view = View(FloatRect(0,0,100,100));
367 
368         //the portion of the screen the view is displaying is at position (0,0) and takes up the remaining size of the screen.(expressed as a ratio)
369         view.viewport = FloatRect(0,0,1,1);
370 
371         auto renderTexture = new RenderTexture();
372 
373         renderTexture.create(1000,1000);
374 
375         renderTexture.clear();
376 
377         //set the view of the renderTexture
378         renderTexture.view = view;
379 
380         //draw some things using this view
381 
382         //get it ready for rendering
383         renderTexture.display();
384 
385         writeln();
386     }
387 }