c++ - OpenGL 谷歌地图样式 2D 相机/缩放到鼠标光标

标签 c++ opengl camera geometry zooming

我正在尝试在 OpenGL 中实现一个 2D 相机,它的行为类似于 Google map 相机。特别是“缩放到鼠标点”功能。

到目前为止,我已经能够实现平移和缩放 OK - 但前提是缩放锁定在窗口/小部件的中心。如果我尝试缩放鼠标位置, View 似乎会“跳跃”,并且在缩放级别增加后,我放大的项目不再位于鼠标光标下。

下面是我的相机类 - 代码很多,但我无法将其缩小,抱歉!

我在每一帧的开始调用Apply(),当场景平移时我调用SetX/YPos,最后我调用SetScale 与先前的比例 +/- 0.1f 与鼠标滚轮滚动时的鼠标位置。


相机.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;
};

相机.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 map 中,屏幕不会像这样“粘”在中心。

再次更新:

如果这有什么不同的话,我也在使用 Qt,我只有一个基本的 QGLWidget,我正在使用鼠标滚轮事件来执行缩放。我获取滚轮事件的增量,然后将 0.1f 添加或减去从滚轮事件传递到鼠标位置的比例。

最佳答案

  1. 使用当前缩放因子和模型/投影/ View 矩阵获取鼠标光标的世界空间坐标。
  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;
}

关于c++ - OpenGL 谷歌地图样式 2D 相机/缩放到鼠标光标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21561724/

相关文章:

c++ - 复制或移动 FrameBufferObject

swift - 如何在 ARKit 中获取相机的前向 (LookAt) 向量?

c++ - 如何加速我的内存扫描程序?

c++ - 来自字符串的 cv::VideoCapture 在 Mac 上的 Qt5 中给出链接器错误

c++ - 找到数字数组的所有可能的解释

java - 如何使用网络摄像头扫描条形码?

ios - 错误 : Attempt to present <UIImagePickerController: on <ViewController: while a presentation is in progress

c++ - 如何在托管 C++ 中正确实现返回 "this"的类的方法?

c++ - OpenGL-如何随机绘制不重叠的圆

opengl - GLSL:无法获得制服位置