axuy/axuy/view.py

576 lines
21 KiB
Python
Raw Normal View History

2019-02-06 17:08:24 +01:00
# view.py - maintain view on game world
# Copyright (C) 2019 Nguyễn Gia Phong
#
# This file is part of Axuy
#
# Axuy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Axuy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Axuy. If not, see <https://www.gnu.org/licenses/>.
2019-02-10 08:57:52 +01:00
__doc__ = 'Axuy module for map class'
2019-08-23 04:47:53 +02:00
from collections import deque
2019-07-15 12:11:40 +02:00
from configparser import ConfigParser
from os.path import join as pathjoin, pathsep
from math import degrees, log2, radians
from random import randint
2019-07-30 12:59:51 +02:00
from re import IGNORECASE, match
2019-08-23 04:47:53 +02:00
from statistics import mean
from typing import Tuple
2019-07-15 12:11:40 +02:00
from warnings import warn
2019-02-06 17:08:24 +01:00
2019-02-26 06:05:47 +01:00
import glfw
2019-02-06 17:08:24 +01:00
import moderngl
import numpy as np
2019-07-15 12:11:40 +02:00
from appdirs import AppDirs
from PIL import Image
2019-02-08 09:50:41 +01:00
from pyrr import Matrix44
2019-02-06 17:08:24 +01:00
from .pico import TETRAVERTICES, OCTOVERTICES, SHARD_LIFE, Picobot
2019-09-21 16:43:37 +02:00
from .misc import abspath, color, mirror
2019-02-06 17:08:24 +01:00
2019-07-15 12:11:40 +02:00
CONTROL_ALIASES = (('Move left', 'left'), ('Move right', 'right'),
('Move forward', 'forward'), ('Move backward', 'backward'),
2019-09-20 15:27:04 +02:00
('Primary', '1st'), ('Secondary', '2nd'))
2019-07-15 12:11:40 +02:00
MOUSE_PATTERN = 'MOUSE_BUTTON_[1-{}]'.format(glfw.MOUSE_BUTTON_LAST + 1)
INVALID_CONTROL_ERR = '{}: {} is not recognized as a valid control key'
2019-07-30 12:59:51 +02:00
GLFW_VER_WARN = 'Your GLFW version appear to be lower than 3.3, '\
'which might cause stuttering camera rotation.'
2019-07-15 12:11:40 +02:00
2019-07-30 12:59:51 +02:00
ZMIN, ZMAX = -1.0, 1.0
2019-05-23 11:04:40 +02:00
CONWAY = 1.303577269034
2019-09-20 15:27:04 +02:00
ABRTN_MAX = 0.42069
2019-02-10 08:57:52 +01:00
QUAD = np.float32([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]).tobytes()
2019-02-26 06:05:47 +01:00
TETRAINDECIES = np.int32([0, 1, 2, 3, 1, 2, 0, 3, 2, 0, 3, 1])
OCTOINDECIES = np.int32([0, 1, 2, 0, 1, 3, 4, 0, 2, 4, 0, 3,
2, 1, 5, 3, 1, 5, 2, 5, 4, 3, 5, 4])
2019-02-26 06:05:47 +01:00
with open(abspath('shaders/map.vert')) as f: MAP_VERTEX = f.read()
2019-09-04 12:02:39 +02:00
with open(abspath('shaders/map.frag')) as f: MAP_FRAGMENT = f.read()
2019-02-26 06:05:47 +01:00
with open(abspath('shaders/pico.vert')) as f: PICO_VERTEX = f.read()
2019-09-04 12:02:39 +02:00
with open(abspath('shaders/pico.geom')) as f: PICO_GEOMETRY = f.read()
with open(abspath('shaders/pico.frag')) as f: PICO_FRAGMENT = f.read()
2019-02-06 17:08:24 +01:00
with open(abspath('shaders/tex.vert')) as f: TEX_VERTEX = f.read()
with open(abspath('shaders/sat.frag')) as f: SAT_FRAGMENT = f.read()
with open(abspath('shaders/gaussh.vert')) as f: GAUSSH_VERTEX = f.read()
with open(abspath('shaders/gaussv.vert')) as f: GAUSSV_VERTEX = f.read()
with open(abspath('shaders/gauss.frag')) as f: GAUSS_FRAGMENT = f.read()
with open(abspath('shaders/comb.frag')) as f: COMBINE_FRAGMENT = f.read()
2019-02-06 17:08:24 +01:00
2019-07-15 12:11:40 +02:00
class ConfigReader:
"""Object reading and processing command-line arguments
and INI configuration file for Axuy.
Attributes
----------
config : ConfigParser
INI configuration file parser.
host : str
Host to bind the peer to.
port : int
Port to bind the peer to.
seeder : str
Address of the peer that created the map.
2019-09-21 16:24:30 +02:00
size : Tuple[int, int]
2019-07-15 12:11:40 +02:00
GLFW window resolution.
vsync : bool
Vertical synchronization.
2019-07-30 12:59:51 +02:00
zmlvl : float
Zoom level.
2019-09-21 16:24:30 +02:00
key, mouse : Dict[str, int]
2019-07-15 12:11:40 +02:00
Input control.
2019-07-30 12:59:51 +02:00
mouspeed : float
Relative camera rotational speed.
zmspeed : float
Zoom speed, in scroll steps per zoom range.
2019-07-15 12:11:40 +02:00
"""
def __init__(self):
dirs = AppDirs(appname='axuy', appauthor=False, multipath=True)
parents = dirs.site_config_dir.split(pathsep)
parents.append(dirs.user_config_dir)
filenames = [pathjoin(parent, 'settings.ini') for parent in parents]
self.config = ConfigParser()
self.config.read(abspath('settings.ini')) # default configuration
self.config.read(filenames)
# Fallback to None when attribute is missing
def __getattr__(self, name): return None
2019-08-23 04:47:53 +02:00
@property
def seeder(self) -> Tuple[str, int]:
"""Seeder address."""
return self._seed
@seeder.setter
def seeder(self, value):
host, port = value.split(':')
self._seed = host, int(port)
2019-07-30 12:59:51 +02:00
@property
def fov(self) -> float:
"""Horizontal field of view in degrees."""
if self.zmlvl is None: return None
return degrees(2 ** self.zmlvl)
@fov.setter
def fov(self, value):
rad = radians(value)
if rad < 0.5:
warn('Too narrow FOV, falling back to the minimal value.')
self.zmlvl = -1.0
return
elif rad > 2:
2019-08-23 04:47:53 +02:00
warn('Too wide FOV, falling back to the maximal value.')
2019-07-30 12:59:51 +02:00
self.zmlvl = 1.0
return
self.zmlvl = log2(rad)
@property
def mouspeed(self) -> float:
"""Relative mouse speed."""
# Standard to radians per inch for a 800 DPI mouse, at FOV of 60
return self._mouspeed / 800
@mouspeed.setter
def mouspeed(self, value):
self._mouspeed = value
2019-07-15 12:11:40 +02:00
def parse(self):
"""Parse configurations."""
self.size = (self.config.getint('Graphics', 'Screen width'),
self.config.getint('Graphics', 'Screen height'))
self.vsync = self.config.getboolean('Graphics', 'V-sync')
2019-07-30 12:59:51 +02:00
self.fov = self.config.getfloat('Graphics', 'FOV')
2019-07-15 12:11:40 +02:00
self.host = self.config.get('Peer', 'Host')
self.port = self.config.getint('Peer', 'Port')
2019-07-30 12:59:51 +02:00
self.mouspeed = self.config.getfloat('Control', 'Mouse speed')
self.zmspeed = self.config.getfloat('Control', 'Zoom speed')
2019-07-15 12:11:40 +02:00
self.key, self.mouse = {}, {}
for cmd, alias in CONTROL_ALIASES:
i = self.config.get('Control', cmd)
2019-07-30 12:59:51 +02:00
if match(MOUSE_PATTERN, i, flags=IGNORECASE):
2019-07-15 12:11:40 +02:00
self.mouse[alias] = getattr(glfw, i.upper())
2019-07-30 12:59:51 +02:00
continue
try:
self.key[alias] = getattr(glfw, 'KEY_{}'.format(i.upper()))
2019-07-15 12:11:40 +02:00
except AttributeError:
2019-07-30 12:59:51 +02:00
raise ValueError(INVALID_CONTROL_ERR.format(cmd, i))
2019-07-15 12:11:40 +02:00
def read_args(self, arguments):
"""Read and parse a argparse.ArgumentParser.Namespace."""
2019-07-30 12:59:51 +02:00
for option in ('size', 'vsync', 'fov', 'mouspeed', 'zmspeed',
'host', 'port', 'seeder'):
2019-07-15 12:11:40 +02:00
value = getattr(arguments, option)
if value is not None: setattr(self, option, value)
2019-02-06 17:08:24 +01:00
class View:
"""World map and camera placement.
Parameters
----------
2019-09-21 16:24:30 +02:00
address : Tuple[str, int]
2019-03-03 16:04:01 +01:00
IP address (host, port).
2019-07-30 12:59:51 +02:00
camera : Picobot
Protagonist whose view is the camera.
space : np.ndarray of shape (12, 12, 9) of bools
3D array of occupied space.
2019-09-21 16:24:30 +02:00
size : Tuple[int, int]
2019-07-15 12:11:40 +02:00
GLFW window resolution.
vsync : bool
Vertical synchronization.
2019-09-21 16:24:30 +02:00
ctl : Dict[str, int]
2019-07-15 12:11:40 +02:00
Input control.
2019-02-06 17:08:24 +01:00
Attributes
----------
2019-09-21 16:24:30 +02:00
addr : Tuple[str, int]
2019-03-03 16:04:01 +01:00
IP address (host, port).
2019-02-10 08:57:52 +01:00
space : np.ndarray of shape (12, 12, 9) of bools
2019-02-06 17:08:24 +01:00
3D array of occupied space.
2019-07-30 12:59:51 +02:00
camera : Picobot
Protagonist whose view is the camera.
2019-09-21 16:24:30 +02:00
picos : Dict[Tuple[str, int], Picobot]
Enemies characters.
2019-09-21 16:24:30 +02:00
colors : Dict[Tuple[str, int], str]
2019-03-03 16:04:01 +01:00
Color names of enemies.
window : GLFW window
2019-07-30 12:59:51 +02:00
zmlvl : float
Zoom level (from ZMIN to ZMAX).
zmspeed : float
Scroll steps per zoom range.
mouspeed : float
Relative camera rotational speed.
context : moderngl.Context
OpenGL context from which ModernGL objects are created.
2019-02-26 06:05:47 +01:00
maprog : moderngl.Program
Processed executable code in GLSL for map rendering.
2019-02-10 08:57:52 +01:00
mapva : moderngl.VertexArray
2019-02-06 17:08:24 +01:00
Vertex data of the map.
2019-08-26 11:00:04 +02:00
prog : moderngl.Program
Processed executable code in GLSL
for rendering picobots and their shards.
2019-08-26 11:00:04 +02:00
pva : moderngl.VertexArray
Vertex data of picobots.
2019-08-26 11:00:04 +02:00
sva : moderngl.VertexArray
Vertex data of shards.
pfilter : moderngl.VertexArray
Vertex data for filtering highly saturated colors.
gaussh, gaussv : moderngl.Program
Processed executable code in GLSL for Gaussian blur.
gausshva, gaussvva : moderngl.VertexArray
Vertex data for Gaussian blur.
2019-08-26 11:00:04 +02:00
edge : moderngl.Program
2019-05-23 11:04:40 +02:00
Processed executable code in GLSL for final combination
2019-08-23 04:47:53 +02:00
of the bloom effect with additional chromatic aberration
and barrel distortion.
combine : moderngl.VertexArray
Vertex data for final combination of the bloom effect.
fb, ping, pong : moderngl.Framebuffer
Frame buffers for bloom-effect post-processing.
last_time : float
2019-03-03 16:04:01 +01:00
timestamp in seconds of the previous frame.
2019-09-21 16:24:30 +02:00
fpses : Deque[float, ...]
2019-08-23 04:47:53 +02:00
FPS during the last 5 seconds to display the average.
2019-02-06 17:08:24 +01:00
"""
2019-08-26 11:00:04 +02:00
def __init__(self, address, camera, space, config):
2019-02-26 06:05:47 +01:00
# Create GLFW window
if not glfw.init(): raise RuntimeError('Failed to initialize GLFW')
glfw.window_hint(glfw.CLIENT_API, glfw.OPENGL_API)
glfw.window_hint(glfw.CONTEXT_CREATION_API, glfw.NATIVE_CONTEXT_API)
2019-02-26 06:05:47 +01:00
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
2019-07-30 12:59:51 +02:00
width, height = config.size
2019-08-23 04:47:53 +02:00
self.window = glfw.create_window(
width, height, 'axuy@{}:{}'.format(*address), None, None)
2019-02-26 06:05:47 +01:00
if not self.window:
glfw.terminate()
raise RuntimeError('Failed to create GLFW window')
2019-07-30 12:59:51 +02:00
self.key, self.mouse = config.key, config.mouse
2019-08-23 04:47:53 +02:00
self.fpses = deque()
2019-02-26 06:05:47 +01:00
# Attributes for event-handling
2019-09-21 16:43:37 +02:00
self.addr, self.camera, self.space = address, camera, space
self.picos, self.colors = {address: camera}, {address: randint(0, 5)}
self.last_time = glfw.get_time()
2019-02-26 06:05:47 +01:00
# Window's rendering and event-handling configuration
glfw.set_window_icon(self.window, 1, Image.open(abspath('icon.png')))
2019-02-26 06:05:47 +01:00
glfw.make_context_current(self.window)
2019-07-30 12:59:51 +02:00
glfw.swap_interval(config.vsync)
glfw.set_window_size_callback(self.window, self.resize)
2019-02-26 06:05:47 +01:00
glfw.set_input_mode(self.window, glfw.CURSOR, glfw.CURSOR_DISABLED)
glfw.set_input_mode(self.window, glfw.STICKY_KEYS, True)
2019-07-30 12:59:51 +02:00
self.mouspeed = config.mouspeed
glfw.set_cursor_pos_callback(self.window, self.look)
self.zmspeed, self.zmlvl = config.zmspeed, config.zmlvl
2019-02-26 06:05:47 +01:00
glfw.set_scroll_callback(self.window, self.zoom)
glfw.set_mouse_button_callback(self.window, self.shoot)
2019-02-26 06:05:47 +01:00
2019-07-15 12:11:40 +02:00
try:
if glfw.raw_mouse_motion_supported():
glfw.set_input_mode(self.window, glfw.RAW_MOUSE_MOTION, True)
except AttributeError:
2019-07-30 12:59:51 +02:00
warn(GLFW_VER_WARN, category=RuntimeWarning)
2019-07-15 12:11:40 +02:00
2019-02-26 06:05:47 +01:00
# Create OpenGL context
self.context = context = moderngl.create_context()
context.enable_only(moderngl.DEPTH_TEST)
2019-02-26 06:05:47 +01:00
# GLSL program and vertex array for map rendering
2019-02-26 06:05:47 +01:00
self.maprog = context.program(vertex_shader=MAP_VERTEX,
2019-09-04 12:02:39 +02:00
fragment_shader=MAP_FRAGMENT)
2019-09-21 16:43:37 +02:00
mapvb = context.buffer(mirror(space).tobytes())
2019-02-26 06:05:47 +01:00
self.mapva = context.simple_vertex_array(self.maprog, mapvb, 'in_vert')
# GLSL programs and vertex arrays for picos and shards rendering
2019-02-26 06:05:47 +01:00
pvb = [(context.buffer(TETRAVERTICES.tobytes()), '3f', 'in_vert')]
pib = context.buffer(TETRAINDECIES.tobytes())
svb = [(context.buffer(OCTOVERTICES.tobytes()), '3f', 'in_vert')]
sib = context.buffer(OCTOINDECIES.tobytes())
2019-08-26 11:00:04 +02:00
self.prog = context.program(vertex_shader=PICO_VERTEX,
2019-09-04 12:02:39 +02:00
geometry_shader=PICO_GEOMETRY,
fragment_shader=PICO_FRAGMENT)
2019-08-26 11:00:04 +02:00
self.pva = context.vertex_array(self.prog, pvb, pib)
self.sva = context.vertex_array(self.prog, svb, sib)
2019-02-06 17:08:24 +01:00
self.pfilter = context.simple_vertex_array(
context.program(vertex_shader=TEX_VERTEX,
fragment_shader=SAT_FRAGMENT),
context.buffer(QUAD), 'in_vert')
self.gaussh = context.program(vertex_shader=GAUSSH_VERTEX,
fragment_shader=GAUSS_FRAGMENT)
self.gaussh['width'].value = 256
self.gausshva = context.simple_vertex_array(
self.gaussh, context.buffer(QUAD), 'in_vert')
self.gaussv = context.program(vertex_shader=GAUSSV_VERTEX,
fragment_shader=GAUSS_FRAGMENT)
self.gaussv['height'].value = 256 * height / width
self.gaussvva = context.simple_vertex_array(
self.gaussv, context.buffer(QUAD), 'in_vert')
2019-08-26 11:00:04 +02:00
self.edge = context.program(vertex_shader=TEX_VERTEX,
fragment_shader=COMBINE_FRAGMENT)
self.edge['la'].value = 0
self.edge['tex'].value = 1
self.combine = context.simple_vertex_array(
2019-08-26 11:00:04 +02:00
self.edge, context.buffer(QUAD), 'in_vert')
size, table = (width, height), (256, height * 256 // width)
self.fb = context.framebuffer(context.texture(size, 4),
context.depth_renderbuffer(size))
self.fb.color_attachments[0].use(1)
self.ping = context.framebuffer(context.texture(table, 3))
self.pong = context.framebuffer(context.texture(table, 3))
def resize(self, window, width, height):
"""Update viewport on resize."""
context = self.context
context.viewport = 0, 0, width, height
self.gaussv['height'].value = 256 * height / width
self.fb.depth_attachment.release()
for fb in (self.fb, self.ping, self.pong):
for texture in fb.color_attachments: texture.release()
fb.release()
size, table = (width, height), (256, height * 256 // width)
self.fb = context.framebuffer(context.texture(size, 4),
context.depth_renderbuffer(size))
self.fb.color_attachments[0].use(1)
self.ping = context.framebuffer(context.texture(table, 3))
self.pong = context.framebuffer(context.texture(table, 3))
2019-07-30 12:59:51 +02:00
def look(self, window, xpos, ypos):
"""Look according to cursor position.
Present as a callback for GLFW CursorPos event.
"""
center = np.array(glfw.get_window_size(window)) / 2
glfw.set_cursor_pos(window, *center)
self.camera.rotate(*((center - [xpos, ypos]) * self.rotspeed))
2019-02-26 06:05:47 +01:00
def zoom(self, window, xoffset, yoffset):
"""Adjust FOV according to vertical scroll."""
2019-07-30 12:59:51 +02:00
self.zmlvl += yoffset * 2 / self.zmspeed
self.zmlvl = max(self.zmlvl, ZMIN)
self.zmlvl = min(self.zmlvl, ZMAX)
2019-02-06 17:08:24 +01:00
def shoot(self, window, button, action, mods):
"""Shoot on click.
Present as a callback for GLFW MouseButton event.
"""
2019-09-20 15:27:04 +02:00
if action == glfw.PRESS:
if button == self.mouse['1st']:
self.camera.shoot()
elif button == self.mouse['2nd']:
self.camera.shoot(backward=True)
@property
2019-07-15 12:11:40 +02:00
def width(self) -> int:
"""Viewport width."""
return self.context.viewport[2]
@property
2019-07-15 12:11:40 +02:00
def height(self) -> int:
"""Viewport height."""
return self.context.viewport[3]
2019-08-26 11:00:04 +02:00
@property
def health(self) -> float:
"""Camera relative health point."""
return self.camera.health
@property
2019-07-15 12:11:40 +02:00
def pos(self) -> np.float32:
2019-02-10 08:57:52 +01:00
"""Camera position in a NumPy array."""
2019-02-26 06:05:47 +01:00
return self.camera.pos
2019-08-23 04:47:53 +02:00
@property
def postr(self) -> str:
"""Pretty camera position representation."""
return '[{:4.1f} {:4.1f} {:3.1f}]'.format(*self.camera.pos)
@property
2019-07-15 12:11:40 +02:00
def right(self) -> np.float32:
2019-02-10 08:57:52 +01:00
"""Camera right direction."""
return self.camera.rot[0]
@property
2019-07-15 12:11:40 +02:00
def upward(self) -> np.float32:
2019-02-10 08:57:52 +01:00
"""Camera upward direction."""
return self.camera.rot[1]
@property
2019-07-15 12:11:40 +02:00
def forward(self) -> np.float32:
2019-02-10 08:57:52 +01:00
"""Camera forward direction."""
return self.camera.rot[2]
2019-02-06 17:08:24 +01:00
2019-02-26 06:05:47 +01:00
@property
def is_running(self) -> bool:
2019-02-26 06:05:47 +01:00
"""GLFW window status."""
return not glfw.window_should_close(self.window)
2019-07-30 12:59:51 +02:00
@property
def fov(self) -> float:
"""Horizontal field of view in degrees."""
return degrees(2 ** self.zmlvl)
@property
def rotspeed(self) -> float:
"""Camera rotational speed, calculated from FOV and mouse speed."""
return 2**self.zmlvl * self.mouspeed
@property
def visibility(self) -> np.float32:
"""Camera visibility."""
return np.float32(3240 / (self.fov + 240))
2019-03-03 16:04:01 +01:00
@property
2019-07-15 12:11:40 +02:00
def fps(self) -> float:
2019-03-03 16:04:01 +01:00
"""Currently rendered frames per second."""
return self.camera.fps
@fps.setter
def fps(self, fps):
self.camera.fps = fps
2019-08-23 04:47:53 +02:00
self.fpses.appendleft(fps)
@property
def fpstr(self) -> str:
"""Pretty string for displaying average FPS."""
# Average over 5 seconds, like how glxgears do it, but less efficient
while len(self.fpses) > mean(self.fpses) * 5 > 0: self.fpses.pop()
return '{} fps'.format(round(mean(self.fpses)))
def is_pressed(self, *keys) -> bool:
2019-02-26 06:05:47 +01:00
"""Return whether given keys are pressed."""
return any(glfw.get_key(self.window, k) == glfw.PRESS for k in keys)
2019-08-26 11:00:04 +02:00
def prender(self, obj, va, col, bright):
2019-02-26 06:05:47 +01:00
"""Render the obj and its images in bounded 3D space."""
rotation = Matrix44.from_matrix33(obj.rot).astype(np.float32).tobytes()
position = obj.pos.astype(np.float32).tobytes()
2019-08-26 11:00:04 +02:00
self.prog['rot'].write(rotation)
self.prog['pos'].write(position)
self.prog['color'].write(color(col, bright).tobytes())
va.render(moderngl.TRIANGLES)
2019-02-26 06:05:47 +01:00
def render_pico(self, pico):
"""Render pico and its images in bounded 3D space."""
2019-08-26 11:00:04 +02:00
self.prender(pico, self.pva, self.colors[pico.addr], pico.health)
def render_shard(self, shard):
"""Render shard and its images in bounded 3D space."""
2019-08-26 11:00:04 +02:00
self.prender(shard, self.sva,
self.colors[shard.addr], shard.power/SHARD_LIFE)
2019-03-03 16:04:01 +01:00
2019-08-26 11:00:04 +02:00
def add_pico(self, address):
"""Add picobot from given address."""
self.picos[address] = Picobot(address, self.space)
self.colors[address] = randint(0, 5)
2019-02-26 06:05:47 +01:00
def render(self):
"""Render the scene before post-processing."""
visibility = self.visibility
projection = Matrix44.perspective_projection(
self.fov, self.width/self.height, 3E-3, visibility)
2019-02-10 08:57:52 +01:00
view = Matrix44.look_at(self.pos, self.pos + self.forward, self.upward)
2019-02-26 06:05:47 +01:00
vp = (view @ projection).astype(np.float32).tobytes()
2019-02-10 08:57:52 +01:00
# Render map
2019-05-23 11:04:40 +02:00
self.maprog['visibility'].value = visibility
2019-02-26 06:05:47 +01:00
self.maprog['mvp'].write(vp)
2019-02-10 08:57:52 +01:00
self.mapva.render(moderngl.TRIANGLES)
2019-02-26 06:05:47 +01:00
# Render picos and shards
2019-08-26 11:00:04 +02:00
self.prog['visibility'].value = visibility
self.prog['camera'].write(self.pos.tobytes())
self.prog['vp'].write(vp)
picos = list(self.picos.values())
for pico in picos:
shards = {}
for index, shard in pico.shards.items():
shard.update(self.fps, picos)
if not shard.power: continue
self.render_shard(shard)
shards[index] = shard
pico.shards = shards
if pico is not self.camera: self.render_pico(pico)
2019-09-21 16:43:37 +02:00
def draw(self):
"""Render and post-process."""
# Render to framebuffer
self.fb.use()
self.fb.clear()
self.render()
self.fb.color_attachments[0].use()
self.ping.use()
self.ping.clear()
self.pfilter.render(moderngl.TRIANGLES)
self.ping.color_attachments[0].use()
2019-09-21 16:43:37 +02:00
# Gaussian blur
self.pong.use()
self.pong.clear()
self.gausshva.render(moderngl.TRIANGLES)
self.pong.color_attachments[0].use()
self.ping.use()
self.ping.clear()
self.gaussvva.render(moderngl.TRIANGLES)
self.ping.color_attachments[0].use()
2019-09-21 16:43:37 +02:00
# Combine for glow effect, chromatic aberration and barrel distortion
self.context.screen.use()
self.context.clear()
2019-08-29 16:55:43 +02:00
if self.camera.dead:
abrtn = ABRTN_MAX
else:
abrtn = min(ABRTN_MAX, (self.fov*self.health) ** -CONWAY)
self.edge['abrtn'].value = abrtn
2019-08-26 11:00:04 +02:00
self.edge['zoom'].value = (self.zmlvl + 1.0) / 100
self.combine.render(moderngl.TRIANGLES)
2019-02-26 06:05:47 +01:00
glfw.swap_buffers(self.window)
2019-09-21 16:43:37 +02:00
def update(self):
"""Handle input, update GLSL programs and render the map."""
# Update instantaneous FPS
next_time = glfw.get_time()
self.fps = 1 / (next_time-self.last_time)
self.last_time = next_time
# Character movements
right, upward, forward = 0, 0, 0
if self.is_pressed(self.key['forward']): forward += 1
if self.is_pressed(self.key['backward']): forward -= 1
if self.is_pressed(self.key['left']): right -= 1
if self.is_pressed(self.key['right']): right += 1
self.camera.update(right, upward, forward)
self.draw()
glfw.set_window_title(self.window, '{} - axuy@{}:{} ({})'.format(
self.postr, *self.addr, self.fpstr))
2019-02-26 06:05:47 +01:00
glfw.poll_events()
def close(self):
"""Close window."""
glfw.terminate()