All I want to do is create a really simple pan and zoom feature in 2D with OpenGL through pyglet. As you can see, the zooming is working perfectly after the first jump:( Then again, the dragging (panning) is also working, but it also jumps (and it jumps a pretty big one)..
Here is my simplified code and a video (pyglet_test.mp4) that shows how it behaves:
import pyglet
from pyglet.gl import *
# Zooming constants
ZOOM_IN_FACTOR = 1.2
ZOOM_OUT_FACTOR = 1/ZOOM_IN_FACTOR
class App(pyglet.window.Window):
def __init__(self, width, height, *args, **kwargs):
# Create GL configuration
conf = Config( sample_buffers=1,
samples=4,
depth_size=16,
double_buffer=True )
# Initialize parent
super().__init__( width, height, config=conf, *args, **kwargs )
# Create Group
self.group = group = pyglet.graphics.Group()
# Create Batch
self.batch = batch = pyglet.graphics.Batch()
# Create QUAD for testing and add it to batch
batch.add(
4, GL_QUADS, group,
('v2i', ( -50, -50,
50, -50,
50, 50,
-50, 50 )),
('c3B', ( 255, 0, 0,
255, 255, 0,
0, 255, 0,
0, 0, 255 ))
)
# Initialize OpenGL
self.init_gl()
# Initialize camera values
self.camera_x = 0
self.camera_y = 0
self.camera_zoom = 1
def init_gl(self):
# Set clear color
glClearColor(0/255, 0/255, 0/255, 0/255)
# Set antialiasing
glEnable( GL_LINE_SMOOTH )
glEnable( GL_POLYGON_SMOOTH )
glHint( GL_LINE_SMOOTH_HINT, GL_NICEST )
# Set alpha blending
glEnable( GL_BLEND )
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )
# Set viewport
glViewport( 0, 0, self.width, self.height )
# Initialize Projection matrix
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
# Set orthographic projection matrix
glOrtho( 0, self.width, 0, self.height, 1, -1 )
# Initialize Modelview matrix
glMatrixMode( GL_MODELVIEW )
glLoadIdentity()
# Save the default modelview matrix
glPushMatrix()
def on_resize(self, width, height):
# Initialize OpenGL for new dimensions
self.width = width
self.height = height
self.init_gl()
def camera_matrix(transformations):
# Create camera setter function
def set_camera(self):
# Take saved matrix off the stack and reset it
glMatrixMode( GL_MODELVIEW )
glPopMatrix()
glLoadIdentity()
# Call wrapped function
transformations(self)
# Save default matrix again with camera translation
glPushMatrix()
# Return wrapper function
return set_camera
@camera_matrix
def move_camera(self):
# Move to camera position
glTranslatef( self.camera_x, self.camera_y, 0 )
# Scale camera
glScalef( self.camera_zoom, self.camera_zoom, 1 )
@camera_matrix
def zoom_camera(self):
# Move to camera position
glTranslatef( self.camera_x, self.camera_y, 0 )
# Scale camera
glScalef( self.camera_zoom, self.camera_zoom, 1 )
# Move back from camera position
glTranslatef( -self.camera_x, -self.camera_y, 0 )
def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
# Move camera
self.camera_x += dx
self.camera_y += dy
self.move_camera()
def on_mouse_scroll(self, x, y, dx, dy):
# Get scale factor
f = ZOOM_IN_FACTOR if dy < 0 else ZOOM_OUT_FACTOR if dy > 0 else 1
# If zoom_level is in the proper range
if .2 < self.camera_zoom*f < 5:
# Zoom camera
self.camera_x = x
self.camera_y = y
self.camera_zoom *= f
self.zoom_camera()
def on_draw(self):
# Clear window with ClearColor
glClear( GL_COLOR_BUFFER_BIT )
# Pop default matrix onto current matrix
glMatrixMode( GL_MODELVIEW )
glPopMatrix()
# Save default matrix again
glPushMatrix()
# Move to center of the screen
glTranslatef( self.width/2, self.height/2, 0 )
# Draw objects
self.batch.draw()
def run(self):
pyglet.app.run()
# Create instance of app and run it
App(500, 500).run()
After another day of suffering, I finally found a solution: in 2D the easiest way of doing mouse coordinate (pivot point) based zooming and right clicked-and-dragged panning without the jumps is to change the projection matrix with the glOrtho()
function.
Here is a simplified version of my original code -- if you are using Pyglet with seriuos amount of data, you should consider using Groups and Batches, but for the easier understanding I used the glBegin()
, glColor()
, glVertex()
, glEnd()
functions here to draw.
import pyglet
from pyglet.gl import *
# Zooming constants
ZOOM_IN_FACTOR = 1.2
ZOOM_OUT_FACTOR = 1/ZOOM_IN_FACTOR
class App(pyglet.window.Window):
def __init__(self, width, height, *args, **kwargs):
conf = Config(sample_buffers=1,
samples=4,
depth_size=16,
double_buffer=True)
super().__init__(width, height, config=conf, *args, **kwargs)
#Initialize camera values
self.left = 0
self.right = width
self.bottom = 0
self.top = height
self.zoom_level = 1
self.zoomed_width = width
self.zoomed_height = height
def init_gl(self, width, height):
# Set clear color
glClearColor(0/255, 0/255, 0/255, 0/255)
# Set antialiasing
glEnable( GL_LINE_SMOOTH )
glEnable( GL_POLYGON_SMOOTH )
glHint( GL_LINE_SMOOTH_HINT, GL_NICEST )
# Set alpha blending
glEnable( GL_BLEND )
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )
# Set viewport
glViewport( 0, 0, width, height )
def on_resize(self, width, height):
# Set window values
self.width = width
self.height = height
# Initialize OpenGL context
self.init_gl(width, height)
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
# Move camera
self.left -= dx*self.zoom_level
self.right -= dx*self.zoom_level
self.bottom -= dy*self.zoom_level
self.top -= dy*self.zoom_level
def on_mouse_scroll(self, x, y, dx, dy):
# Get scale factor
f = ZOOM_IN_FACTOR if dy > 0 else ZOOM_OUT_FACTOR if dy < 0 else 1
# If zoom_level is in the proper range
if .2 < self.zoom_level*f < 5:
self.zoom_level *= f
mouse_x = x/self.width
mouse_y = y/self.height
mouse_x_in_world = self.left + mouse_x*self.zoomed_width
mouse_y_in_world = self.bottom + mouse_y*self.zoomed_height
self.zoomed_width *= f
self.zoomed_height *= f
self.left = mouse_x_in_world - mouse_x*self.zoomed_width
self.right = mouse_x_in_world + (1 - mouse_x)*self.zoomed_width
self.bottom = mouse_y_in_world - mouse_y*self.zoomed_height
self.top = mouse_y_in_world + (1 - mouse_y)*self.zoomed_height
def on_draw(self):
# Initialize Projection matrix
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
# Initialize Modelview matrix
glMatrixMode( GL_MODELVIEW )
glLoadIdentity()
# Save the default modelview matrix
glPushMatrix()
# Clear window with ClearColor
glClear( GL_COLOR_BUFFER_BIT )
# Set orthographic projection matrix
glOrtho( self.left, self.right, self.bottom, self.top, 1, -1 )
# Draw quad
glBegin( GL_QUADS )
glColor3ub( 0xFF, 0, 0 )
glVertex2i( 10, 10 )
glColor3ub( 0xFF, 0xFF, 0 )
glVertex2i( 110, 10 )
glColor3ub( 0, 0xFF, 0 )
glVertex2i( 110, 110 )
glColor3ub( 0, 0, 0xFF )
glVertex2i( 10, 110 )
glEnd()
# Remove default modelview matrix
glPopMatrix()
def run(self):
pyglet.app.run()
App(500, 500).run()
Functions like glTranslatef
don't work absolutely. Instead, they move the "world" by the specified amount. So if you say glTranslatef(100,100)
you end up 100 units right and down from where you are right now, not at 100, 100
.
What they do in the background is modify the current view matrix. To make this work, you need to write the code like this:
glPushMatrix() # save the current matrix somewhere; gives you a new copy to modify
glTranslatef(100,100) # modify your copy;
# you need to do this *every time* before you draw anything
... draw ...
glPopMatrix() # undo all and any change you made to the matrix
来源:https://stackoverflow.com/questions/19428258/how-to-pan-and-zoom-properly-in-2d