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