Я пытаюсь реализовать 2D-камеру в OpenGL, которая ведет себя как камера Google Maps. В частности, функциональность «масштабирование до точки мыши».
До сих пор я мог осуществлять панорамирование и масштабирование в порядке — но только если масштабирование зафиксировано в центре окна / виджета. Если я пытаюсь увеличить местоположение мыши, вид, кажется, «прыгает», и после увеличения уровня масштабирования элемент, на котором я увеличил изображение, больше не находится под курсором мыши.
Мой класс камеры ниже — довольно много кода, но я не мог сделать его меньше, извините!
Я звоню Apply()
в начале каждого кадра, и я звоню SetX/YPos
когда сцена панорамируется, наконец, я звоню SetScale
с предыдущей шкалой +/- 0.1f
с позицией мыши, когда колесо мыши прокручивается.
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 проблемы:
Обновите снова:
Я также использую Qt, если это имеет какое-то значение, у меня просто есть базовый QGLWidget, и я использую событие колеса мыши для выполнения масштабирования. Я беру дельту события колеса и затем добавляю или вычитаю 0,1f к шкале, проходящей в положении мыши от события колеса.
Как то так (в 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;
}
Других решений пока нет …