.. Copyright (c) 2018-2024 William Emerison Six Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Window Resizing and Proportionality - Demo 03 ============================================= .. figure:: static/screenshots/demo03.png :align: center :alt: Demo 03 :figclass: align-center Demo 03 Problem With Previous Demo ^^^^^^^^^^^^^^^^^^^^^^^^^^ When running Demo02, if the user resizes the windows, then the paddles lose their proportionality, as NDC no longer is mapped to a square screenspace. How to Execute ^^^^^^^^^^^^^^ On Linux or on MacOS, in a shell, type "python src/demo03/demo.py". On Windows, in a command prompt, type "python src\\demo03\\demo.py". Purpose ^^^^^^^ Modify the previous demo, so that if the user resizes the window of the OpenGL program, that the picture does not become distorted. Create procedure to ensure proportionality. Keeping the Paddles Proportional ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the previous demo, if the user resized the window, the paddles appear distorted, as they were shrunk in one direction if the window became too thin or too fat. .. figure:: static/disproportionate1.png :align: center :alt: Yuck :figclass: align-center Yuck .. figure:: static/disproportionate2.png :align: center :alt: Yuck :figclass: align-center Yuck Assume that this is a problem for the application we are making, how could we solve it and keep proportionality regardless of the dimensions of the window? Ideally, we would like to draw our paddles with a black background within a square region in the center of the window, regardless of the dimensions of the window. OpenGL has a solution for us. The *viewport* is a rectangular region contained within the window into which OpenGL will render. By specifing a viewport, OpenGL will convert the normalized-device-coordinates to the sub-window space of the viewport, instead of the whole window. .. figure:: static/viewport.png :align: center :alt: Nice :figclass: align-center Nice .. figure:: static/demo03.png :align: center :alt: Demo 03 :figclass: align-center Demo 03 Because we will only draw in a subset of the window, and because all subsequent chapters will use this functionality, I have created a procedure for use in all chapters named "draw_in_square_viewport". Code ^^^^ GLFW/OpenGL Initialization ~~~~~~~~~~~~~~~~~~~~~~~~~~ The setup code is the same as the previous demo's setup. Initialize GLFW. Set the OpenGL version. Create the window. Set a key handler for closing. Execute the event/drawing loop. The only code showed in this book will be the relevant parts. Consult the python source for the full, working code. Set to Draw in Square Subsection Of Window &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin 1a8e13a46337c0e9ac0f9436953d66dec069eb1f :end-before: doc-region-end 1a8e13a46337c0e9ac0f9436953d66dec069eb1f * declare a function to configure OpenGL to draw only in a square subset of the monitor, i.e. the viewport .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin 263830783a8fbe25283deaa80688f95592917298 :end-before: doc-region-end 263830783a8fbe25283deaa80688f95592917298 * set the clear color to be gray. * glClear clear the color of every pixel in the whole frame buffer, independent of viewport. So now the entire framebuffer is gray. .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin 3db7c4975ad4354e344c0a2f0d4d94125195ce32 :end-before: doc-region-end 3db7c4975ad4354e344c0a2f0d4d94125195ce32 * figure out the minimum dimension of the window. In the image above, the "square_size" is 1200, as the monitor's vertical screenspace is only 1200 pixels tall. * To make a square subregion, we need a number for the distance between vertices of the square. By using the minimium of the width and height, we can at least fill up the screen in one dimension. .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin a2d0bcb5b525e8a68e0bc1ef213359f165981839enablescissortest :end-before: doc-region-end a2d0bcb5b525e8a68e0bc1ef213359f165981839enablescissortest * Enable the scissor test. Internally, OpenGL drivers likely have global variables that we set by calling functions. Every OpenGL feature isn't used by every OpenGL program. For instance, we are not using lighting to add realism. We aren't using texturing. We are using the scissor test, so we must enable it. We only enable the features that we need so that the OpenGL driver doesn't waste time doing unnecessary computations. * the scissor test allows us to specify a region of the framebuffer into which the opengl operations will apply. In this case, the color in every pixel in the framebuffer is currently gray because of the exisiting class to glClearColor. By calling glScissor, we are setting a value in each fragment (i.e., pixel) on a square region of pixels to be true (and false everywhere else) which means "only do the opengl call on these fragments, ignore the others". As we will learn later, OpenGL stores much more information per fragment (i.e. pixel) than just it's current color. .. figure:: static/ccbysa3.0/williamsix/fragment.png :align: center :alt: Fragment :figclass: align-center * Look at the image above of NDC superimposed on Screen Space. From this, the arguments sent to glScissor should be clear. .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin db4245dba3c0c229416c97fe84da3cb87b1f439d :end-before: doc-region-end db4245dba3c0c229416c97fe84da3cb87b1f439d * glClear will only update the square to black values. .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin 9524935cab9f5725f921d11969872ebd9a54e508 :end-before: doc-region-end 9524935cab9f5725f921d11969872ebd9a54e508 * disable the scissor test, so now any opengl calls will happen as usual. So we've drawn black into a square, and disabled the scissor test, so any subsequent OpenGL calls will still be drawn into the full framebuffer. But, we only want to draw within the black square, and the scissor test does not modify the NDC to screenspace transformations. To modify the NDC to screenspace transformations, we set the viewport again, so that the NDC coordinates will be mapped the the region of screen coordinates that we care about, which is the black square. .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin defaeb0e6b9ada0b6c349a4dec907300e4c14acbviewportcall :end-before: doc-region-end defaeb0e6b9ada0b6c349a4dec907300e4c14acbviewportcall The Event Loop &&&&&&&&&&&&&& This demo's event loop is just like the previous demo, but this time we call the procedure to ensure that we only draw in a square subset of the window. .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin 33fecc926105eda74989fb02da7daca03e3bfea8 :end-before: doc-region-end 33fecc926105eda74989fb02da7daca03e3bfea8 .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin 415cedbd77f6cc02a34de30d2da1b24cab344c5c :end-before: doc-region-end 415cedbd77f6cc02a34de30d2da1b24cab344c5c * The event loop is the same as the previous demo, except that we call draw_in_square_viewport every frame at the beginning. .. literalinclude:: ../src/demo03/demo.py :language: python :start-after: doc-region-begin bf9a23e3296c75d786d75f7b0e406448b773b23b :end-before: doc-region-end bf9a23e3296c75d786d75f7b0e406448b773b23b