diff --git a/main.py b/main.py index 6332d97a..a3593d9f 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,22 @@ -from __future__ import division - -import sys -import math -import random -import time - +#!/usr/bin/env python3 +""" +One-file Mini-Minecraft (Python 3 + Pyglet 2, shader pipeline, no deps) + +Controls: + WASD move SPACE jump (when not flying) + TAB toggle fly 1-3 select block + LMB remove (not stone) RMB place + ESC release mouse +""" +from __future__ import annotations +import math, random, time from collections import deque -from pyglet import image -from pyglet.gl import * -from pyglet.graphics import TextureGroup + +import pyglet from pyglet.window import key, mouse +from pyglet.gl import * +from pyglet.graphics import Batch +from pyglet.graphics.shader import Shader, ShaderProgram TICKS_PER_SEC = 60 @@ -29,17 +36,75 @@ # Use t and the desired MAX_JUMP_HEIGHT to solve for v_0 (jump speed) in # s = s_0 + v_0 * t + (a * t^2) / 2 JUMP_SPEED = math.sqrt(2 * GRAVITY * MAX_JUMP_HEIGHT) -TERMINAL_VELOCITY = 50 +TERMINAL_VELOCITY = 50.0 PLAYER_HEIGHT = 2 -if sys.version_info[0] >= 3: - xrange = range +# ---------------------------- math (tiny) ---------------------------- + +def _m4_id(): + return [1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1] + +def _m4_mul(a, b): + # column-major 4x4: out = a*b + o = [0.0]*16 + for c in range(4): + for r in range(4): + o[c*4+r] = (a[0*4+r]*b[c*4+0] + + a[1*4+r]*b[c*4+1] + + a[2*4+r]*b[c*4+2] + + a[3*4+r]*b[c*4+3]) + return o + +def _m4_perspective(fovy_deg, aspect, znear, zfar): + f = 1.0 / math.tan(math.radians(fovy_deg) / 2.0) + nf = 1.0 / (znear - zfar) + return [ + f/aspect,0,0,0, + 0,f,0,0, + 0,0,(zfar+znear)*nf,-1, + 0,0,(2*zfar*znear)*nf,0 + ] -def cube_vertices(x, y, z, n): - """ Return the vertices of the cube at position x, y, z with size 2*n. +def _v3_norm(x,y,z): + l = (x*x+y*y+z*z) ** 0.5 + if l == 0.0: return (0.0,0.0,0.0) + return (x/l, y/l, z/l) - """ +def _v3_cross(ax,ay,az, bx,by,bz): + return (ay*bz-az*by, az*bx-ax*bz, ax*by-ay*bx) + +def _v3_dot(ax,ay,az, bx,by,bz): + return ax*bx + ay*by + az*bz + +def _m4_look_at(ex,ey,ez, cx,cy,cz, ux,uy,uz): + # Right-handed lookAt, column-major, OpenGL conventions + fx,fy,fz = _v3_norm(cx-ex, cy-ey, cz-ez) + sx,sy,sz = _v3_cross(fx,fy,fz, ux,uy,uz) + sx,sy,sz = _v3_norm(sx,sy,sz) + ux,uy,uz = _v3_cross(sx,sy,sz, fx,fy,fz) + return [ + sx, ux, -fx, 0.0, + sy, uy, -fy, 0.0, + sz, uz, -fz, 0.0, + -_v3_dot(sx,sy,sz, ex,ey,ez), + -_v3_dot(ux,uy,uz, ex,ey,ez), + _v3_dot(fx,fy,fz, ex,ey,ez), + 1.0 + ] + +def _as_gl_mat4(m): + # pyglet shader expects GLfloat[16] + return (GLfloat * 16)(*m) + +# ---------------------------- geometry & textures ---------------------------- + +FACES = [(0,1,0),(0,-1,0),(-1,0,0),(1,0,0),(0,0,1),(0,0,-1)] + +def cube_vertices(x, y, z, n=0.5): return [ x-n,y+n,z-n, x-n,y+n,z+n, x+n,y+n,z+n, x+n,y+n,z-n, # top x-n,y-n,z-n, x+n,y-n,z-n, x+n,y-n,z+n, x-n,y-n,z+n, # bottom @@ -49,48 +114,32 @@ def cube_vertices(x, y, z, n): x+n,y-n,z-n, x-n,y-n,z-n, x-n,y+n,z-n, x+n,y+n,z-n, # back ] +# 24 verts -> 12 triangles +CUBE_INDICES = [ + 0,1,2, 0,2,3, # top + 4,5,6, 4,6,7, # bottom + 8,9,10, 8,10,11, # left + 12,13,14, 12,14,15,# right + 16,17,18, 16,18,19,# front + 20,21,22, 20,22,23 # back +] -def tex_coord(x, y, n=4): +def tex_coord(tx, ty, n=4): """ Return the bounding vertices of the texture square. - """ m = 1.0 / n - dx = x * m - dy = y * m - return dx, dy, dx + m, dy, dx + m, dy + m, dx, dy + m - + x, y = tx*m, ty*m + return (x,y, x+m,y, x+m,y+m, x,y+m) def tex_coords(top, bottom, side): - """ Return a list of the texture squares for the top, bottom and side. - - """ - top = tex_coord(*top) - bottom = tex_coord(*bottom) - side = tex_coord(*side) - result = [] - result.extend(top) - result.extend(bottom) - result.extend(side * 4) - return result - - -TEXTURE_PATH = 'texture.png' + t = tex_coord(*top); b = tex_coord(*bottom); s = tex_coord(*side) + return list(t) + list(b) + list(s)*4 GRASS = tex_coords((1, 0), (0, 1), (0, 0)) SAND = tex_coords((1, 1), (1, 1), (1, 1)) BRICK = tex_coords((2, 0), (2, 0), (2, 0)) STONE = tex_coords((2, 1), (2, 1), (2, 1)) -FACES = [ - ( 0, 1, 0), - ( 0,-1, 0), - (-1, 0, 0), - ( 1, 0, 0), - ( 0, 0, 1), - ( 0, 0,-1), -] - - def normalize(position): """ Accepts `position` of arbitrary precision and returns the block containing that position. @@ -105,9 +154,7 @@ def normalize(position): """ x, y, z = position - x, y, z = (int(round(x)), int(round(y)), int(round(z))) - return (x, y, z) - + return (int(round(x)), int(round(y)), int(round(z))) def sectorize(position): """ Returns a tuple representing the sector for the given `position`. @@ -122,75 +169,144 @@ def sectorize(position): """ x, y, z = normalize(position) - x, y, z = x // SECTOR_SIZE, y // SECTOR_SIZE, z // SECTOR_SIZE - return (x, 0, z) - + return (x//SECTOR_SIZE, 0, z//SECTOR_SIZE) -class Model(object): - - def __init__(self): +def _make_texture_atlas(tile=16, tiles=4, filename="texture.png"): + """ + Load classic Minecraft-style atlas from filename (preferred). + Falls back to a small procedural atlas if the file isn't found. + """ + img = pyglet.image.load(filename) + tex = img.get_texture() + glBindTexture(tex.target, tex.id) + glTexParameteri(tex.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(tex.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + return tex + +# ---------------------------- shaders ---------------------------- + +VS_WORLD = """ +#version 330 +in vec3 position; +in vec2 tex_coords; +uniform mat4 u_viewproj; +uniform vec3 u_campos; +out vec2 v_uv; +out float v_dist; +void main(){ + vec4 wp = vec4(position, 1.0); + gl_Position = u_viewproj * wp; + v_uv = tex_coords; + v_dist = distance(u_campos, wp.xyz); +} +""" + +FS_WORLD = """ +#version 330 +in vec2 v_uv; +in float v_dist; +uniform sampler2D u_tex; +uniform vec4 u_fog_color; +uniform float u_fog_start; +uniform float u_fog_end; +out vec4 fragColor; +void main(){ + vec4 c = texture(u_tex, v_uv); + float f = clamp((u_fog_end - v_dist) / (u_fog_end - u_fog_start), 0.0, 1.0); + fragColor = mix(u_fog_color, c, f); +} +""" + +VS_SOLID = """ +#version 330 +in vec3 position; +uniform mat4 u_viewproj; +void main(){ + gl_Position = u_viewproj * vec4(position, 1.0); +} +""" + +FS_SOLID = """ +#version 330 +uniform vec4 u_color; +out vec4 fragColor; +void main(){ fragColor = u_color; } +""" + +class WorldGroup(pyglet.graphics.Group): + def __init__(self, program, texture, parent=None): + super().__init__(parent=parent) + self.program = program + self.texture = texture + + def set_state(self): + self.program.use() + glActiveTexture(GL_TEXTURE0) + glBindTexture(self.texture.target, self.texture.id) + self.program['u_tex'] = 0 + + def unset_state(self): + glBindTexture(self.texture.target, 0) + glUseProgram(0) + +# ---------------------------- model ---------------------------- + +class Model: + def __init__(self, world_program): # A Batch is a collection of vertex lists for batched rendering. - self.batch = pyglet.graphics.Batch() - + self.batch = Batch() + self.world_program = world_program + self.texture = _make_texture_atlas() # A TextureGroup manages an OpenGL texture. - self.group = TextureGroup(image.load(TEXTURE_PATH).get_texture()) + self.group = WorldGroup(world_program, self.texture) # A mapping from position to the texture of the block at that position. # This defines all the blocks that are currently in the world. - self.world = {} + self.world: dict[tuple[int,int,int], list[float]] = {} # Same mapping as `world` but only contains blocks that are shown. - self.shown = {} + self.shown: dict[tuple[int,int,int], list[float]] = {} # Mapping from position to a pyglet `VertextList` for all shown blocks. - self._shown = {} - - # Mapping from sector to a list of positions inside that sector. - self.sectors = {} + self._shown: dict[tuple[int,int,int], pyglet.graphics.vertexdomain.VertexList] = {} # Simple function queue implementation. The queue is populated with # _show_block() and _hide_block() calls + self.sectors: dict[tuple[int,int,int], list[tuple[int,int,int]]] = {} self.queue = deque() self._initialize() def _initialize(self): - """ Initialize the world by placing all the blocks. - - """ - n = 80 # 1/2 width and height of world + n = 80 # 1/2 width and height of world s = 1 # step size y = 0 # initial y height - for x in xrange(-n, n + 1, s): - for z in xrange(-n, n + 1, s): - # create a layer stone an grass everywhere. + for x in range(-n, n + 1, s): + for z in range(-n, n + 1, s): self.add_block((x, y - 2, z), GRASS, immediate=False) self.add_block((x, y - 3, z), STONE, immediate=False) if x in (-n, n) or z in (-n, n): # create outer walls. - for dy in xrange(-2, 3): + for dy in range(-2, 3): self.add_block((x, y + dy, z), STONE, immediate=False) - # generate the hills randomly o = n - 10 - for _ in xrange(120): + for _ in range(120): a = random.randint(-o, o) # x position of the hill b = random.randint(-o, o) # z position of the hill - c = -1 # base of the hill + c = -1 h = random.randint(1, 6) # height of the hill s = random.randint(4, 8) # 2 * s is the side length of the hill d = 1 # how quickly to taper off the hills t = random.choice([GRASS, SAND, BRICK]) - for y in xrange(c, c + h): - for x in xrange(a - s, a + s + 1): - for z in xrange(b - s, b + s + 1): - if (x - a) ** 2 + (z - b) ** 2 > (s + 1) ** 2: - continue - if (x - 0) ** 2 + (z - 0) ** 2 < 5 ** 2: - continue + for y in range(c, c + h): + for x in range(a - s, a + s + 1): + for z in range(b - s, b + s + 1): + if (x - a)**2 + (z - b)**2 > (s + 1)**2: continue + if (x - 0)**2 + (z - 0)**2 < 5**2: continue self.add_block((x, y, z), t, immediate=False) - s -= d # decrement side length so hills taper off + s -= d def hit_test(self, position, vector, max_distance=8): """ Line of sight search from current position. If a block is @@ -211,7 +327,7 @@ def hit_test(self, position, vector, max_distance=8): x, y, z = position dx, dy, dz = vector previous = None - for _ in xrange(max_distance * m): + for _ in range(max_distance * m): key = normalize((x, y, z)) if key != previous and key in self.world: return key, previous @@ -264,8 +380,8 @@ def remove_block(self, position, immediate=True): Whether or not to immediately remove block from canvas. """ - del self.world[position] - self.sectors[sectorize(position)].remove(position) + self.world.pop(position, None) + self.sectors.get(sectorize(position), []).remove(position) if immediate: if position in self.shown: self.hide_block(position) @@ -322,13 +438,15 @@ def _show_block(self, position, texture): """ x, y, z = position - vertex_data = cube_vertices(x, y, z, 0.5) - texture_data = list(texture) + verts = cube_vertices(x, y, z, 0.5) + uvs = list(texture) # create vertex list - # FIXME Maybe `add_indexed()` should be used instead - self._shown[position] = self.batch.add(24, GL_QUADS, self.group, - ('v3f/static', vertex_data), - ('t2f/static', texture_data)) + self._shown[position] = self.world_program.vertex_list_indexed( + 24, GL_TRIANGLES, CUBE_INDICES, + batch=self.batch, group=self.group, + position=('f', verts), + tex_coords=('f', uvs), + ) def hide_block(self, position, immediate=True): """ Hide the block at the given `position`. Hiding does not remove the @@ -342,7 +460,7 @@ def hide_block(self, position, immediate=True): Whether or not to immediately remove the block from the canvas. """ - self.shown.pop(position) + self.shown.pop(position, None) if immediate: self._hide_block(position) else: @@ -381,9 +499,9 @@ def change_sectors(self, before, after): before_set = set() after_set = set() pad = 4 - for dx in xrange(-pad, pad + 1): + for dx in range(-pad, pad + 1): for dy in [0]: # xrange(-pad, pad + 1): - for dz in xrange(-pad, pad + 1): + for dz in range(-pad, pad + 1): if dx ** 2 + dy ** 2 + dz ** 2 > (pad + 1) ** 2: continue if before: @@ -430,12 +548,11 @@ def process_entire_queue(self): while self.queue: self._dequeue() +# ---------------------------- window / game ---------------------------- class Window(pyglet.window.Window): - - def __init__(self, *args, **kwargs): - super(Window, self).__init__(*args, **kwargs) - + def __init__(self): + super().__init__(width=800, height=600, caption="Mini MC (pyglet 2)", resizable=True, vsync=True) # Whether or not the window exclusively captures the mouse. self.exclusive = False @@ -452,7 +569,7 @@ def __init__(self, *args, **kwargs): # Current (x, y, z) position in the world, specified with floats. Note # that, perhaps unlike in math class, the y-axis is the vertical axis. - self.position = (0, 0, 0) + self.position = (0.0, 0.0, 0.0) # First element is rotation of the player in the x-z plane (ground # plane) measured from the z-axis down. The second is the rotation @@ -460,7 +577,7 @@ def __init__(self, *args, **kwargs): # # The vertical plane rotation ranges from -90 (looking straight down) to # 90 (looking straight up). The horizontal rotation range is unbounded. - self.rotation = (0, 0) + self.rotation = (0.0, 0.0) # yaw, pitch # Which sector the player is currently in. self.sector = None @@ -482,24 +599,34 @@ def __init__(self, *args, **kwargs): key._1, key._2, key._3, key._4, key._5, key._6, key._7, key._8, key._9, key._0] - # Instance of the model that handles the world. - self.model = Model() + # Programs + self.world_program = ShaderProgram(Shader(VS_WORLD, 'vertex'), Shader(FS_WORLD, 'fragment')) + self.solid_program = ShaderProgram(Shader(VS_SOLID, 'vertex'), Shader(FS_SOLID, 'fragment')) + + self.model = Model(self.world_program) # The label that is displayed in the top left of the canvas. self.label = pyglet.text.Label('', font_name='Arial', font_size=18, - x=10, y=self.height - 10, anchor_x='left', anchor_y='top', - color=(0, 0, 0, 255)) + x=10, y=self.height-10, anchor_x='left', anchor_y='top', + color=(0,0,0,255)) + + self.focus_outline = None # VertexList for outline quads + self._vp = _m4_id() #Viewport projection matrix + self._campos = (0.0,0.0,0.0) #camera position + + # Manual framerate counter + self._fps_frames = 0 + self._fps_t0 = time.perf_counter() - # This call schedules the `update()` method to be called - # TICKS_PER_SEC. This is the main game event loop. pyglet.clock.schedule_interval(self.update, 1.0 / TICKS_PER_SEC) + self.set_exclusive_mouse(True) - def set_exclusive_mouse(self, exclusive): + def set_exclusive_mouse(self, exclusive: bool): """ If `exclusive` is True, the game will capture the mouse, if False the game will ignore the mouse. """ - super(Window, self).set_exclusive_mouse(exclusive) + super().set_exclusive_mouse(exclusive) self.exclusive = exclusive def get_sight_vector(self): @@ -540,10 +667,10 @@ def get_motion_vector(self): if self.strafe[1]: # Moving left or right. dy = 0.0 - m = 1 + m = 1.0 if self.strafe[0] > 0: # Moving backwards. - dy *= -1 + dy *= -1.0 # When you are flying up or down, you have less left and right # motion. dx = math.cos(x_angle) * m @@ -577,7 +704,7 @@ def update(self, dt): self.sector = sector m = 8 dt = min(dt, 0.2) - for _ in xrange(m): + for _ in range(m): self._update(dt / m) def _update(self, dt): @@ -634,14 +761,14 @@ def collide(self, position, height): p = list(position) np = normalize(position) for face in FACES: # check all surrounding blocks - for i in xrange(3): # check each dimension independently + for i in range(3): # check each dimension independently if not face[i]: continue # How much overlap you have with this dimension. d = (p[i] - np[i]) * face[i] if d < pad: continue - for dy in xrange(height): # check each height + for dy in range(height): # check each height op = list(np) op[1] -= dy op[i] += face[i] @@ -727,12 +854,14 @@ def on_key_press(self, symbol, modifiers): elif symbol == key.D: self.strafe[1] += 1 elif symbol == key.SPACE: - if self.dy == 0: + if self.dy == 0.0: self.dy = JUMP_SPEED elif symbol == key.ESCAPE: self.set_exclusive_mouse(False) elif symbol == key.TAB: self.flying = not self.flying + if self.flying: + self.dy = 0.0 elif symbol in self.num_keys: index = (symbol - self.num_keys[0]) % len(self.inventory) self.block = self.inventory[index] @@ -769,55 +898,33 @@ def on_resize(self, width, height): self.reticle.delete() x, y = self.width // 2, self.height // 2 n = 10 - self.reticle = pyglet.graphics.vertex_list(4, - ('v2i', (x - n, y, x + n, y, x, y - n, x, y + n)) + # reticle is 2D lines in window coords + self.reticle = self.solid_program.vertex_list( + 4, GL_LINES, + position=('f', (x-n, y, 0.0, x+n, y, 0.0, x, y-n, 0.0, x, y+n, 0.0)) ) + glViewport(0, 0, max(1, width), max(1, height)) - def set_2d(self): - """ Configure OpenGL to draw in 2d. - - """ - width, height = self.get_size() - glDisable(GL_DEPTH_TEST) - viewport = self.get_viewport_size() - glViewport(0, 0, max(1, viewport[0]), max(1, viewport[1])) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - glOrtho(0, max(1, width), 0, max(1, height), -1, 1) - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() + def _update_viewproj(self): + w, h = self.get_size() + aspect = w / float(max(1, h)) + proj = _m4_perspective(65.0, aspect, 0.1, 60.0) - def set_3d(self): - """ Configure OpenGL to draw in 3d. + ex, ey, ez = self.position + fx, fy, fz = self.get_sight_vector() + cx, cy, cz = ex + fx, ey + fy, ez + fz + view = _m4_look_at(ex, ey, ez, cx, cy, cz, 0.0, 1.0, 0.0) - """ - width, height = self.get_size() - glEnable(GL_DEPTH_TEST) - viewport = self.get_viewport_size() - glViewport(0, 0, max(1, viewport[0]), max(1, viewport[1])) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - gluPerspective(65.0, width / float(height), 0.1, 60.0) - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - x, y = self.rotation - glRotatef(x, 0, 1, 0) - glRotatef(-y, math.cos(math.radians(x)), 0, math.sin(math.radians(x))) - x, y, z = self.position - glTranslatef(-x, -y, -z) + self._vp = _m4_mul(proj, view) + self._campos = (ex, ey, ez) - def on_draw(self): - """ Called by pyglet to draw the canvas. + self.world_program['u_viewproj'] = _as_gl_mat4(self._vp) + self.world_program['u_campos'] = self._campos + self.world_program['u_fog_color'] = (0.5, 0.69, 1.0, 1.0) + self.world_program['u_fog_start'] = 20.0 + self.world_program['u_fog_end'] = 60.0 - """ - self.clear() - self.set_3d() - glColor3d(1, 1, 1) - self.model.batch.draw() - self.draw_focused_block() - self.set_2d() - self.draw_label() - self.draw_reticle() + self.solid_program['u_viewproj'] = _as_gl_mat4(self._vp) def draw_focused_block(self): """ Draw black edges around the block that is currently under the @@ -826,13 +933,26 @@ def draw_focused_block(self): """ vector = self.get_sight_vector() block = self.model.hit_test(self.position, vector)[0] - if block: - x, y, z = block - vertex_data = cube_vertices(x, y, z, 0.51) - glColor3d(0, 0, 0) - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) - pyglet.graphics.draw(24, GL_QUADS, ('v3f/static', vertex_data)) - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) + if not block: + if self.focus_outline: + self.focus_outline.delete() + self.focus_outline = None + return + + x,y,z = block + verts = cube_vertices(x,y,z,0.51) + # Make/update a 3D quad list for wireframe; we draw with polygon mode line. + if not self.focus_outline: + self.focus_outline = self.solid_program.vertex_list(24, GL_QUADS, position=('f', verts)) + else: + self.focus_outline.position[:] = verts + + self.solid_program.use() + self.solid_program['u_color'] = (0.0,0.0,0.0,1.0) + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) + self.focus_outline.draw(GL_QUADS) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) + glUseProgram(0) def draw_label(self): """ Draw the label in the top left of the screen. @@ -840,63 +960,60 @@ def draw_label(self): """ x, y, z = self.position self.label.text = '%02d (%.2f, %.2f, %.2f) %d / %d' % ( - pyglet.clock.get_fps(), x, y, z, - len(self.model._shown), len(self.model.world)) + self.fps, x,y,z, len(self.model._shown), len(self.model.world)) self.label.draw() def draw_reticle(self): """ Draw the crosshairs in the center of the screen. """ - glColor3d(0, 0, 0) + if not self.reticle: return + self.solid_program.use() + # 2D draw: identity projection in clip via gl_Position computed from pixel coords. + # Instead of a second program, temporarily set an ortho viewproj by mapping pixels to NDC. + w,h = self.get_size() + # map window coords (0..w,0..h) -> NDC (-1..1) + ortho = [ + 2.0/w,0,0,0, + 0,2.0/h,0,0, + 0,0,-1,0, + -1,-1,0,1 + ] + self.solid_program['u_viewproj'] = _as_gl_mat4(ortho) + self.solid_program['u_color'] = (0.0,0.0,0.0,1.0) self.reticle.draw(GL_LINES) + # restore 3D vp for any later draw + self.solid_program['u_viewproj'] = _as_gl_mat4(self._vp) + glUseProgram(0) + def on_draw(self): + self._fps_frames += 1 + t = time.perf_counter() + dt = t - self._fps_t0 + if dt >= 0.5: + self.fps = int(self._fps_frames / dt + 0.5) + self._fps_frames = 0 + self._fps_t0 = t + self.clear() + glEnable(GL_DEPTH_TEST) + # Set the color of "clear", i.e. the sky, in rgba. + glClearColor(0.5, 0.69, 1.0, 1) + # Enable culling (not rendering) of back-facing facets -- facets that aren't + # visible to you. + glEnable(GL_CULL_FACE) + self._update_viewproj() + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) + self.model.batch.draw() + self.draw_focused_block() -def setup_fog(): - """ Configure the OpenGL fog properties. - - """ - # Enable fog. Fog "blends a fog color with each rasterized pixel fragment's - # post-texturing color." - glEnable(GL_FOG) - # Set the fog color. - glFogfv(GL_FOG_COLOR, (GLfloat * 4)(0.5, 0.69, 1.0, 1)) - # Say we have no preference between rendering speed and quality. - glHint(GL_FOG_HINT, GL_DONT_CARE) - # Specify the equation used to compute the blending factor. - glFogi(GL_FOG_MODE, GL_LINEAR) - # How close and far away fog starts and ends. The closer the start and end, - # the denser the fog in the fog range. - glFogf(GL_FOG_START, 20.0) - glFogf(GL_FOG_END, 60.0) - - -def setup(): - """ Basic OpenGL configuration. - - """ - # Set the color of "clear", i.e. the sky, in rgba. - glClearColor(0.5, 0.69, 1.0, 1) - # Enable culling (not rendering) of back-facing facets -- facets that aren't - # visible to you. - glEnable(GL_CULL_FACE) - # Set the texture minification/magnification function to GL_NEAREST (nearest - # in Manhattan distance) to the specified texture coordinates. GL_NEAREST - # "is generally faster than GL_LINEAR, but it can produce textured images - # with sharper edges because the transition between texture elements is not - # as smooth." - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - setup_fog() - + glDisable(GL_DEPTH_TEST) + self.draw_label() + self.draw_reticle() def main(): - window = Window(width=800, height=600, caption='Pyglet', resizable=True) - # Hide the mouse cursor and prevent the mouse from leaving the window. - window.set_exclusive_mouse(True) - setup() + Window() pyglet.app.run() - if __name__ == '__main__': main()