Sign in to follow this  
Lance Mazon

2D Tile Based World Problem

Recommended Posts

I have a 2D world built of tiles, mapped to their proper coordinates in the world. I've set my viewport and GlOrtho to the size of the screen, and am using glScale to zoom in/out. Only when I do it I'm only seeing the part of the world originally rendered. I believe this to be because my glOrtho is restricting the tiles that are drawn. So do I need to reset my viewport, then my glOrtho and then do my glScale?

Share this post


Link to post
Share on other sites
If you take a digital camera and hit zoom, do trees and people get bigger? No, the camera just sees less of the world. To scale with ortho you need to change how much it sees. Only the viewport is the size of the screen. Ortho is how far left in the world will hit the left edge of your screen and how far right in the world will map to the right.

Share this post


Link to post
Share on other sites
So I leave the viewport set to the resolution of the screen, and use glOrtho to control the left and right mappings of the world. glScale just makes things bigger or smaller, so probably is not the best way to zoom in and out since I'll either be rendering more than I need to, or have to change my ortho because I'm not rendering enough. True?

Share this post


Link to post
Share on other sites
That did it. The viewport is set to the screen pixel resolution, and then ortho is being used to set what corresponds to the left and right portions of the screen. Now to just get the zoom working. I see two choices... Continue using glScale, which seems easiest, or further adjust the ortho to also reflect the zoom, which is slightly more complex because I have to keep the aspect ratio in mind. I'm off to experiment a bit. Thanks! =D

Share this post


Link to post
Share on other sites
For orthographic zooming, I typically just scale the modelview matrix:
[source lang="cpp"]glMatrixMode( GL_MODELVIEW );
glScale(1.1f, 1.1f, 1.0f);[/source]
There are other methods, like scaling the projection matrix, but they usually require additional translations to do a proper "in place" zoom, whereas this method "just works" (in my experience).

Share this post


Link to post
Share on other sites
Alright... so what I need to do is to make the default rendering view be a fully zoomed out view, and then use glScale to zoom in. My problem at the moment is that the zoom level that I render at is somewhere in the middle of the zoom levels I want to make available. I'll rework it a bit and it should work great. I wonder if there's a way I could also move the clipping plane when I zoom so the unseen geometry isn't being rendered as well....

Share this post


Link to post
Share on other sites
[quote name='Lance42' timestamp='1346691463' post='4976106']
I wonder if there's a way I could also move the clipping plane when I zoom so the unseen geometry isn't being rendered as well....
[/quote]

...?

By definition, "unseen" geometry is not rendered.

Share this post


Link to post
Share on other sites
If you want to zoom, zoom. Don't scale, don't move closer to the object or any of the other misguided attempts to make stuff "look bigger" that will eventually result in clipping if you move to close or scale too much.

Imagine you zoom in on a house, but a branch from a tree in front of you is covering half of it. Does it make any sense for a zoom to magically have the branch disappear, because it got clipped or you moved past it?

As dpadam450 already said, zoom by adjusting the projection matrix like a "real camera" and you won't get any unexpected effects.

Why would you care about the aspect ratio? It doesn't change if you multiply ALL sides by the same factor (as in: the two dimensions that matter).

Get in the habit of setting your ortho projection at the beginning of each frame, multiply your dimensions with your current zoom factor and be done with it. It works best if your projection is symmetric (from -x to +x instead of from 0 to +x) or you will need to also shift the values to avoid drifting closer to one corner.

Zoom in: zoom factor / 1.2, zoom out: zoom factor * 1.2. (or whatever numbers works for you) Edited by Trienco

Share this post


Link to post
Share on other sites
[quote name='Goran Milovanovic' timestamp='1346701371' post='4976158']
[quote name='Lance42' timestamp='1346691463' post='4976106']
I wonder if there's a way I could also move the clipping plane when I zoom so the unseen geometry isn't being rendered as well....
[/quote]

...?

By definition, "unseen" geometry is not rendered.
[/quote]

If you render a triangle and then use glScale to zoom as is suggested by some, OpenGL is still rendering the triangle, even though you can't see it. Or so I'm told.

Share this post


Link to post
Share on other sites
[quote name='Trienco' timestamp='1346734070' post='4976305']
If you want to zoom, zoom. Don't scale, don't move closer to the object or any of the other misguided attempts to make stuff "look bigger" that will eventually result in clipping if you move to close or scale too much.

Imagine you zoom in on a house, but a branch from a tree in front of you is covering half of it. Does it make any sense for a zoom to magically have the branch disappear, because it got clipped or you moved past it?

As dpadam450 already said, zoom by adjusting the projection matrix like a "real camera" and you won't get any unexpected effects.

Why would you care about the aspect ratio? It doesn't change if you multiply ALL sides by the same factor (as in: the two dimensions that matter).

Get in the habit of setting your ortho projection at the beginning of each frame, multiply your dimensions with your current zoom factor and be done with it. It works best if your projection is symmetric (from -x to +x instead of from 0 to +x) or you will need to also shift the values to avoid drifting closer to one corner.

Zoom in: zoom factor / 1.2, zoom out: zoom factor * 1.2. (or whatever numbers works for you)
[/quote]

I will do this and report back.

Share this post


Link to post
Share on other sites
[quote name='Lance42' timestamp='1346741800' post='4976326']
If you render a triangle and then use glScale to zoom as is suggested by some, OpenGL is still rendering the triangle, even though you can't see it. Or so I'm told.
[/quote]

As far as I know, everything that you can't see on screen is implicitly outside of Clip Space, and therefore not rendered ... But maybe there's something that I don't know about.

In either case, here's a complete zoom example written in C, using SDL for basic OpenGL context and input handling:

[source lang="cpp"]/*
* zoomdemo.c
*
* Zooming example by Goran Milovanovic, for Lance42.
*
* Arrow keys move the little square around.
* W key zooms in; S key zooms out.
* ESC to quit (or just close the window).
*
* Compile command used: gcc -Wall -o zoomdemo -lGL -lSDL zoomdemo.c
*
*/

#include "SDL/SDL.h"
#include "GL/gl.h"

SDL_Surface* createDisplay(short width, short height){

SDL_Init(SDL_INIT_EVERYTHING);

SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

SDL_Surface* screen = SDL_SetVideoMode(width, height, 32, SDL_OPENGL);

return screen;

}

void processEvents(char* keyboard){

static SDL_Event event;

while ( SDL_PollEvent(&event) ){

switch (event.type){

case SDL_KEYDOWN:
keyboard[ event.key.keysym.sym ] = 1;
break;

case SDL_KEYUP:
keyboard[ event.key.keysym.sym ] = 0;
break;

case SDL_QUIT:
exit(0);
}
}
}


void glInit(SDL_Surface* screen){

// Set clear color (black)
glClearColor( 0, 0, 0, 1 );

// Set projection matrix
glMatrixMode( GL_PROJECTION );
glLoadIdentity();

float aspect_ratio = (float)screen->w / screen->h;
float X = 10;
float Y = X / aspect_ratio;

glOrtho( -X, X, -Y, Y, 1.0, -1.0 );

// Set modelview matrix
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();


}

void glZoom(float scale){

glMatrixMode(GL_PROJECTION);
glScalef(1 + scale, 1 + scale, 1);
}


typedef struct Vec2{
float x;
float y;
} Vec2;

typedef struct Square{
Vec2 v_coords[4];
Vec2 pos;
} Square;

void squareInit(Square* square){

Vec2* v = square->v_coords;

v[0].x = -1; v[0].y = 1; // 0-----1
v[1].x = 1; v[1].y = 1; // | |
v[2].x = 1; v[2].y = -1; // | |
v[3].x = -1; v[3].y = -1; // 3-----2

square->pos.x = 0;
square->pos.y = 0;

}

void squareDraw(Square* square){

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(square->pos.x, square->pos.y, 0);

glEnableClientState(GL_VERTEX_ARRAY);

glVertexPointer(2, GL_FLOAT, 0, square->v_coords);

glDrawArrays(GL_QUADS, 0, 4);

glDisableClientState(GL_VERTEX_ARRAY);
}



int main(int argc, char* argv[]){

SDL_Surface* screen = createDisplay(640, 480);

glInit(screen);

char keyboard[322];
memset(keyboard, 0, sizeof(keyboard));


Square square;
squareInit(&square);


while (1){

processEvents(keyboard);

if (keyboard[SDLK_ESCAPE])
break;

glZoom((keyboard[SDLK_w] - keyboard[SDLK_s]) * 0.1f); // Incremental zoom


// Move the square
square.pos.x += (keyboard[SDLK_RIGHT] - keyboard[SDLK_LEFT]) * 0.25;
square.pos.y += (keyboard[SDLK_UP] - keyboard[SDLK_DOWN]) * 0.25;


glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer, so we can draw to a clean slate.

squareDraw(&square); // Draw whatever you want to draw.

SDL_GL_SwapBuffers(); // Update the screen with the newly rendered image.


SDL_Delay(16); // Close enough to 60 fps.

}

return 0;
}
[/source]

I'm doing it the "right way" as Trienco rightly pointed out: My glOrtho call creates a symmetric projection, and the zooming is done by simply scaling the projection matrix.

If you have any questions, feel free to ask.

Share this post


Link to post
Share on other sites
[quote name='Goran Milovanovic' timestamp='1346759708' post='4976391']
I'm doing it the "right way" as Trienco rightly pointed out: My glOrtho call creates a symmetric projection, and the zooming is done by simply scaling the projection matrix.
[/quote]

Actually no, because I pointed out that zooming doesn't belong in the modelview matrix at all. Scaling to zoom can make objects close to the camera suddenly disappear if they "grow" beyond the near clipping plane.

If you want to zoom the way an actual camera does it, you simply use this and stay away from the modelview matrix:

glOrtho( -X/scaleFactor, X/scaleFactor, -Y/scaleFactor, Y/scaleFactor, 1.0, -1.0 );

This will make sure that zooming while standing right in front of a wall doesn't turn into a wall hack.


As for rendering invisible geometry. OpenGL will render everything you throw at it, because it can't tell if something is visible without first transforming all vertices and performing clipping. That only saves you half the work (probably more, as rasterizing and shading are usually the expensive parts).

If you don't want something drawn, don't send it to OpenGL. Culling should be pretty simple when dealing with 2D tiles, even with zooming (it's just a factor after all).

Share this post


Link to post
Share on other sites
[quote name='Trienco' timestamp='1346817221' post='4976679']
Actually no, because I pointed out that zooming doesn't belong in the modelview matrix at all. Scaling to zoom can make objects close to the camera suddenly disappear if they "grow" beyond the near clipping plane.
[/quote]

Did you read my code?

I'm scaling the projection matrix, not the modelview matrix.

Share this post


Link to post
Share on other sites
[quote name='Trienco' timestamp='1346817221' post='4976679']
As for rendering invisible geometry. OpenGL will render everything you throw at it, because it can't tell if something is visible without first transforming all vertices and performing clipping. That only saves you half the work (probably more, as rasterizing and shading are usually the expensive parts).

If you don't want something drawn, don't send it to OpenGL. Culling should be pretty simple when dealing with 2D tiles, even with zooming (it's just a factor after all).
[/quote]

I was hoping that OpenGL had optimized ways of figuring out what not to bother rendering, but if I have to figure that out myself I can. Thanks. =D

Share this post


Link to post
Share on other sites
What I ended up doing which seems to work:

[CODE]
public void UpdateCamera(Vector controlInput, double elapsedTime)
{
double scaleOffset = (_scale - 1) * -96;
worldOffset += (controlInput * elapsedTime * 256);
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glLoadIdentity();
Gl.glOrtho(-640 + worldOffset.X + scaleOffset, 640 + worldOffset.X - scaleOffset, -360 + worldOffset.Y + scaleOffset, 360 + worldOffset.Y - scaleOffset, -100, 100);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glLoadIdentity();/* */
}
[/CODE]

This seems to work well. The worldOffset is a vector containing the an X and Y offset putting the camera over the proper place in the world. The scaleOffset is what does the scaling magic. The screen resolution is hard coded for testing purposes, and the hard coded 96 and 256 numbers come from the texture sizes and player movement speed, respectively. Have I done this completely wonky?

Share this post


Link to post
Share on other sites
[quote name='Goran Milovanovic' timestamp='1346819726' post='4976686']
Did you read my code?

I'm scaling the projection matrix, not the modelview matrix.
[/quote]

My bad, I was just quickly scrolling back up and saw your first post, where it was the model view.

[quote]
Have I done this completely wonky?
[/quote]

Well, I don't think adding a scale offset instead of multiplying with a scale factor makes much sense, especially since I would expect that to screw up your aspect ratio.

Let me dig out some old code..
[code]
void zoomBy( float factor )
{
scroll += ( factor < 1.f ? mousePos : Vector3(worldWindow.w/2, worldWindow.h/2,0) ) * invZoom*(1.f-factor);
invZoom *= factor;
}

void zoomIn() { zoomBy(1.25f); }
void zoomOut() { zoomBy(.8f); }

//When setting up the frame for rendering...

glViewport(worldWindow.x, worldWindow.y + (screenHeight-worldWindow.h), worldWindow.w, worldWindow.h);

glOrtho(scroll.x, scroll.x + (worldWindow.w*invZoom), scroll.y + (worldWindow.h*invZoom), scroll.y, -1000000, 1000000);
[/code]

This is probably confusing more than helping. It will always zoom towards the location of the mouse pointer, but when zooming out it uses the center of the view. I find it a lot more convenient than zooming towards the center of the screen and it even lets you quickly zip all over the map without actually having to scroll, just by zooming in and out.

It's also (ab)using the projection to scroll, though one could argue that the "camera position" should be in the modelview, not the projection. The reason is simply so I won't have to consider the current zoom factor when scrolling (you will want to scroll faster when zoomed out and slower when zoomed in).

Note that zooming back out would be easier if the projection was centered around the origin, so I wouldn't have to correct the scroll offset. But since I need a function that will zoom towards/from any arbitrary point anyway, it wouldn't really make much difference and I find it very tedious to think of the screen as going from -width/2 to +width/2 instead of 0 to width.

In this case the "worldWindow" is the area where the world is drawn. I set the viewport to leave out the area at the bottom where the GUI stuff will go. Unless you want to limit the viewport, you can pretend that worldWindow.x/worldWindow.y are 0 and worldWindow.w/worldWindow.h are your screen or window size. So basically looking at my old code it's not even required to have your view centered around the origin. Edited by Trienco

Share this post


Link to post
Share on other sites
[b]@Lance42[/b]

For your purposes, where you just want to zoom in and out (as a camera typically would), my example represents a simpler, and arguably more elegant solution.

You should really take the time to understand it. The key thing to note: I'm not making multiple calls to glOrtho. I just call it once to initialize the matrix, and the zoom is simply a scaling operation implemented via glScalef.

Ultimately, it's your decision, but I would prefer the zoom function that is both easier to implement, and easier to understand.

[b]@Trienco[/b]

Well, from what I understand, he's not trying to shift the zoom point.

However, if he wanted to do that, I would recommend doing it like this:

[code]
// Pseudo code

delta = mouse.worldPosition - camera.position
camera.position = mouse.worldPosition
camera.zoom(scale)
delta *= scale
camera.position -= delta
[/code]

I think that would be cleaner (no need to "dirty" the projection matrix with modelview data).

Share this post


Link to post
Share on other sites
Well, looking back I can now say that decision was made purely for the sake of simplicity and keeping things intuitive. This way the entire "view setup" is in the ortho projection by simply specifying the chunk of the world you want to see (in convenient world coordinates).

Doing it that way is more straight forward than creating a projection at a fixed location and then "shifting the world into place" and when dealing with numbers, I think the easier you can picture it, the better. Of course it doesn't transfer well to perspective projection, so if there are any plans to switch, it's better put in the modelview from the start.

In fact, the modelview isn't used at all, since for a simple 2D tile engine, immediate mode is more than fast enough, even when zooming out a lot. Trying to group tiles into chunks just to use vertex buffers would have been a waste of time, making things more complicated without any real benefit.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this