Before we start looking at the details, I should point out that all the code is now available on github. That includes the previous two examples "opengl1.py" and "opengl2.py", as well as today's, "zoompan.py". If you look in there you can see everything I've written for this project, but be aware that most of it is very haphazard. I'm brushing off and tidying up the corresponding parts as I write each article. Feel free to ask questions about any of the code in there, but please don't beat me up too much for my coding style!
Most of the changes this time are to the main loop:
def main(): video_flags = OPENGL|DOUBLEBUF pygame.init() screen_dimensions = 800, 600 surface = pygame.display.set_mode( screen_dimensions, video_flags) resources = make_resources() frames = 0 done = 0 zoom = 1.0 position = [256, 8192 - 256] dragging = False draglast = 0,0 while not done: while 1: event = pygame.event.poll() if event.type == NOEVENT: break if event.type == KEYDOWN: pass if event.type == QUIT: done = 1 if event.type == MOUSEMOTION: if dragging: mx,my = event.pos lx,ly = draglast dx = (mx - lx)/zoom dy = (my - ly)/zoom position[0] -= dx position[1] += dy draglast = mx, my if event.type == MOUSEBUTTONDOWN: if event.button == 1: draglast = event.pos dragging = True if event.button == 4: zoom *= 2.0 if zoom > 16: zoom = 16.0 if event.button == 5: zoom /= 2.0 if zoom < 1.0/32.0: zoom = 1.0/32.0 x,y = position x = math.floor(x * zoom + 0.5) / zoom y = math.floor(y * zoom + 0.5) / zoom position = [x,y] if event.type == MOUSEBUTTONUP: if event.button == 1: dragging = False render(resources, position, zoom, screen_dimensions) frames += 1 if __name__ == '__main__': main()
Mouse button 1 is the left (or primary) button. When it is pressed down we enter dragging mode and when it is released we leave dragging mode. Whenever the mouse moves in dragging mode, we track how far it moved and update the camera position, taking into account the zoom factor. Note that for mouse events the y coordinate increases down the screen, while our camera y increases up the screen.
One last thing to note is that when zooming out, we apply rounding to the camera position. This is so that every pixel matches up perfectly with a texel. When I didn't have this, sometimes if you zoomed in, panned around and then zoomed out you'd see odd pixels at the boundaries between tiles.
The other minor changes this time are to use mipmapping. This involves some changes to how we set up our texture atlas:
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST) glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
I'm using GL_NEAREST_MIPMAP_NEAREST as the minification function. This means that we don't do any interpolation - we just pick the closest mipmap level and in that mipmap level pick the closest texel. This is fine because our zoom levels are powers of 2 and we keep the texels and pixels lined up. It also avoids some ways for things to go wrong.
The shader also needs a change:
fragcolor = textureGrad(
texture_atlas, atlas_point,
vec2(1/512.0/zoom,0), vec2(0,1/512.0/zoom));
The extra parameters for textureGrad allow us to specify the rate of change of the texture coordinates per pixel both horizontally and vertically. We can calculate them based on the zoom level.
You can get the current version of the code from github. Next week, we'll start looking at reading Minecraft maps. We'll be using an NBT parser for Python with some extra work to read the region files used by recent versions of Minecraft. (Well, recent at the time of writing.)
No comments:
Post a Comment