简体   繁体   English

OpenGL Google地图样式2D相机/缩放至鼠标光标

[英]OpenGL Google maps style 2D camera / zoom to mouse cursor

I am trying to implement a 2D camera in OpenGL that behaves like the Google maps camera. 我正在尝试在OpenGL中实现行为类似于Google Maps相机的2D相机。 Specifically the "zoom to mouse point" functionality. 特别是“缩放到鼠标点”功能。

So far I have been able to implement pan and zoom OK - but only if the zoom is locked to the center of the window/widget. 到目前为止,我已经能够实现“平移和缩放”确定-但仅在缩放锁定在窗口/小部件的中心时才可以。 If I try to zoom on the mouse location the view seems to "jump" and after the zoom level increases the item I zoomed in on is no longer under the mouse cursor. 如果我尝试缩放鼠标位置,则视图似乎“跳转”,并且在缩放级别增加后,我放大的项目不再位于鼠标光标下方。

My camera class is below - quite a lot of code but I couldn't make it any smaller sorry! 我的相机课在下面-很多代码,但是我不能让它变得更小抱歉!

I call Apply() on the start of each frame, and I call SetX/YPos when the scene is panned, finally I call SetScale with the previous scale +/- 0.1f with the mouse position when the mouse wheel is scrolled. 我在每个帧的开始处调用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.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()
{

}

Update: I seem to have noticed 2 issues: 更新:我似乎已经注意到2个问题:

  1. The mouse position in SetScale is incorrect - I don't know why. SetScale中的鼠标位置不正确-我不知道为什么。
  2. No matter what I try glOrtho causes the centre of the screen to be the zoom point,I confirmed this setting the zoom point manually/hard coding it. 无论我尝试glOrtho如何使屏幕的中心成为缩放点,我都确认手动设置了该缩放点/对其进行了硬编码。 In Google maps the screen won't "stick" to the centre like this. 在Google地图中,屏幕不会像这样“粘”在中心。

Update again: 再次更新:

I'm also using Qt if this makes any difference, I just have a basic QGLWidget and I am using the mouse wheel event to perform the zoom. 如果这有任何区别,我也使用Qt,我只有一个基本的QGLWidget,并且正在使用鼠标滚轮事件执行缩放。 I take the delta of the wheel event and then either add or subtract 0.1f to the scale passing in the mouse position from the wheel event. 我获取了转轮事件的增量,然后对从转轮事件传递到鼠标位置的比例添加或减去了0.1f。

  1. Get the world-space coordinates of the mouse cursor using the current zoom factor and model/proj/view matrices. 使用当前缩放系数和模型/项目/视图矩阵获取鼠标光标的世界空间坐标。
  2. Adjust zoom factor 调整缩放系数
  3. Get the world-space mouse coordinates again using the new zoom factor 使用新的缩放系数再次获取世界空间鼠标坐标
  4. Shift the camera position by the difference in world-space mouse coordinates 通过世界空间鼠标坐标中的差异移动相机位置
  5. Redraw scene using new camera position and zoom factor 使用新的相机位置和缩放系数重绘场景

Something like this (in the wheel() callback): 像这样(在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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM