OpenGL Google карты в стиле 2D камеры / масштабирования для курсора мыши

Я пытаюсь реализовать 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 проблемы:

  1. Положение мыши в SetScale неверно — я не знаю почему.
  2. Независимо от того, что я пытаюсь сделать, glOrtho заставляет центр экрана быть точкой увеличения, я подтвердил эту настройку точки увеличения вручную / жестко задал ее. В картах Google экран не будет «прилипать» к центру, как это.

Обновите снова:

Я также использую Qt, если это имеет какое-то значение, у меня просто есть базовый QGLWidget, и я использую событие колеса мыши для выполнения масштабирования. Я беру дельту события колеса и затем добавляю или вычитаю 0,1f к шкале, проходящей в положении мыши от события колеса.

4

Решение

  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;
}
5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]