OpenGL 谷歌地图风格的 2D 相机/缩放到鼠标光标
我正在尝试在 OpenGL 中实现一个 2D 相机,其行为类似于 Google 地图相机.特别是缩放到鼠标点"功能.
I am trying to implement a 2D camera in OpenGL that behaves like the Google maps camera. Specifically the "zoom to mouse point" functionality.
到目前为止,我已经能够实现平移和缩放 OK - 但前提是缩放锁定到窗口/小部件的中心.如果我尝试放大鼠标位置,视图似乎会跳跃",并且在缩放级别增加后,我放大的项目不再位于鼠标光标下.
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!
我在每一帧开始时调用Apply()
,当场景平移时我调用SetX/YPos
,最后我调用SetScale
与前一个比例 +/- 0.1f
与鼠标滚轮滚动时的鼠标位置.
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.
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()
{
}
更新:我似乎注意到了 2 个问题:
Update: I seem to have noticed 2 issues:
- SetScale 中的鼠标位置不正确 - 我不知道为什么.
- 无论我尝试什么 glOrtho 都会使屏幕中心成为缩放点,我确认了此设置手动/硬编码缩放点.在 Google 地图中,屏幕不会像这样粘"在中心.
再次更新:
如果这有什么不同,我也在使用 Qt,我只有一个基本的 QGLWidget,我正在使用鼠标滚轮事件来执行缩放.我获取滚轮事件的增量,然后将 0.1f 添加或减去从滚轮事件传入鼠标位置的比例.
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. 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.
推荐答案
- 使用当前缩放系数和模型/项目/视图矩阵获取鼠标光标的世界空间坐标.
- 调整缩放系数
- 使用新的缩放系数再次获取世界空间鼠标坐标
- 根据世界空间鼠标坐标的差异移动相机位置
- 使用新的相机位置和缩放系数重绘场景
类似这样的东西(在 wheel()
回调中):
Something like this (in the wheel()
callback):
#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;
}
相关文章