OpenGL: How can I move a 2d object without displacing the whole scene?

16,608

Solution 1

GL_MODELVIEW is what you need.

From the OpenGL FAQ, 2.1: http://www.opengl.org/resources/faq/technical/gettingstarted.htm

program_entrypoint
{
    // Determine which depth or pixel format should be used.
    // Create a window with the desired format.
    // Create a rendering context and make it current with the window.
    // Set up initial OpenGL state.
    // Set up callback routines for window resize and window refresh.
}

handle_resize
{
    glViewport(...);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // Set projection transform with glOrtho, glFrustum, gluOrtho2D, gluPerspective, etc.
}

handle_refresh
{
    glClear(...);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // Set view transform with gluLookAt or equivalent

    // For each object (i) in the scene that needs to be rendered:
        // Push relevant stacks, e.g., glPushMatrix, glPushAttrib.
        // Set OpenGL state specific to object (i).
        // Set model transform for object (i) using glTranslatef, glScalef, glRotatef, and/or equivalent.
        // Issue rendering commands for object (i).
        // Pop relevant stacks, (e.g., glPopMatrix, glPopAttrib.)
    // End for loop.

    // Swap buffers.
}

Solution 2

You answer your own question, that is the solution:

glPushMatrix();
glTranslatef(xOffset,yOffset,0);
glBegin(GL_POLYGON);
    glColor3f(theColor.red, theColor.green, theColor.blue);
    glVertex2f(p1.x, p1.y);
    glVertex2f(p2.x, p2.y);
    glVertex2f(p3.x, p3.y);
glEnd();
glPopMatrix();

That will change the modelview matrix while the rectangle is drawn, then it will revert the modelview matrix back to what it were before. Did you actualy tried that? What whent wrong?

Solution 3

If I'm reading your code right, you want to only rotate one element right? If so, do this:

  • Call glPushMatrix();
  • then do your rotation
  • Store how much you've rotated
  • then draw your rotated item
  • then call glPopMatrix();

That will only rotate the one object.

EDIT:

I see that doing that "destroys" the previous rotation. Could you elaborate? That is the correct way to translate/rotate one object.

I also notice that you aren't initializing the Modelview Matrix. You should initialize the Modelview Matrix after you setup your PROJECTION matrix. You also need to make sure that you are initializing both matrices to the identity. And finally, make sure that you are initializing both matrices EVERY time the screen refreshes. To test this, set a breakpoint on your matrix initialization and see if it gets hit only once or every frame.

Share:
16,608
datguywhowanders
Author by

datguywhowanders

Updated on July 27, 2022

Comments

  • datguywhowanders
    datguywhowanders almost 2 years

    Alright, I'm trying to recreate the old classic, Missile Command, using OpenGL in C++. This is my first foray into OpenGL, although I feel fairly comfortable with C++ at this point.

    I figured my first task was to figure out how to move 2d objects around the screen, seemed like it would be fairly simple. I created two quick method calls to make either triangles or quads:

    void makeTriangle(color3f theColor, vertex2f &p1, vertex2f &p2, vertex2f &p3,
                      int &xOffset, int &yOffset)
    {
        //a triangle
        glBegin(GL_POLYGON);
            glColor3f(theColor.red, theColor.green, theColor.blue);
            glVertex2f(p1.x, p1.y);
            glVertex2f(p2.x, p2.y);
            glVertex2f(p3.x, p3.y);
        glEnd();
    }
    
    void makeQuad(color3f theColor, vertex2f &p1, vertex2f &p2, vertex2f &p3,
                  vertex2f &p4, int &xOffset, int &yOffset)
    {
        //a rectangle
        glBegin(GL_POLYGON);
            glColor3f(theColor.red, theColor.green, theColor.blue);
            glVertex2f(p1.x, p1.y);
            glVertex2f(p2.x, p2.y);
            glVertex2f(p3.x, p3.y);
            glVertex2f(p4.x, p4.y);
        glEnd();
    }
    

    color3f and vertex2f are simple classes:

    class vertex2f
    {
    public:
        float x, y;
    
        vertex2f(float a, float b){x=a; y=b;}
    };
    
    class color3f
    {
    public:
        float red, green, blue;
    
        color3f(float a, float b, float c){red=a; green=b; blue=c;}
    };
    

    And here is my main file:

    #include <iostream>
    #include "Shapes.hpp"
    
    using namespace std;
    
    int xOffset = 0, yOffset = 0;
    bool done = false;
    
    void keyboard(unsigned char key, int x, int y)
    {
        if( key == 'q' || key == 'Q')
            {
                exit(0);
                done = true;
            }
    
            if( key == 'a' )
                xOffset = -10;
    
            if( key == 'd' )
                xOffset = 10;
    
            if( key == 's' )
                yOffset = -10;
    
            if( key == 'w' )
                yOffset = 10;
    
    }
    
    
    void init(void)
    {
        //Set color of display window to white
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    
        //Set parameters for world-coordiante clipping window
        glMatrixMode(GL_PROJECTION);
        gluOrtho2D(-400.0,400.0,-300.0,300.0);
    
    }
    
    
    void display(void)
    {
        glClear(GL_COLOR_BUFFER_BIT);
    
        color3f aGreen(0.0, 1.0, 0.0);
        vertex2f pa(-400,-200);
        vertex2f pb(-400,-300);
        vertex2f pc(400,-300);
        vertex2f pd(400,-200);
    
        makeQuad(aGreen,pa,pb,pc,pd,xOffset,yOffset);
    
        color3f aRed(1.0, 0.0, 0.0);
        vertex2f p1(-50.0,-25.0);
        vertex2f p2(50.0,-25.0);
        vertex2f p3(0.0,50.0);
    
        makeTriangle(aRed,p1,p2,p3,xOffset,yOffset);
    
        glFlush();
    }
    
    
    int main(int argc, char** argv)
    {
        // Create Window.
        glutInit(&argc, argv);
        glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
        glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
        glutCreateWindow("test");
    
        // Some initialization.
        init();
    
        while(!done)
        {
        //display functions
        glutDisplayFunc(display);
        glutKeyboardFunc(keyboard);
    
        // Start event loop.
        glutMainLoop();
        }
    
        return 0;
    }
    

    A quad is defined as the "background" for the time being and consists of just a green rectangle along the bottom of the screen. The red triangle is the "object" that I wish to move. On a keypress, an offset is saved in the direction indicated.

    I've tried using glTranslatef(xOffset,yOffset,0); but the problem with that is that it moves both elements on the screen and not just the red triangle. I attempted to put the whole call to draw the triangle between a push and pop matrix operation:

    PushMatrix();
    glTranslatef(xOffset,yOffset,0);
    glBegin(GL_POLYGON);
        glColor3f(theColor.red, theColor.green, theColor.blue);
        glVertex2f(p1.x, p1.y);
        glVertex2f(p2.x, p2.y);
        glVertex2f(p3.x, p3.y);
    glEnd();
    PopMatrix();
    

    As far as I can tell, that destroys any changes that the translation was doing beforehand. I've also tried just changing the values of the x and y coordinates before calling the draw, but that just causes a brief flicker before leaving the triangle in its original position:

    p1.x += xOffset;
    p2.x += xOffset;
    p3.x += xOffset;
    
    p1.y += yOffset;
    p2.y += yOffset;
    p3.y += yOffset;
    

    There has to be a nice simple way of doing this, and I'm just overlooking it. Could someone offer a suggestion please?

    EDIT:

    My actual problem was that I was never refreshing the screen after an initial draw. What I needed was to specify an idle function inside my main loop:

    glutIdleFunc(IdleFunc);
    

    Where the actual IdleFunc looks like:

    GLvoid IdleFunc(GLvoid)
    {
        glutPostRedisplay();
    }
    

    Instead of using glFlush() inside my draw function, I should have been using glutSwapBuffers(). By doing that, the code I had first come up with:

    p1.x += xOffset;
    p2.x += xOffset;
    p3.x += xOffset;
    
    p1.y += yOffset;
    p2.y += yOffset;
    p3.y += yOffset;
    

    Works fine for my purposes. I didn't have a need to translate the matrix, I just needed to draw the element in a different position from one scene to the next.

  • joe_coolish
    joe_coolish about 13 years
    +1 Exactly. This is probably where the problem is. I didn't see any Modelview matrix initialization in the code. Also, I see that holtavolt has put the matrix initialization in the openGL loop, so it is initialized every frame.
  • joe_coolish
    joe_coolish about 13 years
    +1, my thoughts exactly. Could you elaborate more on what doesn't work with the code above?
  • datguywhowanders
    datguywhowanders about 13 years
    Doing this the way I had it resulted in no transformation taking place. After reviewing joe_coolish and holtavolt's answers below, I believe my error lies in not initializing a Modelview Matrix. However, I'm still struggling with the correct way to do that and where to insert it into my code.
  • datguywhowanders
    datguywhowanders about 13 years
    I didn't find the documentation on the opengl website very helpful. I don't understand what most of those comments mean, and a google search of most of them wasn't very enlightening. I know that the answer to my question lies with the Modelview Matrix and the Push and Pop matrix operations, but I can't seem to resolve where they go in my original solution or if I need to toss the whole thing out and start from scratch.
  • holtavolt
    holtavolt about 13 years
    OK - you might want to start with this NeHe OpenGL tutorial that rotates two objects independently to get the form down: nehe.gamedev.net/data/lessons/lesson.asp?lesson=04