Optimize a little bit

This commit is contained in:
Nguyễn Gia Phong 2019-02-10 14:57:52 +07:00
parent de0d42d0c9
commit 49b8f97d6d
6 changed files with 163 additions and 76 deletions

View File

@ -21,12 +21,12 @@ from random import shuffle
import glfw import glfw
import moderngl import moderngl
from .misc import hex2f4 from .misc import color
from .view import View from .view import View
FOV_INIT = 90
FOV_MIN = 30 FOV_MIN = 30
FOV_MAX = 120 FOV_MAX = 120
FOV_INIT = (FOV_MIN+FOV_MAX) / 2
def main(): def main():
@ -52,6 +52,7 @@ def main():
context = moderngl.create_context() context = moderngl.create_context()
context.enable(moderngl.BLEND) context.enable(moderngl.BLEND)
context.enable(moderngl.DEPTH_TEST)
fov = FOV_INIT fov = FOV_INIT
def zoom(window, xoffset, yoffset): def zoom(window, xoffset, yoffset):
@ -85,7 +86,7 @@ def main():
width, height = glfw.get_window_size(window) width, height = glfw.get_window_size(window)
context.viewport = 0, 0, width, height context.viewport = 0, 0, width, height
context.clear(*hex2f4('2e3436')) context.clear(*color('Background'))
view.render(width, height, fov) view.render(width, height, fov)
glfw.swap_buffers(window) glfw.swap_buffers(window)

View File

@ -16,13 +16,34 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with Axuy. If not, see <https://www.gnu.org/licenses/>. # along with Axuy. If not, see <https://www.gnu.org/licenses/>.
from itertools import chain, combinations_with_replacement, permutations
import numpy import numpy
import pkg_resources import pkg_resources
TANGO = {'Background': '2e3436',
'Butter': 'fce94f',
'Orange': 'fcaf3e',
'Chocolate': 'e9b96e',
'Chameleon': '8ae234',
'Sky Blue': '729fcf',
'Plum': 'ad7fa8',
'Scarlet Red': 'ef2929',
'Aluminium': 'eeeeec'}
def hex2f4(hex_color):
"""Return numpy float32 array of RGB colors from given hex_color.""" def color(name):
return numpy.float32([i / 255 for i in bytes.fromhex(hex_color)]) """Return numpy float32 array of RGB colors from color name."""
return numpy.float32([i / 255 for i in bytes.fromhex(TANGO[name])])
def neighbors(x, y, z):
"""Return a generator of coordinates of images point (x, y, z)
in neighbor universes.
"""
for i, j, k in set(chain.from_iterable(
map(permutations, combinations_with_replacement((-1, 0, 1), 3)))):
yield x + i*12, y + j*12, z + k*9
def resource_filename(resource_name): def resource_filename(resource_name):

View File

@ -16,41 +16,86 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with Axuy. If not, see <https://www.gnu.org/licenses/>. # along with Axuy. If not, see <https://www.gnu.org/licenses/>.
from dataclasses import dataclass __doc__ = 'Axuy module for character class'
from math import pi
from random import random
import glfw import glfw
import numpy as np import numpy as np
from pyrr import matrix33 from pyrr import matrix33
BASE = np.float32([[1, 0, 0], [0, 1, 0], [0, 0, -1]])
SPEED = 2 SPEED = 2
MOUSE_SPEED = 1 MOUSE_SPEED = 1/8
@dataclass
class Picobot: class Picobot:
x: float """Game character.
y: float
z: float
space: np.ndarray
rotation: np.ndarray = np.float32([[1, 0, 0], [0, 1, 0], [0, 0, -1]])
fps: float = 60.0
@property Parameters
def pos(self): ----------
"""Return position in a numpy array.""" space : np.ndarray of shape (12, 12, 9) of bools
return np.float32([self.x, self.y, self.z]) 3D array of occupied space.
pos : iterable of length 3 of floats
Position.
rotation : np.ndarray of shape (3, 3) of np.float32
Rotational matrix.
@pos.setter Attributes
def pos(self, postion): ----------
"""Set camera postion.""" space : np.ndarray of shape (12, 12, 9) of bools
self.x, self.y, self.z = postion 3D array of occupied space.
x, y, z : floats
Position.
rotation : np.ndarray of shape (3, 3) of np.float32
Rotational matrix.
fps : float
Currently rendered frames per second.
"""
def __init__(self, space, pos=None, rotation=None):
self.space = space
if pos is None:
x, y, z = random()*12, random()*12, random()*9
while not self.empty(x, y, z):
x, y, z = random()*12, random()*12, random()*9
self.x, self.y, self.z = x, y, z
else:
self.x, self.y, self.z = pos
if rotation is None:
self.rotation = BASE
self.rotate(random()*pi*2, random()*pi*2)
else:
self.rotation = rotation
self.fps = 60.0
def empty(self, x, y, z) -> bool:
"""Return weather a Picobot can be placed at (x, y, z)."""
if self.space[int((x-1/4) % 12)][int(y % 12)][int(z % 9)]: return False
if self.space[int((x+1/4) % 12)][int(y % 12)][int(z % 9)]: return False
if self.space[int(x % 12)][int((y-1/4) % 12)][int(z % 9)]: return False
if self.space[int(x % 12)][int((y+1/4) % 12)][int(z % 9)]: return False
if self.space[int(x % 12)][int(y % 12)][int((z-1/4) % 9)]: return False
if self.space[int(x % 12)][int(y % 12)][int((z+1/4) % 9)]: return False
return True
def rotate(self, yaw, pitch):
"""Rotate yaw radians around y-axis
and pitch radians around x-axis.
"""
self.rotation = (matrix33.create_from_x_rotation(pitch)
@ matrix33.create_from_y_rotation(yaw) @ self.rotation)
def move(self, right=0, upward=0, forward=0): def move(self, right=0, upward=0, forward=0):
"""Move in the given direction.""" """Try to move in the given direction."""
dr = [right, upward, forward] @ self.rotation / self.fps * SPEED dr = [right, upward, forward] @ self.rotation / self.fps * SPEED
x, y, z = self.pos + dr x, y, z = [self.x, self.y, self.z] + dr
if not self.space[int(x%12)][int(y%12)][int(z%9)]: self.pos += dr if self.empty(x, self.y, self.z): self.x = x % 12
self.pos = self.x % 12, self.y % 12, self.z % 9 if self.empty(self.x, y, self.z): self.y = y % 12
if self.empty(self.x, self.y, z): self.z = z % 9
def look(self, window, xpos, ypos): def look(self, window, xpos, ypos):
"""Look according to cursor position. """Look according to cursor position.
@ -58,6 +103,4 @@ class Picobot:
Present as a callback for GLFW CursorPos event. Present as a callback for GLFW CursorPos event.
""" """
center = np.float32(glfw.get_window_size(window)) / 2 center = np.float32(glfw.get_window_size(window)) / 2
yaw, pitch = (center - [xpos, ypos]) / self.fps * MOUSE_SPEED self.rotate(*((center - [xpos, ypos]) / self.fps * MOUSE_SPEED))
self.rotation = (matrix33.create_from_y_rotation(yaw) @
matrix33.create_from_x_rotation(pitch) @ self.rotation)

View File

@ -1,9 +1,15 @@
#version 330 #version 330
uniform vec3 bg;
uniform vec3 color; uniform vec3 color;
in float alpha;
in float depth;
out vec4 f_color; out vec4 f_color;
void main() { void main() {
f_color = vec4(color, alpha); if (depth < 1) {
f_color = vec4(bg * depth + color * (1 - depth), 1.0);
} else {
f_color = vec4(bg, 1.0);
}
} }

View File

@ -1,11 +1,13 @@
#version 330 #version 330
uniform mat4 mvp; uniform mat4 mvp;
uniform vec3 eye; uniform vec3 camera;
uniform float visibility;
in vec3 in_vert; in vec3 in_vert;
out float alpha; out float depth;
void main() { void main() {
gl_Position = mvp * vec4(in_vert, 1.0); gl_Position = mvp * vec4(in_vert, 1.0);
alpha = 1 - distance(eye, in_vert) / 4; depth = distance(camera, in_vert) / visibility;
} }

View File

@ -16,26 +16,39 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with Axuy. If not, see <https://www.gnu.org/licenses/>. # along with Axuy. If not, see <https://www.gnu.org/licenses/>.
from itertools import chain, combinations_with_replacement, permutations __doc__ = 'Axuy module for map class'
from random import random
from itertools import product, starmap
from operator import add
import moderngl import moderngl
import numpy as np import numpy as np
from pyrr import Matrix44 from pyrr import Matrix44
from .misc import hex2f4, resource_filename from .misc import color, neighbors, resource_filename
from .pico import Picobot from .pico import Picobot
# map.npy is generated by ../tools/mapgen # map.npy is generated by ../tools/mapgen
SPACE = np.load(resource_filename('map.npy')) SPACE = np.load(resource_filename('map.npy'))
OXY = np.float32([[0, 0, 0], [1, 0, 0], [1, 1, 0], OXY = np.float32([[0, 0, 0], [1, 0, 0], [1, 1, 0],
[1, 1, 0], [0, 1, 0], [0, 0, 0]]) [1, 1, 0], [0, 1, 0], [0, 0, 0]])
OYZ = np.float32([[0, 0, 0], [0, 1, 0], [0, 1, 1], OYZ = np.float32([[0, 0, 0], [0, 1, 0], [0, 1, 1],
[0, 1, 1], [0, 0, 1], [0, 0, 0]]) [0, 1, 1], [0, 0, 1], [0, 0, 0]])
OZX = np.float32([[0, 0, 0], [1, 0, 0], [1, 0, 1], OZX = np.float32([[0, 0, 0], [1, 0, 0], [1, 0, 1],
[1, 0, 1], [0, 0, 1], [0, 0, 0]]) [1, 0, 1], [0, 0, 1], [0, 0, 0]])
NEIGHBORS = set(chain.from_iterable(map( OCTAHEDRON = np.float32([[+1/4, 0, 0], [0, +1/4, 0], [0, 0, +1/4],
permutations, combinations_with_replacement((-1, 0, 1), 3)))) [+1/4, 0 ,0], [0, +1/4, 0], [0, 0, -1/4],
[+1/4, 0 ,0], [0, -1/4, 0], [0, 0, +1/4],
[+1/4, 0 ,0], [0, -1/4, 0], [0, 0, -1/4],
[-1/4, 0 ,0], [0, +1/4, 0], [0, 0, +1/4],
[-1/4, 0 ,0], [0, +1/4, 0], [0, 0, -1/4],
[-1/4, 0 ,0], [0, -1/4, 0], [0, 0, +1/4],
[-1/4, 0 ,0], [0, -1/4, 0], [0, 0, -1/4]])
TETRAHEDRON = np.float32([[+1, +1, +1], [+1, -1, -1], [-1, +1, -1],
[-1, -1, +1], [+1, -1, -1], [-1, +1, -1],
[+1, +1, +1], [-1, -1, +1], [-1, +1, -1],
[+1, +1, +1], [-1, -1, +1], [+1, -1, -1]])
with open(resource_filename('space.vert')) as f: VERTEX_SHADER = f.read() with open(resource_filename('space.vert')) as f: VERTEX_SHADER = f.read()
with open(resource_filename('space.frag')) as f: FRAGMENT_SHADER = f.read() with open(resource_filename('space.frag')) as f: FRAGMENT_SHADER = f.read()
@ -46,76 +59,77 @@ class View:
Parameters Parameters
---------- ----------
mapid : iterable of ints mapid : iterable of length 48 of ints
order of nodes to sort map.npy. order of nodes to sort map.npy.
context : moderngl.Context context : moderngl.Context
OpenGL context from which ModernGL objects are created. OpenGL context from which ModernGL objects are created.
Attributes Attributes
---------- ----------
space : np.ndarray of bools space : np.ndarray of shape (12, 12, 9) of bools
3D array of occupied space. 3D array of occupied space.
prog : moderngl.Program prog : moderngl.Program
Processed executable code in GLSL. Processed executable code in GLSL.
vao : moderngl.VertexArray mapva : moderngl.VertexArray
Vertex data of the map. Vertex data of the map.
camera : Picobot
Protagonist whose view is the camera.
""" """
def __init__(self, mapid, context): def __init__(self, mapid, context):
space = np.stack([SPACE[i] for i in mapid]).reshape(4, 4, 3, 3, 3, 3) space = np.stack([SPACE[i] for i in mapid]).reshape(4, 4, 3, 3, 3, 3)
oxy, oyz, ozx = set(), set(), set() self.space = np.zeros([12, 12, 9], dtype=bool)
self.space = np.zeros([12, 12, 9]) for (i, j, k, x, y, z), occupied in np.ndenumerate(space):
for (a, b, c, d, e, f), occupied in np.ndenumerate(space): if occupied: self.space[i*3 + x][j*3 + y][k*3 + z] = 1
if occupied:
x, y, z = a*3 + d, b*3 + e, c*3 + f
i, j, k = (x+1) % 12, (y+1) % 12, (z+1) % 9
for tx, ty, tz in NEIGHBORS:
xt, yt, zt = x + tx*12, y + ty*12, z + tz*9
it, jt, kt = i + tx*12, j + ty*12, k + tz*9
oxy.update(((xt, yt, zt), (xt, yt, kt)))
oyz.update(((xt, yt, zt), (it, yt, zt)))
ozx.update(((xt, yt, zt), (xt, jt, zt)))
self.space[x][y][z] = 1
vertices = [] vertices = []
for i in oxy: vertices.extend(i+j for j in OXY) for (x, y, z), occupied in np.ndenumerate(self.space):
for i in oyz: vertices.extend(i+j for j in OYZ) if self.space[x][y][z-1] ^ occupied:
for i in ozx: vertices.extend(i+j for j in OZX) vertices.extend(starmap(add, product(neighbors(x, y, z), OXY)))
if self.space[x-1][y][z] ^ occupied:
vertices.extend(starmap(add, product(neighbors(x, y, z), OYZ)))
if self.space[x][y-1][z] ^ occupied:
vertices.extend(starmap(add, product(neighbors(x, y, z), OZX)))
self.prog = context.program(vertex_shader=VERTEX_SHADER, self.prog = context.program(vertex_shader=VERTEX_SHADER,
fragment_shader=FRAGMENT_SHADER) fragment_shader=FRAGMENT_SHADER)
self.prog['color'].write(hex2f4('eeeeec').tobytes()) self.prog['bg'].write(color('Background').tobytes())
vbo = context.buffer(np.stack(vertices).astype('f4').tobytes()) mapvb = context.buffer(np.stack(vertices).astype(np.float32).tobytes())
self.vao = context.simple_vertex_array(self.prog, vbo, 'in_vert') self.mapva = context.simple_vertex_array(self.prog, mapvb, 'in_vert')
x, y, z = random()*12, random()*12, random()*9 self.camera = Picobot(self.space)
while self.space[int(x)][int(y)][int(z)]:
x, y, z = random()*12, random()*12, random()*9
self.camera = Picobot(x, y, z, self.space)
@property @property
def pos(self): def pos(self):
"""Return camera position in a numpy array.""" """Camera position in a NumPy array."""
return self.camera.pos return np.float32([self.camera.x, self.camera.y, self.camera.z])
@property @property
def right(self): def right(self):
"""Return camera right direction.""" """Camera right direction."""
return self.camera.rotation[0] return self.camera.rotation[0]
@property @property
def upward(self): def upward(self):
"""Return camera upward direction.""" """Camera upward direction."""
return self.camera.rotation[1] return self.camera.rotation[1]
@property @property
def forward(self): def forward(self):
"""Return camera forward direction.""" """Camera forward direction."""
return self.camera.rotation[2] return self.camera.rotation[2]
def render(self, width, height, fov): def render(self, width, height, fov):
"""Render the map.""" """Render the map."""
proj = Matrix44.perspective_projection(fov, width/height, 0.0001, 4) visibility = 360 / fov
look = Matrix44.look_at(self.pos, self.pos + self.forward, self.upward) self.prog['visibility'].write(np.float32(visibility).tobytes())
self.prog['mvp'].write((proj*look).astype(np.float32).tobytes()) self.prog['camera'].write(np.float32(self.pos).tobytes())
self.prog['eye'].write(np.float32(self.pos).tobytes())
self.vao.render(moderngl.TRIANGLES) projection = Matrix44.perspective_projection(fov, width/height,
9e-6, visibility)
view = Matrix44.look_at(self.pos, self.pos + self.forward, self.upward)
vp = view @ projection
self.prog['mvp'].write(vp.astype(np.float32).tobytes())
self.prog['color'].write(color('Aluminium').tobytes())
self.mapva.render(moderngl.TRIANGLES)