Pixelate:Issue 12/Viewport Pushing

From Allegro Wiki

Jump to: navigation, search

Contents

Viewport pushing

~ Adrian Danis (aka RamboBones) 

Background

Viewport pushing is a spiffy little idea I invented, okay to clarify a bit, I've worked out and designed all this on my own with no tutorials or anything other people are bound to have worked it out to. Anyway, on to the important stuff. Anybody who's been using OpenGL, Direct3D or OpenGL via AllegroGL should know about viewports. Just a little side note, I've never used Direct3D before so all my example code will be for OpenGL but all the principles in this article can be applied to Direct3D and even to any 2D renderers. The idea of viewport pushing is that instead of defining a whole new viewport, although you can do that as well, you can also define a viewport inside another viewport. So you think I'm weird? Keep reading.


Theory

The theory of viewport pushing as stated above is that you can define viewports inside other viewports. This requires keeping a list of all the currently set viewports, and then going through the list calculating the final viewport positions. Now, due to the nature of what we're trying to do all viewports must use relative coordinates (ala OpenGL's -1 to 1 system). Don't worry, I'll describe exactly how to do this iteration later, but basically at the end you'll have relative coordinates that can then be turned into screen coordinates and then sent to OpenGL or Direct3D or whatever your using. Here's a diagram so you get the idea:

Image:Viewports.jpg

Now, as you can see each viewport is defined at -0.5,-0.5 to 0.5 0.5 within the viewport before it. If those numbers mean nothing to you then... well... umm... I can't help you ;).


You wanna do this because?

Okay, this is one of the most obvious questions, just why the heck would anyone wanna do this? This is a bit of a more complex question than it seems. Once you think about it though the idea can become very useful. For instance, let's say your writing a nice shiny new car racing game. Now when racing you want to render all the scenery in fullscreen and also put on a little rear view mirror. Now, most games would call the rendering code twice, the first time with the normal viewport and the second time with the rear view viewport, the HUD won't be rendered the second time though :P. Okay... so how the hell will viewport pusing help this, you only need single viewports to do this. Well here's where it gets better, let's say you want to show the replay in a part of the game window, maybe you wanna let the user type their high score in whilest they watch their uber cool replay or something, but... if you were to call your old rendering code you'd get the first stage rendered correctly, but when rendering the rear view mirror it would be in the fullscreen position and not inside the window you're replaying the window in. Okay, in case you hadn't worked it out by now using sub viewports you'd totally eliminate that problem, there are loads of examples I could give but I think you've probably gotten the idea by now :P.


Implementation

Now the moment everybody's been waiting for, just how DO we do this. Well since I'm a realy busy/lazy person ;), I'll only give you an example setup, I highly recommend you don't actually use this setup but rather incorporate it into your own render system, it's only there to teach you the actual basic implementation of what we're doing. This isn't a working code segment either, it's merely a couple of classes that you can use, which I hope work :D. The example code isn't bound to any one API, i.e. OpenGL or Direct3D, either, you can use it with either, or maybe even with Allegro, though not sure how :P.

//class that defines a single viewport. Don't forgot all values are done so
//that y axis goes UP
class CViewport
{
        public:
        CViewport()
        {
                //set default options
                SetViewportDimensions(CVector2(-1,-1),CVector2(1,1));
        }
        virtual ~CViewport(){}
        void operator=(const CViewport &Viewport)
        {
                ViewportDims[0]=Viewport.ViewportDims[0];
                ViewportDims[1]=Viewport.ViewportDims[1];
        }
        private:
        //the CVector2 class is merely a class containing x and y as floats
        //along with lots of operators
        CVector2 ViewportDims[2]; //the dims are as bottom left-top right
        public:
        //the usual get and set functions
        inline void SetViewportDimensions(const CVector2 &Pos1,const CVector2 &Pos2)
        {
                ViewportDims[0]=Pos1;
                ViewportDims[1]=Pos2;
        }
        inline void GetViewportDims(CVector2 &Pos1,CVector2 &Pos2)const
        {
                Pos1=ViewportDims[0];
                Pos2=ViewportDims[1];
        }
        //put a relative coord in and you get the relative coord for inside this viewport, umm..
        //err that was a bad explanation, just wait for the rest of the example code, just remember
        //that the function names here are VERY VERY VERY bad, and should be renamed but this
        //is how the code works in my renderer so I'm not touching it
        inline CVector2 GetScreenCoord(const CVector2 &Coord)const
        {
                return CVector2(ViewportDims[0].x+((Coord.x+1)/(Sscalar)2.0)*(Sscalar)(ViewportDims[1].x-ViewportDims[0].x),ViewportDims[0].y+((Coord.y+1)/(Sscalar)2.0)*(Sscalar)(ViewportDims[1].y-ViewportDims[0].y));
        }
        //does the opposite of above
        inline CVector2 GetRelativeCoord(const CVector2 &Coord)const
        {
                return CVector2((Coord.x-ViewportDims[0].x)/(ViewportDims[1].x-ViewportDims[0].x)*2-1,(Coord.y-ViewportDims[0].y)/(ViewportDims[1].y-ViewportDims[0].y)*2-1);
        }
};

//keeps a list of all our viewports and converts them to screen
//coordinates, which means we DO need the screen width and height
//in this class
class CViewportList
{
        protected:
        //data
        list<CViewport> Viewports;
        unsigned long ScreenWidth,ScreenHeight;
        public:
        //just a generic constructor
        CViewportList(unsigned long SWidth,unsigned long SHeight) : ScreenWidth(SWidth),ScreenHeight(SHeight)
        {
        }
        ~CViewportList()
        {
        }
        //change the screen width and height, in case you've resized your window since
        //creating this class
        void SetScreenSize(unsigned long SWidth,unsigned long SHeight)
        {
                ScreenWidth=SWidth;
                ScreenHeight=SHeight;
        }
        //calculates the two coordinates for the viewport, in standard math notation, i.e. y axis
        //goes UP. Remember if you're using OpenGL which requires x,y and then width and height
        //coordinates and not x,y x,y coordinates then you'll have to modify the values given
        // a bit.
        void CalculateScreenCoords(unsigned long &x1,unsigned long &y1,usigned long &x2,usigned long &y2)
        {
                //temp holders
                CVector2 SC1(-1,-1),SC2(1,1);
                //iterator through the list and calculate the final viewport
                for(list<CViewport>::iterator it=Viewports.begin();it!=Viewports.end();it++)
                {
                        SC1=it->GetScreenCoord(SC1);
                        SC2=it->GetScreenCoord(SC2);
                }
                //now convert these to per pixel screen coordinates
                x1=(ScreenCoord1.x+1)/(float)2.0*(float)RendererWidth;
                y1=(ScreenCoord1.y+1)/(float)2.0*(float)RendererHeight;
                x2=(ScreenCoord2.x+1)/(float)2.0*(float)RendererWidth;
                y2=(ScreenCoord2.y+1)/(float)2.0*(float)RendererHeight;
        }
        //push a new viewport, next time you define a viewport it'll be put inside
        //the last defined viewport
        void PushViewport()
        {
                //pushes on a new viewport and set's it to the full range
                //the CViewport constructor automatically set's the full range
                Viewports.push_back(CViewport());
        }
        //pop a viewport, you're now using the viewport you had before
        //the last call to push viewport
        void PopViewport()
        {
                //make sure there's a viewport to pop off
                if(Viewports.size()>0)
                Viewports.pop_back();
        }
};

As you can see, there's not much to it. Although I should point out that all of the above code was pulled out of my own renderer and hasn't been tested in this form by me at all, I don't even know if it compiles, but you should get the idea anyhow.


Final words

Well I hope you find this technique as useful as I have. Erm... I guess that's all I have to say :D, sorry for boring you all, bye

Personal tools