OpenGL谷歌地图风格2D相机/缩放至鼠标光标

5
我正在尝试在OpenGL中实现一个2D相机,其行为类似于Google地图相机,特别是“缩放到鼠标点”的功能。
到目前为止,我已经成功实现了平移和缩放 - 但只有当缩放锁定在窗口/小部件的中心时才可以。如果我尝试在鼠标位置缩放,则视图似乎会“跳动”,并且在缩放级别增加后,我缩放的物品不再位于鼠标光标下方。
我的相机类如下 - 代码量很大,但很抱歉我无法使它更小!
在每个帧的开始处调用Apply(),当场景被平移时调用SetX/YPos,最后在鼠标滚轮滚动时使用以前的比例+/- 0.1f调用SetScale与鼠标位置。

camera.h

class Camera
{
public:
    Camera();

    void Apply();

    void SetXPos(float xpos);
    void SetYPos(float ypos);
    void SetScale(float scaleFactor, float mx, float my);

    float XPos() const { return m_XPos; }
    float YPos() const { return m_YPos; }
    float Scale() const { return m_ScaleFactor; }

    void SetWindowSize(int w, int h);
    void DrawTestItems();

private:
    void init_matrix();

    float m_XPos;
    float m_YPos;

    float m_ScaleFactor;

    float m_Width;
    float m_Height;

    float m_ZoomX;
    float m_ZoomY;
};

camera.cpp

Camera::Camera()
    : m_XPos(0.0f),
      m_YPos(0.0f),
      m_ScaleFactor(1.0f),
      m_ZoomX(0.0f),
      m_ZoomY(0.0f),
      m_Width(0.0f),
      m_Height(0.0f)
{

}

// Called when window is created and when window is resized
void Camera::SetWindowSize(int w, int h)
{
    m_Width = (float)w;
    m_Height = (float)h;
}

void Camera::init_matrix()
{
    glViewport(0, 0, m_Width, m_Height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    float new_W = m_Width * m_ScaleFactor;
    float new_H = m_Height * m_ScaleFactor;

    // Point to zoom on
    float new_x = m_ZoomX;
    float new_y = m_ZoomY;

    glOrtho( -new_W/2+new_x,
              new_W/2+new_x,
              new_H/2+new_y,
              -new_H/2+new_y,
             -1,1);


    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void Camera::Apply()
{
    // Zoom
    init_matrix();

    // Pan
    glTranslatef( m_XPos, m_YPos, 1.0f );

    DrawTestItems();
}

void Camera::SetXPos(float xpos)
{
    m_XPos = xpos;
}

void Camera::SetYPos(float ypos)
{
    m_YPos = ypos;
}

// mx,my = window coords of mouse pos when wheel was scrolled
// scale factor goes up or down by 0.1f
void Camera::SetScale(float scaleFactor, float mx, float my)
{

    m_ZoomX = (float)mx;
    m_ZoomY = (float)my;

    m_ScaleFactor = scaleFactor;

}

void Camera::DrawTestItems()
{

}

更新:我似乎已经注意到了两个问题:
  1. 在SetScale中,鼠标位置不正确-我不知道原因。
  2. 无论我尝试什么方法,glOrtho都会导致屏幕中心成为缩放点,我通过手动设置/硬编码确认了这一点。在Google地图中,屏幕不会像这样“粘”在中心。
再次更新:
我还使用Qt,如果这有任何区别的话,我只是有一个基本的QGLWidget,我使用鼠标滚轮事件来进行缩放,我获取滚轮事件的delta,然后根据鼠标位置添加或减去0.1f刻度。

如何使用鼠标滚轮实现“鼠标缩放”? - Domenico
1
在鼠标滚轮回调函数(来自Qt),我只需调用m_Camera.SetScale(m_Camera.Scale() + 0.1f,mouseWheelEvent->x(),mouseWheelEvent->y());(根据滚轮方向/增量,也可以使用-0.1f)。 - paulm
1
QWheelEvent文档在此处:http://qt-project.org/doc/qt-4.8/qwheelevent.html - paulm
1个回答

7
  1. 使用当前缩放系数和模型/投影/视图矩阵获取鼠标指针的世界空间坐标。
  2. 调整缩放系数。
  3. 再次使用新的缩放系数获取世界空间鼠标坐标。
  4. 通过世界空间鼠标坐标差异来移动相机位置。
  5. 使用新的相机位置和缩放系数重新绘制场景。

wheel()回调函数中,类似下面这样:

#include <GL/freeglut.h>

#include <iostream>
using namespace std;

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>


glm::dvec3 Unproject( const glm::dvec3& win )
{
    glm::ivec4 view;
    glm::dmat4 proj, model;
    glGetDoublev( GL_MODELVIEW_MATRIX, &model[0][0] );
    glGetDoublev( GL_PROJECTION_MATRIX, &proj[0][0] );
    glGetIntegerv( GL_VIEWPORT, &view[0] );

    glm::dvec3 world = glm::unProject( win, model, proj, view );
    return world;
}

// unprojects the given window point
// and finds the ray intersection with the Z=0 plane
glm::dvec2 PlaneUnproject( const glm::dvec2& win )
{
    glm::dvec3 world1 = Unproject( glm::dvec3( win, 0.01 ) );
    glm::dvec3 world2 = Unproject( glm::dvec3( win, 0.99 ) );

    // u is a value such that:
    // 0 = world1.z + u * ( world2.z - world1.z )
    double u = -world1.z / ( world2.z - world1.z );
    // clamp u to reasonable values
    if( u < 0 ) u = 0;
    if( u > 1 ) u = 1;

    return glm::dvec2( world1 + u * ( world2 - world1 ) );
}

// pixels per unit
const double ppu = 1.0;

glm::dvec2 center( 0 );
double scale = 1.0;
void ApplyCamera()
{
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    const double w = glutGet( GLUT_WINDOW_WIDTH ) / ppu;
    const double h = glutGet( GLUT_WINDOW_HEIGHT ) / ppu;
    glOrtho( -w/2, w/2, -h/2, h/2, -1, 1 );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glScaled( scale, scale, 1.0 );
    glTranslated( -center[0], -center[1], 0 );
}

glm::dvec2 mPos;

glm::dvec2 centerStart( 0 );
int btn = -1;

void mouse( int button, int state, int x, int y )
{
    ApplyCamera();

    y = glutGet( GLUT_WINDOW_HEIGHT ) - y;
    mPos = glm::ivec2( x, y );

    btn = button;
    if( GLUT_LEFT_BUTTON == btn && GLUT_DOWN == state )
    {
        centerStart = PlaneUnproject( glm::dvec2( x, y ) );
    }
    if( GLUT_LEFT_BUTTON == btn && GLUT_UP == state )
    {
        btn = -1;
    }

    glutPostRedisplay();
}

void motion( int x, int y )
{
    y = glutGet( GLUT_WINDOW_HEIGHT ) - y;
    mPos = glm::ivec2( x, y );

    if( GLUT_LEFT_BUTTON == btn )
    {
        ApplyCamera();
        glm::dvec2 cur = PlaneUnproject( glm::dvec2( x, y ) );
        center += ( centerStart - cur );
    }

    glutPostRedisplay();
}

void passiveMotion( int x, int y )
{
    y = glutGet( GLUT_WINDOW_HEIGHT ) - y;
    mPos = glm::ivec2( x, y );
    glutPostRedisplay();
}

void wheel( int wheel, int direction, int x, int y )
{
    y = glutGet( GLUT_WINDOW_HEIGHT ) - y;
    mPos = glm::ivec2( x, y );

    ApplyCamera();
    glm::dvec2 beforeZoom = PlaneUnproject( glm::dvec2( x, y ) );

    const double scaleFactor = 0.90;
    if( direction == -1 )   scale *= scaleFactor;
    if( direction ==  1 )   scale /= scaleFactor;

    ApplyCamera();
    glm::dvec2 afterZoom = PlaneUnproject( glm::dvec2( x, y ) );

    center += ( beforeZoom - afterZoom );

    glutPostRedisplay();
}

void display()
{
    glClearColor( 0, 0, 0, 1 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    ApplyCamera();

    glm::dvec2 cur = PlaneUnproject( mPos );
    cout << cur.x << " " << cur.y << " " << scale << endl;

    glPushMatrix();
    glScalef( 50, 50, 1 );
    glBegin( GL_QUADS );
    glColor3ub( 255, 255, 255 );
    glVertex2i( -1, -1 );
    glVertex2i(  1, -1 );
    glVertex2i(  1,  1 );
    glVertex2i( -1,  1 );
    glEnd();
    glPopMatrix();

    glutSwapBuffers();
}

int main( int argc, char **argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
    glutInitWindowSize( 600, 600 );
    glutCreateWindow( "GLUT" );

    glutMouseFunc( mouse );
    glutMotionFunc( motion );
    glutMouseWheelFunc( wheel );
    glutDisplayFunc( display );
    glutPassiveMotionFunc( passiveMotion );

    glutMainLoop();
    return 0;
}

这似乎不正确。我使用1单位=1像素,所以我将测试矩形更改为100而不是1,并将ppu更改为1而不是80。我发现,如果您将矩形的左上角平移到窗口的左上角,然后使用鼠标在矩形的右下角缩放,那么矩形/平移位置开始向上移动。而鼠标位置不再位于矩形的右下角? - paulm
不过看起来偏差大约20个单位,我本以为误差范围只有0.5-1呢? - paulm
另一个想法-比例尺从不超过10倍,因此远离100-200比例因子。 - paulm
我还有一个问题,希望你能知道。如果中心被缩放并且小部件上显示视口的滚动条,则如果矩形为800x200且视口为400x400,则如何确定滚动条的最大值?当使用比例时,似乎会出现错误。 - paulm
1
绝对完美,谢谢。 - cs.edoardo
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接