Re: 2d zooming and panning

1,040 views
Skip to first unread message

Nathan

unread,
Jul 24, 2012, 11:33:48 PM7/24/12
to pyglet...@googlegroups.com
On Sun, Jul 22, 2012 at 1:12 PM, Derek S. <univers...@live.com> wrote:
> I have made a basic straightedge-and-compass geometry program.
> Point objects are represented by GL_POINTS
> Lines by GL_LINES
> Circles by GL_LINE_LOOP
>
> I've searched about how zooming and panning can be accomplished,
> and have found talk about glMatrixMode, glScale, glTranslate, glOrtho,
> etc...
>
> All of the examples / tutorials I've found deal with C++ and 3D,
> and none of them seem to actually setup a matrix,
> yet they all talk about how zooming/panning is accomplished by
> transforming a matrix..
>
> I have never studied matrices so I'm hoping to speed up the learning curve
> by seeing an example of:
>
> initial matrix
> [0][0][0][0]
> [0][0][0][0]
> [0][0][0][0]
> [0][0][0][0]
>
> zoom
>
> changed matrix
> [0][0][0][0]
> [0][0][0][0]
> [0][0][0][0]
> [0][0][0][0]
>
> I'm also wondering if numpy will be of benefit in this situation.
> I'm already using it for vectors.
>
> If you can provide an example that will help me figure this out
> I would be most grateful! At the very least I would appreciate
> a list of the functions you would recommend using for this.
>
> Thanks!
> (I'm using pyglet 1.1.4)


Here's the code I have in a 2d sidescroller prototype I have. I know
why you are asking -- it took me awhile to work out how to do this.

Assuming that you have a window of width VIEWPORT_WIDTH and height
VIEWPORT_HEIGHT you can scroll the screen so the corner is at
(self.viewportx, self.viewporty) with the following code (this is a
function on a subclass of window)

def scroll(self):
glViewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT)
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
glOrtho(self.viewportx, self.viewportx+VIEWPORT_WIDTH,
self.viewporty, self.viewporty+VIEWPORT_HEIGHT, -1, 1)
glMatrixMode(gl.GL_MODELVIEW)

If anyone knows of an even better way to do that, I'm all ears.

~ Nathan

Greg Ewing

unread,
Jul 25, 2012, 12:13:01 AM7/25/12
to pyglet...@googlegroups.com
> On Sun, Jul 22, 2012 at 1:12 PM, Derek S.<univers...@live.com> wrote:
>> All of the examples / tutorials I've found deal with C++ and 3D,
>> and none of them seem to actually setup a matrix,
>> yet they all talk about how zooming/panning is accomplished by
>> transforming a matrix..

If you use glTranslate, glScale, etc., OpenGL hides the actual
matrices from you, so you don't have to think about them.

That's okay if all you want to do is output, but for a drawing
program you'll probably want to transform mouse locations back
into your world coordinate system, which means you'll need to
know how to do it yourself.

You should be able to find heaps of tutorial info about
using matrices for linear transformations. Here's a start:

http://en.wikipedia.org/wiki/Transformation_matrix

--
Greg

Tristam MacDonald

unread,
Jul 25, 2012, 12:26:07 AM7/25/12
to pyglet...@googlegroups.com
On Wed, Jul 25, 2012 at 12:13 AM, Greg Ewing <greg....@canterbury.ac.nz> wrote:
That's okay if all you want to do is output, but for a drawing
program you'll probably want to transform mouse locations back
into your world coordinate system, which means you'll need to
know how to do it yourself.

While you probably *should* be doing this yourself (especially in the post-immediate-mode era), legacy OpenGL will actually handle this for you too. Look into the gluProject/gluUnProject functions if you are interested.

--
Tristam MacDonald

Greg Ewing

unread,
Jul 25, 2012, 12:54:36 AM7/25/12
to pyglet...@googlegroups.com
On 25/07/12 16:26, Tristam MacDonald wrote:
> While you probably *should* be doing this yourself (especially in the
> post-immediate-mode era), legacy OpenGL will actually handle this for you too.
> Look into the gluProject/gluUnProject functions if you are interested.

That's true -- I'd forgotten about those.

However, there are often other reasons for doing it yourself as well.
For example, doing physics calculations in a game, or manipulating
objects in a drawing program. In any case, it's good to understand
what's going on, even if you use libraries that handle most of it
for you.

--
Greg

Martin Di Paola

unread,
Jul 25, 2012, 8:38:38 AM7/25/12
to pyglet...@googlegroups.com
You can see this links.  This can help you if you want to learn something about the openGL matrix, the transformations, etc. However, the link is a book, so, you should read this with calm. If you need some feedback "right now", you may want to read directly the Chapter 3 and the Appendix F.

http://www.glprogramming.com/red/

Bye!


El domingo, 22 de julio de 2012 16:12:11 UTC-3, Derek S. escribió:
I have made a basic straightedge-and-compass geometry program.
Point objects are represented by GL_POINTS
Lines by GL_LINES
Circles by GL_LINE_LOOP

I've searched about how zooming and panning can be accomplished,
and have found talk about glMatrixMode, glScale, glTranslate, glOrtho, etc...

All of the examples / tutorials I've found deal with C++ and 3D,
and none of them seem to actually setup a matrix,
yet they all talk about how zooming/panning is accomplished by
transforming a matrix..

Txema Vicente

unread,
Jul 25, 2012, 12:44:33 PM7/25/12
to pyglet...@googlegroups.com
I have some code that may help you, the math is in there.

This is for a hierarchy of planes, and all matrices are calculated
"manually".

_tx, _ty, _tz : Translation
_scale: (XY axis)
_rotation: (Z axis)


RADIANS=-0.01745

class Plane(...):

def matrix_update(self):
a = self._scale*math.cos(RADIANS*self._rotation)
b = self._scale*math.sin(RADIANS*self._rotation)
if self.parent is None:
self.matrix=(gl.GLdouble * 16)(
a, -b, 0, 0,
b, a, 0, 0,
0, 0, 1, 0,
self._tx, self._ty, self._tz, 1)
else:
c, d = -b, a
origin=self.parent.matrix
A,C = origin[0], origin[1] #C=-B
B,D = origin[4], origin[5] #D=A
X,Y,Z = origin[12], origin[13], origin[14]
self.matrix=(gl.GLdouble * 16)(
A*a+B*c, C*a+D*c, 0, 0,
A*b+B*d, C*b+D*d, 0, 0,
0, 0, 1, 0,
X + A*self._tx + B*self._ty, Y + C*self._tx +
D*self._ty,
Z + self._tz, 1)

for plane in self.children: plane.matrix_update()



El 22/07/2012 21:12, Derek S. escribi�:
> --
> You received this message because you are subscribed to the Google
> Groups "pyglet-users" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/pyglet-users/-/2o1_ymX6gqkJ.
> To post to this group, send email to pyglet...@googlegroups.com.
> To unsubscribe from this group, send email to
> pyglet-users...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/pyglet-users?hl=en.

Derek S.

unread,
Jul 27, 2012, 2:37:43 PM7/27/12
to pyglet...@googlegroups.com
Thanks for the examples and advice everyone, I really appreciate it!

To start, I was able to adapt Nathan's example for "panning".
It's a bit different than usual. You click somewhere with the middle mouse button
and then that location becomes the new center of the screen.
Ctrl + middle-mouse restores the default view.

    def on_mouse_press(self, x, y, button, modifiers):
        if button == mouse.MIDDLE:
                if self.keys[key.LCTRL]:
                    # Center = default
                    gl.glViewport(0, 0, self.width, self.height)
                    gl.glMatrixMode(gl.GL_PROJECTION)
                    gl.glLoadIdentity()
                    gl.glOrtho(0, self.width,
                                0, self.height,
                                -1, 1)
                    gl.glMatrixMode(gl.GL_MODELVIEW)
                    self.originx = 0
                    self.originy = 0
                else:
                    # Center = xp, yp
                    xp = x + self.originx
                    yp = y + self.originy
                    self.originx = xp - (self.width / 2)
                    self.originy = yp - (self.height / 2)
                    gl.glViewport(0, 0, self.width, self.height)
                    gl.glMatrixMode(gl.GL_PROJECTION)
                    gl.glLoadIdentity()
                    gl.glOrtho(self.originx, self.originx + self.width,
                                self.originy, self.originy + self.height,
                                    -1, 1)
                    gl.glMatrixMode(gl.GL_MODELVIEW)

I found an example of 'zooming to a point of interest' with glOrtho on stackOverflow:
http://stackoverflow.com/a/10136205/1217270
I've tweaked it a dozen times but have been unable to get it working as desired.
I'm keeping track of the origin so that I can translate the window coordinates into world coordinates.
Honestly I'm not sure what they intended the zoomLevel (self.zoom) to be.
Right now this zooms in a bit on the first scroll,
then everything just starts moving down to the left, on subsequent scrolls.
Maybe someone will see what I need to change.

        self.originx = 0.0
        self.originy = 0.0
        self.zoom = 1.0

    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
        if scroll_y > 0:
            self.zoom += 0.1
            xp = x + self.originx
            yp = y + self.originy
            gl.glViewport(0, 0, self.width, self.height)
            gl.glMatrixMode(gl.GL_PROJECTION)
            gl.glLoadIdentity()
            left = (self.originx - xp) / self.zoom + xp
            right = ((self.originx + self.width) - xp) / self.zoom + xp
            bottom = (self.originy - yp) / self.zoom + yp
            top = ((self.originy + self.height) - yp) / self.zoom + yp
            gl.glOrtho(int(left), int(right),
                        int(bottom), int(top),
                        -1, 1)
            gl.glMatrixMode(gl.GL_MODELVIEW)
            self.originx = int(left)
            self.originy = int(bottom)

Derek S.

unread,
Jul 27, 2012, 2:41:25 PM7/27/12
to pyglet...@googlegroups.com
Thank you for the example Txema. I'm going to need to become a little more educated to understand it.
Is A,C the x,y of the bottom left corner of the window, and B,D the top right?
What kind of data type is the matrix, some sort of special double precision tuple?
What do you use this bit of code for (zooming, panning, etc..)?

Txema Vicente

unread,
Jul 28, 2012, 1:30:08 PM7/28/12
to pyglet...@googlegroups.com
When you say "pan" and "zoom", I understand you want to "translate" and "scale".

But do you want to move the camera (GL_PROJECTION matrix) or the world (GL_MODELVIEW matrix)?

To set up the camera. First use glViewport(0, 0, w, h) to define the window limits,
then tell OpenGL you are going to modify the camera with glMatrixMode(GL_PROJECTION).
Now there are two types of camera: isometric (glOrtho) or perspective (gluPerspective).
This functions create a initial GL_PROJECTION matrix for you, but you can apply other transformations by multipying.

Only a perspective camera lets you "zoom" (fov).
You are doing 2D, so probably you should start with isometric.

The code was to show you how a transformation matrix looks like. The matrix is 4x4 = 16 GLdouble.

A matrix transforms the geometry. Let [V] be the geometry.

The identity matrix [I] has no effect:
| 1 0 0 0 |
| 0 1 0 0 | * [V] = [V]
| 0 0 1 0 |
| 0 0 0 1 |

When you call glLoadIdentity, you are setting [I] as the current (GL_MODELVIEW or GL_PROJECTION) matrix.
You can multiply the matrix by other matrix to translate, then multiply by other to rotate, and so on.
glMultMatrix([I]) obviously has no effect.

If now you want to translate the geometry to (tx, ty, tz), you need a matrix like this [T]:
|  1  0  0  0 |
|  0  1  0  0 | * [V] = [T] * [V] = [V translated]
|  0  0  1  0 |
| tx ty tz  1 |

Thit is what glTranslate(tx, ty, tx) does, multiply the current matrix by [T]: same as glMultMatrix([T])

glScale(sx, sy, sz) :
| sx  0  0  0 |
|  0 sy  0  0 | = [S]
|  0  0 sz  0 |
|  0  0  0  1 |


glRotate(0, 0, rz):
| cos(rz) -sin(rz) 0  0 |
| sin(rz) cos(rz)  0  0 | = [R]
|    0        0    1  1 |
|    0        0    0  1 |



One matrix [M]=[T]*[S]*[R] can apply translation, rotation and scale to your geometry in one step (one multiplication).
Remember that [T]*[R] is not [R]*[T]


Try this:

# About OpenGL Projections
#---------------------------------
from pyglet import window,image
from pyglet.window import key
from pyglet.gl import *

def opengl_init():
    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glDepthFunc(GL_LEQUAL)

def playground():

    # Test your code here
    glColor4f(1,1,1,1)
    glBegin(GL_LINES)
    glVertex3f(0,0,0)
    glVertex3f(640,480,0)

    glVertex3f(0,480,0)
    glVertex3f(640,0,0)
    glEnd()

class camera():
    mode=1
    x,y,z=0,0,512
    rx,ry,rz=30,-45,0
    w,h=640,480
    far=8192
    fov=60
          
    def view(self,width,height):
        self.w,self.h=width,height
        glViewport(0, 0, width, height)
        print "Viewport "+str(width)+"x"+str(height)
        if self.mode==2: self.isometric()
        elif self.mode==3: self.perspective()
        else: self.default()
          
    def default(self):
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(0, self.w, 0, self.h, -1, 1)
        glMatrixMode(GL_MODELVIEW)
      
    def isometric(self):
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(-self.w/2.,self.w/2.,-self.h/2.,self.h/2.,0,self.far)
        glMatrixMode(GL_MODELVIEW)
      
    def perspective(self):
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(self.fov, float(self.w)/self.h, 0.1, self.far)
        glMatrixMode(GL_MODELVIEW)
      
    def key(self, symbol, modifiers):
        if symbol==key.F1:
            self.mode=1
            self.default()
            print "Projection: Pyglet default"
        elif symbol==key.F2:
            print "Projection: 3D Isometric"
            self.mode=2
            self.isometric()
        elif symbol==key.F3:
            print "Projection: 3D Perspective"
            self.mode=3
            self.perspective()
        elif self.mode==3 and symbol==key.NUM_SUBTRACT:
            self.fov-=1
            self.perspective()
        elif self.mode==3 and symbol==key.NUM_ADD:
            self.fov+=1
            self.perspective()
        else: print "KEY "+key.symbol_string(symbol)
      
    def drag(self, x, y, dx, dy, button, modifiers):
        if button==1:
            self.x-=dx*2
            self.y-=dy*2
        elif button==2:
            self.x-=dx*2
            self.z-=dy*2
        elif button==4:
            self.ry+=dx/4.
            self.rx-=dy/4.
      
    def apply(self):
        glLoadIdentity()
        if self.mode==1: return
        glTranslatef(-self.x,-self.y,-self.z)
        glRotatef(self.rx,1,0,0)
        glRotatef(self.ry,0,1,0)
        glRotatef(self.rz,0,0,1)

def x_array(list):
    return (GLfloat * len(list))(*list)
      
def axis(d=200):
    vertices,colors=[],[]  
    #XZ RED
    vertices.extend([-d, 0,-d, d, 0,-d, d, 0, d,-d, 0, d])
    for i in range (0,4): colors.extend([1,0,0,0.5])
    #YZ GREEN
    vertices.extend([ 0,-d,-d, 0,-d, d, 0, d, d, 0, d,-d])
    for i in range (0,4): colors.extend([0,1,0,0.5])
    #XY BLUE
    vertices.extend([-d,-d, 0, d,-d, 0, d, d, 0,-d, d, 0])
    for i in range (0,4): colors.extend([0,0,1,0.5])
    return x_array(vertices),x_array(colors)
AXIS_VERTICES,AXIS_COLORS=axis()

def draw_vertex_array(vertices,colors,mode=GL_LINES):
    glEnableClientState(GL_VERTEX_ARRAY)
    glEnableClientState(GL_COLOR_ARRAY)
    glColorPointer(4, GL_FLOAT, 0, colors)
    glVertexPointer(3, GL_FLOAT, 0, vertices)
    glDrawArrays(GL_QUADS, 0, len(vertices)/3)
    glDisableClientState(GL_VERTEX_ARRAY)
    glDisableClientState(GL_COLOR_ARRAY)

def draw_axis():
    glEnable(GL_DEPTH_TEST)
    draw_vertex_array(AXIS_VERTICES,AXIS_COLORS,GL_QUADS)
    glDisable(GL_DEPTH_TEST)

print "OpenGL Projections"
print "---------------------------------"
print "Projection matrix -> F1, F2, F3"
print "Camera -> Drag LMB,CMB,RMB"
print ""
cam=camera()
win = window.Window(resizable=True)
win.on_resize=cam.view
win.on_key_press=cam.key
win.on_mouse_drag=cam.drag
opengl_init()

while not win.has_exit:
    win.dispatch_events()
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    cam.apply()
    draw_axis()
    playground()
    win.flip()



Question:
--
You received this message because you are subscribed to the Google Groups "pyglet-users" group.
To view this discussion on the web visit https://groups.google.com/d/msg/pyglet-users/-/LGCPXGNx3EoJ.

Txema Vicente

unread,
Jul 28, 2012, 3:17:15 PM7/28/12
to pyglet...@googlegroups.com
Hi again.

Yes, you can use glOrtho to scale the camera ("zoom" is for 3D
perspective), you simply specify the "box" the camera sees.

zoom = 2
glViewport(0, 0, width, height) -> set the drawing window.
# Now set uo the camera:
glOrtho(0, width, 0, height, -1, 1) -> Origin at bottom left corner
glOrtho(0, width/zoom, 0, height/zoom, -1, 1) -> Origin at bottom left
corner, zoom x2
glOrtho(-width/2, width/2, -height/2, height/2, -1, 1) -> Origin at center
glOrtho(-width/(2*zoom), width/(2*zoom), -height/(2*zoom),
height/(2*zoom), -1, 1) -> Origin at center, zoom x2


This should work too:

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, width, 0, height, -1, 1)
glTranslatef(pan_x, pan_y, 0) -> move the box
glScalef(zoom, zoom, 1) -> scale the box
glMatrixMode(GL_MODELVIEW)


El 27/07/2012 20:37, Derek S. escribi�:
> --
> You received this message because you are subscribed to the Google
> Groups "pyglet-users" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/pyglet-users/-/7_WaNmIPNCkJ.

Derek S.

unread,
Jul 28, 2012, 4:53:32 PM7/28/12
to pyglet...@googlegroups.com
Thank you for the explanation and examples;
you have provided me with a generous amount of thoughtful help.
:-]

- Derek
Reply all
Reply to author
Forward
0 new messages