Making a working "camera" in a 2D orthogonal environment

Started by CodeBunny, October 17, 2010, 14:39:27

Previous topic - Next topic

CodeBunny

I'm currently working on a 2D engine and I'm trying to figure out how to use a camera object. I get how to pan about the game world using glTranslated() but I'm trying to add support for zoom (a linear scale of the gameworld to have more objects appear on the screen) and rotation (clockwise and counterclockwise spin of the display around the viewpoint).

I understand how to use a "brute force" method (calculating appropriate scales and locations for every object) but I've been trying to figure out how to use built-in OpenGL functionality. glFrustum() and gluLookAt() seem like they should do the trick, but I haven't been able to make them work.

This is how I setup the display:



        GL11.glDisable(GL11.GL_DEPTH_TEST);
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glPushMatrix();
        GL11.glLoadIdentity();
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        GL11.glDisable(GL11.GL_LIGHTING);
        GL11.glOrtho(0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight(), 0, 1, -1);
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glPushMatrix();
        GL11.glLoadIdentity();


Here's an outline of how my render code works:


      GL11.glMatrixMode(GL11.GL_MODELVIEW);
      GL11.glLoadIdentity();

      draw(); //Code that renders textured quads for all game objects, this is working.

      GL11.glMatrixMode(GL11.GL_PROJECTION_MATRIX);
      GL11.glLoadIdentity();
      GL11.glFrustum(left, right, bottom, top, -1, 1); // left/right/bottom/top are the dimension edges "scaled" by the camera zoom
      Project.gluLookAt((float) (c.getX()), (float) (c.getY()), 0, (float) (c.getX()), (float) (c.getY()), 1f, 0, 1f, 0);

     GL11.glPopMatrix();
      Display.update();

I haven't tried to incorporate rotation into this example. I wanted to have translate and zoom working first.

The camera object is pretty simple, it holds a series of doubles that correspond to the x, y, zoom, and rotation.

jediTofu

I can't really tell from your code what's incorrect (as I don't know how you initialized the viewport, etc. and what the values of those variables are).  What happens when you try this?  The only thing that looks wrong to me is gluLookAt:

gluLookAt(
(float) (c.getX()), (float) (c.getY()), 0,
0,  0, 0, //probably want all zeros here, as this is supposed to correspond to the center of your screen
0, 1f, 0);

Have a look at the OpenGL FAQ on this:  http://www.opengl.org/resources/faq/technical/viewing.htm

Also, a good way to figure out exactly what everything is doing is to allow for input to change these parameters around, just for debugging.  So:

Keys:
W = top
A = left
D = right
S = bottom

F = -c.getX()
H = +c.getX()
T = +c.getY()
G = -c.getY()

etc.
cool story, bro

CodeBunny

Thanks for responding!  :)

I'll update the original post with the setup code.

After that, the game operates in a loop, constantly updating the logic and redrawing the screen every 60th of a second. I posted the render call below.

When I try to run this in a test program, with key input modifying camera variables, nothing happens. The game continues normally but the viewpoint doesn't move or zoom.

I didn't know that's how you were supposed to use gluLookAt - I thought you supplied a second point for it to point towards. I've updated the call appropriately.

Fool Running

The only thing that looks wrong to me is that you are setting up your camera after you are drawing and then poping the projection matrix when you are done. This keeps the world from being affected by the camera's values.
Try putting the camera code before you switch to the model view matrix and removing the glPopMatrix call:
     GL11.glMatrixMode(GL11.GL_PROJECTION_MATRIX);
      GL11.glLoadIdentity();
      GL11.glFrustum(left, right, bottom, top, -1, 1); // left/right/bottom/top are the dimension edges "scaled" by the camera zoom
      Project.gluLookAt((float) (c.getX()), (float) (c.getY()), 0, (float) (c.getX()), (float) (c.getY()), 1f, 0, 1f, 0);

      GL11.glMatrixMode(GL11.GL_MODELVIEW);
      GL11.glLoadIdentity();

      draw(); //Code that renders textured quads for all game objects, this is working.

      Display.update();
Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D

jediTofu

Quote from: Fool Running on October 18, 2010, 12:48:25
...poping the projection matrix when you are done...
Wow, completely missed that for some reason lol.  That's gotta be the problem though.
cool story, bro

CodeBunny


CodeBunny

I've updated my code based on what you've said.

Unfortunately, it's still not working (probably because I'm being dumb somewhere), so I think I'll post everything involved:

60 times per second, the render() method is called.

        public void render()
        {
                GL11.glMatrixMode(GL11.GL_PROJECTION_MATRIX);
                GL11.glLoadIdentity();
                setUpView();

                GL11.glMatrixMode(GL11.GL_MODELVIEW);
                GL11.glLoadIdentity();
                draw();
      
                Display.update();
        }

        private void setUpView()
        {
                Camera c = DataManager.getCurrentCamera();
                Project.gluLookAt((float) (c.getX()), (float) (c.getY()), 0, (float) (c.getX()), (float) (c.getY()), 1f, 0, 1f, 0);
        }

        private void draw() //This code is all working,
        {
                DataManager.getCurrentWorld().drawBackground(); // Draws over the last frame (why I don't bother clearing the ColorBuffer) and replaces it with
                                                                                                   // a solid color or an image.
      
                ArrayList<RenderSprite> images = DataManager.getCurrentWorld().getRenderList();
                for(RenderSprite i:images)
                {
                        if(spriteIsOnscreen(i))
                        {
                                i.render();
                        }
                }
        }


Then the render function for the individual objects goes like:

        getColor().bind();
        skin.bind();
                GL11.glTranslated(x, y, 0);
                GL11.glRotatef((float) (rotation*1f), 0, 0, 1f);
                GL11.glBegin(GL11.GL_QUADS);
                        // Maps the quad and the texture on it.
                GL11.glEnd();
        GL11.glLoadIdentity();

jediTofu

Quote from: CodeBunny on October 18, 2010, 13:32:34
What exactly does glPopMatrix() do?

OpenGL has a Stack where you can store the current states of matrices.  So, when you do glPushMatrix, it pushes the current state on the stack, like all your glTranslate's, gluLookAt, etc.  Then you do a bunch of more stuff, glTranslatef, etc...blah blah blah...then when you call glPopMatrix, it goes back to how things were the last time you called glPushMatrix.  Then if you call glPopMatrix again, it goes to how things were the glPushMatrix before that, etc.  It's like an OpenGL Undo.

Coincidentally, there was a recent thread about it (but relating to speed): http://lwjgl.org/forum/index.php/topic,3504.0.html

So essentially, when you're calling glPopMatrix in your code provided, it Undos everything you have done for that matrix (in your case, GL_PROJECTION_MATRIX) up until the last time you called glPushMatrix, or if you never called glPushMatrix, I believe it will return to the identity matrix.
cool story, bro

CodeBunny


ryanm

Quote from: jediTofu on October 18, 2010, 13:50:42
...or if you never called glPushMatrix, I believe it will return to the identity matrix.

Nope, it'll cause a GL_STACK_UNDERFLOW error and leave the current matrix unchanged

jediTofu

Well, I was going to wait for a more experienced programmer to come along to answer the camera issue, but since no other solutions have been given...

This is what I have gathered from testing and researching:

Note:  HEIGHT & WIDTH correspond to your Display Mode's height and width.

(1)
//You put GL_PROJECTION_MATRIX (which won't work); I think you meant GL_PROJECTION
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//1 = zNear (this can not be 0, as there is division in how glFrustum works)
//2 = zFar
//These parameters are supposed to only be positive (glFrustum converts them to negative values)
//Anything between zNear and zFar will be drawn (if zNear and zFar are both 1, nothing will be drawn)
glFrustum(0, WIDTH, HEIGHT, 0, 1,2);

//-1.0f = z
//Any z value between -1.0f and -2.0f will be drawn
glTranslatef(0.0f,0.0f,-1.0f);

//Rest of your drawing code

This works for me and draws, but as for zooming...I tried both these ways:

float zoom = 1.0f; //instance variables
glFrustum(-WIDTH * zoom,WIDTH * zoom,HEIGHT * zoom,-HEIGHT * zoom,1,2);
//and tried this way...
float zoom = 0.0f;
glFrustum(WIDTH - zoom,WIDHT + zoom,HEIGHT + zoom,HEIGHT - zoom,1,2);

They seemed to zoom, but I couldn't figure out how to position the left and bottom correctly.  It would never center (except on no zoom).


(2)
I read that for 2D games and using glOrtho, just calling glScaled or glScalef before you draw everything should be sufficient.  I couldn't get this to work though.


(3)
You're supposed to also be able to do this with glOrtho and is sufficient for 2D games.  Again, I tried this way (same with glFrustum) using the values in your initialization, and I couldn't get the values of left and bottom to center everything correctly.


Anyway, I hope this helps you out and someone else replies to enlighten us on this issue!  In my testing, I was just trying to zoom in and out on a rectangle.  If there are no further replies, you can try http://www.javagaming.org/.  If you figure out a solution (that zooms in while keeping the camera centered), please let me know by PM or here!
cool story, bro

broumbroum

Hi there,
about scaling :
2) is the correct manner, because it involves MODEL_VIEW matrix, which is meant for transforming the rendered objects (models).
I'd not recommend to re-scale PROJECTION with the prj-matrix, because it's not that what it is meant for. It should rather stand in the display rescale() callback.

if using a glortho, then a scaling factor is necessary. But with glfrustum or glproj, you can simply "Pull" the objects to the front with the z-coord (usually adding +n) as the existing projection matrix is transforming all user coords to device coords.

camera simulation : everything would occur into the model_view matrix transforms (gltranslate, glrotate).

jediTofu

Quote from: broumbroum on October 18, 2010, 23:39:19
Hi there,
about scaling :
2) is the correct manner, because it involves MODEL_VIEW matrix, which is meant for transforming the rendered objects (models).
I'd not recommend to re-scale PROJECTION with the prj-matrix, because it's not that what it is meant for. It should rather stand in the display rescale() callback.

if using a glortho, then a scaling factor is necessary. But with glfrustum or glproj, you can simply "Pull" the objects to the front with the z-coord (usually adding +n) as the existing projection matrix is transforming all user coords to device coords.

camera simulation : everything would occur into the model_view matrix transforms (gltranslate, glrotate).

Awesome, I actually got glFrustum working, but I got glScalef working also.  However, I had to do some funky mathematics in order to center the camera; how do you suggest going about centering after the zoom?

This is what my code looks like and works; I just don't want to do all of this funky mathematics in glTranslatef:

  private float zoom = 1.0f;

  public void render() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glScalef(zoom,zoom,zoom); //sounds like a Mazda commercial
    glTranslatef(-(WIDTH - WIDTH / zoom) / 2.0f,-(HEIGHT - HEIGHT / zoom) / 2.0f,0.0f); //centers the screen; how do I get rid of this?
    glPushMatrix();
    drawSquare();
    glPopMatrix();
    //more drawing

    try{Thread.sleep(50);}catch(Exception ex){} //to slow keyboard input
  }

  public void update() {
    if(Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
      zoom += 0.1f;
    }
    if(Keyboard.isKeyDown(Keyboard.KEY_UP)) {
      zoom -= 0.1f;
    }
    if(zoom < 0.0f) {
      zoom = 0.0f;
    }
  }



Sorry CodeBunny, not trying to take over your thread!  I'm just really interested in this as well, as I would like this feature also, and I thought that it'd be easier to do.
cool story, bro

CodeBunny

Dude, thanks so much for the help! I'm really glad there's been so much help available on the forum.

It works now, the only problem is with this custom culling code I have and I can figure that one out on my own. :D

Cheers, everyone.

jediTofu

Quote from: CodeBunny on October 19, 2010, 11:17:48
Dude, thanks so much for the help! I'm really glad there's been so much help available on the forum.

It works now, the only problem is with this custom culling code I have and I can figure that one out on my own. :D

Cheers, everyone.

How did you end up doing it exactly?

This is what I finally settled on, not sure if it's the best way:

    float centerX = WIDTH * 0.5f;
    float centerY = HEIGHT * 0.5f;
    glTranslatef(centerX,centerY,0);   //Move to center of scene
    glScalef(zoom,zoom,zoom);          //Scale at center
    glTranslatef(-centerX,-centerY,0); //Move back to origin

    for(int i = 0; i < 100; i++) {
      glPushMatrix(); //Store current state
      drawSquare();
      glPopMatrix(); //Undo any drawing changes
    }

cool story, bro